summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/50-marco-desktop-key.xml.in20
-rw-r--r--src/50-marco-key.xml.in269
-rw-r--r--src/Makefile.am223
-rw-r--r--src/compositor/compositor-private.h54
-rw-r--r--src/compositor/compositor-xrender.c3078
-rw-r--r--src/compositor/compositor-xrender.h31
-rw-r--r--src/compositor/compositor.c159
-rw-r--r--src/core/async-getprop.c680
-rw-r--r--src/core/async-getprop.h67
-rw-r--r--src/core/atomnames.h166
-rw-r--r--src/core/bell.c397
-rw-r--r--src/core/bell.h108
-rw-r--r--src/core/boxes.c1926
-rw-r--r--src/core/constraints.c1382
-rw-r--r--src/core/constraints.h48
-rw-r--r--src/core/core.c779
-rw-r--r--src/core/delete.c266
-rw-r--r--src/core/display-private.h513
-rw-r--r--src/core/display.c5355
-rw-r--r--src/core/edge-resistance.c1277
-rw-r--r--src/core/edge-resistance.h48
-rw-r--r--src/core/effects.c735
-rw-r--r--src/core/effects.h170
-rw-r--r--src/core/errors.c288
-rw-r--r--src/core/eventqueue.c184
-rw-r--r--src/core/eventqueue.h40
-rw-r--r--src/core/frame-private.h88
-rw-r--r--src/core/frame.c421
-rw-r--r--src/core/group-private.h43
-rw-r--r--src/core/group-props.c234
-rw-r--r--src/core/group-props.h37
-rw-r--r--src/core/group.c274
-rw-r--r--src/core/group.h53
-rw-r--r--src/core/iconcache.c849
-rw-r--r--src/core/iconcache.h79
-rw-r--r--src/core/keybindings.c3352
-rw-r--r--src/core/keybindings.h60
-rw-r--r--src/core/main.c673
-rw-r--r--src/core/marco-Xatomtype.h136
-rw-r--r--src/core/place.c932
-rw-r--r--src/core/place.h37
-rw-r--r--src/core/prefs.c2794
-rw-r--r--src/core/schema-bindings.c195
-rw-r--r--src/core/screen-private.h226
-rw-r--r--src/core/screen.c2815
-rw-r--r--src/core/session.c1831
-rw-r--r--src/core/session.h91
-rw-r--r--src/core/stack.c1661
-rw-r--r--src/core/stack.h402
-rw-r--r--src/core/testasyncgetprop.c497
-rw-r--r--src/core/testboxes.c1416
-rw-r--r--src/core/util.c641
-rw-r--r--src/core/window-private.h640
-rw-r--r--src/core/window-props.c1553
-rw-r--r--src/core/window-props.h129
-rw-r--r--src/core/window.c8178
-rw-r--r--src/core/workspace.c1038
-rw-r--r--src/core/workspace.h113
-rw-r--r--src/core/xprops.c1238
-rw-r--r--src/include/all-keybindings.h386
-rw-r--r--src/include/boxes.h290
-rw-r--r--src/include/common.h300
-rw-r--r--src/include/compositor.h76
-rw-r--r--src/include/core.h208
-rw-r--r--src/include/display.h48
-rw-r--r--src/include/errors.h51
-rw-r--r--src/include/frame.h31
-rw-r--r--src/include/main.h43
-rw-r--r--src/include/prefs.h233
-rw-r--r--src/include/resizepopup.h47
-rw-r--r--src/include/screen.h48
-rw-r--r--src/include/tabpopup.h67
-rw-r--r--src/include/types.h31
-rw-r--r--src/include/ui.h209
-rw-r--r--src/include/util.h136
-rw-r--r--src/include/window.h39
-rw-r--r--src/include/xprops.h227
-rw-r--r--src/libmarco-private.pc.in12
-rw-r--r--src/marco-wm.desktop.in20
-rw-r--r--src/marco.desktop.in17
-rw-r--r--src/marco.schemas.in.in573
-rw-r--r--src/themes/ClearlooksRe/metacity-theme-1.xml1013
-rw-r--r--src/themes/Dopple-Left/metacity-theme-1.xml1135
-rw-r--r--src/themes/Dopple/metacity-theme-1.xml1135
-rw-r--r--src/themes/DustBlue/button_close_normal.pngbin0 -> 3830 bytes
-rw-r--r--src/themes/DustBlue/button_close_prelight.pngbin0 -> 1393 bytes
-rw-r--r--src/themes/DustBlue/button_close_pressed.pngbin0 -> 4087 bytes
-rw-r--r--src/themes/DustBlue/button_max_normal.pngbin0 -> 3185 bytes
-rw-r--r--src/themes/DustBlue/button_max_prelight.pngbin0 -> 1326 bytes
-rw-r--r--src/themes/DustBlue/button_max_pressed.pngbin0 -> 4029 bytes
-rw-r--r--src/themes/DustBlue/button_menu_normal.pngbin0 -> 3450 bytes
-rw-r--r--src/themes/DustBlue/button_menu_prelight.pngbin0 -> 4041 bytes
-rw-r--r--src/themes/DustBlue/button_menu_pressed.pngbin0 -> 4065 bytes
-rw-r--r--src/themes/DustBlue/button_min_normal.pngbin0 -> 3152 bytes
-rw-r--r--src/themes/DustBlue/button_min_prelight.pngbin0 -> 1413 bytes
-rw-r--r--src/themes/DustBlue/button_min_pressed.pngbin0 -> 4093 bytes
-rw-r--r--src/themes/DustBlue/menu.pngbin0 -> 164 bytes
-rw-r--r--src/themes/DustBlue/metacity-theme-1.xml409
-rw-r--r--src/themes/Makefile.am53
-rw-r--r--src/themes/Spidey-Left/metacity-theme-1.xml1086
-rw-r--r--src/themes/Spidey/metacity-theme-1.xml1086
-rw-r--r--src/themes/Splint-Left/metacity-theme-1.xml802
-rw-r--r--src/themes/Splint/metacity-theme-1.xml802
-rw-r--r--src/themes/WinMe/close_normal.pngbin0 -> 256 bytes
-rw-r--r--src/themes/WinMe/close_normal_small.pngbin0 -> 223 bytes
-rw-r--r--src/themes/WinMe/close_pressed.pngbin0 -> 256 bytes
-rw-r--r--src/themes/WinMe/close_pressed_small.pngbin0 -> 219 bytes
-rw-r--r--src/themes/WinMe/maximize_normal.pngbin0 -> 220 bytes
-rw-r--r--src/themes/WinMe/maximize_pressed.pngbin0 -> 241 bytes
-rw-r--r--src/themes/WinMe/metacity-theme-1.xml375
-rw-r--r--src/themes/WinMe/minimize_normal.pngbin0 -> 213 bytes
-rw-r--r--src/themes/WinMe/minimize_pressed.pngbin0 -> 216 bytes
-rw-r--r--src/themes/WinMe/restore_normal.pngbin0 -> 235 bytes
-rw-r--r--src/themes/WinMe/restore_pressed.pngbin0 -> 257 bytes
-rw-r--r--src/themes/eOS/close.pngbin0 -> 1031 bytes
-rw-r--r--src/themes/eOS/close_unfocused.pngbin0 -> 775 bytes
-rw-r--r--src/themes/eOS/close_unfocused_over.pngbin0 -> 1031 bytes
-rw-r--r--src/themes/eOS/maximize.pngbin0 -> 845 bytes
-rw-r--r--src/themes/eOS/maximize_unfocused.pngbin0 -> 775 bytes
-rw-r--r--src/themes/eOS/maximize_unfocused_over.pngbin0 -> 845 bytes
-rw-r--r--src/themes/eOS/menu.pngbin0 -> 775 bytes
-rw-r--r--src/themes/eOS/menu_prelight.pngbin0 -> 755 bytes
-rw-r--r--src/themes/eOS/metacity-theme-1.xml537
-rw-r--r--src/themes/eOS/minimize.pngbin0 -> 800 bytes
-rw-r--r--src/themes/eOS/minimize_unfocused.pngbin0 -> 775 bytes
-rw-r--r--src/themes/eOS/minimize_unfocused_over.pngbin0 -> 800 bytes
-rw-r--r--src/themes/eOS/trough_left.pngbin0 -> 322 bytes
-rw-r--r--src/themes/eOS/trough_left_unfocused.pngbin0 -> 324 bytes
-rw-r--r--src/themes/eOS/trough_middle.pngbin0 -> 188 bytes
-rw-r--r--src/themes/eOS/trough_middle_unfocused.pngbin0 -> 195 bytes
-rw-r--r--src/themes/eOS/trough_right.pngbin0 -> 330 bytes
-rw-r--r--src/themes/eOS/trough_right_unfocused.pngbin0 -> 335 bytes
-rw-r--r--src/themes/eOS/unmaximize.pngbin0 -> 845 bytes
-rw-r--r--src/themes/eOS/unmaximize_unfocused.pngbin0 -> 775 bytes
-rw-r--r--src/themes/eOS/unmaximize_unfocused_over.pngbin0 -> 845 bytes
-rw-r--r--src/tools/Makefile.am33
-rw-r--r--src/tools/marco-grayscale.c109
-rw-r--r--src/tools/marco-mag.c278
-rw-r--r--src/tools/marco-message.c187
-rw-r--r--src/tools/marco-window-demo.c1016
-rw-r--r--src/tools/marco-window-demo.pngbin0 -> 3453 bytes
-rw-r--r--src/ui/draw-workspace.c229
-rw-r--r--src/ui/draw-workspace.h61
-rw-r--r--src/ui/fixedtip.c133
-rw-r--r--src/ui/fixedtip.h69
-rw-r--r--src/ui/frames.c2940
-rw-r--r--src/ui/frames.h163
-rw-r--r--src/ui/gradient.c842
-rw-r--r--src/ui/gradient.h65
-rw-r--r--src/ui/menu.c509
-rw-r--r--src/ui/menu.h51
-rw-r--r--src/ui/metaaccellabel.c456
-rw-r--r--src/ui/metaaccellabel.h106
-rw-r--r--src/ui/preview-widget.c601
-rw-r--r--src/ui/preview-widget.h87
-rw-r--r--src/ui/resizepopup.c217
-rw-r--r--src/ui/tabpopup.c967
-rw-r--r--src/ui/testgradient.c336
-rw-r--r--src/ui/theme-parser.c4212
-rw-r--r--src/ui/theme-parser.h32
-rw-r--r--src/ui/theme-viewer.c1338
-rw-r--r--src/ui/theme.c6652
-rw-r--r--src/ui/theme.h1190
-rw-r--r--src/ui/themewidget.c183
-rw-r--r--src/ui/themewidget.h78
-rw-r--r--src/ui/ui.c1117
-rw-r--r--src/wm-tester/Makefile.am25
-rw-r--r--src/wm-tester/Makefile.in574
-rw-r--r--src/wm-tester/focus-window.c37
-rw-r--r--src/wm-tester/main.c245
-rw-r--r--src/wm-tester/test-gravity.c308
-rw-r--r--src/wm-tester/test-resizing.c257
-rw-r--r--src/wm-tester/test-size-hints.c136
173 files changed, 88353 insertions, 0 deletions
diff --git a/src/50-marco-desktop-key.xml.in b/src/50-marco-desktop-key.xml.in
new file mode 100644
index 00000000..5705607d
--- /dev/null
+++ b/src/50-marco-desktop-key.xml.in
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<KeyListEntries _name="Desktop" wm_name="Marco" package="marco">
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/panel_run_dialog" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/panel_main_menu" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/run_command_screenshot" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/run_command_window_screenshot" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/run_command_terminal" />
+
+</KeyListEntries>
+
diff --git a/src/50-marco-key.xml.in b/src/50-marco-key.xml.in
new file mode 100644
index 00000000..62c98236
--- /dev/null
+++ b/src/50-marco-key.xml.in
@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<KeyListEntries _name="Window Management" wm_name="Marco" package="marco">
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/activate_window_menu" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/toggle_fullscreen" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/toggle_maximized" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/maximize" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/unmaximize" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/toggle_shaded" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/close" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/minimize" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/begin_move" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/begin_resize" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/toggle_on_all_workspaces"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/raise_or_lower" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/raise" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/lower" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/maximize_vertically" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/maximize_horizontally" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_1"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_2"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_3"
+ value="2"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_4"
+ value="3"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_5"
+ value="4"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_6"
+ value="5"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_7"
+ value="6"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_8"
+ value="7"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_9"
+ value="8"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_10"
+ value="9"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_11"
+ value="10"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_12"
+ value="11"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_left"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_right"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_up"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/window_keybindings/move_to_workspace_down"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_windows" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_group" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_panels" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/cycle_windows" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/cycle_group" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/cycle_panels" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/show_desktop" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_1"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_2"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_3"
+ value="2"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_4"
+ value="3"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_5"
+ value="4"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_6"
+ value="5"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_7"
+ value="6"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_8"
+ value="7"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_9"
+ value="8"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_10"
+ value="9"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_11"
+ value="10"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_12"
+ value="11"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_left"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_right"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_up"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+ <KeyListEntry
+ name="/apps/marco/global_keybindings/switch_to_workspace_down"
+ value="1"
+ key="/apps/marco/general/num_workspaces"
+ comparison="gt" />
+
+</KeyListEntries>
+
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..52223298
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,223 @@
+lib_LTLIBRARIES = libmarco-private.la
+
+SUBDIRS=wm-tester tools themes
+
+INCLUDES = \
+ -I$(srcdir)/include \
+ -DMARCO_LIBEXECDIR=\"$(libexecdir)\" \
+ -DHOST_ALIAS=\"@HOST_ALIAS@\" \
+ -DMARCO_LOCALEDIR=\"$(prefix)/@DATADIRNAME@/locale\" \
+ -DMARCO_PKGDATADIR=\"$(pkgdatadir)\" \
+ -DMARCO_DATADIR=\"$(datadir)\" \
+ -DG_LOG_DOMAIN=\"marco\" \
+ -DSN_API_NOT_YET_FROZEN=1 \
+ @MARCO_CFLAGS@
+
+marco_SOURCES = \
+ core/async-getprop.c \
+ core/async-getprop.h \
+ core/atomnames.h \
+ core/bell.c \
+ core/bell.h \
+ core/boxes.c \
+ include/boxes.h \
+ compositor/compositor.c \
+ compositor/compositor-private.h \
+ compositor/compositor-xrender.c \
+ compositor/compositor-xrender.h \
+ include/compositor.h \
+ core/constraints.c \
+ core/constraints.h \
+ core/core.c \
+ core/delete.c \
+ core/display.c \
+ core/display-private.h \
+ include/display.h \
+ ui/draw-workspace.c \
+ ui/draw-workspace.h \
+ core/edge-resistance.c \
+ core/edge-resistance.h \
+ core/effects.c \
+ core/effects.h \
+ core/errors.c \
+ include/errors.h \
+ core/eventqueue.c \
+ core/eventqueue.h \
+ core/frame.c \
+ core/frame-private.h \
+ include/frame.h \
+ ui/gradient.c \
+ ui/gradient.h \
+ core/group-private.h \
+ core/group-props.c \
+ core/group-props.h \
+ core/group.c \
+ core/group.h \
+ core/iconcache.c \
+ core/iconcache.h \
+ core/keybindings.c \
+ core/keybindings.h \
+ core/main.c \
+ include/main.h \
+ core/marco-Xatomtype.h \
+ core/place.c \
+ core/place.h \
+ core/prefs.c \
+ include/prefs.h \
+ core/screen.c \
+ core/screen-private.h \
+ include/screen.h \
+ include/types.h \
+ core/session.c \
+ core/session.h \
+ core/stack.c \
+ core/stack.h \
+ core/util.c \
+ include/util.h \
+ core/window-props.c \
+ core/window-props.h \
+ core/window.c \
+ core/window-private.h \
+ include/window.h \
+ core/workspace.c \
+ core/workspace.h \
+ core/xprops.c \
+ include/xprops.h \
+ include/common.h \
+ include/core.h \
+ include/ui.h \
+ ui/fixedtip.c \
+ ui/fixedtip.h \
+ ui/frames.c \
+ ui/frames.h \
+ ui/menu.c \
+ ui/menu.h \
+ ui/metaaccellabel.c \
+ ui/metaaccellabel.h \
+ ui/resizepopup.c \
+ include/resizepopup.h \
+ ui/tabpopup.c \
+ include/tabpopup.h \
+ ui/theme-parser.c \
+ ui/theme-parser.h \
+ ui/theme.c \
+ ui/theme.h \
+ ui/themewidget.c \
+ ui/themewidget.h \
+ ui/ui.c \
+ include/all-keybindings.h
+
+# by setting libmarco_private_la_CFLAGS, the files shared with
+# marco proper will be compiled with different names.
+libmarco_private_la_CFLAGS =
+libmarco_private_la_SOURCES = \
+ core/boxes.c \
+ include/boxes.h \
+ ui/gradient.c \
+ ui/gradient.h \
+ core/util.c \
+ include/util.h \
+ include/common.h \
+ ui/preview-widget.c \
+ ui/preview-widget.h \
+ ui/theme-parser.c \
+ ui/theme-parser.h \
+ ui/theme.c \
+ ui/theme.h
+
+libmarco_private_la_LDFLAGS = -no-undefined
+libmarco_private_la_LIBADD = @MARCO_LIBS@
+
+libmarcoincludedir = $(includedir)/marco-1/marco-private
+
+libmarcoinclude_HEADERS = \
+ include/boxes.h \
+ ui/gradient.h \
+ include/util.h \
+ include/common.h \
+ ui/preview-widget.h \
+ ui/theme-parser.h \
+ ui/theme.h
+
+marco_theme_viewer_SOURCES= \
+ ui/theme-viewer.c
+
+schema_bindings_SOURCES = \
+ core/schema-bindings.c \
+ marco.schemas.in.in
+
+schema_bindings_LDADD = @MARCO_LIBS@
+marco.schemas.in: schema_bindings ${srcdir}/marco.schemas.in.in
+ @echo Generating keybinding schemas... ${srcdir}/marco.schemas.in.in
+ ${builddir}/schema_bindings ${srcdir}/marco.schemas.in.in ${builddir}/marco.schemas.in
+
+bin_PROGRAMS=marco marco-theme-viewer
+
+EFENCE=
+marco_LDADD=@MARCO_LIBS@ $(EFENCE)
+marco_theme_viewer_LDADD= @MARCO_LIBS@ libmarco-private.la
+
+testboxes_SOURCES=include/util.h core/util.c include/boxes.h core/boxes.c core/testboxes.c
+testgradient_SOURCES=ui/gradient.h ui/gradient.c ui/testgradient.c
+testasyncgetprop_SOURCES=core/async-getprop.h core/async-getprop.c core/testasyncgetprop.c
+
+noinst_PROGRAMS=testboxes testgradient testasyncgetprop schema_bindings
+
+testboxes_LDADD= @MARCO_LIBS@
+testgradient_LDADD= @MARCO_LIBS@
+testasyncgetprop_LDADD= @MARCO_LIBS@
+
+@INTLTOOL_DESKTOP_RULE@
+
+desktopfilesdir=$(datadir)/applications
+desktopfiles_in_files=marco.desktop.in
+desktopfiles_files=$(desktopfiles_in_files:.desktop.in=.desktop)
+desktopfiles_DATA = $(desktopfiles_files)
+
+wmpropertiesdir=$(datadir)/mate/wm-properties
+wmproperties_in_files=marco-wm.desktop.in
+wmproperties_files=$(wmproperties_in_files:.desktop.in=.desktop)
+wmproperties_DATA = $(wmproperties_files)
+
+schemadir = @MATECONF_SCHEMA_FILE_DIR@
+schema_in_files = marco.schemas.in
+schema_DATA = $(schema_in_files:.schemas.in=.schemas)
+
+@INTLTOOL_XML_NOMERGE_RULE@
+
+xmldir = @MATE_KEYBINDINGS_KEYSDIR@
+xml_in_files = 50-marco-desktop-key.xml.in 50-marco-key.xml.in
+xml_DATA = $(xml_in_files:.xml.in=.xml)
+
+@INTLTOOL_SCHEMAS_RULE@
+
+if MATECONF_SCHEMAS_INSTALL
+install-data-local:
+ MATECONF_CONFIG_SOURCE=$(MATECONF_SCHEMA_CONFIG_SOURCE) $(MATECONFTOOL) --makefile-install-rule $(schema_DATA)
+else
+install-data-local:
+endif
+
+BUILT_SOURCES =
+CLEANFILES = \
+ marco.desktop \
+ marco-wm.desktop \
+ marco.schemas \
+ marco.schemas.in \
+ 50-marco-desktop-key.xml \
+ 50-marco-key.xml
+
+pkgconfigdir = $(libdir)/pkgconfig
+
+pkgconfig_DATA = libmarco-private.pc
+
+EXTRA_DIST=$(desktopfiles_files) \
+ $(wmproperties_files) \
+ $(IMAGES) \
+ $(schema_DATA) \
+ $(desktopfiles_in_files) \
+ $(wmproperties_in_files) \
+ $(schema_in_files) \
+ $(xml_in_files) \
+ libmarco-private.pc.in
+
diff --git a/src/compositor/compositor-private.h b/src/compositor/compositor-private.h
new file mode 100644
index 00000000..ef9f8023
--- /dev/null
+++ b/src/compositor/compositor-private.h
@@ -0,0 +1,54 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2008 Iain Holmes
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_COMPOSITOR_PRIVATE_H
+#define META_COMPOSITOR_PRIVATE_H
+
+#include "compositor.h"
+
+struct _MetaCompositor
+{
+ void (* destroy) (MetaCompositor *compositor);
+
+ void (*manage_screen) (MetaCompositor *compositor,
+ MetaScreen *screen);
+ void (*unmanage_screen) (MetaCompositor *compositor,
+ MetaScreen *screen);
+ void (*add_window) (MetaCompositor *compositor,
+ MetaWindow *window,
+ Window xwindow,
+ XWindowAttributes *attrs);
+ void (*remove_window) (MetaCompositor *compositor,
+ Window xwindow);
+ void (*set_updates) (MetaCompositor *compositor,
+ MetaWindow *window,
+ gboolean update);
+ void (*process_event) (MetaCompositor *compositor,
+ XEvent *event,
+ MetaWindow *window);
+ Pixmap (*get_window_pixmap) (MetaCompositor *compositor,
+ MetaWindow *window);
+ void (*set_active_window) (MetaCompositor *compositor,
+ MetaScreen *screen,
+ MetaWindow *window);
+};
+
+#endif
diff --git a/src/compositor/compositor-xrender.c b/src/compositor/compositor-xrender.c
new file mode 100644
index 00000000..e0938706
--- /dev/null
+++ b/src/compositor/compositor-xrender.c
@@ -0,0 +1,3078 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2007 Iain Holmes
+ * Based on xcompmgr - (c) 2003 Keith Packard
+ * xfwm4 - (c) 2005-2007 Olivier Fourdan
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+#define _XOPEN_SOURCE 500 /* for usleep() */
+
+#include <config.h>
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <unistd.h>
+
+#include <gdk/gdk.h>
+
+#include "display.h"
+#include "screen.h"
+#include "frame.h"
+#include "errors.h"
+#include "window.h"
+#include "compositor-private.h"
+#include "compositor-xrender.h"
+#include "xprops.h"
+#include <X11/Xatom.h>
+#include <X11/extensions/shape.h>
+#include <X11/extensions/Xcomposite.h>
+#include <X11/extensions/Xdamage.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/extensions/Xrender.h>
+
+#if COMPOSITE_MAJOR > 0 || COMPOSITE_MINOR >= 2
+#define HAVE_NAME_WINDOW_PIXMAP 1
+#endif
+
+#if COMPOSITE_MAJOR > 0 || COMPOSITE_MINOR >= 3
+#define HAVE_COW 1
+#else
+/* Don't have a cow man...HAAHAAHAA */
+#endif
+
+#define USE_IDLE_REPAINT 1
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+static inline gboolean
+composite_at_least_version (MetaDisplay *display,
+ int maj, int min)
+{
+ static int major = -1;
+ static int minor = -1;
+
+ if (major == -1)
+ meta_display_get_compositor_version (display, &major, &minor);
+
+ return (major > maj || (major == maj && minor >= min));
+}
+
+#define have_name_window_pixmap(display) \
+ composite_at_least_version (display, 0, 2)
+#define have_cow(display) \
+ composite_at_least_version (display, 0, 3)
+
+#endif
+
+typedef enum _MetaCompWindowType
+{
+ META_COMP_WINDOW_NORMAL,
+ META_COMP_WINDOW_DND,
+ META_COMP_WINDOW_DESKTOP,
+ META_COMP_WINDOW_DOCK,
+ META_COMP_WINDOW_MENU,
+ META_COMP_WINDOW_DROP_DOWN_MENU,
+ META_COMP_WINDOW_TOOLTIP,
+} MetaCompWindowType;
+
+typedef enum _MetaShadowType
+{
+ META_SHADOW_SMALL,
+ META_SHADOW_MEDIUM,
+ META_SHADOW_LARGE,
+ LAST_SHADOW_TYPE
+} MetaShadowType;
+
+typedef struct _MetaCompositorXRender
+{
+ MetaCompositor compositor;
+
+ MetaDisplay *display;
+
+ Atom atom_x_root_pixmap;
+ Atom atom_x_set_root;
+ Atom atom_net_wm_window_opacity;
+ Atom atom_net_wm_window_type_dnd;
+
+ Atom atom_net_wm_window_type;
+ Atom atom_net_wm_window_type_desktop;
+ Atom atom_net_wm_window_type_dock;
+ Atom atom_net_wm_window_type_menu;
+ Atom atom_net_wm_window_type_dialog;
+ Atom atom_net_wm_window_type_normal;
+ Atom atom_net_wm_window_type_utility;
+ Atom atom_net_wm_window_type_splash;
+ Atom atom_net_wm_window_type_toolbar;
+ Atom atom_net_wm_window_type_dropdown_menu;
+ Atom atom_net_wm_window_type_tooltip;
+
+#ifdef USE_IDLE_REPAINT
+ guint repaint_id;
+#endif
+ guint enabled : 1;
+ guint show_redraw : 1;
+ guint debug : 1;
+} MetaCompositorXRender;
+
+typedef struct _conv
+{
+ int size;
+ double *data;
+} conv;
+
+typedef struct _shadow
+{
+ conv *gaussian_map;
+ guchar *shadow_corner;
+ guchar *shadow_top;
+} shadow;
+
+typedef struct _MetaCompScreen
+{
+ MetaScreen *screen;
+ GList *windows;
+ GHashTable *windows_by_xid;
+
+ MetaWindow *focus_window;
+
+ Window output;
+
+ gboolean have_shadows;
+ shadow *shadows[LAST_SHADOW_TYPE];
+
+ Picture root_picture;
+ Picture root_buffer;
+ Picture black_picture;
+ Picture trans_black_picture;
+ Picture root_tile;
+ XserverRegion all_damage;
+
+ guint overlays;
+ gboolean compositor_active;
+ gboolean clip_changed;
+
+ GSList *dock_windows;
+} MetaCompScreen;
+
+typedef struct _MetaCompWindow
+{
+ MetaScreen *screen;
+ MetaWindow *window; /* May be NULL if this window isn't managed by Marco */
+ Window id;
+ XWindowAttributes attrs;
+
+#ifdef HAVE_NAME_WINDOW_PIXMAP
+ Pixmap back_pixmap;
+
+ /* When the window is shaded back_pixmap will be replaced with the pixmap
+ for the shaded window. This is a copy of the original unshaded window
+ so that we can still see what the window looked like when it is needed
+ for the _get_window_pixmap function */
+ Pixmap shaded_back_pixmap;
+#endif
+
+ int mode;
+
+ gboolean damaged;
+ gboolean shaped;
+
+ MetaCompWindowType type;
+
+ Damage damage;
+ Picture picture;
+ Picture alpha_pict;
+
+ gboolean needs_shadow;
+ MetaShadowType shadow_type;
+ Picture shadow_pict;
+
+ XserverRegion border_size;
+ XserverRegion extents;
+
+ Picture shadow;
+ int shadow_dx;
+ int shadow_dy;
+ int shadow_width;
+ int shadow_height;
+
+ guint opacity;
+
+ XserverRegion border_clip;
+
+ gboolean updates_frozen;
+ gboolean update_pending;
+} MetaCompWindow;
+
+#define OPAQUE 0xffffffff
+
+#define WINDOW_SOLID 0
+#define WINDOW_ARGB 1
+
+#define SHADOW_SMALL_RADIUS 3.0
+#define SHADOW_MEDIUM_RADIUS 6.0
+#define SHADOW_LARGE_RADIUS 12.0
+
+#define SHADOW_SMALL_OFFSET_X (SHADOW_SMALL_RADIUS * -3 / 2)
+#define SHADOW_SMALL_OFFSET_Y (SHADOW_SMALL_RADIUS * -3 / 2)
+#define SHADOW_MEDIUM_OFFSET_X (SHADOW_MEDIUM_RADIUS * -3 / 2)
+#define SHADOW_MEDIUM_OFFSET_Y (SHADOW_MEDIUM_RADIUS * -5 / 4)
+#define SHADOW_LARGE_OFFSET_X -15
+#define SHADOW_LARGE_OFFSET_Y -15
+
+#define SHADOW_OPACITY 0.66
+
+#define TRANS_OPACITY 0.75
+
+#define DISPLAY_COMPOSITOR(display) ((MetaCompositorXRender *) meta_display_get_compositor (display))
+
+/* Gaussian stuff for creating the shadows */
+static double
+gaussian (double r,
+ double x,
+ double y)
+{
+ return ((1 / (sqrt (2 * G_PI * r))) *
+ exp ((- (x * x + y * y)) / (2 * r * r)));
+}
+
+static conv *
+make_gaussian_map (double r)
+{
+ conv *c;
+ int size, centre;
+ int x, y;
+ double t, g;
+
+ size = ((int) ceil ((r * 3)) + 1) & ~1;
+ centre = size / 2;
+ c = g_malloc (sizeof (conv) + size * size * sizeof (double));
+ c->size = size;
+ c->data = (double *) (c + 1);
+ t = 0.0;
+
+ for (y = 0; y < size; y++)
+ {
+ for (x = 0; x < size; x++)
+ {
+ g = gaussian (r, (double) (x - centre), (double) (y - centre));
+ t += g;
+ c->data[y * size + x] = g;
+ }
+ }
+
+ for (y = 0; y < size; y++)
+ {
+ for (x = 0; x < size; x++)
+ {
+ c->data[y * size + x] /= t;
+ }
+ }
+
+ return c;
+}
+
+static void
+dump_xserver_region (const char *location,
+ MetaDisplay *display,
+ XserverRegion region)
+{
+ MetaCompositorXRender *compositor = DISPLAY_COMPOSITOR (display);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ int nrects;
+ XRectangle *rects;
+ XRectangle bounds;
+
+ if (!compositor->debug)
+ return;
+
+ if (region)
+ {
+ rects = XFixesFetchRegionAndBounds (xdisplay, region, &nrects, &bounds);
+ if (nrects > 0)
+ {
+ int i;
+ fprintf (stderr, "%s (XSR): %d rects, bounds: %d,%d (%d,%d)\n",
+ location, nrects, bounds.x, bounds.y, bounds.width, bounds.height);
+ for (i = 1; i < nrects; i++)
+ fprintf (stderr, "\t%d,%d (%d,%d)\n",
+ rects[i].x, rects[i].y, rects[i].width, rects[i].height);
+ }
+ else
+ fprintf (stderr, "%s (XSR): empty\n", location);
+ XFree (rects);
+ }
+ else
+ fprintf (stderr, "%s (XSR): null\n", location);
+}
+
+/*
+* A picture will help
+*
+* -center 0 width width+center
+* -center +-----+-------------------+-----+
+* | | | |
+* | | | |
+* 0 +-----+-------------------+-----+
+* | | | |
+* | | | |
+* | | | |
+* height +-----+-------------------+-----+
+* | | | |
+* height+ | | | |
+* center +-----+-------------------+-----+
+*/
+static guchar
+sum_gaussian (conv *map,
+ double opacity,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ double *g_data, *g_line;
+ double v;
+ int fx, fy;
+ int fx_start, fx_end;
+ int fy_start, fy_end;
+ int g_size, centre;
+
+ g_line = map->data;
+ g_size = map->size;
+ centre = g_size / 2;
+ fx_start = centre - x;
+ if (fx_start < 0)
+ fx_start = 0;
+
+ fx_end = width + centre - x;
+ if (fx_end > g_size)
+ fx_end = g_size;
+
+ fy_start = centre - y;
+ if (fy_start < 0)
+ fy_start = 0;
+
+ fy_end = height + centre - y;
+ if (fy_end > g_size)
+ fy_end = g_size;
+
+ g_line = g_line + fy_start * g_size + fx_start;
+
+ v = 0.0;
+ for (fy = fy_start; fy < fy_end; fy++)
+ {
+ g_data = g_line;
+ g_line += g_size;
+
+ for (fx = fx_start; fx < fx_end; fx++)
+ v += *g_data++;
+ }
+
+ if (v > 1.0)
+ v = 1.0;
+
+ return ((guchar) (v * opacity * 255.0));
+}
+
+/* precompute shadow corners and sides to save time for large windows */
+static void
+presum_gaussian (shadow *shad)
+{
+ int centre;
+ int opacity, x, y;
+ int msize;
+ conv *map;
+
+ map = shad->gaussian_map;
+ msize = map->size;
+ centre = map->size / 2;
+
+ if (shad->shadow_corner)
+ g_free (shad->shadow_corner);
+ if (shad->shadow_top)
+ g_free (shad->shadow_top);
+
+ shad->shadow_corner = (guchar *)(g_malloc ((msize + 1) * (msize + 1) * 26));
+ shad->shadow_top = (guchar *) (g_malloc ((msize + 1) * 26));
+
+ for (x = 0; x <= msize; x++)
+ {
+
+ shad->shadow_top[25 * (msize + 1) + x] =
+ sum_gaussian (map, 1, x - centre, centre, msize * 2, msize * 2);
+ for (opacity = 0; opacity < 25; opacity++)
+ {
+ shad->shadow_top[opacity * (msize + 1) + x] =
+ shad->shadow_top[25 * (msize + 1) + x] * opacity / 25;
+ }
+
+ for (y = 0; y <= x; y++)
+ {
+ shad->shadow_corner[25 * (msize + 1) * (msize + 1)
+ + y * (msize + 1)
+ + x]
+ = sum_gaussian (map, 1, x - centre, y - centre,
+ msize * 2, msize * 2);
+
+ shad->shadow_corner[25 * (msize + 1) * (msize + 1)
+ + x * (msize + 1) + y] =
+ shad->shadow_corner[25 * (msize + 1) * (msize + 1)
+ + y * (msize + 1) + x];
+
+ for (opacity = 0; opacity < 25; opacity++)
+ {
+ shad->shadow_corner[opacity * (msize + 1) * (msize + 1)
+ + y * (msize + 1) + x]
+ = shad->shadow_corner[opacity * (msize + 1) * (msize + 1)
+ + x * (msize + 1) + y]
+ = shad->shadow_corner[25 * (msize + 1) * (msize + 1)
+ + y * (msize + 1) + x] * opacity / 25;
+ }
+ }
+ }
+}
+
+static void
+generate_shadows (MetaCompScreen *info)
+{
+ double radii[LAST_SHADOW_TYPE] = {SHADOW_SMALL_RADIUS,
+ SHADOW_MEDIUM_RADIUS,
+ SHADOW_LARGE_RADIUS};
+ int i;
+
+ for (i = 0; i < LAST_SHADOW_TYPE; i++) {
+ shadow *shad = g_new0 (shadow, 1);
+
+ shad->gaussian_map = make_gaussian_map (radii[i]);
+ presum_gaussian (shad);
+
+ info->shadows[i] = shad;
+ }
+}
+
+static XImage *
+make_shadow (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaShadowType shadow_type,
+ double opacity,
+ int width,
+ int height)
+{
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ XImage *ximage;
+ guchar *data;
+ shadow *shad;
+ int msize;
+ int ylimit, xlimit;
+ int swidth, sheight;
+ int centre;
+ int x, y;
+ guchar d;
+ int x_diff;
+ int opacity_int = (int)(opacity * 25);
+ int screen_number = meta_screen_get_screen_number (screen);
+
+ if (info==NULL)
+ {
+ return NULL;
+ }
+
+ shad = info->shadows[shadow_type];
+ msize = shad->gaussian_map->size;
+ swidth = width + msize;
+ sheight = height + msize;
+ centre = msize / 2;
+
+ data = g_malloc (swidth * sheight * sizeof (guchar));
+
+ ximage = XCreateImage (xdisplay, DefaultVisual (xdisplay, screen_number),
+ 8, ZPixmap, 0, (char *) data,
+ swidth, sheight, 8, swidth * sizeof (guchar));
+ if (!ximage)
+ {
+ g_free (data);
+ return NULL;
+ }
+
+ /*
+ * Build the gaussian in sections
+ */
+
+ /*
+ * centre (fill the complete data array
+ */
+ if (msize > 0)
+ d = shad->shadow_top[opacity_int * (msize + 1) + msize];
+ else
+ d = sum_gaussian (shad->gaussian_map, opacity, centre,
+ centre, width, height);
+ memset (data, d, sheight * swidth);
+
+ /*
+ * corners
+ */
+ ylimit = msize;
+ if (ylimit > sheight / 2)
+ ylimit = (sheight + 1) / 2;
+
+ xlimit = msize;
+ if (xlimit > swidth / 2)
+ xlimit = (swidth + 1) / 2;
+
+ for (y = 0; y < ylimit; y++)
+ {
+ for (x = 0; x < xlimit; x++)
+ {
+
+ if (xlimit == msize && ylimit == msize)
+ d = shad->shadow_corner[opacity_int * (msize + 1) * (msize + 1) + y * (msize + 1) + x];
+ else
+ d = sum_gaussian (shad->gaussian_map, opacity, x - centre,
+ y - centre, width, height);
+
+ data[y * swidth + x] = d;
+ data[(sheight - y - 1) * swidth + x] = d;
+ data[(sheight - y - 1) * swidth + (swidth - x - 1)] = d;
+ data[y * swidth + (swidth - x - 1)] = d;
+ }
+ }
+
+ /* top/bottom */
+ x_diff = swidth - (msize * 2);
+ if (x_diff > 0 && ylimit > 0)
+ {
+ for (y = 0; y < ylimit; y++)
+ {
+ if (ylimit == msize)
+ d = shad->shadow_top[opacity_int * (msize + 1) + y];
+ else
+ d = sum_gaussian (shad->gaussian_map, opacity, centre,
+ y - centre, width, height);
+
+ memset (&data[y * swidth + msize], d, x_diff);
+ memset (&data[(sheight - y - 1) * swidth + msize], d, x_diff);
+ }
+ }
+
+ /*
+ * sides
+ */
+ for (x = 0; x < xlimit; x++)
+ {
+ if (xlimit == msize)
+ d = shad->shadow_top[opacity_int * (msize + 1) + x];
+ else
+ d = sum_gaussian (shad->gaussian_map, opacity, x - centre,
+ centre, width, height);
+
+ for (y = msize; y < sheight - msize; y++)
+ {
+ data[y * swidth + x] = d;
+ data[y * swidth + (swidth - x - 1)] = d;
+ }
+ }
+
+ return ximage;
+}
+
+static Picture
+shadow_picture (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaShadowType shadow_type,
+ double opacity,
+ Picture alpha_pict,
+ int width,
+ int height,
+ int *wp,
+ int *hp)
+{
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ XImage *shadow_image;
+ Pixmap shadow_pixmap;
+ Picture shadow_picture;
+ Window xroot = meta_screen_get_xroot (screen);
+ GC gc;
+
+ shadow_image = make_shadow (display, screen, shadow_type,
+ opacity, width, height);
+ if (!shadow_image)
+ return None;
+
+ shadow_pixmap = XCreatePixmap (xdisplay, xroot,
+ shadow_image->width, shadow_image->height, 8);
+ if (!shadow_pixmap)
+ {
+ XDestroyImage (shadow_image);
+ return None;
+ }
+
+ shadow_picture = XRenderCreatePicture (xdisplay, shadow_pixmap,
+ XRenderFindStandardFormat (xdisplay,
+PictStandardA8),
+ 0, 0);
+ if (!shadow_picture)
+ {
+ XDestroyImage (shadow_image);
+ XFreePixmap (xdisplay, shadow_pixmap);
+ return None;
+ }
+
+ gc = XCreateGC (xdisplay, shadow_pixmap, 0, 0);
+ if (!gc)
+ {
+ XDestroyImage (shadow_image);
+ XFreePixmap (xdisplay, shadow_pixmap);
+ XRenderFreePicture (xdisplay, shadow_picture);
+ return None;
+ }
+
+ XPutImage (xdisplay, shadow_pixmap, gc, shadow_image, 0, 0, 0, 0,
+ shadow_image->width, shadow_image->height);
+ *wp = shadow_image->width;
+ *hp = shadow_image->height;
+
+ XFreeGC (xdisplay, gc);
+ XDestroyImage (shadow_image);
+ XFreePixmap (xdisplay, shadow_pixmap);
+
+ return shadow_picture;
+}
+
+static MetaCompWindow *
+find_window_for_screen (MetaScreen *screen,
+ Window xwindow)
+{
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+
+ if (info == NULL)
+ return NULL;
+
+ return g_hash_table_lookup (info->windows_by_xid, (gpointer) xwindow);
+}
+
+static MetaCompWindow *
+find_window_in_display (MetaDisplay *display,
+ Window xwindow)
+{
+ GSList *index;
+
+ for (index = meta_display_get_screens (display); index; index = index->next)
+ {
+ MetaCompWindow *cw = find_window_for_screen (index->data, xwindow);
+
+ if (cw != NULL)
+ return cw;
+ }
+
+ return NULL;
+}
+
+static MetaCompWindow *
+find_window_for_child_window_in_display (MetaDisplay *display,
+ Window xwindow)
+{
+ Window ignored1, *ignored2;
+ Window parent;
+ guint ignored_children;
+
+ XQueryTree (meta_display_get_xdisplay (display), xwindow, &ignored1,
+ &parent, &ignored2, &ignored_children);
+
+ if (parent != None)
+ return find_window_in_display (display, parent);
+
+ return NULL;
+}
+
+static Picture
+solid_picture (MetaDisplay *display,
+ MetaScreen *screen,
+ gboolean argb,
+ double a,
+ double r,
+ double g,
+ double b)
+{
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ Pixmap pixmap;
+ Picture picture;
+ XRenderPictureAttributes pa;
+ XRenderPictFormat *render_format;
+ XRenderColor c;
+ Window xroot = meta_screen_get_xroot (screen);
+
+ render_format = XRenderFindStandardFormat (xdisplay,
+ argb ? PictStandardARGB32 : PictStandardA8);
+
+ pixmap = XCreatePixmap (xdisplay, xroot, 1, 1, argb ? 32 : 8);
+ g_return_val_if_fail (pixmap != None, None);
+
+ pa.repeat = TRUE;
+ picture = XRenderCreatePicture (xdisplay, pixmap, render_format,
+ CPRepeat, &pa);
+ if (picture == None)
+ {
+ XFreePixmap (xdisplay, pixmap);
+ g_warning ("(picture != None) failed");
+ return None;
+ }
+
+ c.alpha = a * 0xffff;
+ c.red = r * 0xffff;
+ c.green = g * 0xffff;
+ c.blue = b * 0xffff;
+
+ XRenderFillRectangle (xdisplay, PictOpSrc, picture, &c, 0, 0, 1, 1);
+ XFreePixmap (xdisplay, pixmap);
+
+ return picture;
+}
+
+static Picture
+root_tile (MetaScreen *screen)
+{
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ Picture picture;
+ Pixmap pixmap;
+ gboolean fill = FALSE;
+ XRenderPictureAttributes pa;
+ XRenderPictFormat *format;
+ int p;
+ Atom background_atoms[2];
+ Atom pixmap_atom;
+ int screen_number = meta_screen_get_screen_number (screen);
+ Window xroot = meta_screen_get_xroot (screen);
+
+ pixmap = None;
+ background_atoms[0] = DISPLAY_COMPOSITOR (display)->atom_x_root_pixmap;
+ background_atoms[1] = DISPLAY_COMPOSITOR (display)->atom_x_set_root;
+
+ pixmap_atom = XInternAtom (xdisplay, "PIXMAP", False);
+ for (p = 0; p < 2; p++)
+ {
+ Atom actual_type;
+ int actual_format;
+ gulong nitems, bytes_after;
+ guchar *prop;
+
+ if (XGetWindowProperty (xdisplay, xroot,
+ background_atoms[p],
+ 0, 4, FALSE, AnyPropertyType,
+ &actual_type, &actual_format,
+ &nitems, &bytes_after, &prop) == Success)
+ {
+ if (actual_type == pixmap_atom &&
+ actual_format == 32 &&
+ nitems == 1)
+ {
+ memcpy (&pixmap, prop, 4);
+ XFree (prop);
+ fill = FALSE;
+ break;
+ }
+ }
+ }
+
+ if (!pixmap)
+ {
+ pixmap = XCreatePixmap (xdisplay, xroot, 1, 1,
+ DefaultDepth (xdisplay, screen_number));
+ g_return_val_if_fail (pixmap != None, None);
+ fill = TRUE;
+ }
+
+ pa.repeat = TRUE;
+ format = XRenderFindVisualFormat (xdisplay, DefaultVisual (xdisplay,
+ screen_number));
+ g_return_val_if_fail (format != NULL, None);
+
+ picture = XRenderCreatePicture (xdisplay, pixmap, format, CPRepeat, &pa);
+ if ((picture != None) && (fill))
+ {
+ XRenderColor c;
+
+ /* Background default to just plain ugly grey */
+ c.red = 0x8080;
+ c.green = 0x8080;
+ c.blue = 0x8080;
+ c.alpha = 0xffff;
+
+ XRenderFillRectangle (xdisplay, PictOpSrc, picture, &c, 0, 0, 1, 1);
+ XFreePixmap (xdisplay, pixmap);
+ }
+
+ return picture;
+}
+
+static Picture
+create_root_buffer (MetaScreen *screen)
+{
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+ Picture pict;
+ XRenderPictFormat *format;
+ Pixmap root_pixmap;
+ Visual *visual;
+ int depth, screen_width, screen_height, screen_number;
+
+ if (info == NULL)
+ {
+ return None;
+ }
+
+ meta_screen_get_size (screen, &screen_width, &screen_height);
+ screen_number = meta_screen_get_screen_number (screen);
+ visual = DefaultVisual (xdisplay, screen_number);
+ depth = DefaultDepth (xdisplay, screen_number);
+
+ format = XRenderFindVisualFormat (xdisplay, visual);
+ g_return_val_if_fail (format != NULL, None);
+
+ root_pixmap = XCreatePixmap (xdisplay, info->output,
+ screen_width, screen_height, depth);
+ g_return_val_if_fail (root_pixmap != None, None);
+
+ pict = XRenderCreatePicture (xdisplay, root_pixmap, format, 0, NULL);
+ XFreePixmap (xdisplay, root_pixmap);
+
+ return pict;
+}
+
+static void
+paint_root (MetaScreen *screen,
+ Picture root_buffer)
+{
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+ int width, height;
+
+ if (info == NULL)
+ {
+ return;
+ }
+
+ g_return_if_fail (root_buffer != None);
+
+ if (info->root_tile == None)
+ {
+ info->root_tile = root_tile (screen);
+ g_return_if_fail (info->root_tile != None);
+ }
+
+ meta_screen_get_size (screen, &width, &height);
+ XRenderComposite (xdisplay, PictOpSrc, info->root_tile, None, root_buffer,
+ 0, 0, 0, 0, 0, 0, width, height);
+}
+
+static gboolean
+window_has_shadow (MetaCompWindow *cw)
+{
+ MetaCompScreen *info = meta_screen_get_compositor_data (cw->screen);
+
+ if (info == NULL || info->have_shadows == FALSE)
+ return FALSE;
+
+ /* Always put a shadow around windows with a frame - This should override
+ the restriction about not putting a shadow around shaped windows
+ as the frame might be the reason the window is shaped */
+ if (cw->window)
+ {
+ if (meta_window_get_frame (cw->window)) {
+ meta_verbose ("Window has shadow because it has a frame\n");
+ return TRUE;
+ }
+ }
+
+ /* Never put a shadow around shaped windows */
+ if (cw->shaped) {
+ meta_verbose ("Window has no shadow as it is shaped\n");
+ return FALSE;
+ }
+
+ /* Don't put shadow around DND icon windows */
+ if (cw->type == META_COMP_WINDOW_DND ||
+ cw->type == META_COMP_WINDOW_DESKTOP) {
+ meta_verbose ("Window has no shadow as it is DND or Desktop\n");
+ return FALSE;
+ }
+
+ if (cw->mode != WINDOW_ARGB) {
+ meta_verbose ("Window has shadow as it is not ARGB\n");
+ return TRUE;
+ }
+
+ if (cw->type == META_COMP_WINDOW_MENU ||
+ cw->type == META_COMP_WINDOW_DROP_DOWN_MENU) {
+ meta_verbose ("Window has shadow as it is a menu\n");
+ return TRUE;
+ }
+
+ if (cw->type == META_COMP_WINDOW_TOOLTIP) {
+ meta_verbose ("Window has shadow as it is a tooltip\n");
+ return TRUE;
+ }
+
+ meta_verbose ("Window has no shadow as it fell through\n");
+ return FALSE;
+}
+
+double shadow_offsets_x[LAST_SHADOW_TYPE] = {SHADOW_SMALL_OFFSET_X,
+ SHADOW_MEDIUM_OFFSET_X,
+ SHADOW_LARGE_OFFSET_X};
+double shadow_offsets_y[LAST_SHADOW_TYPE] = {SHADOW_SMALL_OFFSET_Y,
+ SHADOW_MEDIUM_OFFSET_Y,
+ SHADOW_LARGE_OFFSET_Y};
+static XserverRegion
+win_extents (MetaCompWindow *cw)
+{
+ MetaScreen *screen = cw->screen;
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ XRectangle r;
+
+ r.x = cw->attrs.x;
+ r.y = cw->attrs.y;
+ r.width = cw->attrs.width + cw->attrs.border_width * 2;
+ r.height = cw->attrs.height + cw->attrs.border_width * 2;
+
+ if (cw->needs_shadow)
+ {
+ XRectangle sr;
+
+ cw->shadow_dx = shadow_offsets_x [cw->shadow_type];
+ cw->shadow_dy = shadow_offsets_y [cw->shadow_type];
+
+ if (!cw->shadow)
+ {
+ double opacity = SHADOW_OPACITY;
+ if (cw->opacity != (guint) OPAQUE)
+ opacity = opacity * ((double) cw->opacity) / ((double) OPAQUE);
+
+ cw->shadow = shadow_picture (display, screen, cw->shadow_type,
+ opacity, cw->alpha_pict,
+ cw->attrs.width + cw->attrs.border_width * 2,
+ cw->attrs.height + cw->attrs.border_width * 2,
+ &cw->shadow_width, &cw->shadow_height);
+ }
+
+ sr.x = cw->attrs.x + cw->shadow_dx;
+ sr.y = cw->attrs.y + cw->shadow_dy;
+ sr.width = cw->shadow_width;
+ sr.height = cw->shadow_height;
+
+ if (sr.x < r.x)
+ {
+ r.width = (r.x + r.width) - sr.x;
+ r.x = sr.x;
+ }
+
+ if (sr.y < r.y)
+ {
+ r.height = (r.y + r.height) - sr.y;
+ r.y = sr.y;
+ }
+
+ if (sr.x + sr.width > r.x + r.width)
+ r.width = sr.x + sr.width - r.x;
+
+ if (sr.y + sr.height > r.y + r.height)
+ r.height = sr.y + sr.height - r.y;
+ }
+
+ return XFixesCreateRegion (xdisplay, &r, 1);
+}
+
+static XserverRegion
+border_size (MetaCompWindow *cw)
+{
+ MetaScreen *screen = cw->screen;
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ XserverRegion border;
+
+ meta_error_trap_push (display);
+ border = XFixesCreateRegionFromWindow (xdisplay, cw->id,
+ WindowRegionBounding);
+ meta_error_trap_pop (display, FALSE);
+
+ g_return_val_if_fail (border != None, None);
+ XFixesTranslateRegion (xdisplay, border,
+ cw->attrs.x + cw->attrs.border_width,
+ cw->attrs.y + cw->attrs.border_width);
+ return border;
+}
+
+static XRenderPictFormat *
+get_window_format (MetaCompWindow *cw)
+{
+ MetaScreen *screen = cw->screen;
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ XRenderPictFormat *format;
+ int screen_number = meta_screen_get_screen_number (screen);
+
+ format = XRenderFindVisualFormat (xdisplay, cw->attrs.visual);
+ if (!format)
+ format = XRenderFindVisualFormat (xdisplay,
+ DefaultVisual (xdisplay, screen_number));
+ return format;
+}
+
+static Picture
+get_window_picture (MetaCompWindow *cw)
+{
+ MetaScreen *screen = cw->screen;
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ XRenderPictureAttributes pa;
+ XRenderPictFormat *format;
+ Drawable draw;
+
+ draw = cw->id;
+
+ meta_error_trap_push (display);
+
+#ifdef HAVE_NAME_WINDOW_PIXMAP
+ if (have_name_window_pixmap (display))
+ {
+ if (cw->back_pixmap == None)
+ cw->back_pixmap = XCompositeNameWindowPixmap (xdisplay, cw->id);
+
+ if (cw->back_pixmap != None)
+ draw = cw->back_pixmap;
+ }
+#endif
+
+ format = get_window_format (cw);
+ if (format)
+ {
+ Picture pict;
+
+ pa.subwindow_mode = IncludeInferiors;
+
+ pict = XRenderCreatePicture (xdisplay, draw, format, CPSubwindowMode, &pa);
+ meta_error_trap_pop (display, FALSE);
+
+ return pict;
+ }
+
+ meta_error_trap_pop (display, FALSE);
+ return None;
+}
+
+static void
+paint_dock_shadows (MetaScreen *screen,
+ Picture root_buffer,
+ XserverRegion region)
+{
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+ GSList *d;
+
+ if (info == NULL)
+ {
+ return;
+ }
+
+ for (d = info->dock_windows; d; d = d->next)
+ {
+ MetaCompWindow *cw = d->data;
+ XserverRegion shadow_clip;
+
+ if (cw->shadow)
+ {
+ shadow_clip = XFixesCreateRegion (xdisplay, NULL, 0);
+ XFixesIntersectRegion (xdisplay, shadow_clip,
+ cw->border_clip, region);
+
+ XFixesSetPictureClipRegion (xdisplay, root_buffer, 0, 0, shadow_clip);
+
+ XRenderComposite (xdisplay, PictOpOver, info->black_picture,
+ cw->shadow, root_buffer,
+ 0, 0, 0, 0,
+ cw->attrs.x + cw->shadow_dx,
+ cw->attrs.y + cw->shadow_dy,
+ cw->shadow_width, cw->shadow_height);
+ XFixesDestroyRegion (xdisplay, shadow_clip);
+ }
+ }
+}
+
+static void
+paint_windows (MetaScreen *screen,
+ GList *windows,
+ Picture root_buffer,
+ XserverRegion region)
+{
+ MetaDisplay *display = meta_screen_get_display (screen);
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ GList *index, *last;
+ int screen_width, screen_height, screen_number;
+ Window xroot;
+ MetaCompWindow *cw;
+ XserverRegion paint_region, desktop_region;
+
+ if (info == NULL)
+ {
+ return;
+ }
+
+ meta_screen_get_size (screen, &screen_width, &screen_height);
+ screen_number = meta_screen_get_screen_number (screen);
+ xroot = meta_screen_get_xroot (screen);
+
+ if (region == None)
+ {
+ XRectangle r;
+ r.x = 0;
+ r.y = 0;
+ r.width = screen_width;
+ r.height = screen_height;
+ paint_region = XFixesCreateRegion (xdisplay, &r, 1);
+ }
+ else
+ {
+ paint_region = XFixesCreateRegion (xdisplay, NULL, 0);
+ XFixesCopyRegion (xdisplay, paint_region, region);
+ }
+
+ desktop_region = None;
+
+ /*
+ * Painting from top to bottom, reducing the clipping area at
+ * each iteration. Only the opaque windows are painted 1st.
+ */
+ last = NULL;
+ for (index = windows; index; index = index->next)
+ {
+ /* Store the last window we dealt with */
+ last = index;
+
+ cw = (MetaCompWindow *) index->data;
+ if (!cw->damaged)
+ {
+ /* Not damaged */
+ continue;
+ }
+
+#if 0
+ if ((cw->attrs.x + cw->attrs.width < 1) ||
+ (cw->attrs.y + cw->attrs.height < 1) ||
+ (cw->attrs.x >= screen_width) || (cw->attrs.y >= screen_height))
+ {
+ /* Off screen */
+ continue;
+ }
+#endif
+
+ if (cw->picture == None)
+ cw->picture = get_window_picture (cw);
+
+ /* If the clip region of the screen has been changed
+ then we need to recreate the extents of the window */
+ if (info->clip_changed)
+ {
+ if (cw->border_size)
+ {
+ XFixesDestroyRegion (xdisplay, cw->border_size);
+ cw->border_size = None;
+ }
+
+#if 0
+ if (cw->extents)
+ {
+ XFixesDestroyRegion (xdisplay, cw->extents);
+ cw->extents = None;
+ }
+#endif
+ }
+
+ if (cw->border_size == None)
+ cw->border_size = border_size (cw);
+
+ if (cw->extents == None)
+ cw->extents = win_extents (cw);
+
+ if (cw->mode == WINDOW_SOLID)
+ {
+ int x, y, wid, hei;
+
+#ifdef HAVE_NAME_WINDOW_PIXMAP
+ if (have_name_window_pixmap (display))
+ {
+ x = cw->attrs.x;
+ y = cw->attrs.y;
+ wid = cw->attrs.width + cw->attrs.border_width * 2;
+ hei = cw->attrs.height + cw->attrs.border_width * 2;
+ }
+ else
+#endif
+ {
+ x = cw->attrs.x + cw->attrs.border_width;
+ y = cw->attrs.y + cw->attrs.border_width;
+ wid = cw->attrs.width;
+ hei = cw->attrs.height;
+ }
+
+ XFixesSetPictureClipRegion (xdisplay, root_buffer,
+ 0, 0, paint_region);
+ XRenderComposite (xdisplay, PictOpSrc, cw->picture,
+ None, root_buffer, 0, 0, 0, 0,
+ x, y, wid, hei);
+
+ if (cw->type == META_COMP_WINDOW_DESKTOP)
+ {
+ desktop_region = XFixesCreateRegion (xdisplay, 0, 0);
+ XFixesCopyRegion (xdisplay, desktop_region, paint_region);
+ }
+
+ XFixesSubtractRegion (xdisplay, paint_region,
+ paint_region, cw->border_size);
+ }
+
+ if (!cw->border_clip)
+ {
+ cw->border_clip = XFixesCreateRegion (xdisplay, 0, 0);
+ XFixesCopyRegion (xdisplay, cw->border_clip, paint_region);
+ }
+ }
+
+ XFixesSetPictureClipRegion (xdisplay, root_buffer, 0, 0, paint_region);
+ paint_root (screen, root_buffer);
+
+ paint_dock_shadows (screen, root_buffer, desktop_region == None ?
+ paint_region : desktop_region);
+ if (desktop_region != None)
+ XFixesDestroyRegion (xdisplay, desktop_region);
+
+ /*
+ * Painting from bottom to top, translucent windows and shadows are painted
+ */
+ for (index = last; index; index = index->prev)
+ {
+ cw = (MetaCompWindow *) index->data;
+
+ if (cw->picture)
+ {
+ if (cw->shadow && cw->type != META_COMP_WINDOW_DOCK)
+ {
+ XserverRegion shadow_clip;
+
+ shadow_clip = XFixesCreateRegion (xdisplay, NULL, 0);
+ XFixesSubtractRegion (xdisplay, shadow_clip, cw->border_clip,
+ cw->border_size);
+ XFixesSetPictureClipRegion (xdisplay, root_buffer, 0, 0,
+ shadow_clip);
+
+ XRenderComposite (xdisplay, PictOpOver, info->black_picture,
+ cw->shadow, root_buffer,
+ 0, 0, 0, 0,
+ cw->attrs.x + cw->shadow_dx,
+ cw->attrs.y + cw->shadow_dy,
+ cw->shadow_width, cw->shadow_height);
+ if (shadow_clip)
+ XFixesDestroyRegion (xdisplay, shadow_clip);
+ }
+
+ if ((cw->opacity != (guint) OPAQUE) && !(cw->alpha_pict))
+ {
+ cw->alpha_pict = solid_picture (display, screen, FALSE,
+ (double) cw->opacity / OPAQUE,
+ 0, 0, 0);
+ }
+
+ XFixesIntersectRegion (xdisplay, cw->border_clip, cw->border_clip,
+ cw->border_size);
+ XFixesSetPictureClipRegion (xdisplay, root_buffer, 0, 0,
+ cw->border_clip);
+ if (cw->mode == WINDOW_ARGB)
+ {
+ int x, y, wid, hei;
+#ifdef HAVE_NAME_WINDOW_PIXMAP
+ if (have_name_window_pixmap (display))
+ {
+ x = cw->attrs.x;
+ y = cw->attrs.y;
+ wid = cw->attrs.width + cw->attrs.border_width * 2;
+ hei = cw->attrs.height + cw->attrs.border_width * 2;
+ }
+ else
+#endif
+ {
+ x = cw->attrs.x + cw->attrs.border_width;
+ y = cw->attrs.y + cw->attrs.border_width;
+ wid = cw->attrs.width;
+ hei = cw->attrs.height;
+ }
+
+ XRenderComposite (xdisplay, PictOpOver, cw->picture,
+ cw->alpha_pict, root_buffer, 0, 0, 0, 0,
+ x, y, wid, hei);
+ }
+ }
+
+ if (cw->border_clip)
+ {
+ XFixesDestroyRegion (xdisplay, cw->border_clip);
+ cw->border_clip = None;
+ }
+ }
+
+ XFixesDestroyRegion (xdisplay, paint_region);
+}
+
+static void
+paint_all (MetaScreen *screen,
+ XserverRegion region)
+{
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ int screen_width, screen_height;
+
+ /* Set clipping to the given region */
+ XFixesSetPictureClipRegion (xdisplay, info->root_picture, 0, 0, region);
+
+ meta_screen_get_size (screen, &screen_width, &screen_height);
+
+ if (DISPLAY_COMPOSITOR (display)->show_redraw)
+ {
+ Picture overlay;
+
+ dump_xserver_region ("paint_all", display, region);
+
+ /* Make a random colour overlay */
+ overlay = solid_picture (display, screen, TRUE, 1, /* 0.3, alpha */
+ ((double) (rand () % 100)) / 100.0,
+ ((double) (rand () % 100)) / 100.0,
+ ((double) (rand () % 100)) / 100.0);
+
+ XRenderComposite (xdisplay, PictOpOver, overlay, None, info->root_picture,
+ 0, 0, 0, 0, 0, 0, screen_width, screen_height);
+ XRenderFreePicture (xdisplay, overlay);
+ XFlush (xdisplay);
+ usleep (100 * 1000);
+ }
+
+ if (info->root_buffer == None)
+ info->root_buffer = create_root_buffer (screen);
+
+ paint_windows (screen, info->windows, info->root_buffer, region);
+
+ XFixesSetPictureClipRegion (xdisplay, info->root_buffer, 0, 0, region);
+ XRenderComposite (xdisplay, PictOpSrc, info->root_buffer, None,
+ info->root_picture, 0, 0, 0, 0, 0, 0,
+ screen_width, screen_height);
+}
+
+static void
+repair_screen (MetaScreen *screen)
+{
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+
+ if (info!=NULL && info->all_damage != None)
+ {
+ meta_error_trap_push (display);
+ paint_all (screen, info->all_damage);
+ XFixesDestroyRegion (xdisplay, info->all_damage);
+ info->all_damage = None;
+ info->clip_changed = FALSE;
+ meta_error_trap_pop (display, FALSE);
+ }
+}
+
+static void
+repair_display (MetaDisplay *display)
+{
+ GSList *screens = meta_display_get_screens (display);
+ MetaCompositorXRender *compositor = DISPLAY_COMPOSITOR (display);
+
+#ifdef USE_IDLE_REPAINT
+ if (compositor->repaint_id > 0)
+ {
+ g_source_remove (compositor->repaint_id);
+ compositor->repaint_id = 0;
+ }
+#endif
+
+ for (; screens; screens = screens->next)
+ repair_screen ((MetaScreen *) screens->data);
+}
+
+#ifdef USE_IDLE_REPAINT
+static gboolean
+compositor_idle_cb (gpointer data)
+{
+ MetaCompositorXRender *compositor = (MetaCompositorXRender *) data;
+
+ compositor->repaint_id = 0;
+ repair_display (compositor->display);
+
+ return FALSE;
+}
+
+static void
+add_repair (MetaDisplay *display)
+{
+ MetaCompositorXRender *compositor = DISPLAY_COMPOSITOR (display);
+
+ if (compositor->repaint_id > 0)
+ return;
+
+#if 1
+ compositor->repaint_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
+ compositor_idle_cb, compositor,
+ NULL);
+#else
+ /* Limit it to 50fps */
+ compositor->repaint_id = g_timeout_add_full (G_PRIORITY_HIGH, 20,
+ compositor_idle_cb, compositor,
+ NULL);
+#endif
+}
+#endif
+
+static void
+add_damage (MetaScreen *screen,
+ XserverRegion damage)
+{
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+
+ /* dump_xserver_region ("add_damage", display, damage); */
+
+ if (info != NULL && info->all_damage)
+ {
+ XFixesUnionRegion (xdisplay, info->all_damage, info->all_damage, damage);
+ XFixesDestroyRegion (xdisplay, damage);
+ }
+ else
+ info->all_damage = damage;
+
+#ifdef USE_IDLE_REPAINT
+ add_repair (display);
+#endif
+}
+
+static void
+damage_screen (MetaScreen *screen)
+{
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ XserverRegion region;
+ int width, height;
+ XRectangle r;
+
+ r.x = 0;
+ r.y = 0;
+ meta_screen_get_size (screen, &width, &height);
+ r.width = width;
+ r.height = height;
+
+ region = XFixesCreateRegion (xdisplay, &r, 1);
+ dump_xserver_region ("damage_screen", display, region);
+ add_damage (screen, region);
+}
+
+static void
+repair_win (MetaCompWindow *cw)
+{
+ MetaScreen *screen = cw->screen;
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ XserverRegion parts;
+
+ meta_error_trap_push (display);
+ if (!cw->damaged)
+ {
+ parts = win_extents (cw);
+ XDamageSubtract (xdisplay, cw->damage, None, None);
+ }
+ else
+ {
+ parts = XFixesCreateRegion (xdisplay, 0, 0);
+ XDamageSubtract (xdisplay, cw->damage, None, parts);
+ XFixesTranslateRegion (xdisplay, parts,
+ cw->attrs.x + cw->attrs.border_width,
+ cw->attrs.y + cw->attrs.border_width);
+ }
+
+ meta_error_trap_pop (display, FALSE);
+
+ dump_xserver_region ("repair_win", display, parts);
+ add_damage (screen, parts);
+ cw->damaged = TRUE;
+}
+
+static void
+free_win (MetaCompWindow *cw,
+ gboolean destroy)
+{
+ MetaDisplay *display = meta_screen_get_display (cw->screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ MetaCompScreen *info = meta_screen_get_compositor_data (cw->screen);
+
+#ifdef HAVE_NAME_WINDOW_PIXMAP
+ if (have_name_window_pixmap (display))
+ {
+ /* See comment in map_win */
+ if (cw->back_pixmap && destroy)
+ {
+ XFreePixmap (xdisplay, cw->back_pixmap);
+ cw->back_pixmap = None;
+ }
+
+ if (cw->shaded_back_pixmap && destroy)
+ {
+ XFreePixmap (xdisplay, cw->shaded_back_pixmap);
+ cw->shaded_back_pixmap = None;
+ }
+ }
+#endif
+
+ if (cw->picture)
+ {
+ XRenderFreePicture (xdisplay, cw->picture);
+ cw->picture = None;
+ }
+
+ if (cw->shadow)
+ {
+ XRenderFreePicture (xdisplay, cw->shadow);
+ cw->shadow = None;
+ }
+
+ if (cw->alpha_pict)
+ {
+ XRenderFreePicture (xdisplay, cw->alpha_pict);
+ cw->alpha_pict = None;
+ }
+
+ if (cw->shadow_pict)
+ {
+ XRenderFreePicture (xdisplay, cw->shadow_pict);
+ cw->shadow_pict = None;
+ }
+
+ if (cw->border_size)
+ {
+ XFixesDestroyRegion (xdisplay, cw->border_size);
+ cw->border_size = None;
+ }
+
+ if (cw->border_clip)
+ {
+ XFixesDestroyRegion (xdisplay, cw->border_clip);
+ cw->border_clip = None;
+ }
+
+ if (cw->extents)
+ {
+ XFixesDestroyRegion (xdisplay, cw->extents);
+ cw->extents = None;
+ }
+
+ if (destroy)
+ {
+ if (cw->damage != None) {
+ meta_error_trap_push (display);
+ XDamageDestroy (xdisplay, cw->damage);
+ meta_error_trap_pop (display, FALSE);
+
+ cw->damage = None;
+ }
+
+ /* The window may not have been added to the list in this case,
+ but we can check anyway */
+ if (info!=NULL && cw->type == META_COMP_WINDOW_DOCK)
+ info->dock_windows = g_slist_remove (info->dock_windows, cw);
+
+ g_free (cw);
+ }
+}
+
+static void
+map_win (MetaDisplay *display,
+ MetaScreen *screen,
+ Window id)
+{
+ MetaCompWindow *cw = find_window_for_screen (screen, id);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+
+ if (cw == NULL)
+ return;
+
+#ifdef HAVE_NAME_WINDOW_PIXMAP
+ /* The reason we deallocate this here and not in unmap
+ is so that we will still have a valid pixmap for
+ whenever the window is unmapped */
+ if (cw->back_pixmap)
+ {
+ XFreePixmap (xdisplay, cw->back_pixmap);
+ cw->back_pixmap = None;
+ }
+
+ if (cw->shaded_back_pixmap)
+ {
+ XFreePixmap (xdisplay, cw->shaded_back_pixmap);
+ cw->shaded_back_pixmap = None;
+ }
+#endif
+
+ cw->attrs.map_state = IsViewable;
+ cw->damaged = FALSE;
+}
+
+static void
+unmap_win (MetaDisplay *display,
+ MetaScreen *screen,
+ Window id)
+{
+ MetaCompWindow *cw = find_window_for_screen (screen, id);
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+
+ if (cw == NULL || info == NULL)
+ {
+ return;
+ }
+
+ if (cw->window && cw->window == info->focus_window)
+ info->focus_window = NULL;
+
+ cw->attrs.map_state = IsUnmapped;
+ cw->damaged = FALSE;
+
+ if (cw->extents != None)
+ {
+ dump_xserver_region ("unmap_win", display, cw->extents);
+ add_damage (screen, cw->extents);
+ cw->extents = None;
+ }
+
+ free_win (cw, FALSE);
+ info->clip_changed = TRUE;
+}
+
+static void
+determine_mode (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaCompWindow *cw)
+{
+ XRenderPictFormat *format;
+ Display *xdisplay = meta_display_get_xdisplay (display);
+
+ if (cw->alpha_pict)
+ {
+ XRenderFreePicture (xdisplay, cw->alpha_pict);
+ cw->alpha_pict = None;
+ }
+
+ if (cw->shadow_pict)
+ {
+ XRenderFreePicture (xdisplay, cw->shadow_pict);
+ cw->shadow_pict = None;
+ }
+
+ if (cw->attrs.class == InputOnly)
+ format = NULL;
+ else
+ format = XRenderFindVisualFormat (xdisplay, cw->attrs.visual);
+
+ if ((format && format->type == PictTypeDirect && format->direct.alphaMask)
+ || cw->opacity != (guint) OPAQUE)
+ cw->mode = WINDOW_ARGB;
+ else
+ cw->mode = WINDOW_SOLID;
+
+ if (cw->extents)
+ {
+ XserverRegion damage;
+ damage = XFixesCreateRegion (xdisplay, NULL, 0);
+ XFixesCopyRegion (xdisplay, damage, cw->extents);
+
+ dump_xserver_region ("determine_mode", display, damage);
+ add_damage (screen, damage);
+ }
+}
+
+static gboolean
+is_shaped (MetaDisplay *display,
+ Window xwindow)
+{
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ int xws, yws, xbs, ybs;
+ unsigned wws, hws, wbs, hbs;
+ int bounding_shaped, clip_shaped;
+
+ if (meta_display_has_shape (display))
+ {
+ XShapeQueryExtents (xdisplay, xwindow, &bounding_shaped,
+ &xws, &yws, &wws, &hws, &clip_shaped,
+ &xbs, &ybs, &wbs, &hbs);
+ return (bounding_shaped != 0);
+ }
+
+ return FALSE;
+}
+
+static void
+get_window_type (MetaDisplay *display,
+ MetaCompWindow *cw)
+{
+ MetaCompositorXRender *compositor = DISPLAY_COMPOSITOR (display);
+ int n_atoms;
+ Atom *atoms, type_atom;
+ int i;
+
+ type_atom = None;
+ n_atoms = 0;
+ atoms = NULL;
+
+ meta_prop_get_atom_list (display, cw->id,
+ compositor->atom_net_wm_window_type,
+ &atoms, &n_atoms);
+
+ for (i = 0; i < n_atoms; i++)
+ {
+ if (atoms[i] == compositor->atom_net_wm_window_type_dnd ||
+ atoms[i] == compositor->atom_net_wm_window_type_desktop ||
+ atoms[i] == compositor->atom_net_wm_window_type_dock ||
+ atoms[i] == compositor->atom_net_wm_window_type_toolbar ||
+ atoms[i] == compositor->atom_net_wm_window_type_menu ||
+ atoms[i] == compositor->atom_net_wm_window_type_dialog ||
+ atoms[i] == compositor->atom_net_wm_window_type_normal ||
+ atoms[i] == compositor->atom_net_wm_window_type_utility ||
+ atoms[i] == compositor->atom_net_wm_window_type_splash ||
+ atoms[i] == compositor->atom_net_wm_window_type_dropdown_menu ||
+ atoms[i] == compositor->atom_net_wm_window_type_tooltip)
+ {
+ type_atom = atoms[i];
+ break;
+ }
+ }
+
+ meta_XFree (atoms);
+
+ if (type_atom == compositor->atom_net_wm_window_type_dnd)
+ cw->type = META_COMP_WINDOW_DND;
+ else if (type_atom == compositor->atom_net_wm_window_type_desktop)
+ cw->type = META_COMP_WINDOW_DESKTOP;
+ else if (type_atom == compositor->atom_net_wm_window_type_dock)
+ cw->type = META_COMP_WINDOW_DOCK;
+ else if (type_atom == compositor->atom_net_wm_window_type_menu)
+ cw->type = META_COMP_WINDOW_MENU;
+ else if (type_atom == compositor->atom_net_wm_window_type_dropdown_menu)
+ cw->type = META_COMP_WINDOW_DROP_DOWN_MENU;
+ else if (type_atom == compositor->atom_net_wm_window_type_tooltip)
+ cw->type = META_COMP_WINDOW_TOOLTIP;
+ else
+ cw->type = META_COMP_WINDOW_NORMAL;
+
+/* meta_verbose ("Window is %d\n", cw->type); */
+}
+
+/* Must be called with an error trap in place */
+static void
+add_win (MetaScreen *screen,
+ MetaWindow *window,
+ Window xwindow)
+{
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+ MetaCompWindow *cw;
+ gulong event_mask;
+
+ if (info == NULL)
+ return;
+
+ if (xwindow == info->output)
+ return;
+
+ cw = g_new0 (MetaCompWindow, 1);
+ cw->screen = screen;
+ cw->window = window;
+ cw->id = xwindow;
+
+ if (!XGetWindowAttributes (xdisplay, xwindow, &cw->attrs))
+ {
+ g_free (cw);
+ return;
+ }
+ get_window_type (display, cw);
+
+ /* If Marco has decided not to manage this window then the input events
+ won't have been set on the window */
+ event_mask = cw->attrs.your_event_mask | PropertyChangeMask;
+
+ XSelectInput (xdisplay, xwindow, event_mask);
+
+
+#ifdef HAVE_NAME_WINDOW_PIXMAP
+ cw->back_pixmap = None;
+ cw->shaded_back_pixmap = None;
+#endif
+
+ cw->damaged = FALSE;
+ cw->shaped = is_shaped (display, xwindow);
+
+ if (cw->attrs.class == InputOnly)
+ cw->damage = None;
+ else
+ cw->damage = XDamageCreate (xdisplay, xwindow, XDamageReportNonEmpty);
+
+ cw->alpha_pict = None;
+ cw->shadow_pict = None;
+ cw->border_size = None;
+ cw->extents = None;
+ cw->shadow = None;
+ cw->shadow_dx = 0;
+ cw->shadow_dy = 0;
+ cw->shadow_width = 0;
+ cw->shadow_height = 0;
+
+ if (window && meta_window_has_focus (window))
+ cw->shadow_type = META_SHADOW_LARGE;
+ else
+ cw->shadow_type = META_SHADOW_MEDIUM;
+
+ cw->opacity = OPAQUE;
+
+ cw->border_clip = None;
+
+ determine_mode (display, screen, cw);
+ cw->needs_shadow = window_has_shadow (cw);
+
+ /* Only add the window to the list of docks if it needs a shadow */
+ if (cw->type == META_COMP_WINDOW_DOCK && cw->needs_shadow)
+ {
+ meta_verbose ("Appending %p to dock windows\n", cw);
+ info->dock_windows = g_slist_append (info->dock_windows, cw);
+ }
+
+ /* Add this to the list at the top of the stack
+ before it is mapped so that map_win can find it again */
+ info->windows = g_list_prepend (info->windows, cw);
+ g_hash_table_insert (info->windows_by_xid, (gpointer) xwindow, cw);
+
+ if (cw->attrs.map_state == IsViewable)
+ map_win (display, screen, xwindow);
+}
+
+static void
+destroy_win (MetaDisplay *display,
+ Window xwindow,
+ gboolean gone)
+{
+ MetaScreen *screen;
+ MetaCompScreen *info;
+ MetaCompWindow *cw;
+
+ cw = find_window_in_display (display, xwindow);
+
+ if (cw == NULL)
+ return;
+
+ screen = cw->screen;
+
+ if (cw->extents != None)
+ {
+ dump_xserver_region ("destroy_win", display, cw->extents);
+ add_damage (screen, cw->extents);
+ cw->extents = None;
+ }
+
+ info = meta_screen_get_compositor_data (screen);
+ if (info != NULL)
+ {
+ info->windows = g_list_remove (info->windows, (gconstpointer) cw);
+ g_hash_table_remove (info->windows_by_xid, (gpointer) xwindow);
+ }
+
+ free_win (cw, TRUE);
+}
+
+static void
+restack_win (MetaCompWindow *cw,
+ Window above)
+{
+ MetaScreen *screen;
+ MetaCompScreen *info;
+ Window previous_above;
+ GList *sibling, *next;
+
+ screen = cw->screen;
+ info = meta_screen_get_compositor_data (screen);
+
+ if (info == NULL)
+ {
+ return;
+ }
+
+ sibling = g_list_find (info->windows, (gconstpointer) cw);
+ next = g_list_next (sibling);
+ previous_above = None;
+
+ if (next)
+ {
+ MetaCompWindow *ncw = (MetaCompWindow *) next->data;
+ previous_above = ncw->id;
+ }
+
+ /* If above is set to None, the window whose state was changed is on
+ * the bottom of the stack with respect to sibling.
+ */
+ if (above == None)
+ {
+ /* Insert at bottom of window stack */
+ info->windows = g_list_delete_link (info->windows, sibling);
+ info->windows = g_list_append (info->windows, cw);
+ }
+ else if (previous_above != above)
+ {
+ GList *index;
+
+ for (index = info->windows; index; index = index->next) {
+ MetaCompWindow *cw2 = (MetaCompWindow *) index->data;
+ if (cw2->id == above)
+ break;
+ }
+
+ if (index != NULL)
+ {
+ info->windows = g_list_delete_link (info->windows, sibling);
+ info->windows = g_list_insert_before (info->windows, index, cw);
+ }
+ }
+}
+
+static void
+resize_win (MetaCompWindow *cw,
+ int x,
+ int y,
+ int width,
+ int height,
+ int border_width,
+ gboolean override_redirect)
+{
+ MetaScreen *screen = cw->screen;
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+ XserverRegion damage;
+ gboolean debug;
+
+ debug = DISPLAY_COMPOSITOR (display)->debug;
+
+ if (cw->extents)
+ {
+ damage = XFixesCreateRegion (xdisplay, NULL, 0);
+ XFixesCopyRegion (xdisplay, damage, cw->extents);
+ }
+ else
+ {
+ damage = None;
+ if (debug)
+ fprintf (stderr, "no extents to damage !\n");
+ }
+
+ /* { // Damage whole screen each time ! ;-)
+ XRectangle r;
+
+ r.x = 0;
+ r.y = 0;
+ meta_screen_get_size (screen, &r.width, &r.height);
+ fprintf (stderr, "Damage whole screen %d,%d (%d %d)\n",
+ r.x, r.y, r.width, r.height);
+
+ damage = XFixesCreateRegion (xdisplay, &r, 1);
+ } */
+
+ cw->attrs.x = x;
+ cw->attrs.y = y;
+
+ if (cw->attrs.width != width || cw->attrs.height != height)
+ {
+#ifdef HAVE_NAME_WINDOW_PIXMAP
+ if (have_name_window_pixmap (display))
+ {
+ if (cw->shaded_back_pixmap)
+ {
+ XFreePixmap (xdisplay, cw->shaded_back_pixmap);
+ cw->shaded_back_pixmap = None;
+ }
+
+ if (cw->back_pixmap)
+ {
+ /* If the window is shaded, we store the old backing pixmap
+ so we can return a proper image of the window */
+ if (cw->window && meta_window_is_shaded (cw->window))
+ {
+ cw->shaded_back_pixmap = cw->back_pixmap;
+ cw->back_pixmap = None;
+ }
+ else
+ {
+ XFreePixmap (xdisplay, cw->back_pixmap);
+ cw->back_pixmap = None;
+ }
+ }
+ }
+#endif
+ if (cw->picture)
+ {
+ XRenderFreePicture (xdisplay, cw->picture);
+ cw->picture = None;
+ }
+
+ if (cw->shadow)
+ {
+ XRenderFreePicture (xdisplay, cw->shadow);
+ cw->shadow = None;
+ }
+ }
+
+ cw->attrs.width = width;
+ cw->attrs.height = height;
+ cw->attrs.border_width = border_width;
+ cw->attrs.override_redirect = override_redirect;
+
+ if (cw->extents)
+ XFixesDestroyRegion (xdisplay, cw->extents);
+
+ cw->extents = win_extents (cw);
+
+ if (damage)
+ {
+ if (debug)
+ fprintf (stderr, "Inexplicable intersection with new extents!\n");
+
+ XFixesUnionRegion (xdisplay, damage, damage, cw->extents);
+ }
+ else
+ {
+ damage = XFixesCreateRegion (xdisplay, NULL, 0);
+ XFixesCopyRegion (xdisplay, damage, cw->extents);
+ }
+
+ dump_xserver_region ("resize_win", display, damage);
+ add_damage (screen, damage);
+
+ if (info != NULL)
+ {
+ info->clip_changed = TRUE;
+ }
+}
+
+/* event processors must all be called with an error trap in place */
+static void
+process_circulate_notify (MetaCompositorXRender *compositor,
+ XCirculateEvent *event)
+{
+ MetaCompWindow *cw = find_window_in_display (compositor->display,
+ event->window);
+ MetaCompWindow *top;
+ MetaCompScreen *info;
+ MetaScreen *screen;
+ GList *first;
+ Window above;
+
+ if (!cw)
+ return;
+
+ screen = cw->screen;
+ info = meta_screen_get_compositor_data (screen);
+ first = info->windows;
+ top = (MetaCompWindow *) first->data;
+
+ if ((event->place == PlaceOnTop) && top)
+ above = top->id;
+ else
+ above = None;
+ restack_win (cw, above);
+
+ if (info != NULL)
+ {
+ info->clip_changed = TRUE;
+ }
+
+#ifdef USE_IDLE_REPAINT
+ add_repair (compositor->display);
+#endif
+}
+
+static void
+process_configure_notify (MetaCompositorXRender *compositor,
+ XConfigureEvent *event)
+{
+ MetaDisplay *display = compositor->display;
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ MetaCompWindow *cw = find_window_in_display (display, event->window);
+
+ if (cw)
+ {
+#if 0
+ int x = -1, y = -1, width = -1, height = -1;
+ int ex = -1, ey = -1, ewidth = -1, eheight = -1;
+ MetaRectangle *rect;
+
+ if (cw->window) {
+ rect = meta_window_get_rect (cw->window);
+ x = rect->x;
+ y = rect->y;
+ width = rect->width;
+ height = rect->height;
+ }
+ fprintf (stderr, "configure notify xy (%d %d) -> (%d %d), wh (%d %d) -> (%d %d)\n",
+ x, y, event->x, event->y,
+ width, height, event->width, event->height);
+#endif
+
+ if (compositor->debug)
+ {
+ fprintf (stderr, "configure notify %d %d %d\n", cw->damaged,
+ cw->shaped, cw->needs_shadow);
+ dump_xserver_region ("\textents", display, cw->extents);
+ fprintf (stderr, "\txy (%d %d), wh (%d %d)\n",
+ event->x, event->y, event->width, event->height);
+ }
+
+ restack_win (cw, event->above);
+ resize_win (cw, event->x, event->y, event->width, event->height,
+ event->border_width, event->override_redirect);
+ }
+ else
+ {
+ MetaScreen *screen;
+ MetaCompScreen *info;
+
+ /* Might be the root window? */
+ screen = meta_display_screen_for_root (display, event->window);
+ if (screen == NULL)
+ return;
+
+ info = meta_screen_get_compositor_data (screen);
+ if (info != NULL && info->root_buffer)
+ {
+ XRenderFreePicture (xdisplay, info->root_buffer);
+ info->root_buffer = None;
+ }
+
+ damage_screen (screen);
+ }
+}
+
+static void
+process_property_notify (MetaCompositorXRender *compositor,
+ XPropertyEvent *event)
+{
+ MetaDisplay *display = compositor->display;
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ MetaScreen *screen;
+ int p;
+ Atom background_atoms[2];
+
+ /* Check for the background property changing */
+ background_atoms[0] = compositor->atom_x_root_pixmap;
+ background_atoms[1] = compositor->atom_x_set_root;
+
+ for (p = 0; p < 2; p++)
+ {
+ if (event->atom == background_atoms[p])
+ {
+ screen = meta_display_screen_for_root (display, event->window);
+ if (screen)
+ {
+ MetaCompScreen *info = meta_screen_get_compositor_data (screen);
+ Window xroot = meta_screen_get_xroot (screen);
+
+ if (info != NULL && info->root_tile)
+ {
+ XClearArea (xdisplay, xroot, 0, 0, 0, 0, TRUE);
+ XRenderFreePicture (xdisplay, info->root_tile);
+ info->root_tile = None;
+
+ /* Damage the whole screen as we may need to redraw the
+ background ourselves */
+ damage_screen (screen);
+#ifdef USE_IDLE_REPAINT
+ add_repair (display);
+#endif
+
+ return;
+ }
+ }
+ }
+ }
+
+ /* Check for the opacity changing */
+ if (event->atom == compositor->atom_net_wm_window_opacity)
+ {
+ MetaCompWindow *cw = find_window_in_display (display, event->window);
+ gulong value;
+
+ if (!cw)
+ {
+ /* Applications can set this for their toplevel windows, so
+ * this must be propagated to the window managed by the compositor
+ */
+ cw = find_window_for_child_window_in_display (display, event->window);
+ }
+
+ if (!cw)
+ return;
+
+ if (meta_prop_get_cardinal (display, event->window,
+ compositor->atom_net_wm_window_opacity,
+ &value) == FALSE)
+ value = OPAQUE;
+
+ cw->opacity = (guint)value;
+ determine_mode (display, cw->screen, cw);
+ cw->needs_shadow = window_has_shadow (cw);
+
+ if (cw->shadow)
+ {
+ XRenderFreePicture (xdisplay, cw->shadow);
+ cw->shadow = None;
+ }
+
+ if (cw->extents)
+ XFixesDestroyRegion (xdisplay, cw->extents);
+ cw->extents = win_extents (cw);
+
+ cw->damaged = TRUE;
+#ifdef USE_IDLE_REPAINT
+ add_repair (display);
+#endif
+
+ return;
+ }
+
+ if (event->atom == compositor->atom_net_wm_window_type) {
+ MetaCompWindow *cw = find_window_in_display (display, event->window);
+
+ if (!cw)
+ return;
+
+ get_window_type (display, cw);
+ cw->needs_shadow = window_has_shadow (cw);
+ return;
+ }
+}
+
+static void
+expose_area (MetaScreen *screen,
+ XRectangle *rects,
+ int nrects)
+{
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ XserverRegion region;
+
+ region = XFixesCreateRegion (xdisplay, rects, nrects);
+
+ dump_xserver_region ("expose_area", display, region);
+ add_damage (screen, region);
+}
+
+static void
+process_expose (MetaCompositorXRender *compositor,
+ XExposeEvent *event)
+{
+ MetaCompWindow *cw = find_window_in_display (compositor->display,
+ event->window);
+ MetaScreen *screen = NULL;
+ XRectangle rect[1];
+ int origin_x = 0, origin_y = 0;
+
+ if (cw != NULL)
+ {
+ screen = cw->screen;
+ origin_x = cw->attrs.x; /* + cw->attrs.border_width; ? */
+ origin_y = cw->attrs.y; /* + cw->attrs.border_width; ? */
+ }
+ else
+ {
+ screen = meta_display_screen_for_root (compositor->display,
+ event->window);
+ if (screen == NULL)
+ return;
+ }
+
+ rect[0].x = event->x + origin_x;
+ rect[0].y = event->y + origin_y;
+ rect[0].width = event->width;
+ rect[0].height = event->height;
+
+ expose_area (screen, rect, 1);
+}
+
+static void
+process_unmap (MetaCompositorXRender *compositor,
+ XUnmapEvent *event)
+{
+ MetaCompWindow *cw;
+
+ if (event->from_configure)
+ {
+ /* Ignore unmap caused by parent's resize */
+ return;
+ }
+
+
+ cw = find_window_in_display (compositor->display, event->window);
+ if (cw)
+ unmap_win (compositor->display, cw->screen, event->window);
+}
+
+static void
+process_map (MetaCompositorXRender *compositor,
+ XMapEvent *event)
+{
+ MetaCompWindow *cw = find_window_in_display (compositor->display,
+ event->window);
+
+ if (cw)
+ map_win (compositor->display, cw->screen, event->window);
+}
+
+static void
+process_reparent (MetaCompositorXRender *compositor,
+ XReparentEvent *event,
+ MetaWindow *window)
+{
+ MetaScreen *screen;
+
+ screen = meta_display_screen_for_root (compositor->display, event->parent);
+ if (screen != NULL)
+ add_win (screen, window, event->window);
+ else
+ destroy_win (compositor->display, event->window, FALSE);
+}
+
+static void
+process_create (MetaCompositorXRender *compositor,
+ XCreateWindowEvent *event,
+ MetaWindow *window)
+{
+ MetaScreen *screen;
+ /* We are only interested in top level windows, others will
+ be caught by normal marco functions */
+
+ screen = meta_display_screen_for_root (compositor->display, event->parent);
+ if (screen == NULL)
+ return;
+
+ if (!find_window_in_display (compositor->display, event->window))
+ add_win (screen, window, event->window);
+}
+
+static void
+process_destroy (MetaCompositorXRender *compositor,
+ XDestroyWindowEvent *event)
+{
+ destroy_win (compositor->display, event->window, FALSE);
+}
+
+static void
+process_damage (MetaCompositorXRender *compositor,
+ XDamageNotifyEvent *event)
+{
+ MetaCompWindow *cw = find_window_in_display (compositor->display,
+ event->drawable);
+ if (cw == NULL)
+ return;
+
+ repair_win (cw);
+
+#ifdef USE_IDLE_REPAINT
+ if (event->more == FALSE)
+ add_repair (compositor->display);
+#endif
+}
+
+static void
+process_shape (MetaCompositorXRender *compositor,
+ XShapeEvent *event)
+{
+ MetaCompWindow *cw = find_window_in_display (compositor->display,
+ event->window);
+
+ if (cw == NULL)
+ return;
+
+ if (event->kind == ShapeBounding)
+ {
+ if (!event->shaped && cw->shaped)
+ cw->shaped = FALSE;
+
+ resize_win (cw, cw->attrs.x, cw->attrs.y,
+ event->width + event->x, event->height + event->y,
+ cw->attrs.border_width, cw->attrs.override_redirect);
+
+ if (event->shaped && !cw->shaped)
+ cw->shaped = TRUE;
+ }
+}
+
+static int
+timeout_debug (MetaCompositorXRender *compositor)
+{
+ compositor->show_redraw = (g_getenv ("MARCO_DEBUG_REDRAWS") != NULL);
+ compositor->debug = (g_getenv ("MARCO_DEBUG_COMPOSITOR") != NULL);
+
+ return FALSE;
+}
+
+static void
+xrender_add_window (MetaCompositor *compositor,
+ MetaWindow *window,
+ Window xwindow,
+ XWindowAttributes *attrs)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ MetaCompositorXRender *xrc = (MetaCompositorXRender *) compositor;
+ MetaScreen *screen = meta_screen_for_x_screen (attrs->screen);
+
+ meta_error_trap_push (xrc->display);
+ add_win (screen, window, xwindow);
+ meta_error_trap_pop (xrc->display, FALSE);
+#endif
+}
+
+static void
+xrender_remove_window (MetaCompositor *compositor,
+ Window xwindow)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+#endif
+}
+
+static void
+show_overlay_window (MetaScreen *screen,
+ Window cow)
+{
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+
+#ifdef HAVE_COW
+ if (have_cow (display))
+ {
+ XserverRegion region;
+
+ region = XFixesCreateRegion (xdisplay, NULL, 0);
+
+ XFixesSetWindowShapeRegion (xdisplay, cow, ShapeBounding, 0, 0, 0);
+ XFixesSetWindowShapeRegion (xdisplay, cow, ShapeInput, 0, 0, region);
+
+ XFixesDestroyRegion (xdisplay, region);
+
+ damage_screen (screen);
+ }
+#endif
+}
+
+static void
+hide_overlay_window (MetaScreen *screen,
+ Window cow)
+{
+#ifdef HAVE_COW
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ XserverRegion region;
+
+ region = XFixesCreateRegion (xdisplay, NULL, 0);
+ XFixesSetWindowShapeRegion (xdisplay, cow, ShapeBounding, 0, 0, region);
+ XFixesDestroyRegion (xdisplay, region);
+#endif
+}
+
+static Window
+get_output_window (MetaScreen *screen)
+{
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ Window output, xroot;
+
+ xroot = meta_screen_get_xroot (screen);
+
+#ifdef HAVE_COW
+ if (have_cow (display))
+ {
+ output = XCompositeGetOverlayWindow (xdisplay, xroot);
+ XSelectInput (xdisplay, output, ExposureMask);
+ }
+ else
+#endif
+ {
+ output = xroot;
+ }
+
+ return output;
+}
+
+static void
+xrender_manage_screen (MetaCompositor *compositor,
+ MetaScreen *screen)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ MetaCompScreen *info;
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ XRenderPictureAttributes pa;
+ XRenderPictFormat *visual_format;
+ int screen_number = meta_screen_get_screen_number (screen);
+ Window xroot = meta_screen_get_xroot (screen);
+
+ /* Check if the screen is already managed */
+ if (meta_screen_get_compositor_data (screen))
+ return;
+
+ gdk_error_trap_push ();
+ XCompositeRedirectSubwindows (xdisplay, xroot, CompositeRedirectManual);
+ XSync (xdisplay, FALSE);
+
+ if (gdk_error_trap_pop ())
+ {
+ g_warning ("Another compositing manager is running on screen %i",
+ screen_number);
+ return;
+ }
+
+ info = g_new0 (MetaCompScreen, 1);
+ info->screen = screen;
+
+ meta_screen_set_compositor_data (screen, info);
+
+ visual_format = XRenderFindVisualFormat (xdisplay, DefaultVisual (xdisplay,
+ screen_number));
+ if (!visual_format)
+ {
+ g_warning ("Cannot find visual format on screen %i", screen_number);
+ return;
+ }
+
+ info->output = get_output_window (screen);
+
+ pa.subwindow_mode = IncludeInferiors;
+ info->root_picture = XRenderCreatePicture (xdisplay, info->output,
+ visual_format,
+ CPSubwindowMode, &pa);
+ if (info->root_picture == None)
+ {
+ g_warning ("Cannot create root picture on screen %i", screen_number);
+ return;
+ }
+
+ info->root_buffer = None;
+ info->black_picture = solid_picture (display, screen, TRUE, 1, 0, 0, 0);
+
+ info->root_tile = None;
+ info->all_damage = None;
+
+ info->windows = NULL;
+ info->windows_by_xid = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ info->focus_window = meta_display_get_focus_window (display);
+
+ info->compositor_active = TRUE;
+ info->overlays = 0;
+ info->clip_changed = TRUE;
+
+ info->have_shadows = (g_getenv("META_DEBUG_NO_SHADOW") == NULL);
+ if (info->have_shadows)
+ {
+ meta_verbose ("Enabling shadows\n");
+ generate_shadows (info);
+ }
+ else
+ meta_verbose ("Disabling shadows\n");
+
+ XClearArea (xdisplay, info->output, 0, 0, 0, 0, TRUE);
+
+ meta_screen_set_cm_selection (screen);
+
+ /* Now we're up and running we can show the output if needed */
+ show_overlay_window (screen, info->output);
+#endif
+}
+
+static void
+xrender_unmanage_screen (MetaCompositor *compositor,
+ MetaScreen *screen)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ MetaDisplay *display = meta_screen_get_display (screen);
+ Display *xdisplay = meta_display_get_xdisplay (display);
+ MetaCompScreen *info;
+ Window xroot = meta_screen_get_xroot (screen);
+ GList *index;
+
+ info = meta_screen_get_compositor_data (screen);
+
+ /* This screen isn't managed */
+ if (info == NULL)
+ return;
+
+ hide_overlay_window (screen, info->output);
+
+ /* Destroy the windows */
+ for (index = info->windows; index; index = index->next)
+ {
+ MetaCompWindow *cw = (MetaCompWindow *) index->data;
+ free_win (cw, TRUE);
+ }
+ g_list_free (info->windows);
+ g_hash_table_destroy (info->windows_by_xid);
+
+ if (info->root_picture)
+ XRenderFreePicture (xdisplay, info->root_picture);
+
+ if (info->black_picture)
+ XRenderFreePicture (xdisplay, info->black_picture);
+
+ if (info->have_shadows)
+ {
+ int i;
+
+ for (i = 0; i < LAST_SHADOW_TYPE; i++)
+ g_free (info->shadows[i]->gaussian_map);
+ }
+
+ XCompositeUnredirectSubwindows (xdisplay, xroot,
+ CompositeRedirectManual);
+ meta_screen_unset_cm_selection (screen);
+
+#ifdef HAVE_COW
+ XCompositeReleaseOverlayWindow (xdisplay, info->output);
+#endif
+
+ g_free (info);
+
+ meta_screen_set_compositor_data (screen, NULL);
+#endif
+}
+
+static void
+xrender_set_updates (MetaCompositor *compositor,
+ MetaWindow *window,
+ gboolean updates)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+
+#endif
+}
+
+static void
+xrender_destroy (MetaCompositor *compositor)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ g_free (compositor);
+#endif
+}
+
+#if 0
+/* Taking these out because they're empty and never called, and the
+ * compiler complains -- tthurman
+ */
+
+static void
+xrender_begin_move (MetaCompositor *compositor,
+ MetaWindow *window,
+ MetaRectangle *initial,
+ int grab_x,
+ int grab_y)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+#endif
+}
+
+static void
+xrender_update_move (MetaCompositor *compositor,
+ MetaWindow *window,
+ int x,
+ int y)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+#endif
+}
+
+static void
+xrender_end_move (MetaCompositor *compositor,
+ MetaWindow *window)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+#endif
+}
+
+static void
+xrender_free_window (MetaCompositor *compositor,
+ MetaWindow *window)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ /* FIXME: When an undecorated window is hidden this is called,
+ but the window does not get readded if it is subsequentally shown again
+ See http://bugzilla.gnome.org/show_bug.cgi?id=504876
+
+ I don't *think* theres any need for this call anyway, leaving it out
+ does not seem to cause any side effects so far, but I should check with
+ someone who understands more. */
+ /* destroy_win (compositor->display, window->xwindow, FALSE); */
+#endif
+}
+#endif /* 0 */
+
+static void
+xrender_process_event (MetaCompositor *compositor,
+ XEvent *event,
+ MetaWindow *window)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ MetaCompositorXRender *xrc = (MetaCompositorXRender *) compositor;
+ /*
+ * This trap is so that none of the compositor functions cause
+ * X errors. This is really a hack, but I'm afraid I don't understand
+ * enough about Marco/X to know how else you are supposed to do it
+ */
+ meta_error_trap_push (xrc->display);
+ switch (event->type)
+ {
+ case CirculateNotify:
+ process_circulate_notify (xrc, (XCirculateEvent *) event);
+ break;
+
+ case ConfigureNotify:
+ process_configure_notify (xrc, (XConfigureEvent *) event);
+ break;
+
+ case PropertyNotify:
+ process_property_notify (xrc, (XPropertyEvent *) event);
+ break;
+
+ case Expose:
+ process_expose (xrc, (XExposeEvent *) event);
+ break;
+
+ case UnmapNotify:
+ process_unmap (xrc, (XUnmapEvent *) event);
+ break;
+
+ case MapNotify:
+ process_map (xrc, (XMapEvent *) event);
+ break;
+
+ case ReparentNotify:
+ process_reparent (xrc, (XReparentEvent *) event, window);
+ break;
+
+ case CreateNotify:
+ process_create (xrc, (XCreateWindowEvent *) event, window);
+ break;
+
+ case DestroyNotify:
+ process_destroy (xrc, (XDestroyWindowEvent *) event);
+ break;
+
+ default:
+ if (event->type == meta_display_get_damage_event_base (xrc->display) + XDamageNotify)
+ process_damage (xrc, (XDamageNotifyEvent *) event);
+#ifdef HAVE_SHAPE
+ else if (event->type == meta_display_get_shape_event_base (xrc->display) + ShapeNotify)
+ process_shape (xrc, (XShapeEvent *) event);
+#endif /* HAVE_SHAPE */
+ else
+ {
+ meta_error_trap_pop (xrc->display, FALSE);
+ return;
+ }
+ break;
+ }
+
+ meta_error_trap_pop (xrc->display, FALSE);
+#ifndef USE_IDLE_REPAINT
+ repair_display (xrc->display);
+#endif
+
+ return;
+#endif
+}
+
+static Pixmap
+xrender_get_window_pixmap (MetaCompositor *compositor,
+ MetaWindow *window)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ MetaCompWindow *cw = NULL;
+ MetaScreen *screen = meta_window_get_screen (window);
+ MetaFrame *frame = meta_window_get_frame (window);
+
+ cw = find_window_for_screen (screen, frame ? meta_frame_get_xwindow (frame) :
+ meta_window_get_xwindow (window));
+ if (cw == NULL)
+ return None;
+
+#ifdef HAVE_NAME_WINDOW_PIXMAP
+ if (have_name_window_pixmap (meta_window_get_display (window)))
+ {
+ if (meta_window_is_shaded (window))
+ return cw->shaded_back_pixmap;
+ else
+ return cw->back_pixmap;
+ }
+ else
+#endif
+ return None;
+#endif
+}
+
+static void
+xrender_set_active_window (MetaCompositor *compositor,
+ MetaScreen *screen,
+ MetaWindow *window)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ MetaCompositorXRender *xrc = (MetaCompositorXRender *) compositor;
+ MetaDisplay *display;
+ Display *xdisplay;
+ MetaCompWindow *old_focus = NULL, *new_focus = NULL;
+ MetaCompScreen *info = NULL;
+ MetaWindow *old_focus_win = NULL;
+
+ if (compositor == NULL)
+ return;
+
+ display = xrc->display;
+ xdisplay = meta_display_get_xdisplay (display);
+ info = meta_screen_get_compositor_data (screen);
+
+ if (info != NULL)
+ {
+ old_focus_win = info->focus_window;
+ }
+
+ if (old_focus_win)
+ {
+ MetaFrame *f = meta_window_get_frame (old_focus_win);
+
+ old_focus = find_window_for_screen (screen,
+ f ? meta_frame_get_xwindow (f) :
+ meta_window_get_xwindow (old_focus_win));
+ }
+
+ if (window)
+ {
+ MetaFrame *f = meta_window_get_frame (window);
+ new_focus = find_window_for_screen (screen,
+ f ? meta_frame_get_xwindow (f) :
+ meta_window_get_xwindow (window));
+ }
+
+ if (info != NULL)
+ {
+ info->focus_window = window;
+ }
+
+ if (old_focus)
+ {
+ XserverRegion damage;
+
+ /* Tear down old shadows */
+ old_focus->shadow_type = META_SHADOW_MEDIUM;
+ determine_mode (display, screen, old_focus);
+ old_focus->needs_shadow = window_has_shadow (old_focus);
+
+ if (old_focus->attrs.map_state == IsViewable)
+ {
+ if (old_focus->shadow)
+ {
+ XRenderFreePicture (xdisplay, old_focus->shadow);
+ old_focus->shadow = None;
+ }
+
+ if (old_focus->extents)
+ {
+ damage = XFixesCreateRegion (xdisplay, NULL, 0);
+ XFixesCopyRegion (xdisplay, damage, old_focus->extents);
+ XFixesDestroyRegion (xdisplay, old_focus->extents);
+ }
+ else
+ damage = None;
+
+ /* Build new extents */
+ old_focus->extents = win_extents (old_focus);
+
+ if (damage)
+ XFixesUnionRegion (xdisplay, damage, damage, old_focus->extents);
+ else
+ {
+ damage = XFixesCreateRegion (xdisplay, NULL, 0);
+ XFixesCopyRegion (xdisplay, damage, old_focus->extents);
+ }
+
+ dump_xserver_region ("resize_win", display, damage);
+ add_damage (screen, damage);
+
+ if (info != NULL)
+ {
+ info->clip_changed = TRUE;
+ }
+ }
+ }
+
+ if (new_focus)
+ {
+ XserverRegion damage;
+
+ new_focus->shadow_type = META_SHADOW_LARGE;
+ determine_mode (display, screen, new_focus);
+ new_focus->needs_shadow = window_has_shadow (new_focus);
+
+ if (new_focus->shadow)
+ {
+ XRenderFreePicture (xdisplay, new_focus->shadow);
+ new_focus->shadow = None;
+ }
+
+ if (new_focus->extents)
+ {
+ damage = XFixesCreateRegion (xdisplay, NULL, 0);
+ XFixesCopyRegion (xdisplay, damage, new_focus->extents);
+ XFixesDestroyRegion (xdisplay, new_focus->extents);
+ }
+ else
+ damage = None;
+
+ /* Build new extents */
+ new_focus->extents = win_extents (new_focus);
+
+ if (damage)
+ XFixesUnionRegion (xdisplay, damage, damage, new_focus->extents);
+ else
+ {
+ damage = XFixesCreateRegion (xdisplay, NULL, 0);
+ XFixesCopyRegion (xdisplay, damage, new_focus->extents);
+ }
+
+ dump_xserver_region ("resize_win", display, damage);
+ add_damage (screen, damage);
+
+ if (info != NULL)
+ {
+ info->clip_changed = TRUE;
+ }
+ }
+#ifdef USE_IDLE_REPAINT
+ add_repair (display);
+#endif
+#endif
+}
+
+static MetaCompositor comp_info = {
+ xrender_destroy,
+ xrender_manage_screen,
+ xrender_unmanage_screen,
+ xrender_add_window,
+ xrender_remove_window,
+ xrender_set_updates,
+ xrender_process_event,
+ xrender_get_window_pixmap,
+ xrender_set_active_window
+};
+
+MetaCompositor *
+meta_compositor_xrender_new (MetaDisplay *display)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ char *atom_names[] = {
+ "_XROOTPMAP_ID",
+ "_XSETROOT_ID",
+ "_NET_WM_WINDOW_OPACITY",
+ "_NET_WM_WINDOW_TYPE_DND",
+ "_NET_WM_WINDOW_TYPE",
+ "_NET_WM_WINDOW_TYPE_DESKTOP",
+ "_NET_WM_WINDOW_TYPE_DOCK",
+ "_NET_WM_WINDOW_TYPE_MENU",
+ "_NET_WM_WINDOW_TYPE_DIALOG",
+ "_NET_WM_WINDOW_TYPE_NORMAL",
+ "_NET_WM_WINDOW_TYPE_UTILITY",
+ "_NET_WM_WINDOW_TYPE_SPLASH",
+ "_NET_WM_WINDOW_TYPE_TOOLBAR",
+ "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
+ "_NET_WM_WINDOW_TYPE_TOOLTIP"
+ };
+ Atom atoms[G_N_ELEMENTS(atom_names)];
+ MetaCompositorXRender *xrc;
+ MetaCompositor *compositor;
+ Display *xdisplay = meta_display_get_xdisplay (display);
+
+ xrc = g_new (MetaCompositorXRender, 1);
+ xrc->compositor = comp_info;
+
+ compositor = (MetaCompositor *) xrc;
+
+ xrc->display = display;
+
+ meta_verbose ("Creating %d atoms\n", (int) G_N_ELEMENTS (atom_names));
+ XInternAtoms (xdisplay, atom_names, G_N_ELEMENTS (atom_names),
+ False, atoms);
+
+ xrc->atom_x_root_pixmap = atoms[0];
+ xrc->atom_x_set_root = atoms[1];
+ xrc->atom_net_wm_window_opacity = atoms[2];
+ xrc->atom_net_wm_window_type_dnd = atoms[3];
+ xrc->atom_net_wm_window_type = atoms[4];
+ xrc->atom_net_wm_window_type_desktop = atoms[5];
+ xrc->atom_net_wm_window_type_dock = atoms[6];
+ xrc->atom_net_wm_window_type_menu = atoms[7];
+ xrc->atom_net_wm_window_type_dialog = atoms[8];
+ xrc->atom_net_wm_window_type_normal = atoms[9];
+ xrc->atom_net_wm_window_type_utility = atoms[10];
+ xrc->atom_net_wm_window_type_splash = atoms[11];
+ xrc->atom_net_wm_window_type_toolbar = atoms[12];
+ xrc->atom_net_wm_window_type_dropdown_menu = atoms[13];
+ xrc->atom_net_wm_window_type_tooltip = atoms[14];
+
+#ifdef USE_IDLE_REPAINT
+ meta_verbose ("Using idle repaint\n");
+ xrc->repaint_id = 0;
+#endif
+
+ xrc->enabled = TRUE;
+ g_timeout_add (2000, (GSourceFunc) timeout_debug, xrc);
+
+ return compositor;
+#else
+ return NULL;
+#endif
+}
+
+#endif /* HAVE_COMPOSITE_EXTENSIONS */
+
diff --git a/src/compositor/compositor-xrender.h b/src/compositor/compositor-xrender.h
new file mode 100644
index 00000000..5c8a36c0
--- /dev/null
+++ b/src/compositor/compositor-xrender.h
@@ -0,0 +1,31 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2007 Iain Holmes
+ * Based on xcompmgr - (c) 2003 Keith Packard
+ * xfwm4 - (c) 2005-2007 Olivier Fourdan
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_COMPOSITOR_XRENDER_H_
+#define META_COMPOSITOR_XRENDER_H_
+
+#include "types.h"
+
+MetaCompositor *meta_compositor_xrender_new (MetaDisplay *display);
+
+#endif
diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c
new file mode 100644
index 00000000..975bf341
--- /dev/null
+++ b/src/compositor/compositor.c
@@ -0,0 +1,159 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2008 Iain Holmes
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "compositor-private.h"
+#include "compositor-xrender.h"
+
+MetaCompositor *
+meta_compositor_new (MetaDisplay *display)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ /* At some point we would have a way to select between backends */
+ return meta_compositor_xrender_new (display);
+#else
+ return NULL;
+#endif
+}
+
+void
+meta_compositor_destroy (MetaCompositor *compositor)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ if (compositor && compositor->destroy)
+ compositor->destroy (compositor);
+#endif
+}
+
+void
+meta_compositor_add_window (MetaCompositor *compositor,
+ MetaWindow *window,
+ Window xwindow,
+ XWindowAttributes *attrs)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ if (compositor && compositor->add_window)
+ compositor->add_window (compositor, window, xwindow, attrs);
+#endif
+}
+
+void
+meta_compositor_remove_window (MetaCompositor *compositor,
+ Window xwindow)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ if (compositor && compositor->remove_window)
+ compositor->remove_window (compositor, xwindow);
+#endif
+}
+
+void
+meta_compositor_manage_screen (MetaCompositor *compositor,
+ MetaScreen *screen)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ if (compositor && compositor->manage_screen)
+ compositor->manage_screen (compositor, screen);
+#endif
+}
+
+void
+meta_compositor_unmanage_screen (MetaCompositor *compositor,
+ MetaScreen *screen)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ if (compositor && compositor->unmanage_screen)
+ compositor->unmanage_screen (compositor, screen);
+#endif
+}
+
+void
+meta_compositor_set_updates (MetaCompositor *compositor,
+ MetaWindow *window,
+ gboolean updates)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ if (compositor && compositor->set_updates)
+ compositor->set_updates (compositor, window, updates);
+#endif
+}
+
+void
+meta_compositor_process_event (MetaCompositor *compositor,
+ XEvent *event,
+ MetaWindow *window)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ if (compositor && compositor->process_event)
+ compositor->process_event (compositor, event, window);
+#endif
+}
+
+Pixmap
+meta_compositor_get_window_pixmap (MetaCompositor *compositor,
+ MetaWindow *window)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ if (compositor && compositor->get_window_pixmap)
+ return compositor->get_window_pixmap (compositor, window);
+ else
+ return None;
+#else
+ return None;
+#endif
+}
+
+void
+meta_compositor_set_active_window (MetaCompositor *compositor,
+ MetaScreen *screen,
+ MetaWindow *window)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ if (compositor && compositor->set_active_window)
+ compositor->set_active_window (compositor, screen, window);
+#endif
+}
+
+/* These functions are unused at the moment */
+void meta_compositor_begin_move (MetaCompositor *compositor,
+ MetaWindow *window,
+ MetaRectangle *initial,
+ int grab_x,
+ int grab_y)
+{
+}
+
+void meta_compositor_update_move (MetaCompositor *compositor,
+ MetaWindow *window,
+ int x,
+ int y)
+{
+}
+
+void meta_compositor_end_move (MetaCompositor *compositor,
+ MetaWindow *window)
+{
+}
+
+void meta_compositor_free_window (MetaCompositor *compositor,
+ MetaWindow *window)
+{
+}
diff --git a/src/core/async-getprop.c b/src/core/async-getprop.c
new file mode 100644
index 00000000..80322b41
--- /dev/null
+++ b/src/core/async-getprop.c
@@ -0,0 +1,680 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Asynchronous X property getting hack */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ * Copyright (C) 1986, 1998 The Open Group
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation.
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the name of The Open Group shall not be
+ * used in advertising or otherwise to promote the sale, use or other dealings
+ * in this Software without prior written authorization from The Open Group.
+ */
+
+#include <assert.h>
+
+#undef DEBUG_SPEW
+#ifdef DEBUG_SPEW
+#include <stdio.h>
+#endif
+
+#include "async-getprop.h"
+
+#define NEED_REPLIES
+#include <X11/Xlibint.h>
+
+#ifndef NULL
+#define NULL ((void*)0)
+#endif
+
+typedef struct _ListNode ListNode;
+typedef struct _AgPerDisplayData AgPerDisplayData;
+
+struct _ListNode
+{
+ ListNode *next;
+};
+
+struct _AgGetPropertyTask
+{
+ ListNode node;
+
+ AgPerDisplayData *dd;
+ Window window;
+ Atom property;
+
+ unsigned long request_seq;
+ int error;
+
+ Atom actual_type;
+ int actual_format;
+
+ unsigned long n_items;
+ unsigned long bytes_after;
+ char *data;
+
+ Bool have_reply;
+};
+
+struct _AgPerDisplayData
+{
+ ListNode node;
+ _XAsyncHandler async;
+
+ Display *display;
+ ListNode *pending_tasks;
+ ListNode *pending_tasks_tail;
+ ListNode *completed_tasks;
+ ListNode *completed_tasks_tail;
+ int n_tasks_pending;
+ int n_tasks_completed;
+};
+
+static ListNode *display_datas = NULL;
+static ListNode *display_datas_tail = NULL;
+
+static void
+append_to_list (ListNode **head,
+ ListNode **tail,
+ ListNode *task)
+{
+ task->next = NULL;
+
+ if (*tail == NULL)
+ {
+ assert (*head == NULL);
+ *head = task;
+ *tail = task;
+ }
+ else
+ {
+ (*tail)->next = task;
+ *tail = task;
+ }
+}
+
+static void
+remove_from_list (ListNode **head,
+ ListNode **tail,
+ ListNode *task)
+{
+ ListNode *prev;
+ ListNode *node;
+
+ prev = NULL;
+ node = *head;
+ while (node != NULL)
+ {
+ if (node == task)
+ {
+ if (prev)
+ prev->next = node->next;
+ else
+ *head = node->next;
+
+ if (node == *tail)
+ *tail = prev;
+
+ break;
+ }
+
+ prev = node;
+ node = node->next;
+ }
+
+ /* can't remove what's not there */
+ assert (node != NULL);
+
+ node->next = NULL;
+}
+
+static void
+move_to_completed (AgPerDisplayData *dd,
+ AgGetPropertyTask *task)
+{
+ remove_from_list (&dd->pending_tasks,
+ &dd->pending_tasks_tail,
+ &task->node);
+
+ append_to_list (&dd->completed_tasks,
+ &dd->completed_tasks_tail,
+ &task->node);
+
+ dd->n_tasks_pending -= 1;
+ dd->n_tasks_completed += 1;
+}
+
+static AgGetPropertyTask*
+find_pending_by_request_sequence (AgPerDisplayData *dd,
+ unsigned long request_seq)
+{
+ ListNode *node;
+
+ /* if the sequence is after our last pending task, we
+ * aren't going to find a match
+ */
+ {
+ AgGetPropertyTask *task = (AgGetPropertyTask*) dd->pending_tasks_tail;
+ if (task != NULL)
+ {
+ if (task->request_seq < request_seq)
+ return NULL;
+ else if (task->request_seq == request_seq)
+ return task; /* why not check this */
+ }
+ }
+
+ /* Generally we should get replies in the order we sent
+ * requests, so we should usually be using the task
+ * at the head of the list, if we use any task at all.
+ * I'm not sure this is 100% guaranteed, if it is,
+ * it would be a big speedup.
+ */
+
+ node = dd->pending_tasks;
+ while (node != NULL)
+ {
+ AgGetPropertyTask *task = (AgGetPropertyTask*) node;
+
+ if (task->request_seq == request_seq)
+ return task;
+
+ node = node->next;
+ }
+
+ return NULL;
+}
+
+static Bool
+async_get_property_handler (Display *dpy,
+ xReply *rep,
+ char *buf,
+ int len,
+ XPointer data)
+{
+ xGetPropertyReply replbuf;
+ xGetPropertyReply *reply;
+ AgGetPropertyTask *task;
+ AgPerDisplayData *dd;
+ int bytes_read;
+
+ dd = (AgPerDisplayData*) data;
+
+#if 0
+ printf ("%s: seeing request seq %ld buflen %d\n", __FUNCTION__,
+ dpy->last_request_read, len);
+#endif
+
+ task = find_pending_by_request_sequence (dd, dpy->last_request_read);
+
+ if (task == NULL)
+ return False;
+
+ assert (dpy->last_request_read == task->request_seq);
+
+ task->have_reply = True;
+ move_to_completed (dd, task);
+
+ /* read bytes so far */
+ bytes_read = SIZEOF (xReply);
+
+ if (rep->generic.type == X_Error)
+ {
+ xError errbuf;
+
+ task->error = rep->error.errorCode;
+
+#ifdef DEBUG_SPEW
+ printf ("%s: error code = %d (ignoring error, eating %d bytes, generic.length = %ld)\n",
+ __FUNCTION__, task->error, (SIZEOF (xError) - bytes_read),
+ rep->generic.length);
+#endif
+
+ /* We return True (meaning we consumed the reply)
+ * because otherwise it would invoke the X error handler,
+ * and an async API is useless if you have to synchronously
+ * trap X errors. Also GetProperty can always fail, pretty
+ * much, so trapping errors is always what you want.
+ *
+ * We have to eat all the error reply data here.
+ * (kind of a charade as we know sizeof(xError) == sizeof(xReply))
+ *
+ * Passing discard = True seems to break things; I don't understand
+ * why, because there should be no extra data in an error reply,
+ * right?
+ */
+ _XGetAsyncReply (dpy, (char *)&errbuf, rep, buf, len,
+ (SIZEOF (xError) - bytes_read) >> 2, /* in 32-bit words */
+ False); /* really seems like it should be True */
+
+ return True;
+ }
+
+#ifdef DEBUG_SPEW
+ printf ("%s: already read %d bytes reading %d more for total of %d; generic.length = %ld\n",
+ __FUNCTION__, bytes_read, (SIZEOF (xGetPropertyReply) - bytes_read) >> 2,
+ SIZEOF (xGetPropertyReply), rep->generic.length);
+#endif
+
+ /* (kind of a silly as we know sizeof(xGetPropertyReply) == sizeof(xReply)) */
+ reply = (xGetPropertyReply *)
+ _XGetAsyncReply (dpy, (char *)&replbuf, rep, buf, len,
+ (SIZEOF (xGetPropertyReply) - bytes_read) >> 2, /* in 32-bit words */
+ False); /* False means expecting more data to follow,
+ * don't eat the rest of the reply
+ */
+
+ bytes_read = SIZEOF (xGetPropertyReply);
+
+#ifdef DEBUG_SPEW
+ printf ("%s: have reply propertyType = %ld format = %d n_items = %ld\n",
+ __FUNCTION__, reply->propertyType, reply->format, reply->nItems);
+#endif
+
+ assert (task->data == NULL);
+
+ /* This is all copied from XGetWindowProperty(). Not sure we should
+ * LockDisplay(). Not sure I'm passing the right args to
+ * XGetAsyncData(). Not sure about a lot of things.
+ */
+
+ /* LockDisplay (dpy); */
+
+ if (reply->propertyType != None)
+ {
+ long nbytes, netbytes;
+
+ /* this alignment macro from matecorba2 */
+#define ALIGN_VALUE(this, boundary) \
+ (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
+
+ switch (reply->format)
+ {
+ /*
+ * One extra byte is malloced than is needed to contain the property
+ * data, but this last byte is null terminated and convenient for
+ * returning string properties, so the client doesn't then have to
+ * recopy the string to make it null terminated.
+ */
+ case 8:
+ nbytes = reply->nItems;
+ /* there's padding to word boundary */
+ netbytes = ALIGN_VALUE (nbytes, 4);
+ if (nbytes + 1 > 0 &&
+ (task->data = (char *) Xmalloc ((unsigned)nbytes + 1)))
+ {
+#ifdef DEBUG_SPEW
+ printf ("%s: already read %d bytes using %ld, more eating %ld more\n",
+ __FUNCTION__, bytes_read, nbytes, netbytes);
+#endif
+ /* _XReadPad (dpy, (char *) task->data, netbytes); */
+ _XGetAsyncData (dpy, task->data, buf, len,
+ bytes_read, nbytes,
+ netbytes);
+ }
+ break;
+
+ case 16:
+ nbytes = reply->nItems * sizeof (short);
+ netbytes = reply->nItems << 1;
+ netbytes = ALIGN_VALUE (netbytes, 4); /* align to word boundary */
+ if (nbytes + 1 > 0 &&
+ (task->data = (char *) Xmalloc ((unsigned)nbytes + 1)))
+ {
+#ifdef DEBUG_SPEW
+ printf ("%s: already read %d bytes using %ld more, eating %ld more\n",
+ __FUNCTION__, bytes_read, nbytes, netbytes);
+#endif
+ /* _XRead16Pad (dpy, (short *) task->data, netbytes); */
+ _XGetAsyncData (dpy, task->data, buf, len,
+ bytes_read, nbytes, netbytes);
+ }
+ break;
+
+ case 32:
+ /* NOTE buffer is in longs to match XGetWindowProperty() */
+ nbytes = reply->nItems * sizeof (long);
+ netbytes = reply->nItems << 2; /* wire size is always 32 bits though */
+ if (nbytes + 1 > 0 &&
+ (task->data = (char *) Xmalloc ((unsigned)nbytes + 1)))
+ {
+#ifdef DEBUG_SPEW
+ printf ("%s: already read %d bytes using %ld more, eating %ld more\n",
+ __FUNCTION__, bytes_read, nbytes, netbytes);
+#endif
+
+ /* We have to copy the XGetWindowProperty() crackrock
+ * and get format 32 as long even on 64-bit platforms.
+ */
+ if (sizeof (long) == 8)
+ {
+ char *netdata;
+ char *lptr;
+ char *end_lptr;
+
+ /* Store the 32-bit values in the end of the array */
+ netdata = task->data + nbytes / 2;
+
+ _XGetAsyncData (dpy, netdata, buf, len,
+ bytes_read, netbytes,
+ netbytes);
+
+ /* Now move the 32-bit values to the front */
+
+ lptr = task->data;
+ end_lptr = task->data + nbytes;
+ while (lptr != end_lptr)
+ {
+ *(long*) lptr = *(CARD32*) netdata;
+ lptr += sizeof (long);
+ netdata += sizeof (CARD32);
+ }
+ }
+ else
+ {
+ /* Here the wire format matches our actual format */
+ _XGetAsyncData (dpy, task->data, buf, len,
+ bytes_read, netbytes,
+ netbytes);
+ }
+ }
+ break;
+
+ default:
+ /*
+ * This part of the code should never be reached. If it is,
+ * the server sent back a property with an invalid format.
+ * This is a BadImplementation error.
+ *
+ * However this async GetProperty API doesn't report errors
+ * via the standard X mechanism, so don't do anything about
+ * it, other than store it in task->error.
+ */
+ {
+#if 0
+ xError error;
+#endif
+
+ task->error = BadImplementation;
+
+#if 0
+ error.sequenceNumber = task->request_seq;
+ error.type = X_Error;
+ error.majorCode = X_GetProperty;
+ error.minorCode = 0;
+ error.errorCode = BadImplementation;
+
+ _XError (dpy, &error);
+#endif
+ }
+
+ nbytes = netbytes = 0L;
+ break;
+ }
+
+ if (task->data == NULL)
+ {
+ task->error = BadAlloc;
+
+#ifdef DEBUG_SPEW
+ printf ("%s: already read %d bytes eating %ld\n",
+ __FUNCTION__, bytes_read, netbytes);
+#endif
+ /* _XEatData (dpy, (unsigned long) netbytes); */
+ _XGetAsyncData (dpy, NULL, buf, len,
+ bytes_read, 0, netbytes);
+
+ /* UnlockDisplay (dpy); */
+ return BadAlloc; /* not Success */
+ }
+
+ (task->data)[nbytes] = '\0';
+ }
+
+#ifdef DEBUG_SPEW
+ printf ("%s: have data\n", __FUNCTION__);
+#endif
+
+ task->actual_type = reply->propertyType;
+ task->actual_format = reply->format;
+ task->n_items = reply->nItems;
+ task->bytes_after = reply->bytesAfter;
+
+ /* UnlockDisplay (dpy); */
+
+ return True;
+}
+
+static AgPerDisplayData*
+get_display_data (Display *display,
+ Bool create)
+{
+ ListNode *node;
+ AgPerDisplayData *dd;
+
+ node = display_datas;
+ while (node != NULL)
+ {
+ dd = (AgPerDisplayData*) node;
+
+ if (dd->display == display)
+ return dd;
+
+ node = node->next;
+ }
+
+ if (!create)
+ return NULL;
+
+ dd = Xcalloc (1, sizeof (AgPerDisplayData));
+ if (dd == NULL)
+ return NULL;
+
+ dd->display = display;
+ dd->async.next = display->async_handlers;
+ dd->async.handler = async_get_property_handler;
+ dd->async.data = (XPointer) dd;
+ dd->display->async_handlers = &dd->async;
+
+ append_to_list (&display_datas,
+ &display_datas_tail,
+ &dd->node);
+
+ return dd;
+}
+
+static void
+maybe_free_display_data (AgPerDisplayData *dd)
+{
+ if (dd->pending_tasks == NULL &&
+ dd->completed_tasks == NULL)
+ {
+ DeqAsyncHandler (dd->display, &dd->async);
+ remove_from_list (&display_datas, &display_datas_tail,
+ &dd->node);
+ XFree (dd);
+ }
+}
+
+AgGetPropertyTask*
+ag_task_create (Display *dpy,
+ Window window,
+ Atom property,
+ long offset,
+ long length,
+ Bool delete,
+ Atom req_type)
+{
+ AgGetPropertyTask *task;
+ xGetPropertyReq *req;
+ AgPerDisplayData *dd;
+
+ /* Fire up our request */
+ LockDisplay (dpy);
+
+ dd = get_display_data (dpy, True);
+ if (dd == NULL)
+ {
+ UnlockDisplay (dpy);
+ return NULL;
+ }
+
+ GetReq (GetProperty, req);
+ req->window = window;
+ req->property = property;
+ req->type = req_type;
+ req->delete = delete;
+ req->longOffset = offset;
+ req->longLength = length;
+
+ /* Queue up our async task */
+ task = Xcalloc (1, sizeof (AgGetPropertyTask));
+ if (task == NULL)
+ {
+ UnlockDisplay (dpy);
+ return NULL;
+ }
+
+ task->dd = dd;
+ task->window = window;
+ task->property = property;
+ task->request_seq = dpy->request;
+
+ append_to_list (&dd->pending_tasks,
+ &dd->pending_tasks_tail,
+ &task->node);
+ dd->n_tasks_pending += 1;
+
+ UnlockDisplay (dpy);
+
+ SyncHandle ();
+
+ return task;
+}
+
+static void
+free_task (AgGetPropertyTask *task)
+{
+ remove_from_list (&task->dd->completed_tasks,
+ &task->dd->completed_tasks_tail,
+ &task->node);
+ task->dd->n_tasks_completed -= 1;
+ maybe_free_display_data (task->dd);
+ XFree (task);
+}
+
+Status
+ag_task_get_reply_and_free (AgGetPropertyTask *task,
+ Atom *actual_type,
+ int *actual_format,
+ unsigned long *nitems,
+ unsigned long *bytesafter,
+ unsigned char **prop)
+{
+ Display *dpy;
+
+ *prop = NULL;
+
+ dpy = task->dd->display; /* Xlib macros require a variable named "dpy" */
+
+ if (task->error != Success)
+ {
+ Status s = task->error;
+
+ free_task (task);
+
+ return s;
+ }
+
+ if (!task->have_reply)
+ {
+ free_task (task);
+
+ return BadAlloc; /* not Success */
+ }
+
+ *actual_type = task->actual_type;
+ *actual_format = task->actual_format;
+ *nitems = task->n_items;
+ *bytesafter = task->bytes_after;
+
+ *prop = (unsigned char*) task->data; /* pass out ownership of task->data */
+
+ SyncHandle ();
+
+ free_task (task);
+
+ return Success;
+}
+
+Bool
+ag_task_have_reply (AgGetPropertyTask *task)
+{
+ return task->have_reply;
+}
+
+Atom
+ag_task_get_property (AgGetPropertyTask *task)
+{
+ return task->property;
+}
+
+Window
+ag_task_get_window (AgGetPropertyTask *task)
+{
+ return task->window;
+}
+
+Display*
+ag_task_get_display (AgGetPropertyTask *task)
+{
+ return task->dd->display;
+}
+
+AgGetPropertyTask*
+ag_get_next_completed_task (Display *display)
+{
+ AgPerDisplayData *dd;
+
+ dd = get_display_data (display, False);
+
+ if (dd == NULL)
+ return NULL;
+
+#ifdef DEBUG_SPEW
+ printf ("%d pending %d completed\n",
+ dd->n_tasks_pending,
+ dd->n_tasks_completed);
+#endif
+
+ return (AgGetPropertyTask*) dd->completed_tasks;
+}
+
+void*
+ag_Xmalloc (unsigned long bytes)
+{
+ return (void*) Xmalloc (bytes);
+}
+
+void*
+ag_Xmalloc0 (unsigned long bytes)
+{
+ return (void*) Xcalloc (bytes, 1);
+}
diff --git a/src/core/async-getprop.h b/src/core/async-getprop.h
new file mode 100644
index 00000000..c857e930
--- /dev/null
+++ b/src/core/async-getprop.h
@@ -0,0 +1,67 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Asynchronous X property getting hack */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation.
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the name of The Open Group shall not be
+ * used in advertising or otherwise to promote the sale, use or other dealings
+ * in this Software without prior written authorization from The Open Group.
+ */
+
+#ifndef ASYNC_GETPROP_H
+#define ASYNC_GETPROP_H
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+typedef struct _AgGetPropertyTask AgGetPropertyTask;
+
+AgGetPropertyTask* ag_task_create (Display *display,
+ Window window,
+ Atom property,
+ long offset,
+ long length,
+ Bool delete,
+ Atom req_type);
+Status ag_task_get_reply_and_free (AgGetPropertyTask *task,
+ Atom *actual_type,
+ int *actual_format,
+ unsigned long *nitems,
+ unsigned long *bytesafter,
+ unsigned char **prop);
+
+Bool ag_task_have_reply (AgGetPropertyTask *task);
+Atom ag_task_get_property (AgGetPropertyTask *task);
+Window ag_task_get_window (AgGetPropertyTask *task);
+Display* ag_task_get_display (AgGetPropertyTask *task);
+
+AgGetPropertyTask* ag_get_next_completed_task (Display *display);
+
+/* so other headers don't have to include internal Xlib goo */
+void* ag_Xmalloc (unsigned long bytes);
+void* ag_Xmalloc0 (unsigned long bytes);
+
+#endif
+
+
+
+
diff --git a/src/core/atomnames.h b/src/core/atomnames.h
new file mode 100644
index 00000000..551482ac
--- /dev/null
+++ b/src/core/atomnames.h
@@ -0,0 +1,166 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2002, 2003, 2004 Red Hat, Inc.
+ * Copyright (C) 2003, 2004 Rob Adams
+ * Copyright (C) 2004-2006 Elijah Newren
+ * Copyright (C) 2008 Thomas Thurman
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/**
+ * \file atomnames.h A list of atom names.
+ *
+ * This is a list of the names of all the X atoms that Marco uses.
+ * Each is wrapped in a macro "item()" which is undefined here; the
+ * idea is that when you need to make a big list of all the X atoms,
+ * you can define item(), include this file, and then undefine it
+ * again.
+ *
+ * If you also define EWMH_ATOMS_ONLY then you will only get _NET_WM_*
+ * atoms rather than all of them.
+ */
+
+#ifndef item
+#error "item(x) must be defined when you include atomnames.h"
+#endif
+
+#ifndef EWMH_ATOMS_ONLY
+
+item(WM_PROTOCOLS)
+item(WM_TAKE_FOCUS)
+item(WM_DELETE_WINDOW)
+item(WM_STATE)
+item(_MOTIF_WM_HINTS)
+item(WM_CHANGE_STATE)
+item(SM_CLIENT_ID)
+item(WM_CLIENT_LEADER)
+item(WM_WINDOW_ROLE)
+item(UTF8_STRING)
+item(WM_ICON_SIZE)
+item(_KWM_WIN_ICON)
+item(_MARCO_RESTART_MESSAGE)
+item(_MARCO_RELOAD_THEME_MESSAGE)
+item(_MARCO_SET_KEYBINDINGS_MESSAGE)
+item(_MARCO_TOGGLE_VERBOSE)
+item(_MATE_PANEL_ACTION)
+item(_MATE_PANEL_ACTION_MAIN_MENU)
+item(_MATE_PANEL_ACTION_RUN_DIALOG)
+item(_MARCO_SENTINEL)
+item(_MARCO_VERSION)
+item(WM_CLIENT_MACHINE)
+item(MANAGER)
+item(TARGETS)
+item(MULTIPLE)
+item(TIMESTAMP)
+item(VERSION)
+item(ATOM_PAIR)
+
+/* Oddities: These are used, and we need atoms for them,
+ * but when we need all _NET_WM hints (i.e. when we're making
+ * lists of which _NET_WM hints we support in order to advertise
+ * it) we haven't historically listed them. I don't know what
+ * the reason for this is. It may be a bug.
+ */
+item(_NET_WM_SYNC_REQUEST)
+item(_NET_WM_SYNC_REQUEST_COUNTER)
+item(_NET_WM_VISIBLE_NAME)
+item(_NET_WM_VISIBLE_ICON_NAME)
+item(_NET_SUPPORTING_WM_CHECK)
+
+/* But I suppose it's quite reasonable not to advertise using
+ * _NET_SUPPORTED that we support _NET_SUPPORTED :)
+ */
+item(_NET_SUPPORTED)
+
+#endif /* !EWMH_ATOMS_ONLY */
+
+/**************************************************************************/
+
+item(_NET_WM_NAME)
+item(_NET_CLOSE_WINDOW)
+item(_NET_WM_STATE)
+item(_NET_WM_STATE_SHADED)
+item(_NET_WM_STATE_MAXIMIZED_HORZ)
+item(_NET_WM_STATE_MAXIMIZED_VERT)
+item(_NET_WM_DESKTOP)
+item(_NET_NUMBER_OF_DESKTOPS)
+item(_NET_CURRENT_DESKTOP)
+item(_NET_WM_WINDOW_TYPE)
+item(_NET_WM_WINDOW_TYPE_DESKTOP)
+item(_NET_WM_WINDOW_TYPE_DOCK)
+item(_NET_WM_WINDOW_TYPE_TOOLBAR)
+item(_NET_WM_WINDOW_TYPE_MENU)
+item(_NET_WM_WINDOW_TYPE_DIALOG)
+item(_NET_WM_WINDOW_TYPE_NORMAL)
+item(_NET_WM_STATE_MODAL)
+item(_NET_CLIENT_LIST)
+item(_NET_CLIENT_LIST_STACKING)
+item(_NET_WM_STATE_SKIP_TASKBAR)
+item(_NET_WM_STATE_SKIP_PAGER)
+item(_NET_WM_ICON_NAME)
+item(_NET_WM_ICON)
+item(_NET_WM_ICON_GEOMETRY)
+item(_NET_WM_MOVERESIZE)
+item(_NET_ACTIVE_WINDOW)
+item(_NET_WM_STRUT)
+item(_NET_WM_STATE_HIDDEN)
+item(_NET_WM_WINDOW_TYPE_UTILITY)
+item(_NET_WM_WINDOW_TYPE_SPLASH)
+item(_NET_WM_STATE_FULLSCREEN)
+item(_NET_WM_PING)
+item(_NET_WM_PID)
+item(_NET_WORKAREA)
+item(_NET_SHOWING_DESKTOP)
+item(_NET_DESKTOP_LAYOUT)
+item(_NET_DESKTOP_NAMES)
+item(_NET_WM_ALLOWED_ACTIONS)
+item(_NET_WM_ACTION_MOVE)
+item(_NET_WM_ACTION_RESIZE)
+item(_NET_WM_ACTION_SHADE)
+item(_NET_WM_ACTION_STICK)
+item(_NET_WM_ACTION_MAXIMIZE_HORZ)
+item(_NET_WM_ACTION_MAXIMIZE_VERT)
+item(_NET_WM_ACTION_CHANGE_DESKTOP)
+item(_NET_WM_ACTION_CLOSE)
+item(_NET_WM_STATE_ABOVE)
+item(_NET_WM_STATE_BELOW)
+item(_NET_STARTUP_ID)
+item(_NET_WM_STRUT_PARTIAL)
+item(_NET_WM_ACTION_FULLSCREEN)
+item(_NET_WM_ACTION_MINIMIZE)
+item(_NET_FRAME_EXTENTS)
+item(_NET_REQUEST_FRAME_EXTENTS)
+item(_NET_WM_USER_TIME)
+item(_NET_WM_STATE_DEMANDS_ATTENTION)
+item(_NET_MOVERESIZE_WINDOW)
+item(_NET_DESKTOP_GEOMETRY)
+item(_NET_DESKTOP_VIEWPORT)
+item(_NET_WM_USER_TIME_WINDOW)
+item(_NET_WM_ACTION_ABOVE)
+item(_NET_WM_ACTION_BELOW)
+item(_NET_WM_STATE_STICKY)
+item(_NET_WM_FULLSCREEN_MONITORS)
+
+#if 0
+/* We apparently never use: */
+/* item(_NET_RESTACK_WINDOW) */
+#endif
+
+/* eof atomnames.h */
+
diff --git a/src/core/bell.c b/src/core/bell.c
new file mode 100644
index 00000000..560b3569
--- /dev/null
+++ b/src/core/bell.c
@@ -0,0 +1,397 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco visual bell */
+
+/*
+ * Copyright (C) 2002 Sun Microsystems Inc.
+ * Copyright (C) 2005, 2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/**
+ * \file bell.c Ring the bell or flash the screen
+ *
+ * Sometimes, X programs "ring the bell", whatever that means. Marco lets
+ * the user configure the bell to be audible or visible (aka visual), and
+ * if it's visual it can be configured to be frame-flash or fullscreen-flash.
+ * We never get told about audible bells; X handles them just fine by itself.
+ *
+ * Visual bells come in at meta_bell_notify(), which checks we are actually
+ * in visual mode and calls through to bell_visual_notify(). That
+ * function then checks what kind of visual flash you like, and calls either
+ * bell_flash_fullscreen()-- which calls bell_flash_screen() to do
+ * its work-- or bell_flash_frame(), which flashes the focussed window
+ * using bell_flash_window_frame(), unless there is no such window, in
+ * which case it flashes the screen instead. bell_flash_window_frame()
+ * flashes the frame and calls bell_unflash_frame() as a timeout to
+ * remove the flash.
+ *
+ * The visual bell was the result of a discussion in Bugzilla here:
+ * <http://bugzilla.gnome.org/show_bug.cgi?id=99886>.
+ *
+ * Several of the functions in this file are ifdeffed out entirely if we are
+ * found not to have the XKB extension, which is required to do these clever
+ * things with bells; some others are entirely no-ops in that case.
+ */
+
+#include <config.h>
+#include "bell.h"
+#include "screen-private.h"
+#include "prefs.h"
+#include <canberra-gtk.h>
+
+/**
+ * Flashes one entire screen. This is done by making a window the size of the
+ * whole screen (or reusing the old one, if it's still around), mapping it,
+ * painting it white and then black, and then unmapping it. We set saveunder so
+ * that all the windows behind it come back immediately.
+ *
+ * Unlike frame flashes, we don't do fullscreen flashes with a timeout; rather,
+ * we do them in one go, because we don't have to rely on the theme code
+ * redrawing the frame for us in order to do the flash.
+ *
+ * \param display The display which owns the screen (rather redundant)
+ * \param screen The screen to flash
+ *
+ * \bug The way I read it, this appears not to do the flash
+ * the first time we flash a particular display. Am I wrong?
+ *
+ * \bug This appears to destroy our current XSync status.
+ */
+static void
+bell_flash_screen (MetaDisplay *display,
+ MetaScreen *screen)
+{
+ Window root = screen->xroot;
+ int width = screen->rect.width;
+ int height = screen->rect.height;
+
+ if (screen->flash_window == None)
+ {
+ Visual *visual = (Visual *)CopyFromParent;
+ XSetWindowAttributes xswa;
+ int depth = CopyFromParent;
+ xswa.save_under = True;
+ xswa.override_redirect = True;
+ /*
+ * TODO: use XGetVisualInfo and determine which is an
+ * overlay, if one is present, and use the Overlay visual
+ * for this window (for performance reasons).
+ * Not sure how to tell this yet...
+ */
+ screen->flash_window = XCreateWindow (display->xdisplay, root,
+ 0, 0, width, height,
+ 0, depth,
+ InputOutput,
+ visual,
+ /* note: XSun doesn't like SaveUnder here */
+ CWSaveUnder | CWOverrideRedirect,
+ &xswa);
+ XSelectInput (display->xdisplay, screen->flash_window, ExposureMask);
+ XMapWindow (display->xdisplay, screen->flash_window);
+ XSync (display->xdisplay, False);
+ XFlush (display->xdisplay);
+ XUnmapWindow (display->xdisplay, screen->flash_window);
+ }
+ else
+ {
+ /* just draw something in the window */
+ GC gc = XCreateGC (display->xdisplay, screen->flash_window, 0, NULL);
+ XMapWindow (display->xdisplay, screen->flash_window);
+ XSetForeground (display->xdisplay, gc,
+ WhitePixel (display->xdisplay,
+ XScreenNumberOfScreen (screen->xscreen)));
+ XFillRectangle (display->xdisplay, screen->flash_window, gc,
+ 0, 0, width, height);
+ XSetForeground (display->xdisplay, gc,
+ BlackPixel (display->xdisplay,
+ XScreenNumberOfScreen (screen->xscreen)));
+ XFillRectangle (display->xdisplay, screen->flash_window, gc,
+ 0, 0, width, height);
+ XFlush (display->xdisplay);
+ XSync (display->xdisplay, False);
+ XUnmapWindow (display->xdisplay, screen->flash_window);
+ XFreeGC (display->xdisplay, gc);
+ }
+
+ if (meta_prefs_get_focus_mode () != META_FOCUS_MODE_CLICK &&
+ !display->mouse_mode)
+ meta_display_increment_focus_sentinel (display);
+ XFlush (display->xdisplay);
+}
+
+/**
+ * Flashes one screen, or all screens, in response to a bell event.
+ * If the event is on a particular window, flash the screen that
+ * window is on. Otherwise, flash every screen on this display.
+ *
+ * If the configure script found we had no XKB, this does not exist.
+ *
+ * \param display The display the event came in on
+ * \param xkb_ev The bell event
+ */
+#ifdef HAVE_XKB
+static void
+bell_flash_fullscreen (MetaDisplay *display,
+ XkbAnyEvent *xkb_ev)
+{
+ XkbBellNotifyEvent *xkb_bell_ev = (XkbBellNotifyEvent *) xkb_ev;
+ MetaScreen *screen;
+
+ g_assert (xkb_ev->xkb_type == XkbBellNotify);
+ if (xkb_bell_ev->window != None)
+ {
+ screen = meta_display_screen_for_xwindow (display, xkb_bell_ev->window);
+ if (screen)
+ bell_flash_screen (display, screen);
+ }
+ else
+ {
+ GSList *screen_list = display->screens;
+ while (screen_list)
+ {
+ screen = (MetaScreen *) screen_list->data;
+ bell_flash_screen (display, screen);
+ screen_list = screen_list->next;
+ }
+ }
+}
+
+/**
+ * Makes a frame be not flashed; this is the timeout half of
+ * bell_flash_window_frame(). This is done simply by clearing the
+ * flash flag and queuing a redraw of the frame.
+ *
+ * If the configure script found we had no XKB, this does not exist.
+ *
+ * \param data The frame to unflash, cast to a gpointer so it can go into
+ * a callback function.
+ * \return Always FALSE, so we don't get called again.
+ *
+ * \bug This is the parallel to bell_flash_window_frame(), so it should
+ * really be called meta_bell_unflash_window_frame().
+ */
+static gboolean
+bell_unflash_frame (gpointer data)
+{
+ MetaFrame *frame = (MetaFrame *) data;
+ frame->is_flashing = 0;
+ meta_frame_queue_draw (frame);
+ return FALSE;
+}
+
+/**
+ * Makes a frame flash and then return to normal shortly afterwards.
+ * This is done by setting a flag so that the theme
+ * code will temporarily draw the frame as focussed if it's unfocussed and
+ * vice versa, and then queueing a redraw. Lastly, we create a timeout so
+ * that the flag can be unset and the frame re-redrawn.
+ *
+ * If the configure script found we had no XKB, this does not exist.
+ *
+ * \param window The window to flash
+ */
+static void
+bell_flash_window_frame (MetaWindow *window)
+{
+ g_assert (window->frame != NULL);
+ window->frame->is_flashing = 1;
+ meta_frame_queue_draw (window->frame);
+ g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 100,
+ bell_unflash_frame, window->frame, NULL);
+}
+
+/**
+ * Flashes the frame of the focussed window. If there is no focussed window,
+ * flashes the screen.
+ *
+ * \param display The display the bell event came in on
+ * \param xkb_ev The bell event we just received
+ */
+static void
+bell_flash_frame (MetaDisplay *display,
+ XkbAnyEvent *xkb_ev)
+{
+ XkbBellNotifyEvent *xkb_bell_event = (XkbBellNotifyEvent *) xkb_ev;
+ MetaWindow *window;
+
+ g_assert (xkb_ev->xkb_type == XkbBellNotify);
+ window = meta_display_lookup_x_window (display, xkb_bell_event->window);
+ if (!window && (display->focus_window))
+ {
+ window = display->focus_window;
+ }
+ if (window && window->frame)
+ {
+ bell_flash_window_frame (window);
+ }
+ else /* revert to fullscreen flash if there's no focussed window */
+ {
+ bell_flash_fullscreen (display, xkb_ev);
+ }
+}
+
+/**
+ * Gives the user some kind of visual bell substitute, in response to a
+ * bell event. What this is depends on the "visual bell type" pref.
+ *
+ * If the configure script found we had no XKB, this does not exist.
+ *
+ * \param display The display the bell event came in on
+ * \param xkb_ev The bell event we just received
+ *
+ * \bug This should be merged with meta_bell_notify().
+ */
+static void
+bell_visual_notify (MetaDisplay *display,
+ XkbAnyEvent *xkb_ev)
+{
+ switch (meta_prefs_get_visual_bell_type ())
+ {
+ case META_VISUAL_BELL_FULLSCREEN_FLASH:
+ bell_flash_fullscreen (display, xkb_ev);
+ break;
+ case META_VISUAL_BELL_FRAME_FLASH:
+ bell_flash_frame (display, xkb_ev); /* does nothing yet */
+ break;
+ case META_VISUAL_BELL_INVALID:
+ /* do nothing */
+ break;
+ }
+}
+
+void
+meta_bell_notify (MetaDisplay *display,
+ XkbAnyEvent *xkb_ev)
+{
+ /* flash something */
+ if (meta_prefs_get_visual_bell ())
+ bell_visual_notify (display, xkb_ev);
+
+ if (meta_prefs_bell_is_audible ())
+ {
+ ca_proplist *p;
+ XkbBellNotifyEvent *xkb_bell_event = (XkbBellNotifyEvent*) xkb_ev;
+ MetaWindow *window;
+ int res;
+
+ ca_proplist_create (&p);
+ ca_proplist_sets (p, CA_PROP_EVENT_ID, "bell-window-system");
+ ca_proplist_sets (p, CA_PROP_EVENT_DESCRIPTION, _("Bell event"));
+ ca_proplist_sets (p, CA_PROP_CANBERRA_CACHE_CONTROL, "permanent");
+
+ window = meta_display_lookup_x_window (display, xkb_bell_event->window);
+ if (!window && (display->focus_window) && (display->focus_window->frame))
+ window = display->focus_window;
+
+ if (window)
+ {
+ ca_proplist_sets (p, CA_PROP_WINDOW_NAME, window->title);
+ ca_proplist_setf (p, CA_PROP_WINDOW_X11_XID, "%lu", (unsigned long)window->xwindow);
+ ca_proplist_sets (p, CA_PROP_APPLICATION_NAME, window->res_name);
+ ca_proplist_setf (p, CA_PROP_APPLICATION_PROCESS_ID, "%d", window->net_wm_pid);
+ }
+
+ /* First, we try to play a real sound ... */
+ res = ca_context_play_full (ca_gtk_context_get (), 1, p, NULL, NULL);
+
+ ca_proplist_destroy (p);
+
+ if (res != CA_SUCCESS && res != CA_ERROR_DISABLED)
+ {
+ /* ...and in case that failed we use the classic X11 bell. */
+ XkbForceDeviceBell (display->xdisplay,
+ xkb_bell_event->device,
+ xkb_bell_event->bell_class,
+ xkb_bell_event->bell_id,
+ xkb_bell_event->percent);
+ }
+ }
+}
+#endif /* HAVE_XKB */
+
+void
+meta_bell_set_audible (MetaDisplay *display, gboolean audible)
+{
+}
+
+gboolean
+meta_bell_init (MetaDisplay *display)
+{
+#ifdef HAVE_XKB
+ int xkb_base_error_type, xkb_opcode;
+
+ if (!XkbQueryExtension (display->xdisplay, &xkb_opcode,
+ &display->xkb_base_event_type,
+ &xkb_base_error_type,
+ NULL, NULL))
+ {
+ display->xkb_base_event_type = -1;
+ g_message ("could not find XKB extension.");
+ return FALSE;
+ }
+ else
+ {
+ unsigned int mask = XkbBellNotifyMask;
+ gboolean visual_bell_auto_reset = FALSE;
+ /* TRUE if and when non-broken version is available */
+ XkbSelectEvents (display->xdisplay,
+ XkbUseCoreKbd,
+ XkbBellNotifyMask,
+ XkbBellNotifyMask);
+ XkbChangeEnabledControls (display->xdisplay,
+ XkbUseCoreKbd,
+ XkbAudibleBellMask,
+ 0);
+ if (visual_bell_auto_reset) {
+ XkbSetAutoResetControls (display->xdisplay,
+ XkbAudibleBellMask,
+ &mask,
+ &mask);
+ }
+ return TRUE;
+ }
+#endif
+ return FALSE;
+}
+
+void
+meta_bell_shutdown (MetaDisplay *display)
+{
+#ifdef HAVE_XKB
+ /* TODO: persist initial bell state in display, reset here */
+ XkbChangeEnabledControls (display->xdisplay,
+ XkbUseCoreKbd,
+ XkbAudibleBellMask,
+ XkbAudibleBellMask);
+#endif
+}
+
+/**
+ * Deals with a frame being destroyed. This is important because if we're
+ * using a visual bell, we might be flashing the edges of the frame, and
+ * so we'd have a timeout function waiting ready to un-flash them. If the
+ * frame's going away, we can tell the timeout not to bother.
+ *
+ * \param frame The frame which is being destroyed
+ */
+void
+meta_bell_notify_frame_destroy (MetaFrame *frame)
+{
+ if (frame->is_flashing)
+ g_source_remove_by_funcs_user_data (&g_timeout_funcs, frame);
+}
diff --git a/src/core/bell.h b/src/core/bell.h
new file mode 100644
index 00000000..95e3ea9e
--- /dev/null
+++ b/src/core/bell.h
@@ -0,0 +1,108 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file bell.h Ring the bell or flash the screen
+ *
+ * Sometimes, X programs "ring the bell", whatever that means. Marco lets
+ * the user configure the bell to be audible or visible (aka visual), and
+ * if it's visual it can be configured to be frame-flash or fullscreen-flash.
+ * We never get told about audible bells; X handles them just fine by itself.
+ *
+ * The visual bell was the result of a discussion in Bugzilla here:
+ * <http://bugzilla.gnome.org/show_bug.cgi?id=99886>.
+ */
+
+/*
+ * Copyright (C) 2002 Sun Microsystems Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <X11/Xlib.h>
+#ifdef HAVE_XKB
+#include <X11/XKBlib.h>
+#endif
+#include "display-private.h"
+#include "frame-private.h"
+
+#ifdef HAVE_XKB
+/**
+ * Gives the user some kind of visual bell; in fact, this is our response
+ * to any kind of bell request, but we set it up so that we only get
+ * notified about visual bells, and X deals with audible ones.
+ *
+ * If the configure script found we had no XKB, this does not exist.
+ *
+ * \param display The display the bell event came in on
+ * \param xkb_ev The bell event we just received
+ */
+void meta_bell_notify (MetaDisplay *display, XkbAnyEvent *xkb_ev);
+#endif
+
+/**
+ * Turns the bell to audible or visual. This tells X what to do, but
+ * not Marco; you will need to set the "visual bell" pref for that.
+ *
+ * If the configure script found we had no XKB, this is a no-op.
+ *
+ * \param display The display we're configuring
+ * \param audible True for an audible bell, false for a visual bell
+ */
+void meta_bell_set_audible (MetaDisplay *display, gboolean audible);
+
+/**
+ * Initialises the bell subsystem. This involves intialising
+ * XKB (which, despite being a keyboard extension, is the
+ * place to look for bell notifications), then asking it
+ * to send us bell notifications, and then also switching
+ * off the audible bell if we're using a visual one ourselves.
+ *
+ * Unlike most X extensions we use, we only initialise XKB here
+ * (rather than in main()). It's possible that XKB is not
+ * installed at all, but if that was known at build time
+ * we will have HAVE_XKB undefined, which will cause this
+ * function to be a no-op.
+ *
+ * \param display The display which is opening
+ *
+ * \bug There is a line of code that's never run that tells
+ * XKB to reset the bell status after we quit. Bill H said
+ * (<http://bugzilla.gnome.org/show_bug.cgi?id=99886#c12>)
+ * that XFree86's implementation is broken so we shouldn't
+ * call it, but that was in 2002. Is it working now?
+ */
+gboolean meta_bell_init (MetaDisplay *display);
+
+/**
+ * Shuts down the bell subsystem.
+ *
+ * \param display The display which is closing
+ *
+ * \bug This is never called! If we had XkbSetAutoResetControls
+ * enabled in meta_bell_init(), this wouldn't be a problem, but
+ * we don't.
+ */
+void meta_bell_shutdown (MetaDisplay *display);
+
+/**
+ * Deals with a frame being destroyed. This is important because if we're
+ * using a visual bell, we might be flashing the edges of the frame, and
+ * so we'd have a timeout function waiting ready to un-flash them. If the
+ * frame's going away, we can tell the timeout not to bother.
+ *
+ * \param frame The frame which is being destroyed
+ */
+void meta_bell_notify_frame_destroy (MetaFrame *frame);
diff --git a/src/core/boxes.c b/src/core/boxes.c
new file mode 100644
index 00000000..2ae5f06a
--- /dev/null
+++ b/src/core/boxes.c
@@ -0,0 +1,1926 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Simple box operations */
+
+/*
+ * Copyright (C) 2005, 2006 Elijah Newren
+ * [meta_rectangle_intersect() is copyright the GTK+ Team according to Havoc,
+ * see gdkrectangle.c. As far as Havoc knows, he probably wrote
+ * meta_rectangle_equal(), and I'm guessing it's (C) Red Hat. So...]
+ * Copyright (C) 1995-2000 GTK+ Team
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "boxes.h"
+#include "util.h"
+#include <X11/Xutil.h> /* Just for the definition of the various gravities */
+
+char*
+meta_rectangle_to_string (const MetaRectangle *rect,
+ char *output)
+{
+ /* 25 chars: 2 commas, space, plus, trailing \0 + 5 for each digit.
+ * Should be more than enough space. Note that of this space, the
+ * trailing \0 will be overwritten for all but the last rectangle.
+ */
+ g_snprintf (output, RECT_LENGTH, "%d,%d +%d,%d",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return output;
+}
+
+char*
+meta_rectangle_region_to_string (GList *region,
+ const char *separator_string,
+ char *output)
+{
+ /* 27 chars: 2 commas, 2 square brackets, space, plus, trailing \0 + 5
+ * for each digit. Should be more than enough space. Note that of this
+ * space, the trailing \0 will be overwritten for all but the last
+ * rectangle.
+ */
+ char rect_string[RECT_LENGTH];
+
+ GList *tmp = region;
+ char *cur = output;
+
+ if (region == NULL)
+ g_snprintf (output, 10, "(EMPTY)");
+
+ while (tmp)
+ {
+ MetaRectangle *rect = tmp->data;
+ g_snprintf (rect_string, RECT_LENGTH, "[%d,%d +%d,%d]",
+ rect->x, rect->y, rect->width, rect->height);
+ cur = g_stpcpy (cur, rect_string);
+ tmp = tmp->next;
+ if (tmp)
+ cur = g_stpcpy (cur, separator_string);
+ }
+
+ return output;
+}
+
+char*
+meta_rectangle_edge_to_string (const MetaEdge *edge,
+ char *output)
+{
+ /* 25 chars: 2 commas, space, plus, trailing \0 + 5 for each digit.
+ * Should be more than enough space. Note that of this space, the
+ * trailing \0 will be overwritten for all but the last rectangle.
+ *
+ * Plus 2 for parenthesis, 4 for 2 more numbers, 2 more commas, and
+ * 2 more spaces, for a total of 10 more.
+ */
+ g_snprintf (output, EDGE_LENGTH, "[%d,%d +%d,%d], %2d, %2d",
+ edge->rect.x, edge->rect.y, edge->rect.width, edge->rect.height,
+ edge->side_type, edge->edge_type);
+
+ return output;
+}
+
+char*
+meta_rectangle_edge_list_to_string (GList *edge_list,
+ const char *separator_string,
+ char *output)
+{
+ /* 27 chars: 2 commas, 2 square brackets, space, plus, trailing \0 + 5 for
+ * each digit. Should be more than enough space. Note that of this
+ * space, the trailing \0 will be overwritten for all but the last
+ * rectangle.
+ *
+ * Plus 2 for parenthesis, 4 for 2 more numbers, 2 more commas, and
+ * 2 more spaces, for a total of 10 more.
+ */
+ char rect_string[EDGE_LENGTH];
+
+ char *cur = output;
+ GList *tmp = edge_list;
+
+ if (edge_list == NULL)
+ g_snprintf (output, 10, "(EMPTY)");
+
+ while (tmp)
+ {
+ MetaEdge *edge = tmp->data;
+ MetaRectangle *rect = &edge->rect;
+ g_snprintf (rect_string, EDGE_LENGTH, "([%d,%d +%d,%d], %2d, %2d)",
+ rect->x, rect->y, rect->width, rect->height,
+ edge->side_type, edge->edge_type);
+ cur = g_stpcpy (cur, rect_string);
+ tmp = tmp->next;
+ if (tmp)
+ cur = g_stpcpy (cur, separator_string);
+ }
+
+ return output;
+}
+
+MetaRectangle
+meta_rect (int x, int y, int width, int height)
+{
+ MetaRectangle temporary;
+ temporary.x = x;
+ temporary.y = y;
+ temporary.width = width;
+ temporary.height = height;
+
+ return temporary;
+}
+
+int
+meta_rectangle_area (const MetaRectangle *rect)
+{
+ g_return_val_if_fail (rect != NULL, 0);
+ return rect->width * rect->height;
+}
+
+gboolean
+meta_rectangle_intersect (const MetaRectangle *src1,
+ const MetaRectangle *src2,
+ MetaRectangle *dest)
+{
+ int dest_x, dest_y;
+ int dest_w, dest_h;
+ int return_val;
+
+ g_return_val_if_fail (src1 != NULL, FALSE);
+ g_return_val_if_fail (src2 != NULL, FALSE);
+ g_return_val_if_fail (dest != NULL, FALSE);
+
+ return_val = FALSE;
+
+ dest_x = MAX (src1->x, src2->x);
+ dest_y = MAX (src1->y, src2->y);
+ dest_w = MIN (src1->x + src1->width, src2->x + src2->width) - dest_x;
+ dest_h = MIN (src1->y + src1->height, src2->y + src2->height) - dest_y;
+
+ if (dest_w > 0 && dest_h > 0)
+ {
+ dest->x = dest_x;
+ dest->y = dest_y;
+ dest->width = dest_w;
+ dest->height = dest_h;
+ return_val = TRUE;
+ }
+ else
+ {
+ dest->width = 0;
+ dest->height = 0;
+ }
+
+ return return_val;
+}
+
+gboolean
+meta_rectangle_equal (const MetaRectangle *src1,
+ const MetaRectangle *src2)
+{
+ return ((src1->x == src2->x) &&
+ (src1->y == src2->y) &&
+ (src1->width == src2->width) &&
+ (src1->height == src2->height));
+}
+
+void
+meta_rectangle_union (const MetaRectangle *rect1,
+ const MetaRectangle *rect2,
+ MetaRectangle *dest)
+{
+ int dest_x, dest_y;
+ int dest_w, dest_h;
+
+ dest_x = rect1->x;
+ dest_y = rect1->y;
+ dest_w = rect1->width;
+ dest_h = rect1->height;
+
+ if (rect2->x < dest_x)
+ {
+ dest_w += dest_x - rect2->x;
+ dest_x = rect2->x;
+ }
+ if (rect2->y < dest_y)
+ {
+ dest_h += dest_y - rect2->y;
+ dest_y = rect2->y;
+ }
+ if (rect2->x + rect2->width > dest_x + dest_w)
+ dest_w = rect2->x + rect2->width - dest_x;
+ if (rect2->y + rect2->height > dest_y + dest_h)
+ dest_h = rect2->y + rect2->height - dest_y;
+
+ dest->x = dest_x;
+ dest->y = dest_y;
+ dest->width = dest_w;
+ dest->height = dest_h;
+}
+
+gboolean
+meta_rectangle_overlap (const MetaRectangle *rect1,
+ const MetaRectangle *rect2)
+{
+ g_return_val_if_fail (rect1 != NULL, FALSE);
+ g_return_val_if_fail (rect2 != NULL, FALSE);
+
+ return !((rect1->x + rect1->width <= rect2->x) ||
+ (rect2->x + rect2->width <= rect1->x) ||
+ (rect1->y + rect1->height <= rect2->y) ||
+ (rect2->y + rect2->height <= rect1->y));
+}
+
+gboolean
+meta_rectangle_vert_overlap (const MetaRectangle *rect1,
+ const MetaRectangle *rect2)
+{
+ return (rect1->y < rect2->y + rect2->height &&
+ rect2->y < rect1->y + rect1->height);
+}
+
+gboolean
+meta_rectangle_horiz_overlap (const MetaRectangle *rect1,
+ const MetaRectangle *rect2)
+{
+ return (rect1->x < rect2->x + rect2->width &&
+ rect2->x < rect1->x + rect1->width);
+}
+
+gboolean
+meta_rectangle_could_fit_rect (const MetaRectangle *outer_rect,
+ const MetaRectangle *inner_rect)
+{
+ return (outer_rect->width >= inner_rect->width &&
+ outer_rect->height >= inner_rect->height);
+}
+
+gboolean
+meta_rectangle_contains_rect (const MetaRectangle *outer_rect,
+ const MetaRectangle *inner_rect)
+{
+ return
+ inner_rect->x >= outer_rect->x &&
+ inner_rect->y >= outer_rect->y &&
+ inner_rect->x + inner_rect->width <= outer_rect->x + outer_rect->width &&
+ inner_rect->y + inner_rect->height <= outer_rect->y + outer_rect->height;
+}
+
+void
+meta_rectangle_resize_with_gravity (const MetaRectangle *old_rect,
+ MetaRectangle *rect,
+ int gravity,
+ int new_width,
+ int new_height)
+{
+ /* FIXME: I'm too deep into this to know whether the below comment is
+ * still clear or not now that I've moved it out of constraints.c.
+ * boxes.h has a good comment, but I'm not sure if the below info is also
+ * helpful on top of that (or whether it has superfluous info).
+ */
+
+ /* These formulas may look overly simplistic at first but you can work
+ * everything out with a left_frame_with, right_frame_width,
+ * border_width, and old and new client area widths (instead of old total
+ * width and new total width) and you come up with the same formulas.
+ *
+ * Also, note that the reason we can treat NorthWestGravity and
+ * StaticGravity the same is because we're not given a location at
+ * which to place the window--the window was already placed
+ * appropriately before. So, NorthWestGravity for this function
+ * means to just leave the upper left corner of the outer window
+ * where it already is, and StaticGravity for this function means to
+ * just leave the upper left corner of the inner window where it
+ * already is. But leaving either of those two corners where they
+ * already are will ensure that the other corner is fixed as well
+ * (since frame size doesn't change)--thus making the two
+ * equivalent.
+ */
+
+ /* First, the x direction */
+ int adjust = 0;
+ switch (gravity)
+ {
+ case NorthWestGravity:
+ case WestGravity:
+ case SouthWestGravity:
+ rect->x = old_rect->x;
+ break;
+
+ case NorthGravity:
+ case CenterGravity:
+ case SouthGravity:
+ /* FIXME: Needing to adjust new_width kind of sucks, but not doing so
+ * would cause drift.
+ */
+ new_width -= (old_rect->width - new_width) % 2;
+ rect->x = old_rect->x + (old_rect->width - new_width)/2;
+ break;
+
+ case NorthEastGravity:
+ case EastGravity:
+ case SouthEastGravity:
+ rect->x = old_rect->x + (old_rect->width - new_width);
+ break;
+
+ case StaticGravity:
+ default:
+ rect->x = old_rect->x;
+ break;
+ }
+ rect->width = new_width;
+
+ /* Next, the y direction */
+ adjust = 0;
+ switch (gravity)
+ {
+ case NorthWestGravity:
+ case NorthGravity:
+ case NorthEastGravity:
+ rect->y = old_rect->y;
+ break;
+
+ case WestGravity:
+ case CenterGravity:
+ case EastGravity:
+ /* FIXME: Needing to adjust new_height kind of sucks, but not doing so
+ * would cause drift.
+ */
+ new_height -= (old_rect->height - new_height) % 2;
+ rect->y = old_rect->y + (old_rect->height - new_height)/2;
+ break;
+
+ case SouthWestGravity:
+ case SouthGravity:
+ case SouthEastGravity:
+ rect->y = old_rect->y + (old_rect->height - new_height);
+ break;
+
+ case StaticGravity:
+ default:
+ rect->y = old_rect->y;
+ break;
+ }
+ rect->height = new_height;
+}
+
+/* Not so simple helper function for get_minimal_spanning_set_for_region() */
+static GList*
+merge_spanning_rects_in_region (GList *region)
+{
+ /* NOTE FOR ANY OPTIMIZATION PEOPLE OUT THERE: Please see the
+ * documentation of get_minimal_spanning_set_for_region() for performance
+ * considerations that also apply to this function.
+ */
+
+ GList* compare;
+ compare = region;
+
+ if (region == NULL)
+ {
+ meta_warning ("Region to merge was empty! Either you have a some "
+ "pathological STRUT list or there's a bug somewhere!\n");
+ return NULL;
+ }
+
+ while (compare && compare->next)
+ {
+ MetaRectangle *a = compare->data;
+ GList *other = compare->next;
+
+ g_assert (a->width > 0 && a->height > 0);
+
+ while (other)
+ {
+ MetaRectangle *b = other->data;
+ GList *delete_me = NULL;
+
+ g_assert (b->width > 0 && b->height > 0);
+
+ /* If a contains b, just remove b */
+ if (meta_rectangle_contains_rect (a, b))
+ {
+ delete_me = other;
+ }
+ /* If b contains a, just remove a */
+ else if (meta_rectangle_contains_rect (a, b))
+ {
+ delete_me = compare;
+ }
+ /* If a and b might be mergeable horizontally */
+ else if (a->y == b->y && a->height == b->height)
+ {
+ /* If a and b overlap */
+ if (meta_rectangle_overlap (a, b))
+ {
+ int new_x = MIN (a->x, b->x);
+ a->width = MAX (a->x + a->width, b->x + b->width) - new_x;
+ a->x = new_x;
+ delete_me = other;
+ }
+ /* If a and b are adjacent */
+ else if (a->x + a->width == b->x || a->x == b->x + b->width)
+ {
+ int new_x = MIN (a->x, b->x);
+ a->width = MAX (a->x + a->width, b->x + b->width) - new_x;
+ a->x = new_x;
+ delete_me = other;
+ }
+ }
+ /* If a and b might be mergeable vertically */
+ else if (a->x == b->x && a->width == b->width)
+ {
+ /* If a and b overlap */
+ if (meta_rectangle_overlap (a, b))
+ {
+ int new_y = MIN (a->y, b->y);
+ a->height = MAX (a->y + a->height, b->y + b->height) - new_y;
+ a->y = new_y;
+ delete_me = other;
+ }
+ /* If a and b are adjacent */
+ else if (a->y + a->height == b->y || a->y == b->y + b->height)
+ {
+ int new_y = MIN (a->y, b->y);
+ a->height = MAX (a->y + a->height, b->y + b->height) - new_y;
+ a->y = new_y;
+ delete_me = other;
+ }
+ }
+
+ other = other->next;
+
+ /* Delete any rectangle in the list that is no longer wanted */
+ if (delete_me != NULL)
+ {
+ /* Deleting the rect we compare others to is a little tricker */
+ if (compare == delete_me)
+ {
+ compare = compare->next;
+ other = compare->next;
+ a = compare->data;
+ }
+
+ /* Okay, we can free it now */
+ g_free (delete_me->data);
+ region = g_list_delete_link (region, delete_me);
+ }
+
+ }
+
+ compare = compare->next;
+ }
+
+ return region;
+}
+
+/* Simple helper function for get_minimal_spanning_set_for_region()... */
+static gint
+compare_rect_areas (gconstpointer a, gconstpointer b)
+{
+ const MetaRectangle *a_rect = (gconstpointer) a;
+ const MetaRectangle *b_rect = (gconstpointer) b;
+
+ int a_area = meta_rectangle_area (a_rect);
+ int b_area = meta_rectangle_area (b_rect);
+
+ return b_area - a_area; /* positive ret value denotes b > a, ... */
+}
+
+/* This function is trying to find a "minimal spanning set (of rectangles)"
+ * for a given region.
+ *
+ * The region is given by taking basic_rect, then removing the areas
+ * covered by all the rectangles in the all_struts list, and then expanding
+ * the resulting region by the given number of pixels in each direction.
+ *
+ * A "minimal spanning set (of rectangles)" is the best name I could come
+ * up with for the concept I had in mind. Basically, for a given region, I
+ * want a set of rectangles with the property that a window is contained in
+ * the region if and only if it is contained within at least one of the
+ * rectangles.
+ *
+ * The GList* returned will be a list of (allocated) MetaRectangles.
+ * The list will need to be freed by calling
+ * meta_rectangle_free_spanning_set() on it (or by manually
+ * implementing that function...)
+ */
+GList*
+meta_rectangle_get_minimal_spanning_set_for_region (
+ const MetaRectangle *basic_rect,
+ const GSList *all_struts)
+{
+ /* NOTE FOR OPTIMIZERS: This function *might* be somewhat slow,
+ * especially due to the call to merge_spanning_rects_in_region() (which
+ * is O(n^2) where n is the size of the list generated in this function).
+ * This is made more onerous due to the fact that it involves a fair
+ * number of memory allocation and deallocation calls. However, n is 1
+ * for default installations of Mate (because partial struts aren't used
+ * by default and only partial struts increase the size of the spanning
+ * set generated). With one partial strut, n will be 2 or 3. With 2
+ * partial struts, n will probably be 4 or 5. So, n probably isn't large
+ * enough to make this worth bothering. Further, it is only called from
+ * workspace.c:ensure_work_areas_validated (at least as of the time of
+ * writing this comment), which in turn should only be called if the
+ * strut list changes or the screen or xinerama size changes. If it ever
+ * does show up on profiles (most likely because people start using
+ * ridiculously huge numbers of partial struts), possible optimizations
+ * include:
+ *
+ * (1) rewrite merge_spanning_rects_in_region() to be O(n) or O(nlogn).
+ * I'm not totally sure it's possible, but with a couple copies of
+ * the list and sorting them appropriately, I believe it might be.
+ * (2) only call merge_spanning_rects_in_region() with a subset of the
+ * full list of rectangles. I believe from some of my preliminary
+ * debugging and thinking about it that it is possible to figure out
+ * apriori groups of rectangles which are only merge candidates with
+ * each other. (See testboxes.c:get_screen_region() when which==2
+ * and track the steps of this function carefully to see what gave
+ * me the hint that this might work)
+ * (3) figure out how to avoid merge_spanning_rects_in_region(). I think
+ * it might be possible to modify this function to make that
+ * possible, and I spent just a little while thinking about it, but n
+ * wasn't large enough to convince me to care yet.
+ * (4) Some of the stuff Rob mentioned at http://mail.gnome.org/archives\
+ * /marco-devel-list/2005-November/msg00028.html. (Sorry for the
+ * URL splitting.)
+ */
+
+ GList *ret;
+ GList *tmp_list;
+ const GSList *strut_iter;
+ MetaRectangle *temp_rect;
+
+ /* The algorithm is basically as follows:
+ * Initialize rectangle_set to basic_rect
+ * Foreach strut:
+ * Foreach rectangle in rectangle_set:
+ * - Split the rectangle into new rectangles that don't overlap the
+ * strut (but which are as big as possible otherwise)
+ * - Remove the old (pre-split) rectangle from the rectangle_set,
+ * and replace it with the new rectangles generated from the
+ * splitting
+ */
+
+ temp_rect = g_new (MetaRectangle, 1);
+ *temp_rect = *basic_rect;
+ ret = g_list_prepend (NULL, temp_rect);
+
+ strut_iter = all_struts;
+ for (strut_iter = all_struts; strut_iter; strut_iter = strut_iter->next)
+ {
+ GList *rect_iter;
+ MetaRectangle *strut_rect = &((MetaStrut*)strut_iter->data)->rect;
+
+ tmp_list = ret;
+ ret = NULL;
+ rect_iter = tmp_list;
+ while (rect_iter)
+ {
+ MetaRectangle *rect = (MetaRectangle*) rect_iter->data;
+ if (!meta_rectangle_overlap (rect, strut_rect))
+ ret = g_list_prepend (ret, rect);
+ else
+ {
+ /* If there is area in rect left of strut */
+ if (BOX_LEFT (*rect) < BOX_LEFT (*strut_rect))
+ {
+ temp_rect = g_new (MetaRectangle, 1);
+ *temp_rect = *rect;
+ temp_rect->width = BOX_LEFT (*strut_rect) - BOX_LEFT (*rect);
+ ret = g_list_prepend (ret, temp_rect);
+ }
+ /* If there is area in rect right of strut */
+ if (BOX_RIGHT (*rect) > BOX_RIGHT (*strut_rect))
+ {
+ int new_x;
+ temp_rect = g_new (MetaRectangle, 1);
+ *temp_rect = *rect;
+ new_x = BOX_RIGHT (*strut_rect);
+ temp_rect->width = BOX_RIGHT(*rect) - new_x;
+ temp_rect->x = new_x;
+ ret = g_list_prepend (ret, temp_rect);
+ }
+ /* If there is area in rect above strut */
+ if (BOX_TOP (*rect) < BOX_TOP (*strut_rect))
+ {
+ temp_rect = g_new (MetaRectangle, 1);
+ *temp_rect = *rect;
+ temp_rect->height = BOX_TOP (*strut_rect) - BOX_TOP (*rect);
+ ret = g_list_prepend (ret, temp_rect);
+ }
+ /* If there is area in rect below strut */
+ if (BOX_BOTTOM (*rect) > BOX_BOTTOM (*strut_rect))
+ {
+ int new_y;
+ temp_rect = g_new (MetaRectangle, 1);
+ *temp_rect = *rect;
+ new_y = BOX_BOTTOM (*strut_rect);
+ temp_rect->height = BOX_BOTTOM (*rect) - new_y;
+ temp_rect->y = new_y;
+ ret = g_list_prepend (ret, temp_rect);
+ }
+ g_free (rect);
+ }
+ rect_iter = rect_iter->next;
+ }
+ g_list_free (tmp_list);
+ }
+
+ /* Sort by maximal area, just because I feel like it... */
+ ret = g_list_sort (ret, compare_rect_areas);
+
+ /* Merge rectangles if possible so that the list really is minimal */
+ ret = merge_spanning_rects_in_region (ret);
+
+ return ret;
+}
+
+GList*
+meta_rectangle_expand_region (GList *region,
+ const int left_expand,
+ const int right_expand,
+ const int top_expand,
+ const int bottom_expand)
+{
+ return meta_rectangle_expand_region_conditionally (region,
+ left_expand,
+ right_expand,
+ top_expand,
+ bottom_expand,
+ 0,
+ 0);
+}
+
+GList*
+meta_rectangle_expand_region_conditionally (GList *region,
+ const int left_expand,
+ const int right_expand,
+ const int top_expand,
+ const int bottom_expand,
+ const int min_x,
+ const int min_y)
+{
+ GList *tmp_list = region;
+ while (tmp_list)
+ {
+ MetaRectangle *rect = (MetaRectangle*) tmp_list->data;
+ if (rect->width >= min_x)
+ {
+ rect->x -= left_expand;
+ rect->width += (left_expand + right_expand);
+ }
+ if (rect->height >= min_y)
+ {
+ rect->y -= top_expand;
+ rect->height += (top_expand + bottom_expand);
+ }
+ tmp_list = tmp_list->next;
+ }
+
+ return region;
+}
+
+void
+meta_rectangle_expand_to_avoiding_struts (MetaRectangle *rect,
+ const MetaRectangle *expand_to,
+ const MetaDirection direction,
+ const GSList *all_struts)
+{
+ const GSList *strut_iter;
+
+ /* If someone wants this function to handle more fine-grained
+ * direction expanding in the future (e.g. only left, or fully
+ * horizontal plus upward), feel free. But I'm hard-coding for both
+ * horizontal directions (exclusive-)or both vertical directions.
+ */
+ g_assert ((direction == META_DIRECTION_HORIZONTAL) ^
+ (direction == META_DIRECTION_VERTICAL ));
+
+ if (direction == META_DIRECTION_HORIZONTAL)
+ {
+ rect->x = expand_to->x;
+ rect->width = expand_to->width;
+ }
+ else
+ {
+ rect->y = expand_to->y;
+ rect->height = expand_to->height;
+ }
+
+
+ /* Run over all struts */
+ for (strut_iter = all_struts; strut_iter; strut_iter = strut_iter->next)
+ {
+ MetaStrut *strut = (MetaStrut*) strut_iter->data;
+
+ /* Skip struts that don't overlap */
+ if (!meta_rectangle_overlap (&strut->rect, rect))
+ continue;
+
+ if (direction == META_DIRECTION_HORIZONTAL)
+ {
+ if (strut->side == META_SIDE_LEFT)
+ {
+ int offset = BOX_RIGHT(strut->rect) - BOX_LEFT(*rect);
+ rect->x += offset;
+ rect->width -= offset;
+ }
+ else if (strut->side == META_SIDE_RIGHT)
+ {
+ int offset = BOX_RIGHT (*rect) - BOX_LEFT(strut->rect);
+ rect->width -= offset;
+ }
+ /* else ignore the strut */
+ }
+ else /* direction == META_DIRECTION_VERTICAL */
+ {
+ if (strut->side == META_SIDE_TOP)
+ {
+ int offset = BOX_BOTTOM(strut->rect) - BOX_TOP(*rect);
+ rect->y += offset;
+ rect->height -= offset;
+ }
+ else if (strut->side == META_SIDE_BOTTOM)
+ {
+ int offset = BOX_BOTTOM(*rect) - BOX_TOP(strut->rect);
+ rect->height -= offset;
+ }
+ /* else ignore the strut */
+ }
+ } /* end loop over struts */
+} /* end meta_rectangle_expand_to_avoiding_struts */
+
+void
+meta_rectangle_free_list_and_elements (GList *filled_list)
+{
+ g_list_foreach (filled_list,
+ (void (*)(gpointer,gpointer))&g_free, /* ew, for ugly */
+ NULL);
+ g_list_free (filled_list);
+}
+
+gboolean
+meta_rectangle_could_fit_in_region (const GList *spanning_rects,
+ const MetaRectangle *rect)
+{
+ const GList *temp;
+ gboolean could_fit;
+
+ temp = spanning_rects;
+ could_fit = FALSE;
+ while (!could_fit && temp != NULL)
+ {
+ could_fit = could_fit || meta_rectangle_could_fit_rect (temp->data, rect);
+ temp = temp->next;
+ }
+
+ return could_fit;
+}
+
+gboolean
+meta_rectangle_contained_in_region (const GList *spanning_rects,
+ const MetaRectangle *rect)
+{
+ const GList *temp;
+ gboolean contained;
+
+ temp = spanning_rects;
+ contained = FALSE;
+ while (!contained && temp != NULL)
+ {
+ contained = contained || meta_rectangle_contains_rect (temp->data, rect);
+ temp = temp->next;
+ }
+
+ return contained;
+}
+
+gboolean
+meta_rectangle_overlaps_with_region (const GList *spanning_rects,
+ const MetaRectangle *rect)
+{
+ const GList *temp;
+ gboolean overlaps;
+
+ temp = spanning_rects;
+ overlaps = FALSE;
+ while (!overlaps && temp != NULL)
+ {
+ overlaps = overlaps || meta_rectangle_overlap (temp->data, rect);
+ temp = temp->next;
+ }
+
+ return overlaps;
+}
+
+
+void
+meta_rectangle_clamp_to_fit_into_region (const GList *spanning_rects,
+ FixedDirections fixed_directions,
+ MetaRectangle *rect,
+ const MetaRectangle *min_size)
+{
+ const GList *temp;
+ const MetaRectangle *best_rect = NULL;
+ int best_overlap = 0;
+
+ /* First, find best rectangle from spanning_rects to which we can clamp
+ * rect to fit into.
+ */
+ for (temp = spanning_rects; temp; temp = temp->next)
+ {
+ MetaRectangle *compare_rect = temp->data;
+ int maximal_overlap_amount_for_compare;
+
+ /* If x is fixed and the entire width of rect doesn't fit in compare,
+ * skip this rectangle.
+ */
+ if ((fixed_directions & FIXED_DIRECTION_X) &&
+ (compare_rect->x > rect->x ||
+ compare_rect->x + compare_rect->width < rect->x + rect->width))
+ continue;
+
+ /* If y is fixed and the entire height of rect doesn't fit in compare,
+ * skip this rectangle.
+ */
+ if ((fixed_directions & FIXED_DIRECTION_Y) &&
+ (compare_rect->y > rect->y ||
+ compare_rect->y + compare_rect->height < rect->y + rect->height))
+ continue;
+
+ /* If compare can't hold the min_size window, skip this rectangle. */
+ if (compare_rect->width < min_size->width ||
+ compare_rect->height < min_size->height)
+ continue;
+
+ /* Determine maximal overlap amount */
+ maximal_overlap_amount_for_compare =
+ MIN (rect->width, compare_rect->width) *
+ MIN (rect->height, compare_rect->height);
+
+ /* See if this is the best rect so far */
+ if (maximal_overlap_amount_for_compare > best_overlap)
+ {
+ best_rect = compare_rect;
+ best_overlap = maximal_overlap_amount_for_compare;
+ }
+ }
+
+ /* Clamp rect appropriately */
+ if (best_rect == NULL)
+ {
+ meta_warning ("No rect whose size to clamp to found!\n");
+
+ /* If it doesn't fit, at least make it no bigger than it has to be */
+ if (!(fixed_directions & FIXED_DIRECTION_X))
+ rect->width = min_size->width;
+ if (!(fixed_directions & FIXED_DIRECTION_Y))
+ rect->height = min_size->height;
+ }
+ else
+ {
+ rect->width = MIN (rect->width, best_rect->width);
+ rect->height = MIN (rect->height, best_rect->height);
+ }
+}
+
+void
+meta_rectangle_clip_to_region (const GList *spanning_rects,
+ FixedDirections fixed_directions,
+ MetaRectangle *rect)
+{
+ const GList *temp;
+ const MetaRectangle *best_rect = NULL;
+ int best_overlap = 0;
+
+ /* First, find best rectangle from spanning_rects to which we will clip
+ * rect into.
+ */
+ for (temp = spanning_rects; temp; temp = temp->next)
+ {
+ MetaRectangle *compare_rect = temp->data;
+ MetaRectangle overlap;
+ int maximal_overlap_amount_for_compare;
+
+ /* If x is fixed and the entire width of rect doesn't fit in compare,
+ * skip the rectangle.
+ */
+ if ((fixed_directions & FIXED_DIRECTION_X) &&
+ (compare_rect->x > rect->x ||
+ compare_rect->x + compare_rect->width < rect->x + rect->width))
+ continue;
+
+ /* If y is fixed and the entire height of rect doesn't fit in compare,
+ * skip the rectangle.
+ */
+ if ((fixed_directions & FIXED_DIRECTION_Y) &&
+ (compare_rect->y > rect->y ||
+ compare_rect->y + compare_rect->height < rect->y + rect->height))
+ continue;
+
+ /* Determine maximal overlap amount */
+ meta_rectangle_intersect (rect, compare_rect, &overlap);
+ maximal_overlap_amount_for_compare = meta_rectangle_area (&overlap);
+
+ /* See if this is the best rect so far */
+ if (maximal_overlap_amount_for_compare > best_overlap)
+ {
+ best_rect = compare_rect;
+ best_overlap = maximal_overlap_amount_for_compare;
+ }
+ }
+
+ /* Clip rect appropriately */
+ if (best_rect == NULL)
+ meta_warning ("No rect to clip to found!\n");
+ else
+ {
+ /* Extra precaution with checking fixed direction shouldn't be needed
+ * due to logic above, but it shouldn't hurt either.
+ */
+ if (!(fixed_directions & FIXED_DIRECTION_X))
+ {
+ /* Find the new left and right */
+ int new_x = MAX (rect->x, best_rect->x);
+ rect->width = MIN ((rect->x + rect->width) - new_x,
+ (best_rect->x + best_rect->width) - new_x);
+ rect->x = new_x;
+ }
+
+ /* Extra precaution with checking fixed direction shouldn't be needed
+ * due to logic above, but it shouldn't hurt either.
+ */
+ if (!(fixed_directions & FIXED_DIRECTION_Y))
+ {
+ /* Clip the top, if needed */
+ int new_y = MAX (rect->y, best_rect->y);
+ rect->height = MIN ((rect->y + rect->height) - new_y,
+ (best_rect->y + best_rect->height) - new_y);
+ rect->y = new_y;
+ }
+ }
+}
+
+void
+meta_rectangle_shove_into_region (const GList *spanning_rects,
+ FixedDirections fixed_directions,
+ MetaRectangle *rect)
+{
+ const GList *temp;
+ const MetaRectangle *best_rect = NULL;
+ int best_overlap = 0;
+ int shortest_distance = G_MAXINT;
+
+ /* First, find best rectangle from spanning_rects to which we will shove
+ * rect into.
+ */
+
+ for (temp = spanning_rects; temp; temp = temp->next)
+ {
+ MetaRectangle *compare_rect = temp->data;
+ int maximal_overlap_amount_for_compare;
+ int dist_to_compare;
+
+ /* If x is fixed and the entire width of rect doesn't fit in compare,
+ * skip this rectangle.
+ */
+ if ((fixed_directions & FIXED_DIRECTION_X) &&
+ (compare_rect->x > rect->x ||
+ compare_rect->x + compare_rect->width < rect->x + rect->width))
+ continue;
+
+ /* If y is fixed and the entire height of rect doesn't fit in compare,
+ * skip this rectangle.
+ */
+ if ((fixed_directions & FIXED_DIRECTION_Y) &&
+ (compare_rect->y > rect->y ||
+ compare_rect->y + compare_rect->height < rect->y + rect->height))
+ continue;
+
+ /* Determine maximal overlap amount between rect & compare_rect */
+ maximal_overlap_amount_for_compare =
+ MIN (rect->width, compare_rect->width) *
+ MIN (rect->height, compare_rect->height);
+
+ /* Determine distance necessary to put rect into compare_rect */
+ dist_to_compare = 0;
+ if (compare_rect->x > rect->x)
+ dist_to_compare += compare_rect->x - rect->x;
+ if (compare_rect->x + compare_rect->width < rect->x + rect->width)
+ dist_to_compare += (rect->x + rect->width) -
+ (compare_rect->x + compare_rect->width);
+ if (compare_rect->y > rect->y)
+ dist_to_compare += compare_rect->y - rect->y;
+ if (compare_rect->y + compare_rect->height < rect->y + rect->height)
+ dist_to_compare += (rect->y + rect->height) -
+ (compare_rect->y + compare_rect->height);
+
+ /* See if this is the best rect so far */
+ if ((maximal_overlap_amount_for_compare > best_overlap) ||
+ (maximal_overlap_amount_for_compare == best_overlap &&
+ dist_to_compare < shortest_distance))
+ {
+ best_rect = compare_rect;
+ best_overlap = maximal_overlap_amount_for_compare;
+ shortest_distance = dist_to_compare;
+ }
+ }
+
+ /* Shove rect appropriately */
+ if (best_rect == NULL)
+ meta_warning ("No rect to shove into found!\n");
+ else
+ {
+ /* Extra precaution with checking fixed direction shouldn't be needed
+ * due to logic above, but it shouldn't hurt either.
+ */
+ if (!(fixed_directions & FIXED_DIRECTION_X))
+ {
+ /* Shove to the right, if needed */
+ if (best_rect->x > rect->x)
+ rect->x = best_rect->x;
+
+ /* Shove to the left, if needed */
+ if (best_rect->x + best_rect->width < rect->x + rect->width)
+ rect->x = (best_rect->x + best_rect->width) - rect->width;
+ }
+
+ /* Extra precaution with checking fixed direction shouldn't be needed
+ * due to logic above, but it shouldn't hurt either.
+ */
+ if (!(fixed_directions & FIXED_DIRECTION_Y))
+ {
+ /* Shove down, if needed */
+ if (best_rect->y > rect->y)
+ rect->y = best_rect->y;
+
+ /* Shove up, if needed */
+ if (best_rect->y + best_rect->height < rect->y + rect->height)
+ rect->y = (best_rect->y + best_rect->height) - rect->height;
+ }
+ }
+}
+
+void
+meta_rectangle_find_linepoint_closest_to_point (double x1,
+ double y1,
+ double x2,
+ double y2,
+ double px,
+ double py,
+ double *valx,
+ double *valy)
+{
+ /* I'll use the shorthand rx, ry for the return values, valx & valy.
+ * Now, we need (rx,ry) to be on the line between (x1,y1) and (x2,y2).
+ * For that to happen, we first need the slope of the line from (x1,y1)
+ * to (rx,ry) must match the slope of (x1,y1) to (x2,y2), i.e.:
+ * (ry-y1) (y2-y1)
+ * ------- = -------
+ * (rx-x1) (x2-x1)
+ * If x1==x2, though, this gives divide by zero errors, so we want to
+ * rewrite the equation by multiplying both sides by (rx-x1)*(x2-x1):
+ * (ry-y1)(x2-x1) = (y2-y1)(rx-x1)
+ * This is a valid requirement even when x1==x2 (when x1==x2, this latter
+ * equation will basically just mean that rx must be equal to both x1 and
+ * x2)
+ *
+ * The other requirement that we have is that the line from (rx,ry) to
+ * (px,py) must be perpendicular to the line from (x1,y1) to (x2,y2). So
+ * we just need to get a vector in the direction of each line, take the
+ * dot product of the two, and ensure that the result is 0:
+ * (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0.
+ *
+ * This gives us two equations and two unknowns:
+ *
+ * (ry-y1)(x2-x1) = (y2-y1)(rx-x1)
+ * (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0.
+ *
+ * This particular pair of equations is always solvable so long as
+ * (x1,y1) and (x2,y2) are not the same point (and note that anyone who
+ * calls this function that way is braindead because it means that they
+ * really didn't specify a line after all). However, the caller should
+ * be careful to avoid making (x1,y1) and (x2,y2) too close (e.g. like
+ * 10^{-8} apart in each coordinate), otherwise roundoff error could
+ * cause issues. Solving these equations by hand (or using Maple(TM) or
+ * Mathematica(TM) or whatever) results in slightly messy expressions,
+ * but that's all the below few lines do.
+ */
+
+ double diffx, diffy, den;
+ diffx = x2 - x1;
+ diffy = y2 - y1;
+ den = diffx * diffx + diffy * diffy;
+
+ *valx = (py * diffx * diffy + px * diffx * diffx +
+ y2 * x1 * diffy - y1 * x2 * diffy) / den;
+ *valy = (px * diffx * diffy + py * diffy * diffy +
+ x2 * y1 * diffx - x1 * y2 * diffx) / den;
+}
+
+/***************************************************************************/
+/* */
+/* Switching gears to code for edges instead of just rectangles */
+/* */
+/***************************************************************************/
+
+gboolean
+meta_rectangle_edge_aligns (const MetaRectangle *rect, const MetaEdge *edge)
+{
+ /* The reason for the usage of <= below instead of < is because we are
+ * interested in in-the-way-or-adject'ness. So, a left (i.e. vertical
+ * edge) occupying y positions 0-9 (which has a y of 0 and a height of
+ * 10) and a rectangle with top at y=10 would be considered to "align" by
+ * this function.
+ */
+ switch (edge->side_type)
+ {
+ case META_SIDE_LEFT:
+ case META_SIDE_RIGHT:
+ return BOX_TOP (*rect) <= BOX_BOTTOM (edge->rect) &&
+ BOX_TOP (edge->rect) <= BOX_BOTTOM (*rect);
+ case META_SIDE_TOP:
+ case META_SIDE_BOTTOM:
+ return BOX_LEFT (*rect) <= BOX_RIGHT (edge->rect) &&
+ BOX_LEFT (edge->rect) <= BOX_RIGHT (*rect);
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static GList*
+get_rect_minus_overlap (const GList *rect_in_list,
+ MetaRectangle *overlap)
+{
+ MetaRectangle *temp;
+ MetaRectangle *rect = rect_in_list->data;
+ GList *ret = NULL;
+
+ if (BOX_LEFT (*rect) < BOX_LEFT (*overlap))
+ {
+ temp = g_new (MetaRectangle, 1);
+ *temp = *rect;
+ temp->width = BOX_LEFT (*overlap) - BOX_LEFT (*rect);
+ ret = g_list_prepend (ret, temp);
+ }
+ if (BOX_RIGHT (*rect) > BOX_RIGHT (*overlap))
+ {
+ temp = g_new (MetaRectangle, 1);
+ *temp = *rect;
+ temp->x = BOX_RIGHT (*overlap);
+ temp->width = BOX_RIGHT (*rect) - BOX_RIGHT (*overlap);
+ ret = g_list_prepend (ret, temp);
+ }
+ if (BOX_TOP (*rect) < BOX_TOP (*overlap))
+ {
+ temp = g_new (MetaRectangle, 1);
+ temp->x = overlap->x;
+ temp->width = overlap->width;
+ temp->y = BOX_TOP (*rect);
+ temp->height = BOX_TOP (*overlap) - BOX_TOP (*rect);
+ ret = g_list_prepend (ret, temp);
+ }
+ if (BOX_BOTTOM (*rect) > BOX_BOTTOM (*overlap))
+ {
+ temp = g_new (MetaRectangle, 1);
+ temp->x = overlap->x;
+ temp->width = overlap->width;
+ temp->y = BOX_BOTTOM (*overlap);
+ temp->height = BOX_BOTTOM (*rect) - BOX_BOTTOM (*overlap);
+ ret = g_list_prepend (ret, temp);
+ }
+
+ return ret;
+}
+
+static GList*
+replace_rect_with_list (GList *old_element,
+ GList *new_list)
+{
+ GList *ret;
+ g_assert (old_element != NULL);
+
+ if (!new_list)
+ {
+ /* If there is no new list, just remove the old_element */
+ ret = g_list_remove_link (old_element, old_element);
+ }
+ else
+ {
+ /* Fix up the prev and next pointers everywhere */
+ ret = new_list;
+ if (old_element->prev)
+ {
+ old_element->prev->next = new_list;
+ new_list->prev = old_element->prev;
+ }
+ if (old_element->next)
+ {
+ GList *tmp = g_list_last (new_list);
+ old_element->next->prev = tmp;
+ tmp->next = old_element->next;
+ }
+ }
+
+ /* Free the old_element and return the appropriate "next" point */
+ g_free (old_element->data);
+ g_list_free_1 (old_element);
+ return ret;
+}
+
+/* Make a copy of the strut list, make sure that copy only contains parts
+ * of the old_struts that intersect with the region rect, and then do some
+ * magic to make all the new struts disjoint (okay, we we break up struts
+ * that aren't disjoint in a way that the overlapping part is only included
+ * once, so it's not really magic...).
+ */
+static GList*
+get_disjoint_strut_rect_list_in_region (const GSList *old_struts,
+ const MetaRectangle *region)
+{
+ GList *strut_rects;
+ GList *tmp;
+
+ /* First, copy the list */
+ strut_rects = NULL;
+ while (old_struts)
+ {
+ MetaRectangle *cur = &((MetaStrut*)old_struts->data)->rect;
+ MetaRectangle *copy = g_new (MetaRectangle, 1);
+ *copy = *cur;
+ if (meta_rectangle_intersect (copy, region, copy))
+ strut_rects = g_list_prepend (strut_rects, copy);
+ else
+ g_free (copy);
+
+ old_struts = old_struts->next;
+ }
+
+ /* Now, loop over the list and check for intersections, fixing things up
+ * where they do intersect.
+ */
+ tmp = strut_rects;
+ while (tmp)
+ {
+ GList *compare;
+
+ MetaRectangle *cur = tmp->data;
+
+ compare = tmp->next;
+ while (compare)
+ {
+ MetaRectangle *comp = compare->data;
+ MetaRectangle overlap;
+
+ if (meta_rectangle_intersect (cur, comp, &overlap))
+ {
+ /* Get a list of rectangles for each strut that don't overlap
+ * the intersection region.
+ */
+ GList *cur_leftover = get_rect_minus_overlap (tmp, &overlap);
+ GList *comp_leftover = get_rect_minus_overlap (compare, &overlap);
+
+ /* Add the intersection region to cur_leftover */
+ MetaRectangle *overlap_allocated = g_new (MetaRectangle, 1);
+ *overlap_allocated = overlap;
+ cur_leftover = g_list_prepend (cur_leftover, overlap_allocated);
+
+ /* Fix up tmp, compare, and cur -- maybe struts too */
+ if (strut_rects == tmp)
+ {
+ strut_rects = replace_rect_with_list (tmp, cur_leftover);
+ tmp = strut_rects;
+ }
+ else
+ tmp = replace_rect_with_list (tmp, cur_leftover);
+ compare = replace_rect_with_list (compare, comp_leftover);
+
+ if (compare == NULL)
+ break;
+
+ cur = tmp->data;
+ }
+
+ compare = compare->next;
+ }
+
+ tmp = tmp->next;
+ }
+
+ return strut_rects;
+}
+
+gint
+meta_rectangle_edge_cmp_ignore_type (gconstpointer a, gconstpointer b)
+{
+ const MetaEdge *a_edge_rect = (gconstpointer) a;
+ const MetaEdge *b_edge_rect = (gconstpointer) b;
+ int a_compare, b_compare;
+
+ /* Edges must be both vertical or both horizontal, or it doesn't make
+ * sense to compare them.
+ */
+ g_assert ((a_edge_rect->rect.width == 0 && b_edge_rect->rect.width == 0) ||
+ (a_edge_rect->rect.height == 0 && b_edge_rect->rect.height == 0));
+
+ a_compare = b_compare = 0; /* gcc-3.4.2 sucks at figuring initialized'ness */
+
+ if (a_edge_rect->side_type == META_SIDE_LEFT ||
+ a_edge_rect->side_type == META_SIDE_RIGHT)
+ {
+ a_compare = a_edge_rect->rect.x;
+ b_compare = b_edge_rect->rect.x;
+ if (a_compare == b_compare)
+ {
+ a_compare = a_edge_rect->rect.y;
+ b_compare = b_edge_rect->rect.y;
+ }
+ }
+ else if (a_edge_rect->side_type == META_SIDE_TOP ||
+ a_edge_rect->side_type == META_SIDE_BOTTOM)
+ {
+ a_compare = a_edge_rect->rect.y;
+ b_compare = b_edge_rect->rect.y;
+ if (a_compare == b_compare)
+ {
+ a_compare = a_edge_rect->rect.x;
+ b_compare = b_edge_rect->rect.x;
+ }
+ }
+ else
+ g_assert ("Some idiot wanted to sort sides of different types.\n");
+
+ return a_compare - b_compare; /* positive value denotes a > b ... */
+}
+
+/* To make things easily testable, provide a nice way of sorting edges */
+gint
+meta_rectangle_edge_cmp (gconstpointer a, gconstpointer b)
+{
+ const MetaEdge *a_edge_rect = (gconstpointer) a;
+ const MetaEdge *b_edge_rect = (gconstpointer) b;
+
+ int a_compare, b_compare;
+
+ a_compare = a_edge_rect->side_type;
+ b_compare = b_edge_rect->side_type;
+
+ if (a_compare == b_compare)
+ return meta_rectangle_edge_cmp_ignore_type (a, b);
+
+ return a_compare - b_compare; /* positive value denotes a > b ... */
+}
+
+/* Determine whether two given edges overlap */
+static gboolean
+edges_overlap (const MetaEdge *edge1,
+ const MetaEdge *edge2)
+{
+ if (edge1->rect.width == 0 && edge2->rect.width == 0)
+ {
+ return meta_rectangle_vert_overlap (&edge1->rect, &edge2->rect) &&
+ edge1->rect.x == edge2->rect.x;
+ }
+ else if (edge1->rect.height == 0 && edge2->rect.height == 0)
+ {
+ return meta_rectangle_horiz_overlap (&edge1->rect, &edge2->rect) &&
+ edge1->rect.y == edge2->rect.y;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+static gboolean
+rectangle_and_edge_intersection (const MetaRectangle *rect,
+ const MetaEdge *edge,
+ MetaEdge *overlap,
+ int *handle_type)
+{
+ const MetaRectangle *rect2 = &edge->rect;
+ MetaRectangle *result = &overlap->rect;
+ gboolean intersect = TRUE;
+
+ /* We don't know how to set these, so set them to invalid values */
+ overlap->edge_type = -1;
+ overlap->side_type = -1;
+
+ /* Figure out what the intersection is */
+ result->x = MAX (rect->x, rect2->x);
+ result->y = MAX (rect->y, rect2->y);
+ result->width = MIN (BOX_RIGHT (*rect), BOX_RIGHT (*rect2)) - result->x;
+ result->height = MIN (BOX_BOTTOM (*rect), BOX_BOTTOM (*rect2)) - result->y;
+
+ /* Find out if the intersection is empty; have to do it this way since
+ * edges have a thickness of 0
+ */
+ if ((result->width < 0 || result->height < 0) ||
+ (result->width == 0 && result->height == 0))
+ {
+ result->width = 0;
+ result->height = 0;
+ intersect = FALSE;
+ }
+ else
+ {
+ /* Need to figure out the handle_type, a somewhat weird quantity:
+ * 0 - overlap is in middle of rect
+ * -1 - overlap is at the side of rect, and is on the opposite side
+ * of rect than the edge->side_type side
+ * 1 - overlap is at the side of rect, and the side of rect it is
+ * on is the edge->side_type side
+ */
+ switch (edge->side_type)
+ {
+ case META_SIDE_LEFT:
+ if (result->x == rect->x)
+ *handle_type = 1;
+ else if (result->x == BOX_RIGHT (*rect))
+ *handle_type = -1;
+ else
+ *handle_type = 0;
+ break;
+ case META_SIDE_RIGHT:
+ if (result->x == rect->x)
+ *handle_type = -1;
+ else if (result->x == BOX_RIGHT (*rect))
+ *handle_type = 1;
+ else
+ *handle_type = 0;
+ break;
+ case META_SIDE_TOP:
+ if (result->y == rect->y)
+ *handle_type = 1;
+ else if (result->y == BOX_BOTTOM (*rect))
+ *handle_type = -1;
+ else
+ *handle_type = 0;
+ break;
+ case META_SIDE_BOTTOM:
+ if (result->y == rect->y)
+ *handle_type = -1;
+ else if (result->y == BOX_BOTTOM (*rect))
+ *handle_type = 1;
+ else
+ *handle_type = 0;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+ return intersect;
+}
+
+/* Add all edges of the given rect to cur_edges and return the result. If
+ * rect_is_internal is false, the side types are switched (LEFT<->RIGHT and
+ * TOP<->BOTTOM).
+ */
+static GList*
+add_edges (GList *cur_edges,
+ const MetaRectangle *rect,
+ gboolean rect_is_internal)
+{
+ MetaEdge *temp_edge;
+ int i;
+
+ for (i=0; i<4; i++)
+ {
+ temp_edge = g_new (MetaEdge, 1);
+ temp_edge->rect = *rect;
+ switch (i)
+ {
+ case 0:
+ temp_edge->side_type =
+ rect_is_internal ? META_SIDE_LEFT : META_SIDE_RIGHT;
+ temp_edge->rect.width = 0;
+ break;
+ case 1:
+ temp_edge->side_type =
+ rect_is_internal ? META_SIDE_RIGHT : META_SIDE_LEFT;
+ temp_edge->rect.x += temp_edge->rect.width;
+ temp_edge->rect.width = 0;
+ break;
+ case 2:
+ temp_edge->side_type =
+ rect_is_internal ? META_SIDE_TOP : META_SIDE_BOTTOM;
+ temp_edge->rect.height = 0;
+ break;
+ case 3:
+ temp_edge->side_type =
+ rect_is_internal ? META_SIDE_BOTTOM : META_SIDE_TOP;
+ temp_edge->rect.y += temp_edge->rect.height;
+ temp_edge->rect.height = 0;
+ break;
+ }
+ temp_edge->edge_type = META_EDGE_SCREEN;
+ cur_edges = g_list_prepend (cur_edges, temp_edge);
+ }
+
+ return cur_edges;
+}
+
+/* Remove any part of old_edge that intersects remove and add any resulting
+ * edges to cur_list. Return cur_list when finished.
+ */
+static GList*
+split_edge (GList *cur_list,
+ const MetaEdge *old_edge,
+ const MetaEdge *remove)
+{
+ MetaEdge *temp_edge;
+ switch (old_edge->side_type)
+ {
+ case META_SIDE_LEFT:
+ case META_SIDE_RIGHT:
+ g_assert (meta_rectangle_vert_overlap (&old_edge->rect, &remove->rect));
+ if (BOX_TOP (old_edge->rect) < BOX_TOP (remove->rect))
+ {
+ temp_edge = g_new (MetaEdge, 1);
+ *temp_edge = *old_edge;
+ temp_edge->rect.height = BOX_TOP (remove->rect)
+ - BOX_TOP (old_edge->rect);
+ cur_list = g_list_prepend (cur_list, temp_edge);
+ }
+ if (BOX_BOTTOM (old_edge->rect) > BOX_BOTTOM (remove->rect))
+ {
+ temp_edge = g_new (MetaEdge, 1);
+ *temp_edge = *old_edge;
+ temp_edge->rect.y = BOX_BOTTOM (remove->rect);
+ temp_edge->rect.height = BOX_BOTTOM (old_edge->rect)
+ - BOX_BOTTOM (remove->rect);
+ cur_list = g_list_prepend (cur_list, temp_edge);
+ }
+ break;
+ case META_SIDE_TOP:
+ case META_SIDE_BOTTOM:
+ g_assert (meta_rectangle_horiz_overlap (&old_edge->rect, &remove->rect));
+ if (BOX_LEFT (old_edge->rect) < BOX_LEFT (remove->rect))
+ {
+ temp_edge = g_new (MetaEdge, 1);
+ *temp_edge = *old_edge;
+ temp_edge->rect.width = BOX_LEFT (remove->rect)
+ - BOX_LEFT (old_edge->rect);
+ cur_list = g_list_prepend (cur_list, temp_edge);
+ }
+ if (BOX_RIGHT (old_edge->rect) > BOX_RIGHT (remove->rect))
+ {
+ temp_edge = g_new (MetaEdge, 1);
+ *temp_edge = *old_edge;
+ temp_edge->rect.x = BOX_RIGHT (remove->rect);
+ temp_edge->rect.width = BOX_RIGHT (old_edge->rect)
+ - BOX_RIGHT (remove->rect);
+ cur_list = g_list_prepend (cur_list, temp_edge);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return cur_list;
+}
+
+/* Split up edge and remove preliminary edges from strut_edges depending on
+ * if and how rect and edge intersect.
+ */
+static void
+fix_up_edges (MetaRectangle *rect, MetaEdge *edge,
+ GList **strut_edges, GList **edge_splits,
+ gboolean *edge_needs_removal)
+{
+ MetaEdge overlap;
+ int handle_type;
+
+ if (!rectangle_and_edge_intersection (rect, edge, &overlap, &handle_type))
+ return;
+
+ if (handle_type == 0 || handle_type == 1)
+ {
+ /* Put the result of removing overlap from edge into edge_splits */
+ *edge_splits = split_edge (*edge_splits, edge, &overlap);
+ *edge_needs_removal = TRUE;
+ }
+
+ if (handle_type == -1 || handle_type == 1)
+ {
+ /* Remove the overlap from strut_edges */
+ /* First, loop over the edges of the strut */
+ GList *tmp = *strut_edges;
+ while (tmp)
+ {
+ MetaEdge *cur = tmp->data;
+ /* If this is the edge that overlaps, then we need to split it */
+ if (edges_overlap (cur, &overlap))
+ {
+ GList *delete_me = tmp;
+
+ /* Split this edge into some new ones */
+ *strut_edges = split_edge (*strut_edges, cur, &overlap);
+
+ /* Delete the old one */
+ tmp = tmp->next;
+ g_free (cur);
+ *strut_edges = g_list_delete_link (*strut_edges, delete_me);
+ }
+ else
+ tmp = tmp->next;
+ }
+ }
+}
+
+/* This function removes intersections of edges with the rectangles from the
+ * list of edges.
+ */
+GList*
+meta_rectangle_remove_intersections_with_boxes_from_edges (
+ GList *edges,
+ const GSList *rectangles)
+{
+ const GSList *rect_iter;
+ const int opposing = 1;
+
+ /* Now remove all intersections of rectangles with the edge list */
+ rect_iter = rectangles;
+ while (rect_iter)
+ {
+ MetaRectangle *rect = rect_iter->data;
+ GList *edge_iter = edges;
+ while (edge_iter)
+ {
+ MetaEdge *edge = edge_iter->data;
+ MetaEdge overlap;
+ int handle;
+ gboolean edge_iter_advanced = FALSE;
+
+ /* If this edge overlaps with this rect... */
+ if (rectangle_and_edge_intersection (rect, edge, &overlap, &handle))
+ {
+
+ /* "Intersections" where the edges touch but are opposite
+ * sides (e.g. a left edge against the right edge) should not
+ * be split. Note that the comments in
+ * rectangle_and_edge_intersection() say that opposing edges
+ * occur when handle is -1, BUT you need to remember that we
+ * treat the left side of a window as a right edge because
+ * it's what the right side of the window being moved should
+ * be-resisted-by/snap-to. So opposing is really 1. Anyway,
+ * we just keep track of it in the opposing constant set up
+ * above and if handle isn't equal to that, then we know the
+ * edge should be split.
+ */
+ if (handle != opposing)
+ {
+ /* Keep track of this edge so we can delete it below */
+ GList *delete_me = edge_iter;
+ edge_iter = edge_iter->next;
+ edge_iter_advanced = TRUE;
+
+ /* Split the edge and add the result to beginning of edges */
+ edges = split_edge (edges, edge, &overlap);
+
+ /* Now free the edge... */
+ g_free (edge);
+ edges = g_list_delete_link (edges, delete_me);
+ }
+ }
+
+ if (!edge_iter_advanced)
+ edge_iter = edge_iter->next;
+ }
+
+ rect_iter = rect_iter->next;
+ }
+
+ return edges;
+}
+
+/* This function is trying to find all the edges of an onscreen region. */
+GList*
+meta_rectangle_find_onscreen_edges (const MetaRectangle *basic_rect,
+ const GSList *all_struts)
+{
+ GList *ret;
+ GList *fixed_strut_rects;
+ GList *edge_iter;
+ const GList *strut_rect_iter;
+
+ /* The algorithm is basically as follows:
+ * Make sure the struts are disjoint
+ * Initialize the edge_set to the edges of basic_rect
+ * Foreach strut:
+ * Put together a preliminary new edge from the edges of the strut
+ * Foreach edge in edge_set:
+ * - Split the edge if it is partially contained inside the strut
+ * - If the edge matches an edge of the strut (i.e. a strut just
+ * against the edge of the screen or a not-next-to-edge-of-screen
+ * strut adjacent to another), then both the edge from the
+ * edge_set and the preliminary edge for the strut will need to
+ * be split
+ * Add any remaining "preliminary" strut edges to the edge_set
+ */
+
+ /* Make sure the struts are disjoint */
+ fixed_strut_rects =
+ get_disjoint_strut_rect_list_in_region (all_struts, basic_rect);
+
+ /* Start off the list with the edges of basic_rect */
+ ret = add_edges (NULL, basic_rect, TRUE);
+
+ strut_rect_iter = fixed_strut_rects;
+ while (strut_rect_iter)
+ {
+ MetaRectangle *strut_rect = (MetaRectangle*) strut_rect_iter->data;
+
+ /* Get the new possible edges we may need to add from the strut */
+ GList *new_strut_edges = add_edges (NULL, strut_rect, FALSE);
+
+ edge_iter = ret;
+ while (edge_iter)
+ {
+ MetaEdge *cur_edge = edge_iter->data;
+ GList *splits_of_cur_edge = NULL;
+ gboolean edge_needs_removal = FALSE;
+
+ fix_up_edges (strut_rect, cur_edge,
+ &new_strut_edges, &splits_of_cur_edge,
+ &edge_needs_removal);
+
+ if (edge_needs_removal)
+ {
+ /* Delete the old edge */
+ GList *delete_me = edge_iter;
+ edge_iter = edge_iter->next;
+ g_free (cur_edge);
+ ret = g_list_delete_link (ret, delete_me);
+
+ /* Add the new split parts of the edge */
+ ret = g_list_concat (splits_of_cur_edge, ret);
+ }
+ else
+ {
+ edge_iter = edge_iter->next;
+ }
+
+ /* edge_iter was already advanced above */
+ }
+
+ ret = g_list_concat (new_strut_edges, ret);
+ strut_rect_iter = strut_rect_iter->next;
+ }
+
+ /* Sort the list */
+ ret = g_list_sort (ret, meta_rectangle_edge_cmp);
+
+ /* Free the fixed struts list */
+ meta_rectangle_free_list_and_elements (fixed_strut_rects);
+
+ return ret;
+}
+
+GList*
+meta_rectangle_find_nonintersected_xinerama_edges (
+ const GList *xinerama_rects,
+ const GSList *all_struts)
+{
+ /* This function cannot easily be merged with
+ * meta_rectangle_find_onscreen_edges() because real screen edges
+ * and strut edges both are of the type "there ain't anything
+ * immediately on the other side"; xinerama edges are different.
+ */
+ GList *ret;
+ const GList *cur;
+ GSList *temp_rects;
+
+ /* Initialize the return list to be empty */
+ ret = NULL;
+
+ /* start of ret with all the edges of xineramas that are adjacent to
+ * another xinerama.
+ */
+ cur = xinerama_rects;
+ while (cur)
+ {
+ MetaRectangle *cur_rect = cur->data;
+ const GList *compare = xinerama_rects;
+ while (compare)
+ {
+ MetaRectangle *compare_rect = compare->data;
+
+ /* Check if cur might be horizontally adjacent to compare */
+ if (meta_rectangle_vert_overlap(cur_rect, compare_rect))
+ {
+ MetaSide side_type;
+ int y = MAX (cur_rect->y, compare_rect->y);
+ int height = MIN (BOX_BOTTOM (*cur_rect) - y,
+ BOX_BOTTOM (*compare_rect) - y);
+ int width = 0;
+ int x;
+
+ if (BOX_LEFT (*cur_rect) == BOX_RIGHT (*compare_rect))
+ {
+ /* compare_rect is to the left of cur_rect */
+ x = BOX_LEFT (*cur_rect);
+ side_type = META_SIDE_LEFT;
+ }
+ else if (BOX_RIGHT (*cur_rect) == BOX_LEFT (*compare_rect))
+ {
+ /* compare_rect is to the right of cur_rect */
+ x = BOX_RIGHT (*cur_rect);
+ side_type = META_SIDE_RIGHT;
+ }
+ else
+ /* These rectangles aren't adjacent after all */
+ x = INT_MIN;
+
+ /* If the rectangles really are adjacent */
+ if (x != INT_MIN)
+ {
+ /* We need a left edge for the xinerama on the right, and
+ * a right edge for the xinerama on the left. Just fill
+ * up the edges and stick 'em on the list.
+ */
+ MetaEdge *new_edge = g_new (MetaEdge, 1);
+
+ new_edge->rect = meta_rect (x, y, width, height);
+ new_edge->side_type = side_type;
+ new_edge->edge_type = META_EDGE_XINERAMA;
+
+ ret = g_list_prepend (ret, new_edge);
+ }
+ }
+
+ /* Check if cur might be vertically adjacent to compare */
+ if (meta_rectangle_horiz_overlap(cur_rect, compare_rect))
+ {
+ MetaSide side_type;
+ int x = MAX (cur_rect->x, compare_rect->x);
+ int width = MIN (BOX_RIGHT (*cur_rect) - x,
+ BOX_RIGHT (*compare_rect) - x);
+ int height = 0;
+ int y;
+
+ if (BOX_TOP (*cur_rect) == BOX_BOTTOM (*compare_rect))
+ {
+ /* compare_rect is to the top of cur_rect */
+ y = BOX_TOP (*cur_rect);
+ side_type = META_SIDE_TOP;
+ }
+ else if (BOX_BOTTOM (*cur_rect) == BOX_TOP (*compare_rect))
+ {
+ /* compare_rect is to the bottom of cur_rect */
+ y = BOX_BOTTOM (*cur_rect);
+ side_type = META_SIDE_BOTTOM;
+ }
+ else
+ /* These rectangles aren't adjacent after all */
+ y = INT_MIN;
+
+ /* If the rectangles really are adjacent */
+ if (y != INT_MIN)
+ {
+ /* We need a top edge for the xinerama on the bottom, and
+ * a bottom edge for the xinerama on the top. Just fill
+ * up the edges and stick 'em on the list.
+ */
+ MetaEdge *new_edge = g_new (MetaEdge, 1);
+
+ new_edge->rect = meta_rect (x, y, width, height);
+ new_edge->side_type = side_type;
+ new_edge->edge_type = META_EDGE_XINERAMA;
+
+ ret = g_list_prepend (ret, new_edge);
+ }
+ }
+
+ compare = compare->next;
+ }
+ cur = cur->next;
+ }
+
+ temp_rects = NULL;
+ for (; all_struts; all_struts = all_struts->next)
+ temp_rects = g_slist_prepend (temp_rects,
+ &((MetaStrut*)all_struts->data)->rect);
+ ret = meta_rectangle_remove_intersections_with_boxes_from_edges (ret,
+ temp_rects);
+ g_slist_free (temp_rects);
+
+ /* Sort the list */
+ ret = g_list_sort (ret, meta_rectangle_edge_cmp);
+
+ return ret;
+}
diff --git a/src/core/constraints.c b/src/core/constraints.c
new file mode 100644
index 00000000..6abc7d5c
--- /dev/null
+++ b/src/core/constraints.c
@@ -0,0 +1,1382 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco size/position constraints */
+
+/*
+ * Copyright (C) 2002, 2003 Red Hat, Inc.
+ * Copyright (C) 2003, 2004 Rob Adams
+ * Copyright (C) 2005, 2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "constraints.h"
+#include "workspace.h"
+#include "place.h"
+#include "prefs.h"
+
+#include <stdlib.h>
+#include <math.h>
+
+#if 0
+ // This is the short and sweet version of how to hack on this file; see
+ // doc/how-constraints-works.txt for the gory details. The basics of
+ // understanding this file can be shown by the steps needed to add a new
+ // constraint, which are:
+ // 1) Add a new entry in the ConstraintPriority enum; higher values
+ // have higher priority
+ // 2) Write a new function following the format of the example below,
+ // "constrain_whatever".
+ // 3) Add your function to the all_constraints and all_constraint_names
+ // arrays (the latter of which is for debugging purposes)
+ //
+ // An example constraint function, constrain_whatever:
+ //
+ // /* constrain_whatever does the following:
+ // * Quits (returning true) if priority is higher than PRIORITY_WHATEVER
+ // * If check_only is TRUE
+ // * Returns whether the constraint is satisfied or not
+ // * otherwise
+ // * Enforces the constraint
+ // * Note that the value of PRIORITY_WHATEVER is centralized with the
+ // * priorities of other constraints in the definition of ConstrainPriority
+ // * for easier maintenance and shuffling of priorities.
+ // */
+ // static gboolean
+ // constrain_whatever (MetaWindow *window,
+ // ConstraintInfo *info,
+ // ConstraintPriority priority,
+ // gboolean check_only)
+ // {
+ // if (priority > PRIORITY_WHATEVER)
+ // return TRUE;
+ //
+ // /* Determine whether constraint applies; note that if the constraint
+ // * cannot possibly be satisfied, constraint_applies should be set to
+ // * false. If we don't do this, all constraints with a lesser priority
+ // * will be dropped along with this one, and we'd rather apply as many as
+ // * possible.
+ // */
+ // if (!constraint_applies)
+ // return TRUE;
+ //
+ // /* Determine whether constraint is already satisfied; if we're only
+ // * checking the status of whether the constraint is satisfied, we end
+ // * here.
+ // */
+ // if (check_only || constraint_already_satisfied)
+ // return constraint_already_satisfied;
+ //
+ // /* Enforce constraints */
+ // return TRUE; /* Note that we exited early if check_only is FALSE; also,
+ // * we know we can return TRUE here because we exited early
+ // * if the constraint could not be satisfied; not that the
+ // * return value is heeded in this case...
+ // */
+ // }
+#endif
+
+typedef enum
+{
+ PRIORITY_MINIMUM = 0, /* Dummy value used for loop start = min(all priorities) */
+ PRIORITY_ASPECT_RATIO = 0,
+ PRIORITY_ENTIRELY_VISIBLE_ON_SINGLE_XINERAMA = 0,
+ PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA = 1,
+ PRIORITY_SIZE_HINTS_INCREMENTS = 1,
+ PRIORITY_MAXIMIZATION = 2,
+ PRIORITY_FULLSCREEN = 2,
+ PRIORITY_SIZE_HINTS_LIMITS = 3,
+ PRIORITY_TITLEBAR_VISIBLE = 4,
+ PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA = 4,
+ PRIORITY_MAXIMUM = 4 /* Dummy value used for loop end = max(all priorities) */
+} ConstraintPriority;
+
+typedef enum
+{
+ ACTION_MOVE,
+ ACTION_RESIZE,
+ ACTION_MOVE_AND_RESIZE
+} ActionType;
+
+typedef struct
+{
+ MetaRectangle orig;
+ MetaRectangle current;
+ MetaFrameGeometry *fgeom;
+ ActionType action_type;
+ gboolean is_user_action;
+
+ /* I know that these two things probably look similar at first, but they
+ * have much different uses. See doc/how-constraints-works.txt for for
+ * explanation of the differences and similarity between resize_gravity
+ * and fixed_directions
+ */
+ int resize_gravity;
+ FixedDirections fixed_directions;
+
+ /* work_area_xinerama - current xinerama region minus struts
+ * entire_xinerama - current xienrama, including strut regions
+ */
+ MetaRectangle work_area_xinerama;
+ MetaRectangle entire_xinerama;
+
+ /* Spanning rectangles for the non-covered (by struts) region of the
+ * screen and also for just the current xinerama
+ */
+ GList *usable_screen_region;
+ GList *usable_xinerama_region;
+} ConstraintInfo;
+
+static gboolean constrain_maximization (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only);
+static gboolean constrain_fullscreen (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only);
+static gboolean constrain_size_increments (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only);
+static gboolean constrain_size_limits (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only);
+static gboolean constrain_aspect_ratio (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only);
+static gboolean constrain_to_single_xinerama (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only);
+static gboolean constrain_fully_onscreen (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only);
+static gboolean constrain_titlebar_visible (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only);
+static gboolean constrain_partially_onscreen (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only);
+
+static void setup_constraint_info (ConstraintInfo *info,
+ MetaWindow *window,
+ MetaFrameGeometry *orig_fgeom,
+ MetaMoveResizeFlags flags,
+ int resize_gravity,
+ const MetaRectangle *orig,
+ MetaRectangle *new);
+static void place_window_if_needed (MetaWindow *window,
+ ConstraintInfo *info);
+static void update_onscreen_requirements (MetaWindow *window,
+ ConstraintInfo *info);
+static void extend_by_frame (MetaRectangle *rect,
+ const MetaFrameGeometry *fgeom);
+static void unextend_by_frame (MetaRectangle *rect,
+ const MetaFrameGeometry *fgeom);
+static inline void get_size_limits (const MetaWindow *window,
+ const MetaFrameGeometry *fgeom,
+ gboolean include_frame,
+ MetaRectangle *min_size,
+ MetaRectangle *max_size);
+
+typedef gboolean (* ConstraintFunc) (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only);
+
+typedef struct {
+ ConstraintFunc func;
+ const char* name;
+} Constraint;
+
+static const Constraint all_constraints[] = {
+ {constrain_maximization, "constrain_maximization"},
+ {constrain_fullscreen, "constrain_fullscreen"},
+ {constrain_size_increments, "constrain_size_increments"},
+ {constrain_size_limits, "constrain_size_limits"},
+ {constrain_aspect_ratio, "constrain_aspect_ratio"},
+ {constrain_to_single_xinerama, "constrain_to_single_xinerama"},
+ {constrain_fully_onscreen, "constrain_fully_onscreen"},
+ {constrain_titlebar_visible, "constrain_titlebar_visible"},
+ {constrain_partially_onscreen, "constrain_partially_onscreen"},
+ {NULL, NULL}
+};
+
+static gboolean
+do_all_constraints (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only)
+{
+ const Constraint *constraint;
+ gboolean satisfied;
+
+ constraint = &all_constraints[0];
+ satisfied = TRUE;
+ while (constraint->func != NULL)
+ {
+ satisfied = satisfied &&
+ (*constraint->func) (window, info, priority, check_only);
+
+ if (!check_only)
+ {
+ /* Log how the constraint modified the position */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "info->current is %d,%d +%d,%d after %s\n",
+ info->current.x, info->current.y,
+ info->current.width, info->current.height,
+ constraint->name);
+ }
+ else if (!satisfied)
+ {
+ /* Log which constraint was not satisfied */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "constraint %s not satisfied.\n",
+ constraint->name);
+ return FALSE;
+ }
+ ++constraint;
+ }
+
+ return TRUE;
+}
+
+void
+meta_window_constrain (MetaWindow *window,
+ MetaFrameGeometry *orig_fgeom,
+ MetaMoveResizeFlags flags,
+ int resize_gravity,
+ const MetaRectangle *orig,
+ MetaRectangle *new)
+{
+ ConstraintInfo info;
+ ConstraintPriority priority = PRIORITY_MINIMUM;
+ gboolean satisfied = FALSE;
+
+ /* WARNING: orig and new specify positions and sizes of the inner window,
+ * not the outer. This is a common gotcha since half the constraints
+ * deal with inner window position/size and half deal with outer. See
+ * doc/how-constraints-works.txt for more information.
+ */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Constraining %s in move from %d,%d %dx%d to %d,%d %dx%d\n",
+ window->desc,
+ orig->x, orig->y, orig->width, orig->height,
+ new->x, new->y, new->width, new->height);
+
+ setup_constraint_info (&info,
+ window,
+ orig_fgeom,
+ flags,
+ resize_gravity,
+ orig,
+ new);
+ place_window_if_needed (window, &info);
+
+ while (!satisfied && priority <= PRIORITY_MAXIMUM) {
+ gboolean check_only = TRUE;
+
+ /* Individually enforce all the high-enough priority constraints */
+ do_all_constraints (window, &info, priority, !check_only);
+
+ /* Check if all high-enough priority constraints are simultaneously
+ * satisfied
+ */
+ satisfied = do_all_constraints (window, &info, priority, check_only);
+
+ /* Drop the least important constraints if we can't satisfy them all */
+ priority++;
+ }
+
+ /* Make sure we use the constrained position */
+ *new = info.current;
+
+ /* We may need to update window->require_fully_onscreen,
+ * window->require_on_single_xinerama, and perhaps other quantities
+ * if this was a user move or user move-and-resize operation.
+ */
+ update_onscreen_requirements (window, &info);
+
+ /* Ew, what an ugly way to do things. Destructors (in a real OOP language,
+ * not gobject-style--gobject would be more pain than it's worth) or
+ * smart pointers would be so much nicer here. *shrug*
+ */
+ if (!orig_fgeom)
+ g_free (info.fgeom);
+}
+
+static void
+setup_constraint_info (ConstraintInfo *info,
+ MetaWindow *window,
+ MetaFrameGeometry *orig_fgeom,
+ MetaMoveResizeFlags flags,
+ int resize_gravity,
+ const MetaRectangle *orig,
+ MetaRectangle *new)
+{
+ const MetaXineramaScreenInfo *xinerama_info;
+ MetaWorkspace *cur_workspace;
+
+ info->orig = *orig;
+ info->current = *new;
+
+ /* Create a fake frame geometry if none really exists */
+ if (orig_fgeom && !window->fullscreen)
+ info->fgeom = orig_fgeom;
+ else
+ info->fgeom = g_new0 (MetaFrameGeometry, 1);
+
+ if (flags & META_IS_MOVE_ACTION && flags & META_IS_RESIZE_ACTION)
+ info->action_type = ACTION_MOVE_AND_RESIZE;
+ else if (flags & META_IS_RESIZE_ACTION)
+ info->action_type = ACTION_RESIZE;
+ else if (flags & META_IS_MOVE_ACTION)
+ info->action_type = ACTION_MOVE;
+ else
+ g_error ("BAD, BAD developer! No treat for you! (Fix your calls to "
+ "meta_window_move_resize_internal()).\n");
+
+ info->is_user_action = (flags & META_IS_USER_ACTION);
+
+ info->resize_gravity = resize_gravity;
+
+ /* FIXME: fixed_directions might be more sane if we (a) made it
+ * depend on the grab_op type instead of current amount of movement
+ * (thus implying that it only has effect when user_action is true,
+ * and (b) ignored it for aspect ratio windows -- at least in those
+ * cases where both directions do actually change size.
+ */
+ info->fixed_directions = FIXED_DIRECTION_NONE;
+ /* If x directions don't change but either y direction does */
+ if ( orig->x == new->x && orig->x + orig->width == new->x + new->width &&
+ (orig->y != new->y || orig->y + orig->height != new->y + new->height))
+ {
+ info->fixed_directions = FIXED_DIRECTION_X;
+ }
+ /* If y directions don't change but either x direction does */
+ if ( orig->y == new->y && orig->y + orig->height == new->y + new->height &&
+ (orig->x != new->x || orig->x + orig->width != new->x + new->width ))
+ {
+ info->fixed_directions = FIXED_DIRECTION_Y;
+ }
+ /* The point of fixed directions is just that "move to nearest valid
+ * position" is sometimes a poorer choice than "move to nearest
+ * valid position but only change this coordinate" for windows the
+ * user is explicitly moving. This isn't ever true for things that
+ * aren't explicit user interaction, though, so just clear it out.
+ */
+ if (!info->is_user_action)
+ info->fixed_directions = FIXED_DIRECTION_NONE;
+
+ xinerama_info =
+ meta_screen_get_xinerama_for_rect (window->screen, &info->current);
+ meta_window_get_work_area_for_xinerama (window,
+ xinerama_info->number,
+ &info->work_area_xinerama);
+
+ if (!window->fullscreen || window->fullscreen_monitors[0] == -1)
+ {
+ info->entire_xinerama = xinerama_info->rect;
+ }
+ else
+ {
+ int i = 0;
+ long monitor;
+
+ monitor = window->fullscreen_monitors[i];
+ info->entire_xinerama =
+ window->screen->xinerama_infos[monitor].rect;
+ for (i = 1; i <= 3; i++)
+ {
+ monitor = window->fullscreen_monitors[i];
+ meta_rectangle_union (&info->entire_xinerama,
+ &window->screen->xinerama_infos[monitor].rect,
+ &info->entire_xinerama);
+ }
+ }
+
+ cur_workspace = window->screen->active_workspace;
+ info->usable_screen_region =
+ meta_workspace_get_onscreen_region (cur_workspace);
+ info->usable_xinerama_region =
+ meta_workspace_get_onxinerama_region (cur_workspace,
+ xinerama_info->number);
+
+ /* Workaround braindead legacy apps that don't know how to
+ * fullscreen themselves properly.
+ */
+ if (meta_prefs_get_force_fullscreen() &&
+ meta_rectangle_equal (new, &xinerama_info->rect) &&
+ window->has_fullscreen_func &&
+ !window->fullscreen)
+ {
+ /*
+ meta_topic (META_DEBUG_GEOMETRY,
+ */
+ meta_warning (
+ "Treating resize request of legacy application %s as a "
+ "fullscreen request\n",
+ window->desc);
+ meta_window_make_fullscreen_internal (window);
+ }
+
+ /* Log all this information for debugging */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Setting up constraint info:\n"
+ " orig: %d,%d +%d,%d\n"
+ " new : %d,%d +%d,%d\n"
+ " fgeom: %d,%d,%d,%d\n"
+ " action_type : %s\n"
+ " is_user_action : %s\n"
+ " resize_gravity : %s\n"
+ " fixed_directions: %s\n"
+ " work_area_xinerama: %d,%d +%d,%d\n"
+ " entire_xinerama : %d,%d +%d,%d\n",
+ info->orig.x, info->orig.y, info->orig.width, info->orig.height,
+ info->current.x, info->current.y,
+ info->current.width, info->current.height,
+ info->fgeom->left_width, info->fgeom->right_width,
+ info->fgeom->top_height, info->fgeom->bottom_height,
+ (info->action_type == ACTION_MOVE) ? "Move" :
+ (info->action_type == ACTION_RESIZE) ? "Resize" :
+ (info->action_type == ACTION_MOVE_AND_RESIZE) ? "Move&Resize" :
+ "Freakin' Invalid Stupid",
+ (info->is_user_action) ? "true" : "false",
+ meta_gravity_to_string (info->resize_gravity),
+ (info->fixed_directions == FIXED_DIRECTION_NONE) ? "None" :
+ (info->fixed_directions == FIXED_DIRECTION_X) ? "X fixed" :
+ (info->fixed_directions == FIXED_DIRECTION_Y) ? "Y fixed" :
+ "Freakin' Invalid Stupid",
+ info->work_area_xinerama.x, info->work_area_xinerama.y,
+ info->work_area_xinerama.width,
+ info->work_area_xinerama.height,
+ info->entire_xinerama.x, info->entire_xinerama.y,
+ info->entire_xinerama.width, info->entire_xinerama.height);
+}
+
+static void
+place_window_if_needed(MetaWindow *window,
+ ConstraintInfo *info)
+{
+ gboolean did_placement;
+
+ /* Do placement if any, so we go ahead and apply position
+ * constraints in a move-only context. Don't place
+ * maximized/minimized/fullscreen windows until they are
+ * unmaximized, unminimized and unfullscreened.
+ */
+ did_placement = FALSE;
+ if (!window->placed &&
+ window->calc_placement &&
+ !(window->maximized_horizontally ||
+ window->maximized_vertically) &&
+ !window->minimized &&
+ !window->fullscreen)
+ {
+ MetaRectangle placed_rect = info->orig;
+ MetaWorkspace *cur_workspace;
+ const MetaXineramaScreenInfo *xinerama_info;
+
+ meta_window_place (window, info->fgeom, info->orig.x, info->orig.y,
+ &placed_rect.x, &placed_rect.y);
+ did_placement = TRUE;
+
+ /* placing the window may have changed the xinerama. Find the
+ * new xinerama and update the ConstraintInfo
+ */
+ xinerama_info =
+ meta_screen_get_xinerama_for_rect (window->screen, &placed_rect);
+ info->entire_xinerama = xinerama_info->rect;
+ meta_window_get_work_area_for_xinerama (window,
+ xinerama_info->number,
+ &info->work_area_xinerama);
+ cur_workspace = window->screen->active_workspace;
+ info->usable_xinerama_region =
+ meta_workspace_get_onxinerama_region (cur_workspace,
+ xinerama_info->number);
+
+
+ info->current.x = placed_rect.x;
+ info->current.y = placed_rect.y;
+
+ /* Since we just barely placed the window, there's no reason to
+ * consider any of the directions fixed.
+ */
+ info->fixed_directions = FIXED_DIRECTION_NONE;
+ }
+
+ if (window->placed || did_placement)
+ {
+ if (window->maximize_horizontally_after_placement ||
+ window->maximize_vertically_after_placement ||
+ window->fullscreen_after_placement)
+ {
+ /* define a sane saved_rect so that the user can unmaximize or
+ * make unfullscreen to something reasonable.
+ */
+ if (info->current.width >= info->work_area_xinerama.width)
+ {
+ info->current.width = .75 * info->work_area_xinerama.width;
+ info->current.x = info->work_area_xinerama.x +
+ .125 * info->work_area_xinerama.width;
+ }
+ if (info->current.height >= info->work_area_xinerama.height)
+ {
+ info->current.height = .75 * info->work_area_xinerama.height;
+ info->current.y = info->work_area_xinerama.y +
+ .083 * info->work_area_xinerama.height;
+ }
+
+ if (window->maximize_horizontally_after_placement ||
+ window->maximize_vertically_after_placement)
+ meta_window_maximize_internal (window,
+ (window->maximize_horizontally_after_placement ?
+ META_MAXIMIZE_HORIZONTAL : 0 ) |
+ (window->maximize_vertically_after_placement ?
+ META_MAXIMIZE_VERTICAL : 0), &info->current);
+
+ /* maximization may have changed frame geometry */
+ if (window->frame && !window->fullscreen)
+ meta_frame_calc_geometry (window->frame, info->fgeom);
+
+ if (window->fullscreen_after_placement)
+ {
+ window->saved_rect = info->current;
+ window->fullscreen = TRUE;
+ window->fullscreen_after_placement = FALSE;
+ }
+
+ window->maximize_horizontally_after_placement = FALSE;
+ window->maximize_vertically_after_placement = FALSE;
+ }
+ if (window->minimize_after_placement)
+ {
+ meta_window_minimize (window);
+ window->minimize_after_placement = FALSE;
+ }
+ }
+}
+
+static void
+update_onscreen_requirements (MetaWindow *window,
+ ConstraintInfo *info)
+{
+ gboolean old;
+
+ /* We only apply the various onscreen requirements to normal windows */
+ if (window->type == META_WINDOW_DESKTOP ||
+ window->type == META_WINDOW_DOCK)
+ return;
+
+ /* We don't want to update the requirements for fullscreen windows;
+ * fullscreen windows are specially handled anyway, and it updating
+ * the requirements when windows enter fullscreen mode mess up the
+ * handling of the window when it leaves that mode (especially when
+ * the application sends a bunch of configurerequest events). See
+ * #353699.
+ */
+ if (window->fullscreen)
+ return;
+
+ /* USABILITY NOTE: Naturally, I only want the require_fully_onscreen,
+ * require_on_single_xinerama, and require_titlebar_visible flags to
+ * *become false* due to user interactions (which is allowed since
+ * certain constraints are ignored for user interactions regardless of
+ * the setting of these flags). However, whether to make these flags
+ * *become true* due to just an application interaction is a little
+ * trickier. It's possible that users may find not doing that strange
+ * since two application interactions that resize in opposite ways don't
+ * necessarily end up cancelling--but it may also be strange for the user
+ * to have an application resize the window so that it's onscreen, the
+ * user forgets about it, and then later the app is able to resize itself
+ * off the screen. Anyway, for now, I think the latter is the more
+ * problematic case but this may need to be revisited.
+ */
+
+ /* The require onscreen/on-single-xinerama and titlebar_visible
+ * stuff is relative to the outer window, not the inner
+ */
+ extend_by_frame (&info->current, info->fgeom);
+
+ /* Update whether we want future constraint runs to require the
+ * window to be on fully onscreen.
+ */
+ old = window->require_fully_onscreen;
+ window->require_fully_onscreen =
+ meta_rectangle_contained_in_region (info->usable_screen_region,
+ &info->current);
+ if (old ^ window->require_fully_onscreen)
+ meta_topic (META_DEBUG_GEOMETRY,
+ "require_fully_onscreen for %s toggled to %s\n",
+ window->desc,
+ window->require_fully_onscreen ? "TRUE" : "FALSE");
+
+ /* Update whether we want future constraint runs to require the
+ * window to be on a single xinerama.
+ */
+ old = window->require_on_single_xinerama;
+ window->require_on_single_xinerama =
+ meta_rectangle_contained_in_region (info->usable_xinerama_region,
+ &info->current);
+ if (old ^ window->require_on_single_xinerama)
+ meta_topic (META_DEBUG_GEOMETRY,
+ "require_on_single_xinerama for %s toggled to %s\n",
+ window->desc,
+ window->require_on_single_xinerama ? "TRUE" : "FALSE");
+
+ /* Update whether we want future constraint runs to require the
+ * titlebar to be visible.
+ */
+ if (window->frame && window->decorated)
+ {
+ MetaRectangle titlebar_rect;
+
+ titlebar_rect = info->current;
+ titlebar_rect.height = info->fgeom->top_height;
+ old = window->require_titlebar_visible;
+ window->require_titlebar_visible =
+ meta_rectangle_overlaps_with_region (info->usable_screen_region,
+ &titlebar_rect);
+ if (old ^ window->require_titlebar_visible)
+ meta_topic (META_DEBUG_GEOMETRY,
+ "require_titlebar_visible for %s toggled to %s\n",
+ window->desc,
+ window->require_titlebar_visible ? "TRUE" : "FALSE");
+ }
+
+ /* Don't forget to restore the position of the window */
+ unextend_by_frame (&info->current, info->fgeom);
+}
+
+static void
+extend_by_frame (MetaRectangle *rect,
+ const MetaFrameGeometry *fgeom)
+{
+ rect->x -= fgeom->left_width;
+ rect->y -= fgeom->top_height;
+ rect->width += fgeom->left_width + fgeom->right_width;
+ rect->height += fgeom->top_height + fgeom->bottom_height;
+}
+
+static void
+unextend_by_frame (MetaRectangle *rect,
+ const MetaFrameGeometry *fgeom)
+{
+ rect->x += fgeom->left_width;
+ rect->y += fgeom->top_height;
+ rect->width -= fgeom->left_width + fgeom->right_width;
+ rect->height -= fgeom->top_height + fgeom->bottom_height;
+}
+
+static inline void
+get_size_limits (const MetaWindow *window,
+ const MetaFrameGeometry *fgeom,
+ gboolean include_frame,
+ MetaRectangle *min_size,
+ MetaRectangle *max_size)
+{
+ /* We pack the results into MetaRectangle structs just for convienience; we
+ * don't actually use the position of those rects.
+ */
+ min_size->width = window->size_hints.min_width;
+ min_size->height = window->size_hints.min_height;
+ max_size->width = window->size_hints.max_width;
+ max_size->height = window->size_hints.max_height;
+
+ if (include_frame)
+ {
+ int fw = fgeom->left_width + fgeom->right_width;
+ int fh = fgeom->top_height + fgeom->bottom_height;
+
+ min_size->width += fw;
+ min_size->height += fh;
+ max_size->width += fw;
+ max_size->height += fh;
+ }
+}
+
+static gboolean
+constrain_maximization (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only)
+{
+ MetaRectangle target_size;
+ MetaRectangle min_size, max_size;
+ gboolean hminbad, vminbad;
+ gboolean horiz_equal, vert_equal;
+ gboolean constraint_already_satisfied;
+
+ if (priority > PRIORITY_MAXIMIZATION)
+ return TRUE;
+
+ /* Determine whether constraint applies; exit if it doesn't */
+ if (!window->maximized_horizontally && !window->maximized_vertically)
+ return TRUE;
+
+ /* Calculate target_size = maximized size of (window + frame) */
+ if (window->maximized_horizontally && window->maximized_vertically)
+ target_size = info->work_area_xinerama;
+ else
+ {
+ /* Amount of maximization possible in a single direction depends
+ * on which struts could occlude the window given its current
+ * position. For example, a vertical partial strut on the right
+ * is only relevant for a horizontally maximized window when the
+ * window is at a vertical position where it could be occluded
+ * by that partial strut.
+ */
+ MetaDirection direction;
+ GSList *active_workspace_struts;
+
+ if (window->maximized_horizontally)
+ direction = META_DIRECTION_HORIZONTAL;
+ else
+ direction = META_DIRECTION_VERTICAL;
+ active_workspace_struts = window->screen->active_workspace->all_struts;
+
+ target_size = info->current;
+ extend_by_frame (&target_size, info->fgeom);
+ meta_rectangle_expand_to_avoiding_struts (&target_size,
+ &info->entire_xinerama,
+ direction,
+ active_workspace_struts);
+ }
+ /* Now make target_size = maximized size of client window */
+ unextend_by_frame (&target_size, info->fgeom);
+
+ /* Check min size constraints; max size constraints are ignored for maximized
+ * windows, as per bug 327543.
+ */
+ get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size);
+ hminbad = target_size.width < min_size.width && window->maximized_horizontally;
+ vminbad = target_size.height < min_size.height && window->maximized_vertically;
+ if (hminbad || vminbad)
+ return TRUE;
+
+ /* Determine whether constraint is already satisfied; exit if it is */
+ horiz_equal = target_size.x == info->current.x &&
+ target_size.width == info->current.width;
+ vert_equal = target_size.y == info->current.y &&
+ target_size.height == info->current.height;
+ constraint_already_satisfied =
+ (horiz_equal || !window->maximized_horizontally) &&
+ (vert_equal || !window->maximized_vertically);
+ if (check_only || constraint_already_satisfied)
+ return constraint_already_satisfied;
+
+ /*** Enforce constraint ***/
+ if (window->maximized_horizontally)
+ {
+ info->current.x = target_size.x;
+ info->current.width = target_size.width;
+ }
+ if (window->maximized_vertically)
+ {
+ info->current.y = target_size.y;
+ info->current.height = target_size.height;
+ }
+ return TRUE;
+}
+
+static gboolean
+constrain_fullscreen (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only)
+{
+ MetaRectangle min_size, max_size, xinerama;
+ gboolean too_big, too_small, constraint_already_satisfied;
+
+ if (priority > PRIORITY_FULLSCREEN)
+ return TRUE;
+
+ /* Determine whether constraint applies; exit if it doesn't */
+ if (!window->fullscreen)
+ return TRUE;
+
+ xinerama = info->entire_xinerama;
+
+ get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size);
+ too_big = !meta_rectangle_could_fit_rect (&xinerama, &min_size);
+ too_small = !meta_rectangle_could_fit_rect (&max_size, &xinerama);
+ if (too_big || too_small)
+ return TRUE;
+
+ /* Determine whether constraint is already satisfied; exit if it is */
+ constraint_already_satisfied =
+ meta_rectangle_equal (&info->current, &xinerama);
+ if (check_only || constraint_already_satisfied)
+ return constraint_already_satisfied;
+
+ /*** Enforce constraint ***/
+ info->current = xinerama;
+ return TRUE;
+}
+
+static gboolean
+constrain_size_increments (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only)
+{
+ int bh, hi, bw, wi, extra_height, extra_width;
+ int new_width, new_height;
+ gboolean constraint_already_satisfied;
+ MetaRectangle *start_rect;
+
+ if (priority > PRIORITY_SIZE_HINTS_INCREMENTS)
+ return TRUE;
+
+ /* Determine whether constraint applies; exit if it doesn't */
+ if (META_WINDOW_MAXIMIZED (window) || window->fullscreen ||
+ info->action_type == ACTION_MOVE)
+ return TRUE;
+
+ /* Determine whether constraint is already satisfied; exit if it is */
+ bh = window->size_hints.base_height;
+ hi = window->size_hints.height_inc;
+ bw = window->size_hints.base_width;
+ wi = window->size_hints.width_inc;
+ extra_height = (info->current.height - bh) % hi;
+ extra_width = (info->current.width - bw) % wi;
+ /* ignore size increments for maximized windows */
+ if (window->maximized_horizontally)
+ extra_width *= 0;
+ if (window->maximized_vertically)
+ extra_height *= 0;
+ /* constraint is satisfied iff there is no extra height or width */
+ constraint_already_satisfied =
+ (extra_height == 0 && extra_width == 0);
+
+ if (check_only || constraint_already_satisfied)
+ return constraint_already_satisfied;
+
+ /*** Enforce constraint ***/
+ new_width = info->current.width - extra_width;
+ new_height = info->current.height - extra_height;
+
+ /* Adjusting down instead of up (as done in the above two lines) may
+ * violate minimum size constraints; fix the adjustment if this
+ * happens.
+ */
+ if (new_width < window->size_hints.min_width)
+ new_width += ((window->size_hints.min_width - new_width)/wi + 1)*wi;
+ if (new_height < window->size_hints.min_height)
+ new_height += ((window->size_hints.min_height - new_height)/hi + 1)*hi;
+
+ /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity
+ * See bug 448183
+ */
+ if (info->action_type == ACTION_MOVE_AND_RESIZE)
+ start_rect = &info->current;
+ else
+ start_rect = &info->orig;
+
+ /* Resize to the new size */
+ meta_rectangle_resize_with_gravity (start_rect,
+ &info->current,
+ info->resize_gravity,
+ new_width,
+ new_height);
+ return TRUE;
+}
+
+static gboolean
+constrain_size_limits (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only)
+{
+ MetaRectangle min_size, max_size;
+ gboolean too_big, too_small, constraint_already_satisfied;
+ int new_width, new_height;
+ MetaRectangle *start_rect;
+
+ if (priority > PRIORITY_SIZE_HINTS_LIMITS)
+ return TRUE;
+
+ /* Determine whether constraint applies; exit if it doesn't.
+ *
+ * Note: The old code didn't apply this constraint for fullscreen or
+ * maximized windows--but that seems odd to me. *shrug*
+ */
+ if (info->action_type == ACTION_MOVE)
+ return TRUE;
+
+ /* Determine whether constraint is already satisfied; exit if it is */
+ get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size);
+ /* We ignore max-size limits for maximized windows; see #327543 */
+ if (window->maximized_horizontally)
+ max_size.width = MAX (max_size.width, info->current.width);
+ if (window->maximized_vertically)
+ max_size.height = MAX (max_size.height, info->current.height);
+ too_small = !meta_rectangle_could_fit_rect (&info->current, &min_size);
+ too_big = !meta_rectangle_could_fit_rect (&max_size, &info->current);
+ constraint_already_satisfied = !too_big && !too_small;
+ if (check_only || constraint_already_satisfied)
+ return constraint_already_satisfied;
+
+ /*** Enforce constraint ***/
+ new_width = CLAMP (info->current.width, min_size.width, max_size.width);
+ new_height = CLAMP (info->current.height, min_size.height, max_size.height);
+
+ /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity
+ * See bug 448183
+ */
+ if (info->action_type == ACTION_MOVE_AND_RESIZE)
+ start_rect = &info->current;
+ else
+ start_rect = &info->orig;
+
+ meta_rectangle_resize_with_gravity (start_rect,
+ &info->current,
+ info->resize_gravity,
+ new_width,
+ new_height);
+ return TRUE;
+}
+
+static gboolean
+constrain_aspect_ratio (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only)
+{
+ double minr, maxr;
+ gboolean constraints_are_inconsistent, constraint_already_satisfied;
+ int fudge, new_width, new_height;
+ double best_width, best_height;
+ double alt_width, alt_height;
+ MetaRectangle *start_rect;
+
+ if (priority > PRIORITY_ASPECT_RATIO)
+ return TRUE;
+
+ /* Determine whether constraint applies; exit if it doesn't. */
+ minr = window->size_hints.min_aspect.x /
+ (double)window->size_hints.min_aspect.y;
+ maxr = window->size_hints.max_aspect.x /
+ (double)window->size_hints.max_aspect.y;
+ constraints_are_inconsistent = minr > maxr;
+ if (constraints_are_inconsistent ||
+ META_WINDOW_MAXIMIZED (window) || window->fullscreen ||
+ info->action_type == ACTION_MOVE)
+ return TRUE;
+
+ /* Determine whether constraint is already satisfied; exit if it is. We
+ * need the following to hold:
+ *
+ * width
+ * minr <= ------ <= maxr
+ * height
+ *
+ * But we need to allow for some slight fudging since width and height
+ * are integers instead of floating point numbers (this is particularly
+ * important when minr == maxr), so we allow width and height to be off
+ * a little bit from strictly satisfying these equations. For just one
+ * sided resizing, we have to make the fudge factor a little bigger
+ * because of how meta_rectangle_resize_with_gravity treats those as
+ * being a resize increment (FIXME: I should handle real resize
+ * increments better here...)
+ */
+ switch (info->resize_gravity)
+ {
+ case WestGravity:
+ case NorthGravity:
+ case SouthGravity:
+ case EastGravity:
+ fudge = 2;
+ break;
+
+ case NorthWestGravity:
+ case SouthWestGravity:
+ case CenterGravity:
+ case NorthEastGravity:
+ case SouthEastGravity:
+ case StaticGravity:
+ default:
+ fudge = 1;
+ break;
+ }
+ constraint_already_satisfied =
+ info->current.width - (info->current.height * minr ) > -minr*fudge &&
+ info->current.width - (info->current.height * maxr ) < maxr*fudge;
+ if (check_only || constraint_already_satisfied)
+ return constraint_already_satisfied;
+
+ /*** Enforce constraint ***/
+ new_width = info->current.width;
+ new_height = info->current.height;
+
+ switch (info->resize_gravity)
+ {
+ case WestGravity:
+ case EastGravity:
+ /* Yeah, I suck for doing implicit rounding -- sue me */
+ new_height = CLAMP (new_height, new_width / maxr, new_width / minr);
+ break;
+
+ case NorthGravity:
+ case SouthGravity:
+ /* Yeah, I suck for doing implicit rounding -- sue me */
+ new_width = CLAMP (new_width, new_height * minr, new_height * maxr);
+ break;
+
+ case NorthWestGravity:
+ case SouthWestGravity:
+ case CenterGravity:
+ case NorthEastGravity:
+ case SouthEastGravity:
+ case StaticGravity:
+ default:
+ /* Find what width would correspond to new_height, and what height would
+ * correspond to new_width */
+ alt_width = CLAMP (new_width, new_height * minr, new_height * maxr);
+ alt_height = CLAMP (new_height, new_width / maxr, new_width / minr);
+
+ /* The line connecting the points (alt_width, new_height) and
+ * (new_width, alt_height) provide a range of
+ * valid-for-the-aspect-ratio-constraint sizes. We want the
+ * size in that range closest to the value requested, i.e. the
+ * point on the line which is closest to the point (new_width,
+ * new_height)
+ */
+ meta_rectangle_find_linepoint_closest_to_point (alt_width, new_height,
+ new_width, alt_height,
+ new_width, new_height,
+ &best_width, &best_height);
+
+ /* Yeah, I suck for doing implicit rounding -- sue me */
+ new_width = best_width;
+ new_height = best_height;
+
+ break;
+ }
+
+ /* Figure out what original rect to pass to meta_rectangle_resize_with_gravity
+ * See bug 448183
+ */
+ if (info->action_type == ACTION_MOVE_AND_RESIZE)
+ start_rect = &info->current;
+ else
+ start_rect = &info->orig;
+
+ meta_rectangle_resize_with_gravity (start_rect,
+ &info->current,
+ info->resize_gravity,
+ new_width,
+ new_height);
+
+ return TRUE;
+}
+
+static gboolean
+do_screen_and_xinerama_relative_constraints (
+ MetaWindow *window,
+ GList *region_spanning_rectangles,
+ ConstraintInfo *info,
+ gboolean check_only)
+{
+ gboolean exit_early = FALSE, constraint_satisfied;
+ MetaRectangle how_far_it_can_be_smushed, min_size, max_size;
+
+#ifdef WITH_VERBOSE_MODE
+ if (meta_is_verbose ())
+ {
+ /* First, log some debugging information */
+ char spanning_region[1 + 28 * g_list_length (region_spanning_rectangles)];
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "screen/xinerama constraint; region_spanning_rectangles: %s\n",
+ meta_rectangle_region_to_string (region_spanning_rectangles, ", ",
+ spanning_region));
+ }
+#endif
+
+ /* Determine whether constraint applies; exit if it doesn't */
+ how_far_it_can_be_smushed = info->current;
+ get_size_limits (window, info->fgeom, TRUE, &min_size, &max_size);
+ extend_by_frame (&info->current, info->fgeom);
+
+ if (info->action_type != ACTION_MOVE)
+ {
+ if (!(info->fixed_directions & FIXED_DIRECTION_X))
+ how_far_it_can_be_smushed.width = min_size.width;
+
+ if (!(info->fixed_directions & FIXED_DIRECTION_Y))
+ how_far_it_can_be_smushed.height = min_size.height;
+ }
+ if (!meta_rectangle_could_fit_in_region (region_spanning_rectangles,
+ &how_far_it_can_be_smushed))
+ exit_early = TRUE;
+
+ /* Determine whether constraint is already satisfied; exit if it is */
+ constraint_satisfied =
+ meta_rectangle_contained_in_region (region_spanning_rectangles,
+ &info->current);
+ if (exit_early || constraint_satisfied || check_only)
+ {
+ unextend_by_frame (&info->current, info->fgeom);
+ return constraint_satisfied;
+ }
+
+ /* Enforce constraint */
+
+ /* Clamp rectangle size for resize or move+resize actions */
+ if (info->action_type != ACTION_MOVE)
+ meta_rectangle_clamp_to_fit_into_region (region_spanning_rectangles,
+ info->fixed_directions,
+ &info->current,
+ &min_size);
+
+ if (info->is_user_action && info->action_type == ACTION_RESIZE)
+ /* For user resize, clip to the relevant region */
+ meta_rectangle_clip_to_region (region_spanning_rectangles,
+ info->fixed_directions,
+ &info->current);
+ else
+ /* For everything else, shove the rectangle into the relevant region */
+ meta_rectangle_shove_into_region (region_spanning_rectangles,
+ info->fixed_directions,
+ &info->current);
+
+ unextend_by_frame (&info->current, info->fgeom);
+ return TRUE;
+}
+
+static gboolean
+constrain_to_single_xinerama (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only)
+{
+ if (priority > PRIORITY_ENTIRELY_VISIBLE_ON_SINGLE_XINERAMA)
+ return TRUE;
+
+ /* Exit early if we know the constraint won't apply--note that this constraint
+ * is only meant for normal windows (e.g. we don't want docks to be shoved
+ * "onscreen" by their own strut) and we can't apply it to frameless windows
+ * or else users will be unable to move windows such as XMMS across xineramas.
+ */
+ if (window->type == META_WINDOW_DESKTOP ||
+ window->type == META_WINDOW_DOCK ||
+ window->screen->n_xinerama_infos == 1 ||
+ !window->require_on_single_xinerama ||
+ !window->frame ||
+ info->is_user_action)
+ return TRUE;
+
+ /* Have a helper function handle the constraint for us */
+ return do_screen_and_xinerama_relative_constraints (window,
+ info->usable_xinerama_region,
+ info,
+ check_only);
+}
+
+static gboolean
+constrain_fully_onscreen (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only)
+{
+ if (priority > PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA)
+ return TRUE;
+
+ /* Exit early if we know the constraint won't apply--note that this constraint
+ * is only meant for normal windows (e.g. we don't want docks to be shoved
+ * "onscreen" by their own strut).
+ */
+ if (window->type == META_WINDOW_DESKTOP ||
+ window->type == META_WINDOW_DOCK ||
+ window->fullscreen ||
+ !window->require_fully_onscreen ||
+ info->is_user_action)
+ return TRUE;
+
+ /* Have a helper function handle the constraint for us */
+ return do_screen_and_xinerama_relative_constraints (window,
+ info->usable_screen_region,
+ info,
+ check_only);
+}
+
+static gboolean
+constrain_titlebar_visible (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only)
+{
+ gboolean unconstrained_user_action;
+ gboolean retval;
+ int bottom_amount;
+ int horiz_amount_offscreen, vert_amount_offscreen;
+ int horiz_amount_onscreen, vert_amount_onscreen;
+
+ if (priority > PRIORITY_TITLEBAR_VISIBLE)
+ return TRUE;
+
+ /* Allow the titlebar beyond the top of the screen only if the user wasn't
+ * clicking on the frame to start the move.
+ */
+ unconstrained_user_action =
+ info->is_user_action && !window->display->grab_frame_action;
+
+ /* Exit early if we know the constraint won't apply--note that this constraint
+ * is only meant for normal windows (e.g. we don't want docks to be shoved
+ * "onscreen" by their own strut).
+ */
+ if (window->type == META_WINDOW_DESKTOP ||
+ window->type == META_WINDOW_DOCK ||
+ window->fullscreen ||
+ !window->require_titlebar_visible ||
+ !window->decorated ||
+ unconstrained_user_action)
+ return TRUE;
+
+ /* Determine how much offscreen things are allowed. We first need to
+ * figure out how much must remain on the screen. For that, we use 25%
+ * window width/height but clamp to the range of (10,75) pixels. This is
+ * somewhat of a seat of my pants random guess at what might look good.
+ * Then, the amount that is allowed off is just the window size minus
+ * this amount (but no less than 0 for tiny windows).
+ */
+ horiz_amount_onscreen = info->current.width / 4;
+ vert_amount_onscreen = info->current.height / 4;
+ horiz_amount_onscreen = CLAMP (horiz_amount_onscreen, 10, 75);
+ vert_amount_onscreen = CLAMP (vert_amount_onscreen, 10, 75);
+ horiz_amount_offscreen = info->current.width - horiz_amount_onscreen;
+ vert_amount_offscreen = info->current.height - vert_amount_onscreen;
+ horiz_amount_offscreen = MAX (horiz_amount_offscreen, 0);
+ vert_amount_offscreen = MAX (vert_amount_offscreen, 0);
+ /* Allow the titlebar to touch the bottom panel; If there is no titlebar,
+ * require vert_amount to remain on the screen.
+ */
+ if (window->frame)
+ {
+ bottom_amount = info->current.height + info->fgeom->bottom_height;
+ vert_amount_onscreen = info->fgeom->top_height;
+ }
+ else
+ bottom_amount = vert_amount_offscreen;
+
+ /* Extend the region, have a helper function handle the constraint,
+ * then return the region to its original size.
+ */
+ meta_rectangle_expand_region_conditionally (info->usable_screen_region,
+ horiz_amount_offscreen,
+ horiz_amount_offscreen,
+ 0, /* Don't let titlebar off */
+ bottom_amount,
+ horiz_amount_onscreen,
+ vert_amount_onscreen);
+ retval =
+ do_screen_and_xinerama_relative_constraints (window,
+ info->usable_screen_region,
+ info,
+ check_only);
+ meta_rectangle_expand_region_conditionally (info->usable_screen_region,
+ -horiz_amount_offscreen,
+ -horiz_amount_offscreen,
+ 0, /* Don't let titlebar off */
+ -bottom_amount,
+ horiz_amount_onscreen,
+ vert_amount_onscreen);
+
+ return retval;
+}
+
+static gboolean
+constrain_partially_onscreen (MetaWindow *window,
+ ConstraintInfo *info,
+ ConstraintPriority priority,
+ gboolean check_only)
+{
+ gboolean retval;
+ int top_amount, bottom_amount;
+ int horiz_amount_offscreen, vert_amount_offscreen;
+ int horiz_amount_onscreen, vert_amount_onscreen;
+
+ if (priority > PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA)
+ return TRUE;
+
+ /* Exit early if we know the constraint won't apply--note that this constraint
+ * is only meant for normal windows (e.g. we don't want docks to be shoved
+ * "onscreen" by their own strut).
+ */
+ if (window->type == META_WINDOW_DESKTOP ||
+ window->type == META_WINDOW_DOCK)
+ return TRUE;
+
+ /* Determine how much offscreen things are allowed. We first need to
+ * figure out how much must remain on the screen. For that, we use 25%
+ * window width/height but clamp to the range of (10,75) pixels. This is
+ * somewhat of a seat of my pants random guess at what might look good.
+ * Then, the amount that is allowed off is just the window size minus
+ * this amount (but no less than 0 for tiny windows).
+ */
+ horiz_amount_onscreen = info->current.width / 4;
+ vert_amount_onscreen = info->current.height / 4;
+ horiz_amount_onscreen = CLAMP (horiz_amount_onscreen, 10, 75);
+ vert_amount_onscreen = CLAMP (vert_amount_onscreen, 10, 75);
+ horiz_amount_offscreen = info->current.width - horiz_amount_onscreen;
+ vert_amount_offscreen = info->current.height - vert_amount_onscreen;
+ horiz_amount_offscreen = MAX (horiz_amount_offscreen, 0);
+ vert_amount_offscreen = MAX (vert_amount_offscreen, 0);
+ top_amount = vert_amount_offscreen;
+ /* Allow the titlebar to touch the bottom panel; If there is no titlebar,
+ * require vert_amount to remain on the screen.
+ */
+ if (window->frame)
+ {
+ bottom_amount = info->current.height + info->fgeom->bottom_height;
+ vert_amount_onscreen = info->fgeom->top_height;
+ }
+ else
+ bottom_amount = vert_amount_offscreen;
+
+ /* Extend the region, have a helper function handle the constraint,
+ * then return the region to its original size.
+ */
+ meta_rectangle_expand_region_conditionally (info->usable_screen_region,
+ horiz_amount_offscreen,
+ horiz_amount_offscreen,
+ top_amount,
+ bottom_amount,
+ horiz_amount_onscreen,
+ vert_amount_onscreen);
+ retval =
+ do_screen_and_xinerama_relative_constraints (window,
+ info->usable_screen_region,
+ info,
+ check_only);
+ meta_rectangle_expand_region_conditionally (info->usable_screen_region,
+ -horiz_amount_offscreen,
+ -horiz_amount_offscreen,
+ -top_amount,
+ -bottom_amount,
+ horiz_amount_onscreen,
+ vert_amount_onscreen);
+
+ return retval;
+}
diff --git a/src/core/constraints.h b/src/core/constraints.h
new file mode 100644
index 00000000..d374581f
--- /dev/null
+++ b/src/core/constraints.h
@@ -0,0 +1,48 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco size/position constraints */
+
+/*
+ * Copyright (C) 2002 Red Hat, Inc.
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_CONSTRAINTS_H
+#define META_CONSTRAINTS_H
+
+#include "util.h"
+#include "window-private.h"
+#include "frame-private.h"
+
+typedef enum
+{
+ META_IS_CONFIGURE_REQUEST = 1 << 0,
+ META_DO_GRAVITY_ADJUST = 1 << 1,
+ META_IS_USER_ACTION = 1 << 2,
+ META_IS_MOVE_ACTION = 1 << 3,
+ META_IS_RESIZE_ACTION = 1 << 4
+} MetaMoveResizeFlags;
+
+void meta_window_constrain (MetaWindow *window,
+ MetaFrameGeometry *orig_fgeom,
+ MetaMoveResizeFlags flags,
+ int resize_gravity,
+ const MetaRectangle *orig,
+ MetaRectangle *new);
+
+#endif /* META_CONSTRAINTS_H */
diff --git a/src/core/core.c b/src/core/core.c
new file mode 100644
index 00000000..963cbfa7
--- /dev/null
+++ b/src/core/core.c
@@ -0,0 +1,779 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco interface used by GTK+ UI to talk to core */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2003 Rob Adams
+ * Copyright (C) 2004-2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "core.h"
+#include "frame-private.h"
+#include "workspace.h"
+#include "prefs.h"
+
+/* Looks up the MetaWindow representing the frame of the given X window.
+ * Used as a helper function by a bunch of the functions below.
+ *
+ * FIXME: The functions that use this function throw the result away
+ * after use. Many of these functions tend to be called in small groups,
+ * which results in get_window() getting called several times in succession
+ * with the same parameters. We should profile to see whether this wastes
+ * much time, and if it does we should look into a generalised
+ * meta_core_get_window_info() which takes a bunch of pointers to variables
+ * to put its results in, and only fills in the non-null ones.
+ */
+static MetaWindow *
+get_window (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaDisplay *display;
+ MetaWindow *window;
+
+ display = meta_display_for_x_display (xdisplay);
+ window = meta_display_lookup_x_window (display, frame_xwindow);
+
+ if (window == NULL || window->frame == NULL)
+ {
+ meta_bug ("No such frame window 0x%lx!\n", frame_xwindow);
+ return NULL;
+ }
+
+ return window;
+}
+
+void
+meta_core_get (Display *xdisplay,
+ Window xwindow,
+ ...)
+{
+ va_list args;
+ MetaCoreGetType request;
+
+ MetaDisplay *display = meta_display_for_x_display (xdisplay);
+ MetaWindow *window = meta_display_lookup_x_window (display, xwindow);
+
+ va_start (args, xwindow);
+
+ request = va_arg (args, MetaCoreGetType);
+
+ /* Now, we special-case the first request slightly. Mostly, requests
+ * for information on windows which have no frame are errors.
+ * But sometimes we may want to know *whether* a window has a frame.
+ * In this case, pass the key META_CORE_WINDOW_HAS_FRAME
+ * as the *first* request, with a pointer to a boolean; if the window
+ * has no frame, this will be set to False and meta_core_get will
+ * exit immediately (so the values of any other requests will be
+ * undefined). Otherwise it will be set to True and meta_core_get will
+ * continue happily on its way.
+ */
+
+ if (request != META_CORE_WINDOW_HAS_FRAME &&
+ (window == NULL || window->frame == NULL)) {
+ meta_bug ("No such frame window 0x%lx!\n", xwindow);
+ return;
+ }
+
+ while (request != META_CORE_GET_END) {
+
+ gpointer answer = va_arg (args, gpointer);
+
+ switch (request) {
+ case META_CORE_WINDOW_HAS_FRAME:
+ *((gboolean*)answer) = window != NULL && window->frame != NULL;
+ if (!*((gboolean*)answer)) return; /* see above */
+ break;
+ case META_CORE_GET_CLIENT_WIDTH:
+ *((gint*)answer) = window->rect.width;
+ break;
+ case META_CORE_GET_CLIENT_HEIGHT:
+ *((gint*)answer) = window->rect.height;
+ break;
+ case META_CORE_IS_TITLEBAR_ONSCREEN:
+ *((gboolean*)answer) = meta_window_titlebar_is_onscreen (window);
+ break;
+ case META_CORE_GET_CLIENT_XWINDOW:
+ *((Window*)answer) = window->xwindow;
+ break;
+ case META_CORE_GET_FRAME_FLAGS:
+ *((MetaFrameFlags*)answer) = meta_frame_get_flags (window->frame);
+ break;
+ case META_CORE_GET_FRAME_TYPE:
+ {
+ MetaFrameType base_type = META_FRAME_TYPE_LAST;
+
+ switch (window->type)
+ {
+ case META_WINDOW_NORMAL:
+ base_type = META_FRAME_TYPE_NORMAL;
+ break;
+
+ case META_WINDOW_DIALOG:
+ base_type = META_FRAME_TYPE_DIALOG;
+ break;
+
+ case META_WINDOW_MODAL_DIALOG:
+ base_type = META_FRAME_TYPE_MODAL_DIALOG;
+ break;
+
+ case META_WINDOW_MENU:
+ base_type = META_FRAME_TYPE_MENU;
+ break;
+
+ case META_WINDOW_UTILITY:
+ base_type = META_FRAME_TYPE_UTILITY;
+ break;
+
+ case META_WINDOW_DESKTOP:
+ case META_WINDOW_DOCK:
+ case META_WINDOW_TOOLBAR:
+ case META_WINDOW_SPLASHSCREEN:
+ /* No frame */
+ base_type = META_FRAME_TYPE_LAST;
+ break;
+
+ }
+
+ if (base_type == META_FRAME_TYPE_LAST)
+ {
+ /* can't add border if undecorated */
+ *((MetaFrameType*)answer) = META_FRAME_TYPE_LAST;
+ }
+ else if (window->border_only)
+ {
+ /* override base frame type */
+ *((MetaFrameType*)answer) = META_FRAME_TYPE_BORDER;
+ }
+ else
+ {
+ *((MetaFrameType*)answer) = base_type;
+ }
+
+ break;
+ }
+ case META_CORE_GET_MINI_ICON:
+ *((GdkPixbuf**)answer) = window->mini_icon;
+ break;
+ case META_CORE_GET_ICON:
+ *((GdkPixbuf**)answer) = window->icon;
+ break;
+ case META_CORE_GET_X:
+ meta_window_get_position (window, (int*)answer, NULL);
+ break;
+ case META_CORE_GET_Y:
+ meta_window_get_position (window, NULL, (int*)answer);
+ break;
+ case META_CORE_GET_FRAME_WORKSPACE:
+ *((gint*)answer) = meta_window_get_net_wm_desktop (window);
+ break;
+ case META_CORE_GET_FRAME_X:
+ *((gint*)answer) = window->frame->rect.x;
+ break;
+ case META_CORE_GET_FRAME_Y:
+ *((gint*)answer) = window->frame->rect.y;
+ break;
+ case META_CORE_GET_FRAME_WIDTH:
+ *((gint*)answer) = window->frame->rect.width;
+ break;
+ case META_CORE_GET_FRAME_HEIGHT:
+ *((gint*)answer) = window->frame->rect.height;
+ break;
+ case META_CORE_GET_SCREEN_WIDTH:
+ *((gint*)answer) = window->screen->rect.width;
+ break;
+ case META_CORE_GET_SCREEN_HEIGHT:
+ *((gint*)answer) = window->screen->rect.height;
+ break;
+
+ default:
+ meta_warning(_("Unknown window information request: %d"), request);
+ }
+
+ request = va_arg (args, MetaCoreGetType);
+ }
+
+ va_end (args);
+}
+
+void
+meta_core_queue_frame_resize (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_queue (window, META_QUEUE_MOVE_RESIZE);
+}
+
+void
+meta_core_user_move (Display *xdisplay,
+ Window frame_xwindow,
+ int x,
+ int y)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_move (window, TRUE, x, y);
+}
+
+void
+meta_core_user_resize (Display *xdisplay,
+ Window frame_xwindow,
+ int gravity,
+ int width,
+ int height)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_resize_with_gravity (window, TRUE, width, height, gravity);
+}
+
+void
+meta_core_user_raise (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_raise (window);
+}
+
+void
+meta_core_user_lower_and_unfocus (Display *xdisplay,
+ Window frame_xwindow,
+ guint32 timestamp)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_lower (window);
+
+ if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK &&
+ meta_prefs_get_raise_on_click ())
+ {
+ /* Move window to the back of the focusing workspace's MRU list.
+ * Do extra sanity checks to avoid possible race conditions.
+ * (Borrowed from window.c.)
+ */
+ if (window->screen->active_workspace &&
+ meta_window_located_on_workspace (window,
+ window->screen->active_workspace))
+ {
+ GList* link;
+ link = g_list_find (window->screen->active_workspace->mru_list,
+ window);
+ g_assert (link);
+
+ window->screen->active_workspace->mru_list =
+ g_list_remove_link (window->screen->active_workspace->mru_list,
+ link);
+ g_list_free (link);
+
+ window->screen->active_workspace->mru_list =
+ g_list_append (window->screen->active_workspace->mru_list,
+ window);
+ }
+ }
+
+ /* focus the default window, if needed */
+ if (window->has_focus)
+ meta_workspace_focus_default_window (window->screen->active_workspace,
+ NULL,
+ timestamp);
+}
+
+void
+meta_core_user_focus (Display *xdisplay,
+ Window frame_xwindow,
+ guint32 timestamp)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_focus (window, timestamp);
+}
+
+void
+meta_core_minimize (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_minimize (window);
+}
+
+void
+meta_core_maximize (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+
+ meta_window_maximize (window,
+ META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL);
+}
+
+void
+meta_core_toggle_maximize_vertically (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+
+ if (META_WINDOW_MAXIMIZED_VERTICALLY (window))
+ meta_window_unmaximize (window,
+ META_MAXIMIZE_VERTICAL);
+ else
+ meta_window_maximize (window,
+ META_MAXIMIZE_VERTICAL);
+}
+
+void
+meta_core_toggle_maximize_horizontally (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+
+ if (META_WINDOW_MAXIMIZED_HORIZONTALLY (window))
+ meta_window_unmaximize (window,
+ META_MAXIMIZE_HORIZONTAL);
+ else
+ meta_window_maximize (window,
+ META_MAXIMIZE_HORIZONTAL);
+}
+
+void
+meta_core_toggle_maximize (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+
+ if (META_WINDOW_MAXIMIZED (window))
+ meta_window_unmaximize (window,
+ META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL);
+ else
+ meta_window_maximize (window,
+ META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL);
+}
+
+void
+meta_core_unmaximize (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+
+ meta_window_unmaximize (window,
+ META_MAXIMIZE_HORIZONTAL | META_MAXIMIZE_VERTICAL);
+}
+
+void
+meta_core_delete (Display *xdisplay,
+ Window frame_xwindow,
+ guint32 timestamp)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_delete (window, timestamp);
+}
+
+void
+meta_core_unshade (Display *xdisplay,
+ Window frame_xwindow,
+ guint32 timestamp)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_unshade (window, timestamp);
+}
+
+void
+meta_core_shade (Display *xdisplay,
+ Window frame_xwindow,
+ guint32 timestamp)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_shade (window, timestamp);
+}
+
+void
+meta_core_unstick (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_unstick (window);
+}
+
+void
+meta_core_make_above (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_make_above (window);
+}
+
+void
+meta_core_unmake_above (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_unmake_above (window);
+}
+
+void
+meta_core_stick (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_stick (window);
+}
+
+void
+meta_core_change_workspace (Display *xdisplay,
+ Window frame_xwindow,
+ int new_workspace)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ meta_window_change_workspace (window,
+ meta_screen_get_workspace_by_index (window->screen,
+ new_workspace));
+}
+
+int
+meta_core_get_num_workspaces (Screen *xscreen)
+{
+ MetaScreen *screen;
+
+ screen = meta_screen_for_x_screen (xscreen);
+
+ return meta_screen_get_n_workspaces (screen);
+}
+
+int
+meta_core_get_active_workspace (Screen *xscreen)
+{
+ MetaScreen *screen;
+
+ screen = meta_screen_for_x_screen (xscreen);
+
+ return meta_workspace_index (screen->active_workspace);
+}
+
+void
+meta_core_show_window_menu (Display *xdisplay,
+ Window frame_xwindow,
+ int root_x,
+ int root_y,
+ int button,
+ guint32 timestamp)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+ meta_window_focus (window, timestamp);
+
+ meta_window_show_menu (window, root_x, root_y, button, timestamp);
+}
+
+void
+meta_core_get_menu_accelerator (MetaMenuOp menu_op,
+ int workspace,
+ unsigned int *keysym,
+ MetaVirtualModifier *modifiers)
+{
+ const char *name;
+
+ name = NULL;
+
+ switch (menu_op)
+ {
+ case META_MENU_OP_NONE:
+ /* No keybinding for this one */
+ break;
+ case META_MENU_OP_DELETE:
+ name = "close";
+ break;
+ case META_MENU_OP_MINIMIZE:
+ name = "minimize";
+ break;
+ case META_MENU_OP_UNMAXIMIZE:
+ name = "unmaximize";
+ break;
+ case META_MENU_OP_MAXIMIZE:
+ name = "maximize";
+ break;
+ case META_MENU_OP_UNSHADE:
+ case META_MENU_OP_SHADE:
+ name = "toggle_shaded";
+ break;
+ case META_MENU_OP_UNSTICK:
+ case META_MENU_OP_STICK:
+ name = "toggle_on_all_workspaces";
+ break;
+ case META_MENU_OP_ABOVE:
+ case META_MENU_OP_UNABOVE:
+ name = "toggle_above";
+ break;
+ case META_MENU_OP_WORKSPACES:
+ switch (workspace)
+ {
+ case 1:
+ name = "move_to_workspace_1";
+ break;
+ case 2:
+ name = "move_to_workspace_2";
+ break;
+ case 3:
+ name = "move_to_workspace_3";
+ break;
+ case 4:
+ name = "move_to_workspace_4";
+ break;
+ case 5:
+ name = "move_to_workspace_5";
+ break;
+ case 6:
+ name = "move_to_workspace_6";
+ break;
+ case 7:
+ name = "move_to_workspace_7";
+ break;
+ case 8:
+ name = "move_to_workspace_8";
+ break;
+ case 9:
+ name = "move_to_workspace_9";
+ break;
+ case 10:
+ name = "move_to_workspace_10";
+ break;
+ case 11:
+ name = "move_to_workspace_11";
+ break;
+ case 12:
+ name = "move_to_workspace_12";
+ break;
+ }
+ break;
+ case META_MENU_OP_MOVE:
+ name = "begin_move";
+ break;
+ case META_MENU_OP_RESIZE:
+ name = "begin_resize";
+ break;
+ case META_MENU_OP_MOVE_LEFT:
+ name = "move_to_workspace_left";
+ break;
+ case META_MENU_OP_MOVE_RIGHT:
+ name = "move_to_workspace_right";
+ break;
+ case META_MENU_OP_MOVE_UP:
+ name = "move_to_workspace_up";
+ break;
+ case META_MENU_OP_MOVE_DOWN:
+ name = "move_to_workspace_down";
+ break;
+ case META_MENU_OP_RECOVER:
+ /* No keybinding for this one */
+ break;
+ }
+
+ if (name)
+ {
+ meta_prefs_get_window_binding (name, keysym, modifiers);
+ }
+ else
+ {
+ *keysym = 0;
+ *modifiers = 0;
+ }
+}
+
+const char*
+meta_core_get_workspace_name_with_index (Display *xdisplay,
+ Window xroot,
+ int index)
+{
+ MetaDisplay *display;
+ MetaScreen *screen;
+ MetaWorkspace *workspace;
+
+ display = meta_display_for_x_display (xdisplay);
+ screen = meta_display_screen_for_root (display, xroot);
+ g_assert (screen != NULL);
+ workspace = meta_screen_get_workspace_by_index (screen, index);
+ return workspace ? meta_workspace_get_name (workspace) : NULL;
+}
+
+gboolean
+meta_core_begin_grab_op (Display *xdisplay,
+ Window frame_xwindow,
+ MetaGrabOp op,
+ gboolean pointer_already_grabbed,
+ gboolean frame_action,
+ int button,
+ gulong modmask,
+ guint32 timestamp,
+ int root_x,
+ int root_y)
+{
+ MetaWindow *window = get_window (xdisplay, frame_xwindow);
+ MetaDisplay *display;
+ MetaScreen *screen;
+
+ display = meta_display_for_x_display (xdisplay);
+ screen = meta_display_screen_for_xwindow (display, frame_xwindow);
+
+ g_assert (screen != NULL);
+
+ return meta_display_begin_grab_op (display, screen, window,
+ op, pointer_already_grabbed,
+ frame_action,
+ button, modmask,
+ timestamp, root_x, root_y);
+}
+
+void
+meta_core_end_grab_op (Display *xdisplay,
+ guint32 timestamp)
+{
+ MetaDisplay *display;
+
+ display = meta_display_for_x_display (xdisplay);
+
+ meta_display_end_grab_op (display, timestamp);
+}
+
+MetaGrabOp
+meta_core_get_grab_op (Display *xdisplay)
+{
+ MetaDisplay *display;
+
+ display = meta_display_for_x_display (xdisplay);
+
+ return display->grab_op;
+}
+
+Window
+meta_core_get_grab_frame (Display *xdisplay)
+{
+ MetaDisplay *display;
+
+ display = meta_display_for_x_display (xdisplay);
+
+ g_assert (display != NULL);
+ g_assert (display->grab_op == META_GRAB_OP_NONE ||
+ display->grab_screen != NULL);
+ g_assert (display->grab_op == META_GRAB_OP_NONE ||
+ display->grab_screen->display->xdisplay == xdisplay);
+
+ if (display->grab_op != META_GRAB_OP_NONE &&
+ display->grab_window &&
+ display->grab_window->frame)
+ return display->grab_window->frame->xwindow;
+ else
+ return None;
+}
+
+int
+meta_core_get_grab_button (Display *xdisplay)
+{
+ MetaDisplay *display;
+
+ display = meta_display_for_x_display (xdisplay);
+
+ if (display->grab_op == META_GRAB_OP_NONE)
+ return -1;
+
+ return display->grab_button;
+}
+
+void
+meta_core_grab_buttons (Display *xdisplay,
+ Window frame_xwindow)
+{
+ MetaDisplay *display;
+
+ display = meta_display_for_x_display (xdisplay);
+
+ meta_verbose ("Grabbing buttons on frame 0x%lx\n", frame_xwindow);
+ meta_display_grab_window_buttons (display, frame_xwindow);
+}
+
+void
+meta_core_set_screen_cursor (Display *xdisplay,
+ Window frame_on_screen,
+ MetaCursor cursor)
+{
+ MetaWindow *window = get_window (xdisplay, frame_on_screen);
+
+ meta_frame_set_screen_cursor (window->frame, cursor);
+}
+
+void
+meta_core_increment_event_serial (Display *xdisplay)
+{
+ MetaDisplay *display;
+
+ display = meta_display_for_x_display (xdisplay);
+
+ meta_display_increment_event_serial (display);
+}
+
+void
+meta_invalidate_default_icons (void)
+{
+ MetaDisplay *display = meta_get_display ();
+ GSList *windows;
+ GSList *l;
+
+ if (display == NULL)
+ return; /* We can validly be called before the display is opened. */
+
+ windows = meta_display_list_windows (display);
+ for (l = windows; l != NULL; l = l->next)
+ {
+ MetaWindow *window = (MetaWindow*)l->data;
+
+ if (window->icon_cache.origin == USING_FALLBACK_ICON)
+ {
+ meta_icon_cache_free (&(window->icon_cache));
+ meta_window_update_icon_now (window);
+ }
+ }
+
+ g_slist_free (windows);
+}
+
diff --git a/src/core/delete.c b/src/core/delete.c
new file mode 100644
index 00000000..f0590e3b
--- /dev/null
+++ b/src/core/delete.c
@@ -0,0 +1,266 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco window deletion */
+
+/*
+ * Copyright (C) 2001, 2002 Havoc Pennington
+ * Copyright (C) 2004 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+#define _SVID_SOURCE /* for gethostname() */
+
+#include <config.h>
+#include "util.h"
+#include "window-private.h"
+#include "errors.h"
+#include "workspace.h"
+
+#include <sys/types.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+static void meta_window_present_delete_dialog (MetaWindow *window,
+ guint32 timestamp);
+
+static void
+delete_ping_reply_func (MetaDisplay *display,
+ Window xwindow,
+ guint32 timestamp,
+ void *user_data)
+{
+ meta_topic (META_DEBUG_PING,
+ "Got reply to delete ping for %s\n",
+ ((MetaWindow*)user_data)->desc);
+
+ /* we do nothing */
+}
+
+static void
+dialog_exited (GPid pid,
+ int status,
+ gpointer user_data)
+{
+ MetaWindow *ours = (MetaWindow*) user_data;
+
+ ours->dialog_pid = -1;
+
+ /* exit status of 1 means the user pressed "Force Quit" */
+ if (WIFEXITED (status) && WEXITSTATUS (status) == 1)
+ meta_window_kill (ours);
+}
+
+static void
+delete_ping_timeout_func (MetaDisplay *display,
+ Window xwindow,
+ guint32 timestamp,
+ void *user_data)
+{
+ MetaWindow *window = user_data;
+ char *window_title;
+ gchar *window_content, *tmp;
+ GPid dialog_pid;
+
+ meta_topic (META_DEBUG_PING,
+ "Got delete ping timeout for %s\n",
+ window->desc);
+
+ if (window->dialog_pid >= 0)
+ {
+ meta_window_present_delete_dialog (window, timestamp);
+ return;
+ }
+
+ window_title = g_locale_from_utf8 (window->title, -1, NULL, NULL, NULL);
+
+ /* Translators: %s is a window title */
+ tmp = g_strdup_printf (_("<tt>%s</tt> is not responding."),
+ window_title);
+ window_content = g_strdup_printf (
+ "<big><b>%s</b></big>\n\n<i>%s</i>",
+ tmp,
+ _("You may choose to wait a short while for it to "
+ "continue or force the application to quit entirely."));
+
+ g_free (window_title);
+
+ dialog_pid =
+ meta_show_dialog ("--question",
+ window_content, 0,
+ window->screen->number,
+ _("_Wait"), _("_Force Quit"), window->xwindow,
+ NULL, NULL);
+
+ g_free (window_content);
+ g_free (tmp);
+
+ window->dialog_pid = dialog_pid;
+ g_child_watch_add (dialog_pid, dialog_exited, window);
+}
+
+void
+meta_window_delete (MetaWindow *window,
+ guint32 timestamp)
+{
+ meta_error_trap_push (window->display);
+ if (window->delete_window)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Deleting %s with delete_window request\n",
+ window->desc);
+ meta_window_send_icccm_message (window,
+ window->display->atom_WM_DELETE_WINDOW,
+ timestamp);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Deleting %s with explicit kill\n",
+ window->desc);
+ XKillClient (window->display->xdisplay, window->xwindow);
+ }
+ meta_error_trap_pop (window->display, FALSE);
+
+ meta_display_ping_window (window->display,
+ window,
+ timestamp,
+ delete_ping_reply_func,
+ delete_ping_timeout_func,
+ window);
+
+ if (window->has_focus)
+ {
+ /* FIXME Clean this up someday
+ * http://bugzilla.gnome.org/show_bug.cgi?id=108706
+ */
+#if 0
+ /* This is unfortunately going to result in weirdness
+ * if the window doesn't respond to the delete event.
+ * I don't know how to avoid that though.
+ */
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing default window because focus window %s was deleted/killed\n",
+ window->desc);
+ meta_workspace_focus_default_window (window->screen->active_workspace,
+ window);
+#else
+ meta_topic (META_DEBUG_FOCUS,
+ "Not unfocusing %s on delete/kill\n",
+ window->desc);
+#endif
+ }
+ else
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Window %s was deleted/killed but didn't have focus\n",
+ window->desc);
+ }
+}
+
+
+void
+meta_window_kill (MetaWindow *window)
+{
+ char buf[257];
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Killing %s brutally\n",
+ window->desc);
+
+ if (window->wm_client_machine != NULL &&
+ window->net_wm_pid > 0)
+ {
+ if (gethostname (buf, sizeof(buf)-1) == 0)
+ {
+ if (strcmp (buf, window->wm_client_machine) == 0)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Killing %s with kill()\n",
+ window->desc);
+
+ if (kill (window->net_wm_pid, 9) < 0)
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Failed to signal %s: %s\n",
+ window->desc, strerror (errno));
+ }
+ }
+ else
+ {
+ meta_warning (_("Failed to get hostname: %s\n"),
+ strerror (errno));
+ }
+ }
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Disconnecting %s with XKillClient()\n",
+ window->desc);
+ meta_error_trap_push (window->display);
+ XKillClient (window->display->xdisplay, window->xwindow);
+ meta_error_trap_pop (window->display, FALSE);
+}
+
+void
+meta_window_free_delete_dialog (MetaWindow *window)
+{
+ if (window->dialog_pid >= 0)
+ {
+ kill (window->dialog_pid, 9);
+ window->dialog_pid = -1;
+ }
+}
+
+static void
+meta_window_present_delete_dialog (MetaWindow *window, guint32 timestamp)
+{
+ meta_topic (META_DEBUG_PING,
+ "Presenting existing ping dialog for %s\n",
+ window->desc);
+
+ if (window->dialog_pid >= 0)
+ {
+ GSList *windows;
+ GSList *tmp;
+
+ /* Activate transient for window that belongs to
+ * marco-dialog
+ */
+
+ windows = meta_display_list_windows (window->display);
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ if (w->xtransient_for == window->xwindow &&
+ w->res_class &&
+ g_ascii_strcasecmp (w->res_class, "marco-dialog") == 0)
+ {
+ meta_window_activate (w, timestamp);
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (windows);
+ }
+}
diff --git a/src/core/display-private.h b/src/core/display-private.h
new file mode 100644
index 00000000..692e25f2
--- /dev/null
+++ b/src/core/display-private.h
@@ -0,0 +1,513 @@
+/* Marco X display handler */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2002 Red Hat, Inc.
+ * Copyright (C) 2003 Rob Adams
+ * Copyright (C) 2004-2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_DISPLAY_PRIVATE_H
+#define META_DISPLAY_PRIVATE_H
+
+#ifndef PACKAGE
+ #error "config.h not included"
+#endif
+
+#include <glib.h>
+#include <X11/Xlib.h>
+#include "eventqueue.h"
+#include "common.h"
+#include "boxes.h"
+#include "display.h"
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ #include <libsn/sn.h>
+#endif
+
+#ifdef HAVE_XSYNC
+ #include <X11/extensions/sync.h>
+#endif
+
+typedef struct _MetaKeyBinding MetaKeyBinding;
+typedef struct _MetaStack MetaStack;
+typedef struct _MetaUISlave MetaUISlave;
+typedef struct _MetaWorkspace MetaWorkspace;
+
+typedef struct _MetaGroupPropHooks MetaGroupPropHooks;
+
+typedef struct MetaEdgeResistanceData MetaEdgeResistanceData;
+
+typedef void (*MetaWindowPingFunc) (MetaDisplay* display, Window xwindow, guint32 timestamp, gpointer user_data);
+
+
+#define _NET_WM_STATE_REMOVE 0 /* remove/unset property */
+#define _NET_WM_STATE_ADD 1 /* add/set property */
+#define _NET_WM_STATE_TOGGLE 2 /* toggle property */
+
+/* This is basically a bogus number, just has to be large enough
+ * to handle the expected case of the alt+tab operation, where
+ * we want to ignore serials from UnmapNotify on the tab popup,
+ * and the LeaveNotify/EnterNotify from the pointer ungrab
+ */
+#define N_IGNORED_SERIALS 4
+
+struct _MetaDisplay {
+ char* name;
+ Display* xdisplay;
+
+ Window leader_window;
+ Window timestamp_pinging_window;
+
+ /* Pull in all the names of atoms as fields; we will intern them when the
+ * class is constructed.
+ */
+ #define item(x) Atom atom_##x;
+ #include "atomnames.h"
+ #undef item
+
+ /* This is the actual window from focus events,
+ * not the one we last set
+ */
+ MetaWindow* focus_window;
+
+ /* window we are expecting a FocusIn event for or the current focus
+ * window if we are not expecting any FocusIn/FocusOut events; not
+ * perfect because applications can call XSetInputFocus directly.
+ * (It could also be messed up if a timestamp later than current
+ * time is sent to meta_display_set_input_focus_window, though that
+ * would be a programming error). See bug 154598 for more info.
+ */
+ MetaWindow* expected_focus_window;
+
+ /* last timestamp passed to XSetInputFocus */
+ guint32 last_focus_time;
+
+ /* last user interaction time in any app */
+ guint32 last_user_time;
+
+ /* whether we're using mousenav (only relevant for sloppy&mouse focus modes;
+ * !mouse_mode means "keynav mode")
+ */
+ guint mouse_mode: 1;
+
+ /* Helper var used when focus_new_windows setting is 'strict'; only
+ * relevant in 'strict' mode and if the focus window is a terminal.
+ * In that case, we don't allow new windows to take focus away from
+ * a terminal, but if the user explicitly did something that should
+ * allow a different window to gain focus (e.g. global keybinding or
+ * clicking on a dock), then we will allow the transfer.
+ */
+ guint allow_terminal_deactivation: 1;
+
+ guint static_gravity_works: 1;
+
+ /*< private-ish >*/
+ guint error_trap_synced_at_last_pop: 1;
+ MetaEventQueue* events;
+ GSList* screens;
+ MetaScreen* active_screen;
+ GHashTable* window_ids;
+ int error_traps;
+ int (*error_trap_handler) (Display* display, XErrorEvent* error);
+ int server_grab_count;
+
+ /* serials of leave/unmap events that may
+ * correspond to an enter event we should
+ * ignore
+ */
+ unsigned long ignored_serials[N_IGNORED_SERIALS];
+ Window ungrab_should_not_cause_focus_window;
+
+ guint32 current_time;
+
+ /* Pings which we're waiting for a reply from */
+ GSList* pending_pings;
+
+ /* Pending autoraise */
+ guint autoraise_timeout_id;
+ MetaWindow* autoraise_window;
+
+ /* Alt+click button grabs */
+ unsigned int window_grab_modifiers;
+
+ /* current window operation */
+ MetaGrabOp grab_op;
+ MetaScreen* grab_screen;
+ MetaWindow* grab_window;
+ Window grab_xwindow;
+ int grab_button;
+ int grab_anchor_root_x;
+ int grab_anchor_root_y;
+ MetaRectangle grab_anchor_window_pos;
+ int grab_latest_motion_x;
+ int grab_latest_motion_y;
+ gulong grab_mask;
+ guint grab_have_pointer : 1;
+ guint grab_have_keyboard : 1;
+ guint grab_wireframe_active : 1;
+ guint grab_was_cancelled : 1; /* Only used in wireframe mode */
+ guint grab_frame_action : 1;
+ MetaRectangle grab_wireframe_rect;
+ MetaRectangle grab_wireframe_last_xor_rect;
+ MetaRectangle grab_initial_window_pos;
+ int grab_initial_x, grab_initial_y; /* These are only relevant for */
+ gboolean grab_threshold_movement_reached; /* raise_on_click == FALSE. */
+ MetaResizePopup* grab_resize_popup;
+ GTimeVal grab_last_moveresize_time;
+ guint32 grab_motion_notify_time;
+ int grab_wireframe_last_display_width;
+ int grab_wireframe_last_display_height;
+ GList* grab_old_window_stacking;
+ MetaEdgeResistanceData* grab_edge_resistance_data;
+ unsigned int grab_last_user_action_was_snap;
+
+ /* we use property updates as sentinels for certain window focus events
+ * to avoid some race conditions on EnterNotify events
+ */
+ int sentinel_counter;
+
+ #ifdef HAVE_XKB
+ int xkb_base_event_type;
+ guint32 last_bell_time;
+ #endif
+
+ #ifdef HAVE_XSYNC
+ /* alarm monitoring client's _NET_WM_SYNC_REQUEST_COUNTER */
+ XSyncAlarm grab_sync_request_alarm;
+ #endif
+
+ int grab_resize_timeout_id;
+
+ /* Keybindings stuff */
+ MetaKeyBinding* key_bindings;
+ int n_key_bindings;
+ int min_keycode;
+ int max_keycode;
+ KeySym* keymap;
+ int keysyms_per_keycode;
+ XModifierKeymap* modmap;
+ unsigned int ignored_modifier_mask;
+ unsigned int num_lock_mask;
+ unsigned int scroll_lock_mask;
+ unsigned int hyper_mask;
+ unsigned int super_mask;
+ unsigned int meta_mask;
+
+ /* Xinerama cache */
+ unsigned int xinerama_cache_invalidated: 1;
+
+ /* Opening the display */
+ unsigned int display_opening: 1;
+
+ /* Closing down the display */
+ int closing;
+
+ /* To detect double clicks
+ *
+ * https://github.com/stefano-k/Mate-Desktop-Environment/commit/b0e5fb03eb21dae8f02692f11ef391bfc5ccba33
+ */
+ guint button_click_number;
+ Window button_click_window;
+ int button_click_x;
+ int button_click_y;
+ guint32 button_click_time;
+
+ /* Managed by group.c */
+ GHashTable* groups_by_leader;
+
+ /* currently-active window menu if any */
+ MetaWindowMenu* window_menu;
+ MetaWindow* window_with_menu;
+
+ /* Managed by window-props.c */
+ gpointer* prop_hooks_table;
+ GHashTable* prop_hooks;
+
+ /* Managed by group-props.c */
+ MetaGroupPropHooks* group_prop_hooks;
+
+ /* Managed by compositor.c */
+ MetaCompositor* compositor;
+
+ #ifdef HAVE_STARTUP_NOTIFICATION
+ SnDisplay* sn_display;
+ #endif
+
+ #ifdef HAVE_XSYNC
+ int xsync_event_base;
+ int xsync_error_base;
+ #endif
+
+ #ifdef HAVE_SHAPE
+ int shape_event_base;
+ int shape_error_base;
+ #endif
+
+ #ifdef HAVE_RENDER
+ int render_event_base;
+ int render_error_base;
+ #endif
+
+ #ifdef HAVE_COMPOSITE_EXTENSIONS
+ int composite_event_base;
+ int composite_error_base;
+ int composite_major_version;
+ int composite_minor_version;
+ int damage_event_base;
+ int damage_error_base;
+ int xfixes_event_base;
+ int xfixes_error_base;
+ #endif
+
+ #ifdef HAVE_XSYNC
+ unsigned int have_xsync : 1;
+ #define META_DISPLAY_HAS_XSYNC(display) ((display)->have_xsync)
+ #else
+ #define META_DISPLAY_HAS_XSYNC(display) FALSE
+ #endif
+
+ #ifdef HAVE_SHAPE
+ unsigned int have_shape : 1;
+ #define META_DISPLAY_HAS_SHAPE(display) ((display)->have_shape)
+ #else
+ #define META_DISPLAY_HAS_SHAPE(display) FALSE
+ #endif
+
+ #ifdef HAVE_RENDER
+ unsigned int have_render : 1;
+ #define META_DISPLAY_HAS_RENDER(display) ((display)->have_render)
+ #else
+ #define META_DISPLAY_HAS_RENDER(display) FALSE
+ #endif
+
+ #ifdef HAVE_COMPOSITE_EXTENSIONS
+ unsigned int have_composite : 1;
+ unsigned int have_damage : 1;
+ unsigned int have_xfixes : 1;
+ #define META_DISPLAY_HAS_COMPOSITE(display) ((display)->have_composite)
+ #define META_DISPLAY_HAS_DAMAGE(display) ((display)->have_damage)
+ #define META_DISPLAY_HAS_XFIXES(display) ((display)->have_xfixes)
+ #else
+ #define META_DISPLAY_HAS_COMPOSITE(display) FALSE
+ #define META_DISPLAY_HAS_DAMAGE(display) FALSE
+ #define META_DISPLAY_HAS_XFIXES(display) FALSE
+ #endif
+};
+
+/* Xserver time can wraparound, thus comparing two timestamps needs to take
+ * this into account. Here's a little macro to help out. If no wraparound
+ * has occurred, this is equivalent to
+ * time1 < time2
+ * Of course, the rest of the ugliness of this macro comes from accounting
+ * for the fact that wraparound can occur and the fact that a timestamp of
+ * 0 must be special-cased since it means older than anything else.
+ *
+ * Note that this is NOT an equivalent for time1 <= time2; if that's what
+ * you need then you'll need to swap the order of the arguments and negate
+ * the result.
+ */
+#define XSERVER_TIME_IS_BEFORE_ASSUMING_REAL_TIMESTAMPS(time1, time2) \
+ ( (( (time1) < (time2) ) && ( (time2) - (time1) < ((guint32)-1)/2 )) || \
+ (( (time1) > (time2) ) && ( (time1) - (time2) > ((guint32)-1)/2 )) \
+ )
+#define XSERVER_TIME_IS_BEFORE(time1, time2) \
+ ( (time1) == 0 || \
+ (XSERVER_TIME_IS_BEFORE_ASSUMING_REAL_TIMESTAMPS(time1, time2) && \
+ (time2) != 0) \
+ )
+
+gboolean meta_display_open (void);
+void meta_display_close (MetaDisplay *display,
+ guint32 timestamp);
+MetaScreen* meta_display_screen_for_x_screen (MetaDisplay *display,
+ Screen *screen);
+MetaScreen* meta_display_screen_for_xwindow (MetaDisplay *display,
+ Window xindow);
+void meta_display_grab (MetaDisplay *display);
+void meta_display_ungrab (MetaDisplay *display);
+
+void meta_display_unmanage_screen (MetaDisplay **display,
+ MetaScreen *screen,
+ guint32 timestamp);
+
+void meta_display_unmanage_windows_for_screen (MetaDisplay *display,
+ MetaScreen *screen,
+ guint32 timestamp);
+
+/* Utility function to compare the stacking of two windows */
+int meta_display_stack_cmp (const void *a,
+ const void *b);
+
+/* A given MetaWindow may have various X windows that "belong"
+ * to it, such as the frame window.
+ */
+MetaWindow* meta_display_lookup_x_window (MetaDisplay *display,
+ Window xwindow);
+void meta_display_register_x_window (MetaDisplay *display,
+ Window *xwindowp,
+ MetaWindow *window);
+void meta_display_unregister_x_window (MetaDisplay *display,
+ Window xwindow);
+/* Return whether the xwindow is a no focus window for any of the screens */
+gboolean meta_display_xwindow_is_a_no_focus_window (MetaDisplay *display,
+ Window xwindow);
+
+GSList* meta_display_list_windows (MetaDisplay *display);
+
+MetaDisplay* meta_display_for_x_display (Display *xdisplay);
+MetaDisplay* meta_get_display (void);
+
+Cursor meta_display_create_x_cursor (MetaDisplay *display,
+ MetaCursor cursor);
+
+void meta_display_set_grab_op_cursor (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaGrabOp op,
+ gboolean change_pointer,
+ Window grab_xwindow,
+ guint32 timestamp);
+
+gboolean meta_display_begin_grab_op (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ MetaGrabOp op,
+ gboolean pointer_already_grabbed,
+ gboolean frame_action,
+ int button,
+ gulong modmask,
+ guint32 timestamp,
+ int root_x,
+ int root_y);
+void meta_display_end_grab_op (MetaDisplay *display,
+ guint32 timestamp);
+
+void meta_display_check_threshold_reached (MetaDisplay *display,
+ int x,
+ int y);
+void meta_display_grab_window_buttons (MetaDisplay *display,
+ Window xwindow);
+void meta_display_ungrab_window_buttons (MetaDisplay *display,
+ Window xwindow);
+
+void meta_display_grab_focus_window_button (MetaDisplay *display,
+ MetaWindow *window);
+void meta_display_ungrab_focus_window_button (MetaDisplay *display,
+ MetaWindow *window);
+
+/* Next two functions are defined in edge-resistance.c */
+void meta_display_compute_resistance_and_snapping_edges (MetaDisplay *display);
+void meta_display_cleanup_edges (MetaDisplay *display);
+
+/* make a request to ensure the event serial has changed */
+void meta_display_increment_event_serial (MetaDisplay *display);
+
+void meta_display_update_active_window_hint (MetaDisplay *display);
+
+guint32 meta_display_get_current_time (MetaDisplay *display);
+guint32 meta_display_get_current_time_roundtrip (MetaDisplay *display);
+
+/* utility goo */
+const char* meta_event_mode_to_string (int m);
+const char* meta_event_detail_to_string (int d);
+
+void meta_display_queue_retheme_all_windows (MetaDisplay *display);
+void meta_display_retheme_all (void);
+
+void meta_display_set_cursor_theme (const char *theme,
+ int size);
+
+void meta_display_ping_window (MetaDisplay *display,
+ MetaWindow *window,
+ guint32 timestamp,
+ MetaWindowPingFunc ping_reply_func,
+ MetaWindowPingFunc ping_timeout_func,
+ void *user_data);
+gboolean meta_display_window_has_pending_pings (MetaDisplay *display,
+ MetaWindow *window);
+
+typedef enum
+{
+ META_TAB_LIST_NORMAL,
+ META_TAB_LIST_DOCKS,
+ META_TAB_LIST_GROUP
+} MetaTabList;
+
+typedef enum
+{
+ META_TAB_SHOW_ICON, /* Alt-Tab mode */
+ META_TAB_SHOW_INSTANTLY /* Alt-Esc mode */
+} MetaTabShowType;
+
+GList* meta_display_get_tab_list (MetaDisplay *display,
+ MetaTabList type,
+ MetaScreen *screen,
+ MetaWorkspace *workspace);
+
+MetaWindow* meta_display_get_tab_next (MetaDisplay *display,
+ MetaTabList type,
+ MetaScreen *screen,
+ MetaWorkspace *workspace,
+ MetaWindow *window,
+ gboolean backward);
+
+MetaWindow* meta_display_get_tab_current (MetaDisplay *display,
+ MetaTabList type,
+ MetaScreen *screen,
+ MetaWorkspace *workspace);
+
+int meta_resize_gravity_from_grab_op (MetaGrabOp op);
+
+gboolean meta_grab_op_is_moving (MetaGrabOp op);
+gboolean meta_grab_op_is_resizing (MetaGrabOp op);
+
+void meta_display_devirtualize_modifiers (MetaDisplay *display,
+ MetaVirtualModifier modifiers,
+ unsigned int *mask);
+
+void meta_display_increment_focus_sentinel (MetaDisplay *display);
+void meta_display_decrement_focus_sentinel (MetaDisplay *display);
+gboolean meta_display_focus_sentinel_clear (MetaDisplay *display);
+
+/* meta_display_set_input_focus_window is like XSetInputFocus, except
+ * that (a) it can't detect timestamps later than the current time,
+ * since Marco isn't part of the XServer, and thus gives erroneous
+ * behavior in this circumstance (so don't do it), (b) it uses
+ * display->last_focus_time since we don't have access to the true
+ * Xserver one, (c) it makes use of display->user_time since checking
+ * whether a window should be allowed to be focused should depend
+ * on user_time events (see bug 167358, comment 15 in particular)
+ */
+void meta_display_set_input_focus_window (MetaDisplay *display,
+ MetaWindow *window,
+ gboolean focus_frame,
+ guint32 timestamp);
+
+/* meta_display_focus_the_no_focus_window is called when the
+ * designated no_focus_window should be focused, but is otherwise the
+ * same as meta_display_set_input_focus_window
+ */
+void meta_display_focus_the_no_focus_window (MetaDisplay *display,
+ MetaScreen *screen,
+ guint32 timestamp);
+
+void meta_display_queue_autoraise_callback (MetaDisplay *display,
+ MetaWindow *window);
+void meta_display_remove_autoraise_callback (MetaDisplay *display);
+
+#endif /* META_DISPLAY_PRIVATE_H */
diff --git a/src/core/display.c b/src/core/display.c
new file mode 100644
index 00000000..59ec9021
--- /dev/null
+++ b/src/core/display.c
@@ -0,0 +1,5355 @@
+/* Marco X display handler */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2002, 2003, 2004 Red Hat, Inc.
+ * Copyright (C) 2003, 2004 Rob Adams
+ * Copyright (C) 2004-2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/**
+ * \file display.c Handles operations on an X display.
+ *
+ * The display is represented as a MetaDisplay struct.
+ */
+
+#include <config.h>
+#include "display-private.h"
+#include "util.h"
+#include "main.h"
+#include "screen-private.h"
+#include "window-private.h"
+#include "window-props.h"
+#include "group-props.h"
+#include "frame-private.h"
+#include "errors.h"
+#include "keybindings.h"
+#include "prefs.h"
+#include "resizepopup.h"
+#include "xprops.h"
+#include "workspace.h"
+#include "bell.h"
+#include "effects.h"
+#include "compositor.h"
+#include <X11/Xatom.h>
+#include <X11/cursorfont.h>
+
+#ifdef HAVE_SOLARIS_XINERAMA
+ #include <X11/extensions/xinerama.h>
+#endif
+
+#ifdef HAVE_XFREE_XINERAMA
+ #include <X11/extensions/Xinerama.h>
+#endif
+
+#ifdef HAVE_RANDR
+ #include <X11/extensions/Xrandr.h>
+#endif
+
+#ifdef HAVE_SHAPE
+ #include <X11/extensions/shape.h>
+#endif
+
+#ifdef HAVE_RENDER
+ #include <X11/extensions/Xrender.h>
+#endif
+
+#ifdef HAVE_XKB
+ #include <X11/XKBlib.h>
+#endif
+
+#ifdef HAVE_XCURSOR
+ #include <X11/Xcursor/Xcursor.h>
+#endif
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ #include <X11/extensions/Xcomposite.h>
+ #include <X11/extensions/Xdamage.h>
+ #include <X11/extensions/Xfixes.h>
+ #include <gtk/gtk.h>
+#endif
+
+#include <string.h>
+
+#define GRAB_OP_IS_WINDOW_SWITCH(g) \
+ (g == META_GRAB_OP_KEYBOARD_TABBING_NORMAL || \
+ g == META_GRAB_OP_KEYBOARD_TABBING_DOCK || \
+ g == META_GRAB_OP_KEYBOARD_TABBING_GROUP || \
+ g == META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL || \
+ g == META_GRAB_OP_KEYBOARD_ESCAPING_DOCK || \
+ g == META_GRAB_OP_KEYBOARD_ESCAPING_GROUP)
+
+/**
+ * \defgroup pings Pings
+ *
+ * Sometimes we want to see whether a window is responding,
+ * so we send it a "ping" message and see whether it sends us back a "pong"
+ * message within a reasonable time. Here we have a system which lets us
+ * nominate one function to be called if we get the pong in time and another
+ * function if we don't. The system is rather more complicated than it needs
+ * to be, since we only ever use it to destroy windows which are asked to
+ * close themselves and don't do so within a reasonable amount of time, and
+ * therefore we always use the same callbacks. It's possible that we might
+ * use it for other things in future, or on the other hand we might decide
+ * that we're never going to do so and simplify it a bit.
+ */
+
+/**
+ * Describes a ping on a window. When we send a ping to a window, we build
+ * one of these structs, and it eventually gets passed to the timeout function
+ * or to the function which handles the response from the window. If the window
+ * does or doesn't respond to the ping, we use this information to deal with
+ * these facts; we have a handler function for each.
+ *
+ * \ingroup pings
+ */
+typedef struct
+{
+ MetaDisplay *display;
+ Window xwindow;
+ guint32 timestamp;
+ MetaWindowPingFunc ping_reply_func;
+ MetaWindowPingFunc ping_timeout_func;
+ void *user_data;
+ guint ping_timeout_id;
+} MetaPingData;
+
+typedef struct
+{
+ MetaDisplay *display;
+ Window xwindow;
+} MetaAutoRaiseData;
+
+/**
+ * The display we're managing. This is a singleton object. (Historically,
+ * this was a list of displays, but there was never any way to add more
+ * than one element to it.) The goofy name is because we don't want it
+ * to shadow the parameter in its object methods.
+ */
+static MetaDisplay *the_display = NULL;
+
+#ifdef WITH_VERBOSE_MODE
+static void meta_spew_event (MetaDisplay *display,
+ XEvent *event);
+#endif
+
+static gboolean event_callback (XEvent *event,
+ gpointer data);
+static Window event_get_modified_window (MetaDisplay *display,
+ XEvent *event);
+static guint32 event_get_time (MetaDisplay *display,
+ XEvent *event);
+static void process_request_frame_extents (MetaDisplay *display,
+ XEvent *event);
+static void process_pong_message (MetaDisplay *display,
+ XEvent *event);
+static void process_selection_request (MetaDisplay *display,
+ XEvent *event);
+static void process_selection_clear (MetaDisplay *display,
+ XEvent *event);
+
+static void update_window_grab_modifiers (MetaDisplay *display);
+
+static void prefs_changed_callback (MetaPreference pref,
+ void *data);
+
+static void sanity_check_timestamps (MetaDisplay *display,
+ guint32 known_good_timestamp);
+
+MetaGroup* get_focussed_group (MetaDisplay *display);
+
+/**
+ * Destructor for MetaPingData structs. Will destroy the
+ * event source for the struct as well.
+ *
+ * \ingroup pings
+ */
+static void
+ping_data_free (MetaPingData *ping_data)
+{
+ /* Remove the timeout */
+ if (ping_data->ping_timeout_id != 0)
+ g_source_remove (ping_data->ping_timeout_id);
+
+ g_free (ping_data);
+}
+
+/**
+ * Frees every pending ping structure for the given X window on the
+ * given display. This means that we also destroy the timeouts.
+ *
+ * \param display The display the window appears on
+ * \param xwindow The X ID of the window whose pings we should remove
+ *
+ * \ingroup pings
+ *
+ */
+static void
+remove_pending_pings_for_window (MetaDisplay *display, Window xwindow)
+{
+ GSList *tmp;
+ GSList *dead;
+
+ /* could obviously be more efficient, don't care */
+
+ /* build list to be removed */
+ dead = NULL;
+ for (tmp = display->pending_pings; tmp; tmp = tmp->next)
+ {
+ MetaPingData *ping_data = tmp->data;
+
+ if (ping_data->xwindow == xwindow)
+ dead = g_slist_prepend (dead, ping_data);
+ }
+
+ /* remove what we found */
+ for (tmp = dead; tmp; tmp = tmp->next)
+ {
+ MetaPingData *ping_data = tmp->data;
+
+ display->pending_pings = g_slist_remove (display->pending_pings, ping_data);
+ ping_data_free (ping_data);
+ }
+
+ g_slist_free (dead);
+}
+
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+static void
+sn_error_trap_push (SnDisplay *sn_display,
+ Display *xdisplay)
+{
+ MetaDisplay *display;
+ display = meta_display_for_x_display (xdisplay);
+ if (display != NULL)
+ meta_error_trap_push (display);
+}
+
+static void
+sn_error_trap_pop (SnDisplay *sn_display,
+ Display *xdisplay)
+{
+ MetaDisplay *display;
+ display = meta_display_for_x_display (xdisplay);
+ if (display != NULL)
+ meta_error_trap_pop (display, FALSE);
+}
+#endif
+
+static void
+enable_compositor (MetaDisplay *display,
+ gboolean composite_windows)
+{
+ GSList *list;
+
+ if (!META_DISPLAY_HAS_COMPOSITE (display) ||
+ !META_DISPLAY_HAS_DAMAGE (display) ||
+ !META_DISPLAY_HAS_XFIXES (display) ||
+ !META_DISPLAY_HAS_RENDER (display))
+ {
+ meta_warning (_("Missing %s extension required for compositing"),
+ !META_DISPLAY_HAS_COMPOSITE (display) ? "composite" :
+ !META_DISPLAY_HAS_DAMAGE (display) ? "damage" :
+ !META_DISPLAY_HAS_XFIXES (display) ? "xfixes" : "render");
+ return;
+ }
+
+ if (!display->compositor)
+ display->compositor = meta_compositor_new (display);
+
+ if (!display->compositor)
+ return;
+
+ for (list = display->screens; list != NULL; list = list->next)
+ {
+ MetaScreen *screen = list->data;
+
+ meta_compositor_manage_screen (screen->display->compositor,
+ screen);
+
+ if (composite_windows)
+ meta_screen_composite_all_windows (screen);
+ }
+}
+
+static void
+disable_compositor (MetaDisplay *display)
+{
+ GSList *list;
+
+ if (!display->compositor)
+ return;
+
+ for (list = display->screens; list != NULL; list = list->next)
+ {
+ MetaScreen *screen = list->data;
+
+ meta_compositor_unmanage_screen (screen->display->compositor,
+ screen);
+ }
+
+ meta_compositor_destroy (display->compositor);
+ display->compositor = NULL;
+}
+
+/**
+ * Opens a new display, sets it up, initialises all the X extensions
+ * we will need, and adds it to the list of displays.
+ *
+ * \return True if the display was opened successfully, and False
+ * otherwise-- that is, if the display doesn't exist or it already
+ * has a window manager.
+ *
+ * \ingroup main
+ */
+gboolean
+meta_display_open (void)
+{
+ Display *xdisplay;
+ GSList *screens;
+ GSList *tmp;
+ int i;
+ guint32 timestamp;
+
+ /* A list of all atom names, so that we can intern them in one go. */
+ char *atom_names[] = {
+#define item(x) #x,
+#include "atomnames.h"
+#undef item
+ };
+ Atom atoms[G_N_ELEMENTS(atom_names)];
+
+ meta_verbose ("Opening display '%s'\n", XDisplayName (NULL));
+
+ xdisplay = meta_ui_get_display ();
+
+ if (xdisplay == NULL)
+ {
+ meta_warning (_("Failed to open X Window System display '%s'\n"),
+ XDisplayName (NULL));
+ return FALSE;
+ }
+
+ if (meta_is_syncing ())
+ XSynchronize (xdisplay, True);
+
+ g_assert (the_display == NULL);
+ the_display = g_new (MetaDisplay, 1);
+
+ the_display->closing = 0;
+
+ /* here we use XDisplayName which is what the user
+ * probably put in, vs. DisplayString(display) which is
+ * canonicalized by XOpenDisplay()
+ */
+ the_display->name = g_strdup (XDisplayName (NULL));
+ the_display->xdisplay = xdisplay;
+ the_display->error_trap_synced_at_last_pop = TRUE;
+ the_display->error_traps = 0;
+ the_display->error_trap_handler = NULL;
+ the_display->server_grab_count = 0;
+ the_display->display_opening = TRUE;
+
+ the_display->pending_pings = NULL;
+ the_display->autoraise_timeout_id = 0;
+ the_display->autoraise_window = NULL;
+ the_display->focus_window = NULL;
+ the_display->expected_focus_window = NULL;
+ the_display->grab_old_window_stacking = NULL;
+
+ the_display->mouse_mode = TRUE; /* Only relevant for mouse or sloppy focus */
+ the_display->allow_terminal_deactivation = TRUE; /* Only relevant for when a
+ terminal has the focus */
+
+#ifdef HAVE_XSYNC
+ the_display->grab_sync_request_alarm = None;
+#endif
+
+ /* FIXME copy the checks from GDK probably */
+ the_display->static_gravity_works = g_getenv ("MARCO_USE_STATIC_GRAVITY") != NULL;
+
+ meta_bell_init (the_display);
+
+ meta_display_init_keys (the_display);
+
+ update_window_grab_modifiers (the_display);
+
+ meta_prefs_add_listener (prefs_changed_callback, the_display);
+
+ meta_verbose ("Creating %d atoms\n", (int) G_N_ELEMENTS (atom_names));
+ XInternAtoms (the_display->xdisplay, atom_names, G_N_ELEMENTS (atom_names),
+ False, atoms);
+ {
+ int i = 0;
+#define item(x) the_display->atom_##x = atoms[i++];
+#include "atomnames.h"
+#undef item
+ }
+
+ the_display->prop_hooks = NULL;
+ meta_display_init_window_prop_hooks (the_display);
+ the_display->group_prop_hooks = NULL;
+ meta_display_init_group_prop_hooks (the_display);
+
+ /* Offscreen unmapped window used for _NET_SUPPORTING_WM_CHECK,
+ * created in screen_new
+ */
+ the_display->leader_window = None;
+ the_display->timestamp_pinging_window = None;
+
+ the_display->xinerama_cache_invalidated = TRUE;
+
+ the_display->groups_by_leader = NULL;
+
+ the_display->window_with_menu = NULL;
+ the_display->window_menu = NULL;
+
+ the_display->screens = NULL;
+ the_display->active_screen = NULL;
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ the_display->sn_display = sn_display_new (the_display->xdisplay,
+ sn_error_trap_push,
+ sn_error_trap_pop);
+#endif
+
+ the_display->events = NULL;
+
+ /* Get events */
+ meta_ui_add_event_func (the_display->xdisplay,
+ event_callback,
+ the_display);
+
+ the_display->window_ids = g_hash_table_new (meta_unsigned_long_hash,
+ meta_unsigned_long_equal);
+
+ i = 0;
+ while (i < N_IGNORED_SERIALS)
+ {
+ the_display->ignored_serials[i] = 0;
+ ++i;
+ }
+ the_display->ungrab_should_not_cause_focus_window = None;
+
+ the_display->current_time = CurrentTime;
+ the_display->sentinel_counter = 0;
+
+ the_display->grab_resize_timeout_id = 0;
+ the_display->grab_have_keyboard = FALSE;
+
+#ifdef HAVE_XKB
+ the_display->last_bell_time = 0;
+#endif
+
+ the_display->grab_op = META_GRAB_OP_NONE;
+ the_display->grab_wireframe_active = FALSE;
+ the_display->grab_window = NULL;
+ the_display->grab_screen = NULL;
+ the_display->grab_resize_popup = NULL;
+
+ the_display->grab_edge_resistance_data = NULL;
+
+#ifdef HAVE_XSYNC
+ {
+ int major, minor;
+
+ the_display->have_xsync = FALSE;
+
+ the_display->xsync_error_base = 0;
+ the_display->xsync_event_base = 0;
+
+ /* I don't think we really have to fill these in */
+ major = SYNC_MAJOR_VERSION;
+ minor = SYNC_MINOR_VERSION;
+
+ if (!XSyncQueryExtension (the_display->xdisplay,
+ &the_display->xsync_event_base,
+ &the_display->xsync_error_base) ||
+ !XSyncInitialize (the_display->xdisplay,
+ &major, &minor))
+ {
+ the_display->xsync_error_base = 0;
+ the_display->xsync_event_base = 0;
+ }
+ else
+ the_display->have_xsync = TRUE;
+
+ meta_verbose ("Attempted to init Xsync, found version %d.%d error base %d event base %d\n",
+ major, minor,
+ the_display->xsync_error_base,
+ the_display->xsync_event_base);
+ }
+#else /* HAVE_XSYNC */
+ meta_verbose ("Not compiled with Xsync support\n");
+#endif /* !HAVE_XSYNC */
+
+
+#ifdef HAVE_SHAPE
+ {
+ the_display->have_shape = FALSE;
+
+ the_display->shape_error_base = 0;
+ the_display->shape_event_base = 0;
+
+ if (!XShapeQueryExtension (the_display->xdisplay,
+ &the_display->shape_event_base,
+ &the_display->shape_error_base))
+ {
+ the_display->shape_error_base = 0;
+ the_display->shape_event_base = 0;
+ }
+ else
+ the_display->have_shape = TRUE;
+
+ meta_verbose ("Attempted to init Shape, found error base %d event base %d\n",
+ the_display->shape_error_base,
+ the_display->shape_event_base);
+ }
+#else /* HAVE_SHAPE */
+ meta_verbose ("Not compiled with Shape support\n");
+#endif /* !HAVE_SHAPE */
+
+#ifdef HAVE_RENDER
+ {
+ the_display->have_render = FALSE;
+
+ the_display->render_error_base = 0;
+ the_display->render_event_base = 0;
+
+ if (!XRenderQueryExtension (the_display->xdisplay,
+ &the_display->render_event_base,
+ &the_display->render_error_base))
+ {
+ the_display->render_error_base = 0;
+ the_display->render_event_base = 0;
+ }
+ else
+ the_display->have_render = TRUE;
+
+ meta_verbose ("Attempted to init Render, found error base %d event base %d\n",
+ the_display->render_error_base,
+ the_display->render_event_base);
+ }
+#else /* HAVE_RENDER */
+ meta_verbose ("Not compiled with Render support\n");
+#endif /* !HAVE_RENDER */
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ {
+ the_display->have_composite = FALSE;
+
+ the_display->composite_error_base = 0;
+ the_display->composite_event_base = 0;
+
+ if (!XCompositeQueryExtension (the_display->xdisplay,
+ &the_display->composite_event_base,
+ &the_display->composite_error_base))
+ {
+ the_display->composite_error_base = 0;
+ the_display->composite_event_base = 0;
+ }
+ else
+ {
+ the_display->composite_major_version = 0;
+ the_display->composite_minor_version = 0;
+ if (XCompositeQueryVersion (the_display->xdisplay,
+ &the_display->composite_major_version,
+ &the_display->composite_minor_version))
+ {
+ the_display->have_composite = TRUE;
+ }
+ else
+ {
+ the_display->composite_major_version = 0;
+ the_display->composite_minor_version = 0;
+ }
+ }
+
+ meta_verbose ("Attempted to init Composite, found error base %d event base %d "
+ "extn ver %d %d\n",
+ the_display->composite_error_base,
+ the_display->composite_event_base,
+ the_display->composite_major_version,
+ the_display->composite_minor_version);
+
+ the_display->have_damage = FALSE;
+
+ the_display->damage_error_base = 0;
+ the_display->damage_event_base = 0;
+
+ if (!XDamageQueryExtension (the_display->xdisplay,
+ &the_display->damage_event_base,
+ &the_display->damage_error_base))
+ {
+ the_display->damage_error_base = 0;
+ the_display->damage_event_base = 0;
+ }
+ else
+ the_display->have_damage = TRUE;
+
+ meta_verbose ("Attempted to init Damage, found error base %d event base %d\n",
+ the_display->damage_error_base,
+ the_display->damage_event_base);
+
+ the_display->have_xfixes = FALSE;
+
+ the_display->xfixes_error_base = 0;
+ the_display->xfixes_event_base = 0;
+
+ if (!XFixesQueryExtension (the_display->xdisplay,
+ &the_display->xfixes_event_base,
+ &the_display->xfixes_error_base))
+ {
+ the_display->xfixes_error_base = 0;
+ the_display->xfixes_event_base = 0;
+ }
+ else
+ the_display->have_xfixes = TRUE;
+
+ meta_verbose ("Attempted to init XFixes, found error base %d event base %d\n",
+ the_display->xfixes_error_base,
+ the_display->xfixes_event_base);
+ }
+#else /* HAVE_COMPOSITE_EXTENSIONS */
+ meta_verbose ("Not compiled with Composite support\n");
+#endif /* !HAVE_COMPOSITE_EXTENSIONS */
+
+#ifdef HAVE_XCURSOR
+ {
+ XcursorSetTheme (the_display->xdisplay, meta_prefs_get_cursor_theme ());
+ XcursorSetDefaultSize (the_display->xdisplay, meta_prefs_get_cursor_size ());
+ }
+#else /* HAVE_XCURSOR */
+ meta_verbose ("Not compiled with Xcursor support\n");
+#endif /* !HAVE_XCURSOR */
+
+ /* Create the leader window here. Set its properties and
+ * use the timestamp from one of the PropertyNotify events
+ * that will follow.
+ */
+ {
+ gulong data[1];
+ XEvent event;
+
+ /* We only care about the PropertyChangeMask in the next 30 or so lines of
+ * code. Note that gdk will at some point unset the PropertyChangeMask for
+ * this window, so we can't rely on it still being set later. See bug
+ * 354213 for details.
+ */
+ the_display->leader_window =
+ meta_create_offscreen_window (the_display->xdisplay,
+ DefaultRootWindow (the_display->xdisplay),
+ PropertyChangeMask);
+
+ meta_prop_set_utf8_string_hint (the_display,
+ the_display->leader_window,
+ the_display->atom__NET_WM_NAME,
+ "Marco");
+
+ meta_prop_set_utf8_string_hint (the_display,
+ the_display->leader_window,
+ the_display->atom__MARCO_VERSION,
+ VERSION);
+
+ data[0] = the_display->leader_window;
+ XChangeProperty (the_display->xdisplay,
+ the_display->leader_window,
+ the_display->atom__NET_SUPPORTING_WM_CHECK,
+ XA_WINDOW,
+ 32, PropModeReplace, (guchar*) data, 1);
+
+ XWindowEvent (the_display->xdisplay,
+ the_display->leader_window,
+ PropertyChangeMask,
+ &event);
+
+ timestamp = event.xproperty.time;
+
+ /* Make it painfully clear that we can't rely on PropertyNotify events on
+ * this window, as per bug 354213.
+ */
+ XSelectInput(the_display->xdisplay,
+ the_display->leader_window,
+ NoEventMask);
+ }
+
+ /* Make a little window used only for pinging the server for timestamps; note
+ * that meta_create_offscreen_window already selects for PropertyChangeMask.
+ */
+ the_display->timestamp_pinging_window =
+ meta_create_offscreen_window (the_display->xdisplay,
+ DefaultRootWindow (the_display->xdisplay),
+ PropertyChangeMask);
+
+ the_display->last_focus_time = timestamp;
+ the_display->last_user_time = timestamp;
+ the_display->compositor = NULL;
+
+ screens = NULL;
+
+ i = 0;
+ while (i < ScreenCount (xdisplay))
+ {
+ MetaScreen *screen;
+
+ screen = meta_screen_new (the_display, i, timestamp);
+
+ if (screen)
+ screens = g_slist_prepend (screens, screen);
+ ++i;
+ }
+
+ the_display->screens = screens;
+
+ if (screens == NULL)
+ {
+ /* This would typically happen because all the screens already
+ * have window managers.
+ */
+ meta_display_close (the_display, timestamp);
+ return FALSE;
+ }
+
+ /* We don't composite the windows here because they will be composited
+ faster with the call to meta_screen_manage_all_windows further down
+ the code */
+ if (meta_prefs_get_compositing_manager ())
+ enable_compositor (the_display, FALSE);
+
+ meta_display_grab (the_display);
+
+ /* Now manage all existing windows */
+ tmp = the_display->screens;
+ while (tmp != NULL)
+ {
+ MetaScreen *screen = tmp->data;
+
+ meta_screen_manage_all_windows (screen);
+
+ tmp = tmp->next;
+ }
+
+ {
+ Window focus;
+ int ret_to;
+
+ /* kinda bogus because GetInputFocus has no possible errors */
+ meta_error_trap_push (the_display);
+
+ /* FIXME: This is totally broken; see comment 9 of bug 88194 about this */
+ focus = None;
+ ret_to = RevertToPointerRoot;
+ XGetInputFocus (the_display->xdisplay, &focus, &ret_to);
+
+ /* Force a new FocusIn (does this work?) */
+
+ /* Use the same timestamp that was passed to meta_screen_new(),
+ * as it is the most recent timestamp.
+ */
+ if (focus == None || focus == PointerRoot)
+ /* Just focus the no_focus_window on the first screen */
+ meta_display_focus_the_no_focus_window (the_display,
+ the_display->screens->data,
+ timestamp);
+ else
+ {
+ MetaWindow * window;
+ window = meta_display_lookup_x_window (the_display, focus);
+ if (window)
+ meta_display_set_input_focus_window (the_display, window, FALSE, timestamp);
+ else
+ /* Just focus the no_focus_window on the first screen */
+ meta_display_focus_the_no_focus_window (the_display,
+ the_display->screens->data,
+ timestamp);
+ }
+
+ meta_error_trap_pop (the_display, FALSE);
+ }
+
+ meta_display_ungrab (the_display);
+
+ /* Done opening new display */
+ the_display->display_opening = FALSE;
+
+ return TRUE;
+}
+
+static void
+listify_func (gpointer key, gpointer value, gpointer data)
+{
+ GSList **listp;
+
+ listp = data;
+ *listp = g_slist_prepend (*listp, value);
+}
+
+static gint
+ptrcmp (gconstpointer a, gconstpointer b)
+{
+ if (a < b)
+ return -1;
+ else if (a > b)
+ return 1;
+ else
+ return 0;
+}
+
+GSList*
+meta_display_list_windows (MetaDisplay *display)
+{
+ GSList *winlist;
+ GSList *tmp;
+ GSList *prev;
+
+ winlist = NULL;
+ g_hash_table_foreach (display->window_ids,
+ listify_func,
+ &winlist);
+
+ /* Uniquify the list, since both frame windows and plain
+ * windows are in the hash
+ */
+ winlist = g_slist_sort (winlist, ptrcmp);
+
+ prev = NULL;
+ tmp = winlist;
+ while (tmp != NULL)
+ {
+ GSList *next;
+
+ next = tmp->next;
+
+ if (next &&
+ next->data == tmp->data)
+ {
+ /* Delete tmp from list */
+
+ if (prev)
+ prev->next = next;
+
+ if (tmp == winlist)
+ winlist = next;
+
+ g_slist_free_1 (tmp);
+
+ /* leave prev unchanged */
+ }
+ else
+ {
+ prev = tmp;
+ }
+
+ tmp = next;
+ }
+
+ return winlist;
+}
+
+void
+meta_display_close (MetaDisplay *display,
+ guint32 timestamp)
+{
+ GSList *tmp;
+
+ g_assert (display != NULL);
+
+ if (display->closing != 0)
+ {
+ /* The display's already been closed. */
+ return;
+ }
+
+ if (display->error_traps > 0)
+ meta_bug ("Display closed with error traps pending\n");
+
+ display->closing += 1;
+
+ meta_prefs_remove_listener (prefs_changed_callback, display);
+
+ meta_display_remove_autoraise_callback (display);
+
+ if (display->grab_old_window_stacking)
+ g_list_free (display->grab_old_window_stacking);
+
+ /* Stop caring about events */
+ meta_ui_remove_event_func (display->xdisplay,
+ event_callback,
+ display);
+
+ /* Free all screens */
+ tmp = display->screens;
+ while (tmp != NULL)
+ {
+ MetaScreen *screen = tmp->data;
+ meta_screen_free (screen, timestamp);
+ tmp = tmp->next;
+ }
+
+ g_slist_free (display->screens);
+ display->screens = NULL;
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ if (display->sn_display)
+ {
+ sn_display_unref (display->sn_display);
+ display->sn_display = NULL;
+ }
+#endif
+
+ /* Must be after all calls to meta_window_free() since they
+ * unregister windows
+ */
+ g_hash_table_destroy (display->window_ids);
+
+ if (display->leader_window != None)
+ XDestroyWindow (display->xdisplay, display->leader_window);
+
+ XFlush (display->xdisplay);
+
+ meta_display_free_window_prop_hooks (display);
+ meta_display_free_group_prop_hooks (display);
+
+ g_free (display->name);
+
+ meta_display_shutdown_keys (display);
+
+ if (display->compositor)
+ meta_compositor_destroy (display->compositor);
+
+ g_free (display);
+ display = NULL;
+
+ meta_quit (META_EXIT_SUCCESS);
+}
+
+MetaScreen*
+meta_display_screen_for_root (MetaDisplay *display,
+ Window xroot)
+{
+ GSList *tmp;
+
+ tmp = display->screens;
+ while (tmp != NULL)
+ {
+ MetaScreen *screen = tmp->data;
+
+ if (xroot == screen->xroot)
+ return screen;
+
+ tmp = tmp->next;
+ }
+
+ return NULL;
+}
+
+MetaScreen*
+meta_display_screen_for_xwindow (MetaDisplay *display,
+ Window xwindow)
+{
+ XWindowAttributes attr;
+ int result;
+
+ meta_error_trap_push (display);
+ attr.screen = NULL;
+ result = XGetWindowAttributes (display->xdisplay, xwindow, &attr);
+ meta_error_trap_pop (display, TRUE);
+
+ /* Note, XGetWindowAttributes is on all kinds of crack
+ * and returns 1 on success 0 on failure, rather than Success
+ * on success.
+ */
+ if (result == 0 || attr.screen == NULL)
+ return NULL;
+
+ return meta_display_screen_for_x_screen (display, attr.screen);
+}
+
+MetaScreen*
+meta_display_screen_for_x_screen (MetaDisplay *display,
+ Screen *xscreen)
+{
+ GSList *tmp;
+
+ tmp = display->screens;
+ while (tmp != NULL)
+ {
+ MetaScreen *screen = tmp->data;
+
+ if (xscreen == screen->xscreen)
+ return screen;
+
+ tmp = tmp->next;
+ }
+
+ return NULL;
+}
+
+/* Grab/ungrab routines taken from fvwm */
+void
+meta_display_grab (MetaDisplay *display)
+{
+ if (display->server_grab_count == 0)
+ {
+ XGrabServer (display->xdisplay);
+ }
+ display->server_grab_count += 1;
+ meta_verbose ("Grabbing display, grab count now %d\n",
+ display->server_grab_count);
+}
+
+void
+meta_display_ungrab (MetaDisplay *display)
+{
+ if (display->server_grab_count == 0)
+ meta_bug ("Ungrabbed non-grabbed server\n");
+
+ display->server_grab_count -= 1;
+ if (display->server_grab_count == 0)
+ {
+ /* FIXME we want to purge all pending "queued" stuff
+ * at this point, such as window hide/show
+ */
+ XUngrabServer (display->xdisplay);
+ XFlush (display->xdisplay);
+ }
+
+ meta_verbose ("Ungrabbing display, grab count now %d\n",
+ display->server_grab_count);
+}
+
+/**
+ * Returns the singleton MetaDisplay if "xdisplay" matches the X display it's
+ * managing; otherwise gives a warning and returns NULL. When we were claiming
+ * to be able to manage multiple displays, this was supposed to find the
+ * display out of the list which matched that display. Now it's merely an
+ * extra sanity check.
+ *
+ * \param xdisplay An X display
+ * \return The singleton X display, or NULL if "xdisplay" isn't the one
+ * we're managing.
+ */
+MetaDisplay*
+meta_display_for_x_display (Display *xdisplay)
+{
+ if (the_display->xdisplay == xdisplay)
+ return the_display;
+
+ meta_warning ("Could not find display for X display %p, probably going to crash\n",
+ xdisplay);
+
+ return NULL;
+}
+
+/**
+ * Accessor for the singleton MetaDisplay.
+ *
+ * \return The only MetaDisplay there is. This can be NULL, but only
+ * during startup.
+ */
+MetaDisplay*
+meta_get_display (void)
+{
+ return the_display;
+}
+
+#ifdef WITH_VERBOSE_MODE
+static gboolean dump_events = TRUE;
+#endif
+
+static gboolean
+grab_op_is_mouse_only (MetaGrabOp op)
+{
+ switch (op)
+ {
+ case META_GRAB_OP_MOVING:
+ case META_GRAB_OP_RESIZING_SE:
+ case META_GRAB_OP_RESIZING_S:
+ case META_GRAB_OP_RESIZING_SW:
+ case META_GRAB_OP_RESIZING_N:
+ case META_GRAB_OP_RESIZING_NE:
+ case META_GRAB_OP_RESIZING_NW:
+ case META_GRAB_OP_RESIZING_W:
+ case META_GRAB_OP_RESIZING_E:
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+static gboolean
+grab_op_is_mouse (MetaGrabOp op)
+{
+ switch (op)
+ {
+ case META_GRAB_OP_MOVING:
+ case META_GRAB_OP_RESIZING_SE:
+ case META_GRAB_OP_RESIZING_S:
+ case META_GRAB_OP_RESIZING_SW:
+ case META_GRAB_OP_RESIZING_N:
+ case META_GRAB_OP_RESIZING_NE:
+ case META_GRAB_OP_RESIZING_NW:
+ case META_GRAB_OP_RESIZING_W:
+ case META_GRAB_OP_RESIZING_E:
+ case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
+ case META_GRAB_OP_KEYBOARD_RESIZING_S:
+ case META_GRAB_OP_KEYBOARD_RESIZING_N:
+ case META_GRAB_OP_KEYBOARD_RESIZING_W:
+ case META_GRAB_OP_KEYBOARD_RESIZING_E:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ case META_GRAB_OP_KEYBOARD_MOVING:
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+static gboolean
+grab_op_is_keyboard (MetaGrabOp op)
+{
+ switch (op)
+ {
+ case META_GRAB_OP_KEYBOARD_MOVING:
+ case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
+ case META_GRAB_OP_KEYBOARD_RESIZING_S:
+ case META_GRAB_OP_KEYBOARD_RESIZING_N:
+ case META_GRAB_OP_KEYBOARD_RESIZING_W:
+ case META_GRAB_OP_KEYBOARD_RESIZING_E:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ case META_GRAB_OP_KEYBOARD_TABBING_NORMAL:
+ case META_GRAB_OP_KEYBOARD_TABBING_DOCK:
+ case META_GRAB_OP_KEYBOARD_TABBING_GROUP:
+ case META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL:
+ case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK:
+ case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP:
+ case META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING:
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+gboolean
+meta_grab_op_is_resizing (MetaGrabOp op)
+{
+ switch (op)
+ {
+ case META_GRAB_OP_RESIZING_SE:
+ case META_GRAB_OP_RESIZING_S:
+ case META_GRAB_OP_RESIZING_SW:
+ case META_GRAB_OP_RESIZING_N:
+ case META_GRAB_OP_RESIZING_NE:
+ case META_GRAB_OP_RESIZING_NW:
+ case META_GRAB_OP_RESIZING_W:
+ case META_GRAB_OP_RESIZING_E:
+ case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
+ case META_GRAB_OP_KEYBOARD_RESIZING_S:
+ case META_GRAB_OP_KEYBOARD_RESIZING_N:
+ case META_GRAB_OP_KEYBOARD_RESIZING_W:
+ case META_GRAB_OP_KEYBOARD_RESIZING_E:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+gboolean
+meta_grab_op_is_moving (MetaGrabOp op)
+{
+ switch (op)
+ {
+ case META_GRAB_OP_MOVING:
+ case META_GRAB_OP_KEYBOARD_MOVING:
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+/* Get time of current event, or CurrentTime if none. */
+guint32
+meta_display_get_current_time (MetaDisplay *display)
+{
+ return display->current_time;
+}
+
+/* Get a timestamp, even if it means a roundtrip */
+guint32
+meta_display_get_current_time_roundtrip (MetaDisplay *display)
+{
+ guint32 timestamp;
+
+ timestamp = meta_display_get_current_time (display);
+ if (timestamp == CurrentTime)
+ {
+ XEvent property_event;
+
+ /* Using the property XA_PRIMARY because it's safe; nothing
+ * would use it as a property. The type doesn't matter.
+ */
+ XChangeProperty (display->xdisplay,
+ display->timestamp_pinging_window,
+ XA_PRIMARY, XA_STRING, 8,
+ PropModeAppend, NULL, 0);
+ XWindowEvent (display->xdisplay,
+ display->timestamp_pinging_window,
+ PropertyChangeMask,
+ &property_event);
+ timestamp = property_event.xproperty.time;
+ }
+
+ sanity_check_timestamps (display, timestamp);
+
+ return timestamp;
+}
+
+static void
+add_ignored_serial (MetaDisplay *display,
+ unsigned long serial)
+{
+ int i;
+
+ /* don't add the same serial more than once */
+ if (display->ignored_serials[N_IGNORED_SERIALS-1] == serial)
+ return;
+
+ /* shift serials to the left */
+ i = 0;
+ while (i < (N_IGNORED_SERIALS - 1))
+ {
+ display->ignored_serials[i] = display->ignored_serials[i+1];
+ ++i;
+ }
+ /* put new one on the end */
+ display->ignored_serials[i] = serial;
+}
+
+static gboolean
+serial_is_ignored (MetaDisplay *display,
+ unsigned long serial)
+{
+ int i;
+
+ i = 0;
+ while (i < N_IGNORED_SERIALS)
+ {
+ if (display->ignored_serials[i] == serial)
+ return TRUE;
+ ++i;
+ }
+ return FALSE;
+}
+
+static void
+reset_ignores (MetaDisplay *display)
+{
+ int i;
+
+ i = 0;
+ while (i < N_IGNORED_SERIALS)
+ {
+ display->ignored_serials[i] = 0;
+ ++i;
+ }
+
+ display->ungrab_should_not_cause_focus_window = None;
+}
+
+static gboolean
+window_raise_with_delay_callback (void *data)
+{
+ MetaWindow *window;
+ MetaAutoRaiseData *auto_raise;
+
+ auto_raise = data;
+
+ meta_topic (META_DEBUG_FOCUS,
+ "In autoraise callback for window 0x%lx\n",
+ auto_raise->xwindow);
+
+ auto_raise->display->autoraise_timeout_id = 0;
+ auto_raise->display->autoraise_window = NULL;
+
+ window = meta_display_lookup_x_window (auto_raise->display,
+ auto_raise->xwindow);
+
+ if (window == NULL)
+ return FALSE;
+
+ /* If we aren't already on top, check whether the pointer is inside
+ * the window and raise the window if so.
+ */
+ if (meta_stack_get_top (window->screen->stack) != window)
+ {
+ int x, y, root_x, root_y;
+ Window root, child;
+ unsigned int mask;
+ gboolean same_screen;
+ gboolean point_in_window;
+
+ meta_error_trap_push (window->display);
+ same_screen = XQueryPointer (window->display->xdisplay,
+ window->xwindow,
+ &root, &child,
+ &root_x, &root_y, &x, &y, &mask);
+ meta_error_trap_pop (window->display, TRUE);
+
+ point_in_window =
+ (window->frame && POINT_IN_RECT (root_x, root_y, window->frame->rect)) ||
+ (window->frame == NULL && POINT_IN_RECT (root_x, root_y, window->rect));
+ if (same_screen && point_in_window)
+ meta_window_raise (window);
+ else
+ meta_topic (META_DEBUG_FOCUS,
+ "Pointer not inside window, not raising %s\n",
+ window->desc);
+ }
+
+ return FALSE;
+}
+
+void
+meta_display_queue_autoraise_callback (MetaDisplay *display,
+ MetaWindow *window)
+{
+ MetaAutoRaiseData *auto_raise_data;
+
+ meta_topic (META_DEBUG_FOCUS,
+ "Queuing an autoraise timeout for %s with delay %d\n",
+ window->desc,
+ meta_prefs_get_auto_raise_delay ());
+
+ auto_raise_data = g_new (MetaAutoRaiseData, 1);
+ auto_raise_data->display = window->display;
+ auto_raise_data->xwindow = window->xwindow;
+
+ if (display->autoraise_timeout_id != 0)
+ g_source_remove (display->autoraise_timeout_id);
+
+ display->autoraise_timeout_id =
+ g_timeout_add_full (G_PRIORITY_DEFAULT,
+ meta_prefs_get_auto_raise_delay (),
+ window_raise_with_delay_callback,
+ auto_raise_data,
+ g_free);
+ display->autoraise_window = window;
+}
+
+#if 0
+static void
+handle_net_restack_window (MetaDisplay* display,
+ XEvent *event)
+{
+ MetaWindow *window;
+
+ window = meta_display_lookup_x_window (display,
+ event->xclient.window);
+
+ if (window)
+ {
+ /* FIXME: The EWMH includes a sibling for the restack request, but we
+ * (stupidly) don't currently support these types of raises.
+ *
+ * Also, unconditionally following these is REALLY stupid--we should
+ * combine this code with the stuff in
+ * meta_window_configure_request() which is smart about whether to
+ * follow the request or do something else (though not smart enough
+ * and is also too stupid to handle the sibling stuff).
+ */
+ switch (event->xclient.data.l[2])
+ {
+ case Above:
+ meta_window_raise (window);
+ break;
+ case Below:
+ meta_window_lower (window);
+ break;
+ case TopIf:
+ case BottomIf:
+ case Opposite:
+ break;
+ }
+ }
+}
+#endif
+
+/* We do some of our event handling in core/frames.c, which expects
+ * GDK events delivered by GTK+. However, since the transition to
+ * client side windows, we can't let GDK see button events, since the
+ * client-side tracking of implicit and explicit grabs it does will
+ * get confused by our direct use of X grabs.
+ *
+ * So we do a very minimal GDK => GTK event conversion here and send on the
+ * events we care about, and then filter them out so they don't go
+ * through the normal GDK event handling.
+ *
+ * To reduce the amount of code, the only events fields filled out
+ * below are the ones that frames.c uses. If frames.c is modified to
+ * use more fields, more fields need to be filled out below.
+ *
+ * https://github.com/stefano-k/Mate-Desktop-Environment/commit/b0e5fb03eb21dae8f02692f11ef391bfc5ccba33
+ */
+
+static gboolean maybe_send_event_to_gtk(MetaDisplay* display, XEvent* xevent)
+{
+ /* We're always using the default display */
+ GdkDisplay* gdk_display = gdk_display_get_default();
+ GdkEvent gdk_event;
+ GdkWindow* gdk_window;
+ Window window;
+
+ switch (xevent->type)
+ {
+ case ButtonPress:
+ case ButtonRelease:
+ window = xevent->xbutton.window;
+ break;
+
+ case MotionNotify:
+ window = xevent->xmotion.window;
+ break;
+
+ case EnterNotify:
+
+ case LeaveNotify:
+ window = xevent->xcrossing.window;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ gdk_window = gdk_window_lookup_for_display(gdk_display, window);
+
+ if (gdk_window == NULL)
+ {
+ return FALSE;
+ }
+
+ /* If GDK already things it has a grab, we better let it see events; this
+ * is the menu-navigation case and events need to get sent to the appropriate
+ * (client-side) subwindow for individual menu items.
+ */
+
+ if (gdk_display_pointer_is_grabbed(gdk_display))
+ {
+ return FALSE;
+ }
+
+ memset(&gdk_event, 0, sizeof(gdk_event));
+
+ switch (xevent->type)
+ {
+
+ case ButtonPress:
+
+ case ButtonRelease:
+
+ if (xevent->type == ButtonPress)
+ {
+ GtkSettings* settings = gtk_settings_get_default();
+
+ int double_click_time;
+ int double_click_distance;
+
+ g_object_get (settings,
+ "gtk-double-click-time", &double_click_time,
+ "gtk-double-click-distance", &double_click_distance,
+ NULL);
+
+ if (xevent->xbutton.button == display->button_click_number &&
+ xevent->xbutton.window == display->button_click_window &&
+ xevent->xbutton.time < display->button_click_time + double_click_time &&
+ ABS(xevent->xbutton.x - display->button_click_x) <= double_click_distance &&
+ ABS (xevent->xbutton.y - display->button_click_y) <= double_click_distance)
+ {
+
+ gdk_event.button.type = GDK_2BUTTON_PRESS;
+ display->button_click_number = 0;
+ }
+ else
+ {
+ gdk_event.button.type = GDK_BUTTON_PRESS;
+ display->button_click_number = xevent->xbutton.button;
+ display->button_click_window = xevent->xbutton.window;
+ display->button_click_time = xevent->xbutton.time;
+ display->button_click_x = xevent->xbutton.x;
+ display->button_click_y = xevent->xbutton.y;
+ }
+ }
+ else
+ {
+ gdk_event.button.type = GDK_BUTTON_RELEASE;
+ }
+
+ gdk_event.button.window = gdk_window;
+ gdk_event.button.button = xevent->xbutton.button;
+ gdk_event.button.time = xevent->xbutton.time;
+ gdk_event.button.x = xevent->xbutton.x;
+ gdk_event.button.y = xevent->xbutton.y;
+ gdk_event.button.x_root = xevent->xbutton.x_root;
+ gdk_event.button.y_root = xevent->xbutton.y_root;
+
+ break;
+
+ case MotionNotify:
+ gdk_event.motion.type = GDK_MOTION_NOTIFY;
+ gdk_event.motion.window = gdk_window;
+ break;
+
+ case EnterNotify:
+
+ case LeaveNotify:
+ gdk_event.crossing.type = xevent->type == EnterNotify ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
+ gdk_event.crossing.window = gdk_window;
+ gdk_event.crossing.x = xevent->xcrossing.x;
+ gdk_event.crossing.y = xevent->xcrossing.y;
+ break;
+
+ default:
+ g_assert_not_reached();
+ break;
+ }
+
+ /* If we've gotten here, we've filled in the gdk_event and should send it on */
+ gtk_main_do_event(&gdk_event);
+ return TRUE;
+}
+
+/**
+ * This is the most important function in the whole program. It is the heart,
+ * it is the nexus, it is the Grand Central Station of Marco's world.
+ * When we create a MetaDisplay, we ask GDK to pass *all* events for *all*
+ * windows to this function. So every time anything happens that we might
+ * want to know about, this function gets called. You see why it gets a bit
+ * busy around here. Most of this function is a ginormous switch statement
+ * dealing with all the kinds of events that might turn up.
+ *
+ * \param event The event that just happened
+ * \param data The MetaDisplay that events are coming from, cast to a gpointer
+ * so that it can be sent to a callback
+ *
+ * \ingroup main
+ */
+static gboolean event_callback(XEvent* event, gpointer data)
+{
+ MetaWindow *window;
+ MetaWindow *property_for_window;
+ MetaDisplay *display;
+ Window modified;
+ gboolean frame_was_receiver;
+ gboolean filter_out_event;
+
+ display = data;
+
+#ifdef WITH_VERBOSE_MODE
+ if (dump_events)
+ meta_spew_event (display, event);
+#endif
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ sn_display_process_event (display->sn_display, event);
+#endif
+
+ filter_out_event = FALSE;
+ display->current_time = event_get_time (display, event);
+ display->xinerama_cache_invalidated = TRUE;
+
+ modified = event_get_modified_window (display, event);
+
+ if (event->type == ButtonPress)
+ {
+ /* filter out scrollwheel */
+ if (event->xbutton.button == 4 ||
+ event->xbutton.button == 5)
+ return FALSE;
+ }
+ else if (event->type == UnmapNotify)
+ {
+ if (meta_ui_window_should_not_cause_focus (display->xdisplay,
+ modified))
+ {
+ add_ignored_serial (display, event->xany.serial);
+ meta_topic (META_DEBUG_FOCUS,
+ "Adding EnterNotify serial %lu to ignored focus serials\n",
+ event->xany.serial);
+ }
+ }
+ else if (event->type == LeaveNotify &&
+ event->xcrossing.mode == NotifyUngrab &&
+ modified == display->ungrab_should_not_cause_focus_window)
+ {
+ add_ignored_serial (display, event->xany.serial);
+ meta_topic (META_DEBUG_FOCUS,
+ "Adding LeaveNotify serial %lu to ignored focus serials\n",
+ event->xany.serial);
+ }
+
+ if (modified != None)
+ window = meta_display_lookup_x_window (display, modified);
+ else
+ window = NULL;
+
+ /* We only want to respond to _NET_WM_USER_TIME property notify
+ * events on _NET_WM_USER_TIME_WINDOW windows; in particular,
+ * responding to UnmapNotify events is kind of bad.
+ */
+ property_for_window = NULL;
+ if (window && modified == window->user_time_window)
+ {
+ property_for_window = window;
+ window = NULL;
+ }
+
+
+ frame_was_receiver = FALSE;
+ if (window &&
+ window->frame &&
+ modified == window->frame->xwindow)
+ {
+ /* Note that if the frame and the client both have an
+ * XGrabButton (as is normal with our setup), the event
+ * goes to the frame.
+ */
+ frame_was_receiver = TRUE;
+ meta_topic (META_DEBUG_EVENTS, "Frame was receiver of event for %s\n",
+ window->desc);
+ }
+
+#ifdef HAVE_XSYNC
+ if (META_DISPLAY_HAS_XSYNC (display) &&
+ event->type == (display->xsync_event_base + XSyncAlarmNotify) &&
+ ((XSyncAlarmNotifyEvent*)event)->alarm == display->grab_sync_request_alarm)
+ {
+ filter_out_event = TRUE; /* GTK doesn't want to see this really */
+
+ if (display->grab_op != META_GRAB_OP_NONE &&
+ display->grab_window != NULL &&
+ grab_op_is_mouse (display->grab_op))
+ meta_window_handle_mouse_grab_op_event (display->grab_window, event);
+ }
+#endif /* HAVE_XSYNC */
+
+#ifdef HAVE_SHAPE
+ if (META_DISPLAY_HAS_SHAPE (display) &&
+ event->type == (display->shape_event_base + ShapeNotify))
+ {
+ filter_out_event = TRUE; /* GTK doesn't want to see this really */
+
+ if (window && !frame_was_receiver)
+ {
+ XShapeEvent *sev = (XShapeEvent*) event;
+
+ if (sev->kind == ShapeBounding)
+ {
+ if (sev->shaped && !window->has_shape)
+ {
+ window->has_shape = TRUE;
+ meta_topic (META_DEBUG_SHAPES,
+ "Window %s now has a shape\n",
+ window->desc);
+ }
+ else if (!sev->shaped && window->has_shape)
+ {
+ window->has_shape = FALSE;
+ meta_topic (META_DEBUG_SHAPES,
+ "Window %s no longer has a shape\n",
+ window->desc);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_SHAPES,
+ "Window %s shape changed\n",
+ window->desc);
+ }
+
+ if (window->frame)
+ {
+ window->frame->need_reapply_frame_shape = TRUE;
+ meta_warning("from event callback\n");
+ meta_window_queue (window, META_QUEUE_MOVE_RESIZE);
+ }
+ }
+ }
+ else
+ {
+ meta_topic (META_DEBUG_SHAPES,
+ "ShapeNotify not on a client window (window %s frame_was_receiver = %d)\n",
+ window ? window->desc : "(none)",
+ frame_was_receiver);
+ }
+ }
+#endif /* HAVE_SHAPE */
+
+ if (window && ((event->type == KeyPress) || (event->type == ButtonPress)))
+ {
+ if (CurrentTime == display->current_time)
+ {
+ /* We can't use missing (i.e. invalid) timestamps to set user time,
+ * nor do we want to use them to sanity check other timestamps.
+ * See bug 313490 for more details.
+ */
+ meta_warning ("Event has no timestamp! You may be using a broken "
+ "program such as xse. Please ask the authors of that "
+ "program to fix it.\n");
+ }
+ else
+ {
+ meta_window_set_user_time (window, display->current_time);
+ sanity_check_timestamps (display, display->current_time);
+ }
+ }
+
+ switch (event->type)
+ {
+ case KeyPress:
+ case KeyRelease:
+ meta_display_process_key_event (display, window, event);
+ break;
+ case ButtonPress:
+ if ((window &&
+ grab_op_is_mouse (display->grab_op) &&
+ display->grab_button != (int) event->xbutton.button &&
+ display->grab_window == window) ||
+ grab_op_is_keyboard (display->grab_op))
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Ending grab op %u on window %s due to button press\n",
+ display->grab_op,
+ (display->grab_window ?
+ display->grab_window->desc :
+ "none"));
+ if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op))
+ {
+ MetaScreen *screen;
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Syncing to old stack positions.\n");
+ screen =
+ meta_display_screen_for_root (display, event->xany.window);
+
+ if (screen!=NULL)
+ meta_stack_set_positions (screen->stack,
+ display->grab_old_window_stacking);
+ }
+ meta_display_end_grab_op (display,
+ event->xbutton.time);
+ }
+ else if (window && display->grab_op == META_GRAB_OP_NONE)
+ {
+ gboolean begin_move = FALSE;
+ unsigned int grab_mask;
+ gboolean unmodified;
+
+ grab_mask = display->window_grab_modifiers;
+ if (g_getenv ("MARCO_DEBUG_BUTTON_GRABS"))
+ grab_mask |= ControlMask;
+
+ /* Two possible sources of an unmodified event; one is a
+ * client that's letting button presses pass through to the
+ * frame, the other is our focus_window_grab on unmodified
+ * button 1. So for all such events we focus the window.
+ */
+ unmodified = (event->xbutton.state & grab_mask) == 0;
+
+ if (unmodified ||
+ event->xbutton.button == 1)
+ {
+ /* don't focus if frame received, will be lowered in
+ * frames.c or special-cased if the click was on a
+ * minimize/close button.
+ */
+ if (!frame_was_receiver)
+ {
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+ else
+ meta_topic (META_DEBUG_FOCUS,
+ "Not raising window on click due to don't-raise-on-click option\n");
+
+ /* Don't focus panels--they must explicitly request focus.
+ * See bug 160470
+ */
+ if (window->type != META_WINDOW_DOCK)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing %s due to unmodified button %u press (display.c)\n",
+ window->desc, event->xbutton.button);
+ meta_window_focus (window, event->xbutton.time);
+ }
+ else
+ /* However, do allow terminals to lose focus due to new
+ * window mappings after the user clicks on a panel.
+ */
+ display->allow_terminal_deactivation = TRUE;
+ }
+
+ /* you can move on alt-click but not on
+ * the click-to-focus
+ */
+ if (!unmodified)
+ begin_move = TRUE;
+ }
+ else if (!unmodified && event->xbutton.button == meta_prefs_get_mouse_button_resize())
+ {
+ if (window->has_resize_func)
+ {
+ gboolean north, south;
+ gboolean west, east;
+ int root_x, root_y;
+ MetaGrabOp op;
+
+ meta_window_get_position (window, &root_x, &root_y);
+
+ west = event->xbutton.x_root < (root_x + 1 * window->rect.width / 3);
+ east = event->xbutton.x_root > (root_x + 2 * window->rect.width / 3);
+ north = event->xbutton.y_root < (root_y + 1 * window->rect.height / 3);
+ south = event->xbutton.y_root > (root_y + 2 * window->rect.height / 3);
+
+ if (north && west)
+ op = META_GRAB_OP_RESIZING_NW;
+ else if (north && east)
+ op = META_GRAB_OP_RESIZING_NE;
+ else if (south && west)
+ op = META_GRAB_OP_RESIZING_SW;
+ else if (south && east)
+ op = META_GRAB_OP_RESIZING_SE;
+ else if (north)
+ op = META_GRAB_OP_RESIZING_N;
+ else if (west)
+ op = META_GRAB_OP_RESIZING_W;
+ else if (east)
+ op = META_GRAB_OP_RESIZING_E;
+ else if (south)
+ op = META_GRAB_OP_RESIZING_S;
+ else /* Middle region is no-op to avoid user triggering wrong action */
+ op = META_GRAB_OP_NONE;
+
+ if (op != META_GRAB_OP_NONE)
+ meta_display_begin_grab_op (display,
+ window->screen,
+ window,
+ op,
+ TRUE,
+ FALSE,
+ event->xbutton.button,
+ 0,
+ event->xbutton.time,
+ event->xbutton.x_root,
+ event->xbutton.y_root);
+ }
+ }
+ else if (event->xbutton.button == meta_prefs_get_mouse_button_menu())
+ {
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+ meta_window_show_menu (window,
+ event->xbutton.x_root,
+ event->xbutton.y_root,
+ event->xbutton.button,
+ event->xbutton.time);
+ }
+
+ if (!frame_was_receiver && unmodified)
+ {
+ /* This is from our synchronous grab since
+ * it has no modifiers and was on the client window
+ */
+ int mode;
+
+ /* When clicking a different app in click-to-focus
+ * in application-based mode, and the different
+ * app is not a dock or desktop, eat the focus click.
+ */
+ if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK &&
+ meta_prefs_get_application_based () &&
+ !window->has_focus &&
+ window->type != META_WINDOW_DOCK &&
+ window->type != META_WINDOW_DESKTOP &&
+ (display->focus_window == NULL ||
+ !meta_window_same_application (window,
+ display->focus_window)))
+ mode = AsyncPointer; /* eat focus click */
+ else
+ mode = ReplayPointer; /* give event back */
+
+ meta_verbose ("Allowing events mode %s time %u\n",
+ mode == AsyncPointer ? "AsyncPointer" : "ReplayPointer",
+ (unsigned int)event->xbutton.time);
+
+ XAllowEvents (display->xdisplay,
+ mode, event->xbutton.time);
+ }
+
+ if (begin_move && window->has_move_func)
+ {
+ meta_display_begin_grab_op (display,
+ window->screen,
+ window,
+ META_GRAB_OP_MOVING,
+ TRUE,
+ FALSE,
+ event->xbutton.button,
+ 0,
+ event->xbutton.time,
+ event->xbutton.x_root,
+ event->xbutton.y_root);
+ }
+ }
+ break;
+ case ButtonRelease:
+ if (display->grab_window == window &&
+ grab_op_is_mouse (display->grab_op))
+ meta_window_handle_mouse_grab_op_event (window, event);
+ break;
+ case MotionNotify:
+ if (display->grab_window == window &&
+ grab_op_is_mouse (display->grab_op))
+ meta_window_handle_mouse_grab_op_event (window, event);
+ break;
+ case EnterNotify:
+ if (display->grab_window == window &&
+ grab_op_is_mouse (display->grab_op))
+ {
+ meta_window_handle_mouse_grab_op_event (window, event);
+ break;
+ }
+
+ /* If the mouse switches screens, active the default window on the new
+ * screen; this will make keybindings and workspace-launched items
+ * actually appear on the right screen.
+ */
+ {
+ MetaScreen *new_screen =
+ meta_display_screen_for_root (display, event->xcrossing.root);
+
+ if (new_screen != NULL && display->active_screen != new_screen)
+ meta_workspace_focus_default_window (new_screen->active_workspace,
+ NULL,
+ event->xcrossing.time);
+ }
+
+ /* Check if we've entered a window; do this even if window->has_focus to
+ * avoid races.
+ */
+ if (window && !serial_is_ignored (display, event->xany.serial) &&
+ event->xcrossing.mode != NotifyGrab &&
+ event->xcrossing.mode != NotifyUngrab &&
+ event->xcrossing.detail != NotifyInferior &&
+ meta_display_focus_sentinel_clear (display))
+ {
+ switch (meta_prefs_get_focus_mode ())
+ {
+ case META_FOCUS_MODE_SLOPPY:
+ case META_FOCUS_MODE_MOUSE:
+ display->mouse_mode = TRUE;
+ if (window->type != META_WINDOW_DOCK &&
+ window->type != META_WINDOW_DESKTOP)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing %s due to enter notify with serial %lu "
+ "at time %lu, and setting display->mouse_mode to "
+ "TRUE.\n",
+ window->desc,
+ event->xany.serial,
+ event->xcrossing.time);
+
+ meta_window_focus (window, event->xcrossing.time);
+
+ /* stop ignoring stuff */
+ reset_ignores (display);
+
+ if (meta_prefs_get_auto_raise ())
+ {
+ meta_display_queue_autoraise_callback (display, window);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Auto raise is disabled\n");
+ }
+ }
+ /* In mouse focus mode, we defocus when the mouse *enters*
+ * the DESKTOP window, instead of defocusing on LeaveNotify.
+ * This is because having the mouse enter override-redirect
+ * child windows unfortunately causes LeaveNotify events that
+ * we can't distinguish from the mouse actually leaving the
+ * toplevel window as we expect. But, since we filter out
+ * EnterNotify events on override-redirect windows, this
+ * alternative mechanism works great.
+ */
+ if (window->type == META_WINDOW_DESKTOP &&
+ meta_prefs_get_focus_mode() == META_FOCUS_MODE_MOUSE &&
+ display->expected_focus_window != NULL)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Unsetting focus from %s due to mouse entering "
+ "the DESKTOP window\n",
+ display->expected_focus_window->desc);
+ meta_display_focus_the_no_focus_window (display,
+ window->screen,
+ event->xcrossing.time);
+ }
+ break;
+ case META_FOCUS_MODE_CLICK:
+ break;
+ }
+
+ if (window->type == META_WINDOW_DOCK)
+ meta_window_raise (window);
+ }
+ break;
+ case LeaveNotify:
+ if (display->grab_window == window &&
+ grab_op_is_mouse (display->grab_op))
+ meta_window_handle_mouse_grab_op_event (window, event);
+ else if (window != NULL)
+ {
+ if (window->type == META_WINDOW_DOCK &&
+ event->xcrossing.mode != NotifyGrab &&
+ event->xcrossing.mode != NotifyUngrab &&
+ !window->has_focus)
+ meta_window_lower (window);
+ }
+ break;
+ case FocusIn:
+ case FocusOut:
+ if (window)
+ {
+ meta_window_notify_focus (window, event);
+ }
+ else if (meta_display_xwindow_is_a_no_focus_window (display,
+ event->xany.window))
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focus %s event received on no_focus_window 0x%lx "
+ "mode %s detail %s\n",
+ event->type == FocusIn ? "in" :
+ event->type == FocusOut ? "out" :
+ "???",
+ event->xany.window,
+ meta_event_mode_to_string (event->xfocus.mode),
+ meta_event_detail_to_string (event->xfocus.detail));
+ }
+ else
+ {
+ MetaScreen *screen =
+ meta_display_screen_for_root(display,
+ event->xany.window);
+ if (screen == NULL)
+ break;
+
+ meta_topic (META_DEBUG_FOCUS,
+ "Focus %s event received on root window 0x%lx "
+ "mode %s detail %s\n",
+ event->type == FocusIn ? "in" :
+ event->type == FocusOut ? "out" :
+ "???",
+ event->xany.window,
+ meta_event_mode_to_string (event->xfocus.mode),
+ meta_event_detail_to_string (event->xfocus.detail));
+
+ if (event->type == FocusIn &&
+ event->xfocus.detail == NotifyDetailNone)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focus got set to None, probably due to "
+ "brain-damage in the X protocol (see bug "
+ "125492). Setting the default focus window.\n");
+ meta_workspace_focus_default_window (screen->active_workspace,
+ NULL,
+ meta_display_get_current_time_roundtrip (display));
+ }
+ else if (event->type == FocusIn &&
+ event->xfocus.mode == NotifyNormal &&
+ event->xfocus.detail == NotifyInferior)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focus got set to root window, probably due to "
+ "mate-session logout dialog usage (see bug "
+ "153220). Setting the default focus window.\n");
+ meta_workspace_focus_default_window (screen->active_workspace,
+ NULL,
+ meta_display_get_current_time_roundtrip (display));
+ }
+
+ }
+ break;
+ case KeymapNotify:
+ break;
+ case Expose:
+ break;
+ case GraphicsExpose:
+ break;
+ case NoExpose:
+ break;
+ case VisibilityNotify:
+ break;
+ case CreateNotify:
+ break;
+
+ case DestroyNotify:
+ if (window)
+ {
+ /* FIXME: It sucks that DestroyNotify events don't come with
+ * a timestamp; could we do something better here? Maybe X
+ * will change one day?
+ */
+ guint32 timestamp;
+ timestamp = meta_display_get_current_time_roundtrip (display);
+
+ if (display->grab_op != META_GRAB_OP_NONE &&
+ display->grab_window == window)
+ meta_display_end_grab_op (display, timestamp);
+
+ if (frame_was_receiver)
+ {
+ meta_warning ("Unexpected destruction of frame 0x%lx, not sure if this should silently fail or be considered a bug\n",
+ window->frame->xwindow);
+ meta_error_trap_push (display);
+ meta_window_destroy_frame (window->frame->window);
+ meta_error_trap_pop (display, FALSE);
+ }
+ else
+ {
+ /* Unmanage destroyed window */
+ meta_window_free (window, timestamp);
+ window = NULL;
+ }
+ }
+ break;
+ case UnmapNotify:
+ if (window)
+ {
+ /* FIXME: It sucks that UnmapNotify events don't come with
+ * a timestamp; could we do something better here? Maybe X
+ * will change one day?
+ */
+ guint32 timestamp;
+ timestamp = meta_display_get_current_time_roundtrip (display);
+
+ if (display->grab_op != META_GRAB_OP_NONE &&
+ display->grab_window == window &&
+ ((window->frame == NULL) || !window->frame->mapped))
+ meta_display_end_grab_op (display, timestamp);
+
+ if (!frame_was_receiver)
+ {
+ if (window->unmaps_pending == 0)
+ {
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Window %s withdrawn\n",
+ window->desc);
+
+ meta_effect_run_close (window, NULL, NULL);
+
+ /* Unmanage withdrawn window */
+ window->withdrawn = TRUE;
+ meta_window_free (window, timestamp);
+ window = NULL;
+ }
+ else
+ {
+ window->unmaps_pending -= 1;
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Received pending unmap, %d now pending\n",
+ window->unmaps_pending);
+ }
+ }
+
+ /* Unfocus on UnmapNotify, do this after the possible
+ * window_free above so that window_free can see if window->has_focus
+ * and move focus to another window
+ */
+ if (window)
+ meta_window_notify_focus (window, event);
+ }
+ break;
+ case MapNotify:
+ break;
+ case MapRequest:
+ if (window == NULL)
+ {
+ window = meta_window_new (display, event->xmaprequest.window,
+ FALSE);
+ }
+ /* if frame was receiver it's some malicious send event or something */
+ else if (!frame_was_receiver && window)
+ {
+ meta_verbose ("MapRequest on %s mapped = %d minimized = %d\n",
+ window->desc, window->mapped, window->minimized);
+ if (window->minimized)
+ {
+ meta_window_unminimize (window);
+ if (window->workspace != window->screen->active_workspace)
+ {
+ meta_verbose ("Changing workspace due to MapRequest mapped = %d minimized = %d\n",
+ window->mapped, window->minimized);
+ meta_window_change_workspace (window,
+ window->screen->active_workspace);
+ }
+ }
+ }
+ break;
+ case ReparentNotify:
+ break;
+ case ConfigureNotify:
+ /* Handle screen resize */
+ {
+ MetaScreen *screen;
+
+ screen = meta_display_screen_for_root (display,
+ event->xconfigure.window);
+
+ if (screen != NULL)
+ {
+#ifdef HAVE_RANDR
+ /* do the resize the official way */
+ XRRUpdateConfiguration (event);
+#else
+ /* poke around in Xlib */
+ screen->xscreen->width = event->xconfigure.width;
+ screen->xscreen->height = event->xconfigure.height;
+#endif
+
+ meta_screen_resize (screen,
+ event->xconfigure.width,
+ event->xconfigure.height);
+ }
+ }
+ break;
+ case ConfigureRequest:
+ /* This comment and code is found in both twm and fvwm */
+ /*
+ * According to the July 27, 1988 ICCCM draft, we should ignore size and
+ * position fields in the WM_NORMAL_HINTS property when we map a window.
+ * Instead, we'll read the current geometry. Therefore, we should respond
+ * to configuration requests for windows which have never been mapped.
+ */
+ if (window == NULL)
+ {
+ unsigned int xwcm;
+ XWindowChanges xwc;
+
+ xwcm = event->xconfigurerequest.value_mask &
+ (CWX | CWY | CWWidth | CWHeight | CWBorderWidth);
+
+ xwc.x = event->xconfigurerequest.x;
+ xwc.y = event->xconfigurerequest.y;
+ xwc.width = event->xconfigurerequest.width;
+ xwc.height = event->xconfigurerequest.height;
+ xwc.border_width = event->xconfigurerequest.border_width;
+
+ meta_verbose ("Configuring withdrawn window to %d,%d %dx%d border %d (some values may not be in mask)\n",
+ xwc.x, xwc.y, xwc.width, xwc.height, xwc.border_width);
+ meta_error_trap_push (display);
+ XConfigureWindow (display->xdisplay, event->xconfigurerequest.window,
+ xwcm, &xwc);
+ meta_error_trap_pop (display, FALSE);
+ }
+ else
+ {
+ if (!frame_was_receiver)
+ meta_window_configure_request (window, event);
+ }
+ break;
+ case GravityNotify:
+ break;
+ case ResizeRequest:
+ break;
+ case CirculateNotify:
+ break;
+ case CirculateRequest:
+ break;
+ case PropertyNotify:
+ {
+ MetaGroup *group;
+ MetaScreen *screen;
+
+ if (window && !frame_was_receiver)
+ meta_window_property_notify (window, event);
+ else if (property_for_window && !frame_was_receiver)
+ meta_window_property_notify (property_for_window, event);
+
+ group = meta_display_lookup_group (display,
+ event->xproperty.window);
+ if (group != NULL)
+ meta_group_property_notify (group, event);
+
+ screen = NULL;
+ if (window == NULL &&
+ group == NULL) /* window/group != NULL means it wasn't a root window */
+ screen = meta_display_screen_for_root (display,
+ event->xproperty.window);
+
+ if (screen != NULL)
+ {
+ if (event->xproperty.atom ==
+ display->atom__NET_DESKTOP_LAYOUT)
+ meta_screen_update_workspace_layout (screen);
+ else if (event->xproperty.atom ==
+ display->atom__NET_DESKTOP_NAMES)
+ meta_screen_update_workspace_names (screen);
+#if 0
+ else if (event->xproperty.atom ==
+ display->atom__NET_RESTACK_WINDOW)
+ handle_net_restack_window (display, event);
+#endif
+
+ /* we just use this property as a sentinel to avoid
+ * certain race conditions. See the comment for the
+ * sentinel_counter variable declaration in display.h
+ */
+ if (event->xproperty.atom ==
+ display->atom__MARCO_SENTINEL)
+ {
+ meta_display_decrement_focus_sentinel (display);
+ }
+ }
+ }
+ break;
+ case SelectionClear:
+ /* do this here instead of at end of function
+ * so we can return
+ */
+
+ /* FIXME: Clearing display->current_time here makes no sense to
+ * me; who put this here and why?
+ */
+ display->current_time = CurrentTime;
+
+ process_selection_clear (display, event);
+ /* Note that processing that may have resulted in
+ * closing the display... so return right away.
+ */
+ return FALSE;
+ case SelectionRequest:
+ process_selection_request (display, event);
+ break;
+ case SelectionNotify:
+ break;
+ case ColormapNotify:
+ if (window && !frame_was_receiver)
+ window->colormap = event->xcolormap.colormap;
+ break;
+ case ClientMessage:
+ if (window)
+ {
+ if (!frame_was_receiver)
+ meta_window_client_message (window, event);
+ }
+ else
+ {
+ MetaScreen *screen;
+
+ screen = meta_display_screen_for_root (display,
+ event->xclient.window);
+
+ if (screen)
+ {
+ if (event->xclient.message_type ==
+ display->atom__NET_CURRENT_DESKTOP)
+ {
+ int space;
+ MetaWorkspace *workspace;
+ guint32 time;
+
+ space = event->xclient.data.l[0];
+ time = event->xclient.data.l[1];
+
+ meta_verbose ("Request to change current workspace to %d with "
+ "specified timestamp of %u\n",
+ space, time);
+
+ workspace =
+ meta_screen_get_workspace_by_index (screen,
+ space);
+
+ /* Handle clients using the older version of the spec... */
+ if (time == 0 && workspace)
+ {
+ meta_warning ("Received a NET_CURRENT_DESKTOP message "
+ "from a broken (outdated) client who sent "
+ "a 0 timestamp\n");
+ time = meta_display_get_current_time_roundtrip (display);
+ }
+
+ if (workspace)
+ meta_workspace_activate (workspace, time);
+ else
+ meta_verbose ("Don't know about workspace %d\n", space);
+ }
+ else if (event->xclient.message_type ==
+ display->atom__NET_NUMBER_OF_DESKTOPS)
+ {
+ int num_spaces;
+
+ num_spaces = event->xclient.data.l[0];
+
+ meta_verbose ("Request to set number of workspaces to %d\n",
+ num_spaces);
+
+ meta_prefs_set_num_workspaces (num_spaces);
+ }
+ else if (event->xclient.message_type ==
+ display->atom__NET_SHOWING_DESKTOP)
+ {
+ gboolean showing_desktop;
+ guint32 timestamp;
+
+ showing_desktop = event->xclient.data.l[0] != 0;
+ /* FIXME: Braindead protocol doesn't have a timestamp */
+ timestamp = meta_display_get_current_time_roundtrip (display);
+ meta_verbose ("Request to %s desktop\n",
+ showing_desktop ? "show" : "hide");
+
+ if (showing_desktop)
+ meta_screen_show_desktop (screen, timestamp);
+ else
+ {
+ meta_screen_unshow_desktop (screen);
+ meta_workspace_focus_default_window (screen->active_workspace, NULL, timestamp);
+ }
+ }
+ else if (event->xclient.message_type ==
+ display->atom__MARCO_RESTART_MESSAGE)
+ {
+ meta_verbose ("Received restart request\n");
+ meta_restart ();
+ }
+ else if (event->xclient.message_type ==
+ display->atom__MARCO_RELOAD_THEME_MESSAGE)
+ {
+ meta_verbose ("Received reload theme request\n");
+ meta_ui_set_current_theme (meta_prefs_get_theme (),
+ TRUE);
+ meta_display_retheme_all ();
+ }
+ else if (event->xclient.message_type ==
+ display->atom__MARCO_SET_KEYBINDINGS_MESSAGE)
+ {
+ meta_verbose ("Received set keybindings request = %d\n",
+ (int) event->xclient.data.l[0]);
+ meta_set_keybindings_disabled (!event->xclient.data.l[0]);
+ }
+ else if (event->xclient.message_type ==
+ display->atom__MARCO_TOGGLE_VERBOSE)
+ {
+ meta_verbose ("Received toggle verbose message\n");
+ meta_set_verbose (!meta_is_verbose ());
+ }
+ else if (event->xclient.message_type ==
+ display->atom_WM_PROTOCOLS)
+ {
+ meta_verbose ("Received WM_PROTOCOLS message\n");
+
+ if ((Atom)event->xclient.data.l[0] == display->atom__NET_WM_PING)
+ {
+ process_pong_message (display, event);
+
+ /* We don't want ping reply events going into
+ * the GTK+ event loop because gtk+ will treat
+ * them as ping requests and send more replies.
+ */
+ filter_out_event = TRUE;
+ }
+ }
+ }
+
+ if (event->xclient.message_type ==
+ display->atom__NET_REQUEST_FRAME_EXTENTS)
+ {
+ meta_verbose ("Received _NET_REQUEST_FRAME_EXTENTS message\n");
+ process_request_frame_extents (display, event);
+ }
+ }
+ break;
+ case MappingNotify:
+ {
+ gboolean ignore_current;
+
+ ignore_current = FALSE;
+
+ /* Check whether the next event is an identical MappingNotify
+ * event. If it is, ignore the current event, we'll update
+ * when we get the next one.
+ */
+ if (XPending (display->xdisplay))
+ {
+ XEvent next_event;
+
+ XPeekEvent (display->xdisplay, &next_event);
+
+ if (next_event.type == MappingNotify &&
+ next_event.xmapping.request == event->xmapping.request)
+ ignore_current = TRUE;
+ }
+
+ if (!ignore_current)
+ {
+ /* Let XLib know that there is a new keyboard mapping.
+ */
+ XRefreshKeyboardMapping (&event->xmapping);
+ meta_display_process_mapping_event (display, event);
+ }
+ }
+ break;
+ default:
+#ifdef HAVE_XKB
+ if (event->type == display->xkb_base_event_type)
+ {
+ XkbAnyEvent *xkb_ev = (XkbAnyEvent *) event;
+
+ switch (xkb_ev->xkb_type)
+ {
+ case XkbBellNotify:
+ if (XSERVER_TIME_IS_BEFORE(display->last_bell_time,
+ xkb_ev->time - 100))
+ {
+ display->last_bell_time = xkb_ev->time;
+ meta_bell_notify (display, xkb_ev);
+ }
+ break;
+ }
+ }
+#endif
+ break;
+ }
+
+ if (display->compositor)
+ {
+ meta_compositor_process_event (display->compositor, event, window);
+ }
+
+ /* generates event on wrong window.
+ * https://github.com/stefano-k/Mate-Desktop-Environment/commit/b0e5fb03eb21dae8f02692f11ef391bfc5ccba33
+ */
+ if (maybe_send_event_to_gtk(display, event))
+ {
+ filter_out_event = TRUE;
+ }
+
+ display->current_time = CurrentTime;
+ return filter_out_event;
+}
+
+/* Return the window this has to do with, if any, rather
+ * than the frame or root window that was selecting
+ * for substructure
+ */
+static Window
+event_get_modified_window (MetaDisplay *display,
+ XEvent *event)
+{
+ switch (event->type)
+ {
+ case KeyPress:
+ case KeyRelease:
+ case ButtonPress:
+ case ButtonRelease:
+ case MotionNotify:
+ case FocusIn:
+ case FocusOut:
+ case KeymapNotify:
+ case Expose:
+ case GraphicsExpose:
+ case NoExpose:
+ case VisibilityNotify:
+ case ResizeRequest:
+ case PropertyNotify:
+ case SelectionClear:
+ case SelectionRequest:
+ case SelectionNotify:
+ case ColormapNotify:
+ case ClientMessage:
+ case EnterNotify:
+ case LeaveNotify:
+ return event->xany.window;
+
+ case CreateNotify:
+ return event->xcreatewindow.window;
+
+ case DestroyNotify:
+ return event->xdestroywindow.window;
+
+ case UnmapNotify:
+ return event->xunmap.window;
+
+ case MapNotify:
+ return event->xmap.window;
+
+ case MapRequest:
+ return event->xmaprequest.window;
+
+ case ReparentNotify:
+ return event->xreparent.window;
+
+ case ConfigureNotify:
+ return event->xconfigure.window;
+
+ case ConfigureRequest:
+ return event->xconfigurerequest.window;
+
+ case GravityNotify:
+ return event->xgravity.window;
+
+ case CirculateNotify:
+ return event->xcirculate.window;
+
+ case CirculateRequest:
+ return event->xcirculaterequest.window;
+
+ case MappingNotify:
+ return None;
+
+ default:
+#ifdef HAVE_SHAPE
+ if (META_DISPLAY_HAS_SHAPE (display) &&
+ event->type == (display->shape_event_base + ShapeNotify))
+ {
+ XShapeEvent *sev = (XShapeEvent*) event;
+ return sev->window;
+ }
+#endif
+
+ return None;
+ }
+}
+
+static guint32
+event_get_time (MetaDisplay *display,
+ XEvent *event)
+{
+ switch (event->type)
+ {
+ case KeyPress:
+ case KeyRelease:
+ return event->xkey.time;
+
+ case ButtonPress:
+ case ButtonRelease:
+ return event->xbutton.time;
+
+ case MotionNotify:
+ return event->xmotion.time;
+
+ case PropertyNotify:
+ return event->xproperty.time;
+
+ case SelectionClear:
+ case SelectionRequest:
+ case SelectionNotify:
+ return event->xselection.time;
+
+ case EnterNotify:
+ case LeaveNotify:
+ return event->xcrossing.time;
+
+ case FocusIn:
+ case FocusOut:
+ case KeymapNotify:
+ case Expose:
+ case GraphicsExpose:
+ case NoExpose:
+ case MapNotify:
+ case UnmapNotify:
+ case VisibilityNotify:
+ case ResizeRequest:
+ case ColormapNotify:
+ case ClientMessage:
+ case CreateNotify:
+ case DestroyNotify:
+ case MapRequest:
+ case ReparentNotify:
+ case ConfigureNotify:
+ case ConfigureRequest:
+ case GravityNotify:
+ case CirculateNotify:
+ case CirculateRequest:
+ case MappingNotify:
+ default:
+ return CurrentTime;
+ }
+}
+
+#ifdef WITH_VERBOSE_MODE
+const char*
+meta_event_detail_to_string (int d)
+{
+ const char *detail = "???";
+ switch (d)
+ {
+ /* We are an ancestor in the A<->B focus change relationship */
+ case NotifyAncestor:
+ detail = "NotifyAncestor";
+ break;
+ case NotifyDetailNone:
+ detail = "NotifyDetailNone";
+ break;
+ /* We are a descendant in the A<->B focus change relationship */
+ case NotifyInferior:
+ detail = "NotifyInferior";
+ break;
+ case NotifyNonlinear:
+ detail = "NotifyNonlinear";
+ break;
+ case NotifyNonlinearVirtual:
+ detail = "NotifyNonlinearVirtual";
+ break;
+ case NotifyPointer:
+ detail = "NotifyPointer";
+ break;
+ case NotifyPointerRoot:
+ detail = "NotifyPointerRoot";
+ break;
+ case NotifyVirtual:
+ detail = "NotifyVirtual";
+ break;
+ }
+
+ return detail;
+}
+#endif /* WITH_VERBOSE_MODE */
+
+#ifdef WITH_VERBOSE_MODE
+const char*
+meta_event_mode_to_string (int m)
+{
+ const char *mode = "???";
+ switch (m)
+ {
+ case NotifyNormal:
+ mode = "NotifyNormal";
+ break;
+ case NotifyGrab:
+ mode = "NotifyGrab";
+ break;
+ case NotifyUngrab:
+ mode = "NotifyUngrab";
+ break;
+ /* not sure any X implementations are missing this, but
+ * it seems to be absent from some docs.
+ */
+#ifdef NotifyWhileGrabbed
+ case NotifyWhileGrabbed:
+ mode = "NotifyWhileGrabbed";
+ break;
+#endif
+ }
+
+ return mode;
+}
+#endif /* WITH_VERBOSE_MODE */
+
+#ifdef WITH_VERBOSE_MODE
+static const char*
+stack_mode_to_string (int mode)
+{
+ switch (mode)
+ {
+ case Above:
+ return "Above";
+ case Below:
+ return "Below";
+ case TopIf:
+ return "TopIf";
+ case BottomIf:
+ return "BottomIf";
+ case Opposite:
+ return "Opposite";
+ }
+
+ return "Unknown";
+}
+#endif /* WITH_VERBOSE_MODE */
+
+#ifdef WITH_VERBOSE_MODE
+static char*
+key_event_description (Display *xdisplay,
+ XEvent *event)
+{
+ KeySym keysym;
+ const char *str;
+
+ keysym = XKeycodeToKeysym (xdisplay, event->xkey.keycode, 0);
+
+ str = XKeysymToString (keysym);
+
+ return g_strdup_printf ("Key '%s' state 0x%x",
+ str ? str : "none", event->xkey.state);
+}
+#endif /* WITH_VERBOSE_MODE */
+
+#ifdef HAVE_XSYNC
+#ifdef WITH_VERBOSE_MODE
+static gint64
+sync_value_to_64 (const XSyncValue *value)
+{
+ gint64 v;
+
+ v = XSyncValueLow32 (*value);
+ v |= (((gint64)XSyncValueHigh32 (*value)) << 32);
+
+ return v;
+}
+#endif /* WITH_VERBOSE_MODE */
+
+#ifdef WITH_VERBOSE_MODE
+static const char*
+alarm_state_to_string (XSyncAlarmState state)
+{
+ switch (state)
+ {
+ case XSyncAlarmActive:
+ return "Active";
+ case XSyncAlarmInactive:
+ return "Inactive";
+ case XSyncAlarmDestroyed:
+ return "Destroyed";
+ default:
+ return "(unknown)";
+ }
+}
+#endif /* WITH_VERBOSE_MODE */
+
+#endif /* HAVE_XSYNC */
+
+#ifdef WITH_VERBOSE_MODE
+static void
+meta_spew_event (MetaDisplay *display,
+ XEvent *event)
+{
+ const char *name = NULL;
+ char *extra = NULL;
+ char *winname;
+ MetaScreen *screen;
+
+ if (!meta_is_verbose())
+ return;
+
+ /* filter overnumerous events */
+ if (event->type == Expose || event->type == MotionNotify ||
+ event->type == NoExpose)
+ return;
+
+ switch (event->type)
+ {
+ case KeyPress:
+ name = "KeyPress";
+ extra = key_event_description (display->xdisplay, event);
+ break;
+ case KeyRelease:
+ name = "KeyRelease";
+ extra = key_event_description (display->xdisplay, event);
+ break;
+ case ButtonPress:
+ name = "ButtonPress";
+ extra = g_strdup_printf ("button %u state 0x%x x %d y %d root 0x%lx same_screen %d",
+ event->xbutton.button,
+ event->xbutton.state,
+ event->xbutton.x,
+ event->xbutton.y,
+ event->xbutton.root,
+ event->xbutton.same_screen);
+ break;
+ case ButtonRelease:
+ name = "ButtonRelease";
+ extra = g_strdup_printf ("button %u state 0x%x x %d y %d root 0x%lx same_screen %d",
+ event->xbutton.button,
+ event->xbutton.state,
+ event->xbutton.x,
+ event->xbutton.y,
+ event->xbutton.root,
+ event->xbutton.same_screen);
+ break;
+ case MotionNotify:
+ name = "MotionNotify";
+ extra = g_strdup_printf ("win: 0x%lx x: %d y: %d",
+ event->xmotion.window,
+ event->xmotion.x,
+ event->xmotion.y);
+ break;
+ case EnterNotify:
+ name = "EnterNotify";
+ extra = g_strdup_printf ("win: 0x%lx root: 0x%lx subwindow: 0x%lx mode: %s detail: %s focus: %d x: %d y: %d",
+ event->xcrossing.window,
+ event->xcrossing.root,
+ event->xcrossing.subwindow,
+ meta_event_mode_to_string (event->xcrossing.mode),
+ meta_event_detail_to_string (event->xcrossing.detail),
+ event->xcrossing.focus,
+ event->xcrossing.x,
+ event->xcrossing.y);
+ break;
+ case LeaveNotify:
+ name = "LeaveNotify";
+ extra = g_strdup_printf ("win: 0x%lx root: 0x%lx subwindow: 0x%lx mode: %s detail: %s focus: %d x: %d y: %d",
+ event->xcrossing.window,
+ event->xcrossing.root,
+ event->xcrossing.subwindow,
+ meta_event_mode_to_string (event->xcrossing.mode),
+ meta_event_detail_to_string (event->xcrossing.detail),
+ event->xcrossing.focus,
+ event->xcrossing.x,
+ event->xcrossing.y);
+ break;
+ case FocusIn:
+ name = "FocusIn";
+ extra = g_strdup_printf ("detail: %s mode: %s\n",
+ meta_event_detail_to_string (event->xfocus.detail),
+ meta_event_mode_to_string (event->xfocus.mode));
+ break;
+ case FocusOut:
+ name = "FocusOut";
+ extra = g_strdup_printf ("detail: %s mode: %s\n",
+ meta_event_detail_to_string (event->xfocus.detail),
+ meta_event_mode_to_string (event->xfocus.mode));
+ break;
+ case KeymapNotify:
+ name = "KeymapNotify";
+ break;
+ case Expose:
+ name = "Expose";
+ break;
+ case GraphicsExpose:
+ name = "GraphicsExpose";
+ break;
+ case NoExpose:
+ name = "NoExpose";
+ break;
+ case VisibilityNotify:
+ name = "VisibilityNotify";
+ break;
+ case CreateNotify:
+ name = "CreateNotify";
+ extra = g_strdup_printf ("parent: 0x%lx window: 0x%lx",
+ event->xcreatewindow.parent,
+ event->xcreatewindow.window);
+ break;
+ case DestroyNotify:
+ name = "DestroyNotify";
+ extra = g_strdup_printf ("event: 0x%lx window: 0x%lx",
+ event->xdestroywindow.event,
+ event->xdestroywindow.window);
+ break;
+ case UnmapNotify:
+ name = "UnmapNotify";
+ extra = g_strdup_printf ("event: 0x%lx window: 0x%lx from_configure: %d",
+ event->xunmap.event,
+ event->xunmap.window,
+ event->xunmap.from_configure);
+ break;
+ case MapNotify:
+ name = "MapNotify";
+ extra = g_strdup_printf ("event: 0x%lx window: 0x%lx override_redirect: %d",
+ event->xmap.event,
+ event->xmap.window,
+ event->xmap.override_redirect);
+ break;
+ case MapRequest:
+ name = "MapRequest";
+ extra = g_strdup_printf ("window: 0x%lx parent: 0x%lx\n",
+ event->xmaprequest.window,
+ event->xmaprequest.parent);
+ break;
+ case ReparentNotify:
+ name = "ReparentNotify";
+ extra = g_strdup_printf ("window: 0x%lx parent: 0x%lx event: 0x%lx\n",
+ event->xreparent.window,
+ event->xreparent.parent,
+ event->xreparent.event);
+ break;
+ case ConfigureNotify:
+ name = "ConfigureNotify";
+ extra = g_strdup_printf ("x: %d y: %d w: %d h: %d above: 0x%lx override_redirect: %d",
+ event->xconfigure.x,
+ event->xconfigure.y,
+ event->xconfigure.width,
+ event->xconfigure.height,
+ event->xconfigure.above,
+ event->xconfigure.override_redirect);
+ break;
+ case ConfigureRequest:
+ name = "ConfigureRequest";
+ extra = g_strdup_printf ("parent: 0x%lx window: 0x%lx x: %d %sy: %d %sw: %d %sh: %d %sborder: %d %sabove: %lx %sstackmode: %s %s",
+ event->xconfigurerequest.parent,
+ event->xconfigurerequest.window,
+ event->xconfigurerequest.x,
+ event->xconfigurerequest.value_mask &
+ CWX ? "" : "(unset) ",
+ event->xconfigurerequest.y,
+ event->xconfigurerequest.value_mask &
+ CWY ? "" : "(unset) ",
+ event->xconfigurerequest.width,
+ event->xconfigurerequest.value_mask &
+ CWWidth ? "" : "(unset) ",
+ event->xconfigurerequest.height,
+ event->xconfigurerequest.value_mask &
+ CWHeight ? "" : "(unset) ",
+ event->xconfigurerequest.border_width,
+ event->xconfigurerequest.value_mask &
+ CWBorderWidth ? "" : "(unset)",
+ event->xconfigurerequest.above,
+ event->xconfigurerequest.value_mask &
+ CWSibling ? "" : "(unset)",
+ stack_mode_to_string (event->xconfigurerequest.detail),
+ event->xconfigurerequest.value_mask &
+ CWStackMode ? "" : "(unset)");
+ break;
+ case GravityNotify:
+ name = "GravityNotify";
+ break;
+ case ResizeRequest:
+ name = "ResizeRequest";
+ extra = g_strdup_printf ("width = %d height = %d",
+ event->xresizerequest.width,
+ event->xresizerequest.height);
+ break;
+ case CirculateNotify:
+ name = "CirculateNotify";
+ break;
+ case CirculateRequest:
+ name = "CirculateRequest";
+ break;
+ case PropertyNotify:
+ {
+ char *str;
+ const char *state;
+
+ name = "PropertyNotify";
+
+ meta_error_trap_push (display);
+ str = XGetAtomName (display->xdisplay,
+ event->xproperty.atom);
+ meta_error_trap_pop (display, TRUE);
+
+ if (event->xproperty.state == PropertyNewValue)
+ state = "PropertyNewValue";
+ else if (event->xproperty.state == PropertyDelete)
+ state = "PropertyDelete";
+ else
+ state = "???";
+
+ extra = g_strdup_printf ("atom: %s state: %s",
+ str ? str : "(unknown atom)",
+ state);
+ meta_XFree (str);
+ }
+ break;
+ case SelectionClear:
+ name = "SelectionClear";
+ break;
+ case SelectionRequest:
+ name = "SelectionRequest";
+ break;
+ case SelectionNotify:
+ name = "SelectionNotify";
+ break;
+ case ColormapNotify:
+ name = "ColormapNotify";
+ break;
+ case ClientMessage:
+ {
+ char *str;
+ name = "ClientMessage";
+ meta_error_trap_push (display);
+ str = XGetAtomName (display->xdisplay,
+ event->xclient.message_type);
+ meta_error_trap_pop (display, TRUE);
+ extra = g_strdup_printf ("type: %s format: %d\n",
+ str ? str : "(unknown atom)",
+ event->xclient.format);
+ meta_XFree (str);
+ }
+ break;
+ case MappingNotify:
+ name = "MappingNotify";
+ break;
+ default:
+#ifdef HAVE_XSYNC
+ if (META_DISPLAY_HAS_XSYNC (display) &&
+ event->type == (display->xsync_event_base + XSyncAlarmNotify))
+ {
+ XSyncAlarmNotifyEvent *aevent = (XSyncAlarmNotifyEvent*) event;
+
+ name = "XSyncAlarmNotify";
+ extra =
+ g_strdup_printf ("alarm: 0x%lx"
+ " counter_value: %" G_GINT64_FORMAT
+ " alarm_value: %" G_GINT64_FORMAT
+ " time: %u alarm state: %s",
+ aevent->alarm,
+ (gint64) sync_value_to_64 (&aevent->counter_value),
+ (gint64) sync_value_to_64 (&aevent->alarm_value),
+ (unsigned int)aevent->time,
+ alarm_state_to_string (aevent->state));
+ }
+ else
+#endif /* HAVE_XSYNC */
+#ifdef HAVE_SHAPE
+ if (META_DISPLAY_HAS_SHAPE (display) &&
+ event->type == (display->shape_event_base + ShapeNotify))
+ {
+ XShapeEvent *sev = (XShapeEvent*) event;
+
+ name = "ShapeNotify";
+
+ extra =
+ g_strdup_printf ("kind: %s "
+ "x: %d y: %d w: %u h: %u "
+ "shaped: %d",
+ sev->kind == ShapeBounding ?
+ "ShapeBounding" :
+ (sev->kind == ShapeClip ?
+ "ShapeClip" : "(unknown)"),
+ sev->x, sev->y, sev->width, sev->height,
+ sev->shaped);
+ }
+ else
+#endif /* HAVE_SHAPE */
+ {
+ name = "(Unknown event)";
+ extra = g_strdup_printf ("type: %d", event->xany.type);
+ }
+ break;
+ }
+
+ screen = meta_display_screen_for_root (display, event->xany.window);
+
+ if (screen)
+ winname = g_strdup_printf ("root %d", screen->number);
+ else
+ winname = g_strdup_printf ("0x%lx", event->xany.window);
+
+ meta_topic (META_DEBUG_EVENTS,
+ "%s on %s%s %s %sserial %lu\n", name, winname,
+ extra ? ":" : "", extra ? extra : "",
+ event->xany.send_event ? "SEND " : "",
+ event->xany.serial);
+
+ g_free (winname);
+
+ if (extra)
+ g_free (extra);
+}
+#endif /* WITH_VERBOSE_MODE */
+
+MetaWindow*
+meta_display_lookup_x_window (MetaDisplay *display,
+ Window xwindow)
+{
+ return g_hash_table_lookup (display->window_ids, &xwindow);
+}
+
+void
+meta_display_register_x_window (MetaDisplay *display,
+ Window *xwindowp,
+ MetaWindow *window)
+{
+ g_return_if_fail (g_hash_table_lookup (display->window_ids, xwindowp) == NULL);
+
+ g_hash_table_insert (display->window_ids, xwindowp, window);
+}
+
+void
+meta_display_unregister_x_window (MetaDisplay *display,
+ Window xwindow)
+{
+ g_return_if_fail (g_hash_table_lookup (display->window_ids, &xwindow) != NULL);
+
+ g_hash_table_remove (display->window_ids, &xwindow);
+
+ /* Remove any pending pings */
+ remove_pending_pings_for_window (display, xwindow);
+}
+
+gboolean
+meta_display_xwindow_is_a_no_focus_window (MetaDisplay *display,
+ Window xwindow)
+{
+ gboolean is_a_no_focus_window = FALSE;
+ GSList *temp = display->screens;
+ while (temp != NULL) {
+ MetaScreen *screen = temp->data;
+ if (screen->no_focus_window == xwindow) {
+ is_a_no_focus_window = TRUE;
+ break;
+ }
+ temp = temp->next;
+ }
+
+ return is_a_no_focus_window;
+}
+
+Cursor
+meta_display_create_x_cursor (MetaDisplay *display,
+ MetaCursor cursor)
+{
+ Cursor xcursor;
+ guint glyph;
+
+ switch (cursor)
+ {
+ case META_CURSOR_DEFAULT:
+ glyph = XC_left_ptr;
+ break;
+ case META_CURSOR_NORTH_RESIZE:
+ glyph = XC_top_side;
+ break;
+ case META_CURSOR_SOUTH_RESIZE:
+ glyph = XC_bottom_side;
+ break;
+ case META_CURSOR_WEST_RESIZE:
+ glyph = XC_left_side;
+ break;
+ case META_CURSOR_EAST_RESIZE:
+ glyph = XC_right_side;
+ break;
+ case META_CURSOR_SE_RESIZE:
+ glyph = XC_bottom_right_corner;
+ break;
+ case META_CURSOR_SW_RESIZE:
+ glyph = XC_bottom_left_corner;
+ break;
+ case META_CURSOR_NE_RESIZE:
+ glyph = XC_top_right_corner;
+ break;
+ case META_CURSOR_NW_RESIZE:
+ glyph = XC_top_left_corner;
+ break;
+ case META_CURSOR_MOVE_OR_RESIZE_WINDOW:
+ glyph = XC_fleur;
+ break;
+ case META_CURSOR_BUSY:
+ glyph = XC_watch;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ glyph = 0; /* silence compiler */
+ break;
+ }
+
+ xcursor = XCreateFontCursor (display->xdisplay, glyph);
+
+ return xcursor;
+}
+
+static Cursor
+xcursor_for_op (MetaDisplay *display,
+ MetaGrabOp op)
+{
+ MetaCursor cursor = META_CURSOR_DEFAULT;
+
+ switch (op)
+ {
+ case META_GRAB_OP_RESIZING_SE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ cursor = META_CURSOR_SE_RESIZE;
+ break;
+ case META_GRAB_OP_RESIZING_S:
+ case META_GRAB_OP_KEYBOARD_RESIZING_S:
+ cursor = META_CURSOR_SOUTH_RESIZE;
+ break;
+ case META_GRAB_OP_RESIZING_SW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ cursor = META_CURSOR_SW_RESIZE;
+ break;
+ case META_GRAB_OP_RESIZING_N:
+ case META_GRAB_OP_KEYBOARD_RESIZING_N:
+ cursor = META_CURSOR_NORTH_RESIZE;
+ break;
+ case META_GRAB_OP_RESIZING_NE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ cursor = META_CURSOR_NE_RESIZE;
+ break;
+ case META_GRAB_OP_RESIZING_NW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ cursor = META_CURSOR_NW_RESIZE;
+ break;
+ case META_GRAB_OP_RESIZING_W:
+ case META_GRAB_OP_KEYBOARD_RESIZING_W:
+ cursor = META_CURSOR_WEST_RESIZE;
+ break;
+ case META_GRAB_OP_RESIZING_E:
+ case META_GRAB_OP_KEYBOARD_RESIZING_E:
+ cursor = META_CURSOR_EAST_RESIZE;
+ break;
+ case META_GRAB_OP_MOVING:
+ case META_GRAB_OP_KEYBOARD_MOVING:
+ case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
+ cursor = META_CURSOR_MOVE_OR_RESIZE_WINDOW;
+ break;
+
+ default:
+ break;
+ }
+
+ if (cursor == META_CURSOR_DEFAULT)
+ return None;
+ return meta_display_create_x_cursor (display, cursor);
+}
+
+void
+meta_display_set_grab_op_cursor (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaGrabOp op,
+ gboolean change_pointer,
+ Window grab_xwindow,
+ guint32 timestamp)
+{
+ Cursor cursor;
+
+ cursor = xcursor_for_op (display, op);
+
+#define GRAB_MASK (PointerMotionMask | \
+ ButtonPressMask | ButtonReleaseMask | \
+ EnterWindowMask | LeaveWindowMask)
+
+ if (change_pointer)
+ {
+ meta_error_trap_push_with_return (display);
+ XChangeActivePointerGrab (display->xdisplay,
+ GRAB_MASK,
+ cursor,
+ timestamp);
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Changed pointer with XChangeActivePointerGrab()\n");
+
+ if (meta_error_trap_pop_with_return (display, FALSE) != Success)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Error trapped from XChangeActivePointerGrab()\n");
+ if (display->grab_have_pointer)
+ display->grab_have_pointer = FALSE;
+ }
+ }
+ else
+ {
+ g_assert (screen != NULL);
+
+ meta_error_trap_push (display);
+ if (XGrabPointer (display->xdisplay,
+ grab_xwindow,
+ False,
+ GRAB_MASK,
+ GrabModeAsync, GrabModeAsync,
+ screen->xroot,
+ cursor,
+ timestamp) == GrabSuccess)
+ {
+ display->grab_have_pointer = TRUE;
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "XGrabPointer() returned GrabSuccess time %u\n",
+ timestamp);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "XGrabPointer() failed time %u\n",
+ timestamp);
+ }
+ meta_error_trap_pop (display, TRUE);
+ }
+
+#undef GRAB_MASK
+
+ if (cursor != None)
+ XFreeCursor (display->xdisplay, cursor);
+}
+
+gboolean
+meta_display_begin_grab_op (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ MetaGrabOp op,
+ gboolean pointer_already_grabbed,
+ gboolean frame_action,
+ int button,
+ gulong modmask,
+ guint32 timestamp,
+ int root_x,
+ int root_y)
+{
+ Window grab_xwindow;
+
+ if (grab_op_is_mouse (op) && meta_grab_op_is_moving (op))
+ {
+ if (display->compositor)
+ {
+ meta_compositor_begin_move (display->compositor,
+ window, &window->rect,
+ root_x, root_y);
+ }
+ }
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Doing grab op %u on window %s button %d pointer already grabbed: %d pointer pos %d,%d\n",
+ op, window ? window->desc : "none", button, pointer_already_grabbed,
+ root_x, root_y);
+
+ if (display->grab_op != META_GRAB_OP_NONE)
+ {
+ if (window)
+ meta_warning ("Attempt to perform window operation %u on window %s when operation %u on %s already in effect\n",
+ op, window->desc, display->grab_op,
+ display->grab_window ? display->grab_window->desc : "none");
+ return FALSE;
+ }
+
+ if (window &&
+ (meta_grab_op_is_moving (op) || meta_grab_op_is_resizing (op)))
+ {
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+ else
+ {
+ display->grab_initial_x = root_x;
+ display->grab_initial_y = root_y;
+ display->grab_threshold_movement_reached = FALSE;
+ }
+ }
+
+ /* FIXME:
+ * If we have no MetaWindow we do our best
+ * and try to do the grab on the RootWindow.
+ * This will fail if anyone else has any
+ * key grab on the RootWindow.
+ */
+ if (window)
+ grab_xwindow = window->frame ? window->frame->xwindow : window->xwindow;
+ else
+ grab_xwindow = screen->xroot;
+
+ display->grab_have_pointer = FALSE;
+
+ if (pointer_already_grabbed)
+ display->grab_have_pointer = TRUE;
+
+ meta_display_set_grab_op_cursor (display, screen, op, FALSE, grab_xwindow,
+ timestamp);
+
+ if (!display->grab_have_pointer)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "XGrabPointer() failed\n");
+ return FALSE;
+ }
+
+ /* Grab keys for keyboard ops and mouse move/resizes; see #126497 */
+ if (grab_op_is_keyboard (op) || grab_op_is_mouse_only (op))
+ {
+ if (window)
+ display->grab_have_keyboard =
+ meta_window_grab_all_keys (window, timestamp);
+
+ else
+ display->grab_have_keyboard =
+ meta_screen_grab_all_keys (screen, timestamp);
+
+ if (!display->grab_have_keyboard)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "grabbing all keys failed, ungrabbing pointer\n");
+ XUngrabPointer (display->xdisplay, timestamp);
+ display->grab_have_pointer = FALSE;
+ return FALSE;
+ }
+ }
+
+ display->grab_op = op;
+ display->grab_window = window;
+ display->grab_screen = screen;
+ display->grab_xwindow = grab_xwindow;
+ display->grab_button = button;
+ display->grab_mask = modmask;
+ display->grab_anchor_root_x = root_x;
+ display->grab_anchor_root_y = root_y;
+ display->grab_latest_motion_x = root_x;
+ display->grab_latest_motion_y = root_y;
+ display->grab_last_moveresize_time.tv_sec = 0;
+ display->grab_last_moveresize_time.tv_usec = 0;
+ display->grab_motion_notify_time = 0;
+ display->grab_old_window_stacking = NULL;
+#ifdef HAVE_XSYNC
+ display->grab_sync_request_alarm = None;
+ display->grab_last_user_action_was_snap = FALSE;
+#endif
+ display->grab_was_cancelled = FALSE;
+ display->grab_frame_action = frame_action;
+
+ if (display->grab_resize_timeout_id)
+ {
+ g_source_remove (display->grab_resize_timeout_id);
+ display->grab_resize_timeout_id = 0;
+ }
+
+ if (display->grab_window)
+ {
+ meta_window_get_client_root_coords (display->grab_window,
+ &display->grab_initial_window_pos);
+ display->grab_anchor_window_pos = display->grab_initial_window_pos;
+
+ display->grab_wireframe_active =
+ (meta_prefs_get_reduced_resources () && !meta_prefs_get_mate_accessibility ()) &&
+ (meta_grab_op_is_resizing (display->grab_op) ||
+ meta_grab_op_is_moving (display->grab_op));
+
+ if (display->grab_wireframe_active)
+ {
+ meta_window_calc_showing (display->grab_window);
+ meta_window_begin_wireframe (window);
+ }
+
+#ifdef HAVE_XSYNC
+ if (!display->grab_wireframe_active &&
+ meta_grab_op_is_resizing (display->grab_op) &&
+ display->grab_window->sync_request_counter != None)
+ {
+ XSyncAlarmAttributes values;
+ XSyncValue init;
+
+ meta_error_trap_push_with_return (display);
+
+ /* Set the counter to 0, so we know that the application's
+ * responses to the client messages will always trigger
+ * a PositiveTransition
+ */
+
+ XSyncIntToValue (&init, 0);
+ XSyncSetCounter (display->xdisplay,
+ display->grab_window->sync_request_counter, init);
+
+ display->grab_window->sync_request_serial = 0;
+ display->grab_window->sync_request_time.tv_sec = 0;
+ display->grab_window->sync_request_time.tv_usec = 0;
+
+ values.trigger.counter = display->grab_window->sync_request_counter;
+ values.trigger.value_type = XSyncAbsolute;
+ values.trigger.test_type = XSyncPositiveTransition;
+ XSyncIntToValue (&values.trigger.wait_value,
+ display->grab_window->sync_request_serial + 1);
+
+ /* After triggering, increment test_value by this.
+ * (NOT wait_value above)
+ */
+ XSyncIntToValue (&values.delta, 1);
+
+ /* we want events (on by default anyway) */
+ values.events = True;
+
+ display->grab_sync_request_alarm = XSyncCreateAlarm (display->xdisplay,
+ XSyncCACounter |
+ XSyncCAValueType |
+ XSyncCAValue |
+ XSyncCATestType |
+ XSyncCADelta |
+ XSyncCAEvents,
+ &values);
+
+ if (meta_error_trap_pop_with_return (display, FALSE) != Success)
+ display->grab_sync_request_alarm = None;
+
+ meta_topic (META_DEBUG_RESIZING,
+ "Created update alarm 0x%lx\n",
+ display->grab_sync_request_alarm);
+ }
+#endif
+ }
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Grab op %u on window %s successful\n",
+ display->grab_op, window ? window->desc : "(null)");
+
+ g_assert (display->grab_window != NULL || display->grab_screen != NULL);
+ g_assert (display->grab_op != META_GRAB_OP_NONE);
+
+ /* If this is a move or resize, cache the window edges for
+ * resistance/snapping
+ */
+ if (meta_grab_op_is_resizing (display->grab_op) ||
+ meta_grab_op_is_moving (display->grab_op))
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Computing edges to resist-movement or snap-to for %s.\n",
+ window->desc);
+ meta_display_compute_resistance_and_snapping_edges (display);
+ }
+
+ /* Save the old stacking */
+ if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op))
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Saving old stack positions; old pointer was %p.\n",
+ display->grab_old_window_stacking);
+ display->grab_old_window_stacking =
+ meta_stack_get_positions (screen->stack);
+ }
+
+ /* Do this last, after everything is set up. */
+ switch (op)
+ {
+ case META_GRAB_OP_KEYBOARD_TABBING_NORMAL:
+ meta_screen_ensure_tab_popup (screen,
+ META_TAB_LIST_NORMAL,
+ META_TAB_SHOW_ICON);
+ break;
+ case META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL:
+ meta_screen_ensure_tab_popup (screen,
+ META_TAB_LIST_NORMAL,
+ META_TAB_SHOW_INSTANTLY);
+ break;
+
+ case META_GRAB_OP_KEYBOARD_TABBING_DOCK:
+ meta_screen_ensure_tab_popup (screen,
+ META_TAB_LIST_DOCKS,
+ META_TAB_SHOW_ICON);
+ break;
+ case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK:
+ meta_screen_ensure_tab_popup (screen,
+ META_TAB_LIST_DOCKS,
+ META_TAB_SHOW_INSTANTLY);
+ break;
+ case META_GRAB_OP_KEYBOARD_TABBING_GROUP:
+ meta_screen_ensure_tab_popup (screen,
+ META_TAB_LIST_GROUP,
+ META_TAB_SHOW_ICON);
+ break;
+ case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP:
+ meta_screen_ensure_tab_popup (screen,
+ META_TAB_LIST_GROUP,
+ META_TAB_SHOW_INSTANTLY);
+
+ case META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING:
+ meta_screen_ensure_workspace_popup (screen);
+ break;
+
+ default:
+ break;
+ }
+
+ if (display->grab_window)
+ {
+ meta_window_refresh_resize_popup (display->grab_window);
+ }
+
+ return TRUE;
+}
+
+void
+meta_display_end_grab_op (MetaDisplay *display,
+ guint32 timestamp)
+{
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Ending grab op %u at time %u\n", display->grab_op, timestamp);
+
+ if (display->grab_op == META_GRAB_OP_NONE)
+ return;
+
+ if (display->grab_window != NULL)
+ display->grab_window->shaken_loose = FALSE;
+
+ if (display->grab_window != NULL &&
+ !meta_prefs_get_raise_on_click () &&
+ (meta_grab_op_is_moving (display->grab_op) ||
+ meta_grab_op_is_resizing (display->grab_op)))
+ {
+ /* Only raise the window in orthogonal raise
+ * ('do-not-raise-on-click') mode if the user didn't try to move
+ * or resize the given window by at least a threshold amount.
+ * For raise on click mode, the window was raised at the
+ * beginning of the grab_op.
+ */
+ if (!display->grab_threshold_movement_reached)
+ meta_window_raise (display->grab_window);
+ }
+
+ if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op) ||
+ display->grab_op == META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING)
+ {
+ meta_ui_tab_popup_free (display->grab_screen->tab_popup);
+ display->grab_screen->tab_popup = NULL;
+
+ /* If the ungrab here causes an EnterNotify, ignore it for
+ * sloppy focus
+ */
+ display->ungrab_should_not_cause_focus_window = display->grab_xwindow;
+ }
+
+ /* If this was a move or resize clear out the edge cache */
+ if (meta_grab_op_is_resizing (display->grab_op) ||
+ meta_grab_op_is_moving (display->grab_op))
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Clearing out the edges for resistance/snapping");
+ meta_display_cleanup_edges (display);
+ }
+
+ if (display->grab_old_window_stacking != NULL)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Clearing out the old stack position, which was %p.\n",
+ display->grab_old_window_stacking);
+ g_list_free (display->grab_old_window_stacking);
+ display->grab_old_window_stacking = NULL;
+ }
+
+ if (display->grab_wireframe_active)
+ {
+ display->grab_wireframe_active = FALSE;
+ meta_window_end_wireframe (display->grab_window);
+
+ if (!display->grab_was_cancelled)
+ {
+ if (meta_grab_op_is_moving (display->grab_op))
+ meta_window_move (display->grab_window,
+ TRUE,
+ display->grab_wireframe_rect.x,
+ display->grab_wireframe_rect.y);
+ if (meta_grab_op_is_resizing (display->grab_op))
+ meta_window_resize_with_gravity (display->grab_window,
+ TRUE,
+ display->grab_wireframe_rect.width,
+ display->grab_wireframe_rect.height,
+ meta_resize_gravity_from_grab_op (display->grab_op));
+ }
+ meta_window_calc_showing (display->grab_window);
+ }
+
+ if (display->compositor &&
+ display->grab_window &&
+ grab_op_is_mouse (display->grab_op) &&
+ meta_grab_op_is_moving (display->grab_op))
+ {
+ meta_compositor_end_move (display->compositor,
+ display->grab_window);
+ }
+
+ if (display->grab_have_pointer)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Ungrabbing pointer with timestamp %u\n", timestamp);
+ XUngrabPointer (display->xdisplay, timestamp);
+ }
+
+ if (display->grab_have_keyboard)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Ungrabbing all keys timestamp %u\n", timestamp);
+ if (display->grab_window)
+ meta_window_ungrab_all_keys (display->grab_window, timestamp);
+ else
+ meta_screen_ungrab_all_keys (display->grab_screen, timestamp);
+ }
+
+#ifdef HAVE_XSYNC
+ if (display->grab_sync_request_alarm != None)
+ {
+ XSyncDestroyAlarm (display->xdisplay,
+ display->grab_sync_request_alarm);
+ display->grab_sync_request_alarm = None;
+ }
+#endif /* HAVE_XSYNC */
+
+ display->grab_window = NULL;
+ display->grab_screen = NULL;
+ display->grab_xwindow = None;
+ display->grab_op = META_GRAB_OP_NONE;
+
+ if (display->grab_resize_popup)
+ {
+ meta_ui_resize_popup_free (display->grab_resize_popup);
+ display->grab_resize_popup = NULL;
+ }
+
+ if (display->grab_resize_timeout_id)
+ {
+ g_source_remove (display->grab_resize_timeout_id);
+ display->grab_resize_timeout_id = 0;
+ }
+}
+
+void
+meta_display_check_threshold_reached (MetaDisplay *display,
+ int x,
+ int y)
+{
+ /* Don't bother doing the check again if we've already reached the threshold */
+ if (meta_prefs_get_raise_on_click () ||
+ display->grab_threshold_movement_reached)
+ return;
+
+ if (ABS (display->grab_initial_x - x) >= 8 ||
+ ABS (display->grab_initial_y - y) >= 8)
+ display->grab_threshold_movement_reached = TRUE;
+}
+
+static void
+meta_change_button_grab (MetaDisplay *display,
+ Window xwindow,
+ gboolean grab,
+ gboolean sync,
+ int button,
+ int modmask)
+{
+ unsigned int ignored_mask;
+
+ meta_verbose ("%s 0x%lx sync = %d button = %d modmask 0x%x\n",
+ grab ? "Grabbing" : "Ungrabbing",
+ xwindow,
+ sync, button, modmask);
+
+ meta_error_trap_push (display);
+
+ ignored_mask = 0;
+ while (ignored_mask <= display->ignored_modifier_mask)
+ {
+ if (ignored_mask & ~(display->ignored_modifier_mask))
+ {
+ /* Not a combination of ignored modifiers
+ * (it contains some non-ignored modifiers)
+ */
+ ++ignored_mask;
+ continue;
+ }
+
+ if (meta_is_debugging ())
+ meta_error_trap_push_with_return (display);
+
+ /* GrabModeSync means freeze until XAllowEvents */
+
+ if (grab)
+ XGrabButton (display->xdisplay, button, modmask | ignored_mask,
+ xwindow, False,
+ ButtonPressMask | ButtonReleaseMask |
+ PointerMotionMask | PointerMotionHintMask,
+ sync ? GrabModeSync : GrabModeAsync,
+ GrabModeAsync,
+ False, None);
+ else
+ XUngrabButton (display->xdisplay, button, modmask | ignored_mask,
+ xwindow);
+
+ if (meta_is_debugging ())
+ {
+ int result;
+
+ result = meta_error_trap_pop_with_return (display, FALSE);
+
+ if (result != Success)
+ meta_verbose ("Failed to %s button %d with mask 0x%x for window 0x%lx error code %d\n",
+ grab ? "grab" : "ungrab",
+ button, modmask | ignored_mask, xwindow, result);
+ }
+
+ ++ignored_mask;
+ }
+
+ meta_error_trap_pop (display, FALSE);
+}
+
+void
+meta_display_grab_window_buttons (MetaDisplay *display,
+ Window xwindow)
+{
+ /* Grab Alt + button1 for moving window.
+ * Grab Alt + button2 for resizing window.
+ * Grab Alt + button3 for popping up window menu.
+ * Grab Alt + Shift + button1 for snap-moving window.
+ */
+ meta_verbose ("Grabbing window buttons for 0x%lx\n", xwindow);
+
+ /* FIXME If we ignored errors here instead of spewing, we could
+ * put one big error trap around the loop and avoid a bunch of
+ * XSync()
+ */
+
+ if (display->window_grab_modifiers != 0)
+ {
+ gboolean debug = g_getenv ("MARCO_DEBUG_BUTTON_GRABS") != NULL;
+ int i;
+ for (i = 1; i < 4; i++)
+ {
+ meta_change_button_grab (display, xwindow,
+ TRUE,
+ FALSE,
+ i, display->window_grab_modifiers);
+
+ /* This is for debugging, since I end up moving the Xnest
+ * otherwise ;-)
+ */
+ if (debug)
+ meta_change_button_grab (display, xwindow,
+ TRUE,
+ FALSE,
+ i, ControlMask);
+ }
+
+ /* In addition to grabbing Alt+Button1 for moving the window,
+ * grab Alt+Shift+Button1 for snap-moving the window. See bug
+ * 112478. Unfortunately, this doesn't work with
+ * Shift+Alt+Button1 for some reason; so at least part of the
+ * order still matters, which sucks (please FIXME).
+ */
+ meta_change_button_grab (display, xwindow,
+ TRUE,
+ FALSE,
+ 1, display->window_grab_modifiers | ShiftMask);
+ }
+}
+
+void
+meta_display_ungrab_window_buttons (MetaDisplay *display,
+ Window xwindow)
+{
+ gboolean debug;
+ int i;
+
+ if (display->window_grab_modifiers == 0)
+ return;
+
+ debug = g_getenv ("MARCO_DEBUG_BUTTON_GRABS") != NULL;
+ i = 1;
+ while (i < 4)
+ {
+ meta_change_button_grab (display, xwindow,
+ FALSE, FALSE, i,
+ display->window_grab_modifiers);
+
+ if (debug)
+ meta_change_button_grab (display, xwindow,
+ FALSE, FALSE, i, ControlMask);
+
+ ++i;
+ }
+}
+
+/* Grab buttons we only grab while unfocused in click-to-focus mode */
+#define MAX_FOCUS_BUTTON 4
+void
+meta_display_grab_focus_window_button (MetaDisplay *display,
+ MetaWindow *window)
+{
+ /* Grab button 1 for activating unfocused windows */
+ meta_verbose ("Grabbing unfocused window buttons for %s\n", window->desc);
+
+#if 0
+ /* FIXME:115072 */
+ /* Don't grab at all unless in click to focus mode. In click to
+ * focus, we may sometimes be clever about intercepting and eating
+ * the focus click. But in mouse focus, we never do that since the
+ * focus window may not be raised, and who wants to think about
+ * mouse focus anyway.
+ */
+ if (meta_prefs_get_focus_mode () != META_FOCUS_MODE_CLICK)
+ {
+ meta_verbose (" (well, not grabbing since not in click to focus mode)\n");
+ return;
+ }
+#endif
+
+ if (window->have_focus_click_grab)
+ {
+ meta_verbose (" (well, not grabbing since we already have the grab)\n");
+ return;
+ }
+
+ /* FIXME If we ignored errors here instead of spewing, we could
+ * put one big error trap around the loop and avoid a bunch of
+ * XSync()
+ */
+
+ {
+ int i = 1;
+ while (i < MAX_FOCUS_BUTTON)
+ {
+ meta_change_button_grab (display,
+ window->xwindow,
+ TRUE, TRUE,
+ i, 0);
+
+ ++i;
+ }
+
+ window->have_focus_click_grab = TRUE;
+ }
+}
+
+void
+meta_display_ungrab_focus_window_button (MetaDisplay *display,
+ MetaWindow *window)
+{
+ meta_verbose ("Ungrabbing unfocused window buttons for %s\n", window->desc);
+
+ if (!window->have_focus_click_grab)
+ return;
+
+ {
+ int i = 1;
+ while (i < MAX_FOCUS_BUTTON)
+ {
+ meta_change_button_grab (display, window->xwindow,
+ FALSE, FALSE, i, 0);
+
+ ++i;
+ }
+
+ window->have_focus_click_grab = FALSE;
+ }
+}
+
+void
+meta_display_increment_event_serial (MetaDisplay *display)
+{
+ /* We just make some random X request */
+ XDeleteProperty (display->xdisplay, display->leader_window,
+ display->atom__MOTIF_WM_HINTS);
+}
+
+void
+meta_display_update_active_window_hint (MetaDisplay *display)
+{
+ GSList *tmp;
+
+ gulong data[1];
+
+ if (display->focus_window)
+ data[0] = display->focus_window->xwindow;
+ else
+ data[0] = None;
+
+ tmp = display->screens;
+ while (tmp != NULL)
+ {
+ MetaScreen *screen = tmp->data;
+
+ meta_error_trap_push (display);
+ XChangeProperty (display->xdisplay, screen->xroot,
+ display->atom__NET_ACTIVE_WINDOW,
+ XA_WINDOW,
+ 32, PropModeReplace, (guchar*) data, 1);
+
+ meta_error_trap_pop (display, FALSE);
+
+ tmp = tmp->next;
+ }
+}
+
+void
+meta_display_queue_retheme_all_windows (MetaDisplay *display)
+{
+ GSList* windows;
+ GSList *tmp;
+
+ windows = meta_display_list_windows (display);
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *window = tmp->data;
+
+ meta_window_queue (window, META_QUEUE_MOVE_RESIZE);
+ if (window->frame)
+ {
+ window->frame->need_reapply_frame_shape = TRUE;
+
+ meta_frame_queue_draw (window->frame);
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (windows);
+}
+
+void
+meta_display_retheme_all (void)
+{
+ meta_display_queue_retheme_all_windows (meta_get_display ());
+}
+
+void
+meta_display_set_cursor_theme (const char *theme,
+ int size)
+{
+#ifdef HAVE_XCURSOR
+ GSList *tmp;
+
+ MetaDisplay *display = meta_get_display ();
+
+ XcursorSetTheme (display->xdisplay, theme);
+ XcursorSetDefaultSize (display->xdisplay, size);
+
+ tmp = display->screens;
+ while (tmp != NULL)
+ {
+ MetaScreen *screen = tmp->data;
+
+ meta_screen_update_cursor (screen);
+
+ tmp = tmp->next;
+ }
+
+#endif
+}
+
+/**
+ * Stores whether syncing is currently enabled.
+ */
+static gboolean is_syncing = FALSE;
+
+/**
+ * Returns whether X synchronisation is currently enabled.
+ *
+ * \return true if we must wait for events whenever we send X requests;
+ * false otherwise.
+ *
+ * \bug This is *only* called by meta_display_open, but by that time
+ * we have already turned syncing on or off on startup, and we don't
+ * have any way to do so while Marco is running, so it's rather
+ * pointless.
+ */
+gboolean
+meta_is_syncing (void)
+{
+ return is_syncing;
+}
+
+/**
+ * A handy way to turn on synchronisation on or off for every display.
+ *
+ * \bug Of course there is only one display ever anyway, so this can
+ * be rather hugely simplified.
+ */
+void
+meta_set_syncing (gboolean setting)
+{
+ if (setting != is_syncing)
+ {
+ is_syncing = setting;
+ if (meta_get_display ())
+ XSynchronize (meta_get_display ()->xdisplay, is_syncing);
+ }
+}
+
+/**
+ * How long, in milliseconds, we should wait after pinging a window
+ * before deciding it's not going to get back to us.
+ */
+#define PING_TIMEOUT_DELAY 5000
+
+/**
+ * Does whatever it is we decided to do when a window didn't respond
+ * to a ping. We also remove the ping from the display's list of
+ * pending pings. This function is called by the event loop when the timeout
+ * times out which we created at the start of the ping.
+ *
+ * \param data All the information about this ping. It is a MetaPingData
+ * cast to a void* in order to be passable to a timeout function.
+ * This function will also free this parameter.
+ *
+ * \return Always returns false, because this function is called as a
+ * timeout and we don't want to run the timer again.
+ *
+ * \ingroup pings
+ */
+static gboolean
+meta_display_ping_timeout (gpointer data)
+{
+ MetaPingData *ping_data;
+
+ ping_data = data;
+
+ ping_data->ping_timeout_id = 0;
+
+ meta_topic (META_DEBUG_PING,
+ "Ping %u on window %lx timed out\n",
+ ping_data->timestamp, ping_data->xwindow);
+
+ (* ping_data->ping_timeout_func) (ping_data->display, ping_data->xwindow,
+ ping_data->timestamp, ping_data->user_data);
+
+ ping_data->display->pending_pings =
+ g_slist_remove (ping_data->display->pending_pings,
+ ping_data);
+ ping_data_free (ping_data);
+
+ return FALSE;
+}
+
+/**
+ * Sends a ping request to a window. The window must respond to
+ * the request within a certain amount of time. If it does, we
+ * will call one callback; if the time passes and we haven't had
+ * a response, we call a different callback. The window must have
+ * the hint showing that it can respond to a ping; if it doesn't,
+ * we call the "got a response" callback immediately and return.
+ * This function returns straight away after setting things up;
+ * the callbacks will be called from the event loop.
+ *
+ * \param display The MetaDisplay that the window is on
+ * \param window The MetaWindow to send the ping to
+ * \param timestamp The timestamp of the ping. Used for uniqueness.
+ * Cannot be CurrentTime; use a real timestamp!
+ * \param ping_reply_func The callback to call if we get a response.
+ * \param ping_timeout_func The callback to call if we don't get a response.
+ * \param user_data Arbitrary data that will be passed to the callback
+ * function. (In practice it's often a pointer to
+ * the window.)
+ *
+ * \bug This should probably be a method on windows, rather than displays
+ * for one of their windows.
+ *
+ * \ingroup pings
+ */
+void
+meta_display_ping_window (MetaDisplay *display,
+ MetaWindow *window,
+ guint32 timestamp,
+ MetaWindowPingFunc ping_reply_func,
+ MetaWindowPingFunc ping_timeout_func,
+ gpointer user_data)
+{
+ MetaPingData *ping_data;
+
+ if (timestamp == CurrentTime)
+ {
+ meta_warning ("Tried to ping a window with CurrentTime! Not allowed.\n");
+ return;
+ }
+
+ if (!window->net_wm_ping)
+ {
+ if (ping_reply_func)
+ (* ping_reply_func) (display, window->xwindow, timestamp, user_data);
+
+ return;
+ }
+
+ ping_data = g_new (MetaPingData, 1);
+ ping_data->display = display;
+ ping_data->xwindow = window->xwindow;
+ ping_data->timestamp = timestamp;
+ ping_data->ping_reply_func = ping_reply_func;
+ ping_data->ping_timeout_func = ping_timeout_func;
+ ping_data->user_data = user_data;
+ ping_data->ping_timeout_id = g_timeout_add (PING_TIMEOUT_DELAY,
+ meta_display_ping_timeout,
+ ping_data);
+
+ display->pending_pings = g_slist_prepend (display->pending_pings, ping_data);
+
+ meta_topic (META_DEBUG_PING,
+ "Sending ping with timestamp %u to window %s\n",
+ timestamp, window->desc);
+ meta_window_send_icccm_message (window,
+ display->atom__NET_WM_PING,
+ timestamp);
+}
+
+static void
+process_request_frame_extents (MetaDisplay *display,
+ XEvent *event)
+{
+ /* The X window whose frame extents will be set. */
+ Window xwindow = event->xclient.window;
+ unsigned long data[4] = { 0, 0, 0, 0 };
+
+ MotifWmHints *hints = NULL;
+ gboolean hints_set = FALSE;
+
+ meta_verbose ("Setting frame extents for 0x%lx\n", xwindow);
+
+ /* See if the window is decorated. */
+ hints_set = meta_prop_get_motif_hints (display,
+ xwindow,
+ display->atom__MOTIF_WM_HINTS,
+ &hints);
+ if ((hints_set && hints->decorations) || !hints_set)
+ {
+ int top = 0;
+ int bottom = 0;
+ int left = 0;
+ int right = 0;
+
+ MetaScreen *screen;
+
+ screen = meta_display_screen_for_xwindow (display,
+ event->xclient.window);
+ if (screen == NULL)
+ {
+ meta_warning ("Received request to set _NET_FRAME_EXTENTS "
+ "on 0x%lx which is on a screen we are not managing\n",
+ event->xclient.window);
+ meta_XFree (hints);
+ return;
+ }
+
+ /* Return estimated frame extents for a normal window. */
+ meta_ui_theme_get_frame_borders (screen->ui,
+ META_FRAME_TYPE_NORMAL,
+ 0,
+ &top,
+ &bottom,
+ &left,
+ &right);
+
+ data[0] = left;
+ data[1] = right;
+ data[2] = top;
+ data[3] = bottom;
+ }
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Setting _NET_FRAME_EXTENTS on unmanaged window 0x%lx "
+ "to top = %lu, left = %lu, bottom = %lu, right = %lu\n",
+ xwindow, data[0], data[1], data[2], data[3]);
+
+ meta_error_trap_push (display);
+ XChangeProperty (display->xdisplay, xwindow,
+ display->atom__NET_FRAME_EXTENTS,
+ XA_CARDINAL,
+ 32, PropModeReplace, (guchar*) data, 4);
+ meta_error_trap_pop (display, FALSE);
+
+ meta_XFree (hints);
+}
+
+/**
+ * Process the pong (the response message) from the ping we sent
+ * to the window. This involves removing the timeout, calling the
+ * reply handler function, and freeing memory.
+ *
+ * \param display the display we got the pong from
+ * \param event the XEvent which is a pong; we can tell which
+ * ping it corresponds to because it bears the
+ * same timestamp.
+ *
+ * \ingroup pings
+ */
+static void
+process_pong_message (MetaDisplay *display,
+ XEvent *event)
+{
+ GSList *tmp;
+ guint32 timestamp = event->xclient.data.l[1];
+
+ meta_topic (META_DEBUG_PING, "Received a pong with timestamp %u\n",
+ timestamp);
+
+ for (tmp = display->pending_pings; tmp; tmp = tmp->next)
+ {
+ MetaPingData *ping_data = tmp->data;
+
+ if (timestamp == ping_data->timestamp)
+ {
+ meta_topic (META_DEBUG_PING,
+ "Matching ping found for pong %u\n",
+ ping_data->timestamp);
+
+ /* Remove the ping data from the list */
+ display->pending_pings = g_slist_remove (display->pending_pings,
+ ping_data);
+
+ /* Remove the timeout */
+ if (ping_data->ping_timeout_id != 0)
+ {
+ g_source_remove (ping_data->ping_timeout_id);
+ ping_data->ping_timeout_id = 0;
+ }
+
+ /* Call callback */
+ (* ping_data->ping_reply_func) (display,
+ ping_data->xwindow,
+ ping_data->timestamp,
+ ping_data->user_data);
+
+ ping_data_free (ping_data);
+
+ break;
+ }
+ }
+}
+
+/**
+ * Finds whether a window has any pings waiting on it.
+ *
+ * \param display The MetaDisplay of the window.
+ * \param window The MetaWindow whose pings we want to know about.
+ *
+ * \return True if there is at least one ping which has been sent
+ * to the window without getting a response; false otherwise.
+ *
+ * \bug This should probably be a method on windows, rather than displays
+ * for one of their windows.
+ *
+ * \ingroup pings
+ */
+gboolean
+meta_display_window_has_pending_pings (MetaDisplay *display,
+ MetaWindow *window)
+{
+ GSList *tmp;
+
+ for (tmp = display->pending_pings; tmp; tmp = tmp->next)
+ {
+ MetaPingData *ping_data = tmp->data;
+
+ if (ping_data->xwindow == window->xwindow)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+MetaGroup*
+get_focussed_group (MetaDisplay *display)
+{
+ if (display->focus_window)
+ return display->focus_window->group;
+ else
+ return NULL;
+}
+
+#define IN_TAB_CHAIN(w,t) (((t) == META_TAB_LIST_NORMAL && META_WINDOW_IN_NORMAL_TAB_CHAIN (w)) \
+ || ((t) == META_TAB_LIST_DOCKS && META_WINDOW_IN_DOCK_TAB_CHAIN (w)) \
+ || ((t) == META_TAB_LIST_GROUP && META_WINDOW_IN_GROUP_TAB_CHAIN (w, get_focussed_group(w->display))))
+
+static MetaWindow*
+find_tab_forward (MetaDisplay *display,
+ MetaTabList type,
+ MetaScreen *screen,
+ MetaWorkspace *workspace,
+ GList *start,
+ gboolean skip_first)
+{
+ GList *tmp;
+
+ g_return_val_if_fail (start != NULL, NULL);
+ g_return_val_if_fail (workspace != NULL, NULL);
+
+ tmp = start;
+ if (skip_first)
+ tmp = tmp->next;
+
+ while (tmp != NULL)
+ {
+ MetaWindow *window = tmp->data;
+
+ if (window->screen == screen &&
+ IN_TAB_CHAIN (window, type))
+ return window;
+
+ tmp = tmp->next;
+ }
+
+ tmp = workspace->mru_list;
+ while (tmp != start && tmp != NULL)
+ {
+ MetaWindow *window = tmp->data;
+
+ if (IN_TAB_CHAIN (window, type))
+ return window;
+
+ tmp = tmp->next;
+ }
+
+ return NULL;
+}
+
+static MetaWindow*
+find_tab_backward (MetaDisplay *display,
+ MetaTabList type,
+ MetaScreen *screen,
+ MetaWorkspace *workspace,
+ GList *start,
+ gboolean skip_last)
+{
+ GList *tmp;
+
+ g_return_val_if_fail (start != NULL, NULL);
+ g_return_val_if_fail (workspace != NULL, NULL);
+
+ tmp = start;
+ if (skip_last)
+ tmp = tmp->prev;
+ while (tmp != NULL)
+ {
+ MetaWindow *window = tmp->data;
+
+ if (window->screen == screen &&
+ IN_TAB_CHAIN (window, type))
+ return window;
+
+ tmp = tmp->prev;
+ }
+
+ tmp = g_list_last (workspace->mru_list);
+ while (tmp != start)
+ {
+ MetaWindow *window = tmp->data;
+
+ if (IN_TAB_CHAIN (window, type))
+ return window;
+
+ tmp = tmp->prev;
+ }
+
+ return NULL;
+}
+
+GList*
+meta_display_get_tab_list (MetaDisplay *display,
+ MetaTabList type,
+ MetaScreen *screen,
+ MetaWorkspace *workspace)
+{
+ GList *tab_list;
+
+ g_return_val_if_fail (workspace != NULL, NULL);
+
+ /* Windows sellout mode - MRU order. Collect unminimized windows
+ * then minimized so minimized windows aren't in the way so much.
+ */
+ {
+ GList *tmp;
+
+ tab_list = NULL;
+ tmp = workspace->mru_list;
+ while (tmp != NULL)
+ {
+ MetaWindow *window = tmp->data;
+
+ if (!window->minimized &&
+ window->screen == screen &&
+ IN_TAB_CHAIN (window, type))
+ tab_list = g_list_prepend (tab_list, window);
+
+ tmp = tmp->next;
+ }
+ }
+
+ {
+ GList *tmp;
+
+ tmp = workspace->mru_list;
+ while (tmp != NULL)
+ {
+ MetaWindow *window = tmp->data;
+
+ if (window->minimized &&
+ window->screen == screen &&
+ IN_TAB_CHAIN (window, type))
+ tab_list = g_list_prepend (tab_list, window);
+
+ tmp = tmp->next;
+ }
+ }
+
+ tab_list = g_list_reverse (tab_list);
+
+ {
+ GSList *tmp;
+ MetaWindow *l_window;
+
+ tmp = meta_display_list_windows (display);
+
+ /* Go through all windows */
+ while (tmp != NULL)
+ {
+ l_window=tmp->data;
+
+ /* Check to see if it demands attention */
+ if (l_window->wm_state_demands_attention &&
+ l_window->workspace!=workspace &&
+ IN_TAB_CHAIN (l_window, type))
+ {
+ /* if it does, add it to the popup */
+ tab_list = g_list_prepend (tab_list, l_window);
+ }
+
+ tmp = tmp->next;
+ } /* End while tmp!=NULL */
+ }
+
+ return tab_list;
+}
+
+MetaWindow*
+meta_display_get_tab_next (MetaDisplay *display,
+ MetaTabList type,
+ MetaScreen *screen,
+ MetaWorkspace *workspace,
+ MetaWindow *window,
+ gboolean backward)
+{
+ gboolean skip;
+ GList *tab_list;
+ MetaWindow *ret;
+ tab_list = meta_display_get_tab_list(display,
+ type,
+ screen,
+ workspace);
+
+ if (tab_list == NULL)
+ return NULL;
+
+ if (window != NULL)
+ {
+ g_assert (window->display == display);
+
+ if (backward)
+ ret = find_tab_backward (display, type, screen, workspace,
+ g_list_find (tab_list,
+ window),
+ TRUE);
+ else
+ ret = find_tab_forward (display, type, screen, workspace,
+ g_list_find (tab_list,
+ window),
+ TRUE);
+ }
+ else
+ {
+ skip = display->focus_window != NULL &&
+ tab_list->data == display->focus_window;
+ if (backward)
+ ret = find_tab_backward (display, type, screen, workspace,
+ tab_list, skip);
+ else
+ ret = find_tab_forward (display, type, screen, workspace,
+ tab_list, skip);
+ }
+
+ g_list_free (tab_list);
+ return ret;
+}
+
+MetaWindow*
+meta_display_get_tab_current (MetaDisplay *display,
+ MetaTabList type,
+ MetaScreen *screen,
+ MetaWorkspace *workspace)
+{
+ MetaWindow *window;
+
+ window = display->focus_window;
+
+ if (window != NULL &&
+ window->screen == screen &&
+ IN_TAB_CHAIN (window, type) &&
+ (workspace == NULL ||
+ meta_window_located_on_workspace (window, workspace)))
+ return window;
+ else
+ return NULL;
+}
+
+int
+meta_resize_gravity_from_grab_op (MetaGrabOp op)
+{
+ int gravity;
+
+ gravity = -1;
+ switch (op)
+ {
+ case META_GRAB_OP_RESIZING_SE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ gravity = NorthWestGravity;
+ break;
+ case META_GRAB_OP_KEYBOARD_RESIZING_S:
+ case META_GRAB_OP_RESIZING_S:
+ gravity = NorthGravity;
+ break;
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ case META_GRAB_OP_RESIZING_SW:
+ gravity = NorthEastGravity;
+ break;
+ case META_GRAB_OP_KEYBOARD_RESIZING_N:
+ case META_GRAB_OP_RESIZING_N:
+ gravity = SouthGravity;
+ break;
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ case META_GRAB_OP_RESIZING_NE:
+ gravity = SouthWestGravity;
+ break;
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ case META_GRAB_OP_RESIZING_NW:
+ gravity = SouthEastGravity;
+ break;
+ case META_GRAB_OP_KEYBOARD_RESIZING_E:
+ case META_GRAB_OP_RESIZING_E:
+ gravity = WestGravity;
+ break;
+ case META_GRAB_OP_KEYBOARD_RESIZING_W:
+ case META_GRAB_OP_RESIZING_W:
+ gravity = EastGravity;
+ break;
+ case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
+ gravity = CenterGravity;
+ break;
+ default:
+ break;
+ }
+
+ return gravity;
+}
+
+static MetaScreen*
+find_screen_for_selection (MetaDisplay *display,
+ Window owner,
+ Atom selection)
+{
+ GSList *tmp;
+
+ tmp = display->screens;
+ while (tmp != NULL)
+ {
+ MetaScreen *screen = tmp->data;
+
+ if (screen->wm_sn_selection_window == owner &&
+ screen->wm_sn_atom == selection)
+ return screen;
+
+ tmp = tmp->next;
+ }
+
+ return NULL;
+}
+
+/* from fvwm2, Copyright Matthias Clasen, Dominik Vogt */
+static gboolean
+convert_property (MetaDisplay *display,
+ MetaScreen *screen,
+ Window w,
+ Atom target,
+ Atom property)
+{
+#define N_TARGETS 4
+ Atom conversion_targets[N_TARGETS];
+ long icccm_version[] = { 2, 0 };
+
+ conversion_targets[0] = display->atom_TARGETS;
+ conversion_targets[1] = display->atom_MULTIPLE;
+ conversion_targets[2] = display->atom_TIMESTAMP;
+ conversion_targets[3] = display->atom_VERSION;
+
+ meta_error_trap_push_with_return (display);
+ if (target == display->atom_TARGETS)
+ XChangeProperty (display->xdisplay, w, property,
+ XA_ATOM, 32, PropModeReplace,
+ (unsigned char *)conversion_targets, N_TARGETS);
+ else if (target == display->atom_TIMESTAMP)
+ XChangeProperty (display->xdisplay, w, property,
+ XA_INTEGER, 32, PropModeReplace,
+ (unsigned char *)&screen->wm_sn_timestamp, 1);
+ else if (target == display->atom_VERSION)
+ XChangeProperty (display->xdisplay, w, property,
+ XA_INTEGER, 32, PropModeReplace,
+ (unsigned char *)icccm_version, 2);
+ else
+ {
+ meta_error_trap_pop_with_return (display, FALSE);
+ return FALSE;
+ }
+
+ if (meta_error_trap_pop_with_return (display, FALSE) != Success)
+ return FALSE;
+
+ /* Be sure the PropertyNotify has arrived so we
+ * can send SelectionNotify
+ */
+ /* FIXME the error trap pop synced anyway, right? */
+ meta_topic (META_DEBUG_SYNC, "Syncing on %s\n", G_STRFUNC);
+ XSync (display->xdisplay, False);
+
+ return TRUE;
+}
+
+/* from fvwm2, Copyright Matthias Clasen, Dominik Vogt */
+static void
+process_selection_request (MetaDisplay *display,
+ XEvent *event)
+{
+ XSelectionEvent reply;
+ MetaScreen *screen;
+
+ screen = find_screen_for_selection (display,
+ event->xselectionrequest.owner,
+ event->xselectionrequest.selection);
+
+ if (screen == NULL)
+ {
+ char *str;
+
+ meta_error_trap_push (display);
+ str = XGetAtomName (display->xdisplay,
+ event->xselectionrequest.selection);
+ meta_error_trap_pop (display, TRUE);
+
+ meta_verbose ("Selection request with selection %s window 0x%lx not a WM_Sn selection we recognize\n",
+ str ? str : "(bad atom)", event->xselectionrequest.owner);
+
+ meta_XFree (str);
+
+ return;
+ }
+
+ reply.type = SelectionNotify;
+ reply.display = display->xdisplay;
+ reply.requestor = event->xselectionrequest.requestor;
+ reply.selection = event->xselectionrequest.selection;
+ reply.target = event->xselectionrequest.target;
+ reply.property = None;
+ reply.time = event->xselectionrequest.time;
+
+ if (event->xselectionrequest.target == display->atom_MULTIPLE)
+ {
+ if (event->xselectionrequest.property != None)
+ {
+ Atom type, *adata;
+ int i, format;
+ unsigned long num, rest;
+ unsigned char *data;
+
+ meta_error_trap_push_with_return (display);
+ if (XGetWindowProperty (display->xdisplay,
+ event->xselectionrequest.requestor,
+ event->xselectionrequest.property, 0, 256, False,
+ display->atom_ATOM_PAIR,
+ &type, &format, &num, &rest, &data) != Success)
+ {
+ meta_error_trap_pop_with_return (display, TRUE);
+ return;
+ }
+
+ if (meta_error_trap_pop_with_return (display, TRUE) == Success)
+ {
+ /* FIXME: to be 100% correct, should deal with rest > 0,
+ * but since we have 4 possible targets, we will hardly ever
+ * meet multiple requests with a length > 8
+ */
+ adata = (Atom*)data;
+ i = 0;
+ while (i < (int) num)
+ {
+ if (!convert_property (display, screen,
+ event->xselectionrequest.requestor,
+ adata[i], adata[i+1]))
+ adata[i+1] = None;
+ i += 2;
+ }
+
+ meta_error_trap_push (display);
+ XChangeProperty (display->xdisplay,
+ event->xselectionrequest.requestor,
+ event->xselectionrequest.property,
+ display->atom_ATOM_PAIR,
+ 32, PropModeReplace, data, num);
+ meta_error_trap_pop (display, FALSE);
+ meta_XFree (data);
+ }
+ }
+ }
+ else
+ {
+ if (event->xselectionrequest.property == None)
+ event->xselectionrequest.property = event->xselectionrequest.target;
+
+ if (convert_property (display, screen,
+ event->xselectionrequest.requestor,
+ event->xselectionrequest.target,
+ event->xselectionrequest.property))
+ reply.property = event->xselectionrequest.property;
+ }
+
+ XSendEvent (display->xdisplay,
+ event->xselectionrequest.requestor,
+ False, 0L, (XEvent*)&reply);
+
+ meta_verbose ("Handled selection request\n");
+}
+
+static void
+process_selection_clear (MetaDisplay *display,
+ XEvent *event)
+{
+ /* We need to unmanage the screen on which we lost the selection */
+ MetaScreen *screen;
+
+ screen = find_screen_for_selection (display,
+ event->xselectionclear.window,
+ event->xselectionclear.selection);
+
+
+ if (screen != NULL)
+ {
+ meta_verbose ("Got selection clear for screen %d on display %s\n",
+ screen->number, display->name);
+
+ meta_display_unmanage_screen (&display,
+ screen,
+ event->xselectionclear.time);
+
+ if (!display)
+ the_display = NULL;
+
+ /* display and screen may both be invalid memory... */
+
+ return;
+ }
+
+ {
+ char *str;
+
+ meta_error_trap_push (display);
+ str = XGetAtomName (display->xdisplay,
+ event->xselectionclear.selection);
+ meta_error_trap_pop (display, TRUE);
+
+ meta_verbose ("Selection clear with selection %s window 0x%lx not a WM_Sn selection we recognize\n",
+ str ? str : "(bad atom)", event->xselectionclear.window);
+
+ meta_XFree (str);
+ }
+}
+
+void
+meta_display_unmanage_screen (MetaDisplay **displayp,
+ MetaScreen *screen,
+ guint32 timestamp)
+{
+ MetaDisplay *display = *displayp;
+
+ meta_verbose ("Unmanaging screen %d on display %s\n",
+ screen->number, display->name);
+
+ g_return_if_fail (g_slist_find (display->screens, screen) != NULL);
+
+ meta_screen_free (screen, timestamp);
+ display->screens = g_slist_remove (display->screens, screen);
+
+ if (display->screens == NULL)
+ {
+ meta_display_close (display, timestamp);
+ *displayp = NULL;
+ }
+}
+
+void
+meta_display_unmanage_windows_for_screen (MetaDisplay *display,
+ MetaScreen *screen,
+ guint32 timestamp)
+{
+ GSList *tmp;
+ GSList *winlist;
+
+ winlist = meta_display_list_windows (display);
+ winlist = g_slist_sort (winlist, meta_display_stack_cmp);
+
+ /* Unmanage all windows */
+ tmp = winlist;
+ while (tmp != NULL)
+ {
+ meta_window_free (tmp->data, timestamp);
+
+ tmp = tmp->next;
+ }
+ g_slist_free (winlist);
+}
+
+int
+meta_display_stack_cmp (const void *a,
+ const void *b)
+{
+ MetaWindow *aw = (void*) a;
+ MetaWindow *bw = (void*) b;
+
+ if (aw->screen == bw->screen)
+ return meta_stack_windows_cmp (aw->screen->stack, aw, bw);
+ /* Then assume screens are stacked by number */
+ else if (aw->screen->number < bw->screen->number)
+ return -1;
+ else if (aw->screen->number > bw->screen->number)
+ return 1;
+ else
+ return 0; /* not reached in theory, if windows on same display */
+}
+
+void
+meta_display_devirtualize_modifiers (MetaDisplay *display,
+ MetaVirtualModifier modifiers,
+ unsigned int *mask)
+{
+ *mask = 0;
+
+ if (modifiers & META_VIRTUAL_SHIFT_MASK)
+ *mask |= ShiftMask;
+ if (modifiers & META_VIRTUAL_CONTROL_MASK)
+ *mask |= ControlMask;
+ if (modifiers & META_VIRTUAL_ALT_MASK)
+ *mask |= Mod1Mask;
+ if (modifiers & META_VIRTUAL_META_MASK)
+ *mask |= display->meta_mask;
+ if (modifiers & META_VIRTUAL_HYPER_MASK)
+ *mask |= display->hyper_mask;
+ if (modifiers & META_VIRTUAL_SUPER_MASK)
+ *mask |= display->super_mask;
+ if (modifiers & META_VIRTUAL_MOD2_MASK)
+ *mask |= Mod2Mask;
+ if (modifiers & META_VIRTUAL_MOD3_MASK)
+ *mask |= Mod3Mask;
+ if (modifiers & META_VIRTUAL_MOD4_MASK)
+ *mask |= Mod4Mask;
+ if (modifiers & META_VIRTUAL_MOD5_MASK)
+ *mask |= Mod5Mask;
+}
+
+static void
+update_window_grab_modifiers (MetaDisplay *display)
+
+{
+ MetaVirtualModifier virtual_mods;
+ unsigned int mods;
+
+ virtual_mods = meta_prefs_get_mouse_button_mods ();
+ meta_display_devirtualize_modifiers (display, virtual_mods,
+ &mods);
+
+ display->window_grab_modifiers = mods;
+}
+
+static void
+prefs_changed_callback (MetaPreference pref,
+ void *data)
+{
+ MetaDisplay *display = data;
+
+ /* It may not be obvious why we regrab on focus mode
+ * change; it's because we handle focus clicks a
+ * bit differently for the different focus modes.
+ */
+ if (pref == META_PREF_MOUSE_BUTTON_MODS ||
+ pref == META_PREF_FOCUS_MODE)
+ {
+ MetaDisplay *display = data;
+ GSList *windows;
+ GSList *tmp;
+
+ windows = meta_display_list_windows (display);
+
+ /* Ungrab all */
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+ meta_display_ungrab_window_buttons (display, w->xwindow);
+ meta_display_ungrab_focus_window_button (display, w);
+ tmp = tmp->next;
+ }
+
+ /* change our modifier */
+ if (pref == META_PREF_MOUSE_BUTTON_MODS)
+ update_window_grab_modifiers (display);
+
+ /* Grab all */
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+ if (w->type != META_WINDOW_DOCK)
+ {
+ meta_display_grab_focus_window_button (display, w);
+ meta_display_grab_window_buttons (display, w->xwindow);
+ }
+ tmp = tmp->next;
+ }
+
+ g_slist_free (windows);
+ }
+ else if (pref == META_PREF_AUDIBLE_BELL)
+ {
+ meta_bell_set_audible (display, meta_prefs_bell_is_audible ());
+ }
+ else if (pref == META_PREF_COMPOSITING_MANAGER)
+ {
+ gboolean cm = meta_prefs_get_compositing_manager ();
+
+ if (cm)
+ enable_compositor (display, TRUE);
+ else
+ disable_compositor (display);
+ }
+}
+
+void
+meta_display_increment_focus_sentinel (MetaDisplay *display)
+{
+ unsigned long data[1];
+
+ data[0] = meta_display_get_current_time (display);
+
+ XChangeProperty (display->xdisplay,
+ ((MetaScreen*) display->screens->data)->xroot,
+ display->atom__MARCO_SENTINEL,
+ XA_CARDINAL,
+ 32, PropModeReplace, (guchar*) data, 1);
+
+ display->sentinel_counter += 1;
+}
+
+void
+meta_display_decrement_focus_sentinel (MetaDisplay *display)
+{
+ display->sentinel_counter -= 1;
+
+ if (display->sentinel_counter < 0)
+ display->sentinel_counter = 0;
+}
+
+gboolean
+meta_display_focus_sentinel_clear (MetaDisplay *display)
+{
+ return (display->sentinel_counter == 0);
+}
+
+static void
+sanity_check_timestamps (MetaDisplay *display,
+ guint32 timestamp)
+{
+ if (XSERVER_TIME_IS_BEFORE (timestamp, display->last_focus_time))
+ {
+ meta_warning ("last_focus_time (%u) is greater than comparison "
+ "timestamp (%u). This most likely represents a buggy "
+ "client sending inaccurate timestamps in messages such as "
+ "_NET_ACTIVE_WINDOW. Trying to work around...\n",
+ display->last_focus_time, timestamp);
+ display->last_focus_time = timestamp;
+ }
+ if (XSERVER_TIME_IS_BEFORE (timestamp, display->last_user_time))
+ {
+ GSList *windows;
+ GSList *tmp;
+
+ meta_warning ("last_user_time (%u) is greater than comparison "
+ "timestamp (%u). This most likely represents a buggy "
+ "client sending inaccurate timestamps in messages such as "
+ "_NET_ACTIVE_WINDOW. Trying to work around...\n",
+ display->last_user_time, timestamp);
+ display->last_user_time = timestamp;
+
+ windows = meta_display_list_windows (display);
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *window = tmp->data;
+
+ if (XSERVER_TIME_IS_BEFORE (timestamp, window->net_wm_user_time))
+ {
+ meta_warning ("%s appears to be one of the offending windows "
+ "with a timestamp of %u. Working around...\n",
+ window->desc, window->net_wm_user_time);
+ window->net_wm_user_time = timestamp;
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (windows);
+ }
+}
+
+static gboolean
+timestamp_too_old (MetaDisplay *display,
+ MetaWindow *window,
+ guint32 *timestamp)
+{
+ /* FIXME: If Soeren's suggestion in bug 151984 is implemented, it will allow
+ * us to sanity check the timestamp here and ensure it doesn't correspond to
+ * a future time (though we would want to rename to
+ * timestamp_too_old_or_in_future).
+ */
+
+ if (*timestamp == CurrentTime)
+ {
+ meta_warning ("Got a request to focus %s with a timestamp of 0. This "
+ "shouldn't happen!\n",
+ window ? window->desc : "the no_focus_window");
+ meta_print_backtrace ();
+ *timestamp = meta_display_get_current_time_roundtrip (display);
+ return FALSE;
+ }
+ else if (XSERVER_TIME_IS_BEFORE (*timestamp, display->last_focus_time))
+ {
+ if (XSERVER_TIME_IS_BEFORE (*timestamp, display->last_user_time))
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Ignoring focus request for %s since %u "
+ "is less than %u and %u.\n",
+ window ? window->desc : "the no_focus_window",
+ *timestamp,
+ display->last_user_time,
+ display->last_focus_time);
+ return TRUE;
+ }
+ else
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Received focus request for %s which is newer than most "
+ "recent user_time, but less recent than "
+ "last_focus_time (%u < %u < %u); adjusting "
+ "accordingly. (See bug 167358)\n",
+ window ? window->desc : "the no_focus_window",
+ display->last_user_time,
+ *timestamp,
+ display->last_focus_time);
+ *timestamp = display->last_focus_time;
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+void
+meta_display_set_input_focus_window (MetaDisplay *display,
+ MetaWindow *window,
+ gboolean focus_frame,
+ guint32 timestamp)
+{
+ if (timestamp_too_old (display, window, &timestamp))
+ return;
+
+ meta_error_trap_push (display);
+ XSetInputFocus (display->xdisplay,
+ focus_frame ? window->frame->xwindow : window->xwindow,
+ RevertToPointerRoot,
+ timestamp);
+ meta_error_trap_pop (display, FALSE);
+
+ display->expected_focus_window = window;
+ display->last_focus_time = timestamp;
+ display->active_screen = window->screen;
+
+ if (window != display->autoraise_window)
+ meta_display_remove_autoraise_callback (window->display);
+}
+
+void
+meta_display_focus_the_no_focus_window (MetaDisplay *display,
+ MetaScreen *screen,
+ guint32 timestamp)
+{
+ if (timestamp_too_old (display, NULL, &timestamp))
+ return;
+
+ XSetInputFocus (display->xdisplay,
+ screen->no_focus_window,
+ RevertToPointerRoot,
+ timestamp);
+ display->expected_focus_window = NULL;
+ display->last_focus_time = timestamp;
+ display->active_screen = screen;
+
+ meta_display_remove_autoraise_callback (display);
+}
+
+void
+meta_display_remove_autoraise_callback (MetaDisplay *display)
+{
+ if (display->autoraise_timeout_id != 0)
+ {
+ g_source_remove (display->autoraise_timeout_id);
+ display->autoraise_timeout_id = 0;
+ display->autoraise_window = NULL;
+ }
+}
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+void
+meta_display_get_compositor_version (MetaDisplay *display,
+ int *major,
+ int *minor)
+{
+ *major = display->composite_major_version;
+ *minor = display->composite_minor_version;
+}
+#endif
+
+Display *
+meta_display_get_xdisplay (MetaDisplay *display)
+{
+ return display->xdisplay;
+}
+
+MetaCompositor *
+meta_display_get_compositor (MetaDisplay *display)
+{
+ return display->compositor;
+}
+
+GSList *
+meta_display_get_screens (MetaDisplay *display)
+{
+ return display->screens;
+}
+
+gboolean
+meta_display_has_shape (MetaDisplay *display)
+{
+ return META_DISPLAY_HAS_SHAPE (display);
+}
+
+MetaWindow *
+meta_display_get_focus_window (MetaDisplay *display)
+{
+ return display->focus_window;
+}
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+int
+meta_display_get_damage_event_base (MetaDisplay *display)
+{
+ return display->damage_event_base;
+}
+#endif
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+#ifdef HAVE_SHAPE
+int
+meta_display_get_shape_event_base (MetaDisplay *display)
+{
+ return display->shape_event_base;
+}
+#endif
+#endif
diff --git a/src/core/edge-resistance.c b/src/core/edge-resistance.c
new file mode 100644
index 00000000..fd3f2d44
--- /dev/null
+++ b/src/core/edge-resistance.c
@@ -0,0 +1,1277 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Edge resistance for move/resize operations */
+
+/*
+ * Copyright (C) 2005, 2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "edge-resistance.h"
+#include "boxes.h"
+#include "display-private.h"
+#include "workspace.h"
+
+/* A simple macro for whether a given window's edges are potentially
+ * relevant for resistance/snapping during a move/resize operation
+ */
+#define WINDOW_EDGES_RELEVANT(window, display) \
+ meta_window_should_be_showing (window) && \
+ window->screen == display->grab_screen && \
+ window != display->grab_window && \
+ window->type != META_WINDOW_DESKTOP && \
+ window->type != META_WINDOW_MENU && \
+ window->type != META_WINDOW_SPLASHSCREEN
+
+struct ResistanceDataForAnEdge
+{
+ gboolean timeout_setup;
+ guint timeout_id;
+ int timeout_edge_pos;
+ gboolean timeout_over;
+ GSourceFunc timeout_func;
+ MetaWindow *window;
+ int keyboard_buildup;
+};
+typedef struct ResistanceDataForAnEdge ResistanceDataForAnEdge;
+
+struct MetaEdgeResistanceData
+{
+ GArray *left_edges;
+ GArray *right_edges;
+ GArray *top_edges;
+ GArray *bottom_edges;
+
+ ResistanceDataForAnEdge left_data;
+ ResistanceDataForAnEdge right_data;
+ ResistanceDataForAnEdge top_data;
+ ResistanceDataForAnEdge bottom_data;
+};
+
+/* !WARNING!: this function can return invalid indices (namely, either -1 or
+ * edges->len); this is by design, but you need to remember this.
+ */
+static int
+find_index_of_edge_near_position (const GArray *edges,
+ int position,
+ gboolean want_interval_min,
+ gboolean horizontal)
+{
+ /* This is basically like a binary search, except that we're trying to
+ * find a range instead of an exact value. So, if we have in our array
+ * Value: 3 27 316 316 316 505 522 800 1213
+ * Index: 0 1 2 3 4 5 6 7 8
+ * and we call this function with position=500 & want_interval_min=TRUE
+ * then we should get 5 (because 505 is the first value bigger than 500).
+ * If we call this function with position=805 and want_interval_min=FALSE
+ * then we should get 7 (because 800 is the last value smaller than 800).
+ * A couple more, to make things clear:
+ * position want_interval_min correct_answer
+ * 316 TRUE 2
+ * 316 FALSE 4
+ * 2 FALSE -1
+ * 2000 TRUE 9
+ */
+ int low, high, mid;
+ int compare;
+ MetaEdge *edge;
+
+ /* Initialize mid, edge, & compare in the off change that the array only
+ * has one element.
+ */
+ mid = 0;
+ edge = g_array_index (edges, MetaEdge*, mid);
+ compare = horizontal ? edge->rect.x : edge->rect.y;
+
+ /* Begin the search... */
+ low = 0;
+ high = edges->len - 1;
+ while (low < high)
+ {
+ mid = low + (high - low)/2;
+ edge = g_array_index (edges, MetaEdge*, mid);
+ compare = horizontal ? edge->rect.x : edge->rect.y;
+
+ if (compare == position)
+ break;
+
+ if (compare > position)
+ high = mid - 1;
+ else
+ low = mid + 1;
+ }
+
+ /* mid should now be _really_ close to the index we want, so we start
+ * linearly searching. However, note that we don't know if mid is less
+ * than or greater than what we need and it's possible that there are
+ * several equal values equal to what we were searching for and we ended
+ * up in the middle of them instead of at the end. So we may need to
+ * move mid multiple locations over.
+ */
+ if (want_interval_min)
+ {
+ while (compare >= position && mid > 0)
+ {
+ mid--;
+ edge = g_array_index (edges, MetaEdge*, mid);
+ compare = horizontal ? edge->rect.x : edge->rect.y;
+ }
+ while (compare < position && mid < (int)edges->len - 1)
+ {
+ mid++;
+ edge = g_array_index (edges, MetaEdge*, mid);
+ compare = horizontal ? edge->rect.x : edge->rect.y;
+ }
+
+ /* Special case for no values in array big enough */
+ if (compare < position)
+ return edges->len;
+
+ /* Return the found value */
+ return mid;
+ }
+ else
+ {
+ while (compare <= position && mid < (int)edges->len - 1)
+ {
+ mid++;
+ edge = g_array_index (edges, MetaEdge*, mid);
+ compare = horizontal ? edge->rect.x : edge->rect.y;
+ }
+ while (compare > position && mid > 0)
+ {
+ mid--;
+ edge = g_array_index (edges, MetaEdge*, mid);
+ compare = horizontal ? edge->rect.x : edge->rect.y;
+ }
+
+ /* Special case for no values in array small enough */
+ if (compare > position)
+ return -1;
+
+ /* Return the found value */
+ return mid;
+ }
+}
+
+static gboolean
+points_on_same_side (int ref, int pt1, int pt2)
+{
+ return (pt1 - ref) * (pt2 - ref) > 0;
+}
+
+static int
+find_nearest_position (const GArray *edges,
+ int position,
+ int old_position,
+ const MetaRectangle *new_rect,
+ gboolean horizontal,
+ gboolean only_forward)
+{
+ /* This is basically just a binary search except that we're looking
+ * for the value closest to position, rather than finding that
+ * actual value. Also, we ignore any edges that aren't relevant
+ * given the horizontal/vertical position of new_rect.
+ */
+ int low, high, mid;
+ int compare;
+ MetaEdge *edge;
+ int best, best_dist, i;
+ gboolean edges_align;
+
+ /* Initialize mid, edge, & compare in the off change that the array only
+ * has one element.
+ */
+ mid = 0;
+ edge = g_array_index (edges, MetaEdge*, mid);
+ compare = horizontal ? edge->rect.x : edge->rect.y;
+
+ /* Begin the search... */
+ low = 0;
+ high = edges->len - 1;
+ while (low < high)
+ {
+ mid = low + (high - low)/2;
+ edge = g_array_index (edges, MetaEdge*, mid);
+ compare = horizontal ? edge->rect.x : edge->rect.y;
+
+ if (compare == position)
+ break;
+
+ if (compare > position)
+ high = mid - 1;
+ else
+ low = mid + 1;
+ }
+
+ /* mid should now be _really_ close to the index we want, so we
+ * start searching nearby for something that overlaps and is closer
+ * than the original position.
+ */
+ best = old_position;
+ best_dist = INT_MAX;
+
+ /* Start the search at mid */
+ edge = g_array_index (edges, MetaEdge*, mid);
+ compare = horizontal ? edge->rect.x : edge->rect.y;
+ edges_align = meta_rectangle_edge_aligns (new_rect, edge);
+ if (edges_align &&
+ (!only_forward || !points_on_same_side (position, compare, old_position)))
+ {
+ int dist = ABS (compare - position);
+ if (dist < best_dist)
+ {
+ best = compare;
+ best_dist = dist;
+ }
+ }
+
+ /* Now start searching higher than mid */
+ for (i = mid + 1; i < (int)edges->len; i++)
+ {
+ edge = g_array_index (edges, MetaEdge*, i);
+ compare = horizontal ? edge->rect.x : edge->rect.y;
+
+ edges_align = horizontal ?
+ meta_rectangle_vert_overlap (&edge->rect, new_rect) :
+ meta_rectangle_horiz_overlap (&edge->rect, new_rect);
+
+ if (edges_align &&
+ (!only_forward ||
+ !points_on_same_side (position, compare, old_position)))
+ {
+ int dist = ABS (compare - position);
+ if (dist < best_dist)
+ {
+ best = compare;
+ best_dist = dist;
+ }
+ break;
+ }
+ }
+
+ /* Now start searching lower than mid */
+ for (i = mid-1; i >= 0; i--)
+ {
+ edge = g_array_index (edges, MetaEdge*, i);
+ compare = horizontal ? edge->rect.x : edge->rect.y;
+
+ edges_align = horizontal ?
+ meta_rectangle_vert_overlap (&edge->rect, new_rect) :
+ meta_rectangle_horiz_overlap (&edge->rect, new_rect);
+
+ if (edges_align &&
+ (!only_forward ||
+ !points_on_same_side (position, compare, old_position)))
+ {
+ int dist = ABS (compare - position);
+ if (dist < best_dist)
+ {
+ best = compare;
+ best_dist = dist;
+ }
+ break;
+ }
+ }
+
+ /* Return the best one found */
+ return best;
+}
+
+static gboolean
+movement_towards_edge (MetaSide side, int increment)
+{
+ switch (side)
+ {
+ case META_SIDE_LEFT:
+ case META_SIDE_TOP:
+ return increment < 0;
+ case META_SIDE_RIGHT:
+ case META_SIDE_BOTTOM:
+ return increment > 0;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+edge_resistance_timeout (gpointer data)
+{
+ ResistanceDataForAnEdge *resistance_data = data;
+
+ resistance_data->timeout_over = TRUE;
+ resistance_data->timeout_id = 0;
+ (*resistance_data->timeout_func)(resistance_data->window);
+
+ return FALSE;
+}
+
+static int
+apply_edge_resistance (MetaWindow *window,
+ int old_pos,
+ int new_pos,
+ const MetaRectangle *old_rect,
+ const MetaRectangle *new_rect,
+ GArray *edges,
+ ResistanceDataForAnEdge *resistance_data,
+ GSourceFunc timeout_func,
+ gboolean xdir,
+ gboolean keyboard_op)
+{
+ int i, begin, end;
+ int last_edge;
+ gboolean increasing = new_pos > old_pos;
+ int increment = increasing ? 1 : -1;
+
+ const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW = 16;
+ const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW = 0;
+ const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_XINERAMA = 32;
+ const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_XINERAMA = 0;
+ const int PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN = 32;
+ const int PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN = 0;
+ const int TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW = 0;
+ const int TIMEOUT_RESISTANCE_LENGTH_MS_XINERAMA = 0;
+ const int TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN = 0;
+
+ /* Quit if no movement was specified */
+ if (old_pos == new_pos)
+ return new_pos;
+
+ /* Remove the old timeout if it's no longer relevant */
+ if (resistance_data->timeout_setup &&
+ ((resistance_data->timeout_edge_pos > old_pos &&
+ resistance_data->timeout_edge_pos > new_pos) ||
+ (resistance_data->timeout_edge_pos < old_pos &&
+ resistance_data->timeout_edge_pos < new_pos)))
+ {
+ resistance_data->timeout_setup = FALSE;
+ if (resistance_data->timeout_id != 0)
+ {
+ g_source_remove (resistance_data->timeout_id);
+ resistance_data->timeout_id = 0;
+ }
+ }
+
+ /* Get the range of indices in the edge array that we move past/to. */
+ begin = find_index_of_edge_near_position (edges, old_pos, increasing, xdir);
+ end = find_index_of_edge_near_position (edges, new_pos, !increasing, xdir);
+
+ /* begin and end can be outside the array index, if the window is partially
+ * off the screen
+ */
+ last_edge = edges->len - 1;
+ begin = CLAMP (begin, 0, last_edge);
+ end = CLAMP (end, 0, last_edge);
+
+ /* Loop over all these edges we're moving past/to. */
+ i = begin;
+ while ((increasing && i <= end) ||
+ (!increasing && i >= end))
+ {
+ gboolean edges_align;
+ MetaEdge *edge = g_array_index (edges, MetaEdge*, i);
+ int compare = xdir ? edge->rect.x : edge->rect.y;
+
+ /* Find out if this edge is relevant */
+ edges_align = meta_rectangle_edge_aligns (new_rect, edge) ||
+ meta_rectangle_edge_aligns (old_rect, edge);
+
+ /* Nothing to do unless the edges align */
+ if (!edges_align)
+ {
+ /* Go to the next edge in the range */
+ i += increment;
+ continue;
+ }
+
+ /* Rest is easier to read if we split on keyboard vs. mouse op */
+ if (keyboard_op)
+ {
+ if ((old_pos < compare && compare < new_pos) ||
+ (old_pos > compare && compare > new_pos))
+ return compare;
+ }
+ else /* mouse op */
+ {
+ int threshold;
+
+ /* TIMEOUT RESISTANCE: If the edge is relevant and we're moving
+ * towards it, then we may want to have some kind of time delay
+ * before the user can move past this edge.
+ */
+ if (movement_towards_edge (edge->side_type, increment))
+ {
+ /* First, determine the length of time for the resistance */
+ int timeout_length_ms = 0;
+ switch (edge->edge_type)
+ {
+ case META_EDGE_WINDOW:
+ timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_WINDOW;
+ break;
+ case META_EDGE_XINERAMA:
+ timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_XINERAMA;
+ break;
+ case META_EDGE_SCREEN:
+ timeout_length_ms = TIMEOUT_RESISTANCE_LENGTH_MS_SCREEN;
+ break;
+ }
+
+ if (!resistance_data->timeout_setup &&
+ timeout_length_ms != 0)
+ {
+ resistance_data->timeout_id =
+ g_timeout_add (timeout_length_ms,
+ edge_resistance_timeout,
+ resistance_data);
+ resistance_data->timeout_setup = TRUE;
+ resistance_data->timeout_edge_pos = compare;
+ resistance_data->timeout_over = FALSE;
+ resistance_data->timeout_func = timeout_func;
+ resistance_data->window = window;
+ }
+ if (!resistance_data->timeout_over &&
+ timeout_length_ms != 0)
+ return compare;
+ }
+
+ /* PIXEL DISTANCE MOUSE RESISTANCE: If the edge matters and the
+ * user hasn't moved at least threshold pixels past this edge,
+ * stop movement at this edge. (Note that this is different from
+ * keyboard resistance precisely because keyboard move ops are
+ * relative to previous positions, whereas mouse move ops are
+ * relative to differences in mouse position and mouse position
+ * is an absolute quantity rather than a relative quantity)
+ */
+
+ /* First, determine the threshold */
+ threshold = 0;
+ switch (edge->edge_type)
+ {
+ case META_EDGE_WINDOW:
+ if (movement_towards_edge (edge->side_type, increment))
+ threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_WINDOW;
+ else
+ threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_WINDOW;
+ break;
+ case META_EDGE_XINERAMA:
+ if (movement_towards_edge (edge->side_type, increment))
+ threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_XINERAMA;
+ else
+ threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_XINERAMA;
+ break;
+ case META_EDGE_SCREEN:
+ if (movement_towards_edge (edge->side_type, increment))
+ threshold = PIXEL_DISTANCE_THRESHOLD_TOWARDS_SCREEN;
+ else
+ threshold = PIXEL_DISTANCE_THRESHOLD_AWAYFROM_SCREEN;
+ break;
+ }
+
+ if (ABS (compare - new_pos) < threshold)
+ return compare;
+ }
+
+ /* Go to the next edge in the range */
+ i += increment;
+ }
+
+ return new_pos;
+}
+
+static int
+apply_edge_snapping (int old_pos,
+ int new_pos,
+ const MetaRectangle *new_rect,
+ GArray *edges,
+ gboolean xdir,
+ gboolean keyboard_op)
+{
+ int snap_to;
+
+ if (old_pos == new_pos)
+ return new_pos;
+
+ snap_to = find_nearest_position (edges,
+ new_pos,
+ old_pos,
+ new_rect,
+ xdir,
+ keyboard_op);
+
+ /* If mouse snap-moving, the user could easily accidentally move just a
+ * couple pixels in a direction they didn't mean to move; so ignore snap
+ * movement in those cases unless it's only a small number of pixels
+ * anyway.
+ */
+ if (!keyboard_op &&
+ ABS (snap_to - old_pos) >= 8 &&
+ ABS (new_pos - old_pos) < 8)
+ return old_pos;
+ else
+ /* Otherwise, return the snapping position found */
+ return snap_to;
+}
+
+/* This function takes the position (including any frame) of the window and
+ * a proposed new position (ignoring edge resistance/snapping), and then
+ * applies edge resistance to EACH edge (separately) updating new_outer.
+ * It returns true if new_outer is modified, false otherwise.
+ *
+ * display->grab_edge_resistance_data MUST already be setup or calling this
+ * function will cause a crash.
+ */
+static gboolean
+apply_edge_resistance_to_each_side (MetaDisplay *display,
+ MetaWindow *window,
+ const MetaRectangle *old_outer,
+ MetaRectangle *new_outer,
+ GSourceFunc timeout_func,
+ gboolean auto_snap,
+ gboolean keyboard_op,
+ gboolean is_resize)
+{
+ MetaEdgeResistanceData *edge_data;
+ MetaRectangle modified_rect;
+ gboolean modified;
+ int new_left, new_right, new_top, new_bottom;
+
+ g_assert (display->grab_edge_resistance_data != NULL);
+ edge_data = display->grab_edge_resistance_data;
+
+ if (auto_snap)
+ {
+ /* Do the auto snapping instead of normal edge resistance; in all
+ * cases, we allow snapping to opposite kinds of edges (e.g. left
+ * sides of windows to both left and right edges.
+ */
+
+ new_left = apply_edge_snapping (BOX_LEFT (*old_outer),
+ BOX_LEFT (*new_outer),
+ new_outer,
+ edge_data->left_edges,
+ TRUE,
+ keyboard_op);
+
+ new_right = apply_edge_snapping (BOX_RIGHT (*old_outer),
+ BOX_RIGHT (*new_outer),
+ new_outer,
+ edge_data->right_edges,
+ TRUE,
+ keyboard_op);
+
+ new_top = apply_edge_snapping (BOX_TOP (*old_outer),
+ BOX_TOP (*new_outer),
+ new_outer,
+ edge_data->top_edges,
+ FALSE,
+ keyboard_op);
+
+ new_bottom = apply_edge_snapping (BOX_BOTTOM (*old_outer),
+ BOX_BOTTOM (*new_outer),
+ new_outer,
+ edge_data->bottom_edges,
+ FALSE,
+ keyboard_op);
+ }
+ else
+ {
+ /* Disable edge resistance for resizes when windows have size
+ * increment hints; see #346782. For all other cases, apply
+ * them.
+ */
+ if (!is_resize || window->size_hints.width_inc == 1)
+ {
+ /* Now, apply the normal horizontal edge resistance */
+ new_left = apply_edge_resistance (window,
+ BOX_LEFT (*old_outer),
+ BOX_LEFT (*new_outer),
+ old_outer,
+ new_outer,
+ edge_data->left_edges,
+ &edge_data->left_data,
+ timeout_func,
+ TRUE,
+ keyboard_op);
+ new_right = apply_edge_resistance (window,
+ BOX_RIGHT (*old_outer),
+ BOX_RIGHT (*new_outer),
+ old_outer,
+ new_outer,
+ edge_data->right_edges,
+ &edge_data->right_data,
+ timeout_func,
+ TRUE,
+ keyboard_op);
+ }
+ else
+ {
+ new_left = new_outer->x;
+ new_right = new_outer->x + new_outer->width;
+ }
+ /* Same for vertical resizes... */
+ if (!is_resize || window->size_hints.height_inc == 1)
+ {
+ new_top = apply_edge_resistance (window,
+ BOX_TOP (*old_outer),
+ BOX_TOP (*new_outer),
+ old_outer,
+ new_outer,
+ edge_data->top_edges,
+ &edge_data->top_data,
+ timeout_func,
+ FALSE,
+ keyboard_op);
+ new_bottom = apply_edge_resistance (window,
+ BOX_BOTTOM (*old_outer),
+ BOX_BOTTOM (*new_outer),
+ old_outer,
+ new_outer,
+ edge_data->bottom_edges,
+ &edge_data->bottom_data,
+ timeout_func,
+ FALSE,
+ keyboard_op);
+ }
+ else
+ {
+ new_top = new_outer->y;
+ new_bottom = new_outer->y + new_outer->height;
+ }
+ }
+
+ /* Determine whether anything changed, and save the changes */
+ modified_rect = meta_rect (new_left,
+ new_top,
+ new_right - new_left,
+ new_bottom - new_top);
+ modified = !meta_rectangle_equal (new_outer, &modified_rect);
+ *new_outer = modified_rect;
+ return modified;
+}
+
+void
+meta_display_cleanup_edges (MetaDisplay *display)
+{
+ guint i,j;
+ MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data;
+ GHashTable *edges_to_be_freed;
+
+ g_assert (edge_data != NULL);
+
+ /* We first need to clean out any window edges */
+ edges_to_be_freed = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ g_free, NULL);
+ for (i = 0; i < 4; i++)
+ {
+ GArray *tmp = NULL;
+ MetaSide side;
+ switch (i)
+ {
+ case 0:
+ tmp = edge_data->left_edges;
+ side = META_SIDE_LEFT;
+ break;
+ case 1:
+ tmp = edge_data->right_edges;
+ side = META_SIDE_RIGHT;
+ break;
+ case 2:
+ tmp = edge_data->top_edges;
+ side = META_SIDE_TOP;
+ break;
+ case 3:
+ tmp = edge_data->bottom_edges;
+ side = META_SIDE_BOTTOM;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ for (j = 0; j < tmp->len; j++)
+ {
+ MetaEdge *edge = g_array_index (tmp, MetaEdge*, j);
+ if (edge->edge_type == META_EDGE_WINDOW &&
+ edge->side_type == side)
+ {
+ /* The same edge will appear in two arrays, and we can't free
+ * it yet we still need to compare edge->side_type for the other
+ * array that it is in. So store it in a hash table for later
+ * freeing. Could also do this in a simple linked list.
+ */
+ g_hash_table_insert (edges_to_be_freed, edge, edge);
+ }
+ }
+ }
+
+ /* Now free all the window edges (the key destroy function is g_free) */
+ g_hash_table_destroy (edges_to_be_freed);
+
+ /* Now free the arrays and data */
+ g_array_free (edge_data->left_edges, TRUE);
+ g_array_free (edge_data->right_edges, TRUE);
+ g_array_free (edge_data->top_edges, TRUE);
+ g_array_free (edge_data->bottom_edges, TRUE);
+ edge_data->left_edges = NULL;
+ edge_data->right_edges = NULL;
+ edge_data->top_edges = NULL;
+ edge_data->bottom_edges = NULL;
+
+ /* Cleanup the timeouts */
+ if (edge_data->left_data.timeout_setup &&
+ edge_data->left_data.timeout_id != 0)
+ g_source_remove (edge_data->left_data.timeout_id);
+ if (edge_data->right_data.timeout_setup &&
+ edge_data->right_data.timeout_id != 0)
+ g_source_remove (edge_data->right_data.timeout_id);
+ if (edge_data->top_data.timeout_setup &&
+ edge_data->top_data.timeout_id != 0)
+ g_source_remove (edge_data->top_data.timeout_id);
+ if (edge_data->bottom_data.timeout_setup &&
+ edge_data->bottom_data.timeout_id != 0)
+ g_source_remove (edge_data->bottom_data.timeout_id);
+
+ g_free (display->grab_edge_resistance_data);
+ display->grab_edge_resistance_data = NULL;
+}
+
+static int
+stupid_sort_requiring_extra_pointer_dereference (gconstpointer a,
+ gconstpointer b)
+{
+ const MetaEdge * const *a_edge = a;
+ const MetaEdge * const *b_edge = b;
+ return meta_rectangle_edge_cmp_ignore_type (*a_edge, *b_edge);
+}
+
+static void
+cache_edges (MetaDisplay *display,
+ GList *window_edges,
+ GList *xinerama_edges,
+ GList *screen_edges)
+{
+ MetaEdgeResistanceData *edge_data;
+ GList *tmp;
+ int num_left, num_right, num_top, num_bottom;
+ int i;
+
+ /*
+ * 0th: Print debugging information to the log about the edges
+ */
+#ifdef WITH_VERBOSE_MODE
+ if (meta_is_verbose())
+ {
+ int max_edges = MAX (MAX( g_list_length (window_edges),
+ g_list_length (xinerama_edges)),
+ g_list_length (screen_edges));
+ char big_buffer[(EDGE_LENGTH+2)*max_edges];
+
+ meta_rectangle_edge_list_to_string (window_edges, ", ", big_buffer);
+ meta_topic (META_DEBUG_EDGE_RESISTANCE,
+ "Window edges for resistance : %s\n", big_buffer);
+
+ meta_rectangle_edge_list_to_string (xinerama_edges, ", ", big_buffer);
+ meta_topic (META_DEBUG_EDGE_RESISTANCE,
+ "Xinerama edges for resistance: %s\n", big_buffer);
+
+ meta_rectangle_edge_list_to_string (screen_edges, ", ", big_buffer);
+ meta_topic (META_DEBUG_EDGE_RESISTANCE,
+ "Screen edges for resistance : %s\n", big_buffer);
+ }
+#endif
+
+ /*
+ * 1st: Get the total number of each kind of edge
+ */
+ num_left = num_right = num_top = num_bottom = 0;
+ for (i = 0; i < 3; i++)
+ {
+ tmp = NULL;
+ switch (i)
+ {
+ case 0:
+ tmp = window_edges;
+ break;
+ case 1:
+ tmp = xinerama_edges;
+ break;
+ case 2:
+ tmp = screen_edges;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ while (tmp)
+ {
+ MetaEdge *edge = tmp->data;
+ switch (edge->side_type)
+ {
+ case META_SIDE_LEFT:
+ num_left++;
+ break;
+ case META_SIDE_RIGHT:
+ num_right++;
+ break;
+ case META_SIDE_TOP:
+ num_top++;
+ break;
+ case META_SIDE_BOTTOM:
+ num_bottom++;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ tmp = tmp->next;
+ }
+ }
+
+ /*
+ * 2nd: Allocate the edges
+ */
+ g_assert (display->grab_edge_resistance_data == NULL);
+ display->grab_edge_resistance_data = g_new (MetaEdgeResistanceData, 1);
+ edge_data = display->grab_edge_resistance_data;
+ edge_data->left_edges = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof(MetaEdge*),
+ num_left + num_right);
+ edge_data->right_edges = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof(MetaEdge*),
+ num_left + num_right);
+ edge_data->top_edges = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof(MetaEdge*),
+ num_top + num_bottom);
+ edge_data->bottom_edges = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof(MetaEdge*),
+ num_top + num_bottom);
+
+ /*
+ * 3rd: Add the edges to the arrays
+ */
+ for (i = 0; i < 3; i++)
+ {
+ tmp = NULL;
+ switch (i)
+ {
+ case 0:
+ tmp = window_edges;
+ break;
+ case 1:
+ tmp = xinerama_edges;
+ break;
+ case 2:
+ tmp = screen_edges;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ while (tmp)
+ {
+ MetaEdge *edge = tmp->data;
+ switch (edge->side_type)
+ {
+ case META_SIDE_LEFT:
+ case META_SIDE_RIGHT:
+ g_array_append_val (edge_data->left_edges, edge);
+ g_array_append_val (edge_data->right_edges, edge);
+ break;
+ case META_SIDE_TOP:
+ case META_SIDE_BOTTOM:
+ g_array_append_val (edge_data->top_edges, edge);
+ g_array_append_val (edge_data->bottom_edges, edge);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ tmp = tmp->next;
+ }
+ }
+
+ /*
+ * 4th: Sort the arrays (FIXME: This is kinda dumb since the arrays were
+ * individually sorted earlier and we could have done this faster and
+ * avoided this sort by sticking them into the array with some simple
+ * merging of the lists).
+ */
+ g_array_sort (display->grab_edge_resistance_data->left_edges,
+ stupid_sort_requiring_extra_pointer_dereference);
+ g_array_sort (display->grab_edge_resistance_data->right_edges,
+ stupid_sort_requiring_extra_pointer_dereference);
+ g_array_sort (display->grab_edge_resistance_data->top_edges,
+ stupid_sort_requiring_extra_pointer_dereference);
+ g_array_sort (display->grab_edge_resistance_data->bottom_edges,
+ stupid_sort_requiring_extra_pointer_dereference);
+}
+
+static void
+initialize_grab_edge_resistance_data (MetaDisplay *display)
+{
+ MetaEdgeResistanceData *edge_data = display->grab_edge_resistance_data;
+
+ edge_data->left_data.timeout_setup = FALSE;
+ edge_data->right_data.timeout_setup = FALSE;
+ edge_data->top_data.timeout_setup = FALSE;
+ edge_data->bottom_data.timeout_setup = FALSE;
+
+ edge_data->left_data.keyboard_buildup = 0;
+ edge_data->right_data.keyboard_buildup = 0;
+ edge_data->top_data.keyboard_buildup = 0;
+ edge_data->bottom_data.keyboard_buildup = 0;
+}
+
+void
+meta_display_compute_resistance_and_snapping_edges (MetaDisplay *display)
+{
+ GList *stacked_windows;
+ GList *cur_window_iter;
+ GList *edges;
+ /* Lists of window positions (rects) and their relative stacking positions */
+ int stack_position;
+ GSList *obscuring_windows, *window_stacking;
+ /* The portions of the above lists that still remain at the stacking position
+ * in the layer that we are working on
+ */
+ GSList *rem_windows, *rem_win_stacking;
+
+ /*
+ * 1st: Get the list of relevant windows, from bottom to top
+ */
+ stacked_windows =
+ meta_stack_list_windows (display->grab_screen->stack,
+ display->grab_screen->active_workspace);
+
+ /*
+ * 2nd: we need to separate that stacked list into a list of windows that
+ * can obscure other edges. To make sure we only have windows obscuring
+ * those below it instead of going both ways, we also need to keep a
+ * counter list. Messy, I know.
+ */
+ obscuring_windows = window_stacking = NULL;
+ cur_window_iter = stacked_windows;
+ stack_position = 0;
+ while (cur_window_iter != NULL)
+ {
+ MetaWindow *cur_window = cur_window_iter->data;
+ if (WINDOW_EDGES_RELEVANT (cur_window, display))
+ {
+ MetaRectangle *new_rect;
+ new_rect = g_new (MetaRectangle, 1);
+ meta_window_get_outer_rect (cur_window, new_rect);
+ obscuring_windows = g_slist_prepend (obscuring_windows, new_rect);
+ window_stacking =
+ g_slist_prepend (window_stacking, GINT_TO_POINTER (stack_position));
+ }
+
+ stack_position++;
+ cur_window_iter = cur_window_iter->next;
+ }
+ /* Put 'em in bottom to top order */
+ rem_windows = obscuring_windows = g_slist_reverse (obscuring_windows);
+ rem_win_stacking = window_stacking = g_slist_reverse (window_stacking);
+
+ /*
+ * 3rd: loop over the windows again, this time getting the edges from
+ * them and removing intersections with the relevant obscuring_windows &
+ * obscuring_docks.
+ */
+ edges = NULL;
+ stack_position = 0;
+ cur_window_iter = stacked_windows;
+ while (cur_window_iter != NULL)
+ {
+ MetaRectangle cur_rect;
+ MetaWindow *cur_window = cur_window_iter->data;
+ meta_window_get_outer_rect (cur_window, &cur_rect);
+
+ /* Check if we want to use this window's edges for edge
+ * resistance (note that dock edges are considered screen edges
+ * which are handled separately
+ */
+ if (WINDOW_EDGES_RELEVANT (cur_window, display) &&
+ cur_window->type != META_WINDOW_DOCK)
+ {
+ GList *new_edges;
+ MetaEdge *new_edge;
+ MetaRectangle reduced;
+
+ /* We don't care about snapping to any portion of the window that
+ * is offscreen (we also don't care about parts of edges covered
+ * by other windows or DOCKS, but that's handled below).
+ */
+ meta_rectangle_intersect (&cur_rect,
+ &display->grab_screen->rect,
+ &reduced);
+
+ new_edges = NULL;
+
+ /* Left side of this window is resistance for the right edge of
+ * the window being moved.
+ */
+ new_edge = g_new (MetaEdge, 1);
+ new_edge->rect = reduced;
+ new_edge->rect.width = 0;
+ new_edge->side_type = META_SIDE_RIGHT;
+ new_edge->edge_type = META_EDGE_WINDOW;
+ new_edges = g_list_prepend (new_edges, new_edge);
+
+ /* Right side of this window is resistance for the left edge of
+ * the window being moved.
+ */
+ new_edge = g_new (MetaEdge, 1);
+ new_edge->rect = reduced;
+ new_edge->rect.x += new_edge->rect.width;
+ new_edge->rect.width = 0;
+ new_edge->side_type = META_SIDE_LEFT;
+ new_edge->edge_type = META_EDGE_WINDOW;
+ new_edges = g_list_prepend (new_edges, new_edge);
+
+ /* Top side of this window is resistance for the bottom edge of
+ * the window being moved.
+ */
+ new_edge = g_new (MetaEdge, 1);
+ new_edge->rect = reduced;
+ new_edge->rect.height = 0;
+ new_edge->side_type = META_SIDE_BOTTOM;
+ new_edge->edge_type = META_EDGE_WINDOW;
+ new_edges = g_list_prepend (new_edges, new_edge);
+
+ /* Top side of this window is resistance for the bottom edge of
+ * the window being moved.
+ */
+ new_edge = g_new (MetaEdge, 1);
+ new_edge->rect = reduced;
+ new_edge->rect.y += new_edge->rect.height;
+ new_edge->rect.height = 0;
+ new_edge->side_type = META_SIDE_TOP;
+ new_edge->edge_type = META_EDGE_WINDOW;
+ new_edges = g_list_prepend (new_edges, new_edge);
+
+ /* Update the remaining windows to only those at a higher
+ * stacking position than this one.
+ */
+ while (rem_win_stacking &&
+ stack_position >= GPOINTER_TO_INT (rem_win_stacking->data))
+ {
+ rem_windows = rem_windows->next;
+ rem_win_stacking = rem_win_stacking->next;
+ }
+
+ /* Remove edge portions overlapped by rem_windows and rem_docks */
+ new_edges =
+ meta_rectangle_remove_intersections_with_boxes_from_edges (
+ new_edges,
+ rem_windows);
+
+ /* Save the new edges */
+ edges = g_list_concat (new_edges, edges);
+ }
+
+ stack_position++;
+ cur_window_iter = cur_window_iter->next;
+ }
+
+ /*
+ * 4th: Free the extra memory not needed and sort the list
+ */
+ g_list_free (stacked_windows);
+ /* Free the memory used by the obscuring windows/docks lists */
+ g_slist_free (window_stacking);
+ /* FIXME: Shouldn't there be a helper function to make this one line of code
+ * to free a list instead of four ugly ones?
+ */
+ g_slist_foreach (obscuring_windows,
+ (void (*)(gpointer,gpointer))&g_free, /* ew, for ugly */
+ NULL);
+ g_slist_free (obscuring_windows);
+
+ /* Sort the list. FIXME: Should I bother with this sorting? I just
+ * sort again later in cache_edges() anyway...
+ */
+ edges = g_list_sort (edges, meta_rectangle_edge_cmp);
+
+ /*
+ * 5th: Cache the combination of these edges with the onscreen and
+ * xinerama edges in an array for quick access. Free the edges since
+ * they've been cached elsewhere.
+ */
+ cache_edges (display,
+ edges,
+ display->grab_screen->active_workspace->xinerama_edges,
+ display->grab_screen->active_workspace->screen_edges);
+ g_list_free (edges);
+
+ /*
+ * 6th: Initialize the resistance timeouts and buildups
+ */
+ initialize_grab_edge_resistance_data (display);
+}
+
+/* Note that old_[xy] and new_[xy] are with respect to inner positions of
+ * the window.
+ */
+void
+meta_window_edge_resistance_for_move (MetaWindow *window,
+ int old_x,
+ int old_y,
+ int *new_x,
+ int *new_y,
+ GSourceFunc timeout_func,
+ gboolean snap,
+ gboolean is_keyboard_op)
+{
+ MetaRectangle old_outer, proposed_outer, new_outer;
+ gboolean is_resize;
+
+ if (window == window->display->grab_window &&
+ window->display->grab_wireframe_active)
+ {
+ meta_window_get_xor_rect (window,
+ &window->display->grab_wireframe_rect,
+ &old_outer);
+ }
+ else
+ {
+ meta_window_get_outer_rect (window, &old_outer);
+ }
+ proposed_outer = old_outer;
+ proposed_outer.x += (*new_x - old_x);
+ proposed_outer.y += (*new_y - old_y);
+ new_outer = proposed_outer;
+
+ window->display->grab_last_user_action_was_snap = snap;
+ is_resize = FALSE;
+ if (apply_edge_resistance_to_each_side (window->display,
+ window,
+ &old_outer,
+ &new_outer,
+ timeout_func,
+ snap,
+ is_keyboard_op,
+ is_resize))
+ {
+ /* apply_edge_resistance_to_each_side independently applies
+ * resistance to both the right and left edges of new_outer as both
+ * could meet areas of resistance. But we don't want a resize, so we
+ * just have both edges move according to the stricter of the
+ * resistances. Same thing goes for top & bottom edges.
+ */
+ MetaRectangle *reference;
+ int left_change, right_change, smaller_x_change;
+ int top_change, bottom_change, smaller_y_change;
+
+ if (snap && !is_keyboard_op)
+ reference = &proposed_outer;
+ else
+ reference = &old_outer;
+
+ left_change = BOX_LEFT (new_outer) - BOX_LEFT (*reference);
+ right_change = BOX_RIGHT (new_outer) - BOX_RIGHT (*reference);
+ if ( snap && is_keyboard_op && left_change == 0)
+ smaller_x_change = right_change;
+ else if (snap && is_keyboard_op && right_change == 0)
+ smaller_x_change = left_change;
+ else if (ABS (left_change) < ABS (right_change))
+ smaller_x_change = left_change;
+ else
+ smaller_x_change = right_change;
+
+ top_change = BOX_TOP (new_outer) - BOX_TOP (*reference);
+ bottom_change = BOX_BOTTOM (new_outer) - BOX_BOTTOM (*reference);
+ if ( snap && is_keyboard_op && top_change == 0)
+ smaller_y_change = bottom_change;
+ else if (snap && is_keyboard_op && bottom_change == 0)
+ smaller_y_change = top_change;
+ else if (ABS (top_change) < ABS (bottom_change))
+ smaller_y_change = top_change;
+ else
+ smaller_y_change = bottom_change;
+
+ *new_x = old_x + smaller_x_change +
+ (BOX_LEFT (*reference) - BOX_LEFT (old_outer));
+ *new_y = old_y + smaller_y_change +
+ (BOX_TOP (*reference) - BOX_TOP (old_outer));
+
+ meta_topic (META_DEBUG_EDGE_RESISTANCE,
+ "outer x & y move-to coordinate changed from %d,%d to %d,%d\n",
+ proposed_outer.x, proposed_outer.y,
+ old_outer.x + (*new_x - old_x),
+ old_outer.y + (*new_y - old_y));
+ }
+}
+
+/* Note that old_(width|height) and new_(width|height) are with respect to
+ * sizes of the inner window.
+ */
+void
+meta_window_edge_resistance_for_resize (MetaWindow *window,
+ int old_width,
+ int old_height,
+ int *new_width,
+ int *new_height,
+ int gravity,
+ GSourceFunc timeout_func,
+ gboolean snap,
+ gboolean is_keyboard_op)
+{
+ MetaRectangle old_outer, new_outer;
+ int proposed_outer_width, proposed_outer_height;
+ gboolean is_resize;
+
+ if (window == window->display->grab_window &&
+ window->display->grab_wireframe_active)
+ {
+ meta_window_get_xor_rect (window,
+ &window->display->grab_wireframe_rect,
+ &old_outer);
+ }
+ else
+ {
+ meta_window_get_outer_rect (window, &old_outer);
+ }
+ proposed_outer_width = old_outer.width + (*new_width - old_width);
+ proposed_outer_height = old_outer.height + (*new_height - old_height);
+ meta_rectangle_resize_with_gravity (&old_outer,
+ &new_outer,
+ gravity,
+ proposed_outer_width,
+ proposed_outer_height);
+
+ window->display->grab_last_user_action_was_snap = snap;
+ is_resize = TRUE;
+ if (apply_edge_resistance_to_each_side (window->display,
+ window,
+ &old_outer,
+ &new_outer,
+ timeout_func,
+ snap,
+ is_keyboard_op,
+ is_resize))
+ {
+ *new_width = old_width + (new_outer.width - old_outer.width);
+ *new_height = old_height + (new_outer.height - old_outer.height);
+
+ meta_topic (META_DEBUG_EDGE_RESISTANCE,
+ "outer width & height got changed from %d,%d to %d,%d\n",
+ proposed_outer_width, proposed_outer_height,
+ new_outer.width, new_outer.height);
+ }
+}
diff --git a/src/core/edge-resistance.h b/src/core/edge-resistance.h
new file mode 100644
index 00000000..14ba17a0
--- /dev/null
+++ b/src/core/edge-resistance.h
@@ -0,0 +1,48 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Edge resistance for move/resize operations */
+
+/*
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_EDGE_RESISTANCE_H
+#define META_EDGE_RESISTANCE_H
+
+#include "window-private.h"
+
+void meta_window_edge_resistance_for_move (MetaWindow *window,
+ int old_x,
+ int old_y,
+ int *new_x,
+ int *new_y,
+ GSourceFunc timeout_func,
+ gboolean snap,
+ gboolean is_keyboard_op);
+void meta_window_edge_resistance_for_resize (MetaWindow *window,
+ int old_width,
+ int old_height,
+ int *new_width,
+ int *new_height,
+ int gravity,
+ GSourceFunc timeout_func,
+ gboolean snap,
+ gboolean is_keyboard_op);
+
+#endif /* META_EDGE_RESISTANCE_H */
+
diff --git a/src/core/effects.c b/src/core/effects.c
new file mode 100644
index 00000000..1392b7c2
--- /dev/null
+++ b/src/core/effects.c
@@ -0,0 +1,735 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file effects.c "Special effects" other than compositor effects.
+ *
+ * Before we had a serious compositor, we supported swooping
+ * rectangles for minimising and so on. These are still supported
+ * today, even when the compositor is enabled. The file contains two
+ * parts:
+ *
+ * 1) A set of functions, each of which implements a special effect.
+ * (Only the minimize function does anything interesting; we should
+ * probably get rid of the rest.)
+ *
+ * 2) A set of functions for moving a highlighted wireframe box around
+ * the screen, optionally with height and width shown in the middle.
+ * This is used for moving and resizing when reduced_resources is set.
+ *
+ * There was formerly a system which allowed callers to drop in their
+ * own handlers for various things; it was never used (people who want
+ * their own handlers can just modify this file, after all) and it added
+ * a good deal of extra complexity, so it has been removed. If you want it,
+ * it can be found in svn r3769.
+ *
+ * Once upon a time there were three different ways of drawing the box
+ * animation: window wireframe, window opaque, and root. People who had
+ * the shape extension theoretically had the choice of all three, and
+ * people who didn't weren't given the choice of the wireframe option.
+ * In practice, though, the opaque animation was never perfect, so it came
+ * down to the wireframe option for those who had the extension and
+ * the root option for those who didn't; there was actually no way of choosing
+ * any other option anyway. Work on the opaque animation stopped in 2002;
+ * anyone who wants something like that these days will be using the
+ * compositor anyway.
+ *
+ * In svn r3769 this was made explicit.
+ */
+
+/*
+ * Copyright (C) 2001 Anders Carlsson, Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "effects.h"
+#include "display-private.h"
+#include "ui.h"
+#include "window-private.h"
+#include "prefs.h"
+
+#ifdef HAVE_SHAPE
+#include <X11/extensions/shape.h>
+#endif
+
+#define META_MINIMIZE_ANIMATION_LENGTH 0.25
+#define META_SHADE_ANIMATION_LENGTH 0.2
+
+#include <string.h>
+
+typedef struct MetaEffect MetaEffect;
+typedef struct MetaEffectPriv MetaEffectPriv;
+
+typedef struct
+{
+ MetaScreen *screen;
+
+ double millisecs_duration;
+ GTimeVal start_time;
+
+#ifdef HAVE_SHAPE
+ /** For wireframe window */
+ Window wireframe_xwindow;
+#else
+ /** Rectangle to erase */
+ MetaRectangle last_rect;
+
+ /** First time we've plotted anything in this animation? */
+ gboolean first_time;
+
+ /** For wireframe drawn on root window */
+ GC gc;
+#endif
+
+ MetaRectangle start_rect;
+ MetaRectangle end_rect;
+
+} BoxAnimationContext;
+
+/**
+ * Information we need to know during a maximise or minimise effect.
+ */
+typedef struct
+{
+ /** This is the normal-size window. */
+ MetaRectangle window_rect;
+ /** This is the size of the window when it's an icon. */
+ MetaRectangle icon_rect;
+} MetaMinimizeEffect, MetaUnminimizeEffect;
+
+struct MetaEffectPriv
+{
+ MetaEffectFinished finished;
+ gpointer finished_data;
+};
+
+struct MetaEffect
+{
+ /** The window the effect is applied to. */
+ MetaWindow *window;
+ /** Which effect is happening here. */
+ MetaEffectType type;
+ /** The effect handler can hang data here. */
+ gpointer info;
+
+ union
+ {
+ MetaMinimizeEffect minimize;
+ /* ... and theoretically anything else */
+ } u;
+
+ MetaEffectPriv *priv;
+};
+
+static void run_default_effect_handler (MetaEffect *effect);
+static void run_handler (MetaEffect *effect);
+static void effect_free (MetaEffect *effect);
+
+static MetaEffect *
+create_effect (MetaEffectType type,
+ MetaWindow *window,
+ MetaEffectFinished finished,
+ gpointer finished_data);
+
+static void
+draw_box_animation (MetaScreen *screen,
+ MetaRectangle *initial_rect,
+ MetaRectangle *destination_rect,
+ double seconds_duration);
+
+/**
+ * Creates an effect.
+ *
+ */
+static MetaEffect*
+create_effect (MetaEffectType type,
+ MetaWindow *window,
+ MetaEffectFinished finished,
+ gpointer finished_data)
+{
+ MetaEffect *effect = g_new (MetaEffect, 1);
+
+ effect->type = type;
+ effect->window = window;
+ effect->priv = g_new (MetaEffectPriv, 1);
+ effect->priv->finished = finished;
+ effect->priv->finished_data = finished_data;
+
+ return effect;
+}
+
+/**
+ * Destroys an effect. If the effect has a "finished" hook, it will be
+ * called before cleanup.
+ *
+ * \param effect The effect.
+ */
+static void
+effect_free (MetaEffect *effect)
+{
+ if (effect->priv->finished)
+ effect->priv->finished (effect->priv->finished_data);
+
+ g_free (effect->priv);
+ g_free (effect);
+}
+
+void
+meta_effect_run_focus (MetaWindow *window,
+ MetaEffectFinished finished,
+ gpointer data)
+{
+ MetaEffect *effect;
+
+ g_return_if_fail (window != NULL);
+
+ effect = create_effect (META_EFFECT_FOCUS, window, finished, data);
+
+ run_handler (effect);
+}
+
+void
+meta_effect_run_minimize (MetaWindow *window,
+ MetaRectangle *window_rect,
+ MetaRectangle *icon_rect,
+ MetaEffectFinished finished,
+ gpointer data)
+{
+ MetaEffect *effect;
+
+ g_return_if_fail (window != NULL);
+ g_return_if_fail (icon_rect != NULL);
+
+ effect = create_effect (META_EFFECT_MINIMIZE, window, finished, data);
+
+ effect->u.minimize.window_rect = *window_rect;
+ effect->u.minimize.icon_rect = *icon_rect;
+
+ run_handler (effect);
+}
+
+void
+meta_effect_run_unminimize (MetaWindow *window,
+ MetaRectangle *window_rect,
+ MetaRectangle *icon_rect,
+ MetaEffectFinished finished,
+ gpointer data)
+{
+ MetaEffect *effect;
+
+ g_return_if_fail (window != NULL);
+ g_return_if_fail (icon_rect != NULL);
+
+ effect = create_effect (META_EFFECT_UNMINIMIZE, window, finished, data);
+
+ effect->u.minimize.window_rect = *window_rect;
+ effect->u.minimize.icon_rect = *icon_rect;
+
+ run_handler (effect);
+}
+
+void
+meta_effect_run_close (MetaWindow *window,
+ MetaEffectFinished finished,
+ gpointer data)
+{
+ MetaEffect *effect;
+
+ g_return_if_fail (window != NULL);
+
+ effect = create_effect (META_EFFECT_CLOSE, window,
+ finished, data);
+
+ run_handler (effect);
+}
+
+
+/* old ugly minimization effect */
+
+#ifdef HAVE_SHAPE
+static void
+update_wireframe_window (MetaDisplay *display,
+ Window xwindow,
+ const MetaRectangle *rect)
+{
+ XMoveResizeWindow (display->xdisplay,
+ xwindow,
+ rect->x, rect->y,
+ rect->width, rect->height);
+
+#define OUTLINE_WIDTH 3
+
+ if (rect->width > OUTLINE_WIDTH * 2 &&
+ rect->height > OUTLINE_WIDTH * 2)
+ {
+ XRectangle xrect;
+ Region inner_xregion;
+ Region outer_xregion;
+
+ inner_xregion = XCreateRegion ();
+ outer_xregion = XCreateRegion ();
+
+ xrect.x = 0;
+ xrect.y = 0;
+ xrect.width = rect->width;
+ xrect.height = rect->height;
+
+ XUnionRectWithRegion (&xrect, outer_xregion, outer_xregion);
+
+ xrect.x += OUTLINE_WIDTH;
+ xrect.y += OUTLINE_WIDTH;
+ xrect.width -= OUTLINE_WIDTH * 2;
+ xrect.height -= OUTLINE_WIDTH * 2;
+
+ XUnionRectWithRegion (&xrect, inner_xregion, inner_xregion);
+
+ XSubtractRegion (outer_xregion, inner_xregion, outer_xregion);
+
+ XShapeCombineRegion (display->xdisplay, xwindow,
+ ShapeBounding, 0, 0, outer_xregion, ShapeSet);
+
+ XDestroyRegion (outer_xregion);
+ XDestroyRegion (inner_xregion);
+ }
+ else
+ {
+ /* Unset the shape */
+ XShapeCombineMask (display->xdisplay, xwindow,
+ ShapeBounding, 0, 0, None, ShapeSet);
+ }
+}
+#endif
+
+/**
+ * A hack to force the X server to synchronize with the
+ * graphics hardware.
+ */
+static void
+graphics_sync (BoxAnimationContext *context)
+{
+ XImage *image;
+
+ image = XGetImage (context->screen->display->xdisplay,
+ context->screen->xroot,
+ 0, 0, 1, 1,
+ AllPlanes, ZPixmap);
+
+ XDestroyImage (image);
+}
+
+static gboolean
+effects_draw_box_animation_timeout (BoxAnimationContext *context)
+{
+ double elapsed;
+ GTimeVal current_time;
+ MetaRectangle draw_rect;
+ double fraction;
+
+#ifndef HAVE_SHAPE
+ if (!context->first_time)
+ {
+ /* Restore the previously drawn background */
+ XDrawRectangle (context->screen->display->xdisplay,
+ context->screen->xroot,
+ context->gc,
+ context->last_rect.x, context->last_rect.y,
+ context->last_rect.width, context->last_rect.height);
+ }
+ else
+ context->first_time = FALSE;
+
+#endif /* !HAVE_SHAPE */
+
+ g_get_current_time (&current_time);
+
+ /* We use milliseconds for all times */
+ elapsed =
+ ((((double)current_time.tv_sec - context->start_time.tv_sec) * G_USEC_PER_SEC +
+ (current_time.tv_usec - context->start_time.tv_usec))) / 1000.0;
+
+ if (elapsed < 0)
+ {
+ /* Probably the system clock was set backwards? */
+ meta_warning ("System clock seemed to go backwards?\n");
+ elapsed = G_MAXDOUBLE; /* definitely done. */
+ }
+
+ if (elapsed > context->millisecs_duration)
+ {
+ /* All done */
+#ifdef HAVE_SHAPE
+ XDestroyWindow (context->screen->display->xdisplay,
+ context->wireframe_xwindow);
+#else
+ meta_display_ungrab (context->screen->display);
+ meta_ui_pop_delay_exposes (context->screen->ui);
+ XFreeGC (context->screen->display->xdisplay,
+ context->gc);
+#endif /* !HAVE_SHAPE */
+
+ graphics_sync (context);
+
+ g_free (context);
+ return FALSE;
+ }
+
+ g_assert (context->millisecs_duration > 0.0);
+ fraction = elapsed / context->millisecs_duration;
+
+ draw_rect = context->start_rect;
+
+ /* Now add a delta proportional to elapsed time. */
+ draw_rect.x += (context->end_rect.x - context->start_rect.x) * fraction;
+ draw_rect.y += (context->end_rect.y - context->start_rect.y) * fraction;
+ draw_rect.width += (context->end_rect.width - context->start_rect.width) * fraction;
+ draw_rect.height += (context->end_rect.height - context->start_rect.height) * fraction;
+
+ /* don't confuse X or gdk-pixbuf with bogus rectangles */
+ if (draw_rect.width < 1)
+ draw_rect.width = 1;
+ if (draw_rect.height < 1)
+ draw_rect.height = 1;
+
+#ifdef HAVE_SHAPE
+ update_wireframe_window (context->screen->display,
+ context->wireframe_xwindow,
+ &draw_rect);
+#else
+ context->last_rect = draw_rect;
+
+ /* Draw the rectangle */
+ XDrawRectangle (context->screen->display->xdisplay,
+ context->screen->xroot,
+ context->gc,
+ draw_rect.x, draw_rect.y,
+ draw_rect.width, draw_rect.height);
+
+#endif /* !HAVE_SHAPE */
+
+ /* kick changes onto the server */
+ graphics_sync (context);
+
+ return TRUE;
+}
+
+void
+draw_box_animation (MetaScreen *screen,
+ MetaRectangle *initial_rect,
+ MetaRectangle *destination_rect,
+ double seconds_duration)
+{
+ BoxAnimationContext *context;
+
+#ifdef HAVE_SHAPE
+ XSetWindowAttributes attrs;
+#else
+ XGCValues gc_values;
+#endif
+
+ g_return_if_fail (seconds_duration > 0.0);
+
+ if (g_getenv ("MARCO_DEBUG_EFFECTS"))
+ seconds_duration *= 10; /* slow things down */
+
+ /* Create the animation context */
+ context = g_new0 (BoxAnimationContext, 1);
+
+ context->screen = screen;
+
+ context->millisecs_duration = seconds_duration * 1000.0;
+
+ context->start_rect = *initial_rect;
+ context->end_rect = *destination_rect;
+
+#ifdef HAVE_SHAPE
+
+ attrs.override_redirect = True;
+ attrs.background_pixel = BlackPixel (screen->display->xdisplay,
+ screen->number);
+
+ context->wireframe_xwindow = XCreateWindow (screen->display->xdisplay,
+ screen->xroot,
+ initial_rect->x,
+ initial_rect->y,
+ initial_rect->width,
+ initial_rect->height,
+ 0,
+ CopyFromParent,
+ CopyFromParent,
+ (Visual *)CopyFromParent,
+ CWOverrideRedirect | CWBackPixel,
+ &attrs);
+
+ update_wireframe_window (screen->display,
+ context->wireframe_xwindow,
+ initial_rect);
+
+ XMapWindow (screen->display->xdisplay,
+ context->wireframe_xwindow);
+
+#else /* !HAVE_SHAPE */
+
+ context->first_time = TRUE;
+ gc_values.subwindow_mode = IncludeInferiors;
+ gc_values.function = GXinvert;
+
+ context->gc = XCreateGC (screen->display->xdisplay,
+ screen->xroot,
+ GCSubwindowMode | GCFunction,
+ &gc_values);
+
+ /* Grab the X server to avoid screen dirt */
+ meta_display_grab (context->screen->display);
+ meta_ui_push_delay_exposes (context->screen->ui);
+#endif
+
+ /* Do this only after we get the pixbuf from the server,
+ * so that the animation doesn't get truncated.
+ */
+ g_get_current_time (&context->start_time);
+
+ /* Add the timeout - a short one, could even use an idle,
+ * but this is maybe more CPU-friendly.
+ */
+ g_timeout_add (15,
+ (GSourceFunc)effects_draw_box_animation_timeout,
+ context);
+
+ /* kick changes onto the server */
+ XFlush (context->screen->display->xdisplay);
+}
+
+void
+meta_effects_begin_wireframe (MetaScreen *screen,
+ const MetaRectangle *rect,
+ int width,
+ int height)
+{
+ /* Grab the X server to avoid screen dirt */
+ meta_display_grab (screen->display);
+ meta_ui_push_delay_exposes (screen->ui);
+
+ meta_effects_update_wireframe (screen,
+ NULL, -1, -1,
+ rect, width, height);
+}
+
+static void
+draw_xor_rect (MetaScreen *screen,
+ const MetaRectangle *rect,
+ int width,
+ int height)
+{
+ /* The lines in the center can't overlap the rectangle or each
+ * other, or the XOR gets reversed. So we have to draw things
+ * a bit oddly.
+ */
+ XSegment segments[8];
+ MetaRectangle shrunk_rect;
+ int i;
+
+#define LINE_WIDTH META_WIREFRAME_XOR_LINE_WIDTH
+
+ /* We don't want the wireframe going outside the window area.
+ * It makes it harder for the user to position windows and it exposes other
+ * annoying bugs.
+ */
+ shrunk_rect = *rect;
+
+ shrunk_rect.x += LINE_WIDTH / 2 + LINE_WIDTH % 2;
+ shrunk_rect.y += LINE_WIDTH / 2 + LINE_WIDTH % 2;
+ shrunk_rect.width -= LINE_WIDTH + 2 * (LINE_WIDTH % 2);
+ shrunk_rect.height -= LINE_WIDTH + 2 * (LINE_WIDTH % 2);
+
+ XDrawRectangle (screen->display->xdisplay,
+ screen->xroot,
+ screen->root_xor_gc,
+ shrunk_rect.x, shrunk_rect.y,
+ shrunk_rect.width, shrunk_rect.height);
+
+ /* Don't put lines inside small rectangles where they won't fit */
+ if (shrunk_rect.width < (LINE_WIDTH * 4) ||
+ shrunk_rect.height < (LINE_WIDTH * 4))
+ return;
+
+ if ((width >= 0) && (height >= 0))
+ {
+ XGCValues gc_values = { 0 };
+
+ if (XGetGCValues (screen->display->xdisplay,
+ screen->root_xor_gc,
+ GCFont, &gc_values))
+ {
+ char *text;
+ int text_length;
+
+ XFontStruct *font_struct;
+ int text_width, text_height;
+ int box_x, box_y;
+ int box_width, box_height;
+
+ font_struct = XQueryFont (screen->display->xdisplay,
+ gc_values.font);
+
+ if (font_struct != NULL)
+ {
+ text = g_strdup_printf ("%d x %d", width, height);
+ text_length = strlen (text);
+
+ text_width = text_length * font_struct->max_bounds.width;
+ text_height = font_struct->max_bounds.descent +
+ font_struct->max_bounds.ascent;
+
+ box_width = text_width + 2 * LINE_WIDTH;
+ box_height = text_height + 2 * LINE_WIDTH;
+
+
+ box_x = shrunk_rect.x + (shrunk_rect.width - box_width) / 2;
+ box_y = shrunk_rect.y + (shrunk_rect.height - box_height) / 2;
+
+ if ((box_width < shrunk_rect.width) &&
+ (box_height < shrunk_rect.height))
+ {
+ XFillRectangle (screen->display->xdisplay,
+ screen->xroot,
+ screen->root_xor_gc,
+ box_x, box_y,
+ box_width, box_height);
+ XDrawString (screen->display->xdisplay,
+ screen->xroot,
+ screen->root_xor_gc,
+ box_x + LINE_WIDTH,
+ box_y + LINE_WIDTH + font_struct->max_bounds.ascent,
+ text, text_length);
+ }
+
+ g_free (text);
+
+ XFreeFontInfo (NULL, font_struct, 1);
+
+ if ((box_width + LINE_WIDTH) >= (shrunk_rect.width / 3))
+ return;
+
+ if ((box_height + LINE_WIDTH) >= (shrunk_rect.height / 3))
+ return;
+ }
+ }
+ }
+
+ /* Two vertical lines at 1/3 and 2/3 */
+ segments[0].x1 = shrunk_rect.x + shrunk_rect.width / 3;
+ segments[0].y1 = shrunk_rect.y + LINE_WIDTH / 2 + LINE_WIDTH % 2;
+ segments[0].x2 = segments[0].x1;
+ segments[0].y2 = shrunk_rect.y + shrunk_rect.height - LINE_WIDTH / 2;
+
+ segments[1] = segments[0];
+ segments[1].x1 = shrunk_rect.x + (shrunk_rect.width / 3) * 2;
+ segments[1].x2 = segments[1].x1;
+
+ /* Now make two horizontal lines at 1/3 and 2/3, but not
+ * overlapping the verticals
+ */
+
+ segments[2].x1 = shrunk_rect.x + LINE_WIDTH / 2 + LINE_WIDTH % 2;
+ segments[2].x2 = segments[0].x1 - LINE_WIDTH / 2;
+ segments[2].y1 = shrunk_rect.y + shrunk_rect.height / 3;
+ segments[2].y2 = segments[2].y1;
+
+ segments[3] = segments[2];
+ segments[3].x1 = segments[2].x2 + LINE_WIDTH;
+ segments[3].x2 = segments[1].x1 - LINE_WIDTH / 2;
+
+ segments[4] = segments[3];
+ segments[4].x1 = segments[3].x2 + LINE_WIDTH;
+ segments[4].x2 = shrunk_rect.x + shrunk_rect.width - LINE_WIDTH / 2;
+
+ /* Second horizontal line is just like the first, but
+ * shifted down
+ */
+ i = 5;
+ while (i < 8)
+ {
+ segments[i] = segments[i - 3];
+ segments[i].y1 = shrunk_rect.y + (shrunk_rect.height / 3) * 2;
+ segments[i].y2 = segments[i].y1;
+ ++i;
+ }
+
+ XDrawSegments (screen->display->xdisplay,
+ screen->xroot,
+ screen->root_xor_gc,
+ segments,
+ G_N_ELEMENTS (segments));
+}
+
+void
+meta_effects_update_wireframe (MetaScreen *screen,
+ const MetaRectangle *old_rect,
+ int old_width,
+ int old_height,
+ const MetaRectangle *new_rect,
+ int new_width,
+ int new_height)
+{
+ if (old_rect)
+ draw_xor_rect (screen, old_rect, old_width, old_height);
+
+ if (new_rect)
+ draw_xor_rect (screen, new_rect, new_width, new_height);
+
+ XFlush (screen->display->xdisplay);
+}
+
+void
+meta_effects_end_wireframe (MetaScreen *screen,
+ const MetaRectangle *old_rect,
+ int old_width,
+ int old_height)
+{
+ meta_effects_update_wireframe (screen,
+ old_rect, old_width, old_height,
+ NULL, -1, -1);
+
+ meta_display_ungrab (screen->display);
+ meta_ui_pop_delay_exposes (screen->ui);
+}
+
+static void
+run_default_effect_handler (MetaEffect *effect)
+{
+ switch (effect->type)
+ {
+ case META_EFFECT_MINIMIZE:
+ draw_box_animation (effect->window->screen,
+ &(effect->u.minimize.window_rect),
+ &(effect->u.minimize.icon_rect),
+ META_MINIMIZE_ANIMATION_LENGTH);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+run_handler (MetaEffect *effect)
+{
+ if (meta_prefs_get_mate_animations ())
+ run_default_effect_handler (effect);
+
+ effect_free (effect);
+}
diff --git a/src/core/effects.h b/src/core/effects.h
new file mode 100644
index 00000000..91d09e15
--- /dev/null
+++ b/src/core/effects.h
@@ -0,0 +1,170 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file effects.h "Special effects" other than compositor effects.
+ *
+ * Before we had a serious compositor, we supported swooping
+ * rectangles for minimising and so on. These are still supported
+ * today, even when the compositor is enabled. The file contains two
+ * parts:
+ *
+ * 1) A set of functions, each of which implements a special effect.
+ * (Only the minimize function does anything interesting; we should
+ * probably get rid of the rest.)
+ *
+ * 2) A set of functions for moving a highlighted wireframe box around
+ * the screen, optionally with height and width shown in the middle.
+ * This is used for moving and resizing when reduced_resources is set.
+ *
+ * There was formerly a system which allowed callers to drop in their
+ * own handlers for various things; it was never used (people who want
+ * their own handlers can just modify this file, after all) and it added
+ * a good deal of extra complexity, so it has been removed. If you want it,
+ * it can be found in svn r3769.
+ */
+
+/*
+ * Copyright (C) 2001 Anders Carlsson, Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_EFFECTS_H
+#define META_EFFECTS_H
+
+#include "util.h"
+#include "screen-private.h"
+
+typedef enum
+{
+ META_EFFECT_MINIMIZE,
+ META_EFFECT_UNMINIMIZE,
+ META_EFFECT_FOCUS,
+ META_EFFECT_CLOSE,
+ META_NUM_EFFECTS
+} MetaEffectType;
+
+/**
+ * A callback which will be called when the effect has finished.
+ */
+typedef void (* MetaEffectFinished) (gpointer data);
+
+/**
+ * Performs the minimize effect.
+ *
+ * \param window The window we're moving
+ * \param window_rect Its current state
+ * \param target Where it should end up
+ * \param finished Callback for when it's finished
+ * \param data Data for callback
+ */
+void meta_effect_run_minimize (MetaWindow *window,
+ MetaRectangle *window_rect,
+ MetaRectangle *target,
+ MetaEffectFinished finished,
+ gpointer data);
+
+/**
+ * Performs the unminimize effect. There is no such effect.
+ * FIXME: delete this.
+ *
+ * \param window The window we're moving
+ * \param icon_rect Its current state
+ * \param window_rect Where it should end up
+ * \param finished Callback for when it's finished
+ * \param data Data for callback
+ */
+void meta_effect_run_unminimize (MetaWindow *window,
+ MetaRectangle *window_rect,
+ MetaRectangle *icon_rect,
+ MetaEffectFinished finished,
+ gpointer data);
+
+/**
+ * Performs the close effect. There is no such effect.
+ * FIXME: delete this.
+ *
+ * \param window The window we're moving
+ * \param finished Callback for when it's finished
+ * \param data Data for callback
+ */
+void meta_effect_run_close (MetaWindow *window,
+ MetaEffectFinished finished,
+ gpointer data);
+
+/**
+ * Performs the focus effect. There is no such effect.
+ * FIXME: delete this.
+ *
+ * \param window The window we're moving
+ * \param finished Callback for when it's finished
+ * \param data Data for callback
+ */
+void meta_effect_run_focus (MetaWindow *window,
+ MetaEffectFinished finished,
+ gpointer data);
+
+/**
+ * Grabs the server and paints a wireframe rectangle on the screen.
+ * Since this involves starting a grab, please be considerate of other
+ * users and don't keep the grab for long. You may move the wireframe
+ * around using meta_effects_update_wireframe() and remove it, and undo
+ * the grab, using meta_effects_end_wireframe().
+ *
+ * \param screen The screen to draw the rectangle on.
+ * \param rect The size of the rectangle to draw.
+ * \param width The width to display in the middle (or 0 not to)
+ * \param height The width to display in the middle (or 0 not to)
+ */
+void meta_effects_begin_wireframe (MetaScreen *screen,
+ const MetaRectangle *rect,
+ int width,
+ int height);
+
+/**
+ * Moves a wireframe rectangle around after its creation by
+ * meta_effects_begin_wireframe(). (Perhaps we ought to remember the old
+ * positions and not require people to pass them in?)
+ *
+ * \param old_rect Where the rectangle is now
+ * \param old_width The width that was displayed on it (or 0 if there wasn't)
+ * \param old_height The height that was displayed on it (or 0 if there wasn't)
+ * \param new_rect Where the rectangle is going
+ * \param new_width The width that will be displayed on it (or 0 not to)
+ * \param new_height The height that will be displayed on it (or 0 not to)
+ */
+void meta_effects_update_wireframe (MetaScreen *screen,
+ const MetaRectangle *old_rect,
+ int old_width,
+ int old_height,
+ const MetaRectangle *new_rect,
+ int new_width,
+ int new_height);
+
+/**
+ * Removes a wireframe rectangle from the screen and ends the grab started by
+ * meta_effects_begin_wireframe().
+ *
+ * \param old_rect Where the rectangle is now
+ * \param old_width The width that was displayed on it (or 0 if there wasn't)
+ * \param old_height The height that was displayed on it (or 0 if there wasn't)
+ */
+void meta_effects_end_wireframe (MetaScreen *screen,
+ const MetaRectangle *old_rect,
+ int width,
+ int height);
+
+#endif /* META_EFFECTS_H */
diff --git a/src/core/errors.c b/src/core/errors.c
new file mode 100644
index 00000000..3b2a6334
--- /dev/null
+++ b/src/core/errors.c
@@ -0,0 +1,288 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco X error handling */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington, error trapping inspired by GDK
+ * code copyrighted by the GTK team.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "errors.h"
+#include "display-private.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <gdk/gdk.h>
+
+static int x_error_handler (Display *display,
+ XErrorEvent *error);
+static int x_io_error_handler (Display *display);
+
+void
+meta_errors_init (void)
+{
+ XSetErrorHandler (x_error_handler);
+ XSetIOErrorHandler (x_io_error_handler);
+}
+
+typedef struct ForeignDisplay ForeignDisplay;
+
+struct ForeignDisplay
+{
+ Display *dpy;
+ ErrorHandler handler;
+ gpointer data;
+ ForeignDisplay *next;
+};
+
+static ForeignDisplay *foreign_displays;
+
+void
+meta_errors_register_foreign_display (Display *foreign_dpy,
+ ErrorHandler handler,
+ gpointer data)
+{
+ ForeignDisplay *info = g_new0 (ForeignDisplay, 1);
+ info->dpy = foreign_dpy;
+ info->handler = handler;
+ info->data = data;
+ info->next = foreign_displays;
+ foreign_displays = info;
+}
+
+static void
+meta_error_trap_push_internal (MetaDisplay *display,
+ gboolean need_sync)
+{
+ /* GDK resets the error handler on each push */
+ int (* old_error_handler) (Display *,
+ XErrorEvent *);
+
+ if (need_sync)
+ {
+ XSync (display->xdisplay, False);
+ }
+
+ gdk_error_trap_push ();
+
+ /* old_error_handler will just be equal to x_error_handler
+ * for nested traps
+ */
+ old_error_handler = XSetErrorHandler (x_error_handler);
+
+ /* Replace GDK handler, but save it so we can chain up */
+ if (display->error_trap_handler == NULL)
+ {
+ g_assert (display->error_traps == 0);
+ display->error_trap_handler = old_error_handler;
+ g_assert (display->error_trap_handler != x_error_handler);
+ }
+
+ display->error_traps += 1;
+
+ meta_topic (META_DEBUG_ERRORS, "%d traps remain\n", display->error_traps);
+}
+
+static int
+meta_error_trap_pop_internal (MetaDisplay *display,
+ gboolean need_sync)
+{
+ int result;
+
+ g_assert (display->error_traps > 0);
+
+ if (need_sync)
+ {
+ XSync (display->xdisplay, False);
+ }
+
+ result = gdk_error_trap_pop ();
+
+ display->error_traps -= 1;
+
+ if (display->error_traps == 0)
+ {
+ /* check that GDK put our handler back; this
+ * assumes that there are no pending GDK traps from GDK itself
+ */
+
+ int (* restored_error_handler) (Display *,
+ XErrorEvent *);
+
+ restored_error_handler = XSetErrorHandler (x_error_handler);
+
+ /* remove this */
+ display->error_trap_handler = NULL;
+ }
+
+ meta_topic (META_DEBUG_ERRORS, "%d traps\n", display->error_traps);
+
+ return result;
+}
+
+void
+meta_error_trap_push (MetaDisplay *display)
+{
+ meta_error_trap_push_internal (display, FALSE);
+}
+
+void
+meta_error_trap_pop (MetaDisplay *display,
+ gboolean last_request_was_roundtrip)
+{
+ gboolean need_sync;
+
+ /* we only have to sync when popping the outermost trap */
+ need_sync = (display->error_traps == 1 && !last_request_was_roundtrip);
+
+ if (need_sync)
+ meta_topic (META_DEBUG_SYNC, "Syncing on error_trap_pop, traps = %d, roundtrip = %d\n",
+ display->error_traps, last_request_was_roundtrip);
+
+ display->error_trap_synced_at_last_pop = need_sync || last_request_was_roundtrip;
+
+ meta_error_trap_pop_internal (display, need_sync);
+}
+
+void
+meta_error_trap_push_with_return (MetaDisplay *display)
+{
+ gboolean need_sync;
+
+ /* We don't sync on push_with_return if there are no traps
+ * currently, because we assume that any errors were either covered
+ * by a previous pop, or were fatal.
+ *
+ * More generally, we don't sync if we were synchronized last time
+ * we popped. This is known to be the case if there are no traps,
+ * but we also keep a flag so we know whether it's the case otherwise.
+ */
+
+ if (!display->error_trap_synced_at_last_pop)
+ need_sync = TRUE;
+ else
+ need_sync = FALSE;
+
+ if (need_sync)
+ meta_topic (META_DEBUG_SYNC, "Syncing on error_trap_push_with_return, traps = %d\n",
+ display->error_traps);
+
+ meta_error_trap_push_internal (display, FALSE);
+}
+
+int
+meta_error_trap_pop_with_return (MetaDisplay *display,
+ gboolean last_request_was_roundtrip)
+{
+ if (!last_request_was_roundtrip)
+ meta_topic (META_DEBUG_SYNC, "Syncing on error_trap_pop_with_return, traps = %d, roundtrip = %d\n",
+ display->error_traps, last_request_was_roundtrip);
+
+ display->error_trap_synced_at_last_pop = TRUE;
+
+ return meta_error_trap_pop_internal (display,
+ !last_request_was_roundtrip);
+}
+
+static int
+x_error_handler (Display *xdisplay,
+ XErrorEvent *error)
+{
+ int retval;
+ gchar buf[64];
+ MetaDisplay *display;
+ ForeignDisplay *foreign;
+
+ for (foreign = foreign_displays; foreign != NULL; foreign = foreign->next)
+ {
+ if (foreign->dpy == xdisplay)
+ {
+ foreign->handler (xdisplay, error, foreign->data);
+
+ return 0;
+ }
+ }
+
+ XGetErrorText (xdisplay, error->error_code, buf, 63);
+
+ display = meta_display_for_x_display (xdisplay);
+
+ /* Display can be NULL here because the compositing manager
+ * has its own Display, but Xlib only has one global error handler
+ */
+ if (display->error_traps > 0)
+ {
+ /* we're in an error trap, chain to the trap handler
+ * saved from GDK
+ */
+ meta_verbose ("X error: %s serial %ld error_code %d request_code %d minor_code %d)\n",
+ buf,
+ error->serial,
+ error->error_code,
+ error->request_code,
+ error->minor_code);
+
+ g_assert (display->error_trap_handler != NULL);
+ g_assert (display->error_trap_handler != x_error_handler);
+
+ retval = (* display->error_trap_handler) (xdisplay, error);
+ }
+ else
+ {
+ meta_bug ("Unexpected X error: %s serial %ld error_code %d request_code %d minor_code %d)\n",
+ buf,
+ error->serial,
+ error->error_code,
+ error->request_code,
+ error->minor_code);
+
+ retval = 1; /* compiler warning */
+ }
+
+ return retval;
+}
+
+static int
+x_io_error_handler (Display *xdisplay)
+{
+ MetaDisplay *display;
+
+ display = meta_display_for_x_display (xdisplay);
+
+ if (display == NULL)
+ meta_bug ("IO error received for unknown display?\n");
+
+ if (errno == EPIPE)
+ {
+ meta_warning (_("Lost connection to the display '%s';\n"
+ "most likely the X server was shut down or you killed/destroyed\n"
+ "the window manager.\n"),
+ display->name);
+ }
+ else
+ {
+ meta_warning (_("Fatal IO error %d (%s) on display '%s'.\n"),
+ errno, g_strerror (errno),
+ display->name);
+ }
+
+ /* Xlib would force an exit anyhow */
+ exit (1);
+
+ return 0;
+}
diff --git a/src/core/eventqueue.c b/src/core/eventqueue.c
new file mode 100644
index 00000000..2799cae9
--- /dev/null
+++ b/src/core/eventqueue.c
@@ -0,0 +1,184 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco X event source for main loop */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington (based on GDK code (C) Owen
+ * Taylor, Red Hat Inc.)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA. */
+
+#include "eventqueue.h"
+#include <X11/Xlib.h>
+
+static gboolean eq_prepare (GSource *source,
+ gint *timeout);
+static gboolean eq_check (GSource *source);
+static gboolean eq_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data);
+static void eq_destroy (GSource *source);
+
+static GSourceFuncs eq_funcs = {
+ eq_prepare,
+ eq_check,
+ eq_dispatch,
+ eq_destroy
+};
+
+struct _MetaEventQueue
+{
+ GSource source;
+
+ Display *display;
+ GPollFD poll_fd;
+ int connection_fd;
+ GQueue *events;
+};
+
+MetaEventQueue*
+meta_event_queue_new (Display *display, MetaEventQueueFunc func, gpointer data)
+{
+ GSource *source;
+ MetaEventQueue *eq;
+
+ source = g_source_new (&eq_funcs, sizeof (MetaEventQueue));
+ eq = (MetaEventQueue*) source;
+
+ eq->connection_fd = ConnectionNumber (display);
+ eq->poll_fd.fd = eq->connection_fd;
+ eq->poll_fd.events = G_IO_IN;
+
+ eq->events = g_queue_new ();
+
+ eq->display = display;
+
+ g_source_set_priority (source, G_PRIORITY_DEFAULT);
+ g_source_add_poll (source, &eq->poll_fd);
+ g_source_set_can_recurse (source, TRUE);
+
+ g_source_set_callback (source, (GSourceFunc) func, data, NULL);
+
+ g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ return eq;
+}
+
+void
+meta_event_queue_free (MetaEventQueue *eq)
+{
+ GSource *source;
+
+ source = (GSource*) eq;
+
+ g_source_destroy (source);
+}
+
+static gboolean
+eq_events_pending (MetaEventQueue *eq)
+{
+ return eq->events->length > 0 || XPending (eq->display);
+}
+
+static void
+eq_queue_events (MetaEventQueue *eq)
+{
+ XEvent xevent;
+
+ while (XPending (eq->display))
+ {
+ XEvent *copy;
+
+ XNextEvent (eq->display, &xevent);
+
+ copy = g_new (XEvent, 1);
+ *copy = xevent;
+
+ g_queue_push_tail (eq->events, copy);
+ }
+}
+
+static gboolean
+eq_prepare (GSource *source, gint *timeout)
+{
+ MetaEventQueue *eq;
+
+ eq = (MetaEventQueue*) source;
+
+ *timeout = -1;
+
+ return eq_events_pending (eq);
+}
+
+static gboolean
+eq_check (GSource *source)
+{
+ MetaEventQueue *eq;
+
+ eq = (MetaEventQueue*) source;
+
+ if (eq->poll_fd.revents & G_IO_IN)
+ return eq_events_pending (eq);
+ else
+ return FALSE;
+}
+
+static gboolean
+eq_dispatch (GSource *source, GSourceFunc callback, gpointer user_data)
+{
+ MetaEventQueue *eq;
+
+ eq = (MetaEventQueue*) source;
+
+ eq_queue_events (eq);
+
+ if (eq->events->length > 0)
+ {
+ XEvent *event;
+ MetaEventQueueFunc func;
+
+ event = g_queue_pop_head (eq->events);
+ func = (MetaEventQueueFunc) callback;
+
+ (* func) (event, user_data);
+
+ g_free (event);
+ }
+
+ return TRUE;
+}
+
+static void
+eq_destroy (GSource *source)
+{
+ MetaEventQueue *eq;
+
+ eq = (MetaEventQueue*) source;
+
+ while (eq->events->length > 0)
+ {
+ XEvent *event;
+
+ event = g_queue_pop_head (eq->events);
+
+ g_free (event);
+ }
+
+ g_queue_free (eq->events);
+
+ /* source itself is freed by glib */
+}
diff --git a/src/core/eventqueue.h b/src/core/eventqueue.h
new file mode 100644
index 00000000..8f3596af
--- /dev/null
+++ b/src/core/eventqueue.h
@@ -0,0 +1,40 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco X event source for main loop */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_EVENT_QUEUE_H
+#define META_EVENT_QUEUE_H
+
+#include <glib.h>
+#include <X11/Xlib.h>
+
+typedef struct _MetaEventQueue MetaEventQueue;
+
+typedef void (* MetaEventQueueFunc) (XEvent *event,
+ gpointer data);
+
+MetaEventQueue* meta_event_queue_new (Display *display,
+ MetaEventQueueFunc func,
+ gpointer data);
+void meta_event_queue_free (MetaEventQueue *eq);
+
+#endif
diff --git a/src/core/frame-private.h b/src/core/frame-private.h
new file mode 100644
index 00000000..bde352dc
--- /dev/null
+++ b/src/core/frame-private.h
@@ -0,0 +1,88 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco X window decorations */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_FRAME_PRIVATE_H
+#define META_FRAME_PRIVATE_H
+
+#include "frame.h"
+#include "window-private.h"
+
+typedef struct _MetaFrameGeometry MetaFrameGeometry;
+
+struct _MetaFrameGeometry
+{
+ /* border sizes (space between frame and child) */
+ int left_width;
+ int right_width;
+ int top_height;
+ int bottom_height;
+};
+
+struct _MetaFrame
+{
+ /* window we frame */
+ MetaWindow *window;
+
+ /* reparent window */
+ Window xwindow;
+
+ MetaCursor current_cursor;
+
+ /* This rect is trusted info from where we put the
+ * frame, not the result of ConfigureNotify
+ */
+ MetaRectangle rect;
+
+ /* position of client, size of frame */
+ int child_x;
+ int child_y;
+ int right_width;
+ int bottom_height;
+
+ guint mapped : 1;
+ guint need_reapply_frame_shape : 1;
+ guint is_flashing : 1; /* used by the visual bell flash */
+};
+
+void meta_window_ensure_frame (MetaWindow *window);
+void meta_window_destroy_frame (MetaWindow *window);
+void meta_frame_queue_draw (MetaFrame *frame);
+
+MetaFrameFlags meta_frame_get_flags (MetaFrame *frame);
+
+/* These should ONLY be called from meta_window_move_resize_internal */
+void meta_frame_calc_geometry (MetaFrame *frame,
+ MetaFrameGeometry *geomp);
+void meta_frame_sync_to_window (MetaFrame *frame,
+ int gravity,
+ gboolean need_move,
+ gboolean need_resize);
+
+void meta_frame_set_screen_cursor (MetaFrame *frame,
+ MetaCursor cursor);
+
+#endif
+
+
+
+
diff --git a/src/core/frame.c b/src/core/frame.c
new file mode 100644
index 00000000..2d0bbb51
--- /dev/null
+++ b/src/core/frame.c
@@ -0,0 +1,421 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco X window decorations */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2003, 2004 Red Hat, Inc.
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "frame-private.h"
+#include "bell.h"
+#include "errors.h"
+#include "keybindings.h"
+
+#ifdef HAVE_RENDER
+#include <X11/extensions/Xrender.h>
+#endif
+
+#define EVENT_MASK (SubstructureRedirectMask | \
+ StructureNotifyMask | SubstructureNotifyMask | \
+ ExposureMask | \
+ ButtonPressMask | ButtonReleaseMask | \
+ PointerMotionMask | PointerMotionHintMask | \
+ EnterWindowMask | LeaveWindowMask | \
+ FocusChangeMask | \
+ ColormapChangeMask)
+
+void
+meta_window_ensure_frame (MetaWindow *window)
+{
+ MetaFrame *frame;
+ XSetWindowAttributes attrs;
+ Visual *visual;
+
+ if (window->frame)
+ return;
+
+ /* See comment below for why this is required. */
+ meta_display_grab (window->display);
+
+ frame = g_new (MetaFrame, 1);
+
+ frame->window = window;
+ frame->xwindow = None;
+
+ frame->rect = window->rect;
+ frame->child_x = 0;
+ frame->child_y = 0;
+ frame->bottom_height = 0;
+ frame->right_width = 0;
+ frame->current_cursor = 0;
+
+ frame->mapped = FALSE;
+ frame->need_reapply_frame_shape = TRUE;
+ frame->is_flashing = FALSE;
+
+ meta_verbose ("Framing window %s: visual %s default, depth %d default depth %d\n",
+ window->desc,
+ XVisualIDFromVisual (window->xvisual) ==
+ XVisualIDFromVisual (window->screen->default_xvisual) ?
+ "is" : "is not",
+ window->depth, window->screen->default_depth);
+ meta_verbose ("Frame geometry %d,%d %dx%d\n",
+ frame->rect.x, frame->rect.y,
+ frame->rect.width, frame->rect.height);
+
+ /* Default depth/visual handles clients with weird visuals; they can
+ * always be children of the root depth/visual obviously, but
+ * e.g. DRI games can't be children of a parent that has the same
+ * visual as the client. NULL means default visual.
+ *
+ * We look for an ARGB visual if we can find one, otherwise use
+ * the default of NULL.
+ */
+
+ /* Special case for depth 32 windows (assumed to be ARGB),
+ * we use the window's visual. Otherwise we just use the system visual.
+ */
+ if (window->depth == 32)
+ visual = window->xvisual;
+ else
+ visual = NULL;
+
+ frame->xwindow = meta_ui_create_frame_window (window->screen->ui,
+ window->display->xdisplay,
+ visual,
+ frame->rect.x,
+ frame->rect.y,
+ frame->rect.width,
+ frame->rect.height,
+ frame->window->screen->number);
+
+ meta_verbose ("Frame for %s is 0x%lx\n", frame->window->desc, frame->xwindow);
+ attrs.event_mask = EVENT_MASK;
+ XChangeWindowAttributes (window->display->xdisplay,
+ frame->xwindow, CWEventMask, &attrs);
+
+ meta_display_register_x_window (window->display, &frame->xwindow, window);
+
+ /* Now that frame->xwindow is registered with window, we can set its
+ * background.
+ */
+ meta_ui_reset_frame_bg (window->screen->ui, frame->xwindow);
+
+ /* Reparent the client window; it may be destroyed,
+ * thus the error trap. We'll get a destroy notify later
+ * and free everything. Comment in FVWM source code says
+ * we need a server grab or the child can get its MapNotify
+ * before we've finished reparenting and getting the decoration
+ * window onscreen, so ensure_frame must be called with
+ * a grab.
+ */
+ meta_error_trap_push (window->display);
+ if (window->mapped)
+ {
+ window->mapped = FALSE; /* the reparent will unmap the window,
+ * we don't want to take that as a withdraw
+ */
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Incrementing unmaps_pending on %s for reparent\n", window->desc);
+ window->unmaps_pending += 1;
+ }
+ /* window was reparented to this position */
+ window->rect.x = 0;
+ window->rect.y = 0;
+
+ XReparentWindow (window->display->xdisplay,
+ window->xwindow,
+ frame->xwindow,
+ window->rect.x,
+ window->rect.y);
+ /* FIXME handle this error */
+ meta_error_trap_pop (window->display, FALSE);
+
+ /* stick frame to the window */
+ window->frame = frame;
+
+ if (window->title)
+ meta_ui_set_frame_title (window->screen->ui,
+ window->frame->xwindow,
+ window->title);
+
+ /* Move keybindings to frame instead of window */
+ meta_window_grab_keys (window);
+
+ /* Shape mask */
+ meta_ui_apply_frame_shape (frame->window->screen->ui,
+ frame->xwindow,
+ frame->rect.width,
+ frame->rect.height,
+ frame->window->has_shape);
+ frame->need_reapply_frame_shape = FALSE;
+
+ meta_display_ungrab (window->display);
+}
+
+void
+meta_window_destroy_frame (MetaWindow *window)
+{
+ MetaFrame *frame;
+
+ if (window->frame == NULL)
+ return;
+
+ meta_verbose ("Unframing window %s\n", window->desc);
+
+ frame = window->frame;
+
+ meta_bell_notify_frame_destroy (frame);
+
+ /* Unparent the client window; it may be destroyed,
+ * thus the error trap.
+ */
+ meta_error_trap_push (window->display);
+ if (window->mapped)
+ {
+ window->mapped = FALSE; /* Keep track of unmapping it, so we
+ * can identify a withdraw initiated
+ * by the client.
+ */
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Incrementing unmaps_pending on %s for reparent back to root\n", window->desc);
+ window->unmaps_pending += 1;
+ }
+ XReparentWindow (window->display->xdisplay,
+ window->xwindow,
+ window->screen->xroot,
+ /* Using anything other than meta_window_get_position()
+ * coordinates here means we'll need to ensure a configure
+ * notify event is sent; see bug 399552.
+ */
+ window->frame->rect.x,
+ window->frame->rect.y);
+ meta_error_trap_pop (window->display, FALSE);
+
+ meta_ui_destroy_frame_window (window->screen->ui, frame->xwindow);
+
+ meta_display_unregister_x_window (window->display,
+ frame->xwindow);
+
+ window->frame = NULL;
+
+ /* Move keybindings to window instead of frame */
+ meta_window_grab_keys (window);
+
+ g_free (frame);
+
+ /* Put our state back where it should be */
+ meta_window_queue (window, META_QUEUE_CALC_SHOWING);
+ meta_window_queue (window, META_QUEUE_MOVE_RESIZE);
+}
+
+
+MetaFrameFlags
+meta_frame_get_flags (MetaFrame *frame)
+{
+ MetaFrameFlags flags;
+
+ flags = 0;
+
+ if (frame->window->border_only)
+ {
+ ; /* FIXME this may disable the _function_ as well as decor
+ * in some cases, which is sort of wrong.
+ */
+ }
+ else
+ {
+ flags |= META_FRAME_ALLOWS_MENU;
+
+ if (frame->window->has_close_func)
+ flags |= META_FRAME_ALLOWS_DELETE;
+
+ if (frame->window->has_maximize_func)
+ flags |= META_FRAME_ALLOWS_MAXIMIZE;
+
+ if (frame->window->has_minimize_func)
+ flags |= META_FRAME_ALLOWS_MINIMIZE;
+
+ if (frame->window->has_shade_func)
+ flags |= META_FRAME_ALLOWS_SHADE;
+ }
+
+ if (META_WINDOW_ALLOWS_MOVE (frame->window))
+ flags |= META_FRAME_ALLOWS_MOVE;
+
+ if (META_WINDOW_ALLOWS_HORIZONTAL_RESIZE (frame->window))
+ flags |= META_FRAME_ALLOWS_HORIZONTAL_RESIZE;
+
+ if (META_WINDOW_ALLOWS_VERTICAL_RESIZE (frame->window))
+ flags |= META_FRAME_ALLOWS_VERTICAL_RESIZE;
+
+ if (frame->window->has_focus)
+ flags |= META_FRAME_HAS_FOCUS;
+
+ if (frame->window->shaded)
+ flags |= META_FRAME_SHADED;
+
+ if (frame->window->on_all_workspaces)
+ flags |= META_FRAME_STUCK;
+
+ /* FIXME: Should we have some kind of UI for windows that are just vertically
+ * maximized or just horizontally maximized?
+ */
+ if (META_WINDOW_MAXIMIZED (frame->window))
+ flags |= META_FRAME_MAXIMIZED;
+
+ if (frame->window->fullscreen)
+ flags |= META_FRAME_FULLSCREEN;
+
+ if (frame->is_flashing)
+ flags |= META_FRAME_IS_FLASHING;
+
+ if (frame->window->wm_state_above)
+ flags |= META_FRAME_ABOVE;
+
+ return flags;
+}
+
+void
+meta_frame_calc_geometry (MetaFrame *frame,
+ MetaFrameGeometry *geomp)
+{
+ MetaFrameGeometry geom;
+ MetaWindow *window;
+
+ window = frame->window;
+
+ meta_ui_get_frame_geometry (window->screen->ui,
+ frame->xwindow,
+ &geom.top_height,
+ &geom.bottom_height,
+ &geom.left_width,
+ &geom.right_width);
+
+ *geomp = geom;
+}
+
+static void
+update_shape (MetaFrame *frame)
+{
+ if (frame->need_reapply_frame_shape)
+ {
+ meta_ui_apply_frame_shape (frame->window->screen->ui,
+ frame->xwindow,
+ frame->rect.width,
+ frame->rect.height,
+ frame->window->has_shape);
+ frame->need_reapply_frame_shape = FALSE;
+ }
+}
+
+void
+meta_frame_sync_to_window (MetaFrame *frame,
+ int resize_gravity,
+ gboolean need_move,
+ gboolean need_resize)
+{
+ if (!(need_move || need_resize))
+ {
+ update_shape (frame);
+ return;
+ }
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Syncing frame geometry %d,%d %dx%d (SE: %d,%d)\n",
+ frame->rect.x, frame->rect.y,
+ frame->rect.width, frame->rect.height,
+ frame->rect.x + frame->rect.width,
+ frame->rect.y + frame->rect.height);
+
+ /* set bg to none to avoid flicker */
+ if (need_resize)
+ {
+ meta_ui_unflicker_frame_bg (frame->window->screen->ui,
+ frame->xwindow,
+ frame->rect.width,
+ frame->rect.height);
+
+ /* we need new shape if we're resized */
+ frame->need_reapply_frame_shape = TRUE;
+ }
+
+ /* Done before the window resize, because doing it before means
+ * part of the window being resized becomes unshaped, which may
+ * be sort of hard to see with bg = None. If we did it after
+ * window resize, part of the window being resized would become
+ * shaped, which might be more visible.
+ */
+ update_shape (frame);
+
+ meta_ui_move_resize_frame (frame->window->screen->ui,
+ frame->xwindow,
+ frame->rect.x,
+ frame->rect.y,
+ frame->rect.width,
+ frame->rect.height);
+
+ if (need_resize)
+ {
+ meta_ui_reset_frame_bg (frame->window->screen->ui,
+ frame->xwindow);
+
+ /* If we're interactively resizing the frame, repaint
+ * it immediately so we don't start to lag.
+ */
+ if (frame->window->display->grab_window ==
+ frame->window)
+ meta_ui_repaint_frame (frame->window->screen->ui,
+ frame->xwindow);
+ }
+}
+
+void
+meta_frame_queue_draw (MetaFrame *frame)
+{
+ meta_ui_queue_frame_draw (frame->window->screen->ui,
+ frame->xwindow);
+}
+
+void
+meta_frame_set_screen_cursor (MetaFrame *frame,
+ MetaCursor cursor)
+{
+ Cursor xcursor;
+ if (cursor == frame->current_cursor)
+ return;
+ frame->current_cursor = cursor;
+ if (cursor == META_CURSOR_DEFAULT)
+ XUndefineCursor (frame->window->display->xdisplay, frame->xwindow);
+ else
+ {
+ xcursor = meta_display_create_x_cursor (frame->window->display, cursor);
+ XDefineCursor (frame->window->display->xdisplay, frame->xwindow, xcursor);
+ XFlush (frame->window->display->xdisplay);
+ XFreeCursor (frame->window->display->xdisplay, xcursor);
+ }
+}
+
+Window
+meta_frame_get_xwindow (MetaFrame *frame)
+{
+ return frame->xwindow;
+}
diff --git a/src/core/group-private.h b/src/core/group-private.h
new file mode 100644
index 00000000..bf8dd5a3
--- /dev/null
+++ b/src/core/group-private.h
@@ -0,0 +1,43 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco window group private header */
+
+/*
+ * Copyright (C) 2002 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_GROUP_PRIVATE_H
+#define META_GROUP_PRIVATE_H
+
+#include "group.h"
+
+struct _MetaGroup
+{
+ int refcount;
+ MetaDisplay *display;
+ GSList *windows;
+ Window group_leader;
+ char *startup_id;
+ char *wm_client_machine;
+};
+
+#endif
+
+
+
+
diff --git a/src/core/group-props.c b/src/core/group-props.c
new file mode 100644
index 00000000..05e82900
--- /dev/null
+++ b/src/core/group-props.c
@@ -0,0 +1,234 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* MetaGroup property handling */
+
+/*
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "group-props.h"
+#include "group-private.h"
+#include "xprops.h"
+#include <X11/Xatom.h>
+
+typedef void (* InitValueFunc) (MetaDisplay *display,
+ Atom property,
+ MetaPropValue *value);
+typedef void (* ReloadValueFunc) (MetaGroup *group,
+ MetaPropValue *value);
+
+struct _MetaGroupPropHooks
+{
+ Atom property;
+ InitValueFunc init_func;
+ ReloadValueFunc reload_func;
+};
+
+static void init_prop_value (MetaDisplay *display,
+ Atom property,
+ MetaPropValue *value);
+static void reload_prop_value (MetaGroup *group,
+ MetaPropValue *value);
+static MetaGroupPropHooks* find_hooks (MetaDisplay *display,
+ Atom property);
+
+
+
+void
+meta_group_reload_property (MetaGroup *group,
+ Atom property)
+{
+ meta_group_reload_properties (group, &property, 1);
+}
+
+void
+meta_group_reload_properties (MetaGroup *group,
+ const Atom *properties,
+ int n_properties)
+{
+ int i;
+ MetaPropValue *values;
+
+ g_return_if_fail (properties != NULL);
+ g_return_if_fail (n_properties > 0);
+
+ values = g_new0 (MetaPropValue, n_properties);
+
+ i = 0;
+ while (i < n_properties)
+ {
+ init_prop_value (group->display, properties[i], &values[i]);
+ ++i;
+ }
+
+ meta_prop_get_values (group->display, group->group_leader,
+ values, n_properties);
+
+ i = 0;
+ while (i < n_properties)
+ {
+ reload_prop_value (group, &values[i]);
+
+ ++i;
+ }
+
+ meta_prop_free_values (values, n_properties);
+
+ g_free (values);
+}
+
+/* Fill in the MetaPropValue used to get the value of "property" */
+static void
+init_prop_value (MetaDisplay *display,
+ Atom property,
+ MetaPropValue *value)
+{
+ MetaGroupPropHooks *hooks;
+
+ value->type = META_PROP_VALUE_INVALID;
+ value->atom = None;
+
+ hooks = find_hooks (display, property);
+ if (hooks && hooks->init_func != NULL)
+ (* hooks->init_func) (display, property, value);
+}
+
+static void
+reload_prop_value (MetaGroup *group,
+ MetaPropValue *value)
+{
+ MetaGroupPropHooks *hooks;
+
+ hooks = find_hooks (group->display, value->atom);
+ if (hooks && hooks->reload_func != NULL)
+ (* hooks->reload_func) (group, value);
+}
+
+static void
+init_wm_client_machine (MetaDisplay *display,
+ Atom property,
+ MetaPropValue *value)
+{
+ value->type = META_PROP_VALUE_STRING;
+ value->atom = display->atom_WM_CLIENT_MACHINE;
+}
+
+static void
+reload_wm_client_machine (MetaGroup *group,
+ MetaPropValue *value)
+{
+ g_free (group->wm_client_machine);
+ group->wm_client_machine = NULL;
+
+ if (value->type != META_PROP_VALUE_INVALID)
+ group->wm_client_machine = g_strdup (value->v.str);
+
+ meta_verbose ("Group has client machine \"%s\"\n",
+ group->wm_client_machine ? group->wm_client_machine : "unset");
+}
+
+static void
+init_net_startup_id (MetaDisplay *display,
+ Atom property,
+ MetaPropValue *value)
+{
+ value->type = META_PROP_VALUE_UTF8;
+ value->atom = display->atom__NET_STARTUP_ID;
+}
+
+static void
+reload_net_startup_id (MetaGroup *group,
+ MetaPropValue *value)
+{
+ g_free (group->startup_id);
+ group->startup_id = NULL;
+
+ if (value->type != META_PROP_VALUE_INVALID)
+ group->startup_id = g_strdup (value->v.str);
+
+ meta_verbose ("Group has startup id \"%s\"\n",
+ group->startup_id ? group->startup_id : "unset");
+}
+
+#define N_HOOKS 3
+
+void
+meta_display_init_group_prop_hooks (MetaDisplay *display)
+{
+ int i;
+ MetaGroupPropHooks *hooks;
+
+ g_assert (display->group_prop_hooks == NULL);
+
+ display->group_prop_hooks = g_new0 (MetaGroupPropHooks, N_HOOKS);
+ hooks = display->group_prop_hooks;
+
+ i = 0;
+
+ hooks[i].property = display->atom_WM_CLIENT_MACHINE;
+ hooks[i].init_func = init_wm_client_machine;
+ hooks[i].reload_func = reload_wm_client_machine;
+ ++i;
+
+ hooks[i].property = display->atom__NET_WM_PID;
+ hooks[i].init_func = NULL;
+ hooks[i].reload_func = NULL;
+ ++i;
+
+ hooks[i].property = display->atom__NET_STARTUP_ID;
+ hooks[i].init_func = init_net_startup_id;
+ hooks[i].reload_func = reload_net_startup_id;
+ ++i;
+
+ if (i != N_HOOKS)
+ {
+ g_error ("Initialized %d group hooks should have been %d\n", i, N_HOOKS);
+ }
+}
+
+void
+meta_display_free_group_prop_hooks (MetaDisplay *display)
+{
+ g_assert (display->group_prop_hooks != NULL);
+
+ g_free (display->group_prop_hooks);
+ display->group_prop_hooks = NULL;
+}
+
+static MetaGroupPropHooks*
+find_hooks (MetaDisplay *display,
+ Atom property)
+{
+ int i;
+
+ /* FIXME we could sort the array and do binary search or
+ * something
+ */
+
+ i = 0;
+ while (i < N_HOOKS)
+ {
+ if (display->group_prop_hooks[i].property == property)
+ return &display->group_prop_hooks[i];
+
+ ++i;
+ }
+
+ return NULL;
+}
diff --git a/src/core/group-props.h b/src/core/group-props.h
new file mode 100644
index 00000000..ffde0901
--- /dev/null
+++ b/src/core/group-props.h
@@ -0,0 +1,37 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* MetaGroup property handling */
+
+/*
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_GROUP_PROPS_H
+#define META_GROUP_PROPS_H
+
+#include "group.h"
+
+void meta_group_reload_property (MetaGroup *group,
+ Atom property);
+void meta_group_reload_properties (MetaGroup *group,
+ const Atom *properties,
+ int n_properties);
+void meta_display_init_group_prop_hooks (MetaDisplay *display);
+void meta_display_free_group_prop_hooks (MetaDisplay *display);
+
+#endif /* META_GROUP_PROPS_H */
diff --git a/src/core/group.c b/src/core/group.c
new file mode 100644
index 00000000..35db53c7
--- /dev/null
+++ b/src/core/group.c
@@ -0,0 +1,274 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco window groups */
+
+/*
+ * Copyright (C) 2002 Red Hat Inc.
+ * Copyright (C) 2003 Rob Adams
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "util.h"
+#include "group-private.h"
+#include "group-props.h"
+#include "window.h"
+
+static MetaGroup*
+meta_group_new (MetaDisplay *display,
+ Window group_leader)
+{
+ MetaGroup *group;
+#define N_INITIAL_PROPS 3
+ Atom initial_props[N_INITIAL_PROPS];
+ int i;
+
+ g_assert (N_INITIAL_PROPS == (int) G_N_ELEMENTS (initial_props));
+
+ group = g_new0 (MetaGroup, 1);
+
+ group->display = display;
+ group->windows = NULL;
+ group->group_leader = group_leader;
+ group->refcount = 1; /* owned by caller, hash table has only weak ref */
+
+ if (display->groups_by_leader == NULL)
+ display->groups_by_leader = g_hash_table_new (meta_unsigned_long_hash,
+ meta_unsigned_long_equal);
+
+ g_assert (g_hash_table_lookup (display->groups_by_leader, &group_leader) == NULL);
+
+ g_hash_table_insert (display->groups_by_leader,
+ &group->group_leader,
+ group);
+
+ /* Fill these in the order we want them to be gotten */
+ i = 0;
+ initial_props[i++] = display->atom_WM_CLIENT_MACHINE;
+ initial_props[i++] = display->atom__NET_WM_PID;
+ initial_props[i++] = display->atom__NET_STARTUP_ID;
+ g_assert (N_INITIAL_PROPS == i);
+
+ meta_group_reload_properties (group, initial_props, N_INITIAL_PROPS);
+
+ meta_topic (META_DEBUG_GROUPS,
+ "Created new group with leader 0x%lx\n",
+ group->group_leader);
+
+ return group;
+}
+
+static void
+meta_group_unref (MetaGroup *group)
+{
+ g_return_if_fail (group->refcount > 0);
+
+ group->refcount -= 1;
+ if (group->refcount == 0)
+ {
+ meta_topic (META_DEBUG_GROUPS,
+ "Destroying group with leader 0x%lx\n",
+ group->group_leader);
+
+ g_assert (group->display->groups_by_leader != NULL);
+
+ g_hash_table_remove (group->display->groups_by_leader,
+ &group->group_leader);
+
+ /* mop up hash table, this is how it gets freed on display close */
+ if (g_hash_table_size (group->display->groups_by_leader) == 0)
+ {
+ g_hash_table_destroy (group->display->groups_by_leader);
+ group->display->groups_by_leader = NULL;
+ }
+
+ g_free (group->wm_client_machine);
+ g_free (group->startup_id);
+
+ g_free (group);
+ }
+}
+
+MetaGroup*
+meta_window_get_group (MetaWindow *window)
+{
+ if (window->unmanaging)
+ return NULL;
+
+ return window->group;
+}
+
+void
+meta_window_compute_group (MetaWindow* window)
+{
+ MetaGroup *group;
+ MetaWindow *ancestor;
+
+ /* use window->xwindow if no window->xgroup_leader */
+
+ group = NULL;
+
+ /* Determine the ancestor of the window; its group setting will override the
+ * normal grouping rules; see bug 328211.
+ */
+ ancestor = meta_window_find_root_ancestor (window);
+
+ if (window->display->groups_by_leader)
+ {
+ if (ancestor != window)
+ group = ancestor->group;
+ else if (window->xgroup_leader != None)
+ group = g_hash_table_lookup (window->display->groups_by_leader,
+ &window->xgroup_leader);
+ else
+ group = g_hash_table_lookup (window->display->groups_by_leader,
+ &window->xwindow);
+ }
+
+ if (group != NULL)
+ {
+ window->group = group;
+ group->refcount += 1;
+ }
+ else
+ {
+ if (ancestor != window && ancestor->xgroup_leader != None)
+ group = meta_group_new (window->display,
+ ancestor->xgroup_leader);
+ else if (window->xgroup_leader != None)
+ group = meta_group_new (window->display,
+ window->xgroup_leader);
+ else
+ group = meta_group_new (window->display,
+ window->xwindow);
+
+ window->group = group;
+ }
+
+ window->group->windows = g_slist_prepend (window->group->windows, window);
+
+ meta_topic (META_DEBUG_GROUPS,
+ "Adding %s to group with leader 0x%lx\n",
+ window->desc, group->group_leader);
+
+}
+
+static void
+remove_window_from_group (MetaWindow *window)
+{
+ if (window->group != NULL)
+ {
+ meta_topic (META_DEBUG_GROUPS,
+ "Removing %s from group with leader 0x%lx\n",
+ window->desc, window->group->group_leader);
+
+ window->group->windows =
+ g_slist_remove (window->group->windows,
+ window);
+ meta_group_unref (window->group);
+ window->group = NULL;
+ }
+}
+
+void
+meta_window_group_leader_changed (MetaWindow *window)
+{
+ remove_window_from_group (window);
+ meta_window_compute_group (window);
+}
+
+void
+meta_window_shutdown_group (MetaWindow *window)
+{
+ remove_window_from_group (window);
+}
+
+MetaGroup*
+meta_display_lookup_group (MetaDisplay *display,
+ Window group_leader)
+{
+ MetaGroup *group;
+
+ group = NULL;
+
+ if (display->groups_by_leader)
+ group = g_hash_table_lookup (display->groups_by_leader,
+ &group_leader);
+
+ return group;
+}
+
+GSList*
+meta_group_list_windows (MetaGroup *group)
+{
+ return g_slist_copy (group->windows);
+}
+
+void
+meta_group_update_layers (MetaGroup *group)
+{
+ GSList *tmp;
+ GSList *frozen_stacks;
+
+ if (group->windows == NULL)
+ return;
+
+ frozen_stacks = NULL;
+ tmp = group->windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *window = tmp->data;
+
+ /* we end up freezing the same stack a lot of times,
+ * but doesn't hurt anything. have to handle
+ * groups that span 2 screens.
+ */
+ meta_stack_freeze (window->screen->stack);
+ frozen_stacks = g_slist_prepend (frozen_stacks, window->screen->stack);
+
+ meta_stack_update_layer (window->screen->stack,
+ window);
+
+ tmp = tmp->next;
+ }
+
+ tmp = frozen_stacks;
+ while (tmp != NULL)
+ {
+ meta_stack_thaw (tmp->data);
+ tmp = tmp->next;
+ }
+
+ g_slist_free (frozen_stacks);
+}
+
+const char*
+meta_group_get_startup_id (MetaGroup *group)
+{
+ return group->startup_id;
+}
+
+gboolean
+meta_group_property_notify (MetaGroup *group,
+ XEvent *event)
+{
+ meta_group_reload_property (group,
+ event->xproperty.atom);
+
+ return TRUE;
+
+}
diff --git a/src/core/group.h b/src/core/group.h
new file mode 100644
index 00000000..f71c2c47
--- /dev/null
+++ b/src/core/group.h
@@ -0,0 +1,53 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco window groups */
+
+/*
+ * Copyright (C) 2002 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_GROUP_H
+#define META_GROUP_H
+
+#include "window-private.h"
+
+/* note, can return NULL */
+MetaGroup* meta_window_get_group (MetaWindow *window);
+void meta_window_compute_group (MetaWindow* window);
+void meta_window_shutdown_group (MetaWindow *window);
+
+void meta_window_group_leader_changed (MetaWindow *window);
+
+/* note, can return NULL */
+MetaGroup* meta_display_lookup_group (MetaDisplay *display,
+ Window group_leader);
+
+GSList* meta_group_list_windows (MetaGroup *group);
+
+void meta_group_update_layers (MetaGroup *group);
+
+const char* meta_group_get_startup_id (MetaGroup *group);
+
+gboolean meta_group_property_notify (MetaGroup *group,
+ XEvent *event);
+
+#endif
+
+
+
+
diff --git a/src/core/iconcache.c b/src/core/iconcache.c
new file mode 100644
index 00000000..af9411ba
--- /dev/null
+++ b/src/core/iconcache.c
@@ -0,0 +1,849 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco window icons */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "iconcache.h"
+#include "ui.h"
+#include "errors.h"
+
+#include <X11/Xatom.h>
+
+/* The icon-reading code is also in libwnck, please sync bugfixes */
+
+static void
+get_fallback_icons (MetaScreen *screen,
+ GdkPixbuf **iconp,
+ int ideal_width,
+ int ideal_height,
+ GdkPixbuf **mini_iconp,
+ int ideal_mini_width,
+ int ideal_mini_height)
+{
+ /* we don't scale, should be fixed if we ever un-hardcode the icon
+ * size
+ */
+ *iconp = meta_ui_get_default_window_icon (screen->ui);
+ *mini_iconp = meta_ui_get_default_mini_icon (screen->ui);
+}
+
+static gboolean
+find_largest_sizes (gulong *data,
+ gulong nitems,
+ int *width,
+ int *height)
+{
+ *width = 0;
+ *height = 0;
+
+ while (nitems > 0)
+ {
+ int w, h;
+
+ if (nitems < 3)
+ return FALSE; /* no space for w, h */
+
+ w = data[0];
+ h = data[1];
+
+ if (nitems < ((gulong)(w * h) + 2))
+ return FALSE; /* not enough data */
+
+ *width = MAX (w, *width);
+ *height = MAX (h, *height);
+
+ data += (w * h) + 2;
+ nitems -= (w * h) + 2;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+find_best_size (gulong *data,
+ gulong nitems,
+ int ideal_width,
+ int ideal_height,
+ int *width,
+ int *height,
+ gulong **start)
+{
+ int best_w;
+ int best_h;
+ gulong *best_start;
+ int max_width, max_height;
+
+ *width = 0;
+ *height = 0;
+ *start = NULL;
+
+ if (!find_largest_sizes (data, nitems, &max_width, &max_height))
+ return FALSE;
+
+ if (ideal_width < 0)
+ ideal_width = max_width;
+ if (ideal_height < 0)
+ ideal_height = max_height;
+
+ best_w = 0;
+ best_h = 0;
+ best_start = NULL;
+
+ while (nitems > 0)
+ {
+ int w, h;
+ gboolean replace;
+
+ replace = FALSE;
+
+ if (nitems < 3)
+ return FALSE; /* no space for w, h */
+
+ w = data[0];
+ h = data[1];
+
+ if (nitems < ((gulong)(w * h) + 2))
+ break; /* not enough data */
+
+ if (best_start == NULL)
+ {
+ replace = TRUE;
+ }
+ else
+ {
+ /* work with averages */
+ const int ideal_size = (ideal_width + ideal_height) / 2;
+ int best_size = (best_w + best_h) / 2;
+ int this_size = (w + h) / 2;
+
+ /* larger than desired is always better than smaller */
+ if (best_size < ideal_size &&
+ this_size >= ideal_size)
+ replace = TRUE;
+ /* if we have too small, pick anything bigger */
+ else if (best_size < ideal_size &&
+ this_size > best_size)
+ replace = TRUE;
+ /* if we have too large, pick anything smaller
+ * but still >= the ideal
+ */
+ else if (best_size > ideal_size &&
+ this_size >= ideal_size &&
+ this_size < best_size)
+ replace = TRUE;
+ }
+
+ if (replace)
+ {
+ best_start = data + 2;
+ best_w = w;
+ best_h = h;
+ }
+
+ data += (w * h) + 2;
+ nitems -= (w * h) + 2;
+ }
+
+ if (best_start)
+ {
+ *start = best_start;
+ *width = best_w;
+ *height = best_h;
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static void
+argbdata_to_pixdata (gulong *argb_data, int len, guchar **pixdata)
+{
+ guchar *p;
+ int i;
+
+ *pixdata = g_new (guchar, len * 4);
+ p = *pixdata;
+
+ /* One could speed this up a lot. */
+ i = 0;
+ while (i < len)
+ {
+ guint argb;
+ guint rgba;
+
+ argb = argb_data[i];
+ rgba = (argb << 8) | (argb >> 24);
+
+ *p = rgba >> 24;
+ ++p;
+ *p = (rgba >> 16) & 0xff;
+ ++p;
+ *p = (rgba >> 8) & 0xff;
+ ++p;
+ *p = rgba & 0xff;
+ ++p;
+
+ ++i;
+ }
+}
+
+static gboolean
+read_rgb_icon (MetaDisplay *display,
+ Window xwindow,
+ int ideal_width,
+ int ideal_height,
+ int ideal_mini_width,
+ int ideal_mini_height,
+ int *width,
+ int *height,
+ guchar **pixdata,
+ int *mini_width,
+ int *mini_height,
+ guchar **mini_pixdata)
+{
+ Atom type;
+ int format;
+ gulong nitems;
+ gulong bytes_after;
+ int result, err;
+ guchar *data;
+ gulong *best;
+ int w, h;
+ gulong *best_mini;
+ int mini_w, mini_h;
+ gulong *data_as_long;
+
+ meta_error_trap_push_with_return (display);
+ type = None;
+ data = NULL;
+ result = XGetWindowProperty (display->xdisplay,
+ xwindow,
+ display->atom__NET_WM_ICON,
+ 0, G_MAXLONG,
+ False, XA_CARDINAL, &type, &format, &nitems,
+ &bytes_after, &data);
+ err = meta_error_trap_pop_with_return (display, TRUE);
+
+ if (err != Success ||
+ result != Success)
+ return FALSE;
+
+ if (type != XA_CARDINAL)
+ {
+ XFree (data);
+ return FALSE;
+ }
+
+ data_as_long = (gulong *)data;
+
+ if (!find_best_size (data_as_long, nitems,
+ ideal_width, ideal_height,
+ &w, &h, &best))
+ {
+ XFree (data);
+ return FALSE;
+ }
+
+ if (!find_best_size (data_as_long, nitems,
+ ideal_mini_width, ideal_mini_height,
+ &mini_w, &mini_h, &best_mini))
+ {
+ XFree (data);
+ return FALSE;
+ }
+
+ *width = w;
+ *height = h;
+
+ *mini_width = mini_w;
+ *mini_height = mini_h;
+
+ argbdata_to_pixdata (best, w * h, pixdata);
+ argbdata_to_pixdata (best_mini, mini_w * mini_h, mini_pixdata);
+
+ XFree (data);
+
+ return TRUE;
+}
+
+static void
+free_pixels (guchar *pixels, gpointer data)
+{
+ g_free (pixels);
+}
+
+static void
+get_pixmap_geometry (MetaDisplay *display,
+ Pixmap pixmap,
+ int *w,
+ int *h,
+ int *d)
+{
+ Window root_ignored;
+ int x_ignored, y_ignored;
+ guint width, height;
+ guint border_width_ignored;
+ guint depth;
+
+ if (w)
+ *w = 1;
+ if (h)
+ *h = 1;
+ if (d)
+ *d = 1;
+
+ XGetGeometry (display->xdisplay,
+ pixmap, &root_ignored, &x_ignored, &y_ignored,
+ &width, &height, &border_width_ignored, &depth);
+
+ if (w)
+ *w = width;
+ if (h)
+ *h = height;
+ if (d)
+ *d = depth;
+}
+
+static GdkPixbuf*
+apply_mask (GdkPixbuf *pixbuf,
+ GdkPixbuf *mask)
+{
+ int w, h;
+ int i, j;
+ GdkPixbuf *with_alpha;
+ guchar *src;
+ guchar *dest;
+ int src_stride;
+ int dest_stride;
+
+ w = MIN (gdk_pixbuf_get_width (mask), gdk_pixbuf_get_width (pixbuf));
+ h = MIN (gdk_pixbuf_get_height (mask), gdk_pixbuf_get_height (pixbuf));
+
+ with_alpha = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
+
+ dest = gdk_pixbuf_get_pixels (with_alpha);
+ src = gdk_pixbuf_get_pixels (mask);
+
+ dest_stride = gdk_pixbuf_get_rowstride (with_alpha);
+ src_stride = gdk_pixbuf_get_rowstride (mask);
+
+ i = 0;
+ while (i < h)
+ {
+ j = 0;
+ while (j < w)
+ {
+ guchar *s = src + i * src_stride + j * 3;
+ guchar *d = dest + i * dest_stride + j * 4;
+
+ /* s[0] == s[1] == s[2], they are 255 if the bit was set, 0
+ * otherwise
+ */
+ if (s[0] == 0)
+ d[3] = 0; /* transparent */
+ else
+ d[3] = 255; /* opaque */
+
+ ++j;
+ }
+
+ ++i;
+ }
+
+ return with_alpha;
+}
+
+static gboolean
+try_pixmap_and_mask (MetaDisplay *display,
+ Pixmap src_pixmap,
+ Pixmap src_mask,
+ GdkPixbuf **iconp,
+ int ideal_width,
+ int ideal_height,
+ GdkPixbuf **mini_iconp,
+ int ideal_mini_width,
+ int ideal_mini_height)
+{
+ GdkPixbuf *unscaled = NULL;
+ GdkPixbuf *mask = NULL;
+ int w, h;
+
+ if (src_pixmap == None)
+ return FALSE;
+
+ meta_error_trap_push (display);
+
+ get_pixmap_geometry (display, src_pixmap, &w, &h, NULL);
+
+ unscaled = meta_gdk_pixbuf_get_from_pixmap (NULL,
+ src_pixmap,
+ 0, 0, 0, 0,
+ w, h);
+
+ if (unscaled && src_mask != None)
+ {
+ get_pixmap_geometry (display, src_mask, &w, &h, NULL);
+ mask = meta_gdk_pixbuf_get_from_pixmap (NULL,
+ src_mask,
+ 0, 0, 0, 0,
+ w, h);
+ }
+
+ meta_error_trap_pop (display, FALSE);
+
+ if (mask)
+ {
+ GdkPixbuf *masked;
+
+ masked = apply_mask (unscaled, mask);
+ g_object_unref (G_OBJECT (unscaled));
+ unscaled = masked;
+
+ g_object_unref (G_OBJECT (mask));
+ mask = NULL;
+ }
+
+ if (unscaled)
+ {
+ *iconp =
+ gdk_pixbuf_scale_simple (unscaled,
+ ideal_width > 0 ? ideal_width :
+ gdk_pixbuf_get_width (unscaled),
+ ideal_height > 0 ? ideal_height :
+ gdk_pixbuf_get_height (unscaled),
+ GDK_INTERP_BILINEAR);
+ *mini_iconp =
+ gdk_pixbuf_scale_simple (unscaled,
+ ideal_mini_width > 0 ? ideal_mini_width :
+ gdk_pixbuf_get_width (unscaled),
+ ideal_mini_height > 0 ? ideal_mini_height :
+ gdk_pixbuf_get_height (unscaled),
+ GDK_INTERP_BILINEAR);
+
+ g_object_unref (G_OBJECT (unscaled));
+
+ if (*iconp && *mini_iconp)
+ return TRUE;
+ else
+ {
+ if (*iconp)
+ g_object_unref (G_OBJECT (*iconp));
+ if (*mini_iconp)
+ g_object_unref (G_OBJECT (*mini_iconp));
+ return FALSE;
+ }
+ }
+ else
+ return FALSE;
+}
+
+static void
+get_kwm_win_icon (MetaDisplay *display,
+ Window xwindow,
+ Pixmap *pixmap,
+ Pixmap *mask)
+{
+ Atom type;
+ int format;
+ gulong nitems;
+ gulong bytes_after;
+ guchar *data;
+ Pixmap *icons;
+ int err, result;
+
+ *pixmap = None;
+ *mask = None;
+
+ meta_error_trap_push_with_return (display);
+ icons = NULL;
+ result = XGetWindowProperty (display->xdisplay, xwindow,
+ display->atom__KWM_WIN_ICON,
+ 0, G_MAXLONG,
+ False,
+ display->atom__KWM_WIN_ICON,
+ &type, &format, &nitems,
+ &bytes_after, &data);
+ icons = (Pixmap *)data;
+
+ err = meta_error_trap_pop_with_return (display, TRUE);
+ if (err != Success ||
+ result != Success)
+ return;
+
+ if (type != display->atom__KWM_WIN_ICON)
+ {
+ XFree (icons);
+ return;
+ }
+
+ *pixmap = icons[0];
+ *mask = icons[1];
+
+ XFree (icons);
+
+ return;
+}
+
+void
+meta_icon_cache_init (MetaIconCache *icon_cache)
+{
+ g_return_if_fail (icon_cache != NULL);
+
+ icon_cache->origin = USING_NO_ICON;
+ icon_cache->prev_pixmap = None;
+ icon_cache->prev_mask = None;
+#if 0
+ icon_cache->icon = NULL;
+ icon_cache->mini_icon = NULL;
+ icon_cache->ideal_width = -1; /* won't be a legit width */
+ icon_cache->ideal_height = -1;
+ icon_cache->ideal_mini_width = -1;
+ icon_cache->ideal_mini_height = -1;
+#endif
+ icon_cache->want_fallback = TRUE;
+ icon_cache->wm_hints_dirty = TRUE;
+ icon_cache->kwm_win_icon_dirty = TRUE;
+ icon_cache->net_wm_icon_dirty = TRUE;
+}
+
+static void
+clear_icon_cache (MetaIconCache *icon_cache,
+ gboolean dirty_all)
+{
+#if 0
+ if (icon_cache->icon)
+ g_object_unref (G_OBJECT (icon_cache->icon));
+ icon_cache->icon = NULL;
+
+ if (icon_cache->mini_icon)
+ g_object_unref (G_OBJECT (icon_cache->mini_icon));
+ icon_cache->mini_icon = NULL;
+#endif
+
+ icon_cache->origin = USING_NO_ICON;
+
+ if (dirty_all)
+ {
+ icon_cache->wm_hints_dirty = TRUE;
+ icon_cache->kwm_win_icon_dirty = TRUE;
+ icon_cache->net_wm_icon_dirty = TRUE;
+ }
+}
+
+void
+meta_icon_cache_free (MetaIconCache *icon_cache)
+{
+ clear_icon_cache (icon_cache, FALSE);
+}
+
+void
+meta_icon_cache_property_changed (MetaIconCache *icon_cache,
+ MetaDisplay *display,
+ Atom atom)
+{
+ if (atom == display->atom__NET_WM_ICON)
+ icon_cache->net_wm_icon_dirty = TRUE;
+ else if (atom == display->atom__KWM_WIN_ICON)
+ icon_cache->kwm_win_icon_dirty = TRUE;
+ else if (atom == XA_WM_HINTS)
+ icon_cache->wm_hints_dirty = TRUE;
+}
+
+gboolean
+meta_icon_cache_get_icon_invalidated (MetaIconCache *icon_cache)
+{
+ if (icon_cache->origin <= USING_KWM_WIN_ICON &&
+ icon_cache->kwm_win_icon_dirty)
+ return TRUE;
+ else if (icon_cache->origin <= USING_WM_HINTS &&
+ icon_cache->wm_hints_dirty)
+ return TRUE;
+ else if (icon_cache->origin <= USING_NET_WM_ICON &&
+ icon_cache->net_wm_icon_dirty)
+ return TRUE;
+ else if (icon_cache->origin < USING_FALLBACK_ICON &&
+ icon_cache->want_fallback)
+ return TRUE;
+ else if (icon_cache->origin == USING_NO_ICON)
+ return TRUE;
+ else if (icon_cache->origin == USING_FALLBACK_ICON &&
+ !icon_cache->want_fallback)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void
+replace_cache (MetaIconCache *icon_cache,
+ IconOrigin origin,
+ GdkPixbuf *new_icon,
+ GdkPixbuf *new_mini_icon)
+{
+ clear_icon_cache (icon_cache, FALSE);
+
+ icon_cache->origin = origin;
+
+#if 0
+ if (new_icon)
+ g_object_ref (G_OBJECT (new_icon));
+
+ icon_cache->icon = new_icon;
+
+ if (new_mini_icon)
+ g_object_ref (G_OBJECT (new_mini_icon));
+
+ icon_cache->mini_icon = new_mini_icon;
+#endif
+}
+
+static GdkPixbuf*
+scaled_from_pixdata (guchar *pixdata,
+ int w,
+ int h,
+ int new_w,
+ int new_h)
+{
+ GdkPixbuf *src;
+ GdkPixbuf *dest;
+
+ src = gdk_pixbuf_new_from_data (pixdata,
+ GDK_COLORSPACE_RGB,
+ TRUE,
+ 8,
+ w, h, w * 4,
+ free_pixels,
+ NULL);
+
+ if (src == NULL)
+ return NULL;
+
+ if (w != h)
+ {
+ GdkPixbuf *tmp;
+ int size;
+
+ size = MAX (w, h);
+
+ tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, size, size);
+
+ if (tmp)
+ {
+ gdk_pixbuf_fill (tmp, 0);
+ gdk_pixbuf_copy_area (src, 0, 0, w, h,
+ tmp,
+ (size - w) / 2, (size - h) / 2);
+
+ g_object_unref (src);
+ src = tmp;
+ }
+ }
+
+ if (w != new_w || h != new_h)
+ {
+ dest = gdk_pixbuf_scale_simple (src, new_w, new_h, GDK_INTERP_BILINEAR);
+
+ g_object_unref (G_OBJECT (src));
+ }
+ else
+ {
+ dest = src;
+ }
+
+ return dest;
+}
+
+gboolean
+meta_read_icons (MetaScreen *screen,
+ Window xwindow,
+ MetaIconCache *icon_cache,
+ Pixmap wm_hints_pixmap,
+ Pixmap wm_hints_mask,
+ GdkPixbuf **iconp,
+ int ideal_width,
+ int ideal_height,
+ GdkPixbuf **mini_iconp,
+ int ideal_mini_width,
+ int ideal_mini_height)
+{
+ guchar *pixdata;
+ int w, h;
+ guchar *mini_pixdata;
+ int mini_w, mini_h;
+ Pixmap pixmap;
+ Pixmap mask;
+
+ /* Return value is whether the icon changed */
+
+ g_return_val_if_fail (icon_cache != NULL, FALSE);
+
+ *iconp = NULL;
+ *mini_iconp = NULL;
+
+#if 0
+ if (ideal_width != icon_cache->ideal_width ||
+ ideal_height != icon_cache->ideal_height ||
+ ideal_mini_width != icon_cache->ideal_mini_width ||
+ ideal_mini_height != icon_cache->ideal_mini_height)
+ clear_icon_cache (icon_cache, TRUE);
+
+ icon_cache->ideal_width = ideal_width;
+ icon_cache->ideal_height = ideal_height;
+ icon_cache->ideal_mini_width = ideal_mini_width;
+ icon_cache->ideal_mini_height = ideal_mini_height;
+#endif
+
+ if (!meta_icon_cache_get_icon_invalidated (icon_cache))
+ return FALSE; /* we have no new info to use */
+
+ pixdata = NULL;
+
+ /* Our algorithm here assumes that we can't have for example origin
+ * < USING_NET_WM_ICON and icon_cache->net_wm_icon_dirty == FALSE
+ * unless we have tried to read NET_WM_ICON.
+ *
+ * Put another way, if an icon origin is not dirty, then we have
+ * tried to read it at the current size. If it is dirty, then
+ * we haven't done that since the last change.
+ */
+
+ if (icon_cache->origin <= USING_NET_WM_ICON &&
+ icon_cache->net_wm_icon_dirty)
+
+ {
+ icon_cache->net_wm_icon_dirty = FALSE;
+
+ if (read_rgb_icon (screen->display, xwindow,
+ ideal_width, ideal_height,
+ ideal_mini_width, ideal_mini_height,
+ &w, &h, &pixdata,
+ &mini_w, &mini_h, &mini_pixdata))
+ {
+ *iconp = scaled_from_pixdata (pixdata, w, h,
+ ideal_width, ideal_height);
+
+ *mini_iconp = scaled_from_pixdata (mini_pixdata, mini_w, mini_h,
+ ideal_mini_width, ideal_mini_height);
+
+ if (*iconp && *mini_iconp)
+ {
+ replace_cache (icon_cache, USING_NET_WM_ICON,
+ *iconp, *mini_iconp);
+
+ return TRUE;
+ }
+ else
+ {
+ if (*iconp)
+ g_object_unref (G_OBJECT (*iconp));
+ if (*mini_iconp)
+ g_object_unref (G_OBJECT (*mini_iconp));
+ }
+ }
+ }
+
+ if (icon_cache->origin <= USING_WM_HINTS &&
+ icon_cache->wm_hints_dirty)
+ {
+ icon_cache->wm_hints_dirty = FALSE;
+
+ pixmap = wm_hints_pixmap;
+ mask = wm_hints_mask;
+
+ /* We won't update if pixmap is unchanged;
+ * avoids a get_from_drawable() on every geometry
+ * hints change
+ */
+ if ((pixmap != icon_cache->prev_pixmap ||
+ mask != icon_cache->prev_mask) &&
+ pixmap != None)
+ {
+ if (try_pixmap_and_mask (screen->display,
+ pixmap, mask,
+ iconp, ideal_width, ideal_height,
+ mini_iconp, ideal_mini_width, ideal_mini_height))
+ {
+ icon_cache->prev_pixmap = pixmap;
+ icon_cache->prev_mask = mask;
+
+ replace_cache (icon_cache, USING_WM_HINTS,
+ *iconp, *mini_iconp);
+
+ return TRUE;
+ }
+ }
+ }
+
+ if (icon_cache->origin <= USING_KWM_WIN_ICON &&
+ icon_cache->kwm_win_icon_dirty)
+ {
+ icon_cache->kwm_win_icon_dirty = FALSE;
+
+ get_kwm_win_icon (screen->display, xwindow, &pixmap, &mask);
+
+ if ((pixmap != icon_cache->prev_pixmap ||
+ mask != icon_cache->prev_mask) &&
+ pixmap != None)
+ {
+ if (try_pixmap_and_mask (screen->display, pixmap, mask,
+ iconp, ideal_width, ideal_height,
+ mini_iconp, ideal_mini_width, ideal_mini_height))
+ {
+ icon_cache->prev_pixmap = pixmap;
+ icon_cache->prev_mask = mask;
+
+ replace_cache (icon_cache, USING_KWM_WIN_ICON,
+ *iconp, *mini_iconp);
+
+ return TRUE;
+ }
+ }
+ }
+
+ if (icon_cache->want_fallback &&
+ icon_cache->origin < USING_FALLBACK_ICON)
+ {
+ get_fallback_icons (screen,
+ iconp,
+ ideal_width,
+ ideal_height,
+ mini_iconp,
+ ideal_mini_width,
+ ideal_mini_height);
+
+ replace_cache (icon_cache, USING_FALLBACK_ICON,
+ *iconp, *mini_iconp);
+
+ return TRUE;
+ }
+
+ if (!icon_cache->want_fallback &&
+ icon_cache->origin == USING_FALLBACK_ICON)
+ {
+ /* Get rid of current icon */
+ clear_icon_cache (icon_cache, FALSE);
+
+ return TRUE;
+ }
+
+ /* found nothing new */
+ return FALSE;
+}
diff --git a/src/core/iconcache.h b/src/core/iconcache.h
new file mode 100644
index 00000000..85ea9c66
--- /dev/null
+++ b/src/core/iconcache.h
@@ -0,0 +1,79 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco window icons */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_ICON_CACHE_H
+#define META_ICON_CACHE_H
+
+#include "screen-private.h"
+
+typedef struct _MetaIconCache MetaIconCache;
+
+typedef enum
+{
+ /* These MUST be in ascending order of preference;
+ * i.e. if we get _NET_WM_ICON and already have
+ * WM_HINTS, we prefer _NET_WM_ICON
+ */
+ USING_NO_ICON,
+ USING_FALLBACK_ICON,
+ USING_KWM_WIN_ICON,
+ USING_WM_HINTS,
+ USING_NET_WM_ICON
+} IconOrigin;
+
+struct _MetaIconCache
+{
+ int origin;
+ Pixmap prev_pixmap;
+ Pixmap prev_mask;
+ guint want_fallback : 1;
+ /* TRUE if these props have changed */
+ guint wm_hints_dirty : 1;
+ guint kwm_win_icon_dirty : 1;
+ guint net_wm_icon_dirty : 1;
+};
+
+void meta_icon_cache_init (MetaIconCache *icon_cache);
+void meta_icon_cache_free (MetaIconCache *icon_cache);
+void meta_icon_cache_property_changed (MetaIconCache *icon_cache,
+ MetaDisplay *display,
+ Atom atom);
+gboolean meta_icon_cache_get_icon_invalidated (MetaIconCache *icon_cache);
+
+gboolean meta_read_icons (MetaScreen *screen,
+ Window xwindow,
+ MetaIconCache *icon_cache,
+ Pixmap wm_hints_pixmap,
+ Pixmap wm_hints_mask,
+ GdkPixbuf **iconp,
+ int ideal_width,
+ int ideal_height,
+ GdkPixbuf **mini_iconp,
+ int ideal_mini_width,
+ int ideal_mini_height);
+
+#endif
+
+
+
+
diff --git a/src/core/keybindings.c b/src/core/keybindings.c
new file mode 100644
index 00000000..b9371c85
--- /dev/null
+++ b/src/core/keybindings.c
@@ -0,0 +1,3352 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco Keybindings */
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2002 Red Hat Inc.
+ * Copyright (C) 2003 Rob Adams
+ * Copyright (C) 2004-2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+#define _SVID_SOURCE /* for putenv() */
+
+#include <config.h>
+#include "keybindings.h"
+#include "workspace.h"
+#include "errors.h"
+#include "edge-resistance.h"
+#include "ui.h"
+#include "frame-private.h"
+#include "place.h"
+#include "prefs.h"
+#include "effects.h"
+#include "util.h"
+
+#include <X11/keysym.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef HAVE_XKB
+#include <X11/XKBlib.h>
+#endif
+
+static gboolean all_bindings_disabled = FALSE;
+
+typedef void (* MetaKeyHandlerFunc) (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding);
+
+/* Prototypes for handlers */
+#define keybind(name, handler, param, flags, stroke, description) \
+static void \
+handler (MetaDisplay *display,\
+ MetaScreen *screen,\
+ MetaWindow *window,\
+ XEvent *event,\
+ MetaKeyBinding *binding);
+#include "all-keybindings.h"
+#undef keybind
+
+/* These can't be bound to anything, but they are used to handle
+ * various other events. TODO: Possibly we should include them as event
+ * handler functions and have some kind of flag to say they're unbindable.
+ */
+
+static void handle_workspace_switch (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding);
+
+static gboolean process_mouse_move_resize_grab (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ KeySym keysym);
+
+static gboolean process_keyboard_move_grab (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ KeySym keysym);
+
+static gboolean process_keyboard_resize_grab (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ KeySym keysym);
+
+static gboolean process_tab_grab (MetaDisplay *display,
+ MetaScreen *screen,
+ XEvent *event,
+ KeySym keysym);
+
+static gboolean process_workspace_switch_grab (MetaDisplay *display,
+ MetaScreen *screen,
+ XEvent *event,
+ KeySym keysym);
+
+static void regrab_key_bindings (MetaDisplay *display);
+
+typedef struct
+{
+ const char *name;
+ MetaKeyHandlerFunc func;
+ gint data, flags;
+} MetaKeyHandler;
+
+struct _MetaKeyBinding
+{
+ const char *name;
+ KeySym keysym;
+ KeyCode keycode;
+ unsigned int mask;
+ MetaVirtualModifier modifiers;
+ const MetaKeyHandler *handler;
+};
+
+#define keybind(name, handler, param, flags, stroke, description) \
+ { #name, handler, param, flags },
+static const MetaKeyHandler key_handlers[] = {
+#include "all-keybindings.h"
+ { NULL, NULL, 0, 0 }
+};
+#undef keybind
+
+static void
+reload_keymap (MetaDisplay *display)
+{
+ if (display->keymap)
+ meta_XFree (display->keymap);
+
+ display->keymap = XGetKeyboardMapping (display->xdisplay,
+ display->min_keycode,
+ display->max_keycode -
+ display->min_keycode + 1,
+ &display->keysyms_per_keycode);
+}
+
+static void
+reload_modmap (MetaDisplay *display)
+{
+ XModifierKeymap *modmap;
+ int map_size;
+ int i;
+
+ if (display->modmap)
+ XFreeModifiermap (display->modmap);
+
+ modmap = XGetModifierMapping (display->xdisplay);
+ display->modmap = modmap;
+
+ display->ignored_modifier_mask = 0;
+
+ /* Multiple bits may get set in each of these */
+ display->num_lock_mask = 0;
+ display->scroll_lock_mask = 0;
+ display->meta_mask = 0;
+ display->hyper_mask = 0;
+ display->super_mask = 0;
+
+ /* there are 8 modifiers, and the first 3 are shift, shift lock,
+ * and control
+ */
+ map_size = 8 * modmap->max_keypermod;
+ i = 3 * modmap->max_keypermod;
+ while (i < map_size)
+ {
+ /* get the key code at this point in the map,
+ * see if its keysym is one we're interested in
+ */
+ int keycode = modmap->modifiermap[i];
+
+ if (keycode >= display->min_keycode &&
+ keycode <= display->max_keycode)
+ {
+ int j = 0;
+ KeySym *syms = display->keymap +
+ (keycode - display->min_keycode) * display->keysyms_per_keycode;
+
+ while (j < display->keysyms_per_keycode)
+ {
+ if (syms[j] != 0)
+ {
+ const char *str;
+
+ str = XKeysymToString (syms[j]);
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Keysym %s bound to modifier 0x%x\n",
+ str ? str : "none",
+ (1 << ( i / modmap->max_keypermod)));
+ }
+
+ if (syms[j] == XK_Num_Lock)
+ {
+ /* Mod1Mask is 1 << 3 for example, i.e. the
+ * fourth modifier, i / keyspermod is the modifier
+ * index
+ */
+
+ display->num_lock_mask |= (1 << ( i / modmap->max_keypermod));
+ }
+ else if (syms[j] == XK_Scroll_Lock)
+ {
+ display->scroll_lock_mask |= (1 << ( i / modmap->max_keypermod));
+ }
+ else if (syms[j] == XK_Super_L ||
+ syms[j] == XK_Super_R)
+ {
+ display->super_mask |= (1 << ( i / modmap->max_keypermod));
+ }
+ else if (syms[j] == XK_Hyper_L ||
+ syms[j] == XK_Hyper_R)
+ {
+ display->hyper_mask |= (1 << ( i / modmap->max_keypermod));
+ }
+ else if (syms[j] == XK_Meta_L ||
+ syms[j] == XK_Meta_R)
+ {
+ display->meta_mask |= (1 << ( i / modmap->max_keypermod));
+ }
+
+ ++j;
+ }
+ }
+
+ ++i;
+ }
+
+ display->ignored_modifier_mask = (display->num_lock_mask |
+ display->scroll_lock_mask |
+ LockMask);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Ignoring modmask 0x%x num lock 0x%x scroll lock 0x%x hyper 0x%x super 0x%x meta 0x%x\n",
+ display->ignored_modifier_mask,
+ display->num_lock_mask,
+ display->scroll_lock_mask,
+ display->hyper_mask,
+ display->super_mask,
+ display->meta_mask);
+}
+
+static void
+reload_keycodes (MetaDisplay *display)
+{
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Reloading keycodes for binding tables\n");
+
+ if (display->key_bindings)
+ {
+ int i;
+
+ i = 0;
+ while (i < display->n_key_bindings)
+ {
+ if (display->key_bindings[i].keycode == 0)
+ display->key_bindings[i].keycode = XKeysymToKeycode (
+ display->xdisplay, display->key_bindings[i].keysym);
+
+ ++i;
+ }
+ }
+}
+
+static void
+reload_modifiers (MetaDisplay *display)
+{
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Reloading keycodes for binding tables\n");
+
+ if (display->key_bindings)
+ {
+ int i;
+
+ i = 0;
+ while (i < display->n_key_bindings)
+ {
+ meta_display_devirtualize_modifiers (display,
+ display->key_bindings[i].modifiers,
+ &display->key_bindings[i].mask);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ " Devirtualized mods 0x%x -> 0x%x (%s)\n",
+ display->key_bindings[i].modifiers,
+ display->key_bindings[i].mask,
+ display->key_bindings[i].name);
+
+ ++i;
+ }
+ }
+}
+
+
+static int
+count_bindings (const MetaKeyPref *prefs,
+ int n_prefs)
+{
+ int i;
+ int count;
+
+ count = 0;
+ i = 0;
+ while (i < n_prefs)
+ {
+ GSList *tmp = prefs[i].bindings;
+
+ while (tmp)
+ {
+ MetaKeyCombo *combo = tmp->data;
+
+ if (combo && (combo->keysym != None || combo->keycode != 0))
+ {
+ count += 1;
+
+ if (prefs[i].add_shift &&
+ (combo->modifiers & META_VIRTUAL_SHIFT_MASK) == 0)
+ count += 1;
+ }
+
+ tmp = tmp->next;
+ }
+
+ ++i;
+ }
+
+ return count;
+}
+
+/* FIXME: replace this with a temporary hash */
+static const MetaKeyHandler*
+find_handler (const MetaKeyHandler *handlers,
+ const char *name)
+{
+ const MetaKeyHandler *iter;
+
+ iter = handlers;
+ while (iter->name)
+ {
+ if (strcmp (iter->name, name) == 0)
+ return iter;
+
+ ++iter;
+ }
+
+ return NULL;
+}
+
+static void
+rebuild_binding_table (MetaDisplay *display,
+ MetaKeyBinding **bindings_p,
+ int *n_bindings_p,
+ const MetaKeyPref *prefs,
+ int n_prefs)
+{
+ int n_bindings;
+ int src, dest;
+
+ n_bindings = count_bindings (prefs, n_prefs);
+ g_free (*bindings_p);
+ *bindings_p = g_new0 (MetaKeyBinding, n_bindings);
+
+ src = 0;
+ dest = 0;
+ while (src < n_prefs)
+ {
+ GSList *tmp = prefs[src].bindings;
+
+ while (tmp)
+ {
+ MetaKeyCombo *combo = tmp->data;
+
+ if (combo && (combo->keysym != None || combo->keycode != 0))
+ {
+ const MetaKeyHandler *handler = find_handler (key_handlers, prefs[src].name);
+
+ (*bindings_p)[dest].name = prefs[src].name;
+ (*bindings_p)[dest].handler = handler;
+ (*bindings_p)[dest].keysym = combo->keysym;
+ (*bindings_p)[dest].keycode = combo->keycode;
+ (*bindings_p)[dest].modifiers = combo->modifiers;
+ (*bindings_p)[dest].mask = 0;
+
+ ++dest;
+
+ if (prefs[src].add_shift &&
+ (combo->modifiers & META_VIRTUAL_SHIFT_MASK) == 0)
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Binding %s also needs Shift grabbed\n",
+ prefs[src].name);
+
+ (*bindings_p)[dest].name = prefs[src].name;
+ (*bindings_p)[dest].handler = handler;
+ (*bindings_p)[dest].keysym = combo->keysym;
+ (*bindings_p)[dest].keycode = combo->keycode;
+ (*bindings_p)[dest].modifiers = combo->modifiers |
+ META_VIRTUAL_SHIFT_MASK;
+ (*bindings_p)[dest].mask = 0;
+
+ ++dest;
+ }
+ }
+
+ tmp = tmp->next;
+ }
+
+ ++src;
+ }
+
+ g_assert (dest == n_bindings);
+
+ *n_bindings_p = dest;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ " %d bindings in table\n",
+ *n_bindings_p);
+}
+
+static void
+rebuild_key_binding_table (MetaDisplay *display)
+{
+ const MetaKeyPref *prefs;
+ int n_prefs;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Rebuilding key binding table from preferences\n");
+
+ meta_prefs_get_key_bindings (&prefs, &n_prefs);
+ rebuild_binding_table (display,
+ &display->key_bindings,
+ &display->n_key_bindings,
+ prefs, n_prefs);
+}
+
+static void
+regrab_key_bindings (MetaDisplay *display)
+{
+ GSList *tmp;
+ GSList *windows;
+
+ meta_error_trap_push (display); /* for efficiency push outer trap */
+
+ tmp = display->screens;
+ while (tmp != NULL)
+ {
+ MetaScreen *screen = tmp->data;
+
+ meta_screen_ungrab_keys (screen);
+ meta_screen_grab_keys (screen);
+
+ tmp = tmp->next;
+ }
+
+ windows = meta_display_list_windows (display);
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ meta_window_ungrab_keys (w);
+ meta_window_grab_keys (w);
+
+ tmp = tmp->next;
+ }
+ meta_error_trap_pop (display, FALSE);
+
+ g_slist_free (windows);
+}
+
+static MetaKeyBindingAction
+display_get_keybinding_action (MetaDisplay *display,
+ unsigned int keysym,
+ unsigned int keycode,
+ unsigned long mask)
+{
+ int i;
+
+ i = display->n_key_bindings - 1;
+ while (i >= 0)
+ {
+ if (display->key_bindings[i].keysym == keysym &&
+ display->key_bindings[i].keycode == keycode &&
+ display->key_bindings[i].mask == mask)
+ {
+ return meta_prefs_get_keybinding_action (display->key_bindings[i].name);
+ }
+
+ --i;
+ }
+
+ return META_KEYBINDING_ACTION_NONE;
+}
+
+void
+meta_display_process_mapping_event (MetaDisplay *display,
+ XEvent *event)
+{
+ if (event->xmapping.request == MappingModifier)
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Received MappingModifier event, will reload modmap and redo keybindings\n");
+
+ reload_modmap (display);
+
+ reload_modifiers (display);
+
+ regrab_key_bindings (display);
+ }
+ else if (event->xmapping.request == MappingKeyboard)
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Received MappingKeyboard event, will reload keycodes and redo keybindings\n");
+
+ reload_keymap (display);
+ reload_modmap (display);
+
+ reload_keycodes (display);
+
+ regrab_key_bindings (display);
+ }
+}
+
+static void
+bindings_changed_callback (MetaPreference pref,
+ void *data)
+{
+ MetaDisplay *display;
+
+ display = data;
+
+ switch (pref)
+ {
+ case META_PREF_KEYBINDINGS:
+ rebuild_key_binding_table (display);
+ reload_keycodes (display);
+ reload_modifiers (display);
+ regrab_key_bindings (display);
+ break;
+ default:
+ break;
+ }
+}
+
+
+void
+meta_display_init_keys (MetaDisplay *display)
+{
+ /* Keybindings */
+ display->keymap = NULL;
+ display->keysyms_per_keycode = 0;
+ display->modmap = NULL;
+ display->min_keycode = 0;
+ display->max_keycode = 0;
+ display->ignored_modifier_mask = 0;
+ display->num_lock_mask = 0;
+ display->scroll_lock_mask = 0;
+ display->hyper_mask = 0;
+ display->super_mask = 0;
+ display->meta_mask = 0;
+ display->key_bindings = NULL;
+ display->n_key_bindings = 0;
+
+ XDisplayKeycodes (display->xdisplay,
+ &display->min_keycode,
+ &display->max_keycode);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Display has keycode range %d to %d\n",
+ display->min_keycode,
+ display->max_keycode);
+
+ reload_keymap (display);
+ reload_modmap (display);
+
+ rebuild_key_binding_table (display);
+
+ reload_keycodes (display);
+ reload_modifiers (display);
+
+ /* Keys are actually grabbed in meta_screen_grab_keys() */
+
+ meta_prefs_add_listener (bindings_changed_callback, display);
+}
+
+void
+meta_display_shutdown_keys (MetaDisplay *display)
+{
+ /* Note that display->xdisplay is invalid in this function */
+
+ meta_prefs_remove_listener (bindings_changed_callback, display);
+
+ if (display->keymap)
+ meta_XFree (display->keymap);
+
+ if (display->modmap)
+ XFreeModifiermap (display->modmap);
+ g_free (display->key_bindings);
+}
+
+static const char*
+keysym_name (int keysym)
+{
+ const char *name;
+
+ name = XKeysymToString (keysym);
+ if (name == NULL)
+ name = "(unknown)";
+
+ return name;
+}
+
+/* Grab/ungrab, ignoring all annoying modifiers like NumLock etc. */
+static void
+meta_change_keygrab (MetaDisplay *display,
+ Window xwindow,
+ gboolean grab,
+ int keysym,
+ unsigned int keycode,
+ int modmask)
+{
+ unsigned int ignored_mask;
+
+ /* Grab keycode/modmask, together with
+ * all combinations of ignored modifiers.
+ * X provides no better way to do this.
+ */
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "%s keybinding %s keycode %d mask 0x%x on 0x%lx\n",
+ grab ? "Grabbing" : "Ungrabbing",
+ keysym_name (keysym), keycode,
+ modmask, xwindow);
+
+ /* efficiency, avoid so many XSync() */
+ meta_error_trap_push (display);
+
+ ignored_mask = 0;
+ while (ignored_mask <= display->ignored_modifier_mask)
+ {
+ if (ignored_mask & ~(display->ignored_modifier_mask))
+ {
+ /* Not a combination of ignored modifiers
+ * (it contains some non-ignored modifiers)
+ */
+ ++ignored_mask;
+ continue;
+ }
+
+ if (meta_is_debugging ())
+ meta_error_trap_push_with_return (display);
+ if (grab)
+ XGrabKey (display->xdisplay, keycode,
+ modmask | ignored_mask,
+ xwindow,
+ True,
+ GrabModeAsync, GrabModeSync);
+ else
+ XUngrabKey (display->xdisplay, keycode,
+ modmask | ignored_mask,
+ xwindow);
+
+ if (meta_is_debugging ())
+ {
+ int result;
+
+ result = meta_error_trap_pop_with_return (display, FALSE);
+
+ if (grab && result != Success)
+ {
+ if (result == BadAccess)
+ meta_warning (_("Some other program is already using the key %s with modifiers %x as a binding\n"), keysym_name (keysym), modmask | ignored_mask);
+ else
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Failed to grab key %s with modifiers %x\n",
+ keysym_name (keysym), modmask | ignored_mask);
+ }
+ }
+
+ ++ignored_mask;
+ }
+
+ meta_error_trap_pop (display, FALSE);
+}
+
+static void
+meta_grab_key (MetaDisplay *display,
+ Window xwindow,
+ int keysym,
+ unsigned int keycode,
+ int modmask)
+{
+ meta_change_keygrab (display, xwindow, TRUE, keysym, keycode, modmask);
+}
+
+static void
+grab_keys (MetaKeyBinding *bindings,
+ int n_bindings,
+ MetaDisplay *display,
+ Window xwindow,
+ gboolean binding_per_window)
+{
+ int i;
+
+ g_assert (n_bindings == 0 || bindings != NULL);
+
+ meta_error_trap_push (display);
+
+ i = 0;
+ while (i < n_bindings)
+ {
+ if (!!binding_per_window ==
+ !!(bindings[i].handler->flags & BINDING_PER_WINDOW) &&
+ bindings[i].keycode != 0)
+ {
+ meta_grab_key (display, xwindow,
+ bindings[i].keysym,
+ bindings[i].keycode,
+ bindings[i].mask);
+ }
+
+ ++i;
+ }
+
+ meta_error_trap_pop (display, FALSE);
+}
+
+static void
+ungrab_all_keys (MetaDisplay *display,
+ Window xwindow)
+{
+ if (meta_is_debugging ())
+ meta_error_trap_push_with_return (display);
+ else
+ meta_error_trap_push (display);
+
+ XUngrabKey (display->xdisplay, AnyKey, AnyModifier,
+ xwindow);
+
+ if (meta_is_debugging ())
+ {
+ int result;
+
+ result = meta_error_trap_pop_with_return (display, FALSE);
+
+ if (result != Success)
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Ungrabbing all keys on 0x%lx failed\n", xwindow);
+ }
+ else
+ meta_error_trap_pop (display, FALSE);
+}
+
+void
+meta_screen_grab_keys (MetaScreen *screen)
+{
+ if (screen->all_keys_grabbed)
+ return;
+
+ if (screen->keys_grabbed)
+ return;
+
+ grab_keys (screen->display->key_bindings,
+ screen->display->n_key_bindings,
+ screen->display, screen->xroot,
+ FALSE);
+
+ screen->keys_grabbed = TRUE;
+}
+
+void
+meta_screen_ungrab_keys (MetaScreen *screen)
+{
+ if (screen->keys_grabbed)
+ {
+ ungrab_all_keys (screen->display, screen->xroot);
+ screen->keys_grabbed = FALSE;
+ }
+}
+
+void
+meta_window_grab_keys (MetaWindow *window)
+{
+ if (window->all_keys_grabbed)
+ return;
+
+ if (window->type == META_WINDOW_DOCK)
+ {
+ if (window->keys_grabbed)
+ ungrab_all_keys (window->display, window->xwindow);
+ window->keys_grabbed = FALSE;
+ return;
+ }
+
+ if (window->keys_grabbed)
+ {
+ if (window->frame && !window->grab_on_frame)
+ ungrab_all_keys (window->display, window->xwindow);
+ else if (window->frame == NULL &&
+ window->grab_on_frame)
+ ; /* continue to regrab on client window */
+ else
+ return; /* already all good */
+ }
+
+ grab_keys (window->display->key_bindings,
+ window->display->n_key_bindings,
+ window->display,
+ window->frame ? window->frame->xwindow : window->xwindow,
+ TRUE);
+
+ window->keys_grabbed = TRUE;
+ window->grab_on_frame = window->frame != NULL;
+}
+
+void
+meta_window_ungrab_keys (MetaWindow *window)
+{
+ if (window->keys_grabbed)
+ {
+ if (window->grab_on_frame &&
+ window->frame != NULL)
+ ungrab_all_keys (window->display,
+ window->frame->xwindow);
+ else if (!window->grab_on_frame)
+ ungrab_all_keys (window->display,
+ window->xwindow);
+
+ window->keys_grabbed = FALSE;
+ }
+}
+
+#ifdef WITH_VERBOSE_MODE
+static const char*
+grab_status_to_string (int status)
+{
+ switch (status)
+ {
+ case AlreadyGrabbed:
+ return "AlreadyGrabbed";
+ case GrabSuccess:
+ return "GrabSuccess";
+ case GrabNotViewable:
+ return "GrabNotViewable";
+ case GrabFrozen:
+ return "GrabFrozen";
+ case GrabInvalidTime:
+ return "GrabInvalidTime";
+ default:
+ return "(unknown)";
+ }
+}
+#endif /* WITH_VERBOSE_MODE */
+
+static gboolean
+grab_keyboard (MetaDisplay *display,
+ Window xwindow,
+ guint32 timestamp)
+{
+ int result;
+ int grab_status;
+
+ /* Grab the keyboard, so we get key releases and all key
+ * presses
+ */
+ meta_error_trap_push_with_return (display);
+
+ grab_status = XGrabKeyboard (display->xdisplay,
+ xwindow, True,
+ GrabModeAsync, GrabModeAsync,
+ timestamp);
+
+ if (grab_status != GrabSuccess)
+ {
+ meta_error_trap_pop_with_return (display, TRUE);
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "XGrabKeyboard() returned failure status %s time %u\n",
+ grab_status_to_string (grab_status),
+ timestamp);
+ return FALSE;
+ }
+ else
+ {
+ result = meta_error_trap_pop_with_return (display, TRUE);
+ if (result != Success)
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "XGrabKeyboard() resulted in an error\n");
+ return FALSE;
+ }
+ }
+
+ meta_topic (META_DEBUG_KEYBINDINGS, "Grabbed all keys\n");
+
+ return TRUE;
+}
+
+static void
+ungrab_keyboard (MetaDisplay *display, guint32 timestamp)
+{
+ meta_error_trap_push (display);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Ungrabbing keyboard with timestamp %u\n",
+ timestamp);
+ XUngrabKeyboard (display->xdisplay, timestamp);
+ meta_error_trap_pop (display, FALSE);
+}
+
+gboolean
+meta_screen_grab_all_keys (MetaScreen *screen, guint32 timestamp)
+{
+ gboolean retval;
+
+ if (screen->all_keys_grabbed)
+ return FALSE;
+
+ if (screen->keys_grabbed)
+ meta_screen_ungrab_keys (screen);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Grabbing all keys on RootWindow\n");
+ retval = grab_keyboard (screen->display, screen->xroot, timestamp);
+ if (retval)
+ screen->all_keys_grabbed = TRUE;
+ else
+ meta_screen_grab_keys (screen);
+
+ return retval;
+}
+
+void
+meta_screen_ungrab_all_keys (MetaScreen *screen, guint32 timestamp)
+{
+ if (screen->all_keys_grabbed)
+ {
+ ungrab_keyboard (screen->display, timestamp);
+
+ screen->all_keys_grabbed = FALSE;
+ screen->keys_grabbed = FALSE;
+
+ /* Re-establish our standard bindings */
+ meta_screen_grab_keys (screen);
+ }
+}
+
+gboolean
+meta_window_grab_all_keys (MetaWindow *window,
+ guint32 timestamp)
+{
+ Window grabwindow;
+ gboolean retval;
+
+ if (window->all_keys_grabbed)
+ return FALSE;
+
+ if (window->keys_grabbed)
+ meta_window_ungrab_keys (window);
+
+ /* Make sure the window is focused, otherwise the grab
+ * won't do a lot of good.
+ */
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing %s because we're grabbing all its keys\n",
+ window->desc);
+ meta_window_focus (window, timestamp);
+
+ grabwindow = window->frame ? window->frame->xwindow : window->xwindow;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Grabbing all keys on window %s\n", window->desc);
+ retval = grab_keyboard (window->display, grabwindow, timestamp);
+ if (retval)
+ {
+ window->keys_grabbed = FALSE;
+ window->all_keys_grabbed = TRUE;
+ window->grab_on_frame = window->frame != NULL;
+ }
+
+ return retval;
+}
+
+void
+meta_window_ungrab_all_keys (MetaWindow *window, guint32 timestamp)
+{
+ if (window->all_keys_grabbed)
+ {
+ ungrab_keyboard (window->display, timestamp);
+
+ window->grab_on_frame = FALSE;
+ window->all_keys_grabbed = FALSE;
+ window->keys_grabbed = FALSE;
+
+ /* Re-establish our standard bindings */
+ meta_window_grab_keys (window);
+ }
+}
+
+static gboolean
+is_modifier (MetaDisplay *display,
+ unsigned int keycode)
+{
+ int i;
+ int map_size;
+ gboolean retval = FALSE;
+
+ g_assert (display->modmap);
+
+ map_size = 8 * display->modmap->max_keypermod;
+ i = 0;
+ while (i < map_size)
+ {
+ if (keycode == display->modmap->modifiermap[i])
+ {
+ retval = TRUE;
+ break;
+ }
+ ++i;
+ }
+
+ return retval;
+}
+
+/* Indexes:
+ * shift = 0
+ * lock = 1
+ * control = 2
+ * mod1 = 3
+ * mod2 = 4
+ * mod3 = 5
+ * mod4 = 6
+ * mod5 = 7
+ */
+
+static gboolean
+is_specific_modifier (MetaDisplay *display,
+ unsigned int keycode,
+ unsigned int mask)
+{
+ int i;
+ int end;
+ gboolean retval = FALSE;
+ int mod_index;
+
+ g_assert (display->modmap);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Checking whether code 0x%x is bound to modifier 0x%x\n",
+ keycode, mask);
+
+ mod_index = 0;
+ mask = mask >> 1;
+ while (mask != 0)
+ {
+ mod_index += 1;
+ mask = mask >> 1;
+ }
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Modifier has index %d\n", mod_index);
+
+ end = (mod_index + 1) * display->modmap->max_keypermod;
+ i = mod_index * display->modmap->max_keypermod;
+ while (i < end)
+ {
+ if (keycode == display->modmap->modifiermap[i])
+ {
+ retval = TRUE;
+ break;
+ }
+ ++i;
+ }
+
+ return retval;
+}
+
+static unsigned int
+get_primary_modifier (MetaDisplay *display,
+ unsigned int entire_binding_mask)
+{
+ /* The idea here is to see if the "main" modifier
+ * for Alt+Tab has been pressed/released. So if the binding
+ * is Alt+Shift+Tab then releasing Alt is the thing that
+ * ends the operation. It's pretty random how we order
+ * these.
+ */
+ unsigned int masks[] = { Mod5Mask, Mod4Mask, Mod3Mask,
+ Mod2Mask, Mod1Mask, ControlMask,
+ ShiftMask, LockMask };
+
+ int i;
+
+ i = 0;
+ while (i < (int) G_N_ELEMENTS (masks))
+ {
+ if (entire_binding_mask & masks[i])
+ return masks[i];
+ ++i;
+ }
+
+ return 0;
+}
+
+static gboolean
+keycode_is_primary_modifier (MetaDisplay *display,
+ unsigned int keycode,
+ unsigned int entire_binding_mask)
+{
+ unsigned int primary_modifier;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Checking whether code 0x%x is the primary modifier of mask 0x%x\n",
+ keycode, entire_binding_mask);
+
+ primary_modifier = get_primary_modifier (display, entire_binding_mask);
+ if (primary_modifier != 0)
+ return is_specific_modifier (display, keycode, primary_modifier);
+ else
+ return FALSE;
+}
+
+static gboolean
+primary_modifier_still_pressed (MetaDisplay *display,
+ unsigned int entire_binding_mask)
+{
+ unsigned int primary_modifier;
+ int x, y, root_x, root_y;
+ Window root, child;
+ guint mask;
+ MetaScreen *random_screen;
+ Window random_xwindow;
+
+ primary_modifier = get_primary_modifier (display, entire_binding_mask);
+
+ random_screen = display->screens->data;
+ random_xwindow = random_screen->no_focus_window;
+ XQueryPointer (display->xdisplay,
+ random_xwindow, /* some random window */
+ &root, &child,
+ &root_x, &root_y,
+ &x, &y,
+ &mask);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Primary modifier 0x%x full grab mask 0x%x current state 0x%x\n",
+ primary_modifier, entire_binding_mask, mask);
+
+ if ((mask & primary_modifier) == 0)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+/* now called from only one place, may be worth merging */
+static gboolean
+process_event (MetaKeyBinding *bindings,
+ int n_bindings,
+ MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ KeySym keysym,
+ gboolean on_window)
+{
+ int i;
+
+ /* we used to have release-based bindings but no longer. */
+ if (event->type == KeyRelease)
+ return FALSE;
+
+ /*
+ * TODO: This would be better done with a hash table;
+ * it doesn't suit to use O(n) for such a common operation.
+ */
+ for (i=0; i<n_bindings; i++)
+ {
+ const MetaKeyHandler *handler = bindings[i].handler;
+
+ if ((!on_window && handler->flags & BINDING_PER_WINDOW) ||
+ event->type != KeyPress ||
+ bindings[i].keycode != event->xkey.keycode ||
+ ((event->xkey.state & 0xff & ~(display->ignored_modifier_mask)) !=
+ bindings[i].mask))
+ continue;
+
+ /*
+ * window must be non-NULL for on_window to be true,
+ * and so also window must be non-NULL if we get here and
+ * this is a BINDING_PER_WINDOW binding.
+ */
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Binding keycode 0x%x mask 0x%x matches event 0x%x state 0x%x\n",
+ bindings[i].keycode, bindings[i].mask,
+ event->xkey.keycode, event->xkey.state);
+
+ if (handler == NULL)
+ meta_bug ("Binding %s has no handler\n", bindings[i].name);
+ else
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Running handler for %s\n",
+ bindings[i].name);
+
+ /* Global keybindings count as a let-the-terminal-lose-focus
+ * due to new window mapping until the user starts
+ * interacting with the terminal again.
+ */
+ display->allow_terminal_deactivation = TRUE;
+
+ (* handler->func) (display, screen,
+ bindings[i].handler->flags & BINDING_PER_WINDOW? window: NULL,
+ event,
+ &bindings[i]);
+ return TRUE;
+ }
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "No handler found for this event in this binding table\n");
+ return FALSE;
+}
+
+/* Handle a key event. May be called recursively: some key events cause
+ * grabs to be ended and then need to be processed again in their own
+ * right. This cannot cause infinite recursion because we never call
+ * ourselves when there wasn't a grab, and we always clear the grab
+ * first; the invariant is enforced using an assertion. See #112560.
+ * FIXME: We need to prove there are no race conditions here.
+ * FIXME: Does it correctly handle alt-Tab being followed by another
+ * grabbing keypress without letting go of alt?
+ * FIXME: An iterative solution would probably be simpler to understand
+ * (and help us solve the other fixmes).
+ */
+void
+meta_display_process_key_event (MetaDisplay *display,
+ MetaWindow *window,
+ XEvent *event)
+{
+ KeySym keysym;
+ gboolean keep_grab;
+ gboolean all_keys_grabbed;
+ const char *str;
+ MetaScreen *screen;
+
+ XAllowEvents (display->xdisplay,
+ all_bindings_disabled ? ReplayKeyboard : AsyncKeyboard,
+ event->xkey.time);
+ if (all_bindings_disabled)
+ return;
+
+ /* if key event was on root window, we have a shortcut */
+ screen = meta_display_screen_for_root (display, event->xkey.window);
+
+ /* else round-trip to server */
+ if (screen == NULL)
+ screen = meta_display_screen_for_xwindow (display,
+ event->xany.window);
+
+ if (screen == NULL)
+ return; /* event window is destroyed */
+
+ /* ignore key events on popup menus and such. */
+ if (window == NULL &&
+ meta_ui_window_is_widget (screen->ui, event->xany.window))
+ return;
+
+ /* window may be NULL */
+
+ keysym = XKeycodeToKeysym (display->xdisplay, event->xkey.keycode, 0);
+
+ str = XKeysymToString (keysym);
+
+ /* was topic */
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Processing key %s event, keysym: %s state: 0x%x window: %s\n",
+ event->type == KeyPress ? "press" : "release",
+ str ? str : "none", event->xkey.state,
+ window ? window->desc : "(no window)");
+
+ keep_grab = TRUE;
+ all_keys_grabbed = window ? window->all_keys_grabbed : screen->all_keys_grabbed;
+ if (all_keys_grabbed)
+ {
+ if (display->grab_op == META_GRAB_OP_NONE)
+ return;
+ /* If we get here we have a global grab, because
+ * we're in some special keyboard mode such as window move
+ * mode.
+ */
+ if (window ? (window == display->grab_window) :
+ (screen == display->grab_screen))
+ {
+ switch (display->grab_op)
+ {
+ case META_GRAB_OP_MOVING:
+ case META_GRAB_OP_RESIZING_SE:
+ case META_GRAB_OP_RESIZING_S:
+ case META_GRAB_OP_RESIZING_SW:
+ case META_GRAB_OP_RESIZING_N:
+ case META_GRAB_OP_RESIZING_NE:
+ case META_GRAB_OP_RESIZING_NW:
+ case META_GRAB_OP_RESIZING_W:
+ case META_GRAB_OP_RESIZING_E:
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Processing event for mouse-only move/resize\n");
+ g_assert (window != NULL);
+ keep_grab = process_mouse_move_resize_grab (display, screen,
+ window, event, keysym);
+ break;
+
+ case META_GRAB_OP_KEYBOARD_MOVING:
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Processing event for keyboard move\n");
+ g_assert (window != NULL);
+ keep_grab = process_keyboard_move_grab (display, screen,
+ window, event, keysym);
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
+ case META_GRAB_OP_KEYBOARD_RESIZING_S:
+ case META_GRAB_OP_KEYBOARD_RESIZING_N:
+ case META_GRAB_OP_KEYBOARD_RESIZING_W:
+ case META_GRAB_OP_KEYBOARD_RESIZING_E:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Processing event for keyboard resize\n");
+ g_assert (window != NULL);
+ keep_grab = process_keyboard_resize_grab (display, screen,
+ window, event, keysym);
+ break;
+
+ case META_GRAB_OP_KEYBOARD_TABBING_NORMAL:
+ case META_GRAB_OP_KEYBOARD_TABBING_DOCK:
+ case META_GRAB_OP_KEYBOARD_TABBING_GROUP:
+ case META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL:
+ case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK:
+ case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP:
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Processing event for keyboard tabbing/cycling\n");
+ keep_grab = process_tab_grab (display, screen, event, keysym);
+ break;
+
+ case META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING:
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Processing event for keyboard workspace switching\n");
+ keep_grab = process_workspace_switch_grab (display, screen, event, keysym);
+ break;
+
+ default:
+ break;
+ }
+ }
+ if (!keep_grab)
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Ending grab op %u on key event sym %s\n",
+ display->grab_op, XKeysymToString (keysym));
+ meta_display_end_grab_op (display, event->xkey.time);
+ return;
+ }
+ }
+ /* Do the normal keybindings */
+ process_event (display->key_bindings,
+ display->n_key_bindings,
+ display, screen, window, event, keysym,
+ !all_keys_grabbed && window);
+}
+
+static gboolean
+process_mouse_move_resize_grab (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ KeySym keysym)
+{
+ /* don't care about releases, but eat them, don't end grab */
+ if (event->type == KeyRelease)
+ return TRUE;
+
+ if (keysym == XK_Escape)
+ {
+ /* End move or resize and restore to original state. If the
+ * window was a maximized window that had been "shaken loose" we
+ * need to remaximize it. In normal cases, we need to do a
+ * moveresize now to get the position back to the original. In
+ * wireframe mode, we just need to set grab_was_cancelled to tru
+ * to avoid avoid moveresizing to the position of the wireframe.
+ */
+ if (window->shaken_loose)
+ meta_window_maximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+ else if (!display->grab_wireframe_active)
+ meta_window_move_resize (display->grab_window,
+ TRUE,
+ display->grab_initial_window_pos.x,
+ display->grab_initial_window_pos.y,
+ display->grab_initial_window_pos.width,
+ display->grab_initial_window_pos.height);
+ else
+ display->grab_was_cancelled = TRUE;
+
+ /* End grab */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+process_keyboard_move_grab (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ KeySym keysym)
+{
+ gboolean handled;
+ int x, y;
+ int incr;
+ gboolean smart_snap;
+
+ handled = FALSE;
+
+ /* don't care about releases, but eat them, don't end grab */
+ if (event->type == KeyRelease)
+ return TRUE;
+
+ /* don't end grab on modifier key presses */
+ if (is_modifier (display, event->xkey.keycode))
+ return TRUE;
+
+ if (display->grab_wireframe_active)
+ {
+ x = display->grab_wireframe_rect.x;
+ y = display->grab_wireframe_rect.y;
+ }
+ else
+ {
+ meta_window_get_position (window, &x, &y);
+ }
+
+ smart_snap = (event->xkey.state & ShiftMask) != 0;
+
+#define SMALL_INCREMENT 1
+#define NORMAL_INCREMENT 10
+
+ if (smart_snap)
+ incr = 1;
+ else if (event->xkey.state & ControlMask)
+ incr = SMALL_INCREMENT;
+ else
+ incr = NORMAL_INCREMENT;
+
+ if (keysym == XK_Escape)
+ {
+ /* End move and restore to original state. If the window was a
+ * maximized window that had been "shaken loose" we need to
+ * remaximize it. In normal cases, we need to do a moveresize
+ * now to get the position back to the original. In wireframe
+ * mode, we just need to set grab_was_cancelled to tru to avoid
+ * avoid moveresizing to the position of the wireframe.
+ */
+ if (window->shaken_loose)
+ meta_window_maximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+ else if (!display->grab_wireframe_active)
+ meta_window_move_resize (display->grab_window,
+ TRUE,
+ display->grab_initial_window_pos.x,
+ display->grab_initial_window_pos.y,
+ display->grab_initial_window_pos.width,
+ display->grab_initial_window_pos.height);
+ else
+ display->grab_was_cancelled = TRUE;
+ }
+
+ /* When moving by increments, we still snap to edges if the move
+ * to the edge is smaller than the increment. This is because
+ * Shift + arrow to snap is sort of a hidden feature. This way
+ * people using just arrows shouldn't get too frustrated.
+ */
+ switch (keysym)
+ {
+ case XK_KP_Home:
+ case XK_KP_Prior:
+ case XK_Up:
+ case XK_KP_Up:
+ y -= incr;
+ handled = TRUE;
+ break;
+ case XK_KP_End:
+ case XK_KP_Next:
+ case XK_Down:
+ case XK_KP_Down:
+ y += incr;
+ handled = TRUE;
+ break;
+ }
+
+ switch (keysym)
+ {
+ case XK_KP_Home:
+ case XK_KP_End:
+ case XK_Left:
+ case XK_KP_Left:
+ x -= incr;
+ handled = TRUE;
+ break;
+ case XK_KP_Prior:
+ case XK_KP_Next:
+ case XK_Right:
+ case XK_KP_Right:
+ x += incr;
+ handled = TRUE;
+ break;
+ }
+
+ if (handled)
+ {
+ MetaRectangle old_rect;
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Computed new window location %d,%d due to keypress\n",
+ x, y);
+
+ if (display->grab_wireframe_active)
+ old_rect = display->grab_wireframe_rect;
+ else
+ meta_window_get_client_root_coords (window, &old_rect);
+
+ meta_window_edge_resistance_for_move (window,
+ old_rect.x,
+ old_rect.y,
+ &x,
+ &y,
+ NULL,
+ smart_snap,
+ TRUE);
+
+ if (display->grab_wireframe_active)
+ {
+ meta_window_update_wireframe (window, x, y,
+ display->grab_wireframe_rect.width,
+ display->grab_wireframe_rect.height);
+ }
+ else
+ {
+ meta_window_move (window, TRUE, x, y);
+ }
+
+ meta_window_update_keyboard_move (window);
+ }
+
+ return handled;
+}
+
+static gboolean
+process_keyboard_resize_grab_op_change (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ KeySym keysym)
+{
+ gboolean handled;
+
+ handled = FALSE;
+ switch (display->grab_op)
+ {
+ case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
+ switch (keysym)
+ {
+ case XK_Up:
+ case XK_KP_Up:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_N;
+ handled = TRUE;
+ break;
+ case XK_Down:
+ case XK_KP_Down:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_S;
+ handled = TRUE;
+ break;
+ case XK_Left:
+ case XK_KP_Left:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_W;
+ handled = TRUE;
+ break;
+ case XK_Right:
+ case XK_KP_Right:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_E;
+ handled = TRUE;
+ break;
+ }
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_S:
+ switch (keysym)
+ {
+ case XK_Left:
+ case XK_KP_Left:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_W;
+ handled = TRUE;
+ break;
+ case XK_Right:
+ case XK_KP_Right:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_E;
+ handled = TRUE;
+ break;
+ }
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_N:
+ switch (keysym)
+ {
+ case XK_Left:
+ case XK_KP_Left:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_W;
+ handled = TRUE;
+ break;
+ case XK_Right:
+ case XK_KP_Right:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_E;
+ handled = TRUE;
+ break;
+ }
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_W:
+ switch (keysym)
+ {
+ case XK_Up:
+ case XK_KP_Up:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_N;
+ handled = TRUE;
+ break;
+ case XK_Down:
+ case XK_KP_Down:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_S;
+ handled = TRUE;
+ break;
+ }
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_E:
+ switch (keysym)
+ {
+ case XK_Up:
+ case XK_KP_Up:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_N;
+ handled = TRUE;
+ break;
+ case XK_Down:
+ case XK_KP_Down:
+ display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_S;
+ handled = TRUE;
+ break;
+ }
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ if (handled)
+ {
+ meta_window_update_keyboard_resize (window, TRUE);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+process_keyboard_resize_grab (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ KeySym keysym)
+{
+ gboolean handled;
+ int height_inc;
+ int width_inc;
+ int width, height;
+ gboolean smart_snap;
+ int gravity;
+
+ handled = FALSE;
+
+ /* don't care about releases, but eat them, don't end grab */
+ if (event->type == KeyRelease)
+ return TRUE;
+
+ /* don't end grab on modifier key presses */
+ if (is_modifier (display, event->xkey.keycode))
+ return TRUE;
+
+ if (keysym == XK_Escape)
+ {
+ /* End resize and restore to original state. If not in
+ * wireframe mode, we need to do a moveresize now to get the
+ * position back to the original. If we are in wireframe mode,
+ * we need to avoid moveresizing to the position of the
+ * wireframe.
+ */
+ if (!display->grab_wireframe_active)
+ meta_window_move_resize (display->grab_window,
+ TRUE,
+ display->grab_initial_window_pos.x,
+ display->grab_initial_window_pos.y,
+ display->grab_initial_window_pos.width,
+ display->grab_initial_window_pos.height);
+ else
+ display->grab_was_cancelled = TRUE;
+
+ return FALSE;
+ }
+
+ if (process_keyboard_resize_grab_op_change (display, screen, window,
+ event, keysym))
+ return TRUE;
+
+ if (display->grab_wireframe_active)
+ {
+ width = display->grab_wireframe_rect.width;
+ height = display->grab_wireframe_rect.height;
+ }
+ else
+ {
+ width = window->rect.width;
+ height = window->rect.height;
+ }
+
+ gravity = meta_resize_gravity_from_grab_op (display->grab_op);
+
+ smart_snap = (event->xkey.state & ShiftMask) != 0;
+
+#define SMALL_INCREMENT 1
+#define NORMAL_INCREMENT 10
+
+ if (smart_snap)
+ {
+ height_inc = 1;
+ width_inc = 1;
+ }
+ else if (event->xkey.state & ControlMask)
+ {
+ width_inc = SMALL_INCREMENT;
+ height_inc = SMALL_INCREMENT;
+ }
+ else
+ {
+ width_inc = NORMAL_INCREMENT;
+ height_inc = NORMAL_INCREMENT;
+ }
+
+ /* If this is a resize increment window, make the amount we resize
+ * the window by match that amount (well, unless snap resizing...)
+ */
+ if (window->size_hints.width_inc > 1)
+ width_inc = window->size_hints.width_inc;
+ if (window->size_hints.height_inc > 1)
+ height_inc = window->size_hints.height_inc;
+
+ switch (keysym)
+ {
+ case XK_Up:
+ case XK_KP_Up:
+ switch (gravity)
+ {
+ case NorthGravity:
+ case NorthWestGravity:
+ case NorthEastGravity:
+ /* Move bottom edge up */
+ height -= height_inc;
+ break;
+
+ case SouthGravity:
+ case SouthWestGravity:
+ case SouthEastGravity:
+ /* Move top edge up */
+ height += height_inc;
+ break;
+
+ case EastGravity:
+ case WestGravity:
+ case CenterGravity:
+ g_assert_not_reached ();
+ break;
+ }
+
+ handled = TRUE;
+ break;
+
+ case XK_Down:
+ case XK_KP_Down:
+ switch (gravity)
+ {
+ case NorthGravity:
+ case NorthWestGravity:
+ case NorthEastGravity:
+ /* Move bottom edge down */
+ height += height_inc;
+ break;
+
+ case SouthGravity:
+ case SouthWestGravity:
+ case SouthEastGravity:
+ /* Move top edge down */
+ height -= height_inc;
+ break;
+
+ case EastGravity:
+ case WestGravity:
+ case CenterGravity:
+ g_assert_not_reached ();
+ break;
+ }
+
+ handled = TRUE;
+ break;
+
+ case XK_Left:
+ case XK_KP_Left:
+ switch (gravity)
+ {
+ case EastGravity:
+ case SouthEastGravity:
+ case NorthEastGravity:
+ /* Move left edge left */
+ width += width_inc;
+ break;
+
+ case WestGravity:
+ case SouthWestGravity:
+ case NorthWestGravity:
+ /* Move right edge left */
+ width -= width_inc;
+ break;
+
+ case NorthGravity:
+ case SouthGravity:
+ case CenterGravity:
+ g_assert_not_reached ();
+ break;
+ }
+
+ handled = TRUE;
+ break;
+
+ case XK_Right:
+ case XK_KP_Right:
+ switch (gravity)
+ {
+ case EastGravity:
+ case SouthEastGravity:
+ case NorthEastGravity:
+ /* Move left edge right */
+ width -= width_inc;
+ break;
+
+ case WestGravity:
+ case SouthWestGravity:
+ case NorthWestGravity:
+ /* Move right edge right */
+ width += width_inc;
+ break;
+
+ case NorthGravity:
+ case SouthGravity:
+ case CenterGravity:
+ g_assert_not_reached ();
+ break;
+ }
+
+ handled = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ /* fixup hack (just paranoia, not sure it's required) */
+ if (height < 1)
+ height = 1;
+ if (width < 1)
+ width = 1;
+
+ if (handled)
+ {
+ MetaRectangle old_rect;
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Computed new window size due to keypress: "
+ "%dx%d, gravity %s\n",
+ width, height, meta_gravity_to_string (gravity));
+
+ if (display->grab_wireframe_active)
+ old_rect = display->grab_wireframe_rect;
+ else
+ old_rect = window->rect; /* Don't actually care about x,y */
+
+ /* Do any edge resistance/snapping */
+ meta_window_edge_resistance_for_resize (window,
+ old_rect.width,
+ old_rect.height,
+ &width,
+ &height,
+ gravity,
+ NULL,
+ smart_snap,
+ TRUE);
+
+ if (display->grab_wireframe_active)
+ {
+ MetaRectangle new_position;
+ meta_rectangle_resize_with_gravity (&display->grab_wireframe_rect,
+ &new_position,
+ gravity,
+ width,
+ height);
+ meta_window_update_wireframe (window,
+ new_position.x,
+ new_position.y,
+ new_position.width,
+ new_position.height);
+ }
+ else
+ {
+ /* We don't need to update unless the specified width and height
+ * are actually different from what we had before.
+ */
+ if (window->rect.width != width || window->rect.height != height)
+ meta_window_resize_with_gravity (window,
+ TRUE,
+ width,
+ height,
+ gravity);
+ }
+ meta_window_update_keyboard_resize (window, FALSE);
+ }
+
+ return handled;
+}
+
+static gboolean
+end_keyboard_grab (MetaDisplay *display,
+ unsigned int keycode)
+{
+#ifdef HAVE_XKB
+ if (display->xkb_base_event_type > 0)
+ {
+ unsigned int primary_modifier;
+ XkbStateRec state;
+
+ primary_modifier = get_primary_modifier (display, display->grab_mask);
+
+ XkbGetState (display->xdisplay, XkbUseCoreKbd, &state);
+
+ if (!(primary_modifier & state.mods))
+ return TRUE;
+ }
+ else
+#endif
+ {
+ if (keycode_is_primary_modifier (display, keycode, display->grab_mask))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+process_tab_grab (MetaDisplay *display,
+ MetaScreen *screen,
+ XEvent *event,
+ KeySym keysym)
+{
+ MetaKeyBindingAction action;
+ gboolean popup_not_showing;
+ gboolean backward;
+ gboolean key_used;
+ Window prev_xwindow;
+ MetaWindow *prev_window;
+
+ if (screen != display->grab_screen)
+ return FALSE;
+
+ g_return_val_if_fail (screen->tab_popup != NULL, FALSE);
+
+ if (event->type == KeyRelease &&
+ end_keyboard_grab (display, event->xkey.keycode))
+ {
+ /* We're done, move to the new window. */
+ Window target_xwindow;
+ MetaWindow *target_window;
+
+ target_xwindow =
+ (Window) meta_ui_tab_popup_get_selected (screen->tab_popup);
+ target_window =
+ meta_display_lookup_x_window (display, target_xwindow);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Ending tab operation, primary modifier released\n");
+
+ if (target_window)
+ {
+ target_window->tab_unminimized = FALSE;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Activating target window\n");
+
+ meta_topic (META_DEBUG_FOCUS, "Activating %s due to tab popup "
+ "selection and turning mouse_mode off\n",
+ target_window->desc);
+ display->mouse_mode = FALSE;
+ meta_window_activate (target_window, event->xkey.time);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Ending grab early so we can focus the target window\n");
+ meta_display_end_grab_op (display, event->xkey.time);
+
+ return TRUE; /* we already ended the grab */
+ }
+
+ return FALSE; /* end grab */
+ }
+
+ /* don't care about other releases, but eat them, don't end grab */
+ if (event->type == KeyRelease)
+ return TRUE;
+
+ /* don't end grab on modifier key presses */
+ if (is_modifier (display, event->xkey.keycode))
+ return TRUE;
+
+ prev_xwindow = (Window) meta_ui_tab_popup_get_selected (screen->tab_popup);
+ prev_window = meta_display_lookup_x_window (display, prev_xwindow);
+ action = display_get_keybinding_action (display,
+ keysym,
+ event->xkey.keycode,
+ display->grab_mask);
+
+ /* Cancel when alt-Escape is pressed during using alt-Tab, and vice
+ * versa.
+ */
+ switch (action)
+ {
+ case META_KEYBINDING_ACTION_CYCLE_PANELS:
+ case META_KEYBINDING_ACTION_CYCLE_WINDOWS:
+ case META_KEYBINDING_ACTION_CYCLE_PANELS_BACKWARD:
+ case META_KEYBINDING_ACTION_CYCLE_WINDOWS_BACKWARD:
+ /* CYCLE_* are traditionally Escape-based actions,
+ * and should cancel traditionally Tab-based ones.
+ */
+ switch (display->grab_op)
+ {
+ case META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL:
+ case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK:
+ /* carry on */
+ break;
+ default:
+ return FALSE;
+ }
+ break;
+ case META_KEYBINDING_ACTION_SWITCH_PANELS:
+ case META_KEYBINDING_ACTION_SWITCH_WINDOWS:
+ case META_KEYBINDING_ACTION_SWITCH_PANELS_BACKWARD:
+ case META_KEYBINDING_ACTION_SWITCH_WINDOWS_BACKWARD:
+ /* SWITCH_* are traditionally Tab-based actions,
+ * and should cancel traditionally Escape-based ones.
+ */
+ switch (display->grab_op)
+ {
+ case META_GRAB_OP_KEYBOARD_TABBING_NORMAL:
+ case META_GRAB_OP_KEYBOARD_TABBING_DOCK:
+ /* carry on */
+ break;
+ default:
+ /* Also, we must re-lower and re-minimize whatever window
+ * we'd previously raised and unminimized.
+ */
+ meta_stack_set_positions (screen->stack,
+ screen->display->grab_old_window_stacking);
+ if (prev_window && prev_window->tab_unminimized)
+ {
+ meta_window_minimize (prev_window);
+ prev_window->tab_unminimized = FALSE;
+ }
+ return FALSE;
+ }
+ break;
+ case META_KEYBINDING_ACTION_CYCLE_GROUP:
+ case META_KEYBINDING_ACTION_CYCLE_GROUP_BACKWARD:
+ case META_KEYBINDING_ACTION_SWITCH_GROUP:
+ case META_KEYBINDING_ACTION_SWITCH_GROUP_BACKWARD:
+ switch (display->grab_op)
+ {
+ case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP:
+ case META_GRAB_OP_KEYBOARD_TABBING_GROUP:
+ /* carry on */
+ break;
+ default:
+ return FALSE;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ /* !! TO HERE !!
+ * alt-f6 during alt-{Tab,Escape} does not end the grab
+ * but does change the grab op (and redraws the window,
+ * of course).
+ * See _{SWITCH,CYCLE}_GROUP.@@@
+ */
+
+ popup_not_showing = FALSE;
+ key_used = FALSE;
+ backward = FALSE;
+
+ switch (action)
+ {
+ case META_KEYBINDING_ACTION_CYCLE_PANELS:
+ case META_KEYBINDING_ACTION_CYCLE_WINDOWS:
+ case META_KEYBINDING_ACTION_CYCLE_GROUP:
+ popup_not_showing = TRUE;
+ key_used = TRUE;
+ break;
+ case META_KEYBINDING_ACTION_CYCLE_PANELS_BACKWARD:
+ case META_KEYBINDING_ACTION_CYCLE_WINDOWS_BACKWARD:
+ case META_KEYBINDING_ACTION_CYCLE_GROUP_BACKWARD:
+ popup_not_showing = TRUE;
+ key_used = TRUE;
+ backward = TRUE;
+ break;
+ case META_KEYBINDING_ACTION_SWITCH_PANELS:
+ case META_KEYBINDING_ACTION_SWITCH_WINDOWS:
+ case META_KEYBINDING_ACTION_SWITCH_GROUP:
+ key_used = TRUE;
+ break;
+ case META_KEYBINDING_ACTION_SWITCH_PANELS_BACKWARD:
+ case META_KEYBINDING_ACTION_SWITCH_WINDOWS_BACKWARD:
+ case META_KEYBINDING_ACTION_SWITCH_GROUP_BACKWARD:
+ key_used = TRUE;
+ backward = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ if (key_used)
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Key pressed, moving tab focus in popup\n");
+
+ if (event->xkey.state & ShiftMask)
+ backward = !backward;
+
+ if (backward)
+ meta_ui_tab_popup_backward (screen->tab_popup);
+ else
+ meta_ui_tab_popup_forward (screen->tab_popup);
+
+ if (popup_not_showing)
+ {
+ /* We can't actually change window focus, due to the grab.
+ * but raise the window.
+ */
+ Window target_xwindow;
+ MetaWindow *target_window;
+
+ meta_stack_set_positions (screen->stack,
+ display->grab_old_window_stacking);
+
+ target_xwindow =
+ (Window) meta_ui_tab_popup_get_selected (screen->tab_popup);
+ target_window =
+ meta_display_lookup_x_window (display, target_xwindow);
+
+ if (prev_window && prev_window->tab_unminimized)
+ {
+ prev_window->tab_unminimized = FALSE;
+ meta_window_minimize (prev_window);
+ }
+
+ if (target_window)
+ {
+ meta_window_raise (target_window);
+ target_window->tab_unminimized = target_window->minimized;
+ meta_window_unminimize (target_window);
+ }
+ }
+ }
+ else
+ {
+ /* end grab */
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Ending tabbing/cycling, uninteresting key pressed\n");
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Syncing to old stack positions.\n");
+ meta_stack_set_positions (screen->stack,
+ screen->display->grab_old_window_stacking);
+
+ if (prev_window && prev_window->tab_unminimized)
+ {
+ meta_window_minimize (prev_window);
+ prev_window->tab_unminimized = FALSE;
+ }
+ }
+
+ return key_used;
+}
+
+static void
+handle_switch_to_workspace (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *event_window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ gint which = binding->handler->data;
+ MetaWorkspace *workspace;
+
+ if (which < 0)
+ {
+ /* Negative workspace numbers are directions with respect to the
+ * current workspace. While we could insta-switch here by setting
+ * workspace to the result of meta_workspace_get_neighbor(), when
+ * people request a workspace switch to the left or right via
+ * the keyboard, they actually want a tab popup. So we should
+ * go there instead.
+ *
+ * Note that we're the only caller of that function, so perhaps
+ * we should merge with it.
+ */
+ handle_workspace_switch (display, screen, event_window, event, binding);
+ return;
+ }
+
+ workspace = meta_screen_get_workspace_by_index (screen, which);
+
+ if (workspace)
+ {
+ meta_workspace_activate (workspace, event->xkey.time);
+ }
+ else
+ {
+ /* We could offer to create it I suppose */
+ }
+}
+
+static void
+error_on_command (int command_index,
+ const char *command,
+ const char *message,
+ int screen_number,
+ guint32 timestamp)
+{
+ if (command_index < 0)
+ meta_warning ("Error on terminal command \"%s\": %s\n", command, message);
+ else
+ meta_warning ("Error on command %d \"%s\": %s\n",
+ command_index, command, message);
+
+ /*
+ marco-dialog said:
+
+ FIXME offer to change the value of the command's mateconf key
+ */
+
+ if (command && strcmp(command, "")!=0)
+ {
+ char *text = g_strdup_printf (
+ /* Displayed when a keybinding which is
+ * supposed to launch a program fails.
+ */
+ _("There was an error running "
+ "<tt>%s</tt>:\n\n%s"),
+ command,
+ message);
+
+ meta_show_dialog ("--error",
+ text,
+ NULL,
+ screen_number,
+ NULL, NULL, 0,
+ NULL, NULL);
+
+ g_free (text);
+
+ }
+ else
+ {
+ meta_show_dialog ("--error",
+ message,
+ NULL,
+ screen_number,
+ NULL, NULL, 0,
+ NULL, NULL);
+ }
+}
+
+static void
+set_display_setup_func (void *data)
+{
+ const char *screen_name = data;
+ char *full;
+
+ full = g_strdup_printf ("DISPLAY=%s", screen_name);
+
+ putenv (full);
+
+ /* do not free full, because putenv is lame */
+}
+
+static gboolean
+meta_spawn_command_line_async_on_screen (const gchar *command_line,
+ MetaScreen *screen,
+ GError **error)
+{
+ gboolean retval;
+ gchar **argv = NULL;
+
+ g_return_val_if_fail (command_line != NULL, FALSE);
+
+ if (!g_shell_parse_argv (command_line,
+ NULL, &argv,
+ error))
+ return FALSE;
+
+ retval = g_spawn_async (NULL,
+ argv,
+ NULL,
+ G_SPAWN_SEARCH_PATH,
+ set_display_setup_func,
+ screen->screen_name,
+ NULL,
+ error);
+ g_strfreev (argv);
+
+ return retval;
+}
+
+
+static void
+handle_run_command (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ gint which = binding->handler->data;
+ const char *command;
+ GError *err;
+
+ command = meta_prefs_get_command (which);
+
+ if (command == NULL)
+ {
+ char *s;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "No command %d to run in response to keybinding press\n",
+ which);
+
+ s = g_strdup_printf (_("No command %d has been defined.\n"),
+ which + 1);
+ error_on_command (which, NULL, s, screen->number, event->xkey.time);
+ g_free (s);
+
+ return;
+ }
+
+ err = NULL;
+ if (!meta_spawn_command_line_async_on_screen (command, screen, &err))
+ {
+ error_on_command (which, command, err->message, screen->number, event->xkey.time);
+
+ g_error_free (err);
+ }
+}
+
+
+static void
+handle_maximize_vertically (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->has_resize_func)
+ {
+ if (window->maximized_vertically)
+ meta_window_unmaximize (window, META_MAXIMIZE_VERTICAL);
+ else
+ meta_window_maximize (window, META_MAXIMIZE_VERTICAL);
+ }
+}
+
+static void
+handle_maximize_horizontally (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->has_resize_func)
+ {
+ if (window->maximized_horizontally)
+ meta_window_unmaximize (window, META_MAXIMIZE_HORIZONTAL);
+ else
+ meta_window_maximize (window, META_MAXIMIZE_HORIZONTAL);
+ }
+}
+
+/* Move a window to a corner; to_bottom/to_right are FALSE for the
+ * top or left edge, or TRUE for the bottom/right edge. xchange/ychange
+ * are FALSE if that dimension is not to be changed, TRUE otherwise.
+ * Together they describe which of the four corners, or four sides,
+ * is desired.
+ */
+static void
+handle_move_to_corner_backend (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ gboolean xchange,
+ gboolean ychange,
+ gboolean to_right,
+ gboolean to_bottom)
+{
+ MetaRectangle work_area;
+ MetaRectangle outer;
+ int orig_x, orig_y;
+ int new_x, new_y;
+ int frame_width, frame_height;
+
+ meta_window_get_work_area_all_xineramas (window, &work_area);
+ meta_window_get_outer_rect (window, &outer);
+ meta_window_get_position (window, &orig_x, &orig_y);
+
+ frame_width = (window->frame ? window->frame->child_x : 0);
+ frame_height = (window->frame ? window->frame->child_y : 0);
+
+ if (xchange) {
+ new_x = work_area.x + (to_right ?
+ (work_area.width + frame_width) - outer.width :
+ 0);
+ } else {
+ new_x = orig_x;
+ }
+
+ if (ychange) {
+ new_y = work_area.y + (to_bottom ?
+ (work_area.height + frame_height) - outer.height :
+ 0);
+ } else {
+ new_y = orig_y;
+ }
+
+ meta_window_move_resize (window,
+ FALSE,
+ new_x,
+ new_y,
+ window->rect.width,
+ window->rect.height);
+}
+
+static void
+handle_move_to_corner_nw (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ handle_move_to_corner_backend (display, screen, window, TRUE, TRUE, FALSE, FALSE);
+}
+
+static void
+handle_move_to_corner_ne (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ handle_move_to_corner_backend (display, screen, window, TRUE, TRUE, TRUE, FALSE);
+}
+
+static void
+handle_move_to_corner_sw (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ handle_move_to_corner_backend (display, screen, window, TRUE, TRUE, FALSE, TRUE);
+}
+
+static void
+handle_move_to_corner_se (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ handle_move_to_corner_backend (display, screen, window, TRUE, TRUE, TRUE, TRUE);
+}
+
+static void
+handle_move_to_side_n (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ handle_move_to_corner_backend (display, screen, window, FALSE, TRUE, FALSE, FALSE);
+}
+
+static void
+handle_move_to_side_s (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ handle_move_to_corner_backend (display, screen, window, FALSE, TRUE, FALSE, TRUE);
+}
+
+static void
+handle_move_to_side_e (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ handle_move_to_corner_backend (display, screen, window, TRUE, FALSE, TRUE, FALSE);
+}
+
+static void
+handle_move_to_side_w (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ handle_move_to_corner_backend (display, screen, window, TRUE, FALSE, FALSE, FALSE);
+}
+
+static void
+handle_move_to_center (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ MetaRectangle work_area;
+ MetaRectangle outer;
+ int orig_x, orig_y;
+ int frame_width, frame_height;
+
+ meta_window_get_work_area_all_xineramas (window, &work_area);
+ meta_window_get_outer_rect (window, &outer);
+ meta_window_get_position (window, &orig_x, &orig_y);
+
+ frame_width = (window->frame ? window->frame->child_x : 0);
+ frame_height = (window->frame ? window->frame->child_y : 0);
+
+ meta_window_move_resize (window,
+ TRUE,
+ work_area.x + (work_area.width +frame_width -outer.width )/2,
+ work_area.y + (work_area.height+frame_height-outer.height)/2,
+ window->rect.width,
+ window->rect.height);
+}
+
+static gboolean
+process_workspace_switch_grab (MetaDisplay *display,
+ MetaScreen *screen,
+ XEvent *event,
+ KeySym keysym)
+{
+ MetaWorkspace *workspace;
+
+ if (screen != display->grab_screen)
+ return FALSE;
+
+ g_return_val_if_fail (screen->tab_popup != NULL, FALSE);
+
+ if (event->type == KeyRelease &&
+ end_keyboard_grab (display, event->xkey.keycode))
+ {
+ /* We're done, move to the new workspace. */
+ MetaWorkspace *target_workspace;
+
+ target_workspace =
+ (MetaWorkspace *) meta_ui_tab_popup_get_selected (screen->tab_popup);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Ending workspace tab operation, primary modifier released\n");
+
+ if (target_workspace == screen->active_workspace)
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Ending grab so we can focus on the target workspace\n");
+ meta_display_end_grab_op (display, event->xkey.time);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Focusing default window on target workspace\n");
+
+ meta_workspace_focus_default_window (target_workspace,
+ NULL,
+ event->xkey.time);
+
+ return TRUE; /* we already ended the grab */
+ }
+
+ /* Workspace switching should have already occurred on KeyPress */
+ meta_warning ("target_workspace != active_workspace. Some other event must have occurred.\n");
+
+ return FALSE; /* end grab */
+ }
+
+ /* don't care about other releases, but eat them, don't end grab */
+ if (event->type == KeyRelease)
+ return TRUE;
+
+ /* don't end grab on modifier key presses */
+ if (is_modifier (display, event->xkey.keycode))
+ return TRUE;
+
+ /* select the next workspace in the tabpopup */
+ workspace =
+ (MetaWorkspace *) meta_ui_tab_popup_get_selected (screen->tab_popup);
+
+ if (workspace)
+ {
+ MetaWorkspace *target_workspace;
+ MetaKeyBindingAction action;
+
+ action = display_get_keybinding_action (display,
+ keysym,
+ event->xkey.keycode,
+ display->grab_mask);
+
+ switch (action)
+ {
+ case META_KEYBINDING_ACTION_WORKSPACE_UP:
+ target_workspace = meta_workspace_get_neighbor (workspace,
+ META_MOTION_UP);
+ break;
+
+ case META_KEYBINDING_ACTION_WORKSPACE_DOWN:
+ target_workspace = meta_workspace_get_neighbor (workspace,
+ META_MOTION_DOWN);
+ break;
+
+ case META_KEYBINDING_ACTION_WORKSPACE_LEFT:
+ target_workspace = meta_workspace_get_neighbor (workspace,
+ META_MOTION_LEFT);
+ break;
+
+ case META_KEYBINDING_ACTION_WORKSPACE_RIGHT:
+ target_workspace = meta_workspace_get_neighbor (workspace,
+ META_MOTION_RIGHT);
+ break;
+
+ default:
+ target_workspace = NULL;
+ break;
+ }
+
+ if (target_workspace)
+ {
+ meta_ui_tab_popup_select (screen->tab_popup,
+ (MetaTabEntryKey) target_workspace);
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Tab key pressed, moving tab focus in popup\n");
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Activating target workspace\n");
+
+ meta_workspace_activate (target_workspace, event->xkey.time);
+
+ return TRUE; /* we already ended the grab */
+ }
+ }
+
+ /* end grab */
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Ending workspace tabbing & focusing default window; uninteresting key pressed\n");
+ workspace =
+ (MetaWorkspace *) meta_ui_tab_popup_get_selected (screen->tab_popup);
+ meta_workspace_focus_default_window (workspace, NULL, event->xkey.time);
+ return FALSE;
+}
+
+static void
+handle_show_desktop (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (screen->active_workspace->showing_desktop)
+ {
+ meta_screen_unshow_desktop (screen);
+ meta_workspace_focus_default_window (screen->active_workspace,
+ NULL,
+ event->xkey.time);
+ }
+ else
+ meta_screen_show_desktop (screen, event->xkey.time);
+}
+
+static void
+handle_panel (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ MetaKeyBindingAction action = binding->handler->data;
+ Atom action_atom;
+ XClientMessageEvent ev;
+
+ action_atom = None;
+ switch (action)
+ {
+ /* FIXME: The numbers are wrong */
+ case META_KEYBINDING_ACTION_PANEL_MAIN_MENU:
+ action_atom = display->atom__MATE_PANEL_ACTION_MAIN_MENU;
+ break;
+ case META_KEYBINDING_ACTION_PANEL_RUN_DIALOG:
+ action_atom = display->atom__MATE_PANEL_ACTION_RUN_DIALOG;
+ break;
+ default:
+ return;
+ }
+
+ ev.type = ClientMessage;
+ ev.window = screen->xroot;
+ ev.message_type = display->atom__MATE_PANEL_ACTION;
+ ev.format = 32;
+ ev.data.l[0] = action_atom;
+ ev.data.l[1] = event->xkey.time;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Sending panel message with timestamp %lu, and turning mouse_mode "
+ "off due to keybinding press\n", event->xkey.time);
+ display->mouse_mode = FALSE;
+
+ meta_error_trap_push (display);
+
+ /* Release the grab for the panel before sending the event */
+ XUngrabKeyboard (display->xdisplay, event->xkey.time);
+
+ XSendEvent (display->xdisplay,
+ screen->xroot,
+ False,
+ StructureNotifyMask,
+ (XEvent*) &ev);
+
+ meta_error_trap_pop (display, FALSE);
+}
+
+static void
+handle_activate_window_menu (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *event_window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (display->focus_window)
+ {
+ int x, y;
+
+ meta_window_get_position (display->focus_window,
+ &x, &y);
+
+ if (meta_ui_get_direction() == META_UI_DIRECTION_RTL)
+ x += display->focus_window->rect.width;
+
+ meta_window_show_menu (display->focus_window,
+ x, y,
+ 0,
+ event->xkey.time);
+ }
+}
+
+static MetaGrabOp
+tab_op_from_tab_type (MetaTabList type)
+{
+ switch (type)
+ {
+ case META_TAB_LIST_NORMAL:
+ return META_GRAB_OP_KEYBOARD_TABBING_NORMAL;
+ case META_TAB_LIST_DOCKS:
+ return META_GRAB_OP_KEYBOARD_TABBING_DOCK;
+ case META_TAB_LIST_GROUP:
+ return META_GRAB_OP_KEYBOARD_TABBING_GROUP;
+ }
+
+ g_assert_not_reached ();
+
+ return 0;
+}
+
+static MetaGrabOp
+cycle_op_from_tab_type (MetaTabList type)
+{
+ switch (type)
+ {
+ case META_TAB_LIST_NORMAL:
+ return META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL;
+ case META_TAB_LIST_DOCKS:
+ return META_GRAB_OP_KEYBOARD_ESCAPING_DOCK;
+ case META_TAB_LIST_GROUP:
+ return META_GRAB_OP_KEYBOARD_ESCAPING_GROUP;
+ }
+
+ g_assert_not_reached ();
+
+ return 0;
+}
+
+static void
+do_choose_window (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *event_window,
+ XEvent *event,
+ MetaKeyBinding *binding,
+ gboolean backward,
+ gboolean show_popup)
+{
+ MetaTabList type = binding->handler->data;
+ MetaWindow *initial_selection;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Tab list = %u show_popup = %d\n", type, show_popup);
+
+ /* reverse direction if shift is down */
+ if (event->xkey.state & ShiftMask)
+ backward = !backward;
+
+ initial_selection = meta_display_get_tab_next (display,
+ type,
+ screen,
+ screen->active_workspace,
+ NULL,
+ backward);
+
+ /* Note that focus_window may not be in the tab chain, but it's OK */
+ if (initial_selection == NULL)
+ initial_selection = meta_display_get_tab_current (display,
+ type, screen,
+ screen->active_workspace);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Initially selecting window %s\n",
+ initial_selection ? initial_selection->desc : "(none)");
+
+ if (initial_selection != NULL)
+ {
+ if (binding->mask == 0)
+ {
+ /* If no modifiers, we can't do the "hold down modifier to keep
+ * moving" thing, so we just instaswitch by one window.
+ */
+ meta_topic (META_DEBUG_FOCUS,
+ "Activating %s and turning off mouse_mode due to "
+ "switch/cycle windows with no modifiers\n",
+ initial_selection->desc);
+ display->mouse_mode = FALSE;
+ meta_window_activate (initial_selection, event->xkey.time);
+ }
+ else if (meta_display_begin_grab_op (display,
+ screen,
+ NULL,
+ show_popup ?
+ tab_op_from_tab_type (type) :
+ cycle_op_from_tab_type (type),
+ FALSE,
+ FALSE,
+ 0,
+ binding->mask,
+ event->xkey.time,
+ 0, 0))
+ {
+ if (!primary_modifier_still_pressed (display,
+ binding->mask))
+ {
+ /* This handles a race where modifier might be released
+ * before we establish the grab. must end grab
+ * prior to trying to focus a window.
+ */
+ meta_topic (META_DEBUG_FOCUS,
+ "Ending grab, activating %s, and turning off "
+ "mouse_mode due to switch/cycle windows where "
+ "modifier was released prior to grab\n",
+ initial_selection->desc);
+ meta_display_end_grab_op (display, event->xkey.time);
+ display->mouse_mode = FALSE;
+ meta_window_activate (initial_selection, event->xkey.time);
+ }
+ else
+ {
+ meta_ui_tab_popup_select (screen->tab_popup,
+ (MetaTabEntryKey) initial_selection->xwindow);
+
+ if (show_popup)
+ meta_ui_tab_popup_set_showing (screen->tab_popup,
+ TRUE);
+ else
+ {
+ meta_window_raise (initial_selection);
+ initial_selection->tab_unminimized =
+ initial_selection->minimized;
+ meta_window_unminimize (initial_selection);
+ }
+ }
+ }
+ }
+}
+
+static void
+handle_switch (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *event_window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ gint backwards = binding->handler->flags & BINDING_IS_REVERSED;
+
+ do_choose_window (display, screen, event_window, event, binding,
+ backwards, TRUE);
+}
+
+static void
+handle_cycle (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *event_window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ gint backwards = binding->handler->flags & BINDING_IS_REVERSED;
+
+ do_choose_window (display, screen, event_window, event, binding,
+ backwards, FALSE);
+}
+
+
+static void
+handle_toggle_fullscreen (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->fullscreen)
+ meta_window_unmake_fullscreen (window);
+ else if (window->has_fullscreen_func)
+ meta_window_make_fullscreen (window);
+}
+
+static void
+handle_toggle_above (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->wm_state_above)
+ meta_window_unmake_above (window);
+ else
+ meta_window_make_above (window);
+}
+
+static void
+handle_toggle_maximized (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (META_WINDOW_MAXIMIZED (window))
+ meta_window_unmaximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+ else if (window->has_maximize_func)
+ meta_window_maximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+}
+
+static void
+handle_maximize (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->has_maximize_func)
+ meta_window_maximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+}
+
+static void
+handle_unmaximize (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->maximized_vertically || window->maximized_horizontally)
+ meta_window_unmaximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+}
+
+static void
+handle_toggle_shaded (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->shaded)
+ meta_window_unshade (window, event->xkey.time);
+ else if (window->has_shade_func)
+ meta_window_shade (window, event->xkey.time);
+}
+
+static void
+handle_close (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->has_close_func)
+ meta_window_delete (window, event->xkey.time);
+}
+
+static void
+handle_minimize (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->has_minimize_func)
+ meta_window_minimize (window);
+}
+
+static void
+handle_begin_move (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->has_move_func)
+ {
+ meta_window_begin_grab_op (window,
+ META_GRAB_OP_KEYBOARD_MOVING,
+ FALSE,
+ event->xkey.time);
+ }
+}
+
+static void
+handle_begin_resize (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->has_resize_func)
+ {
+ meta_window_begin_grab_op (window,
+ META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN,
+ FALSE,
+ event->xkey.time);
+ }
+}
+
+static void
+handle_toggle_on_all_workspaces (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ if (window->on_all_workspaces)
+ meta_window_unstick (window);
+ else
+ meta_window_stick (window);
+}
+
+static void
+handle_move_to_workspace (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ gint which = binding->handler->data;
+ gboolean flip = (which < 0);
+ MetaWorkspace *workspace;
+
+ /* If which is zero or positive, it's a workspace number, and the window
+ * should move to the workspace with that number.
+ *
+ * However, if it's negative, it's a direction with respect to the current
+ * position; it's expressed as a member of the MetaMotionDirection enum,
+ * all of whose members are negative. Such a change is called a flip.
+ */
+
+ if (window->always_sticky)
+ return;
+
+ workspace = NULL;
+ if (flip)
+ {
+ workspace = meta_workspace_get_neighbor (screen->active_workspace,
+ which);
+ }
+ else
+ {
+ workspace = meta_screen_get_workspace_by_index (screen, which);
+ }
+
+ if (workspace)
+ {
+ /* Activate second, so the window is never unmapped */
+ meta_window_change_workspace (window, workspace);
+ if (flip)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Resetting mouse_mode to FALSE due to "
+ "handle_move_to_workspace() call with flip set.\n");
+ workspace->screen->display->mouse_mode = FALSE;
+ meta_workspace_activate_with_focus (workspace,
+ window,
+ event->xkey.time);
+ }
+ }
+ else
+ {
+ /* We could offer to create it I suppose */
+ }
+}
+
+static void
+handle_raise_or_lower (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ /* Get window at pointer */
+
+ MetaWindow *above = NULL;
+
+ /* Check if top */
+ if (meta_stack_get_top (window->screen->stack) == window)
+ {
+ meta_window_lower (window);
+ return;
+ }
+
+ /* else check if windows in same layer are intersecting it */
+
+ above = meta_stack_get_above (window->screen->stack, window, TRUE);
+
+ while (above)
+ {
+ MetaRectangle tmp, win_rect, above_rect;
+
+ if (above->mapped)
+ {
+ meta_window_get_outer_rect (window, &win_rect);
+ meta_window_get_outer_rect (above, &above_rect);
+
+ /* Check if obscured */
+ if (meta_rectangle_intersect (&win_rect, &above_rect, &tmp))
+ {
+ meta_window_raise (window);
+ return;
+ }
+ }
+
+ above = meta_stack_get_above (window->screen->stack, above, TRUE);
+ }
+
+ /* window is not obscured */
+ meta_window_lower (window);
+}
+
+static void
+handle_raise (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ meta_window_raise (window);
+}
+
+static void
+handle_lower (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ meta_window_lower (window);
+}
+
+static void
+handle_workspace_switch (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ gint motion = binding->handler->data;
+ unsigned int grab_mask;
+
+ g_assert (motion < 0);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Starting tab between workspaces, showing popup\n");
+
+ /* FIXME should we use binding->mask ? */
+ grab_mask = event->xkey.state & ~(display->ignored_modifier_mask);
+
+ if (meta_display_begin_grab_op (display,
+ screen,
+ NULL,
+ META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING,
+ FALSE,
+ FALSE,
+ 0,
+ grab_mask,
+ event->xkey.time,
+ 0, 0))
+ {
+ MetaWorkspace *next;
+ gboolean grabbed_before_release;
+
+ next = meta_workspace_get_neighbor (screen->active_workspace, motion);
+ g_assert (next);
+
+ grabbed_before_release = primary_modifier_still_pressed (display, grab_mask);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Activating target workspace\n");
+
+ if (!grabbed_before_release)
+ {
+ /* end the grab right away, modifier possibly released
+ * before we could establish the grab and receive the
+ * release event. Must end grab before we can switch
+ * spaces.
+ */
+ meta_display_end_grab_op (display, event->xkey.time);
+ }
+
+ meta_workspace_activate (next, event->xkey.time);
+
+ if (grabbed_before_release)
+ {
+ meta_ui_tab_popup_select (screen->tab_popup, (MetaTabEntryKey) next);
+
+ /* only after selecting proper space */
+ meta_ui_tab_popup_set_showing (screen->tab_popup, TRUE);
+ }
+ }
+}
+
+static void
+handle_set_spew_mark (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ meta_verbose ("-- MARK MARK MARK MARK --\n");
+}
+
+void
+meta_set_keybindings_disabled (gboolean setting)
+{
+ all_bindings_disabled = setting;
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Keybindings %s\n", all_bindings_disabled ? "disabled" : "enabled");
+}
+
+static void
+handle_run_terminal (MetaDisplay *display,
+ MetaScreen *screen,
+ MetaWindow *window,
+ XEvent *event,
+ MetaKeyBinding *binding)
+{
+ const char *command;
+ GError *err;
+
+ command = meta_prefs_get_terminal_command ();
+
+ if (command == NULL)
+ {
+ char *s;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "No terminal command to run in response to "
+ "keybinding press\n");
+
+ s = g_strdup_printf (_("No terminal command has been defined.\n"));
+ error_on_command (-1, NULL, s, screen->number, event->xkey.time);
+ g_free (s);
+
+ return;
+ }
+
+ err = NULL;
+ if (!meta_spawn_command_line_async_on_screen (command, screen, &err))
+ {
+ error_on_command (-1, command, err->message, screen->number,
+ event->xkey.time);
+
+ g_error_free (err);
+ }
+}
diff --git a/src/core/keybindings.h b/src/core/keybindings.h
new file mode 100644
index 00000000..618520b4
--- /dev/null
+++ b/src/core/keybindings.h
@@ -0,0 +1,60 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file keybindings.h Grab and ungrab keys, and process the key events
+ *
+ * Performs global X grabs on the keys we need to be told about, like
+ * the one to close a window. It also deals with incoming key events.
+ */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_KEYBINDINGS_H
+#define META_KEYBINDINGS_H
+
+#include "display-private.h"
+#include "window.h"
+
+void meta_display_init_keys (MetaDisplay *display);
+void meta_display_shutdown_keys (MetaDisplay *display);
+void meta_screen_grab_keys (MetaScreen *screen);
+void meta_screen_ungrab_keys (MetaScreen *screen);
+gboolean meta_screen_grab_all_keys (MetaScreen *screen,
+ guint32 timestamp);
+void meta_screen_ungrab_all_keys (MetaScreen *screen,
+ guint32 timestamp);
+void meta_window_grab_keys (MetaWindow *window);
+void meta_window_ungrab_keys (MetaWindow *window);
+gboolean meta_window_grab_all_keys (MetaWindow *window,
+ guint32 timestamp);
+void meta_window_ungrab_all_keys (MetaWindow *window,
+ guint32 timestamp);
+void meta_display_process_key_event (MetaDisplay *display,
+ MetaWindow *window,
+ XEvent *event);
+void meta_set_keybindings_disabled (gboolean setting);
+void meta_display_process_mapping_event (MetaDisplay *display,
+ XEvent *event);
+
+#endif
+
+
+
+
diff --git a/src/core/main.c b/src/core/main.c
new file mode 100644
index 00000000..38c69b0a
--- /dev/null
+++ b/src/core/main.c
@@ -0,0 +1,673 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco main() */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/**
+ * \file
+ * Program startup.
+ * Functions which parse the command-line arguments, create the display,
+ * kick everything off and then close down Marco when it's time to go.
+ */
+
+/**
+ * \mainpage
+ * Marco - a boring window manager for the adult in you
+ *
+ * Many window managers are like Marshmallow Froot Loops; Marco
+ * is like Cheerios.
+ *
+ * The best way to get a handle on how the whole system fits together
+ * is discussed in doc/code-overview.txt; if you're looking for functions
+ * to investigate, read main(), meta_display_open(), and event_callback().
+ */
+
+#define _GNU_SOURCE
+#define _SVID_SOURCE /* for putenv() and some signal-related functions */
+
+#include <config.h>
+#include "main.h"
+#include "util.h"
+#include "display-private.h"
+#include "errors.h"
+#include "ui.h"
+#include "session.h"
+#include "prefs.h"
+
+#include <glib-object.h>
+#include <glib/gprintf.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <wait.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <time.h>
+#include <unistd.h>
+
+/**
+ * The exit code we'll return to our parent process when we eventually die.
+ */
+static MetaExitCode meta_exit_code = META_EXIT_SUCCESS;
+
+/**
+ * Handle on the main loop, so that we have an easy way of shutting Marco
+ * down.
+ */
+static GMainLoop *meta_main_loop = NULL;
+
+/**
+ * If set, Marco will spawn an identical copy of itself immediately
+ * before quitting.
+ */
+static gboolean meta_restart_after_quit = FALSE;
+
+static void prefs_changed_callback (MetaPreference pref,
+ gpointer data);
+
+/**
+ * Prints log messages. If Marco was compiled with backtrace support,
+ * also prints a backtrace (see meta_print_backtrace()).
+ *
+ * \param log_domain the domain the error occurred in (we ignore this)
+ * \param log_level the log level so that we can filter out less
+ * important messages
+ * \param message the message to log
+ * \param user_data arbitrary data (we ignore this)
+ */
+static void
+log_handler (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ meta_warning ("Log level %d: %s\n", log_level, message);
+ meta_print_backtrace ();
+}
+
+/**
+ * Prints the version notice. This is shown when Marco is called
+ * with the --version switch.
+ */
+static void
+version (void)
+{
+ const int latest_year = 2009;
+ char yearbuffer[256];
+ GDate date;
+
+ /* this is all so the string to translate stays constant.
+ * see how much we love the translators.
+ */
+ g_date_set_dmy (&date, 1, G_DATE_JANUARY, latest_year);
+ if (g_date_strftime (yearbuffer, sizeof (yearbuffer), "%Y", &date)==0)
+ /* didn't work? fall back to decimal representation */
+ g_sprintf (yearbuffer, "%d", latest_year);
+
+ g_print (_("marco %s\n"
+ "Copyright (C) 2001-%s Havoc Pennington, Red Hat, Inc., and others\n"
+ "This is free software; see the source for copying conditions.\n"
+ "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"),
+ VERSION, yearbuffer);
+ exit (0);
+}
+
+/**
+ * Prints a list of which configure script options were used to
+ * build this copy of Marco. This is actually always called
+ * on startup, but it's all no-op unless we're in verbose mode
+ * (see meta_set_verbose).
+ */
+static void
+meta_print_compilation_info (void)
+{
+#ifdef HAVE_SHAPE
+ meta_verbose ("Compiled with shape extension\n");
+#else
+ meta_verbose ("Compiled without shape extension\n");
+#endif
+#ifdef HAVE_XINERAMA
+ meta_topic (META_DEBUG_XINERAMA, "Compiled with Xinerama extension\n");
+#else
+ meta_topic (META_DEBUG_XINERAMA, "Compiled without Xinerama extension\n");
+#endif
+#ifdef HAVE_XFREE_XINERAMA
+ meta_topic (META_DEBUG_XINERAMA, " (using XFree86 Xinerama)\n");
+#else
+ meta_topic (META_DEBUG_XINERAMA, " (not using XFree86 Xinerama)\n");
+#endif
+#ifdef HAVE_SOLARIS_XINERAMA
+ meta_topic (META_DEBUG_XINERAMA, " (using Solaris Xinerama)\n");
+#else
+ meta_topic (META_DEBUG_XINERAMA, " (not using Solaris Xinerama)\n");
+#endif
+#ifdef HAVE_XSYNC
+ meta_verbose ("Compiled with sync extension\n");
+#else
+ meta_verbose ("Compiled without sync extension\n");
+#endif
+#ifdef HAVE_RANDR
+ meta_verbose ("Compiled with randr extension\n");
+#else
+ meta_verbose ("Compiled without randr extension\n");
+#endif
+#ifdef HAVE_STARTUP_NOTIFICATION
+ meta_verbose ("Compiled with startup notification\n");
+#else
+ meta_verbose ("Compiled without startup notification\n");
+#endif
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ meta_verbose ("Compiled with composite extensions\n");
+#else
+ meta_verbose ("Compiled without composite extensions\n");
+#endif
+}
+
+/**
+ * Prints the version number, the current timestamp (not the
+ * build date), the locale, the character encoding, and a list
+ * of configure script options that were used to build this
+ * copy of Marco. This is actually always called
+ * on startup, but it's all no-op unless we're in verbose mode
+ * (see meta_set_verbose).
+ */
+static void
+meta_print_self_identity (void)
+{
+ char buf[256];
+ GDate d;
+ const char *charset;
+
+ /* Version and current date. */
+ g_date_clear (&d, 1);
+ g_date_set_time_t (&d, time (NULL));
+ g_date_strftime (buf, sizeof (buf), "%x", &d);
+ meta_verbose ("Marco version %s running on %s\n",
+ VERSION, buf);
+
+ /* Locale and encoding. */
+ g_get_charset (&charset);
+ meta_verbose ("Running in locale \"%s\" with encoding \"%s\"\n",
+ setlocale (LC_ALL, NULL), charset);
+
+ /* Compilation settings. */
+ meta_print_compilation_info ();
+}
+
+/**
+ * The set of possible options that can be set on Marco's
+ * command line. This type exists so that meta_parse_options() can
+ * write to an instance of it.
+ */
+typedef struct
+{
+ gchar *save_file;
+ gchar *display_name;
+ gchar *client_id;
+ gboolean replace_wm;
+ gboolean disable_sm;
+ gboolean print_version;
+ gboolean sync;
+ gboolean composite;
+ gboolean no_composite;
+ gboolean no_force_fullscreen;
+} MetaArguments;
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+#define COMPOSITE_OPTS_FLAGS 0
+#else /* HAVE_COMPOSITE_EXTENSIONS */
+/* No compositor, so don't show the arguments in --help */
+#define COMPOSITE_OPTS_FLAGS G_OPTION_FLAG_HIDDEN
+#endif /* HAVE_COMPOSITE_EXTENSIONS */
+
+/**
+ * Parses argc and argv and returns the
+ * arguments that Marco understands in meta_args.
+ *
+ * The strange call signature has to be written like it is so
+ * that g_option_context_parse() gets a chance to modify argc and
+ * argv.
+ *
+ * \param argc Pointer to the number of arguments Marco was given
+ * \param argv Pointer to the array of arguments Marco was given
+ * \param meta_args The result of parsing the arguments.
+ **/
+static void
+meta_parse_options (int *argc, char ***argv,
+ MetaArguments *meta_args)
+{
+ MetaArguments my_args = {NULL, NULL, NULL,
+ FALSE, FALSE, FALSE, FALSE, FALSE};
+ GOptionEntry options[] = {
+ {
+ "sm-disable", 0, 0, G_OPTION_ARG_NONE,
+ &my_args.disable_sm,
+ N_("Disable connection to session manager"),
+ NULL
+ },
+ {
+ "replace", 0, 0, G_OPTION_ARG_NONE,
+ &my_args.replace_wm,
+ N_("Replace the running window manager with Marco"),
+ NULL
+ },
+ {
+ "sm-client-id", 0, 0, G_OPTION_ARG_STRING,
+ &my_args.client_id,
+ N_("Specify session management ID"),
+ "ID"
+ },
+ {
+ "display", 'd', 0, G_OPTION_ARG_STRING,
+ &my_args.display_name, N_("X Display to use"),
+ "DISPLAY"
+ },
+ {
+ "sm-save-file", 0, 0, G_OPTION_ARG_FILENAME,
+ &my_args.save_file,
+ N_("Initialize session from savefile"),
+ "FILE"
+ },
+ {
+ "version", 0, 0, G_OPTION_ARG_NONE,
+ &my_args.print_version,
+ N_("Print version"),
+ NULL
+ },
+ {
+ "sync", 0, 0, G_OPTION_ARG_NONE,
+ &my_args.sync,
+ N_("Make X calls synchronous"),
+ NULL
+ },
+ {
+ "composite", 'c', COMPOSITE_OPTS_FLAGS, G_OPTION_ARG_NONE,
+ &my_args.composite,
+ N_("Turn compositing on"),
+ NULL
+ },
+ {
+ "no-composite", 0, COMPOSITE_OPTS_FLAGS, G_OPTION_ARG_NONE,
+ &my_args.no_composite,
+ N_("Turn compositing off"),
+ NULL
+ },
+ {
+ "no-force-fullscreen", 0, COMPOSITE_OPTS_FLAGS, G_OPTION_ARG_NONE,
+ &my_args.no_force_fullscreen,
+ N_("Don't make fullscreen windows that are maximized and have no decorations"),
+ NULL
+ },
+ {NULL}
+ };
+ GOptionContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_option_context_new (NULL);
+ g_option_context_add_main_entries (ctx, options, "marco");
+ if (!g_option_context_parse (ctx, argc, argv, &error))
+ {
+ g_print ("marco: %s\n", error->message);
+ exit(1);
+ }
+ g_option_context_free (ctx);
+ /* Return the parsed options through the meta_args param. */
+ *meta_args = my_args;
+}
+
+/**
+ * Selects which display Marco should use. It first tries to use
+ * display_name as the display. If display_name is NULL then
+ * try to use the environment variable MARCO_DISPLAY. If that
+ * also is NULL, use the default - :0.0
+ */
+static void
+meta_select_display (gchar *display_name)
+{
+ gchar *envVar = "";
+ if (display_name)
+ envVar = g_strconcat ("DISPLAY=", display_name, NULL);
+ else if (g_getenv ("MARCO_DISPLAY"))
+ envVar = g_strconcat ("DISPLAY=",
+ g_getenv ("MARCO_DISPLAY"), NULL);
+ /* DO NOT FREE envVar, putenv() sucks */
+ putenv (envVar);
+}
+
+static void
+meta_finalize (void)
+{
+ MetaDisplay *display = meta_get_display();
+
+ meta_session_shutdown ();
+
+ if (display)
+ meta_display_close (display,
+ CurrentTime); /* I doubt correct timestamps matter here */
+}
+
+static int sigterm_pipe_fds[2] = { -1, -1 };
+
+static void
+sigterm_handler (int signum)
+{
+ if (sigterm_pipe_fds[1] >= 0)
+ {
+ int dummy;
+
+ dummy = write (sigterm_pipe_fds[1], "", 1);
+ close (sigterm_pipe_fds[1]);
+ sigterm_pipe_fds[1] = -1;
+ }
+}
+
+static gboolean
+on_sigterm (void)
+{
+ meta_quit (META_EXIT_SUCCESS);
+ return FALSE;
+}
+
+/**
+ * This is where the story begins. It parses commandline options and
+ * environment variables, sets up the screen, hands control off to
+ * GTK, and cleans up afterwards.
+ *
+ * \param argc Number of arguments (as usual)
+ * \param argv Array of arguments (as usual)
+ *
+ * \bug It's a bit long. It would be good to split it out into separate
+ * functions.
+ */
+int
+main (int argc, char **argv)
+{
+ struct sigaction act;
+ sigset_t empty_mask;
+ MetaArguments meta_args;
+ const gchar *log_domains[] = {
+ NULL, G_LOG_DOMAIN, "Gtk", "Gdk", "GLib",
+ "Pango", "GLib-GObject", "GThread"
+ };
+ guint i;
+ GIOChannel *channel;
+
+ if (!g_thread_supported ())
+ g_thread_init (NULL);
+
+ if (setlocale (LC_ALL, "") == NULL)
+ meta_warning ("Locale not understood by C library, internationalization will not work\n");
+
+ g_type_init ();
+
+ sigemptyset (&empty_mask);
+ act.sa_handler = SIG_IGN;
+ act.sa_mask = empty_mask;
+ act.sa_flags = 0;
+ if (sigaction (SIGPIPE, &act, NULL) < 0)
+ g_printerr ("Failed to register SIGPIPE handler: %s\n",
+ g_strerror (errno));
+#ifdef SIGXFSZ
+ if (sigaction (SIGXFSZ, &act, NULL) < 0)
+ g_printerr ("Failed to register SIGXFSZ handler: %s\n",
+ g_strerror (errno));
+#endif
+
+ if (pipe (sigterm_pipe_fds) != 0)
+ g_printerr ("Failed to create SIGTERM pipe: %s\n",
+ g_strerror (errno));
+
+ channel = g_io_channel_unix_new (sigterm_pipe_fds[0]);
+ g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL);
+ g_io_add_watch (channel, G_IO_IN, (GIOFunc) on_sigterm, NULL);
+ g_io_channel_set_close_on_unref (channel, TRUE);
+ g_io_channel_unref (channel);
+
+ act.sa_handler = &sigterm_handler;
+ if (sigaction (SIGTERM, &act, NULL) < 0)
+ g_printerr ("Failed to register SIGTERM handler: %s\n",
+ g_strerror (errno));
+
+ if (g_getenv ("MARCO_VERBOSE"))
+ meta_set_verbose (TRUE);
+ if (g_getenv ("MARCO_DEBUG"))
+ meta_set_debugging (TRUE);
+
+ if (g_get_home_dir ())
+ if (chdir (g_get_home_dir ()) < 0)
+ meta_warning ("Could not change to home directory %s.\n",
+ g_get_home_dir ());
+
+ meta_print_self_identity ();
+
+ bindtextdomain (GETTEXT_PACKAGE, MARCO_LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ /* Parse command line arguments.*/
+ meta_parse_options (&argc, &argv, &meta_args);
+
+ meta_set_syncing (meta_args.sync || (g_getenv ("MARCO_SYNC") != NULL));
+
+ if (meta_args.print_version)
+ version ();
+
+ meta_select_display (meta_args.display_name);
+
+ if (meta_args.replace_wm)
+ meta_set_replace_current_wm (TRUE);
+
+ if (meta_args.save_file && meta_args.client_id)
+ meta_fatal ("Can't specify both SM save file and SM client id\n");
+
+ meta_main_loop = g_main_loop_new (NULL, FALSE);
+
+ meta_ui_init (&argc, &argv);
+
+ /* must be after UI init so we can override GDK handlers */
+ meta_errors_init ();
+
+ /* Load prefs */
+ meta_prefs_init ();
+ meta_prefs_add_listener (prefs_changed_callback, NULL);
+
+
+#if 1
+
+ for (i=0; i<G_N_ELEMENTS(log_domains); i++)
+ g_log_set_handler (log_domains[i],
+ G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
+ log_handler, NULL);
+
+#endif
+
+ if (g_getenv ("MARCO_G_FATAL_WARNINGS") != NULL)
+ g_log_set_always_fatal (G_LOG_LEVEL_MASK);
+
+ meta_ui_set_current_theme (meta_prefs_get_theme (), FALSE);
+
+ /* Try to find some theme that'll work if the theme preference
+ * doesn't exist. First try Simple (the default theme) then just
+ * try anything in the themes directory.
+ */
+ if (!meta_ui_have_a_theme ())
+ meta_ui_set_current_theme ("Simple", FALSE);
+
+ if (!meta_ui_have_a_theme ())
+ {
+ const char *dir_entry = NULL;
+ GError *err = NULL;
+ GDir *themes_dir = NULL;
+
+ if (!(themes_dir = g_dir_open (MARCO_DATADIR"/themes", 0, &err)))
+ {
+ meta_fatal (_("Failed to scan themes directory: %s\n"), err->message);
+ g_error_free (err);
+ }
+ else
+ {
+ while (((dir_entry = g_dir_read_name (themes_dir)) != NULL) &&
+ (!meta_ui_have_a_theme ()))
+ {
+ meta_ui_set_current_theme (dir_entry, FALSE);
+ }
+
+ g_dir_close (themes_dir);
+ }
+ }
+
+ if (!meta_ui_have_a_theme ())
+ meta_fatal (_("Could not find a theme! Be sure %s exists and contains the usual themes.\n"),
+ MARCO_DATADIR"/themes");
+
+ /* Connect to SM as late as possible - but before managing display,
+ * or we might try to manage a window before we have the session
+ * info
+ */
+ if (!meta_args.disable_sm)
+ {
+ if (meta_args.client_id == NULL)
+ {
+ const gchar *desktop_autostart_id;
+
+ desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");
+
+ if (desktop_autostart_id != NULL)
+ meta_args.client_id = g_strdup (desktop_autostart_id);
+ }
+
+ /* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to
+ * use the same client id. */
+ g_unsetenv ("DESKTOP_AUTOSTART_ID");
+
+ meta_session_init (meta_args.client_id, meta_args.save_file);
+ }
+ /* Free memory possibly allocated by the argument parsing which are
+ * no longer needed.
+ */
+ g_free (meta_args.save_file);
+ g_free (meta_args.display_name);
+ g_free (meta_args.client_id);
+
+ if (meta_args.composite || meta_args.no_composite)
+ meta_prefs_set_compositing_manager (meta_args.composite);
+
+ if (meta_args.no_force_fullscreen)
+ meta_prefs_set_force_fullscreen (FALSE);
+
+ if (!meta_display_open ())
+ meta_exit (META_EXIT_ERROR);
+
+ g_main_loop_run (meta_main_loop);
+
+ meta_finalize ();
+
+ if (meta_restart_after_quit)
+ {
+ GError *err;
+
+ err = NULL;
+ if (!g_spawn_async (NULL,
+ argv,
+ NULL,
+ G_SPAWN_SEARCH_PATH,
+ NULL,
+ NULL,
+ NULL,
+ &err))
+ {
+ meta_fatal (_("Failed to restart: %s\n"),
+ err->message);
+ g_error_free (err); /* not reached anyhow */
+ meta_exit_code = META_EXIT_ERROR;
+ }
+ }
+
+ return meta_exit_code;
+}
+
+/**
+ * Stops Marco. This tells the event loop to stop processing; it is rather
+ * dangerous to use this rather than meta_restart() because this will leave
+ * the user with no window manager. We generally do this only if, for example,
+ * the session manager asks us to; we assume the session manager knows what
+ * it's talking about.
+ *
+ * \param code The success or failure code to return to the calling process.
+ */
+void
+meta_quit (MetaExitCode code)
+{
+ meta_exit_code = code;
+
+ if (g_main_loop_is_running (meta_main_loop))
+ g_main_loop_quit (meta_main_loop);
+}
+
+/**
+ * Restarts Marco. In practice, this tells the event loop to stop
+ * processing, having first set the meta_restart_after_quit flag which
+ * tells Marco to spawn an identical copy of itself before quitting.
+ * This happens on receipt of a _MARCO_RESTART_MESSAGE client event.
+ */
+void
+meta_restart (void)
+{
+ meta_restart_after_quit = TRUE;
+ meta_quit (META_EXIT_SUCCESS);
+}
+
+/**
+ * Called on pref changes. (One of several functions of its kind and purpose.)
+ *
+ * \bug Why are these particular prefs handled in main.c and not others?
+ * Should they be?
+ *
+ * \param pref Which preference has changed
+ * \param data Arbitrary data (which we ignore)
+ */
+static void
+prefs_changed_callback (MetaPreference pref,
+ gpointer data)
+{
+ switch (pref)
+ {
+ case META_PREF_THEME:
+ meta_ui_set_current_theme (meta_prefs_get_theme (), FALSE);
+ meta_display_retheme_all ();
+ break;
+
+ case META_PREF_CURSOR_THEME:
+ case META_PREF_CURSOR_SIZE:
+ meta_display_set_cursor_theme (meta_prefs_get_cursor_theme (),
+ meta_prefs_get_cursor_size ());
+ break;
+ default:
+ /* handled elsewhere or otherwise */
+ break;
+ }
+}
diff --git a/src/core/marco-Xatomtype.h b/src/core/marco-Xatomtype.h
new file mode 100644
index 00000000..5698ed31
--- /dev/null
+++ b/src/core/marco-Xatomtype.h
@@ -0,0 +1,136 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file marco-Xatomtype.h Types for communicating with X about properties
+ *
+ * This files defines crock C structures for calling XGetWindowProperty and
+ * XChangeProperty. All fields must be longs as the semantics of property
+ * routines will handle conversion to and from actual 32 bit objects. If your
+ * compiler doesn't treat &structoflongs the same as &arrayoflongs[0], you
+ * will have some work to do.
+ */
+
+/***********************************************************
+
+Copyright 1987, 1998 The Open Group
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of The Open Group shall not be
+used in advertising or otherwise to promote the sale, use or other dealings
+in this Software without prior written authorization from The Open Group.
+
+
+Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts.
+
+ All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of Digital not be
+used in advertising or publicity pertaining to distribution of the
+software without specific, written prior permission.
+
+DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
+DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+
+******************************************************************/
+
+#ifndef _XATOMTYPE_H_
+#define _XATOMTYPE_H_
+
+#define BOOL long
+#define SIGNEDINT long
+#define UNSIGNEDINT unsigned long
+#define RESOURCEID unsigned long
+
+
+/* this structure may be extended, but do not change the order */
+typedef struct {
+ UNSIGNEDINT flags;
+ SIGNEDINT x, y, width, height; /* need to cvt; only for pre-ICCCM */
+ SIGNEDINT minWidth, minHeight; /* need to cvt */
+ SIGNEDINT maxWidth, maxHeight; /* need to cvt */
+ SIGNEDINT widthInc, heightInc; /* need to cvt */
+ SIGNEDINT minAspectX, minAspectY; /* need to cvt */
+ SIGNEDINT maxAspectX, maxAspectY; /* need to cvt */
+ SIGNEDINT baseWidth,baseHeight; /* need to cvt; ICCCM version 1 */
+ SIGNEDINT winGravity; /* need to cvt; ICCCM version 1 */
+} xPropSizeHints;
+#define OldNumPropSizeElements 15 /* pre-ICCCM */
+#define NumPropSizeElements 18 /* ICCCM version 1 */
+
+/* this structure may be extended, but do not change the order */
+/* RGB properties */
+typedef struct {
+ RESOURCEID colormap;
+ UNSIGNEDINT red_max;
+ UNSIGNEDINT red_mult;
+ UNSIGNEDINT green_max;
+ UNSIGNEDINT green_mult;
+ UNSIGNEDINT blue_max;
+ UNSIGNEDINT blue_mult;
+ UNSIGNEDINT base_pixel;
+ RESOURCEID visualid; /* ICCCM version 1 */
+ RESOURCEID killid; /* ICCCM version 1 */
+} xPropStandardColormap;
+#define OldNumPropStandardColormapElements 8 /* pre-ICCCM */
+#define NumPropStandardColormapElements 10 /* ICCCM version 1 */
+
+
+/* this structure may be extended, but do not change the order */
+typedef struct {
+ UNSIGNEDINT flags;
+ BOOL input; /* need to convert */
+ SIGNEDINT initialState; /* need to cvt */
+ RESOURCEID iconPixmap;
+ RESOURCEID iconWindow;
+ SIGNEDINT iconX; /* need to cvt */
+ SIGNEDINT iconY; /* need to cvt */
+ RESOURCEID iconMask;
+ UNSIGNEDINT windowGroup;
+ } xPropWMHints;
+#define NumPropWMHintsElements 9 /* number of elements in this structure */
+
+/* this structure defines the icon size hints information */
+typedef struct {
+ SIGNEDINT minWidth, minHeight; /* need to cvt */
+ SIGNEDINT maxWidth, maxHeight; /* need to cvt */
+ SIGNEDINT widthInc, heightInc; /* need to cvt */
+ } xPropIconSize;
+#define NumPropIconSizeElements 6 /* number of elements in this structure */
+
+/* this structure defines the window manager state information */
+typedef struct {
+ SIGNEDINT state; /* need to cvt */
+ RESOURCEID iconWindow;
+} xPropWMState;
+#define NumPropWMStateElements 2 /* number of elements in struct */
+
+#undef BOOL
+#undef SIGNEDINT
+#undef UNSIGNEDINT
+#undef RESOURCEID
+
+#endif /* _XATOMTYPE_H_ */
diff --git a/src/core/place.c b/src/core/place.c
new file mode 100644
index 00000000..cb1458f0
--- /dev/null
+++ b/src/core/place.c
@@ -0,0 +1,932 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco window placement */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2002, 2003 Red Hat, Inc.
+ * Copyright (C) 2003 Rob Adams
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include "place.h"
+#include "workspace.h"
+#include "prefs.h"
+#include <gdk/gdk.h>
+#include <math.h>
+#include <stdlib.h>
+
+typedef enum
+{
+ META_LEFT,
+ META_RIGHT,
+ META_TOP,
+ META_BOTTOM
+} MetaWindowDirection;
+
+static gint
+northwestcmp (gconstpointer a, gconstpointer b)
+{
+ MetaWindow *aw = (gpointer) a;
+ MetaWindow *bw = (gpointer) b;
+ int from_origin_a;
+ int from_origin_b;
+ int ax, ay, bx, by;
+
+ /* we're interested in the frame position for cascading,
+ * not meta_window_get_position()
+ */
+ if (aw->frame)
+ {
+ ax = aw->frame->rect.x;
+ ay = aw->frame->rect.y;
+ }
+ else
+ {
+ ax = aw->rect.x;
+ ay = aw->rect.y;
+ }
+
+ if (bw->frame)
+ {
+ bx = bw->frame->rect.x;
+ by = bw->frame->rect.y;
+ }
+ else
+ {
+ bx = bw->rect.x;
+ by = bw->rect.y;
+ }
+
+ /* probably there's a fast good-enough-guess we could use here. */
+ from_origin_a = sqrt (ax * ax + ay * ay);
+ from_origin_b = sqrt (bx * bx + by * by);
+
+ if (from_origin_a < from_origin_b)
+ return -1;
+ else if (from_origin_a > from_origin_b)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+find_next_cascade (MetaWindow *window,
+ MetaFrameGeometry *fgeom,
+ /* visible windows on relevant workspaces */
+ GList *windows,
+ int x,
+ int y,
+ int *new_x,
+ int *new_y)
+{
+ GList *tmp;
+ GList *sorted;
+ int cascade_x, cascade_y;
+ int x_threshold, y_threshold;
+ int window_width, window_height;
+ int cascade_stage;
+ MetaRectangle work_area;
+ const MetaXineramaScreenInfo* current;
+
+ sorted = g_list_copy (windows);
+ sorted = g_list_sort (sorted, northwestcmp);
+
+ /* This is a "fuzzy" cascade algorithm.
+ * For each window in the list, we find where we'd cascade a
+ * new window after it. If a window is already nearly at that
+ * position, we move on.
+ */
+
+ /* arbitrary-ish threshold, honors user attempts to
+ * manually cascade.
+ */
+#define CASCADE_FUZZ 15
+ if (fgeom)
+ {
+ x_threshold = MAX (fgeom->left_width, CASCADE_FUZZ);
+ y_threshold = MAX (fgeom->top_height, CASCADE_FUZZ);
+ }
+ else
+ {
+ x_threshold = CASCADE_FUZZ;
+ y_threshold = CASCADE_FUZZ;
+ }
+
+ /* Find furthest-SE origin of all workspaces.
+ * cascade_x, cascade_y are the target position
+ * of NW corner of window frame.
+ */
+
+ current = meta_screen_get_current_xinerama (window->screen);
+ meta_window_get_work_area_for_xinerama (window, current->number, &work_area);
+
+ cascade_x = MAX (0, work_area.x);
+ cascade_y = MAX (0, work_area.y);
+
+ /* Find first cascade position that's not used. */
+
+ window_width = window->frame ? window->frame->rect.width : window->rect.width;
+ window_height = window->frame ? window->frame->rect.height : window->rect.height;
+
+ cascade_stage = 0;
+ tmp = sorted;
+ while (tmp != NULL)
+ {
+ MetaWindow *w;
+ int wx, wy;
+
+ w = tmp->data;
+
+ /* we want frame position, not window position */
+ if (w->frame)
+ {
+ wx = w->frame->rect.x;
+ wy = w->frame->rect.y;
+ }
+ else
+ {
+ wx = w->rect.x;
+ wy = w->rect.y;
+ }
+
+ if (ABS (wx - cascade_x) < x_threshold &&
+ ABS (wy - cascade_y) < y_threshold)
+ {
+ /* This window is "in the way", move to next cascade
+ * point. The new window frame should go at the origin
+ * of the client window we're stacking above.
+ */
+ meta_window_get_position (w, &wx, &wy);
+ cascade_x = wx;
+ cascade_y = wy;
+
+ /* If we go off the screen, start over with a new cascade */
+ if (((cascade_x + window_width) >
+ (work_area.x + work_area.width)) ||
+ ((cascade_y + window_height) >
+ (work_area.y + work_area.height)))
+ {
+ cascade_x = MAX (0, work_area.x);
+ cascade_y = MAX (0, work_area.y);
+
+#define CASCADE_INTERVAL 50 /* space between top-left corners of cascades */
+ cascade_stage += 1;
+ cascade_x += CASCADE_INTERVAL * cascade_stage;
+
+ /* start over with a new cascade translated to the right, unless
+ * we are out of space
+ */
+ if ((cascade_x + window_width) <
+ (work_area.x + work_area.width))
+ {
+ tmp = sorted;
+ continue;
+ }
+ else
+ {
+ /* All out of space, this cascade_x won't work */
+ cascade_x = MAX (0, work_area.x);
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* Keep searching for a further-down-the-diagonal window. */
+ }
+
+ tmp = tmp->next;
+ }
+
+ /* cascade_x and cascade_y will match the last window in the list
+ * that was "in the way" (in the approximate cascade diagonal)
+ */
+
+ g_list_free (sorted);
+
+ /* Convert coords to position of window, not position of frame. */
+ if (fgeom == NULL)
+ {
+ *new_x = cascade_x;
+ *new_y = cascade_y;
+ }
+ else
+ {
+ *new_x = cascade_x + fgeom->left_width;
+ *new_y = cascade_y + fgeom->top_height;
+ }
+}
+
+static void
+find_most_freespace (MetaWindow *window,
+ MetaFrameGeometry *fgeom,
+ /* visible windows on relevant workspaces */
+ MetaWindow *focus_window,
+ int x,
+ int y,
+ int *new_x,
+ int *new_y)
+{
+ MetaWindowDirection side;
+ int max_area;
+ int max_width, max_height, left, right, top, bottom;
+ int left_space, right_space, top_space, bottom_space;
+ int frame_size_left, frame_size_top;
+ MetaRectangle work_area;
+ MetaRectangle avoid;
+ MetaRectangle outer;
+
+ frame_size_left = fgeom ? fgeom->left_width : 0;
+ frame_size_top = fgeom ? fgeom->top_height : 0;
+
+ meta_window_get_work_area_current_xinerama (focus_window, &work_area);
+ meta_window_get_outer_rect (focus_window, &avoid);
+ meta_window_get_outer_rect (window, &outer);
+
+ /* Find the areas of choosing the various sides of the focus window */
+ max_width = MIN (avoid.width, outer.width);
+ max_height = MIN (avoid.height, outer.height);
+ left_space = avoid.x - work_area.x;
+ right_space = work_area.width - (avoid.x + avoid.width - work_area.x);
+ top_space = avoid.y - work_area.y;
+ bottom_space = work_area.height - (avoid.y + avoid.height - work_area.y);
+ left = MIN (left_space, outer.width);
+ right = MIN (right_space, outer.width);
+ top = MIN (top_space, outer.height);
+ bottom = MIN (bottom_space, outer.height);
+
+ /* Find out which side of the focus_window can show the most of the window */
+ side = META_LEFT;
+ max_area = left*max_height;
+ if (right*max_height > max_area)
+ {
+ side = META_RIGHT;
+ max_area = right*max_height;
+ }
+ if (top*max_width > max_area)
+ {
+ side = META_TOP;
+ max_area = top*max_width;
+ }
+ if (bottom*max_width > max_area)
+ {
+ side = META_BOTTOM;
+ max_area = bottom*max_width;
+ }
+
+ /* Give up if there's no where to put it (i.e. focus window is maximized) */
+ if (max_area == 0)
+ return;
+
+ /* Place the window on the relevant side; if the whole window fits,
+ * make it adjacent to the focus window; if not, make sure the
+ * window doesn't go off the edge of the screen.
+ */
+ switch (side)
+ {
+ case META_LEFT:
+ *new_y = avoid.y + frame_size_top;
+ if (left_space > outer.width)
+ *new_x = avoid.x - outer.width + frame_size_left;
+ else
+ *new_x = work_area.x + frame_size_left;
+ break;
+ case META_RIGHT:
+ *new_y = avoid.y + frame_size_top;
+ if (right_space > outer.width)
+ *new_x = avoid.x + avoid.width + frame_size_left;
+ else
+ *new_x = work_area.x + work_area.width - outer.width + frame_size_left;
+ break;
+ case META_TOP:
+ *new_x = avoid.x + frame_size_left;
+ if (top_space > outer.height)
+ *new_y = avoid.y - outer.height + frame_size_top;
+ else
+ *new_y = work_area.y + frame_size_top;
+ break;
+ case META_BOTTOM:
+ *new_x = avoid.x + frame_size_left;
+ if (bottom_space > outer.height)
+ *new_y = avoid.y + avoid.height + frame_size_top;
+ else
+ *new_y = work_area.y + work_area.height - outer.height + frame_size_top;
+ break;
+ }
+}
+
+static void
+avoid_being_obscured_as_second_modal_dialog (MetaWindow *window,
+ MetaFrameGeometry *fgeom,
+ int *x,
+ int *y)
+{
+ /* We can't center this dialog if it was denied focus and it
+ * overlaps with the focus window and this dialog is modal and this
+ * dialog is in the same app as the focus window (*phew*...please
+ * don't make me say that ten times fast). See bug 307875 comment 11
+ * and 12 for details, but basically it means this is probably a
+ * second modal dialog for some app while the focus window is the
+ * first modal dialog. We should probably make them simultaneously
+ * visible in general, but it becomes mandatory to do so due to
+ * buggy apps (e.g. those using gtk+ *sigh*) because in those cases
+ * this second modal dialog also happens to be modal to the first
+ * dialog in addition to the main window, while it has only let us
+ * know about the modal-to-the-main-window part.
+ */
+
+ MetaWindow *focus_window;
+ MetaRectangle overlap;
+
+ focus_window = window->display->focus_window;
+
+ if (window->denied_focus_and_not_transient &&
+ window->wm_state_modal && /* FIXME: Maybe do this for all transients? */
+ meta_window_same_application (window, focus_window) &&
+ meta_rectangle_intersect (&window->rect,
+ &focus_window->rect,
+ &overlap))
+ {
+ find_most_freespace (window, fgeom, focus_window, *x, *y, x, y);
+ meta_topic (META_DEBUG_PLACEMENT,
+ "Dialog window %s was denied focus but may be modal "
+ "to the focus window; had to move it to avoid the "
+ "focus window\n",
+ window->desc);
+ }
+}
+
+static gboolean
+rectangle_overlaps_some_window (MetaRectangle *rect,
+ GList *windows)
+{
+ GList *tmp;
+ MetaRectangle dest;
+
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *other = tmp->data;
+ MetaRectangle other_rect;
+
+ switch (other->type)
+ {
+ case META_WINDOW_DOCK:
+ case META_WINDOW_SPLASHSCREEN:
+ case META_WINDOW_DESKTOP:
+ case META_WINDOW_DIALOG:
+ case META_WINDOW_MODAL_DIALOG:
+ break;
+
+ case META_WINDOW_NORMAL:
+ case META_WINDOW_UTILITY:
+ case META_WINDOW_TOOLBAR:
+ case META_WINDOW_MENU:
+ meta_window_get_outer_rect (other, &other_rect);
+
+ if (meta_rectangle_intersect (rect, &other_rect, &dest))
+ return TRUE;
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+
+ return FALSE;
+}
+
+static gint
+leftmost_cmp (gconstpointer a, gconstpointer b)
+{
+ MetaWindow *aw = (gpointer) a;
+ MetaWindow *bw = (gpointer) b;
+ int ax, bx;
+
+ /* we're interested in the frame position for cascading,
+ * not meta_window_get_position()
+ */
+ if (aw->frame)
+ ax = aw->frame->rect.x;
+ else
+ ax = aw->rect.x;
+
+ if (bw->frame)
+ bx = bw->frame->rect.x;
+ else
+ bx = bw->rect.x;
+
+ if (ax < bx)
+ return -1;
+ else if (ax > bx)
+ return 1;
+ else
+ return 0;
+}
+
+static gint
+topmost_cmp (gconstpointer a, gconstpointer b)
+{
+ MetaWindow *aw = (gpointer) a;
+ MetaWindow *bw = (gpointer) b;
+ int ay, by;
+
+ /* we're interested in the frame position for cascading,
+ * not meta_window_get_position()
+ */
+ if (aw->frame)
+ ay = aw->frame->rect.y;
+ else
+ ay = aw->rect.y;
+
+ if (bw->frame)
+ by = bw->frame->rect.y;
+ else
+ by = bw->rect.y;
+
+ if (ay < by)
+ return -1;
+ else if (ay > by)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+center_tile_rect_in_area (MetaRectangle *rect,
+ MetaRectangle *work_area)
+{
+ int fluff;
+
+ /* The point here is to tile a window such that "extra"
+ * space is equal on either side (i.e. so a full screen
+ * of windows tiled this way would center the windows
+ * as a group)
+ */
+
+ fluff = (work_area->width % (rect->width+1)) / 2;
+ rect->x = work_area->x + fluff;
+ fluff = (work_area->height % (rect->height+1)) / 3;
+ rect->y = work_area->y + fluff;
+}
+
+/* Find the leftmost, then topmost, empty area on the workspace
+ * that can contain the new window.
+ *
+ * Cool feature to have: if we can't fit the current window size,
+ * try shrinking the window (within geometry constraints). But
+ * beware windows such as Emacs with no sane minimum size, we
+ * don't want to create a 1x1 Emacs.
+ */
+static gboolean
+find_first_fit (MetaWindow *window,
+ MetaFrameGeometry *fgeom,
+ /* visible windows on relevant workspaces */
+ GList *windows,
+ int xinerama,
+ int x,
+ int y,
+ int *new_x,
+ int *new_y)
+{
+ /* This algorithm is limited - it just brute-force tries
+ * to fit the window in a small number of locations that are aligned
+ * with existing windows. It tries to place the window on
+ * the bottom of each existing window, and then to the right
+ * of each existing window, aligned with the left/top of the
+ * existing window in each of those cases.
+ */
+ int retval;
+ GList *below_sorted;
+ GList *right_sorted;
+ GList *tmp;
+ MetaRectangle rect;
+ MetaRectangle work_area;
+
+ retval = FALSE;
+
+ /* Below each window */
+ below_sorted = g_list_copy (windows);
+ below_sorted = g_list_sort (below_sorted, leftmost_cmp);
+ below_sorted = g_list_sort (below_sorted, topmost_cmp);
+
+ /* To the right of each window */
+ right_sorted = g_list_copy (windows);
+ right_sorted = g_list_sort (right_sorted, topmost_cmp);
+ right_sorted = g_list_sort (right_sorted, leftmost_cmp);
+
+ rect.width = window->rect.width;
+ rect.height = window->rect.height;
+
+ if (fgeom)
+ {
+ rect.width += fgeom->left_width + fgeom->right_width;
+ rect.height += fgeom->top_height + fgeom->bottom_height;
+ }
+
+#ifdef WITH_VERBOSE_MODE
+ {
+ char xinerama_location_string[RECT_LENGTH];
+ meta_rectangle_to_string (&window->screen->xinerama_infos[xinerama].rect,
+ xinerama_location_string);
+ meta_topic (META_DEBUG_XINERAMA,
+ "Natural xinerama is %s\n",
+ xinerama_location_string);
+ }
+#endif
+
+ meta_window_get_work_area_for_xinerama (window, xinerama, &work_area);
+
+ center_tile_rect_in_area (&rect, &work_area);
+
+ if (meta_rectangle_contains_rect (&work_area, &rect) &&
+ !rectangle_overlaps_some_window (&rect, windows))
+ {
+ *new_x = rect.x;
+ *new_y = rect.y;
+ if (fgeom)
+ {
+ *new_x += fgeom->left_width;
+ *new_y += fgeom->top_height;
+ }
+
+ retval = TRUE;
+
+ goto out;
+ }
+
+ /* try below each window */
+ tmp = below_sorted;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+ MetaRectangle outer_rect;
+
+ meta_window_get_outer_rect (w, &outer_rect);
+
+ rect.x = outer_rect.x;
+ rect.y = outer_rect.y + outer_rect.height;
+
+ if (meta_rectangle_contains_rect (&work_area, &rect) &&
+ !rectangle_overlaps_some_window (&rect, below_sorted))
+ {
+ *new_x = rect.x;
+ *new_y = rect.y;
+ if (fgeom)
+ {
+ *new_x += fgeom->left_width;
+ *new_y += fgeom->top_height;
+ }
+
+ retval = TRUE;
+
+ goto out;
+ }
+
+ tmp = tmp->next;
+ }
+
+ /* try to the right of each window */
+ tmp = right_sorted;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+ MetaRectangle outer_rect;
+
+ meta_window_get_outer_rect (w, &outer_rect);
+
+ rect.x = outer_rect.x + outer_rect.width;
+ rect.y = outer_rect.y;
+
+ if (meta_rectangle_contains_rect (&work_area, &rect) &&
+ !rectangle_overlaps_some_window (&rect, right_sorted))
+ {
+ *new_x = rect.x;
+ *new_y = rect.y;
+ if (fgeom)
+ {
+ *new_x += fgeom->left_width;
+ *new_y += fgeom->top_height;
+ }
+
+ retval = TRUE;
+
+ goto out;
+ }
+
+ tmp = tmp->next;
+ }
+
+ out:
+
+ g_list_free (below_sorted);
+ g_list_free (right_sorted);
+ return retval;
+}
+
+void
+meta_window_place (MetaWindow *window,
+ MetaFrameGeometry *fgeom,
+ int x,
+ int y,
+ int *new_x,
+ int *new_y)
+{
+ GList *windows;
+ const MetaXineramaScreenInfo *xi;
+
+ /* frame member variables should NEVER be used in here, only
+ * MetaFrameGeometry. But remember fgeom == NULL
+ * for undecorated windows. Also, this function should
+ * NEVER have side effects other than computing the
+ * placement coordinates.
+ */
+
+ meta_topic (META_DEBUG_PLACEMENT, "Placing window %s\n", window->desc);
+
+ windows = NULL;
+
+ switch (window->type)
+ {
+ /* Run placement algorithm on these. */
+ case META_WINDOW_NORMAL:
+ case META_WINDOW_DIALOG:
+ case META_WINDOW_MODAL_DIALOG:
+ case META_WINDOW_SPLASHSCREEN:
+ break;
+
+ /* Assume the app knows best how to place these, no placement
+ * algorithm ever (other than "leave them as-is")
+ */
+ case META_WINDOW_DESKTOP:
+ case META_WINDOW_DOCK:
+ case META_WINDOW_TOOLBAR:
+ case META_WINDOW_MENU:
+ case META_WINDOW_UTILITY:
+ goto done_no_constraints;
+ }
+
+ if (meta_prefs_get_disable_workarounds ())
+ {
+ switch (window->type)
+ {
+ /* Only accept USPosition on normal windows because the app is full
+ * of shit claiming the user set -geometry for a dialog or dock
+ */
+ case META_WINDOW_NORMAL:
+ if (window->size_hints.flags & USPosition)
+ {
+ /* don't constrain with placement algorithm */
+ meta_topic (META_DEBUG_PLACEMENT,
+ "Honoring USPosition for %s instead of using placement algorithm\n", window->desc);
+
+ goto done;
+ }
+ break;
+
+ /* Ignore even USPosition on dialogs, splashscreen */
+ case META_WINDOW_DIALOG:
+ case META_WINDOW_MODAL_DIALOG:
+ case META_WINDOW_SPLASHSCREEN:
+ break;
+
+ /* Assume the app knows best how to place these. */
+ case META_WINDOW_DESKTOP:
+ case META_WINDOW_DOCK:
+ case META_WINDOW_TOOLBAR:
+ case META_WINDOW_MENU:
+ case META_WINDOW_UTILITY:
+ if (window->size_hints.flags & PPosition)
+ {
+ meta_topic (META_DEBUG_PLACEMENT,
+ "Not placing non-normal non-dialog window with PPosition set\n");
+ goto done_no_constraints;
+ }
+ break;
+ }
+ }
+ else
+ {
+ /* workarounds enabled */
+
+ if ((window->size_hints.flags & PPosition) ||
+ (window->size_hints.flags & USPosition))
+ {
+ meta_topic (META_DEBUG_PLACEMENT,
+ "Not placing window with PPosition or USPosition set\n");
+ avoid_being_obscured_as_second_modal_dialog (window, fgeom, &x, &y);
+ goto done_no_constraints;
+ }
+ }
+
+ if ((window->type == META_WINDOW_DIALOG ||
+ window->type == META_WINDOW_MODAL_DIALOG) &&
+ window->xtransient_for != None)
+ {
+ /* Center horizontally, at top of parent vertically */
+
+ MetaWindow *parent;
+
+ parent =
+ meta_display_lookup_x_window (window->display,
+ window->xtransient_for);
+
+ if (parent)
+ {
+ int w;
+
+ meta_window_get_position (parent, &x, &y);
+ w = parent->rect.width;
+
+ /* center of parent */
+ x = x + w / 2;
+ /* center of child over center of parent */
+ x -= window->rect.width / 2;
+
+ /* "visually" center window over parent, leaving twice as
+ * much space below as on top.
+ */
+ y += (parent->rect.height - window->rect.height)/3;
+
+ /* put top of child's frame, not top of child's client */
+ if (fgeom)
+ y += fgeom->top_height;
+
+ meta_topic (META_DEBUG_PLACEMENT, "Centered window %s over transient parent\n",
+ window->desc);
+
+ avoid_being_obscured_as_second_modal_dialog (window, fgeom, &x, &y);
+
+ goto done;
+ }
+ }
+
+ /* FIXME UTILITY with transient set should be stacked up
+ * on the sides of the parent window or something.
+ */
+
+ if (window->type == META_WINDOW_DIALOG ||
+ window->type == META_WINDOW_MODAL_DIALOG ||
+ window->type == META_WINDOW_SPLASHSCREEN)
+ {
+ /* Center on current xinerama (i.e. on current monitor) */
+ int w, h;
+
+ /* Warning, this function is a round trip! */
+ xi = meta_screen_get_current_xinerama (window->screen);
+
+ w = xi->rect.width;
+ h = xi->rect.height;
+
+ x = (w - window->rect.width) / 2;
+ y = (h - window->rect.height) / 2;
+
+ x += xi->rect.x;
+ y += xi->rect.y;
+
+ meta_topic (META_DEBUG_PLACEMENT, "Centered window %s on screen %d xinerama %d\n",
+ window->desc, window->screen->number, xi->number);
+
+ goto done_check_denied_focus;
+ }
+
+ /* Find windows that matter (not minimized, on same workspace
+ * as placed window, may be shaded - if shaded we pretend it isn't
+ * for placement purposes)
+ */
+ {
+ GSList *all_windows;
+ GSList *tmp;
+
+ all_windows = meta_display_list_windows (window->display);
+
+ tmp = all_windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ if (meta_window_showing_on_its_workspace (w) &&
+ w != window &&
+ (window->workspace == w->workspace ||
+ window->on_all_workspaces || w->on_all_workspaces))
+ windows = g_list_prepend (windows, w);
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (all_windows);
+ }
+
+ /* Warning, this is a round trip! */
+ xi = meta_screen_get_current_xinerama (window->screen);
+
+ /* "Origin" placement algorithm */
+ x = xi->rect.x;
+ y = xi->rect.y;
+
+ if (find_first_fit (window, fgeom, windows,
+ xi->number,
+ x, y, &x, &y))
+ goto done_check_denied_focus;
+
+ /* Maximize windows if they are too big for their work area (bit of
+ * a hack here). Assume undecorated windows probably don't intend to
+ * be maximized.
+ */
+ if (window->has_maximize_func && window->decorated &&
+ !window->fullscreen)
+ {
+ MetaRectangle workarea;
+ MetaRectangle outer;
+
+ meta_window_get_work_area_for_xinerama (window,
+ xi->number,
+ &workarea);
+ meta_window_get_outer_rect (window, &outer);
+
+ /* If the window is bigger than the screen, then automaximize. Do NOT
+ * auto-maximize the directions independently. See #419810.
+ */
+ if (outer.width >= workarea.width && outer.height >= workarea.height)
+ {
+ window->maximize_horizontally_after_placement = TRUE;
+ window->maximize_vertically_after_placement = TRUE;
+ }
+ }
+
+ /* If no placement has been done, revert to cascade to avoid
+ * fully overlapping window (e.g. starting multiple terminals)
+ * */
+ if (x == xi->rect.x && y == xi->rect.y)
+ find_next_cascade (window, fgeom, windows, x, y, &x, &y);
+
+ done_check_denied_focus:
+ /* If the window is being denied focus and isn't a transient of the
+ * focus window, we do NOT want it to overlap with the focus window
+ * if at all possible. This is guaranteed to only be called if the
+ * focus_window is non-NULL, and we try to avoid that window.
+ */
+ if (window->denied_focus_and_not_transient)
+ {
+ gboolean found_fit;
+ MetaWindow *focus_window;
+ MetaRectangle overlap;
+
+ focus_window = window->display->focus_window;
+ g_assert (focus_window != NULL);
+
+ /* No need to do anything if the window doesn't overlap at all */
+ found_fit = !meta_rectangle_intersect (&window->rect,
+ &focus_window->rect,
+ &overlap);
+
+ /* Try to do a first fit again, this time only taking into account the
+ * focus window.
+ */
+ if (!found_fit)
+ {
+ GList *focus_window_list;
+ focus_window_list = g_list_prepend (NULL, focus_window);
+
+ /* Reset x and y ("origin" placement algorithm) */
+ x = xi->rect.x;
+ y = xi->rect.y;
+
+ found_fit = find_first_fit (window, fgeom, focus_window_list,
+ xi->number,
+ x, y, &x, &y);
+ g_list_free (focus_window_list);
+ }
+
+ /* If that still didn't work, just place it where we can see as much
+ * as possible.
+ */
+ if (!found_fit)
+ find_most_freespace (window, fgeom, focus_window, x, y, &x, &y);
+ }
+
+ done:
+ g_list_free (windows);
+
+ done_no_constraints:
+
+ *new_x = x;
+ *new_y = y;
+}
diff --git a/src/core/place.h b/src/core/place.h
new file mode 100644
index 00000000..85b7cd3f
--- /dev/null
+++ b/src/core/place.h
@@ -0,0 +1,37 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco window placement */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_PLACE_H
+#define META_PLACE_H
+
+#include "window-private.h"
+#include "frame-private.h"
+
+void meta_window_place (MetaWindow *window,
+ MetaFrameGeometry *fgeom,
+ int x,
+ int y,
+ int *new_x,
+ int *new_y);
+
+#endif
diff --git a/src/core/prefs.c b/src/core/prefs.c
new file mode 100644
index 00000000..494d3da1
--- /dev/null
+++ b/src/core/prefs.c
@@ -0,0 +1,2794 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco preferences */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington, Copyright (C) 2002 Red Hat Inc.
+ * Copyright (C) 2006 Elijah Newren
+ * Copyright (C) 2008 Thomas Thurman
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "prefs.h"
+#include "ui.h"
+#include "util.h"
+#ifdef HAVE_MATECONF
+#include <mateconf/mateconf-client.h>
+#endif
+#include <string.h>
+#include <stdlib.h>
+
+#define MAX_REASONABLE_WORKSPACES 36
+
+#define MAX_COMMANDS (32 + NUM_EXTRA_COMMANDS)
+#define NUM_EXTRA_COMMANDS 2
+#define SCREENSHOT_COMMAND_IDX (MAX_COMMANDS - 2)
+#define WIN_SCREENSHOT_COMMAND_IDX (MAX_COMMANDS - 1)
+
+/* If you add a key, it needs updating in init() and in the mateconf
+ * notify listener and of course in the .schemas file.
+ *
+ * Keys which are handled by one of the unified handlers below are
+ * not given a name here, because the purpose of the unified handlers
+ * is that keys should be referred to exactly once.
+ */
+#define KEY_TITLEBAR_FONT "/apps/marco/general/titlebar_font"
+#define KEY_NUM_WORKSPACES "/apps/marco/general/num_workspaces"
+#define KEY_COMPOSITOR "/apps/marco/general/compositing_manager"
+#define KEY_MATE_ACCESSIBILITY "/desktop/mate/interface/accessibility"
+
+#define KEY_COMMAND_DIRECTORY "/apps/marco/keybinding_commands"
+#define KEY_COMMAND_PREFIX "/apps/marco/keybinding_commands/command_"
+
+#define KEY_TERMINAL_DIR "/desktop/mate/applications/terminal"
+#define KEY_TERMINAL_COMMAND KEY_TERMINAL_DIR "/exec"
+
+#define KEY_SCREEN_BINDINGS_PREFIX "/apps/marco/global_keybindings"
+#define KEY_WINDOW_BINDINGS_PREFIX "/apps/marco/window_keybindings"
+#define KEY_LIST_BINDINGS_SUFFIX "_list"
+
+#define KEY_WORKSPACE_NAME_DIRECTORY "/apps/marco/workspace_names"
+#define KEY_WORKSPACE_NAME_PREFIX "/apps/marco/workspace_names/name_"
+
+
+#ifdef HAVE_MATECONF
+static MateConfClient *default_client = NULL;
+static GList *changes = NULL;
+static guint changed_idle;
+static GList *listeners = NULL;
+#endif
+
+static gboolean use_system_font = FALSE;
+static PangoFontDescription *titlebar_font = NULL;
+static MetaVirtualModifier mouse_button_mods = Mod1Mask;
+static MetaFocusMode focus_mode = META_FOCUS_MODE_CLICK;
+static MetaFocusNewWindows focus_new_windows = META_FOCUS_NEW_WINDOWS_SMART;
+static gboolean raise_on_click = TRUE;
+static char* current_theme = NULL;
+static int num_workspaces = 4;
+static MetaActionTitlebar action_double_click_titlebar = META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE;
+static MetaActionTitlebar action_middle_click_titlebar = META_ACTION_TITLEBAR_LOWER;
+static MetaActionTitlebar action_right_click_titlebar = META_ACTION_TITLEBAR_MENU;
+static gboolean application_based = FALSE;
+static gboolean disable_workarounds = FALSE;
+static gboolean auto_raise = FALSE;
+static gboolean auto_raise_delay = 500;
+static gboolean provide_visual_bell = FALSE;
+static gboolean bell_is_audible = TRUE;
+static gboolean reduced_resources = FALSE;
+static gboolean mate_accessibility = FALSE;
+static gboolean mate_animations = TRUE;
+static char *cursor_theme = NULL;
+static int cursor_size = 24;
+static gboolean compositing_manager = FALSE;
+static gboolean resize_with_right_button = FALSE;
+static gboolean force_fullscreen = TRUE;
+
+static MetaVisualBellType visual_bell_type = META_VISUAL_BELL_FULLSCREEN_FLASH;
+static MetaButtonLayout button_layout;
+
+/* The screenshot commands are at the end */
+static char *commands[MAX_COMMANDS] = { NULL, };
+
+static char *terminal_command = NULL;
+
+static char *workspace_names[MAX_REASONABLE_WORKSPACES] = { NULL, };
+
+#ifdef HAVE_MATECONF
+static gboolean handle_preference_update_enum (const gchar *key, MateConfValue *value);
+
+static gboolean update_key_binding (const char *name,
+ const char *value);
+static gboolean find_and_update_list_binding (MetaKeyPref *bindings,
+ const char *name,
+ GSList *value);
+static gboolean update_key_list_binding (const char *name,
+ GSList *value);
+static gboolean update_command (const char *name,
+ const char *value);
+static gboolean update_workspace_name (const char *name,
+ const char *value);
+
+static void change_notify (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data);
+
+static char* mateconf_key_for_workspace_name (int i);
+
+static void queue_changed (MetaPreference pref);
+
+typedef enum
+ {
+ META_LIST_OF_STRINGS,
+ META_LIST_OF_MATECONFVALUE_STRINGS
+ } MetaStringListType;
+
+static gboolean update_list_binding (MetaKeyPref *binding,
+ GSList *value,
+ MetaStringListType type_of_value);
+
+static void cleanup_error (GError **error);
+static gboolean get_bool (const char *key, gboolean *val);
+static void maybe_give_disable_workarounds_warning (void);
+
+static void titlebar_handler (MetaPreference, const gchar*, gboolean*);
+static void theme_name_handler (MetaPreference, const gchar*, gboolean*);
+static void mouse_button_mods_handler (MetaPreference, const gchar*, gboolean*);
+static void button_layout_handler (MetaPreference, const gchar*, gboolean*);
+
+#endif /* HAVE_MATECONF */
+
+static gboolean update_binding (MetaKeyPref *binding,
+ const char *value);
+
+static void init_bindings (void);
+static void init_commands (void);
+static void init_workspace_names (void);
+
+#ifndef HAVE_MATECONF
+static void init_button_layout (void);
+#endif /* !HAVE_MATECONF */
+
+#ifdef HAVE_MATECONF
+
+typedef struct
+{
+ MetaPrefsChangedFunc func;
+ gpointer data;
+} MetaPrefsListener;
+
+static MateConfEnumStringPair symtab_focus_mode[] =
+ {
+ { META_FOCUS_MODE_CLICK, "click" },
+ { META_FOCUS_MODE_SLOPPY, "sloppy" },
+ { META_FOCUS_MODE_MOUSE, "mouse" },
+ { 0, NULL },
+ };
+
+static MateConfEnumStringPair symtab_focus_new_windows[] =
+ {
+ { META_FOCUS_NEW_WINDOWS_SMART, "smart" },
+ { META_FOCUS_NEW_WINDOWS_STRICT, "strict" },
+ { 0, NULL },
+ };
+
+static MateConfEnumStringPair symtab_visual_bell_type[] =
+ {
+ /* Note to the reader: 0 is an invalid value; these start at 1. */
+ { META_VISUAL_BELL_FULLSCREEN_FLASH, "fullscreen" },
+ { META_VISUAL_BELL_FRAME_FLASH, "frame_flash" },
+ { 0, NULL },
+ };
+
+static MateConfEnumStringPair symtab_titlebar_action[] =
+ {
+ { META_ACTION_TITLEBAR_TOGGLE_SHADE, "toggle_shade" },
+ { META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE, "toggle_maximize" },
+ { META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_HORIZONTALLY,
+ "toggle_maximize_horizontally" },
+ { META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_VERTICALLY,
+ "toggle_maximize_vertically" },
+ { META_ACTION_TITLEBAR_MINIMIZE, "minimize" },
+ { META_ACTION_TITLEBAR_NONE, "none" },
+ { META_ACTION_TITLEBAR_LOWER, "lower" },
+ { META_ACTION_TITLEBAR_MENU, "menu" },
+ { META_ACTION_TITLEBAR_TOGGLE_SHADE, "toggle_shade" },
+ { 0, NULL },
+ };
+
+/**
+ * The details of one preference which is constrained to be
+ * one of a small number of string values-- in other words,
+ * an enumeration.
+ *
+ * We could have done this other ways. One particularly attractive
+ * possibility would have been to represent the entire symbol table
+ * as a space-separated string literal in the list of symtabs, so
+ * the focus mode enums could have been represented simply by
+ * "click sloppy mouse". However, the simplicity gained would have
+ * been outweighed by the bugs caused when the ordering of the enum
+ * strings got out of sync with the actual enum statement. Also,
+ * there is existing library code to use this kind of symbol tables.
+ *
+ * Other things we might consider doing to clean this up in the
+ * future include:
+ *
+ * - most of the keys begin with the same prefix, and perhaps we
+ * could assume it if they don't start with a slash
+ *
+ * - there are several cases where a single identifier could be used
+ * to generate an entire entry, and perhaps this could be done
+ * with a macro. (This would reduce clarity, however, and is
+ * probably a bad thing.)
+ *
+ * - these types all begin with a gchar* (and contain a MetaPreference)
+ * and we can factor out the repeated code in the handlers by taking
+ * advantage of this using some kind of union arrangement.
+ */
+typedef struct
+{
+ gchar *key;
+ MetaPreference pref;
+ MateConfEnumStringPair *symtab;
+ gpointer target;
+} MetaEnumPreference;
+
+typedef struct
+{
+ gchar *key;
+ MetaPreference pref;
+ gboolean *target;
+ gboolean becomes_true_on_destruction;
+} MetaBoolPreference;
+
+typedef struct
+{
+ gchar *key;
+ MetaPreference pref;
+
+ /**
+ * A handler. Many of the string preferences aren't stored as
+ * strings and need parsing; others of them have default values
+ * which can't be solved in the general case. If you include a
+ * function pointer here, it will be called before the string
+ * value is written out to the target variable.
+ *
+ * The function is passed two arguments: the preference, and
+ * the new string as a gchar*. It returns a gboolean;
+ * only if this is true, the listeners will be informed that
+ * the preference has changed.
+ *
+ * This may be NULL. If it is, see "target", below.
+ */
+ void (*handler) (MetaPreference pref,
+ const gchar *string_value,
+ gboolean *inform_listeners);
+
+ /**
+ * Where to write the incoming string.
+ *
+ * This must be NULL if the handler is non-NULL.
+ * If the incoming string is NULL, no change will be made.
+ */
+ gchar **target;
+
+} MetaStringPreference;
+
+#define METAINTPREFERENCE_NO_CHANGE_ON_DESTROY G_MININT
+
+typedef struct
+{
+ gchar *key;
+ MetaPreference pref;
+ gint *target;
+ /**
+ * Minimum and maximum values of the integer.
+ * If the new value is out of bounds, it will be discarded with a warning.
+ */
+ gint minimum, maximum;
+ /**
+ * Value to use if the key is destroyed.
+ * If this is METAINTPREFERENCE_NO_CHANGE_ON_DESTROY, it will
+ * not be changed when the key is destroyed.
+ */
+ gint value_if_destroyed;
+} MetaIntPreference;
+
+/* FIXMEs: */
+/* @@@ Don't use NULL lines at the end; glib can tell you how big it is */
+/* @@@ /apps/marco/general should be assumed if first char is not / */
+/* @@@ Will it ever be possible to merge init and update? If not, why not? */
+
+static MetaEnumPreference preferences_enum[] =
+ {
+ { "/apps/marco/general/focus_new_windows",
+ META_PREF_FOCUS_NEW_WINDOWS,
+ symtab_focus_new_windows,
+ &focus_new_windows,
+ },
+ { "/apps/marco/general/focus_mode",
+ META_PREF_FOCUS_MODE,
+ symtab_focus_mode,
+ &focus_mode,
+ },
+ { "/apps/marco/general/visual_bell_type",
+ META_PREF_VISUAL_BELL_TYPE,
+ symtab_visual_bell_type,
+ &visual_bell_type,
+ },
+ { "/apps/marco/general/action_double_click_titlebar",
+ META_PREF_ACTION_DOUBLE_CLICK_TITLEBAR,
+ symtab_titlebar_action,
+ &action_double_click_titlebar,
+ },
+ { "/apps/marco/general/action_middle_click_titlebar",
+ META_PREF_ACTION_MIDDLE_CLICK_TITLEBAR,
+ symtab_titlebar_action,
+ &action_middle_click_titlebar,
+ },
+ { "/apps/marco/general/action_right_click_titlebar",
+ META_PREF_ACTION_RIGHT_CLICK_TITLEBAR,
+ symtab_titlebar_action,
+ &action_right_click_titlebar,
+ },
+ { NULL, 0, NULL, NULL },
+ };
+
+static MetaBoolPreference preferences_bool[] =
+ {
+ { "/apps/marco/general/raise_on_click",
+ META_PREF_RAISE_ON_CLICK,
+ &raise_on_click,
+ TRUE,
+ },
+ { "/apps/marco/general/titlebar_uses_system_font",
+ META_PREF_TITLEBAR_FONT, /* note! shares a pref */
+ &use_system_font,
+ TRUE,
+ },
+ { "/apps/marco/general/application_based",
+ META_PREF_APPLICATION_BASED,
+ NULL, /* feature is known but disabled */
+ FALSE,
+ },
+ { "/apps/marco/general/disable_workarounds",
+ META_PREF_DISABLE_WORKAROUNDS,
+ &disable_workarounds,
+ FALSE,
+ },
+ { "/apps/marco/general/auto_raise",
+ META_PREF_AUTO_RAISE,
+ &auto_raise,
+ FALSE,
+ },
+ { "/apps/marco/general/visual_bell",
+ META_PREF_VISUAL_BELL,
+ &provide_visual_bell, /* FIXME: change the name: it's confusing */
+ FALSE,
+ },
+ { "/apps/marco/general/audible_bell",
+ META_PREF_AUDIBLE_BELL,
+ &bell_is_audible, /* FIXME: change the name: it's confusing */
+ FALSE,
+ },
+ { "/apps/marco/general/reduced_resources",
+ META_PREF_REDUCED_RESOURCES,
+ &reduced_resources,
+ FALSE,
+ },
+ { "/desktop/mate/interface/accessibility",
+ META_PREF_MATE_ACCESSIBILITY,
+ &mate_accessibility,
+ FALSE,
+ },
+ { "/desktop/mate/interface/enable_animations",
+ META_PREF_MATE_ANIMATIONS,
+ &mate_animations,
+ TRUE,
+ },
+ { "/apps/marco/general/compositing_manager",
+ META_PREF_COMPOSITING_MANAGER,
+ &compositing_manager,
+ FALSE,
+ },
+ { "/apps/marco/general/resize_with_right_button",
+ META_PREF_RESIZE_WITH_RIGHT_BUTTON,
+ &resize_with_right_button,
+ FALSE,
+ },
+ { NULL, 0, NULL, FALSE },
+ };
+
+static MetaStringPreference preferences_string[] =
+ {
+ { "/apps/marco/general/mouse_button_modifier",
+ META_PREF_MOUSE_BUTTON_MODS,
+ mouse_button_mods_handler,
+ NULL,
+ },
+ { "/apps/marco/general/theme",
+ META_PREF_THEME,
+ theme_name_handler,
+ NULL,
+ },
+ { KEY_TITLEBAR_FONT,
+ META_PREF_TITLEBAR_FONT,
+ titlebar_handler,
+ NULL,
+ },
+ { KEY_TERMINAL_COMMAND,
+ META_PREF_TERMINAL_COMMAND,
+ NULL,
+ &terminal_command,
+ },
+ { "/apps/marco/general/button_layout",
+ META_PREF_BUTTON_LAYOUT,
+ button_layout_handler,
+ NULL,
+ },
+ { "/desktop/mate/peripherals/mouse/cursor_theme",
+ META_PREF_CURSOR_THEME,
+ NULL,
+ &cursor_theme,
+ },
+ { NULL, 0, NULL, NULL },
+ };
+
+static MetaIntPreference preferences_int[] =
+ {
+ { "/apps/marco/general/num_workspaces",
+ META_PREF_NUM_WORKSPACES,
+ &num_workspaces,
+ /* I would actually recommend we change the destroy value to 4
+ * and get rid of METAINTPREFERENCE_NO_CHANGE_ON_DESTROY entirely.
+ * -- tthurman
+ */
+ 1, MAX_REASONABLE_WORKSPACES, METAINTPREFERENCE_NO_CHANGE_ON_DESTROY,
+ },
+ { "/apps/marco/general/auto_raise_delay",
+ META_PREF_AUTO_RAISE_DELAY,
+ &auto_raise_delay,
+ 0, 10000, 0,
+ /* @@@ Get rid of MAX_REASONABLE_AUTO_RAISE_DELAY */
+ },
+ { "/desktop/mate/peripherals/mouse/cursor_size",
+ META_PREF_CURSOR_SIZE,
+ &cursor_size,
+ 1, 128, 24,
+ },
+ { NULL, 0, NULL, 0, 0, 0, },
+ };
+
+static void
+handle_preference_init_enum (void)
+{
+ MetaEnumPreference *cursor = preferences_enum;
+
+ while (cursor->key!=NULL)
+ {
+ char *value;
+ GError *error = NULL;
+
+ if (cursor->target==NULL)
+ {
+ ++cursor;
+ continue;
+ }
+
+ value = mateconf_client_get_string (default_client,
+ cursor->key,
+ &error);
+ cleanup_error (&error);
+
+ if (value==NULL)
+ {
+ ++cursor;
+ continue;
+ }
+
+ if (!mateconf_string_to_enum (cursor->symtab,
+ value,
+ (gint *) cursor->target))
+ meta_warning (_("MateConf key '%s' is set to an invalid value\n"),
+ cursor->key);
+
+ g_free (value);
+
+ ++cursor;
+ }
+}
+
+static void
+handle_preference_init_bool (void)
+{
+ MetaBoolPreference *cursor = preferences_bool;
+
+ while (cursor->key!=NULL)
+ {
+ if (cursor->target!=NULL)
+ get_bool (cursor->key, cursor->target);
+
+ ++cursor;
+ }
+
+ maybe_give_disable_workarounds_warning ();
+}
+
+static void
+handle_preference_init_string (void)
+{
+ MetaStringPreference *cursor = preferences_string;
+
+ while (cursor->key!=NULL)
+ {
+ char *value;
+ GError *error = NULL;
+ gboolean dummy = TRUE;
+
+ /* the string "value" will be newly allocated */
+ value = mateconf_client_get_string (default_client,
+ cursor->key,
+ &error);
+ cleanup_error (&error);
+
+ if (cursor->handler)
+ {
+ if (cursor->target)
+ meta_bug ("%s has both a target and a handler\n", cursor->key);
+
+ cursor->handler (cursor->pref, value, &dummy);
+
+ g_free (value);
+ }
+ else if (cursor->target)
+ {
+ if (*(cursor->target))
+ g_free (*(cursor->target));
+
+ *(cursor->target) = value;
+ }
+
+ ++cursor;
+ }
+}
+
+static void
+handle_preference_init_int (void)
+{
+ MetaIntPreference *cursor = preferences_int;
+
+
+ while (cursor->key!=NULL)
+ {
+ gint value;
+ GError *error = NULL;
+
+ value = mateconf_client_get_int (default_client,
+ cursor->key,
+ &error);
+ cleanup_error (&error);
+
+ if (value < cursor->minimum || value > cursor->maximum)
+ {
+ meta_warning (_("%d stored in MateConf key %s is out of range %d to %d\n"),
+ value, cursor->key, cursor->minimum, cursor->maximum);
+ /* Former behaviour for out-of-range values was:
+ * - number of workspaces was clamped;
+ * - auto raise delay was always reset to zero even if too high!;
+ * - cursor size was ignored.
+ *
+ * These seem to be meaningless variations. If they did
+ * have meaning we could have put them into MetaIntPreference.
+ * The last of these is the closest to how we behave for
+ * other types, so I think we should standardise on that.
+ */
+ }
+ else if (cursor->target)
+ *cursor->target = value;
+
+ ++cursor;
+ }
+}
+
+static gboolean
+handle_preference_update_enum (const gchar *key, MateConfValue *value)
+{
+ MetaEnumPreference *cursor = preferences_enum;
+ gint old_value;
+
+ while (cursor->key!=NULL && strcmp (key, cursor->key)!=0)
+ ++cursor;
+
+ if (cursor->key==NULL)
+ /* Didn't recognise that key. */
+ return FALSE;
+
+ /* Setting it to null (that is, removing it) always means
+ * "don't change".
+ */
+
+ if (value==NULL)
+ return TRUE;
+
+ /* Check the type. Enums are always strings. */
+
+ if (value->type != MATECONF_VALUE_STRING)
+ {
+ meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"),
+ key);
+ /* But we did recognise it. */
+ return TRUE;
+ }
+
+ /* We need to know whether the value changes, so
+ * store the current value away.
+ */
+
+ old_value = * ((gint *) cursor->target);
+
+ /* Now look it up... */
+
+ if (!mateconf_string_to_enum (cursor->symtab,
+ mateconf_value_get_string (value),
+ (gint *) cursor->target))
+ {
+ /*
+ * We found it, but it was invalid. Complain.
+ *
+ * FIXME: This replicates the original behaviour, but in the future
+ * we might consider reverting invalid keys to their original values.
+ * (We know the old value, so we can look up a suitable string in
+ * the symtab.)
+ *
+ * (Empty comment follows so the translators don't see this.)
+ */
+
+ /* */
+ meta_warning (_("MateConf key '%s' is set to an invalid value\n"),
+ key);
+ return TRUE;
+ }
+
+ /* Did it change? If so, tell the listeners about it. */
+
+ if (old_value != *((gint *) cursor->target))
+ queue_changed (cursor->pref);
+
+ return TRUE;
+}
+
+static gboolean
+handle_preference_update_bool (const gchar *key, MateConfValue *value)
+{
+ MetaBoolPreference *cursor = preferences_bool;
+ gboolean old_value;
+
+ while (cursor->key!=NULL && strcmp (key, cursor->key)!=0)
+ ++cursor;
+
+ if (cursor->key==NULL)
+ /* Didn't recognise that key. */
+ return FALSE;
+
+ if (cursor->target==NULL)
+ /* No work for us to do. */
+ return TRUE;
+
+ if (value==NULL)
+ {
+ /* Value was destroyed; let's get out of here. */
+
+ if (cursor->becomes_true_on_destruction)
+ /* This preserves the behaviour of the old system, but
+ * for all I know that might have been an oversight.
+ */
+ *((gboolean *)cursor->target) = TRUE;
+
+ return TRUE;
+ }
+
+ /* Check the type. */
+
+ if (value->type != MATECONF_VALUE_BOOL)
+ {
+ meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"),
+ key);
+ /* But we did recognise it. */
+ return TRUE;
+ }
+
+ /* We need to know whether the value changes, so
+ * store the current value away.
+ */
+
+ old_value = * ((gboolean *) cursor->target);
+
+ /* Now look it up... */
+
+ *((gboolean *) cursor->target) = mateconf_value_get_bool (value);
+
+ /* Did it change? If so, tell the listeners about it. */
+
+ if (old_value != *((gboolean *) cursor->target))
+ queue_changed (cursor->pref);
+
+ if (cursor->pref==META_PREF_DISABLE_WORKAROUNDS)
+ maybe_give_disable_workarounds_warning ();
+
+ return TRUE;
+}
+
+static gboolean
+handle_preference_update_string (const gchar *key, MateConfValue *value)
+{
+ MetaStringPreference *cursor = preferences_string;
+ const gchar *value_as_string;
+ gboolean inform_listeners = TRUE;
+
+ while (cursor->key!=NULL && strcmp (key, cursor->key)!=0)
+ ++cursor;
+
+ if (cursor->key==NULL)
+ /* Didn't recognise that key. */
+ return FALSE;
+
+ if (value==NULL)
+ return TRUE;
+
+ /* Check the type. */
+
+ if (value->type != MATECONF_VALUE_STRING)
+ {
+ meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"),
+ key);
+ /* But we did recognise it. */
+ return TRUE;
+ }
+
+ /* Docs: "The returned string is not a copy, don't try to free it." */
+ value_as_string = mateconf_value_get_string (value);
+
+ if (cursor->handler)
+ cursor->handler (cursor->pref, value_as_string, &inform_listeners);
+ else if (cursor->target)
+ {
+ if (*(cursor->target))
+ g_free(*(cursor->target));
+
+ if (value_as_string!=NULL)
+ *(cursor->target) = g_strdup (value_as_string);
+ else
+ *(cursor->target) = NULL;
+
+ inform_listeners =
+ (value_as_string==NULL && *(cursor->target)==NULL) ||
+ (value_as_string!=NULL && *(cursor->target)!=NULL &&
+ strcmp (value_as_string, *(cursor->target))==0);
+ }
+
+ if (inform_listeners)
+ queue_changed (cursor->pref);
+
+ return TRUE;
+}
+
+static gboolean
+handle_preference_update_int (const gchar *key, MateConfValue *value)
+{
+ MetaIntPreference *cursor = preferences_int;
+ gint new_value;
+
+ while (cursor->key!=NULL && strcmp (key, cursor->key)!=0)
+ ++cursor;
+
+ if (cursor->key==NULL)
+ /* Didn't recognise that key. */
+ return FALSE;
+
+ if (cursor->target==NULL)
+ /* No work for us to do. */
+ return TRUE;
+
+ if (value==NULL)
+ {
+ /* Value was destroyed. */
+
+ if (cursor->value_if_destroyed != METAINTPREFERENCE_NO_CHANGE_ON_DESTROY)
+ *((gint *)cursor->target) = cursor->value_if_destroyed;
+
+ return TRUE;
+ }
+
+ /* Check the type. */
+
+ if (value->type != MATECONF_VALUE_INT)
+ {
+ meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"),
+ key);
+ /* But we did recognise it. */
+ return TRUE;
+ }
+
+ new_value = mateconf_value_get_int (value);
+
+ if (new_value < cursor->minimum || new_value > cursor->maximum)
+ {
+ meta_warning (_("%d stored in MateConf key %s is out of range %d to %d\n"),
+ new_value, cursor->key,
+ cursor->minimum, cursor->maximum);
+ return TRUE;
+ }
+
+ /* Did it change? If so, tell the listeners about it. */
+
+ if (*cursor->target != new_value)
+ {
+ *cursor->target = new_value;
+ queue_changed (cursor->pref);
+ }
+
+ return TRUE;
+
+}
+
+
+/****************************************************************************/
+/* Listeners. */
+/****************************************************************************/
+
+void
+meta_prefs_add_listener (MetaPrefsChangedFunc func,
+ gpointer data)
+{
+ MetaPrefsListener *l;
+
+ l = g_new (MetaPrefsListener, 1);
+ l->func = func;
+ l->data = data;
+
+ listeners = g_list_prepend (listeners, l);
+}
+
+void
+meta_prefs_remove_listener (MetaPrefsChangedFunc func,
+ gpointer data)
+{
+ GList *tmp;
+
+ tmp = listeners;
+ while (tmp != NULL)
+ {
+ MetaPrefsListener *l = tmp->data;
+
+ if (l->func == func &&
+ l->data == data)
+ {
+ g_free (l);
+ listeners = g_list_delete_link (listeners, tmp);
+
+ return;
+ }
+
+ tmp = tmp->next;
+ }
+
+ meta_bug ("Did not find listener to remove\n");
+}
+
+static void
+emit_changed (MetaPreference pref)
+{
+ GList *tmp;
+ GList *copy;
+
+ meta_topic (META_DEBUG_PREFS, "Notifying listeners that pref %s changed\n",
+ meta_preference_to_string (pref));
+
+ copy = g_list_copy (listeners);
+
+ tmp = copy;
+
+ while (tmp != NULL)
+ {
+ MetaPrefsListener *l = tmp->data;
+
+ (* l->func) (pref, l->data);
+
+ tmp = tmp->next;
+ }
+
+ g_list_free (copy);
+}
+
+static gboolean
+changed_idle_handler (gpointer data)
+{
+ GList *tmp;
+ GList *copy;
+
+ changed_idle = 0;
+
+ copy = g_list_copy (changes); /* reentrancy paranoia */
+
+ g_list_free (changes);
+ changes = NULL;
+
+ tmp = copy;
+ while (tmp != NULL)
+ {
+ MetaPreference pref = GPOINTER_TO_INT (tmp->data);
+
+ emit_changed (pref);
+
+ tmp = tmp->next;
+ }
+
+ g_list_free (copy);
+
+ return FALSE;
+}
+
+static void
+queue_changed (MetaPreference pref)
+{
+ meta_topic (META_DEBUG_PREFS, "Queueing change of pref %s\n",
+ meta_preference_to_string (pref));
+
+ if (g_list_find (changes, GINT_TO_POINTER (pref)) == NULL)
+ changes = g_list_prepend (changes, GINT_TO_POINTER (pref));
+ else
+ meta_topic (META_DEBUG_PREFS, "Change of pref %s was already pending\n",
+ meta_preference_to_string (pref));
+
+ /* add idle at priority below the mateconf notify idle */
+ if (changed_idle == 0)
+ changed_idle = g_idle_add_full (META_PRIORITY_PREFS_NOTIFY,
+ changed_idle_handler, NULL, NULL);
+}
+
+#else /* HAVE_MATECONF */
+
+void
+meta_prefs_add_listener (MetaPrefsChangedFunc func,
+ gpointer data)
+{
+ /* Nothing, because they have mateconf turned off */
+}
+
+void
+meta_prefs_remove_listener (MetaPrefsChangedFunc func,
+ gpointer data)
+{
+ /* Nothing, because they have mateconf turned off */
+}
+
+#endif /* HAVE_MATECONF */
+
+
+/****************************************************************************/
+/* Initialisation. */
+/****************************************************************************/
+
+#ifdef HAVE_MATECONF
+/* @@@ again, use glib's ability to tell you the size of the array */
+static gchar *mateconf_dirs_we_are_interested_in[] = {
+ "/apps/marco",
+ KEY_TERMINAL_DIR,
+ KEY_MATE_ACCESSIBILITY,
+ "/desktop/mate/peripherals/mouse",
+ "/desktop/mate/interface",
+ NULL,
+};
+#endif
+
+void
+meta_prefs_init (void)
+{
+#ifdef HAVE_MATECONF
+ GError *err = NULL;
+ gchar **mateconf_dir_cursor;
+
+ if (default_client != NULL)
+ return;
+
+ /* returns a reference which we hold forever */
+ default_client = mateconf_client_get_default ();
+
+ for (mateconf_dir_cursor=mateconf_dirs_we_are_interested_in;
+ *mateconf_dir_cursor!=NULL;
+ mateconf_dir_cursor++)
+ {
+ mateconf_client_add_dir (default_client,
+ *mateconf_dir_cursor,
+ MATECONF_CLIENT_PRELOAD_RECURSIVE,
+ &err);
+ cleanup_error (&err);
+ }
+
+ /* Pick up initial values. */
+
+ handle_preference_init_enum ();
+ handle_preference_init_bool ();
+ handle_preference_init_string ();
+ handle_preference_init_int ();
+
+ /* @@@ Is there any reason we don't do the add_dir here? */
+ for (mateconf_dir_cursor=mateconf_dirs_we_are_interested_in;
+ *mateconf_dir_cursor!=NULL;
+ mateconf_dir_cursor++)
+ {
+ mateconf_client_notify_add (default_client,
+ *mateconf_dir_cursor,
+ change_notify,
+ NULL,
+ NULL,
+ &err);
+ cleanup_error (&err);
+ }
+
+#else /* HAVE_MATECONF */
+
+ /* Set defaults for some values that can't be set at initialization time of
+ * the static globals. In the case of the theme, note that there is code
+ * elsewhere that will do everything possible to fallback to an existing theme
+ * if the one here does not exist.
+ */
+ titlebar_font = pango_font_description_from_string ("Sans Bold 10");
+ current_theme = g_strdup ("ClearlooksRe");
+
+ init_button_layout();
+#endif /* HAVE_MATECONF */
+
+ init_bindings ();
+ init_commands ();
+ init_workspace_names ();
+}
+
+
+/****************************************************************************/
+/* Updates. */
+/****************************************************************************/
+
+#ifdef HAVE_MATECONF
+
+gboolean (*preference_update_handler[]) (const gchar*, MateConfValue*) = {
+ handle_preference_update_enum,
+ handle_preference_update_bool,
+ handle_preference_update_string,
+ handle_preference_update_int,
+ NULL
+};
+
+static void
+change_notify (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ const char *key;
+ MateConfValue *value;
+ gint i=0;
+
+ key = mateconf_entry_get_key (entry);
+ value = mateconf_entry_get_value (entry);
+
+ /* First, search for a handler that might know what to do. */
+
+ /* FIXME: When this is all working, since the first item in every
+ * array is the gchar* of the key, there's no reason we can't
+ * find the correct record for that key here and save code duplication.
+ */
+
+ while (preference_update_handler[i]!=NULL)
+ {
+ if (preference_update_handler[i] (key, value))
+ goto out; /* Get rid of this eventually */
+
+ i++;
+ }
+
+ if (g_str_has_prefix (key, KEY_WINDOW_BINDINGS_PREFIX) ||
+ g_str_has_prefix (key, KEY_SCREEN_BINDINGS_PREFIX))
+ {
+ if (g_str_has_suffix (key, KEY_LIST_BINDINGS_SUFFIX))
+ {
+ GSList *list;
+
+ if (value && value->type != MATECONF_VALUE_LIST)
+ {
+ meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"),
+ key);
+ goto out;
+ }
+
+ list = value ? mateconf_value_get_list (value) : NULL;
+
+ if (update_key_list_binding (key, list))
+ queue_changed (META_PREF_KEYBINDINGS);
+ }
+ else
+ {
+ const char *str;
+
+ if (value && value->type != MATECONF_VALUE_STRING)
+ {
+ meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"),
+ key);
+ goto out;
+ }
+
+ str = value ? mateconf_value_get_string (value) : NULL;
+
+ if (update_key_binding (key, str))
+ queue_changed (META_PREF_KEYBINDINGS);
+ }
+ }
+ else if (g_str_has_prefix (key, KEY_COMMAND_PREFIX))
+ {
+ const char *str;
+
+ if (value && value->type != MATECONF_VALUE_STRING)
+ {
+ meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"),
+ key);
+ goto out;
+ }
+
+ str = value ? mateconf_value_get_string (value) : NULL;
+
+ if (update_command (key, str))
+ queue_changed (META_PREF_COMMANDS);
+ }
+ else if (g_str_has_prefix (key, KEY_WORKSPACE_NAME_PREFIX))
+ {
+ const char *str;
+
+ if (value && value->type != MATECONF_VALUE_STRING)
+ {
+ meta_warning (_("MateConf key \"%s\" is set to an invalid type\n"),
+ key);
+ goto out;
+ }
+
+ str = value ? mateconf_value_get_string (value) : NULL;
+
+ if (update_workspace_name (key, str))
+ queue_changed (META_PREF_WORKSPACE_NAMES);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_PREFS, "Key %s doesn't mean anything to Marco\n",
+ key);
+ }
+
+ out:
+ /* nothing */
+ return; /* AIX compiler wants something after a label like out: */
+}
+
+static void
+cleanup_error (GError **error)
+{
+ if (*error)
+ {
+ meta_warning ("%s\n", (*error)->message);
+
+ g_error_free (*error);
+ *error = NULL;
+ }
+}
+
+/* get_bool returns TRUE if *val is filled in, FALSE otherwise */
+/* @@@ probably worth moving this inline; only used once */
+static gboolean
+get_bool (const char *key, gboolean *val)
+{
+ GError *err = NULL;
+ MateConfValue *value;
+ gboolean filled_in = FALSE;
+
+ value = mateconf_client_get (default_client, key, &err);
+ cleanup_error (&err);
+ if (value)
+ {
+ if (value->type == MATECONF_VALUE_BOOL)
+ {
+ *val = mateconf_value_get_bool (value);
+ filled_in = TRUE;
+ }
+ mateconf_value_free (value);
+ }
+
+ return filled_in;
+}
+
+/**
+ * Special case: give a warning the first time disable_workarounds
+ * is turned on.
+ */
+static void
+maybe_give_disable_workarounds_warning (void)
+{
+ static gboolean first_disable = TRUE;
+
+ if (first_disable && disable_workarounds)
+ {
+ first_disable = FALSE;
+
+ meta_warning (_("Workarounds for broken applications disabled. "
+ "Some applications may not behave properly.\n"));
+ }
+}
+
+#endif /* HAVE_MATECONF */
+
+MetaVirtualModifier
+meta_prefs_get_mouse_button_mods (void)
+{
+ return mouse_button_mods;
+}
+
+MetaFocusMode
+meta_prefs_get_focus_mode (void)
+{
+ return focus_mode;
+}
+
+MetaFocusNewWindows
+meta_prefs_get_focus_new_windows (void)
+{
+ return focus_new_windows;
+}
+
+gboolean
+meta_prefs_get_raise_on_click (void)
+{
+ /* Force raise_on_click on for click-to-focus, as requested by Havoc
+ * in #326156.
+ */
+ return raise_on_click || focus_mode == META_FOCUS_MODE_CLICK;
+}
+
+const char*
+meta_prefs_get_theme (void)
+{
+ return current_theme;
+}
+
+const char*
+meta_prefs_get_cursor_theme (void)
+{
+ return cursor_theme;
+}
+
+int
+meta_prefs_get_cursor_size (void)
+{
+ return cursor_size;
+}
+
+
+/****************************************************************************/
+/* Handlers for string preferences. */
+/****************************************************************************/
+
+#ifdef HAVE_MATECONF
+
+static void
+titlebar_handler (MetaPreference pref,
+ const gchar *string_value,
+ gboolean *inform_listeners)
+{
+ PangoFontDescription *new_desc = NULL;
+
+ if (string_value)
+ new_desc = pango_font_description_from_string (string_value);
+
+ if (new_desc == NULL)
+ {
+ meta_warning (_("Could not parse font description "
+ "\"%s\" from MateConf key %s\n"),
+ string_value ? string_value : "(null)",
+ KEY_TITLEBAR_FONT);
+
+ *inform_listeners = FALSE;
+
+ return;
+ }
+
+ /* Is the new description the same as the old? */
+
+ if (titlebar_font &&
+ pango_font_description_equal (new_desc, titlebar_font))
+ {
+ pango_font_description_free (new_desc);
+ *inform_listeners = FALSE;
+ return;
+ }
+
+ /* No, so free the old one and put ours in instead. */
+
+ if (titlebar_font)
+ pango_font_description_free (titlebar_font);
+
+ titlebar_font = new_desc;
+
+}
+
+static void
+theme_name_handler (MetaPreference pref,
+ const gchar *string_value,
+ gboolean *inform_listeners)
+{
+ g_free (current_theme);
+
+ /* Fallback crackrock */
+ if (string_value == NULL)
+ current_theme = g_strdup ("ClearlooksRe");
+ else
+ current_theme = g_strdup (string_value);
+}
+
+static void
+mouse_button_mods_handler (MetaPreference pref,
+ const gchar *string_value,
+ gboolean *inform_listeners)
+{
+ MetaVirtualModifier mods;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Mouse button modifier has new mateconf value \"%s\"\n",
+ string_value);
+ if (string_value && meta_ui_parse_modifier (string_value, &mods))
+ {
+ mouse_button_mods = mods;
+ }
+ else
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Failed to parse new mateconf value\n");
+
+ meta_warning (_("\"%s\" found in configuration database is "
+ "not a valid value for mouse button modifier\n"),
+ string_value);
+
+ *inform_listeners = FALSE;
+ }
+}
+
+static gboolean
+button_layout_equal (const MetaButtonLayout *a,
+ const MetaButtonLayout *b)
+{
+ int i;
+
+ i = 0;
+ while (i < MAX_BUTTONS_PER_CORNER)
+ {
+ if (a->left_buttons[i] != b->left_buttons[i])
+ return FALSE;
+ if (a->right_buttons[i] != b->right_buttons[i])
+ return FALSE;
+ if (a->left_buttons_has_spacer[i] != b->left_buttons_has_spacer[i])
+ return FALSE;
+ if (a->right_buttons_has_spacer[i] != b->right_buttons_has_spacer[i])
+ return FALSE;
+ ++i;
+ }
+
+ return TRUE;
+}
+
+static MetaButtonFunction
+button_function_from_string (const char *str)
+{
+ /* FIXME: mateconf_string_to_enum is the obvious way to do this */
+
+ if (strcmp (str, "menu") == 0)
+ return META_BUTTON_FUNCTION_MENU;
+ else if (strcmp (str, "minimize") == 0)
+ return META_BUTTON_FUNCTION_MINIMIZE;
+ else if (strcmp (str, "maximize") == 0)
+ return META_BUTTON_FUNCTION_MAXIMIZE;
+ else if (strcmp (str, "close") == 0)
+ return META_BUTTON_FUNCTION_CLOSE;
+ else if (strcmp (str, "shade") == 0)
+ return META_BUTTON_FUNCTION_SHADE;
+ else if (strcmp (str, "above") == 0)
+ return META_BUTTON_FUNCTION_ABOVE;
+ else if (strcmp (str, "stick") == 0)
+ return META_BUTTON_FUNCTION_STICK;
+ else
+ /* don't know; give up */
+ return META_BUTTON_FUNCTION_LAST;
+}
+
+static MetaButtonFunction
+button_opposite_function (MetaButtonFunction ofwhat)
+{
+ switch (ofwhat)
+ {
+ case META_BUTTON_FUNCTION_SHADE:
+ return META_BUTTON_FUNCTION_UNSHADE;
+ case META_BUTTON_FUNCTION_UNSHADE:
+ return META_BUTTON_FUNCTION_SHADE;
+
+ case META_BUTTON_FUNCTION_ABOVE:
+ return META_BUTTON_FUNCTION_UNABOVE;
+ case META_BUTTON_FUNCTION_UNABOVE:
+ return META_BUTTON_FUNCTION_ABOVE;
+
+ case META_BUTTON_FUNCTION_STICK:
+ return META_BUTTON_FUNCTION_UNSTICK;
+ case META_BUTTON_FUNCTION_UNSTICK:
+ return META_BUTTON_FUNCTION_STICK;
+
+ default:
+ return META_BUTTON_FUNCTION_LAST;
+ }
+}
+
+static void
+button_layout_handler (MetaPreference pref,
+ const gchar *string_value,
+ gboolean *inform_listeners)
+{
+ MetaButtonLayout new_layout;
+ char **sides = NULL;
+ int i;
+
+ /* We need to ignore unknown button functions, for
+ * compat with future versions
+ */
+
+ if (string_value)
+ sides = g_strsplit (string_value, ":", 2);
+
+ if (sides != NULL && sides[0] != NULL)
+ {
+ char **buttons;
+ int b;
+ gboolean used[META_BUTTON_FUNCTION_LAST];
+
+ i = 0;
+ while (i < META_BUTTON_FUNCTION_LAST)
+ {
+ used[i] = FALSE;
+ new_layout.left_buttons_has_spacer[i] = FALSE;
+ ++i;
+ }
+
+ buttons = g_strsplit (sides[0], ",", -1);
+ i = 0;
+ b = 0;
+ while (buttons[b] != NULL)
+ {
+ MetaButtonFunction f = button_function_from_string (buttons[b]);
+ if (i > 0 && strcmp("spacer", buttons[b]) == 0)
+ {
+ new_layout.left_buttons_has_spacer[i-1] = TRUE;
+ f = button_opposite_function (f);
+
+ if (f != META_BUTTON_FUNCTION_LAST)
+ {
+ new_layout.left_buttons_has_spacer[i-2] = TRUE;
+ }
+ }
+ else
+ {
+ if (f != META_BUTTON_FUNCTION_LAST && !used[f])
+ {
+ new_layout.left_buttons[i] = f;
+ used[f] = TRUE;
+ ++i;
+
+ f = button_opposite_function (f);
+
+ if (f != META_BUTTON_FUNCTION_LAST)
+ new_layout.left_buttons[i++] = f;
+
+ }
+ else
+ {
+ meta_topic (META_DEBUG_PREFS, "Ignoring unknown or already-used button name \"%s\"\n",
+ buttons[b]);
+ }
+ }
+
+ ++b;
+ }
+
+ new_layout.left_buttons[i] = META_BUTTON_FUNCTION_LAST;
+ new_layout.left_buttons_has_spacer[i] = FALSE;
+
+ g_strfreev (buttons);
+ }
+
+ if (sides != NULL && sides[0] != NULL && sides[1] != NULL)
+ {
+ char **buttons;
+ int b;
+ gboolean used[META_BUTTON_FUNCTION_LAST];
+
+ i = 0;
+ while (i < META_BUTTON_FUNCTION_LAST)
+ {
+ used[i] = FALSE;
+ new_layout.right_buttons_has_spacer[i] = FALSE;
+ ++i;
+ }
+
+ buttons = g_strsplit (sides[1], ",", -1);
+ i = 0;
+ b = 0;
+ while (buttons[b] != NULL)
+ {
+ MetaButtonFunction f = button_function_from_string (buttons[b]);
+ if (i > 0 && strcmp("spacer", buttons[b]) == 0)
+ {
+ new_layout.right_buttons_has_spacer[i-1] = TRUE;
+ f = button_opposite_function (f);
+ if (f != META_BUTTON_FUNCTION_LAST)
+ {
+ new_layout.right_buttons_has_spacer[i-2] = TRUE;
+ }
+ }
+ else
+ {
+ if (f != META_BUTTON_FUNCTION_LAST && !used[f])
+ {
+ new_layout.right_buttons[i] = f;
+ used[f] = TRUE;
+ ++i;
+
+ f = button_opposite_function (f);
+
+ if (f != META_BUTTON_FUNCTION_LAST)
+ new_layout.right_buttons[i++] = f;
+
+ }
+ else
+ {
+ meta_topic (META_DEBUG_PREFS, "Ignoring unknown or already-used button name \"%s\"\n",
+ buttons[b]);
+ }
+ }
+
+ ++b;
+ }
+
+ new_layout.right_buttons[i] = META_BUTTON_FUNCTION_LAST;
+ new_layout.right_buttons_has_spacer[i] = FALSE;
+
+ g_strfreev (buttons);
+ }
+
+ g_strfreev (sides);
+
+ /* Invert the button layout for RTL languages */
+ if (meta_ui_get_direction() == META_UI_DIRECTION_RTL)
+ {
+ MetaButtonLayout rtl_layout;
+ int j;
+
+ for (i = 0; new_layout.left_buttons[i] != META_BUTTON_FUNCTION_LAST; i++);
+ for (j = 0; j < i; j++)
+ {
+ rtl_layout.right_buttons[j] = new_layout.left_buttons[i - j - 1];
+ if (j == 0)
+ rtl_layout.right_buttons_has_spacer[i - 1] = new_layout.left_buttons_has_spacer[i - j - 1];
+ else
+ rtl_layout.right_buttons_has_spacer[j - 1] = new_layout.left_buttons_has_spacer[i - j - 1];
+ }
+ rtl_layout.right_buttons[j] = META_BUTTON_FUNCTION_LAST;
+ rtl_layout.right_buttons_has_spacer[j] = FALSE;
+
+ for (i = 0; new_layout.right_buttons[i] != META_BUTTON_FUNCTION_LAST; i++);
+ for (j = 0; j < i; j++)
+ {
+ rtl_layout.left_buttons[j] = new_layout.right_buttons[i - j - 1];
+ if (j == 0)
+ rtl_layout.left_buttons_has_spacer[i - 1] = new_layout.right_buttons_has_spacer[i - j - 1];
+ else
+ rtl_layout.left_buttons_has_spacer[j - 1] = new_layout.right_buttons_has_spacer[i - j - 1];
+ }
+ rtl_layout.left_buttons[j] = META_BUTTON_FUNCTION_LAST;
+ rtl_layout.left_buttons_has_spacer[j] = FALSE;
+
+ new_layout = rtl_layout;
+ }
+
+ if (button_layout_equal (&button_layout, &new_layout))
+ {
+ /* Same as before, so duck out */
+ *inform_listeners = FALSE;
+ }
+ else
+ {
+ button_layout = new_layout;
+ }
+}
+
+#endif /* HAVE_MATECONF */
+
+const PangoFontDescription*
+meta_prefs_get_titlebar_font (void)
+{
+ if (use_system_font)
+ return NULL;
+ else
+ return titlebar_font;
+}
+
+int
+meta_prefs_get_num_workspaces (void)
+{
+ return num_workspaces;
+}
+
+gboolean
+meta_prefs_get_application_based (void)
+{
+ return FALSE; /* For now, we never want this to do anything */
+
+ return application_based;
+}
+
+gboolean
+meta_prefs_get_disable_workarounds (void)
+{
+ return disable_workarounds;
+}
+
+#ifdef HAVE_MATECONF
+#define MAX_REASONABLE_AUTO_RAISE_DELAY 10000
+
+#endif /* HAVE_MATECONF */
+
+#ifdef WITH_VERBOSE_MODE
+const char*
+meta_preference_to_string (MetaPreference pref)
+{
+ /* FIXME: another case for mateconf_string_to_enum */
+ switch (pref)
+ {
+ case META_PREF_MOUSE_BUTTON_MODS:
+ return "MOUSE_BUTTON_MODS";
+
+ case META_PREF_FOCUS_MODE:
+ return "FOCUS_MODE";
+
+ case META_PREF_FOCUS_NEW_WINDOWS:
+ return "FOCUS_NEW_WINDOWS";
+
+ case META_PREF_RAISE_ON_CLICK:
+ return "RAISE_ON_CLICK";
+
+ case META_PREF_THEME:
+ return "THEME";
+
+ case META_PREF_TITLEBAR_FONT:
+ return "TITLEBAR_FONT";
+
+ case META_PREF_NUM_WORKSPACES:
+ return "NUM_WORKSPACES";
+
+ case META_PREF_APPLICATION_BASED:
+ return "APPLICATION_BASED";
+
+ case META_PREF_KEYBINDINGS:
+ return "KEYBINDINGS";
+
+ case META_PREF_DISABLE_WORKAROUNDS:
+ return "DISABLE_WORKAROUNDS";
+
+ case META_PREF_ACTION_DOUBLE_CLICK_TITLEBAR:
+ return "ACTION_DOUBLE_CLICK_TITLEBAR";
+
+ case META_PREF_ACTION_MIDDLE_CLICK_TITLEBAR:
+ return "ACTION_MIDDLE_CLICK_TITLEBAR";
+
+ case META_PREF_ACTION_RIGHT_CLICK_TITLEBAR:
+ return "ACTION_RIGHT_CLICK_TITLEBAR";
+
+ case META_PREF_AUTO_RAISE:
+ return "AUTO_RAISE";
+
+ case META_PREF_AUTO_RAISE_DELAY:
+ return "AUTO_RAISE_DELAY";
+
+ case META_PREF_COMMANDS:
+ return "COMMANDS";
+
+ case META_PREF_TERMINAL_COMMAND:
+ return "TERMINAL_COMMAND";
+
+ case META_PREF_BUTTON_LAYOUT:
+ return "BUTTON_LAYOUT";
+
+ case META_PREF_WORKSPACE_NAMES:
+ return "WORKSPACE_NAMES";
+
+ case META_PREF_VISUAL_BELL:
+ return "VISUAL_BELL";
+
+ case META_PREF_AUDIBLE_BELL:
+ return "AUDIBLE_BELL";
+
+ case META_PREF_VISUAL_BELL_TYPE:
+ return "VISUAL_BELL_TYPE";
+
+ case META_PREF_REDUCED_RESOURCES:
+ return "REDUCED_RESOURCES";
+
+ case META_PREF_MATE_ACCESSIBILITY:
+ return "MATE_ACCESSIBILTY";
+
+ case META_PREF_MATE_ANIMATIONS:
+ return "MATE_ANIMATIONS";
+
+ case META_PREF_CURSOR_THEME:
+ return "CURSOR_THEME";
+
+ case META_PREF_CURSOR_SIZE:
+ return "CURSOR_SIZE";
+
+ case META_PREF_COMPOSITING_MANAGER:
+ return "COMPOSITING_MANAGER";
+
+ case META_PREF_RESIZE_WITH_RIGHT_BUTTON:
+ return "RESIZE_WITH_RIGHT_BUTTON";
+
+ case META_PREF_FORCE_FULLSCREEN:
+ return "FORCE_FULLSCREEN";
+ }
+
+ return "(unknown)";
+}
+#endif /* WITH_VERBOSE_MODE */
+
+void
+meta_prefs_set_num_workspaces (int n_workspaces)
+{
+#ifdef HAVE_MATECONF
+ GError *err;
+
+ if (default_client == NULL)
+ return;
+
+ if (n_workspaces < 1)
+ n_workspaces = 1;
+ if (n_workspaces > MAX_REASONABLE_WORKSPACES)
+ n_workspaces = MAX_REASONABLE_WORKSPACES;
+
+ err = NULL;
+ mateconf_client_set_int (default_client,
+ KEY_NUM_WORKSPACES,
+ n_workspaces,
+ &err);
+
+ if (err)
+ {
+ meta_warning (_("Error setting number of workspaces to %d: %s\n"),
+ num_workspaces,
+ err->message);
+ g_error_free (err);
+ }
+#endif /* HAVE_MATECONF */
+}
+
+#define keybind(name, handler, param, flags, stroke, description) \
+ { #name, NULL, !!(flags & BINDING_REVERSES), !!(flags & BINDING_PER_WINDOW) },
+static MetaKeyPref key_bindings[] = {
+#include "all-keybindings.h"
+ { NULL, NULL, FALSE }
+};
+#undef keybind
+
+#ifndef HAVE_MATECONF
+
+/**
+ * A type to map names of keybindings (such as "switch_windows")
+ * to the binding strings themselves (such as "<Alt>Tab").
+ * It exists only when MateConf is turned off in ./configure and
+ * functions as a sort of ersatz MateConf.
+ */
+typedef struct
+{
+ const char *name;
+ const char *keybinding;
+} MetaSimpleKeyMapping;
+
+/* FIXME: This would be neater if the array only contained entries whose
+ * default keystroke was non-null. You COULD do this by defining
+ * ONLY_BOUND_BY_DEFAULT around various blocks at the cost of making
+ * the bindings file way more complicated. However, we could stop this being
+ * data and move it into code. Then the compiler would optimise away
+ * the problem lines.
+ */
+
+#define keybind(name, handler, param, flags, stroke, description) \
+ { #name, stroke },
+
+static MetaSimpleKeyMapping key_string_bindings[] = {
+#include "all-keybindings.h"
+ { NULL, NULL }
+};
+#undef keybind
+
+#endif /* NOT HAVE_MATECONF */
+
+static void
+init_bindings (void)
+{
+#ifdef HAVE_MATECONF
+ const char *prefix[] = {
+ KEY_WINDOW_BINDINGS_PREFIX,
+ KEY_SCREEN_BINDINGS_PREFIX,
+ NULL
+ };
+ int i;
+ GSList *list, *l, *list_val;
+ const char *str_val;
+ const char *key;
+ MateConfEntry *entry;
+ MateConfValue *value;
+
+ for (i = 0; prefix[i]; i++)
+ {
+ list = mateconf_client_all_entries (default_client, prefix[i], NULL);
+ for (l = list; l; l = l->next)
+ {
+ entry = l->data;
+ key = mateconf_entry_get_key (entry);
+ value = mateconf_entry_get_value (entry);
+ if (g_str_has_suffix (key, KEY_LIST_BINDINGS_SUFFIX))
+ {
+ list_val = mateconf_client_get_list (default_client, key, MATECONF_VALUE_STRING, NULL);
+
+ update_key_list_binding (key, list_val);
+ g_slist_foreach (list_val, (GFunc)g_free, NULL);
+ g_slist_free (list_val);
+ }
+ else
+ {
+ str_val = mateconf_value_get_string (value);
+ update_key_binding (key, str_val);
+ }
+ mateconf_entry_free (entry);
+ }
+ g_slist_free (list);
+ }
+#else /* HAVE_MATECONF */
+ int i = 0;
+ int which = 0;
+ while (key_string_bindings[i].name)
+ {
+ if (key_string_bindings[i].keybinding == NULL) {
+ ++i;
+ continue;
+ }
+
+ while (strcmp(key_bindings[which].name,
+ key_string_bindings[i].name) != 0)
+ which++;
+
+ /* Set the binding */
+ update_binding (&key_bindings[which],
+ key_string_bindings[i].keybinding);
+
+ ++i;
+ }
+#endif /* HAVE_MATECONF */
+}
+
+static void
+init_commands (void)
+{
+#ifdef HAVE_MATECONF
+ GSList *list, *l;
+ const char *str_val;
+ const char *key;
+ MateConfEntry *entry;
+ MateConfValue *value;
+
+ list = mateconf_client_all_entries (default_client, KEY_COMMAND_DIRECTORY, NULL);
+ for (l = list; l; l = l->next)
+ {
+ entry = l->data;
+ key = mateconf_entry_get_key (entry);
+ value = mateconf_entry_get_value (entry);
+ str_val = mateconf_value_get_string (value);
+ update_command (key, str_val);
+ mateconf_entry_free (entry);
+ }
+ g_slist_free (list);
+#else
+ int i;
+ for (i = 0; i < MAX_COMMANDS; i++)
+ commands[i] = NULL;
+#endif /* HAVE_MATECONF */
+}
+
+static void
+init_workspace_names (void)
+{
+#ifdef HAVE_MATECONF
+ GSList *list, *l;
+ const char *str_val;
+ const char *key;
+ MateConfEntry *entry;
+ MateConfValue *value;
+
+ list = mateconf_client_all_entries (default_client, KEY_WORKSPACE_NAME_DIRECTORY, NULL);
+ for (l = list; l; l = l->next)
+ {
+ entry = l->data;
+ key = mateconf_entry_get_key (entry);
+ value = mateconf_entry_get_value (entry);
+ str_val = mateconf_value_get_string (value);
+ update_workspace_name (key, str_val);
+ mateconf_entry_free (entry);
+ }
+ g_slist_free (list);
+#else
+ int i;
+ for (i = 0; i < MAX_REASONABLE_WORKSPACES; i++)
+ workspace_names[i] = g_strdup_printf (_("Workspace %d"), i + 1);
+
+ meta_topic (META_DEBUG_PREFS,
+ "Initialized workspace names\n");
+#endif /* HAVE_MATECONF */
+}
+
+static gboolean
+update_binding (MetaKeyPref *binding,
+ const char *value)
+{
+ unsigned int keysym;
+ unsigned int keycode;
+ MetaVirtualModifier mods;
+ MetaKeyCombo *combo;
+ gboolean changed;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Binding \"%s\" has new mateconf value \"%s\"\n",
+ binding->name, value ? value : "none");
+
+ keysym = 0;
+ keycode = 0;
+ mods = 0;
+ if (value)
+ {
+ if (!meta_ui_parse_accelerator (value, &keysym, &keycode, &mods))
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Failed to parse new mateconf value\n");
+ meta_warning (_("\"%s\" found in configuration database is not a valid value for keybinding \"%s\"\n"),
+ value, binding->name);
+
+ return FALSE;
+ }
+ }
+
+ /* If there isn't already a first element, make one. */
+ if (!binding->bindings)
+ {
+ MetaKeyCombo *blank = g_malloc0 (sizeof (MetaKeyCombo));
+ binding->bindings = g_slist_alloc();
+ binding->bindings->data = blank;
+ }
+
+ combo = binding->bindings->data;
+
+#ifdef HAVE_MATECONF
+ /* Bug 329676: Bindings which can be shifted must not have no modifiers,
+ * nor only SHIFT as a modifier.
+ */
+
+ if (binding->add_shift &&
+ 0 != keysym &&
+ (META_VIRTUAL_SHIFT_MASK == mods || 0 == mods))
+ {
+ gchar *old_setting;
+ gchar *key;
+ GError *err = NULL;
+
+ meta_warning ("Cannot bind \"%s\" to %s: it needs a modifier "
+ "such as Ctrl or Alt.\n",
+ binding->name,
+ value);
+
+ old_setting = meta_ui_accelerator_name (combo->keysym,
+ combo->modifiers);
+
+ if (!strcmp(old_setting, value))
+ {
+ /* We were about to set it to the same value
+ * that it had originally! This must be caused
+ * by getting an invalid string back from
+ * meta_ui_accelerator_name. Bail out now
+ * so we don't get into an infinite loop.
+ */
+ g_free (old_setting);
+ return TRUE;
+ }
+
+ meta_warning ("Reverting \"%s\" to %s.\n",
+ binding->name,
+ old_setting);
+
+ /* FIXME: add_shift is currently screen_bindings only, but
+ * there's no really good reason it should always be.
+ * So we shouldn't blindly add KEY_SCREEN_BINDINGS_PREFIX
+ * onto here.
+ */
+ key = g_strconcat (KEY_SCREEN_BINDINGS_PREFIX, "/",
+ binding->name, NULL);
+
+ mateconf_client_set_string (mateconf_client_get_default (),
+ key, old_setting, &err);
+
+ if (err)
+ {
+ meta_warning ("Error while reverting keybinding: %s\n",
+ err->message);
+ g_error_free (err);
+ err = NULL;
+ }
+
+ g_free (old_setting);
+ g_free (key);
+
+ /* The call to mateconf_client_set_string() will cause this function
+ * to be called again with the new value, so there's no need to
+ * carry on.
+ */
+ return TRUE;
+ }
+#endif
+
+ changed = FALSE;
+ if (keysym != combo->keysym ||
+ keycode != combo->keycode ||
+ mods != combo->modifiers)
+ {
+ changed = TRUE;
+
+ combo->keysym = keysym;
+ combo->keycode = keycode;
+ combo->modifiers = mods;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "New keybinding for \"%s\" is keysym = 0x%x keycode = 0x%x mods = 0x%x\n",
+ binding->name, combo->keysym, combo->keycode,
+ combo->modifiers);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Keybinding for \"%s\" is unchanged\n", binding->name);
+ }
+
+ return changed;
+}
+
+#ifdef HAVE_MATECONF
+static gboolean
+update_list_binding (MetaKeyPref *binding,
+ GSList *value,
+ MetaStringListType type_of_value)
+{
+ unsigned int keysym;
+ unsigned int keycode;
+ MetaVirtualModifier mods;
+ gboolean changed = FALSE;
+ const gchar *pref_string;
+ GSList *pref_iterator = value, *tmp;
+ MetaKeyCombo *combo;
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Binding \"%s\" has new mateconf value\n",
+ binding->name);
+
+ if (binding->bindings == NULL)
+ {
+ /* We need to insert a dummy element into the list, because the first
+ * element is the one governed by update_binding. We only handle the
+ * subsequent elements.
+ */
+ MetaKeyCombo *blank = g_malloc0 (sizeof (MetaKeyCombo));
+ binding->bindings = g_slist_alloc();
+ binding->bindings->data = blank;
+ }
+
+ /* Okay, so, we're about to provide a new list of key combos for this
+ * action. Delete any pre-existing list.
+ */
+ tmp = binding->bindings->next;
+ while (tmp)
+ {
+ g_free (tmp->data);
+ tmp = tmp->next;
+ }
+ g_slist_free (binding->bindings->next);
+ binding->bindings->next = NULL;
+
+ while (pref_iterator)
+ {
+ keysym = 0;
+ keycode = 0;
+ mods = 0;
+
+ if (!pref_iterator->data)
+ {
+ pref_iterator = pref_iterator->next;
+ continue;
+ }
+
+ switch (type_of_value)
+ {
+ case META_LIST_OF_STRINGS:
+ pref_string = pref_iterator->data;
+ break;
+ case META_LIST_OF_MATECONFVALUE_STRINGS:
+ pref_string = mateconf_value_get_string (pref_iterator->data);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (!meta_ui_parse_accelerator (pref_string, &keysym, &keycode, &mods))
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Failed to parse new mateconf value\n");
+ meta_warning (_("\"%s\" found in configuration database is not a valid value for keybinding \"%s\"\n"),
+ pref_string, binding->name);
+
+ /* Should we remove this value from the list in mateconf? */
+ pref_iterator = pref_iterator->next;
+ continue;
+ }
+
+ /* Bug 329676: Bindings which can be shifted must not have no modifiers,
+ * nor only SHIFT as a modifier.
+ */
+
+ if (binding->add_shift &&
+ 0 != keysym &&
+ (META_VIRTUAL_SHIFT_MASK == mods || 0 == mods))
+ {
+ meta_warning ("Cannot bind \"%s\" to %s: it needs a modifier "
+ "such as Ctrl or Alt.\n",
+ binding->name,
+ pref_string);
+
+ /* Should we remove this value from the list in mateconf? */
+
+ pref_iterator = pref_iterator->next;
+ continue;
+ }
+
+ changed = TRUE;
+
+ combo = g_malloc0 (sizeof (MetaKeyCombo));
+ combo->keysym = keysym;
+ combo->keycode = keycode;
+ combo->modifiers = mods;
+ binding->bindings->next = g_slist_prepend (binding->bindings->next, combo);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "New keybinding for \"%s\" is keysym = 0x%x keycode = 0x%x mods = 0x%x\n",
+ binding->name, keysym, keycode, mods);
+
+ pref_iterator = pref_iterator->next;
+ }
+ return changed;
+}
+
+static const gchar*
+relative_key (const gchar* key)
+{
+ const gchar* end;
+
+ end = strrchr (key, '/');
+
+ ++end;
+
+ return end;
+}
+
+/* Return value is TRUE if a preference changed and we need to
+ * notify
+ */
+static gboolean
+find_and_update_binding (MetaKeyPref *bindings,
+ const char *name,
+ const char *value)
+{
+ const char *key;
+ int i;
+
+ if (*name == '/')
+ key = relative_key (name);
+ else
+ key = name;
+
+ i = 0;
+ while (bindings[i].name &&
+ strcmp (key, bindings[i].name) != 0)
+ ++i;
+
+ if (bindings[i].name)
+ return update_binding (&bindings[i], value);
+ else
+ return FALSE;
+}
+
+static gboolean
+update_key_binding (const char *name,
+ const char *value)
+{
+ return find_and_update_binding (key_bindings, name, value);
+}
+
+static gboolean
+find_and_update_list_binding (MetaKeyPref *bindings,
+ const char *name,
+ GSList *value)
+{
+ const char *key;
+ int i;
+ gchar *name_without_suffix = g_strdup(name);
+
+ name_without_suffix[strlen(name_without_suffix) - strlen(KEY_LIST_BINDINGS_SUFFIX)] = 0;
+
+ if (*name_without_suffix == '/')
+ key = relative_key (name_without_suffix);
+ else
+ key = name_without_suffix;
+
+ i = 0;
+ while (bindings[i].name &&
+ strcmp (key, bindings[i].name) != 0)
+ ++i;
+
+ g_free (name_without_suffix);
+
+ if (bindings[i].name)
+ return update_list_binding (&bindings[i], value, META_LIST_OF_MATECONFVALUE_STRINGS);
+ else
+ return FALSE;
+}
+
+static gboolean
+update_key_list_binding (const char *name,
+ GSList *value)
+{
+ return find_and_update_list_binding (key_bindings, name, value);
+}
+
+static gboolean
+update_command (const char *name,
+ const char *value)
+{
+ char *p;
+ int i;
+
+ p = strrchr (name, '_');
+ if (p == NULL)
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Command %s has no underscore?\n", name);
+ return FALSE;
+ }
+
+ ++p;
+
+ if (g_ascii_isdigit (*p))
+ {
+ i = atoi (p);
+ i -= 1; /* count from 0 not 1 */
+ }
+ else
+ {
+ p = strrchr (name, '/');
+ ++p;
+
+ if (strcmp (p, "command_screenshot") == 0)
+ {
+ i = SCREENSHOT_COMMAND_IDX;
+ }
+ else if (strcmp (p, "command_window_screenshot") == 0)
+ {
+ i = WIN_SCREENSHOT_COMMAND_IDX;
+ }
+ else
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Command %s doesn't end in number?\n", name);
+ return FALSE;
+ }
+ }
+
+ if (i >= MAX_COMMANDS)
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Command %d is too highly numbered, ignoring\n", i);
+ return FALSE;
+ }
+
+ if ((commands[i] == NULL && value == NULL) ||
+ (commands[i] && value && strcmp (commands[i], value) == 0))
+ {
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Command %d is unchanged\n", i);
+ return FALSE;
+ }
+
+ g_free (commands[i]);
+ commands[i] = g_strdup (value);
+
+ meta_topic (META_DEBUG_KEYBINDINGS,
+ "Updated command %d to \"%s\"\n",
+ i, commands[i] ? commands[i] : "none");
+
+ return TRUE;
+}
+
+#endif /* HAVE_MATECONF */
+
+const char*
+meta_prefs_get_command (int i)
+{
+ g_return_val_if_fail (i >= 0 && i < MAX_COMMANDS, NULL);
+
+ return commands[i];
+}
+
+char*
+meta_prefs_get_mateconf_key_for_command (int i)
+{
+ char *key;
+
+ switch (i)
+ {
+ case SCREENSHOT_COMMAND_IDX:
+ key = g_strdup (KEY_COMMAND_PREFIX "screenshot");
+ break;
+ case WIN_SCREENSHOT_COMMAND_IDX:
+ key = g_strdup (KEY_COMMAND_PREFIX "window_screenshot");
+ break;
+ default:
+ key = g_strdup_printf (KEY_COMMAND_PREFIX"%d", i + 1);
+ break;
+ }
+
+ return key;
+}
+
+const char*
+meta_prefs_get_terminal_command (void)
+{
+ return terminal_command;
+}
+
+const char*
+meta_prefs_get_mateconf_key_for_terminal_command (void)
+{
+ return KEY_TERMINAL_COMMAND;
+}
+
+#ifdef HAVE_MATECONF
+static gboolean
+update_workspace_name (const char *name,
+ const char *value)
+{
+ char *p;
+ int i;
+
+ p = strrchr (name, '_');
+ if (p == NULL)
+ {
+ meta_topic (META_DEBUG_PREFS,
+ "Workspace name %s has no underscore?\n", name);
+ return FALSE;
+ }
+
+ ++p;
+
+ if (!g_ascii_isdigit (*p))
+ {
+ meta_topic (META_DEBUG_PREFS,
+ "Workspace name %s doesn't end in number?\n", name);
+ return FALSE;
+ }
+
+ i = atoi (p);
+ i -= 1; /* count from 0 not 1 */
+
+ if (i >= MAX_REASONABLE_WORKSPACES)
+ {
+ meta_topic (META_DEBUG_PREFS,
+ "Workspace name %d is too highly numbered, ignoring\n", i);
+ return FALSE;
+ }
+
+ if (workspace_names[i] && value && strcmp (workspace_names[i], value) == 0)
+ {
+ meta_topic (META_DEBUG_PREFS,
+ "Workspace name %d is unchanged\n", i);
+ return FALSE;
+ }
+
+ /* This is a bad hack. We have to treat empty string as
+ * "unset" because the root window property can't contain
+ * null. So it gets empty string instead and we don't want
+ * that to result in setting the empty string as a value that
+ * overrides "unset".
+ */
+ if (value != NULL && *value != '\0')
+ {
+ g_free (workspace_names[i]);
+ workspace_names[i] = g_strdup (value);
+ }
+ else
+ {
+ /* use a default name */
+ char *d;
+
+ d = g_strdup_printf (_("Workspace %d"), i + 1);
+ if (workspace_names[i] && strcmp (workspace_names[i], d) == 0)
+ {
+ g_free (d);
+ return FALSE;
+ }
+ else
+ {
+ g_free (workspace_names[i]);
+ workspace_names[i] = d;
+ }
+ }
+
+ meta_topic (META_DEBUG_PREFS,
+ "Updated workspace name %d to \"%s\"\n",
+ i, workspace_names[i] ? workspace_names[i] : "none");
+
+ return TRUE;
+}
+#endif /* HAVE_MATECONF */
+
+const char*
+meta_prefs_get_workspace_name (int i)
+{
+ g_return_val_if_fail (i >= 0 && i < MAX_REASONABLE_WORKSPACES, NULL);
+
+ g_assert (workspace_names[i] != NULL);
+
+ meta_topic (META_DEBUG_PREFS,
+ "Getting workspace name for %d: \"%s\"\n",
+ i, workspace_names[i]);
+
+ return workspace_names[i];
+}
+
+void
+meta_prefs_change_workspace_name (int i,
+ const char *name)
+{
+#ifdef HAVE_MATECONF
+ char *key;
+ GError *err;
+
+ g_return_if_fail (i >= 0 && i < MAX_REASONABLE_WORKSPACES);
+
+ meta_topic (META_DEBUG_PREFS,
+ "Changing name of workspace %d to %s\n",
+ i, name ? name : "none");
+
+ /* This is a bad hack. We have to treat empty string as
+ * "unset" because the root window property can't contain
+ * null. So it gets empty string instead and we don't want
+ * that to result in setting the empty string as a value that
+ * overrides "unset".
+ */
+ if (name && *name == '\0')
+ name = NULL;
+
+ if ((name == NULL && workspace_names[i] == NULL) ||
+ (name && workspace_names[i] && strcmp (name, workspace_names[i]) == 0))
+ {
+ meta_topic (META_DEBUG_PREFS,
+ "Workspace %d already has name %s\n",
+ i, name ? name : "none");
+ return;
+ }
+
+ key = mateconf_key_for_workspace_name (i);
+
+ err = NULL;
+ if (name != NULL)
+ mateconf_client_set_string (default_client,
+ key, name,
+ &err);
+ else
+ mateconf_client_unset (default_client,
+ key, &err);
+
+
+ if (err)
+ {
+ meta_warning (_("Error setting name for workspace %d to \"%s\": %s\n"),
+ i, name ? name : "none",
+ err->message);
+ g_error_free (err);
+ }
+
+ g_free (key);
+#else
+ g_free (workspace_names[i]);
+ workspace_names[i] = g_strdup (name);
+#endif /* HAVE_MATECONF */
+}
+
+#ifdef HAVE_MATECONF
+static char*
+mateconf_key_for_workspace_name (int i)
+{
+ char *key;
+
+ key = g_strdup_printf (KEY_WORKSPACE_NAME_PREFIX"%d", i + 1);
+
+ return key;
+}
+#endif /* HAVE_MATECONF */
+
+void
+meta_prefs_get_button_layout (MetaButtonLayout *button_layout_p)
+{
+ *button_layout_p = button_layout;
+}
+
+gboolean
+meta_prefs_get_visual_bell (void)
+{
+ return provide_visual_bell;
+}
+
+gboolean
+meta_prefs_bell_is_audible (void)
+{
+ return bell_is_audible;
+}
+
+MetaVisualBellType
+meta_prefs_get_visual_bell_type (void)
+{
+ return visual_bell_type;
+}
+
+void
+meta_prefs_get_key_bindings (const MetaKeyPref **bindings,
+ int *n_bindings)
+{
+
+ *bindings = key_bindings;
+ *n_bindings = (int) G_N_ELEMENTS (key_bindings) - 1;
+}
+
+MetaActionTitlebar
+meta_prefs_get_action_double_click_titlebar (void)
+{
+ return action_double_click_titlebar;
+}
+
+MetaActionTitlebar
+meta_prefs_get_action_middle_click_titlebar (void)
+{
+ return action_middle_click_titlebar;
+}
+
+MetaActionTitlebar
+meta_prefs_get_action_right_click_titlebar (void)
+{
+ return action_right_click_titlebar;
+}
+
+gboolean
+meta_prefs_get_auto_raise (void)
+{
+ return auto_raise;
+}
+
+int
+meta_prefs_get_auto_raise_delay (void)
+{
+ return auto_raise_delay;
+}
+
+gboolean
+meta_prefs_get_reduced_resources (void)
+{
+ return reduced_resources;
+}
+
+gboolean
+meta_prefs_get_mate_accessibility ()
+{
+ return mate_accessibility;
+}
+
+gboolean
+meta_prefs_get_mate_animations ()
+{
+ return mate_animations;
+}
+
+MetaKeyBindingAction
+meta_prefs_get_keybinding_action (const char *name)
+{
+ int i;
+
+ i = G_N_ELEMENTS (key_bindings) - 2; /* -2 for dummy entry at end */
+ while (i >= 0)
+ {
+ if (strcmp (key_bindings[i].name, name) == 0)
+ return (MetaKeyBindingAction) i;
+
+ --i;
+ }
+
+ return META_KEYBINDING_ACTION_NONE;
+}
+
+/* This is used by the menu system to decide what key binding
+ * to display next to an option. We return the first non-disabled
+ * binding, if any.
+ */
+void
+meta_prefs_get_window_binding (const char *name,
+ unsigned int *keysym,
+ MetaVirtualModifier *modifiers)
+{
+ int i;
+
+ i = G_N_ELEMENTS (key_bindings) - 2; /* -2 for dummy entry at end */
+ while (i >= 0)
+ {
+ if (key_bindings[i].per_window &&
+ strcmp (key_bindings[i].name, name) == 0)
+ {
+ GSList *s = key_bindings[i].bindings;
+
+ while (s)
+ {
+ MetaKeyCombo *c = s->data;
+
+ if (c->keysym!=0 || c->modifiers!=0)
+ {
+ *keysym = c->keysym;
+ *modifiers = c->modifiers;
+ return;
+ }
+
+ s = s->next;
+ }
+
+ /* Not found; return the disabled value */
+ *keysym = *modifiers = 0;
+ return;
+ }
+
+ --i;
+ }
+
+ g_assert_not_reached ();
+}
+
+gboolean
+meta_prefs_get_compositing_manager (void)
+{
+ return compositing_manager;
+}
+
+guint
+meta_prefs_get_mouse_button_resize (void)
+{
+ return resize_with_right_button ? 3: 2;
+}
+
+guint
+meta_prefs_get_mouse_button_menu (void)
+{
+ return resize_with_right_button ? 2: 3;
+}
+
+gboolean
+meta_prefs_get_force_fullscreen (void)
+{
+ return force_fullscreen;
+}
+
+void
+meta_prefs_set_compositing_manager (gboolean whether)
+{
+#ifdef HAVE_MATECONF
+ GError *err = NULL;
+
+ mateconf_client_set_bool (default_client,
+ KEY_COMPOSITOR,
+ whether,
+ &err);
+
+ if (err)
+ {
+ meta_warning (_("Error setting compositor status: %s\n"),
+ err->message);
+ g_error_free (err);
+ }
+#else
+ compositing_manager = whether;
+#endif
+}
+
+#ifndef HAVE_MATECONF
+static void
+init_button_layout(void)
+{
+ MetaButtonLayout button_layout_ltr = {
+ {
+ /* buttons in the group on the left side */
+ META_BUTTON_FUNCTION_MENU,
+ META_BUTTON_FUNCTION_LAST
+ },
+ {
+ /* buttons in the group on the right side */
+ META_BUTTON_FUNCTION_MINIMIZE,
+ META_BUTTON_FUNCTION_MAXIMIZE,
+ META_BUTTON_FUNCTION_CLOSE,
+ META_BUTTON_FUNCTION_LAST
+ }
+ };
+ MetaButtonLayout button_layout_rtl = {
+ {
+ /* buttons in the group on the left side */
+ META_BUTTON_FUNCTION_CLOSE,
+ META_BUTTON_FUNCTION_MAXIMIZE,
+ META_BUTTON_FUNCTION_MINIMIZE,
+ META_BUTTON_FUNCTION_LAST
+ },
+ {
+ /* buttons in the group on the right side */
+ META_BUTTON_FUNCTION_MENU,
+ META_BUTTON_FUNCTION_LAST
+ }
+ };
+
+ button_layout = meta_ui_get_direction() == META_UI_DIRECTION_LTR ?
+ button_layout_ltr : button_layout_rtl;
+};
+
+#endif
+
+void
+meta_prefs_set_force_fullscreen (gboolean whether)
+{
+ force_fullscreen = whether;
+}
+
diff --git a/src/core/schema-bindings.c b/src/core/schema-bindings.c
new file mode 100644
index 00000000..2f1da821
--- /dev/null
+++ b/src/core/schema-bindings.c
@@ -0,0 +1,195 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2008 Thomas Thurman
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/** \file Schema bindings generator.
+ *
+ * This program simply takes the items given in the binding list in
+ * all-keybindings.h and turns them into a portion of
+ * the MateConf .schemas file.
+ *
+ * FIXME: also need to make 50-marco-desktop-key.xml
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <glib.h>
+#include "config.h"
+
+#define _(x) x
+
+static void single_stanza (gboolean is_window, const char *name,
+ const char *default_value,
+ gboolean can_reverse,
+ const char *description);
+
+char *about_keybindings, *about_reversible_keybindings;
+
+char *source_filename, *target_filename;
+FILE *source_file, *target_file;
+
+static void
+single_stanza (gboolean is_window, const char *name,
+ const char *default_value,
+ gboolean can_reverse,
+ const char *description)
+{
+ char *keybinding_type = is_window? "window": "global";
+ char *escaped_default_value, *escaped_description;
+
+ if (description==NULL)
+ return; /* it must be undocumented, so it can't go in this table */
+
+ escaped_description = g_markup_escape_text (description, -1);
+ escaped_default_value = default_value==NULL? "disabled":
+ g_markup_escape_text (default_value, -1);
+
+ fprintf (target_file, " <schema>\n");
+ fprintf (target_file, " <key>/schemas/apps/marco/%s_keybindings/%s</key>\n",
+ keybinding_type, name);
+ fprintf (target_file, " <applyto>/apps/marco/%s_keybindings/%s</applyto>\n",
+ keybinding_type, name);
+ fprintf (target_file, " <owner>marco</owner>\n");
+ fprintf (target_file, " <type>string</type>\n");
+ fprintf (target_file, " <default>%s</default>\n", escaped_default_value);
+
+ fprintf (target_file, " <locale name=\"C\">\n");
+ fprintf (target_file, " <short>%s</short>\n", description);
+ fprintf (target_file, " <long>%s</long>\n",
+ can_reverse? about_reversible_keybindings:
+ about_keybindings);
+ fprintf (target_file, " </locale>\n");
+ fprintf (target_file, " </schema>\n\n");
+
+ g_free (escaped_description);
+
+ if (default_value!=NULL)
+ g_free (escaped_default_value);
+}
+
+static void produce_bindings ();
+
+static void
+produce_bindings ()
+{
+ /* 10240 is ridiculous overkill; we're writing the input file and
+ * the lines are always 80 chars or less.
+ */
+ char buffer[10240];
+
+ source_file = fopen(source_filename, "r");
+
+ if (!source_file)
+ {
+ g_error ("Cannot compile without %s: %s\n",
+ source_filename, strerror (errno));
+ }
+
+ target_file = fopen(target_filename, "w");
+
+ if (!target_file)
+ {
+ g_error ("Cannot create %s: %s\n",
+ target_filename, strerror (errno));
+ }
+
+ while (fgets (buffer, sizeof (buffer), source_file))
+ {
+ if (strstr (buffer, "<!-- GENERATED -->"))
+ break;
+
+ fprintf (target_file, "%s", buffer);
+ }
+
+ if (!feof (source_file))
+ {
+#define keybind(name, handler, param, flags, stroke, description) \
+ single_stanza ( \
+ flags & BINDING_PER_WINDOW, \
+ #name, \
+ stroke, \
+ flags & BINDING_REVERSES, \
+ description);
+#include "all-keybindings.h"
+#undef keybind
+ }
+
+ while (fgets (buffer, sizeof (buffer), source_file))
+ fprintf (target_file, "%s", buffer);
+
+ if (fclose (source_file)!=0)
+ {
+ g_error ("Cannot close %s: %s\n",
+ source_filename, strerror (errno));
+ }
+
+ if (fclose (target_file)!=0)
+ {
+ g_error ("Cannot close %s: %s\n",
+ target_filename, strerror (errno));
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ if (argc!=3)
+ {
+ g_error ("Syntax: %s <source.in.in> <target.in>\n", argv[0]);
+ }
+
+ source_filename = argv[1];
+ target_filename = argv[2];
+
+ /* Translators: Please don't translate "Control", "Shift", etc, since these
+ * are hardcoded (in gtk/gtkaccelgroup.c; it's not marco's fault).
+ * "disabled" must also stay as it is.
+ */
+ about_keybindings = g_markup_escape_text(_( \
+ "The format looks like \"<Control>a\" or \"<Shift><Alt>F1\".\n\n"\
+ "The parser is fairly liberal and allows "\
+ "lower or upper case, and also abbreviations such as \"<Ctl>\" and " \
+ "\"<Ctrl>\". If you set the option to the special string " \
+ "\"disabled\", then there will be no keybinding for this action."),
+ -1);
+
+ about_reversible_keybindings = g_markup_escape_text(_( \
+ "The format looks like \"<Control>a\" or \"<Shift><Alt>F1\".\n\n"\
+ "The parser is fairly liberal and allows "\
+ "lower or upper case, and also abbreviations such as \"<Ctl>\" and " \
+ "\"<Ctrl>\". If you set the option to the special string " \
+ "\"disabled\", then there will be no keybinding for this action.\n\n"\
+ "This keybinding may be reversed by holding down the \"shift\" key; "
+ "therefore, \"shift\" cannot be one of the keys it uses."),
+ -1);
+
+ produce_bindings ();
+
+ g_free (about_keybindings);
+ g_free (about_reversible_keybindings);
+
+ return 0;
+}
+
+/* eof schema-bindings.c */
+
diff --git a/src/core/screen-private.h b/src/core/screen-private.h
new file mode 100644
index 00000000..30941eab
--- /dev/null
+++ b/src/core/screen-private.h
@@ -0,0 +1,226 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file screen-private.h Screens which Marco manages
+ *
+ * Managing X screens.
+ * This file contains methods on this class which are available to
+ * routines in core but not outside it. (See screen.h for the routines
+ * which the rest of the world is allowed to use.)
+ */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2003 Rob Adams
+ * Copyright (C) 2004-2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_SCREEN_PRIVATE_H
+#define META_SCREEN_PRIVATE_H
+
+#include "display-private.h"
+#include "screen.h"
+#include <X11/Xutil.h>
+#include "ui.h"
+
+typedef struct _MetaXineramaScreenInfo MetaXineramaScreenInfo;
+
+struct _MetaXineramaScreenInfo
+{
+ int number;
+ MetaRectangle rect;
+};
+
+typedef void (* MetaScreenWindowFunc) (MetaScreen *screen, MetaWindow *window,
+ gpointer user_data);
+
+typedef enum
+{
+ META_SCREEN_TOPLEFT,
+ META_SCREEN_TOPRIGHT,
+ META_SCREEN_BOTTOMLEFT,
+ META_SCREEN_BOTTOMRIGHT
+} MetaScreenCorner;
+
+typedef enum
+{
+ META_SCREEN_UP,
+ META_SCREEN_DOWN,
+ META_SCREEN_LEFT,
+ META_SCREEN_RIGHT
+} MetaScreenDirection;
+
+#define META_WIREFRAME_XOR_LINE_WIDTH 2
+
+struct _MetaScreen
+{
+ MetaDisplay *display;
+ int number;
+ char *screen_name;
+ Screen *xscreen;
+ Window xroot;
+ int default_depth;
+ Visual *default_xvisual;
+ MetaRectangle rect; /* Size of screen; rect.x & rect.y are always 0 */
+ MetaUI *ui;
+ MetaTabPopup *tab_popup;
+
+ MetaWorkspace *active_workspace;
+
+ /* This window holds the focus when we don't want to focus
+ * any actual clients
+ */
+ Window no_focus_window;
+
+ GList *workspaces;
+
+ MetaStack *stack;
+
+ MetaCursor current_cursor;
+
+ Window flash_window;
+
+ Window wm_sn_selection_window;
+ Atom wm_sn_atom;
+ guint32 wm_sn_timestamp;
+
+ MetaXineramaScreenInfo *xinerama_infos;
+ int n_xinerama_infos;
+
+ /* Cache the current Xinerama */
+ int last_xinerama_index;
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ SnMonitorContext *sn_context;
+ GSList *startup_sequences;
+ guint startup_sequence_timeout;
+#endif
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ Window wm_cm_selection_window;
+ guint32 wm_cm_timestamp;
+#endif
+
+ guint work_area_idle;
+
+ int rows_of_workspaces;
+ int columns_of_workspaces;
+ MetaScreenCorner starting_corner;
+ guint vertical_workspaces : 1;
+
+ guint keys_grabbed : 1;
+ guint all_keys_grabbed : 1;
+
+ int closing;
+
+ /* gc for XOR on root window */
+ GC root_xor_gc;
+
+ /* Managed by compositor.c */
+ gpointer compositor_data;
+};
+
+MetaScreen* meta_screen_new (MetaDisplay *display,
+ int number,
+ guint32 timestamp);
+void meta_screen_free (MetaScreen *screen,
+ guint32 timestamp);
+void meta_screen_manage_all_windows (MetaScreen *screen);
+void meta_screen_foreach_window (MetaScreen *screen,
+ MetaScreenWindowFunc func,
+ gpointer data);
+void meta_screen_queue_frame_redraws (MetaScreen *screen);
+void meta_screen_queue_window_resizes (MetaScreen *screen);
+
+int meta_screen_get_n_workspaces (MetaScreen *screen);
+
+MetaWorkspace* meta_screen_get_workspace_by_index (MetaScreen *screen,
+ int index);
+
+void meta_screen_set_cursor (MetaScreen *screen,
+ MetaCursor cursor);
+void meta_screen_update_cursor (MetaScreen *screen);
+
+void meta_screen_ensure_tab_popup (MetaScreen *screen,
+ MetaTabList list_type,
+ MetaTabShowType show_type);
+void meta_screen_ensure_workspace_popup (MetaScreen *screen);
+
+MetaWindow* meta_screen_get_mouse_window (MetaScreen *screen,
+ MetaWindow *not_this_one);
+
+const MetaXineramaScreenInfo* meta_screen_get_current_xinerama (MetaScreen *screen);
+const MetaXineramaScreenInfo* meta_screen_get_xinerama_for_rect (MetaScreen *screen,
+ MetaRectangle *rect);
+const MetaXineramaScreenInfo* meta_screen_get_xinerama_for_window (MetaScreen *screen,
+ MetaWindow *window);
+
+
+const MetaXineramaScreenInfo* meta_screen_get_xinerama_neighbor (MetaScreen *screen,
+ int which_xinerama,
+ MetaScreenDirection dir);
+void meta_screen_get_natural_xinerama_list (MetaScreen *screen,
+ int** xineramas_list,
+ int* n_xineramas);
+
+void meta_screen_update_workspace_layout (MetaScreen *screen);
+void meta_screen_update_workspace_names (MetaScreen *screen);
+void meta_screen_queue_workarea_recalc (MetaScreen *screen);
+
+Window meta_create_offscreen_window (Display *xdisplay,
+ Window parent,
+ long valuemask);
+
+typedef struct MetaWorkspaceLayout MetaWorkspaceLayout;
+
+struct MetaWorkspaceLayout
+{
+ int rows;
+ int cols;
+ int *grid;
+ int grid_area;
+ int current_row;
+ int current_col;
+};
+
+void meta_screen_calc_workspace_layout (MetaScreen *screen,
+ int num_workspaces,
+ int current_space,
+ MetaWorkspaceLayout *layout);
+void meta_screen_free_workspace_layout (MetaWorkspaceLayout *layout);
+
+void meta_screen_resize (MetaScreen *screen,
+ int width,
+ int height);
+
+void meta_screen_minimize_all_on_active_workspace_except (MetaScreen *screen,
+ MetaWindow *keep);
+
+/* Show/hide the desktop (temporarily hide all windows) */
+void meta_screen_show_desktop (MetaScreen *screen,
+ guint32 timestamp);
+void meta_screen_unshow_desktop (MetaScreen *screen);
+
+/* Update whether the destkop is being shown for the current active_workspace */
+void meta_screen_update_showing_desktop_hint (MetaScreen *screen);
+
+gboolean meta_screen_apply_startup_properties (MetaScreen *screen,
+ MetaWindow *window);
+void meta_screen_composite_all_windows (MetaScreen *screen);
+
+#endif
diff --git a/src/core/screen.c b/src/core/screen.c
new file mode 100644
index 00000000..f9db9c71
--- /dev/null
+++ b/src/core/screen.c
@@ -0,0 +1,2815 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco X screen handler */
+
+/*
+ * Copyright (C) 2001, 2002 Havoc Pennington
+ * Copyright (C) 2002, 2003 Red Hat Inc.
+ * Some ICCCM manager selection code derived from fvwm2,
+ * Copyright (C) 2001 Dominik Vogt, Matthias Clasen, and fvwm2 team
+ * Copyright (C) 2003 Rob Adams
+ * Copyright (C) 2004-2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "screen-private.h"
+#include "util.h"
+#include "errors.h"
+#include "window-private.h"
+#include "frame-private.h"
+#include "prefs.h"
+#include "workspace.h"
+#include "keybindings.h"
+#include "stack.h"
+#include "xprops.h"
+#include "compositor.h"
+
+#ifdef HAVE_SOLARIS_XINERAMA
+#include <X11/extensions/xinerama.h>
+#endif
+#ifdef HAVE_XFREE_XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+
+#include <X11/Xatom.h>
+#include <locale.h>
+#include <string.h>
+#include <stdio.h>
+
+static char* get_screen_name (MetaDisplay *display,
+ int number);
+
+static void update_num_workspaces (MetaScreen *screen,
+ guint32 timestamp);
+static void update_focus_mode (MetaScreen *screen);
+static void set_workspace_names (MetaScreen *screen);
+static void prefs_changed_callback (MetaPreference pref,
+ gpointer data);
+
+static void set_desktop_geometry_hint (MetaScreen *screen);
+static void set_desktop_viewport_hint (MetaScreen *screen);
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+static void meta_screen_sn_event (SnMonitorEvent *event,
+ void *user_data);
+#endif
+
+static int
+set_wm_check_hint (MetaScreen *screen)
+{
+ unsigned long data[1];
+
+ g_return_val_if_fail (screen->display->leader_window != None, 0);
+
+ data[0] = screen->display->leader_window;
+
+ XChangeProperty (screen->display->xdisplay, screen->xroot,
+ screen->display->atom__NET_SUPPORTING_WM_CHECK,
+ XA_WINDOW,
+ 32, PropModeReplace, (guchar*) data, 1);
+
+ return Success;
+}
+
+static void
+unset_wm_check_hint (MetaScreen *screen)
+{
+ XDeleteProperty (screen->display->xdisplay, screen->xroot,
+ screen->display->atom__NET_SUPPORTING_WM_CHECK);
+}
+
+static int
+set_supported_hint (MetaScreen *screen)
+{
+ Atom atoms[] = {
+#define EWMH_ATOMS_ONLY
+#define item(x) screen->display->atom_##x,
+#include "atomnames.h"
+#undef item
+#undef EWMH_ATOMS_ONLY
+ };
+
+ XChangeProperty (screen->display->xdisplay, screen->xroot,
+ screen->display->atom__NET_SUPPORTED,
+ XA_ATOM,
+ 32, PropModeReplace,
+ (guchar*) atoms, G_N_ELEMENTS(atoms));
+
+ return Success;
+}
+
+static int
+set_wm_icon_size_hint (MetaScreen *screen)
+{
+#define N_VALS 6
+ gulong vals[N_VALS];
+
+ /* min width, min height, max w, max h, width inc, height inc */
+ vals[0] = META_ICON_WIDTH;
+ vals[1] = META_ICON_HEIGHT;
+ vals[2] = META_ICON_WIDTH;
+ vals[3] = META_ICON_HEIGHT;
+ vals[4] = 0;
+ vals[5] = 0;
+
+ XChangeProperty (screen->display->xdisplay, screen->xroot,
+ screen->display->atom_WM_ICON_SIZE,
+ XA_CARDINAL,
+ 32, PropModeReplace, (guchar*) vals, N_VALS);
+
+ return Success;
+#undef N_VALS
+}
+
+static void
+reload_xinerama_infos (MetaScreen *screen)
+{
+ MetaDisplay *display;
+
+ {
+ GList *tmp;
+
+ tmp = screen->workspaces;
+ while (tmp != NULL)
+ {
+ MetaWorkspace *space = tmp->data;
+
+ meta_workspace_invalidate_work_area (space);
+
+ tmp = tmp->next;
+ }
+ }
+
+ display = screen->display;
+
+ if (screen->xinerama_infos)
+ g_free (screen->xinerama_infos);
+
+ screen->xinerama_infos = NULL;
+ screen->n_xinerama_infos = 0;
+ screen->last_xinerama_index = 0;
+
+ screen->display->xinerama_cache_invalidated = TRUE;
+
+#ifdef HAVE_XFREE_XINERAMA
+ if (XineramaIsActive (display->xdisplay))
+ {
+ XineramaScreenInfo *infos;
+ int n_infos;
+ int i;
+
+ n_infos = 0;
+ infos = XineramaQueryScreens (display->xdisplay, &n_infos);
+
+ meta_topic (META_DEBUG_XINERAMA,
+ "Found %d Xinerama screens on display %s\n",
+ n_infos, display->name);
+
+ if (n_infos > 0)
+ {
+ screen->xinerama_infos = g_new (MetaXineramaScreenInfo, n_infos);
+ screen->n_xinerama_infos = n_infos;
+
+ i = 0;
+ while (i < n_infos)
+ {
+ screen->xinerama_infos[i].number = infos[i].screen_number;
+ screen->xinerama_infos[i].rect.x = infos[i].x_org;
+ screen->xinerama_infos[i].rect.y = infos[i].y_org;
+ screen->xinerama_infos[i].rect.width = infos[i].width;
+ screen->xinerama_infos[i].rect.height = infos[i].height;
+
+ meta_topic (META_DEBUG_XINERAMA,
+ "Xinerama %d is %d,%d %d x %d\n",
+ screen->xinerama_infos[i].number,
+ screen->xinerama_infos[i].rect.x,
+ screen->xinerama_infos[i].rect.y,
+ screen->xinerama_infos[i].rect.width,
+ screen->xinerama_infos[i].rect.height);
+
+ ++i;
+ }
+ }
+
+ meta_XFree (infos);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_XINERAMA,
+ "No XFree86 Xinerama extension or XFree86 Xinerama inactive on display %s\n",
+ display->name);
+ }
+#else
+ meta_topic (META_DEBUG_XINERAMA,
+ "Marco compiled without XFree86 Xinerama support\n");
+#endif /* HAVE_XFREE_XINERAMA */
+
+#ifdef HAVE_SOLARIS_XINERAMA
+ /* This code from GDK, Copyright (C) 2002 Sun Microsystems */
+ if (screen->n_xinerama_infos == 0 &&
+ XineramaGetState (screen->display->xdisplay,
+ screen->number))
+ {
+ XRectangle monitors[MAXFRAMEBUFFERS];
+ unsigned char hints[16];
+ int result;
+ int n_monitors;
+ int i;
+
+ n_monitors = 0;
+ result = XineramaGetInfo (screen->display->xdisplay,
+ screen->number,
+ monitors, hints,
+ &n_monitors);
+ /* Yes I know it should be Success but the current implementation
+ * returns the num of monitor
+ */
+ if (result > 0)
+ {
+ g_assert (n_monitors > 0);
+
+ screen->xinerama_infos = g_new (MetaXineramaScreenInfo, n_monitors);
+ screen->n_xinerama_infos = n_monitors;
+
+ i = 0;
+ while (i < n_monitors)
+ {
+ screen->xinerama_infos[i].number = i;
+ screen->xinerama_infos[i].rect.x = monitors[i].x;
+ screen->xinerama_infos[i].rect.y = monitors[i].y;
+ screen->xinerama_infos[i].rect.width = monitors[i].width;
+ screen->xinerama_infos[i].rect.height = monitors[i].height;
+
+ meta_topic (META_DEBUG_XINERAMA,
+ "Xinerama %d is %d,%d %d x %d\n",
+ screen->xinerama_infos[i].number,
+ screen->xinerama_infos[i].rect.x,
+ screen->xinerama_infos[i].rect.y,
+ screen->xinerama_infos[i].rect.width,
+ screen->xinerama_infos[i].rect.height);
+
+ ++i;
+ }
+ }
+ }
+ else if (screen->n_xinerama_infos == 0)
+ {
+ meta_topic (META_DEBUG_XINERAMA,
+ "No Solaris Xinerama extension or Solaris Xinerama inactive on display %s\n",
+ display->name);
+ }
+#else
+ meta_topic (META_DEBUG_XINERAMA,
+ "Marco compiled without Solaris Xinerama support\n");
+#endif /* HAVE_SOLARIS_XINERAMA */
+
+
+ /* If no Xinerama, fill in the single screen info so
+ * we can use the field unconditionally
+ */
+ if (screen->n_xinerama_infos == 0)
+ {
+ if (g_getenv ("MARCO_DEBUG_XINERAMA"))
+ {
+ meta_topic (META_DEBUG_XINERAMA,
+ "Pretending a single monitor has two Xinerama screens\n");
+
+ screen->xinerama_infos = g_new (MetaXineramaScreenInfo, 2);
+ screen->n_xinerama_infos = 2;
+
+ screen->xinerama_infos[0].number = 0;
+ screen->xinerama_infos[0].rect = screen->rect;
+ screen->xinerama_infos[0].rect.width = screen->rect.width / 2;
+
+ screen->xinerama_infos[1].number = 1;
+ screen->xinerama_infos[1].rect = screen->rect;
+ screen->xinerama_infos[1].rect.x = screen->rect.width / 2;
+ screen->xinerama_infos[1].rect.width = screen->rect.width / 2;
+ }
+ else
+ {
+ meta_topic (META_DEBUG_XINERAMA,
+ "No Xinerama screens, using default screen info\n");
+
+ screen->xinerama_infos = g_new (MetaXineramaScreenInfo, 1);
+ screen->n_xinerama_infos = 1;
+
+ screen->xinerama_infos[0].number = 0;
+ screen->xinerama_infos[0].rect = screen->rect;
+ }
+ }
+
+ g_assert (screen->n_xinerama_infos > 0);
+ g_assert (screen->xinerama_infos != NULL);
+}
+
+MetaScreen*
+meta_screen_new (MetaDisplay *display,
+ int number,
+ guint32 timestamp)
+{
+ MetaScreen *screen;
+ Window xroot;
+ Display *xdisplay;
+ XWindowAttributes attr;
+ Window new_wm_sn_owner;
+ Window current_wm_sn_owner;
+ gboolean replace_current_wm;
+ Atom wm_sn_atom;
+ char buf[128];
+ guint32 manager_timestamp;
+ gulong current_workspace;
+
+ replace_current_wm = meta_get_replace_current_wm ();
+
+ /* Only display->name, display->xdisplay, and display->error_traps
+ * can really be used in this function, since normally screens are
+ * created from the MetaDisplay constructor
+ */
+
+ xdisplay = display->xdisplay;
+
+ meta_verbose ("Trying screen %d on display '%s'\n",
+ number, display->name);
+
+ xroot = RootWindow (xdisplay, number);
+
+ /* FVWM checks for None here, I don't know if this
+ * ever actually happens
+ */
+ if (xroot == None)
+ {
+ meta_warning (_("Screen %d on display '%s' is invalid\n"),
+ number, display->name);
+ return NULL;
+ }
+
+ sprintf (buf, "WM_S%d", number);
+ wm_sn_atom = XInternAtom (xdisplay, buf, False);
+
+ current_wm_sn_owner = XGetSelectionOwner (xdisplay, wm_sn_atom);
+
+ if (current_wm_sn_owner != None)
+ {
+ XSetWindowAttributes attrs;
+
+ if (!replace_current_wm)
+ {
+ meta_warning (_("Screen %d on display \"%s\" already has a window manager; try using the --replace option to replace the current window manager.\n"),
+ number, display->name);
+
+ return NULL;
+ }
+
+ /* We want to find out when the current selection owner dies */
+ meta_error_trap_push_with_return (display);
+ attrs.event_mask = StructureNotifyMask;
+ XChangeWindowAttributes (xdisplay,
+ current_wm_sn_owner, CWEventMask, &attrs);
+ if (meta_error_trap_pop_with_return (display, FALSE) != Success)
+ current_wm_sn_owner = None; /* don't wait for it to die later on */
+ }
+
+ /* We need SelectionClear and SelectionRequest events on the new_wm_sn_owner,
+ * but those cannot be masked, so we only need NoEventMask.
+ */
+ new_wm_sn_owner = meta_create_offscreen_window (xdisplay, xroot, NoEventMask);
+
+ manager_timestamp = timestamp;
+
+ XSetSelectionOwner (xdisplay, wm_sn_atom, new_wm_sn_owner,
+ manager_timestamp);
+
+ if (XGetSelectionOwner (xdisplay, wm_sn_atom) != new_wm_sn_owner)
+ {
+ meta_warning (_("Could not acquire window manager selection on screen %d display \"%s\"\n"),
+ number, display->name);
+
+ XDestroyWindow (xdisplay, new_wm_sn_owner);
+
+ return NULL;
+ }
+
+ {
+ /* Send client message indicating that we are now the WM */
+ XClientMessageEvent ev;
+
+ ev.type = ClientMessage;
+ ev.window = xroot;
+ ev.message_type = display->atom_MANAGER;
+ ev.format = 32;
+ ev.data.l[0] = manager_timestamp;
+ ev.data.l[1] = wm_sn_atom;
+
+ XSendEvent (xdisplay, xroot, False, StructureNotifyMask, (XEvent*)&ev);
+ }
+
+ /* Wait for old window manager to go away */
+ if (current_wm_sn_owner != None)
+ {
+ XEvent event;
+
+ /* We sort of block infinitely here which is probably lame. */
+
+ meta_verbose ("Waiting for old window manager to exit\n");
+ do
+ {
+ XWindowEvent (xdisplay, current_wm_sn_owner,
+ StructureNotifyMask, &event);
+ }
+ while (event.type != DestroyNotify);
+ }
+
+ /* select our root window events */
+ meta_error_trap_push_with_return (display);
+
+ /* We need to or with the existing event mask since
+ * gtk+ may be interested in other events.
+ */
+ XGetWindowAttributes (xdisplay, xroot, &attr);
+ XSelectInput (xdisplay,
+ xroot,
+ SubstructureRedirectMask | SubstructureNotifyMask |
+ ColormapChangeMask | PropertyChangeMask |
+ LeaveWindowMask | EnterWindowMask |
+ KeyPressMask | KeyReleaseMask |
+ FocusChangeMask | StructureNotifyMask |
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ ExposureMask |
+#endif
+ attr.your_event_mask);
+ if (meta_error_trap_pop_with_return (display, FALSE) != Success)
+ {
+ meta_warning (_("Screen %d on display \"%s\" already has a window manager\n"),
+ number, display->name);
+
+ XDestroyWindow (xdisplay, new_wm_sn_owner);
+
+ return NULL;
+ }
+
+ screen = g_new (MetaScreen, 1);
+ screen->closing = 0;
+
+ screen->display = display;
+ screen->number = number;
+ screen->screen_name = get_screen_name (display, number);
+ screen->xscreen = ScreenOfDisplay (xdisplay, number);
+ screen->xroot = xroot;
+ screen->rect.x = screen->rect.y = 0;
+ screen->rect.width = WidthOfScreen (screen->xscreen);
+ screen->rect.height = HeightOfScreen (screen->xscreen);
+ screen->current_cursor = -1; /* invalid/unset */
+ screen->default_xvisual = DefaultVisualOfScreen (screen->xscreen);
+ screen->default_depth = DefaultDepthOfScreen (screen->xscreen);
+ screen->flash_window = None;
+
+ screen->wm_sn_selection_window = new_wm_sn_owner;
+ screen->wm_sn_atom = wm_sn_atom;
+ screen->wm_sn_timestamp = manager_timestamp;
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ screen->wm_cm_selection_window = meta_create_offscreen_window (xdisplay,
+ xroot,
+ NoEventMask);
+#endif
+ screen->work_area_idle = 0;
+
+ screen->active_workspace = NULL;
+ screen->workspaces = NULL;
+ screen->rows_of_workspaces = 1;
+ screen->columns_of_workspaces = -1;
+ screen->vertical_workspaces = FALSE;
+ screen->starting_corner = META_SCREEN_TOPLEFT;
+ screen->compositor_data = NULL;
+
+ {
+ XFontStruct *font_info;
+ XGCValues gc_values;
+ gulong value_mask = 0;
+
+ gc_values.subwindow_mode = IncludeInferiors;
+ value_mask |= GCSubwindowMode;
+ gc_values.function = GXinvert;
+ value_mask |= GCFunction;
+ gc_values.line_width = META_WIREFRAME_XOR_LINE_WIDTH;
+ value_mask |= GCLineWidth;
+
+ font_info = XLoadQueryFont (screen->display->xdisplay, "fixed");
+
+ if (font_info != NULL)
+ {
+ gc_values.font = font_info->fid;
+ value_mask |= GCFont;
+ XFreeFontInfo (NULL, font_info, 1);
+ }
+ else
+ meta_warning ("xserver doesn't have 'fixed' font.\n");
+
+ screen->root_xor_gc = XCreateGC (screen->display->xdisplay,
+ screen->xroot,
+ value_mask,
+ &gc_values);
+ }
+
+ screen->xinerama_infos = NULL;
+ screen->n_xinerama_infos = 0;
+ screen->last_xinerama_index = 0;
+
+ reload_xinerama_infos (screen);
+
+ meta_screen_set_cursor (screen, META_CURSOR_DEFAULT);
+
+ /* Handle creating a no_focus_window for this screen */
+ screen->no_focus_window =
+ meta_create_offscreen_window (display->xdisplay,
+ screen->xroot,
+ FocusChangeMask|KeyPressMask|KeyReleaseMask);
+ XMapWindow (display->xdisplay, screen->no_focus_window);
+ /* Done with no_focus_window stuff */
+
+ set_wm_icon_size_hint (screen);
+
+ set_supported_hint (screen);
+
+ set_wm_check_hint (screen);
+
+ set_desktop_viewport_hint (screen);
+
+ set_desktop_geometry_hint (screen);
+
+ meta_screen_update_workspace_layout (screen);
+
+ /* Get current workspace */
+ current_workspace = 0;
+ if (meta_prop_get_cardinal (screen->display,
+ screen->xroot,
+ screen->display->atom__NET_CURRENT_DESKTOP,
+ &current_workspace))
+ meta_verbose ("Read existing _NET_CURRENT_DESKTOP = %d\n",
+ (int) current_workspace);
+ else
+ meta_verbose ("No _NET_CURRENT_DESKTOP present\n");
+
+ /* Screens must have at least one workspace at all times,
+ * so create that required workspace.
+ */
+ meta_workspace_activate (meta_workspace_new (screen), timestamp);
+ update_num_workspaces (screen, timestamp);
+
+ set_workspace_names (screen);
+
+ screen->all_keys_grabbed = FALSE;
+ screen->keys_grabbed = FALSE;
+ meta_screen_grab_keys (screen);
+
+ screen->ui = meta_ui_new (screen->display->xdisplay,
+ screen->xscreen);
+
+ screen->tab_popup = NULL;
+
+ screen->stack = meta_stack_new (screen);
+
+ meta_prefs_add_listener (prefs_changed_callback, screen);
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ screen->sn_context =
+ sn_monitor_context_new (screen->display->sn_display,
+ screen->number,
+ meta_screen_sn_event,
+ screen,
+ NULL);
+ screen->startup_sequences = NULL;
+ screen->startup_sequence_timeout = 0;
+#endif
+
+ /* Switch to the _NET_CURRENT_DESKTOP workspace */
+ {
+ MetaWorkspace *space;
+
+ space = meta_screen_get_workspace_by_index (screen,
+ current_workspace);
+
+ if (space != NULL)
+ meta_workspace_activate (space, timestamp);
+ }
+
+ meta_verbose ("Added screen %d ('%s') root 0x%lx\n",
+ screen->number, screen->screen_name, screen->xroot);
+
+ return screen;
+}
+
+void
+meta_screen_free (MetaScreen *screen,
+ guint32 timestamp)
+{
+ MetaDisplay *display;
+ XGCValues gc_values = { 0 };
+
+ display = screen->display;
+
+ screen->closing += 1;
+
+ meta_display_grab (display);
+
+ if (screen->display->compositor)
+ {
+ meta_compositor_unmanage_screen (screen->display->compositor,
+ screen);
+ }
+
+ meta_display_unmanage_windows_for_screen (display, screen, timestamp);
+
+ meta_prefs_remove_listener (prefs_changed_callback, screen);
+
+ meta_screen_ungrab_keys (screen);
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+ g_slist_foreach (screen->startup_sequences,
+ (GFunc) sn_startup_sequence_unref, NULL);
+ g_slist_free (screen->startup_sequences);
+ screen->startup_sequences = NULL;
+
+ if (screen->startup_sequence_timeout != 0)
+ {
+ g_source_remove (screen->startup_sequence_timeout);
+ screen->startup_sequence_timeout = 0;
+ }
+ if (screen->sn_context)
+ {
+ sn_monitor_context_unref (screen->sn_context);
+ screen->sn_context = NULL;
+ }
+#endif
+
+ meta_ui_free (screen->ui);
+
+ meta_stack_free (screen->stack);
+
+ meta_error_trap_push_with_return (screen->display);
+ XSelectInput (screen->display->xdisplay, screen->xroot, 0);
+ if (meta_error_trap_pop_with_return (screen->display, FALSE) != Success)
+ meta_warning (_("Could not release screen %d on display \"%s\"\n"),
+ screen->number, screen->display->name);
+
+ unset_wm_check_hint (screen);
+
+ XDestroyWindow (screen->display->xdisplay,
+ screen->wm_sn_selection_window);
+
+ if (screen->work_area_idle != 0)
+ g_source_remove (screen->work_area_idle);
+
+
+ if (XGetGCValues (screen->display->xdisplay,
+ screen->root_xor_gc,
+ GCFont,
+ &gc_values))
+ {
+ XUnloadFont (screen->display->xdisplay,
+ gc_values.font);
+ }
+
+ XFreeGC (screen->display->xdisplay,
+ screen->root_xor_gc);
+
+ if (screen->xinerama_infos)
+ g_free (screen->xinerama_infos);
+
+ g_free (screen->screen_name);
+ g_free (screen);
+
+ XFlush (display->xdisplay);
+ meta_display_ungrab (display);
+}
+
+typedef struct
+{
+ Window xwindow;
+ XWindowAttributes attrs;
+} WindowInfo;
+
+static GList *
+list_windows (MetaScreen *screen)
+{
+ Window ignored1, ignored2;
+ Window *children;
+ guint n_children, i;
+ GList *result;
+
+ XQueryTree (screen->display->xdisplay,
+ screen->xroot,
+ &ignored1, &ignored2, &children, &n_children);
+
+ result = NULL;
+ for (i = 0; i < n_children; ++i)
+ {
+ WindowInfo *info = g_new0 (WindowInfo, 1);
+
+ meta_error_trap_push_with_return (screen->display);
+
+ XGetWindowAttributes (screen->display->xdisplay,
+ children[i], &info->attrs);
+
+ if (meta_error_trap_pop_with_return (screen->display, TRUE))
+ {
+ meta_verbose ("Failed to get attributes for window 0x%lx\n",
+ children[i]);
+ g_free (info);
+ }
+ else
+ {
+ info->xwindow = children[i];
+ }
+
+ result = g_list_prepend (result, info);
+ }
+
+ if (children)
+ XFree (children);
+
+ return g_list_reverse (result);
+}
+
+void
+meta_screen_manage_all_windows (MetaScreen *screen)
+{
+ GList *windows;
+ GList *list;
+
+ meta_display_grab (screen->display);
+
+ windows = list_windows (screen);
+
+ meta_stack_freeze (screen->stack);
+ for (list = windows; list != NULL; list = list->next)
+ {
+ WindowInfo *info = list->data;
+ MetaWindow *window;
+
+ window = meta_window_new_with_attrs (screen->display, info->xwindow, TRUE,
+ &info->attrs);
+ if (info->xwindow == screen->no_focus_window ||
+ info->xwindow == screen->flash_window ||
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ info->xwindow == screen->wm_cm_selection_window ||
+#endif
+ info->xwindow == screen->wm_sn_selection_window) {
+ meta_verbose ("Not managing our own windows\n");
+ continue;
+ }
+
+ if (screen->display->compositor)
+ meta_compositor_add_window (screen->display->compositor, window,
+ info->xwindow, &info->attrs);
+ }
+ meta_stack_thaw (screen->stack);
+
+ g_list_foreach (windows, (GFunc)g_free, NULL);
+ g_list_free (windows);
+
+ meta_display_ungrab (screen->display);
+}
+
+void
+meta_screen_composite_all_windows (MetaScreen *screen)
+{
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+ MetaDisplay *display;
+ GList *windows, *list;
+
+ display = screen->display;
+ if (!display->compositor)
+ return;
+
+ windows = list_windows (screen);
+
+ meta_stack_freeze (screen->stack);
+
+ for (list = windows; list != NULL; list = list->next)
+ {
+ WindowInfo *info = list->data;
+
+ if (info->xwindow == screen->no_focus_window ||
+ info->xwindow == screen->flash_window ||
+ info->xwindow == screen->wm_sn_selection_window ||
+ info->xwindow == screen->wm_cm_selection_window) {
+ meta_verbose ("Not managing our own windows\n");
+ continue;
+ }
+
+ meta_compositor_add_window (display->compositor,
+ meta_display_lookup_x_window (display,
+ info->xwindow),
+ info->xwindow, &info->attrs);
+ }
+
+ meta_stack_thaw (screen->stack);
+
+ g_list_foreach (windows, (GFunc)g_free, NULL);
+ g_list_free (windows);
+#endif
+}
+
+MetaScreen*
+meta_screen_for_x_screen (Screen *xscreen)
+{
+ MetaDisplay *display;
+
+ display = meta_display_for_x_display (DisplayOfScreen (xscreen));
+
+ if (display == NULL)
+ return NULL;
+
+ return meta_display_screen_for_x_screen (display, xscreen);
+}
+
+static void
+prefs_changed_callback (MetaPreference pref,
+ gpointer data)
+{
+ MetaScreen *screen = data;
+
+ if (pref == META_PREF_NUM_WORKSPACES)
+ {
+ /* MateConf doesn't provide timestamps, but luckily update_num_workspaces
+ * often doesn't need it...
+ */
+ guint32 timestamp =
+ meta_display_get_current_time_roundtrip (screen->display);
+ update_num_workspaces (screen, timestamp);
+ }
+ else if (pref == META_PREF_FOCUS_MODE)
+ {
+ update_focus_mode (screen);
+ }
+ else if (pref == META_PREF_WORKSPACE_NAMES)
+ {
+ set_workspace_names (screen);
+ }
+}
+
+
+static char*
+get_screen_name (MetaDisplay *display,
+ int number)
+{
+ char *p;
+ char *dname;
+ char *scr;
+
+ /* DisplayString gives us a sort of canonical display,
+ * vs. the user-entered name from XDisplayName()
+ */
+ dname = g_strdup (DisplayString (display->xdisplay));
+
+ /* Change display name to specify this screen.
+ */
+ p = strrchr (dname, ':');
+ if (p)
+ {
+ p = strchr (p, '.');
+ if (p)
+ *p = '\0';
+ }
+
+ scr = g_strdup_printf ("%s.%d", dname, number);
+
+ g_free (dname);
+
+ return scr;
+}
+
+static gint
+ptrcmp (gconstpointer a, gconstpointer b)
+{
+ if (a < b)
+ return -1;
+ else if (a > b)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+listify_func (gpointer key, gpointer value, gpointer data)
+{
+ GSList **listp;
+
+ listp = data;
+
+ *listp = g_slist_prepend (*listp, value);
+}
+
+void
+meta_screen_foreach_window (MetaScreen *screen,
+ MetaScreenWindowFunc func,
+ gpointer data)
+{
+ GSList *winlist;
+ GSList *tmp;
+
+ /* If we end up doing this often, just keeping a list
+ * of windows might be sensible.
+ */
+
+ winlist = NULL;
+ g_hash_table_foreach (screen->display->window_ids,
+ listify_func,
+ &winlist);
+
+ winlist = g_slist_sort (winlist, ptrcmp);
+
+ tmp = winlist;
+ while (tmp != NULL)
+ {
+ /* If the next node doesn't contain this window
+ * a second time, delete the window.
+ */
+ if (tmp->next == NULL ||
+ (tmp->next && tmp->next->data != tmp->data))
+ {
+ MetaWindow *window = tmp->data;
+
+ if (window->screen == screen)
+ (* func) (screen, window, data);
+ }
+
+ tmp = tmp->next;
+ }
+ g_slist_free (winlist);
+}
+
+static void
+queue_draw (MetaScreen *screen, MetaWindow *window, gpointer data)
+{
+ if (window->frame)
+ meta_frame_queue_draw (window->frame);
+}
+
+void
+meta_screen_queue_frame_redraws (MetaScreen *screen)
+{
+ meta_screen_foreach_window (screen, queue_draw, NULL);
+}
+
+static void
+queue_resize (MetaScreen *screen, MetaWindow *window, gpointer data)
+{
+ meta_window_queue (window, META_QUEUE_MOVE_RESIZE);
+}
+
+void
+meta_screen_queue_window_resizes (MetaScreen *screen)
+{
+ meta_screen_foreach_window (screen, queue_resize, NULL);
+}
+
+int
+meta_screen_get_n_workspaces (MetaScreen *screen)
+{
+ return g_list_length (screen->workspaces);
+}
+
+MetaWorkspace*
+meta_screen_get_workspace_by_index (MetaScreen *screen,
+ int idx)
+{
+ GList *tmp;
+ int i;
+
+ /* should be robust, idx is maybe from an app */
+ if (idx < 0)
+ return NULL;
+
+ i = 0;
+ tmp = screen->workspaces;
+ while (tmp != NULL)
+ {
+ MetaWorkspace *w = tmp->data;
+
+ if (i == idx)
+ return w;
+
+ ++i;
+ tmp = tmp->next;
+ }
+
+ return NULL;
+}
+
+static void
+set_number_of_spaces_hint (MetaScreen *screen,
+ int n_spaces)
+{
+ unsigned long data[1];
+
+ if (screen->closing > 0)
+ return;
+
+ data[0] = n_spaces;
+
+ meta_verbose ("Setting _NET_NUMBER_OF_DESKTOPS to %lu\n", data[0]);
+
+ meta_error_trap_push (screen->display);
+ XChangeProperty (screen->display->xdisplay, screen->xroot,
+ screen->display->atom__NET_NUMBER_OF_DESKTOPS,
+ XA_CARDINAL,
+ 32, PropModeReplace, (guchar*) data, 1);
+ meta_error_trap_pop (screen->display, FALSE);
+}
+
+static void
+set_desktop_geometry_hint (MetaScreen *screen)
+{
+ unsigned long data[2];
+
+ if (screen->closing > 0)
+ return;
+
+ data[0] = screen->rect.width;
+ data[1] = screen->rect.height;
+
+ meta_verbose ("Setting _NET_DESKTOP_GEOMETRY to %lu, %lu\n", data[0], data[1]);
+
+ meta_error_trap_push (screen->display);
+ XChangeProperty (screen->display->xdisplay, screen->xroot,
+ screen->display->atom__NET_DESKTOP_GEOMETRY,
+ XA_CARDINAL,
+ 32, PropModeReplace, (guchar*) data, 2);
+ meta_error_trap_pop (screen->display, FALSE);
+}
+
+static void
+set_desktop_viewport_hint (MetaScreen *screen)
+{
+ unsigned long data[2];
+
+ if (screen->closing > 0)
+ return;
+
+ /*
+ * Marco does not implement viewports, so this is a fixed 0,0
+ */
+ data[0] = 0;
+ data[1] = 0;
+
+ meta_verbose ("Setting _NET_DESKTOP_VIEWPORT to 0, 0\n");
+
+ meta_error_trap_push (screen->display);
+ XChangeProperty (screen->display->xdisplay, screen->xroot,
+ screen->display->atom__NET_DESKTOP_VIEWPORT,
+ XA_CARDINAL,
+ 32, PropModeReplace, (guchar*) data, 2);
+ meta_error_trap_pop (screen->display, FALSE);
+}
+
+static void
+update_num_workspaces (MetaScreen *screen,
+ guint32 timestamp)
+{
+ int new_num;
+ GList *tmp;
+ int i;
+ GList *extras;
+ MetaWorkspace *last_remaining;
+ gboolean need_change_space;
+
+ new_num = meta_prefs_get_num_workspaces ();
+
+ g_assert (new_num > 0);
+
+ last_remaining = NULL;
+ extras = NULL;
+ i = 0;
+ tmp = screen->workspaces;
+ while (tmp != NULL)
+ {
+ MetaWorkspace *w = tmp->data;
+
+ if (i >= new_num)
+ extras = g_list_prepend (extras, w);
+ else
+ last_remaining = w;
+
+ ++i;
+ tmp = tmp->next;
+ }
+
+ g_assert (last_remaining);
+
+ /* Get rid of the extra workspaces by moving all their windows
+ * to last_remaining, then activating last_remaining if
+ * one of the removed workspaces was active. This will be a bit
+ * wacky if the config tool for changing number of workspaces
+ * is on a removed workspace ;-)
+ */
+ need_change_space = FALSE;
+ tmp = extras;
+ while (tmp != NULL)
+ {
+ MetaWorkspace *w = tmp->data;
+
+ meta_workspace_relocate_windows (w, last_remaining);
+
+ if (w == screen->active_workspace)
+ need_change_space = TRUE;
+
+ tmp = tmp->next;
+ }
+
+ if (need_change_space)
+ meta_workspace_activate (last_remaining, timestamp);
+
+ /* Should now be safe to free the workspaces */
+ tmp = extras;
+ while (tmp != NULL)
+ {
+ MetaWorkspace *w = tmp->data;
+
+ g_assert (w->windows == NULL);
+ meta_workspace_free (w);
+
+ tmp = tmp->next;
+ }
+
+ g_list_free (extras);
+
+ while (i < new_num)
+ {
+ meta_workspace_new (screen);
+ ++i;
+ }
+
+ set_number_of_spaces_hint (screen, new_num);
+
+ meta_screen_queue_workarea_recalc (screen);
+}
+
+static void
+update_focus_mode (MetaScreen *screen)
+{
+ /* nothing to do anymore */ ;
+}
+
+void
+meta_screen_set_cursor (MetaScreen *screen,
+ MetaCursor cursor)
+{
+ Cursor xcursor;
+
+ if (cursor == screen->current_cursor)
+ return;
+
+ screen->current_cursor = cursor;
+
+ xcursor = meta_display_create_x_cursor (screen->display, cursor);
+ XDefineCursor (screen->display->xdisplay, screen->xroot, xcursor);
+ XFlush (screen->display->xdisplay);
+ XFreeCursor (screen->display->xdisplay, xcursor);
+}
+
+void
+meta_screen_update_cursor (MetaScreen *screen)
+{
+ Cursor xcursor;
+
+ xcursor = meta_display_create_x_cursor (screen->display,
+ screen->current_cursor);
+ XDefineCursor (screen->display->xdisplay, screen->xroot, xcursor);
+ XFlush (screen->display->xdisplay);
+ XFreeCursor (screen->display->xdisplay, xcursor);
+}
+
+#define MAX_PREVIEW_SIZE 150.0
+
+static GdkPixbuf *
+get_window_pixbuf (MetaWindow *window,
+ int *width,
+ int *height)
+{
+ Pixmap pmap;
+ GdkPixbuf *pixbuf, *scaled;
+ double ratio;
+
+ pmap = meta_compositor_get_window_pixmap (window->display->compositor,
+ window);
+ if (pmap == None)
+ return NULL;
+
+ pixbuf = meta_ui_get_pixbuf_from_pixmap (pmap);
+ if (pixbuf == NULL)
+ return NULL;
+
+ *width = gdk_pixbuf_get_width (pixbuf);
+ *height = gdk_pixbuf_get_height (pixbuf);
+
+ /* Scale pixbuf to max dimension MAX_PREVIEW_SIZE */
+ if (*width > *height)
+ {
+ ratio = ((double) *width) / MAX_PREVIEW_SIZE;
+ *width = (int) MAX_PREVIEW_SIZE;
+ *height = (int) (((double) *height) / ratio);
+ }
+ else
+ {
+ ratio = ((double) *height) / MAX_PREVIEW_SIZE;
+ *height = (int) MAX_PREVIEW_SIZE;
+ *width = (int) (((double) *width) / ratio);
+ }
+
+ scaled = gdk_pixbuf_scale_simple (pixbuf, *width, *height,
+ GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ return scaled;
+}
+
+void
+meta_screen_ensure_tab_popup (MetaScreen *screen,
+ MetaTabList list_type,
+ MetaTabShowType show_type)
+{
+ MetaTabEntry *entries;
+ GList *tab_list;
+ GList *tmp;
+ int len;
+ int i;
+
+ if (screen->tab_popup)
+ return;
+
+ tab_list = meta_display_get_tab_list (screen->display,
+ list_type,
+ screen,
+ screen->active_workspace);
+
+ len = g_list_length (tab_list);
+
+ entries = g_new (MetaTabEntry, len + 1);
+ entries[len].key = NULL;
+ entries[len].title = NULL;
+ entries[len].icon = NULL;
+
+ i = 0;
+ tmp = tab_list;
+ while (i < len)
+ {
+ MetaWindow *window;
+ MetaRectangle r;
+ GdkPixbuf *win_pixbuf;
+ int width, height;
+
+ window = tmp->data;
+
+ entries[i].key = (MetaTabEntryKey) window->xwindow;
+ entries[i].title = window->title;
+
+ win_pixbuf = get_window_pixbuf (window, &width, &height);
+ if (win_pixbuf == NULL)
+ entries[i].icon = g_object_ref (window->icon);
+ else
+ {
+ int icon_width, icon_height, t_width, t_height;
+#define ICON_OFFSET 6
+
+ icon_width = gdk_pixbuf_get_width (window->icon);
+ icon_height = gdk_pixbuf_get_height (window->icon);
+
+ t_width = width + ICON_OFFSET;
+ t_height = height + ICON_OFFSET;
+
+ entries[i].icon = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ t_width, t_height);
+ gdk_pixbuf_fill (entries[i].icon, 0x00000000);
+ gdk_pixbuf_copy_area (win_pixbuf, 0, 0, width, height,
+ entries[i].icon, 0, 0);
+ g_object_unref (win_pixbuf);
+ gdk_pixbuf_composite (window->icon, entries[i].icon,
+ t_width - icon_width, t_height - icon_height,
+ icon_width, icon_height,
+ t_width - icon_width, t_height - icon_height,
+ 1.0, 1.0, GDK_INTERP_BILINEAR, 255);
+ }
+
+ entries[i].blank = FALSE;
+ entries[i].hidden = !meta_window_showing_on_its_workspace (window);
+ entries[i].demands_attention = window->wm_state_demands_attention;
+
+ if (show_type == META_TAB_SHOW_INSTANTLY ||
+ !entries[i].hidden ||
+ !meta_window_get_icon_geometry (window, &r))
+ meta_window_get_outer_rect (window, &r);
+
+ entries[i].rect = r;
+
+ /* Find inside of highlight rectangle to be used when window is
+ * outlined for tabbing. This should be the size of the
+ * east/west frame, and the size of the south frame, on those
+ * sides. On the top it should be the size of the south frame
+ * edge.
+ */
+#define OUTLINE_WIDTH 5
+ /* Top side */
+ if (!entries[i].hidden &&
+ window->frame && window->frame->bottom_height > 0 &&
+ window->frame->child_y >= window->frame->bottom_height)
+ entries[i].inner_rect.y = window->frame->bottom_height;
+ else
+ entries[i].inner_rect.y = OUTLINE_WIDTH;
+
+ /* Bottom side */
+ if (!entries[i].hidden &&
+ window->frame && window->frame->bottom_height != 0)
+ entries[i].inner_rect.height = r.height
+ - entries[i].inner_rect.y - window->frame->bottom_height;
+ else
+ entries[i].inner_rect.height = r.height
+ - entries[i].inner_rect.y - OUTLINE_WIDTH;
+
+ /* Left side */
+ if (!entries[i].hidden && window->frame && window->frame->child_x != 0)
+ entries[i].inner_rect.x = window->frame->child_x;
+ else
+ entries[i].inner_rect.x = OUTLINE_WIDTH;
+
+ /* Right side */
+ if (!entries[i].hidden &&
+ window->frame && window->frame->right_width != 0)
+ entries[i].inner_rect.width = r.width
+ - entries[i].inner_rect.x - window->frame->right_width;
+ else
+ entries[i].inner_rect.width = r.width
+ - entries[i].inner_rect.x - OUTLINE_WIDTH;
+
+ ++i;
+ tmp = tmp->next;
+ }
+
+ screen->tab_popup = meta_ui_tab_popup_new (entries,
+ screen->number,
+ len,
+ 5, /* FIXME */
+ TRUE);
+
+ for (i = 0; i < len; i++)
+ g_object_unref (entries[i].icon);
+
+ g_free (entries);
+
+ g_list_free (tab_list);
+
+ /* don't show tab popup, since proper window isn't selected yet */
+}
+
+void
+meta_screen_ensure_workspace_popup (MetaScreen *screen)
+{
+ MetaTabEntry *entries;
+ int len;
+ int i;
+ MetaWorkspaceLayout layout;
+ int n_workspaces;
+ int current_workspace;
+
+ if (screen->tab_popup)
+ return;
+
+ current_workspace = meta_workspace_index (screen->active_workspace);
+ n_workspaces = meta_screen_get_n_workspaces (screen);
+
+ meta_screen_calc_workspace_layout (screen, n_workspaces,
+ current_workspace, &layout);
+
+ len = layout.grid_area;
+
+ entries = g_new (MetaTabEntry, len + 1);
+ entries[len].key = NULL;
+ entries[len].title = NULL;
+ entries[len].icon = NULL;
+
+ i = 0;
+ while (i < len)
+ {
+ if (layout.grid[i] >= 0)
+ {
+ MetaWorkspace *workspace;
+
+ workspace = meta_screen_get_workspace_by_index (screen,
+ layout.grid[i]);
+
+ entries[i].key = (MetaTabEntryKey) workspace;
+ entries[i].title = meta_workspace_get_name (workspace);
+ entries[i].icon = NULL;
+ entries[i].blank = FALSE;
+
+ g_assert (entries[i].title != NULL);
+ }
+ else
+ {
+ entries[i].key = NULL;
+ entries[i].title = NULL;
+ entries[i].icon = NULL;
+ entries[i].blank = TRUE;
+ }
+ entries[i].hidden = FALSE;
+ entries[i].demands_attention = FALSE;
+
+ ++i;
+ }
+
+ screen->tab_popup = meta_ui_tab_popup_new (entries,
+ screen->number,
+ len,
+ layout.cols,
+ FALSE);
+
+ g_free (entries);
+ meta_screen_free_workspace_layout (&layout);
+
+ /* don't show tab popup, since proper space isn't selected yet */
+}
+
+MetaWindow*
+meta_screen_get_mouse_window (MetaScreen *screen,
+ MetaWindow *not_this_one)
+{
+ MetaWindow *window;
+ Window root_return, child_return;
+ int root_x_return, root_y_return;
+ int win_x_return, win_y_return;
+ unsigned int mask_return;
+
+ if (not_this_one)
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing mouse window excluding %s\n", not_this_one->desc);
+
+ meta_error_trap_push (screen->display);
+ XQueryPointer (screen->display->xdisplay,
+ screen->xroot,
+ &root_return,
+ &child_return,
+ &root_x_return,
+ &root_y_return,
+ &win_x_return,
+ &win_y_return,
+ &mask_return);
+ meta_error_trap_pop (screen->display, TRUE);
+
+ window = meta_stack_get_default_focus_window_at_point (screen->stack,
+ screen->active_workspace,
+ not_this_one,
+ root_x_return,
+ root_y_return);
+
+ return window;
+}
+
+const MetaXineramaScreenInfo*
+meta_screen_get_xinerama_for_rect (MetaScreen *screen,
+ MetaRectangle *rect)
+{
+ int i;
+ int best_xinerama, xinerama_score;
+
+ if (screen->n_xinerama_infos == 1)
+ return &screen->xinerama_infos[0];
+
+ best_xinerama = 0;
+ xinerama_score = 0;
+
+ for (i = 0; i < screen->n_xinerama_infos; i++)
+ {
+ MetaRectangle dest;
+ if (meta_rectangle_intersect (&screen->xinerama_infos[i].rect,
+ rect,
+ &dest))
+ {
+ int cur = meta_rectangle_area (&dest);
+ if (cur > xinerama_score)
+ {
+ xinerama_score = cur;
+ best_xinerama = i;
+ }
+ }
+ }
+
+ return &screen->xinerama_infos[best_xinerama];
+}
+
+const MetaXineramaScreenInfo*
+meta_screen_get_xinerama_for_window (MetaScreen *screen,
+ MetaWindow *window)
+{
+ MetaRectangle window_rect;
+
+ meta_window_get_outer_rect (window, &window_rect);
+
+ return meta_screen_get_xinerama_for_rect (screen, &window_rect);
+}
+
+const MetaXineramaScreenInfo*
+meta_screen_get_xinerama_neighbor (MetaScreen *screen,
+ int which_xinerama,
+ MetaScreenDirection direction)
+{
+ MetaXineramaScreenInfo* input = screen->xinerama_infos + which_xinerama;
+ MetaXineramaScreenInfo* current;
+ int i;
+
+ for (i = 0; i < screen->n_xinerama_infos; i++)
+ {
+ current = screen->xinerama_infos + i;
+
+ if ((direction == META_SCREEN_RIGHT &&
+ current->rect.x == input->rect.x + input->rect.width &&
+ meta_rectangle_vert_overlap(&current->rect, &input->rect)) ||
+ (direction == META_SCREEN_LEFT &&
+ input->rect.x == current->rect.x + current->rect.width &&
+ meta_rectangle_vert_overlap(&current->rect, &input->rect)) ||
+ (direction == META_SCREEN_UP &&
+ input->rect.y == current->rect.y + current->rect.height &&
+ meta_rectangle_horiz_overlap(&current->rect, &input->rect)) ||
+ (direction == META_SCREEN_DOWN &&
+ current->rect.y == input->rect.y + input->rect.height &&
+ meta_rectangle_horiz_overlap(&current->rect, &input->rect)))
+ {
+ return current;
+ }
+ }
+
+ return NULL;
+}
+
+void
+meta_screen_get_natural_xinerama_list (MetaScreen *screen,
+ int** xineramas_list,
+ int* n_xineramas)
+{
+ const MetaXineramaScreenInfo* current;
+ const MetaXineramaScreenInfo* tmp;
+ GQueue* xinerama_queue;
+ int* visited;
+ int cur = 0;
+ int i;
+
+ *n_xineramas = screen->n_xinerama_infos;
+ *xineramas_list = g_new (int, screen->n_xinerama_infos);
+
+ /* we calculate a natural ordering by which to choose xineramas for
+ * window placement. We start at the current xinerama, and perform
+ * a breadth-first search of the xineramas starting from that
+ * xinerama. We choose preferentially left, then right, then down,
+ * then up. The visitation order produced by this traversal is the
+ * natural xinerama ordering.
+ */
+
+ visited = g_new (int, screen->n_xinerama_infos);
+ for (i = 0; i < screen->n_xinerama_infos; i++)
+ {
+ visited[i] = FALSE;
+ }
+
+ current = meta_screen_get_current_xinerama (screen);
+ xinerama_queue = g_queue_new ();
+ g_queue_push_tail (xinerama_queue, (gpointer) current);
+ visited[current->number] = TRUE;
+
+ while (!g_queue_is_empty (xinerama_queue))
+ {
+ current = (const MetaXineramaScreenInfo*)
+ g_queue_pop_head (xinerama_queue);
+
+ (*xineramas_list)[cur++] = current->number;
+
+ /* enqueue each of the directions */
+ tmp = meta_screen_get_xinerama_neighbor (screen,
+ current->number,
+ META_SCREEN_LEFT);
+ if (tmp && !visited[tmp->number])
+ {
+ g_queue_push_tail (xinerama_queue,
+ (MetaXineramaScreenInfo*) tmp);
+ visited[tmp->number] = TRUE;
+ }
+ tmp = meta_screen_get_xinerama_neighbor (screen,
+ current->number,
+ META_SCREEN_RIGHT);
+ if (tmp && !visited[tmp->number])
+ {
+ g_queue_push_tail (xinerama_queue,
+ (MetaXineramaScreenInfo*) tmp);
+ visited[tmp->number] = TRUE;
+ }
+ tmp = meta_screen_get_xinerama_neighbor (screen,
+ current->number,
+ META_SCREEN_UP);
+ if (tmp && !visited[tmp->number])
+ {
+ g_queue_push_tail (xinerama_queue,
+ (MetaXineramaScreenInfo*) tmp);
+ visited[tmp->number] = TRUE;
+ }
+ tmp = meta_screen_get_xinerama_neighbor (screen,
+ current->number,
+ META_SCREEN_DOWN);
+ if (tmp && !visited[tmp->number])
+ {
+ g_queue_push_tail (xinerama_queue,
+ (MetaXineramaScreenInfo*) tmp);
+ visited[tmp->number] = TRUE;
+ }
+ }
+
+ /* in case we somehow missed some set of xineramas, go through the
+ * visited list and add in any xineramas that were missed
+ */
+ for (i = 0; i < screen->n_xinerama_infos; i++)
+ {
+ if (visited[i] == FALSE)
+ {
+ (*xineramas_list)[cur++] = i;
+ }
+ }
+
+ g_free (visited);
+ g_queue_free (xinerama_queue);
+}
+
+const MetaXineramaScreenInfo*
+meta_screen_get_current_xinerama (MetaScreen *screen)
+{
+ if (screen->n_xinerama_infos == 1)
+ return &screen->xinerama_infos[0];
+
+ /* Sadly, we have to do it this way. Yuck.
+ */
+
+ if (screen->display->xinerama_cache_invalidated)
+ {
+ Window root_return, child_return;
+ int win_x_return, win_y_return;
+ unsigned int mask_return;
+ int i;
+ MetaRectangle pointer_position;
+
+ screen->display->xinerama_cache_invalidated = FALSE;
+
+ pointer_position.width = pointer_position.height = 1;
+ XQueryPointer (screen->display->xdisplay,
+ screen->xroot,
+ &root_return,
+ &child_return,
+ &pointer_position.x,
+ &pointer_position.y,
+ &win_x_return,
+ &win_y_return,
+ &mask_return);
+
+ screen->last_xinerama_index = 0;
+ for (i = 0; i < screen->n_xinerama_infos; i++)
+ {
+ if (meta_rectangle_contains_rect (&screen->xinerama_infos[i].rect,
+ &pointer_position))
+ {
+ screen->last_xinerama_index = i;
+ break;
+ }
+ }
+
+ meta_topic (META_DEBUG_XINERAMA,
+ "Rechecked current Xinerama, now %d\n",
+ screen->last_xinerama_index);
+ }
+
+ return &screen->xinerama_infos[screen->last_xinerama_index];
+}
+
+#define _NET_WM_ORIENTATION_HORZ 0
+#define _NET_WM_ORIENTATION_VERT 1
+
+#define _NET_WM_TOPLEFT 0
+#define _NET_WM_TOPRIGHT 1
+#define _NET_WM_BOTTOMRIGHT 2
+#define _NET_WM_BOTTOMLEFT 3
+
+void
+meta_screen_update_workspace_layout (MetaScreen *screen)
+{
+ gulong *list;
+ int n_items;
+
+ list = NULL;
+ n_items = 0;
+
+ if (meta_prop_get_cardinal_list (screen->display,
+ screen->xroot,
+ screen->display->atom__NET_DESKTOP_LAYOUT,
+ &list, &n_items))
+ {
+ if (n_items == 3 || n_items == 4)
+ {
+ int cols, rows;
+
+ switch (list[0])
+ {
+ case _NET_WM_ORIENTATION_HORZ:
+ screen->vertical_workspaces = FALSE;
+ break;
+ case _NET_WM_ORIENTATION_VERT:
+ screen->vertical_workspaces = TRUE;
+ break;
+ default:
+ meta_warning ("Someone set a weird orientation in _NET_DESKTOP_LAYOUT\n");
+ break;
+ }
+
+ cols = list[1];
+ rows = list[2];
+
+ if (rows <= 0 && cols <= 0)
+ {
+ meta_warning ("Columns = %d rows = %d in _NET_DESKTOP_LAYOUT makes no sense\n", rows, cols);
+ }
+ else
+ {
+ if (rows > 0)
+ screen->rows_of_workspaces = rows;
+ else
+ screen->rows_of_workspaces = -1;
+
+ if (cols > 0)
+ screen->columns_of_workspaces = cols;
+ else
+ screen->columns_of_workspaces = -1;
+ }
+
+ if (n_items == 4)
+ {
+ switch (list[3])
+ {
+ case _NET_WM_TOPLEFT:
+ screen->starting_corner = META_SCREEN_TOPLEFT;
+ break;
+ case _NET_WM_TOPRIGHT:
+ screen->starting_corner = META_SCREEN_TOPRIGHT;
+ break;
+ case _NET_WM_BOTTOMRIGHT:
+ screen->starting_corner = META_SCREEN_BOTTOMRIGHT;
+ break;
+ case _NET_WM_BOTTOMLEFT:
+ screen->starting_corner = META_SCREEN_BOTTOMLEFT;
+ break;
+ default:
+ meta_warning ("Someone set a weird starting corner in _NET_DESKTOP_LAYOUT\n");
+ break;
+ }
+ }
+ else
+ screen->starting_corner = META_SCREEN_TOPLEFT;
+ }
+ else
+ {
+ meta_warning ("Someone set _NET_DESKTOP_LAYOUT to %d integers instead of 4 "
+ "(3 is accepted for backwards compat)\n", n_items);
+ }
+
+ meta_XFree (list);
+ }
+
+ meta_verbose ("Workspace layout rows = %d cols = %d orientation = %d starting corner = %u\n",
+ screen->rows_of_workspaces,
+ screen->columns_of_workspaces,
+ screen->vertical_workspaces,
+ screen->starting_corner);
+}
+
+static void
+set_workspace_names (MetaScreen *screen)
+{
+ /* This updates names on root window when the pref changes,
+ * note we only get prefs change notify if things have
+ * really changed.
+ */
+ GString *flattened;
+ int i;
+ int n_spaces;
+
+ /* flatten to nul-separated list */
+ n_spaces = meta_screen_get_n_workspaces (screen);
+ flattened = g_string_new ("");
+ i = 0;
+ while (i < n_spaces)
+ {
+ const char *name;
+
+ name = meta_prefs_get_workspace_name (i);
+
+ if (name)
+ g_string_append_len (flattened, name,
+ strlen (name) + 1);
+ else
+ g_string_append_len (flattened, "", 1);
+
+ ++i;
+ }
+
+ meta_error_trap_push (screen->display);
+ XChangeProperty (screen->display->xdisplay,
+ screen->xroot,
+ screen->display->atom__NET_DESKTOP_NAMES,
+ screen->display->atom_UTF8_STRING,
+ 8, PropModeReplace,
+ (unsigned char *)flattened->str, flattened->len);
+ meta_error_trap_pop (screen->display, FALSE);
+
+ g_string_free (flattened, TRUE);
+}
+
+void
+meta_screen_update_workspace_names (MetaScreen *screen)
+{
+ char **names;
+ int n_names;
+ int i;
+
+ /* this updates names in prefs when the root window property changes,
+ * iff the new property contents don't match what's already in prefs
+ */
+
+ names = NULL;
+ n_names = 0;
+ if (!meta_prop_get_utf8_list (screen->display,
+ screen->xroot,
+ screen->display->atom__NET_DESKTOP_NAMES,
+ &names, &n_names))
+ {
+ meta_verbose ("Failed to get workspace names from root window %d\n",
+ screen->number);
+ return;
+ }
+
+ i = 0;
+ while (i < n_names)
+ {
+ meta_topic (META_DEBUG_PREFS,
+ "Setting workspace %d name to \"%s\" due to _NET_DESKTOP_NAMES change\n",
+ i, names[i] ? names[i] : "null");
+ meta_prefs_change_workspace_name (i, names[i]);
+
+ ++i;
+ }
+
+ g_strfreev (names);
+}
+
+Window
+meta_create_offscreen_window (Display *xdisplay,
+ Window parent,
+ long valuemask)
+{
+ XSetWindowAttributes attrs;
+
+ /* we want to be override redirect because sometimes we
+ * create a window on a screen we aren't managing.
+ * (but on a display we are managing at least one screen for)
+ */
+ attrs.override_redirect = True;
+ attrs.event_mask = valuemask;
+
+ return XCreateWindow (xdisplay,
+ parent,
+ -100, -100, 1, 1,
+ 0,
+ CopyFromParent,
+ CopyFromParent,
+ (Visual *)CopyFromParent,
+ CWOverrideRedirect | CWEventMask,
+ &attrs);
+}
+
+static void
+set_work_area_hint (MetaScreen *screen)
+{
+ int num_workspaces;
+ GList *tmp_list;
+ unsigned long *data, *tmp;
+ MetaRectangle area;
+
+ num_workspaces = meta_screen_get_n_workspaces (screen);
+ data = g_new (unsigned long, num_workspaces * 4);
+ tmp_list = screen->workspaces;
+ tmp = data;
+
+ while (tmp_list != NULL)
+ {
+ MetaWorkspace *workspace = tmp_list->data;
+
+ if (workspace->screen == screen)
+ {
+ meta_workspace_get_work_area_all_xineramas (workspace, &area);
+ tmp[0] = area.x;
+ tmp[1] = area.y;
+ tmp[2] = area.width;
+ tmp[3] = area.height;
+
+ tmp += 4;
+ }
+
+ tmp_list = tmp_list->next;
+ }
+
+ meta_error_trap_push (screen->display);
+ XChangeProperty (screen->display->xdisplay, screen->xroot,
+ screen->display->atom__NET_WORKAREA,
+ XA_CARDINAL, 32, PropModeReplace,
+ (guchar*) data, num_workspaces*4);
+ g_free (data);
+ meta_error_trap_pop (screen->display, FALSE);
+}
+
+static gboolean
+set_work_area_idle_func (MetaScreen *screen)
+{
+ meta_topic (META_DEBUG_WORKAREA,
+ "Running work area idle function\n");
+
+ screen->work_area_idle = 0;
+
+ set_work_area_hint (screen);
+
+ return FALSE;
+}
+
+void
+meta_screen_queue_workarea_recalc (MetaScreen *screen)
+{
+ /* Recompute work area in an idle */
+ if (screen->work_area_idle == 0)
+ {
+ meta_topic (META_DEBUG_WORKAREA,
+ "Adding work area hint idle function\n");
+ screen->work_area_idle =
+ g_idle_add_full (META_PRIORITY_WORK_AREA_HINT,
+ (GSourceFunc) set_work_area_idle_func,
+ screen,
+ NULL);
+ }
+}
+
+
+#ifdef WITH_VERBOSE_MODE
+static char *
+meta_screen_corner_to_string (MetaScreenCorner corner)
+{
+ switch (corner)
+ {
+ case META_SCREEN_TOPLEFT:
+ return "TopLeft";
+ case META_SCREEN_TOPRIGHT:
+ return "TopRight";
+ case META_SCREEN_BOTTOMLEFT:
+ return "BottomLeft";
+ case META_SCREEN_BOTTOMRIGHT:
+ return "BottomRight";
+ }
+
+ return "Unknown";
+}
+#endif /* WITH_VERBOSE_MODE */
+
+void
+meta_screen_calc_workspace_layout (MetaScreen *screen,
+ int num_workspaces,
+ int current_space,
+ MetaWorkspaceLayout *layout)
+{
+ int rows, cols;
+ int grid_area;
+ int *grid;
+ int i, r, c;
+ int current_row, current_col;
+
+ rows = screen->rows_of_workspaces;
+ cols = screen->columns_of_workspaces;
+ if (rows <= 0 && cols <= 0)
+ cols = num_workspaces;
+
+ if (rows <= 0)
+ rows = num_workspaces / cols + ((num_workspaces % cols) > 0 ? 1 : 0);
+ if (cols <= 0)
+ cols = num_workspaces / rows + ((num_workspaces % rows) > 0 ? 1 : 0);
+
+ /* paranoia */
+ if (rows < 1)
+ rows = 1;
+ if (cols < 1)
+ cols = 1;
+
+ g_assert (rows != 0 && cols != 0);
+
+ grid_area = rows * cols;
+
+ meta_verbose ("Getting layout rows = %d cols = %d current = %d "
+ "num_spaces = %d vertical = %s corner = %s\n",
+ rows, cols, current_space, num_workspaces,
+ screen->vertical_workspaces ? "(true)" : "(false)",
+ meta_screen_corner_to_string (screen->starting_corner));
+
+ /* ok, we want to setup the distances in the workspace array to go
+ * in each direction. Remember, there are many ways that a workspace
+ * array can be setup.
+ * see http://www.freedesktop.org/standards/wm-spec/1.2/html/x109.html
+ * and look at the _NET_DESKTOP_LAYOUT section for details.
+ * For instance:
+ */
+ /* starting_corner = META_SCREEN_TOPLEFT
+ * vertical_workspaces = 0 vertical_workspaces=1
+ * 1234 1357
+ * 5678 2468
+ *
+ * starting_corner = META_SCREEN_TOPRIGHT
+ * vertical_workspaces = 0 vertical_workspaces=1
+ * 4321 7531
+ * 8765 8642
+ *
+ * starting_corner = META_SCREEN_BOTTOMLEFT
+ * vertical_workspaces = 0 vertical_workspaces=1
+ * 5678 2468
+ * 1234 1357
+ *
+ * starting_corner = META_SCREEN_BOTTOMRIGHT
+ * vertical_workspaces = 0 vertical_workspaces=1
+ * 8765 8642
+ * 4321 7531
+ *
+ */
+ /* keep in mind that we could have a ragged layout, e.g. the "8"
+ * in the above grids could be missing
+ */
+
+
+ grid = g_new (int, grid_area);
+
+ current_row = -1;
+ current_col = -1;
+ i = 0;
+
+ switch (screen->starting_corner)
+ {
+ case META_SCREEN_TOPLEFT:
+ if (screen->vertical_workspaces)
+ {
+ c = 0;
+ while (c < cols)
+ {
+ r = 0;
+ while (r < rows)
+ {
+ grid[r*cols+c] = i;
+ ++i;
+ ++r;
+ }
+ ++c;
+ }
+ }
+ else
+ {
+ r = 0;
+ while (r < rows)
+ {
+ c = 0;
+ while (c < cols)
+ {
+ grid[r*cols+c] = i;
+ ++i;
+ ++c;
+ }
+ ++r;
+ }
+ }
+ break;
+ case META_SCREEN_TOPRIGHT:
+ if (screen->vertical_workspaces)
+ {
+ c = cols - 1;
+ while (c >= 0)
+ {
+ r = 0;
+ while (r < rows)
+ {
+ grid[r*cols+c] = i;
+ ++i;
+ ++r;
+ }
+ --c;
+ }
+ }
+ else
+ {
+ r = 0;
+ while (r < rows)
+ {
+ c = cols - 1;
+ while (c >= 0)
+ {
+ grid[r*cols+c] = i;
+ ++i;
+ --c;
+ }
+ ++r;
+ }
+ }
+ break;
+ case META_SCREEN_BOTTOMLEFT:
+ if (screen->vertical_workspaces)
+ {
+ c = 0;
+ while (c < cols)
+ {
+ r = rows - 1;
+ while (r >= 0)
+ {
+ grid[r*cols+c] = i;
+ ++i;
+ --r;
+ }
+ ++c;
+ }
+ }
+ else
+ {
+ r = rows - 1;
+ while (r >= 0)
+ {
+ c = 0;
+ while (c < cols)
+ {
+ grid[r*cols+c] = i;
+ ++i;
+ ++c;
+ }
+ --r;
+ }
+ }
+ break;
+ case META_SCREEN_BOTTOMRIGHT:
+ if (screen->vertical_workspaces)
+ {
+ c = cols - 1;
+ while (c >= 0)
+ {
+ r = rows - 1;
+ while (r >= 0)
+ {
+ grid[r*cols+c] = i;
+ ++i;
+ --r;
+ }
+ --c;
+ }
+ }
+ else
+ {
+ r = rows - 1;
+ while (r >= 0)
+ {
+ c = cols - 1;
+ while (c >= 0)
+ {
+ grid[r*cols+c] = i;
+ ++i;
+ --c;
+ }
+ --r;
+ }
+ }
+ break;
+ }
+
+ if (i != grid_area)
+ meta_bug ("did not fill in the whole workspace grid in %s (%d filled)\n",
+ G_STRFUNC, i);
+
+ current_row = 0;
+ current_col = 0;
+ r = 0;
+ while (r < rows)
+ {
+ c = 0;
+ while (c < cols)
+ {
+ if (grid[r*cols+c] == current_space)
+ {
+ current_row = r;
+ current_col = c;
+ }
+ else if (grid[r*cols+c] >= num_workspaces)
+ {
+ /* flag nonexistent spaces with -1 */
+ grid[r*cols+c] = -1;
+ }
+ ++c;
+ }
+ ++r;
+ }
+
+ layout->rows = rows;
+ layout->cols = cols;
+ layout->grid = grid;
+ layout->grid_area = grid_area;
+ layout->current_row = current_row;
+ layout->current_col = current_col;
+
+#ifdef WITH_VERBOSE_MODE
+ if (meta_is_verbose ())
+ {
+ r = 0;
+ while (r < layout->rows)
+ {
+ meta_verbose (" ");
+ meta_push_no_msg_prefix ();
+ c = 0;
+ while (c < layout->cols)
+ {
+ if (r == layout->current_row &&
+ c == layout->current_col)
+ meta_verbose ("*%2d ", layout->grid[r*layout->cols+c]);
+ else
+ meta_verbose ("%3d ", layout->grid[r*layout->cols+c]);
+ ++c;
+ }
+ meta_verbose ("\n");
+ meta_pop_no_msg_prefix ();
+ ++r;
+ }
+ }
+#endif /* WITH_VERBOSE_MODE */
+}
+
+void
+meta_screen_free_workspace_layout (MetaWorkspaceLayout *layout)
+{
+ g_free (layout->grid);
+}
+
+static void
+meta_screen_resize_func (MetaScreen *screen,
+ MetaWindow *window,
+ void *user_data)
+{
+ if (window->struts)
+ {
+ meta_window_update_struts (window);
+ }
+ meta_window_queue (window, META_QUEUE_MOVE_RESIZE);
+
+ meta_window_recalc_features (window);
+}
+
+void
+meta_screen_resize (MetaScreen *screen,
+ int width,
+ int height)
+{
+ screen->rect.width = width;
+ screen->rect.height = height;
+
+ reload_xinerama_infos (screen);
+ set_desktop_geometry_hint (screen);
+
+ /* Queue a resize on all the windows */
+ meta_screen_foreach_window (screen, meta_screen_resize_func, 0);
+}
+
+void
+meta_screen_update_showing_desktop_hint (MetaScreen *screen)
+{
+ unsigned long data[1];
+
+ data[0] = screen->active_workspace->showing_desktop ? 1 : 0;
+
+ meta_error_trap_push (screen->display);
+ XChangeProperty (screen->display->xdisplay, screen->xroot,
+ screen->display->atom__NET_SHOWING_DESKTOP,
+ XA_CARDINAL,
+ 32, PropModeReplace, (guchar*) data, 1);
+ meta_error_trap_pop (screen->display, FALSE);
+}
+
+static void
+queue_windows_showing (MetaScreen *screen)
+{
+ GSList *windows;
+ GSList *tmp;
+
+ /* Must operate on all windows on display instead of just on the
+ * active_workspace's window list, because the active_workspace's
+ * window list may not contain the on_all_workspace windows.
+ */
+ windows = meta_display_list_windows (screen->display);
+
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ if (w->screen == screen)
+ meta_window_queue (w, META_QUEUE_CALC_SHOWING);
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (windows);
+}
+
+void
+meta_screen_minimize_all_on_active_workspace_except (MetaScreen *screen,
+ MetaWindow *keep)
+{
+ GList *windows;
+ GList *tmp;
+
+ windows = screen->active_workspace->windows;
+
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ if (w->screen == screen &&
+ w->has_minimize_func &&
+ w != keep)
+ meta_window_minimize (w);
+
+ tmp = tmp->next;
+ }
+}
+
+void
+meta_screen_show_desktop (MetaScreen *screen,
+ guint32 timestamp)
+{
+ GList *windows;
+
+ if (screen->active_workspace->showing_desktop)
+ return;
+
+ screen->active_workspace->showing_desktop = TRUE;
+
+ queue_windows_showing (screen);
+
+ /* Focus the most recently used META_WINDOW_DESKTOP window, if there is one;
+ * see bug 159257.
+ */
+ windows = screen->active_workspace->mru_list;
+ while (windows != NULL)
+ {
+ MetaWindow *w = windows->data;
+
+ if (w->screen == screen &&
+ w->type == META_WINDOW_DESKTOP)
+ {
+ meta_window_focus (w, timestamp);
+ break;
+ }
+
+ windows = windows->next;
+ }
+
+
+ meta_screen_update_showing_desktop_hint (screen);
+}
+
+void
+meta_screen_unshow_desktop (MetaScreen *screen)
+{
+ if (!screen->active_workspace->showing_desktop)
+ return;
+
+ screen->active_workspace->showing_desktop = FALSE;
+
+ queue_windows_showing (screen);
+
+ meta_screen_update_showing_desktop_hint (screen);
+}
+
+
+#ifdef HAVE_STARTUP_NOTIFICATION
+static gboolean startup_sequence_timeout (void *data);
+
+static void
+update_startup_feedback (MetaScreen *screen)
+{
+ if (screen->startup_sequences != NULL)
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "Setting busy cursor\n");
+ meta_screen_set_cursor (screen, META_CURSOR_BUSY);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "Setting default cursor\n");
+ meta_screen_set_cursor (screen, META_CURSOR_DEFAULT);
+ }
+}
+
+static void
+add_sequence (MetaScreen *screen,
+ SnStartupSequence *sequence)
+{
+ meta_topic (META_DEBUG_STARTUP,
+ "Adding sequence %s\n",
+ sn_startup_sequence_get_id (sequence));
+ sn_startup_sequence_ref (sequence);
+ screen->startup_sequences = g_slist_prepend (screen->startup_sequences,
+ sequence);
+
+ /* our timeout just polls every second, instead of bothering
+ * to compute exactly when we may next time out
+ */
+ if (screen->startup_sequence_timeout == 0)
+ screen->startup_sequence_timeout = g_timeout_add (1000,
+ startup_sequence_timeout,
+ screen);
+
+ update_startup_feedback (screen);
+}
+
+static void
+remove_sequence (MetaScreen *screen,
+ SnStartupSequence *sequence)
+{
+ meta_topic (META_DEBUG_STARTUP,
+ "Removing sequence %s\n",
+ sn_startup_sequence_get_id (sequence));
+
+ screen->startup_sequences = g_slist_remove (screen->startup_sequences,
+ sequence);
+ sn_startup_sequence_unref (sequence);
+
+ if (screen->startup_sequences == NULL &&
+ screen->startup_sequence_timeout != 0)
+ {
+ g_source_remove (screen->startup_sequence_timeout);
+ screen->startup_sequence_timeout = 0;
+ }
+
+ update_startup_feedback (screen);
+}
+
+typedef struct
+{
+ GSList *list;
+ GTimeVal now;
+} CollectTimedOutData;
+
+/* This should be fairly long, as it should never be required unless
+ * apps or .desktop files are buggy, and it's confusing if
+ * OpenOffice or whatever seems to stop launching - people
+ * might decide they need to launch it again.
+ */
+#define STARTUP_TIMEOUT 15000
+
+static void
+collect_timed_out_foreach (void *element,
+ void *data)
+{
+ CollectTimedOutData *ctod = data;
+ SnStartupSequence *sequence = element;
+ long tv_sec, tv_usec;
+ double elapsed;
+
+ sn_startup_sequence_get_last_active_time (sequence, &tv_sec, &tv_usec);
+
+ elapsed =
+ ((((double)ctod->now.tv_sec - tv_sec) * G_USEC_PER_SEC +
+ (ctod->now.tv_usec - tv_usec))) / 1000.0;
+
+ meta_topic (META_DEBUG_STARTUP,
+ "Sequence used %g seconds vs. %g max: %s\n",
+ elapsed, (double) STARTUP_TIMEOUT,
+ sn_startup_sequence_get_id (sequence));
+
+ if (elapsed > STARTUP_TIMEOUT)
+ ctod->list = g_slist_prepend (ctod->list, sequence);
+}
+
+static gboolean
+startup_sequence_timeout (void *data)
+{
+ MetaScreen *screen = data;
+ CollectTimedOutData ctod;
+ GSList *tmp;
+
+ ctod.list = NULL;
+ g_get_current_time (&ctod.now);
+ g_slist_foreach (screen->startup_sequences,
+ collect_timed_out_foreach,
+ &ctod);
+
+ tmp = ctod.list;
+ while (tmp != NULL)
+ {
+ SnStartupSequence *sequence = tmp->data;
+
+ meta_topic (META_DEBUG_STARTUP,
+ "Timed out sequence %s\n",
+ sn_startup_sequence_get_id (sequence));
+
+ sn_startup_sequence_complete (sequence);
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (ctod.list);
+
+ if (screen->startup_sequences != NULL)
+ {
+ return TRUE;
+ }
+ else
+ {
+ /* remove */
+ screen->startup_sequence_timeout = 0;
+ return FALSE;
+ }
+}
+
+static void
+meta_screen_sn_event (SnMonitorEvent *event,
+ void *user_data)
+{
+ MetaScreen *screen;
+ SnStartupSequence *sequence;
+
+ screen = user_data;
+
+ sequence = sn_monitor_event_get_startup_sequence (event);
+
+ switch (sn_monitor_event_get_type (event))
+ {
+ case SN_MONITOR_EVENT_INITIATED:
+ {
+ const char *wmclass;
+
+ wmclass = sn_startup_sequence_get_wmclass (sequence);
+
+ meta_topic (META_DEBUG_STARTUP,
+ "Received startup initiated for %s wmclass %s\n",
+ sn_startup_sequence_get_id (sequence),
+ wmclass ? wmclass : "(unset)");
+ add_sequence (screen, sequence);
+ }
+ break;
+
+ case SN_MONITOR_EVENT_COMPLETED:
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "Received startup completed for %s\n",
+ sn_startup_sequence_get_id (sequence));
+ remove_sequence (screen,
+ sn_monitor_event_get_startup_sequence (event));
+ }
+ break;
+
+ case SN_MONITOR_EVENT_CHANGED:
+ meta_topic (META_DEBUG_STARTUP,
+ "Received startup changed for %s\n",
+ sn_startup_sequence_get_id (sequence));
+ break;
+
+ case SN_MONITOR_EVENT_CANCELED:
+ meta_topic (META_DEBUG_STARTUP,
+ "Received startup canceled for %s\n",
+ sn_startup_sequence_get_id (sequence));
+ break;
+ }
+}
+#endif
+
+/* Sets the initial_timestamp and initial_workspace properties
+ * of a window according to information given us by the
+ * startup-notification library.
+ *
+ * Returns TRUE if startup properties have been applied, and
+ * FALSE if they have not (for example, if they had already
+ * been applied.)
+ */
+gboolean
+meta_screen_apply_startup_properties (MetaScreen *screen,
+ MetaWindow *window)
+{
+#ifdef HAVE_STARTUP_NOTIFICATION
+ const char *startup_id;
+ GSList *tmp;
+ SnStartupSequence *sequence;
+
+ /* Does the window have a startup ID stored? */
+ startup_id = meta_window_get_startup_id (window);
+
+ meta_topic (META_DEBUG_STARTUP,
+ "Applying startup props to %s id \"%s\"\n",
+ window->desc,
+ startup_id ? startup_id : "(none)");
+
+ sequence = NULL;
+ if (startup_id == NULL)
+ {
+ /* No startup ID stored for the window. Let's ask the
+ * startup-notification library whether there's anything
+ * stored for the resource name or resource class hints.
+ */
+ tmp = screen->startup_sequences;
+ while (tmp != NULL)
+ {
+ const char *wmclass;
+
+ wmclass = sn_startup_sequence_get_wmclass (tmp->data);
+
+ if (wmclass != NULL &&
+ ((window->res_class &&
+ strcmp (wmclass, window->res_class) == 0) ||
+ (window->res_name &&
+ strcmp (wmclass, window->res_name) == 0)))
+ {
+ sequence = tmp->data;
+
+ g_assert (window->startup_id == NULL);
+ window->startup_id = g_strdup (sn_startup_sequence_get_id (sequence));
+ startup_id = window->startup_id;
+
+ meta_topic (META_DEBUG_STARTUP,
+ "Ending legacy sequence %s due to window %s\n",
+ sn_startup_sequence_get_id (sequence),
+ window->desc);
+
+ sn_startup_sequence_complete (sequence);
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+ }
+
+ /* Still no startup ID? Bail. */
+ if (startup_id == NULL)
+ return FALSE;
+
+ /* We might get this far and not know the sequence ID (if the window
+ * already had a startup ID stored), so let's look for one if we don't
+ * already know it.
+ */
+ if (sequence == NULL)
+ {
+ tmp = screen->startup_sequences;
+ while (tmp != NULL)
+ {
+ const char *id;
+
+ id = sn_startup_sequence_get_id (tmp->data);
+
+ if (strcmp (id, startup_id) == 0)
+ {
+ sequence = tmp->data;
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+ }
+
+ if (sequence != NULL)
+ {
+ gboolean changed_something = FALSE;
+
+ meta_topic (META_DEBUG_STARTUP,
+ "Found startup sequence for window %s ID \"%s\"\n",
+ window->desc, startup_id);
+
+ if (!window->initial_workspace_set)
+ {
+ int space = sn_startup_sequence_get_workspace (sequence);
+ if (space >= 0)
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "Setting initial window workspace to %d based on startup info\n",
+ space);
+
+ window->initial_workspace_set = TRUE;
+ window->initial_workspace = space;
+ changed_something = TRUE;
+ }
+ }
+
+ if (!window->initial_timestamp_set)
+ {
+ guint32 timestamp = sn_startup_sequence_get_timestamp (sequence);
+ meta_topic (META_DEBUG_STARTUP,
+ "Setting initial window timestamp to %u based on startup info\n",
+ timestamp);
+
+ window->initial_timestamp_set = TRUE;
+ window->initial_timestamp = timestamp;
+ changed_something = TRUE;
+ }
+
+ return changed_something;
+ }
+ else
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "Did not find startup sequence for window %s ID \"%s\"\n",
+ window->desc, startup_id);
+ }
+
+#endif /* HAVE_STARTUP_NOTIFICATION */
+
+ return FALSE;
+}
+
+int
+meta_screen_get_screen_number (MetaScreen *screen)
+{
+ return screen->number;
+}
+
+MetaDisplay *
+meta_screen_get_display (MetaScreen *screen)
+{
+ return screen->display;
+}
+
+Window
+meta_screen_get_xroot (MetaScreen *screen)
+{
+ return screen->xroot;
+}
+
+void
+meta_screen_get_size (MetaScreen *screen,
+ int *width,
+ int *height)
+{
+ *width = screen->rect.width;
+ *height = screen->rect.height;
+}
+
+gpointer
+meta_screen_get_compositor_data (MetaScreen *screen)
+{
+ return screen->compositor_data;
+}
+
+void
+meta_screen_set_compositor_data (MetaScreen *screen,
+ gpointer compositor)
+{
+ screen->compositor_data = compositor;
+}
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+void
+meta_screen_set_cm_selection (MetaScreen *screen)
+{
+ char selection[32];
+ Atom a;
+
+ screen->wm_cm_timestamp = meta_display_get_current_time_roundtrip (
+ screen->display);
+
+ g_snprintf (selection, sizeof(selection), "_NET_WM_CM_S%d", screen->number);
+ meta_verbose ("Setting selection: %s\n", selection);
+ a = XInternAtom (screen->display->xdisplay, selection, FALSE);
+ XSetSelectionOwner (screen->display->xdisplay, a,
+ screen->wm_cm_selection_window, screen->wm_cm_timestamp);
+}
+
+void
+meta_screen_unset_cm_selection (MetaScreen *screen)
+{
+ char selection[32];
+ Atom a;
+
+ g_snprintf (selection, sizeof(selection), "_NET_WM_CM_S%d", screen->number);
+ a = XInternAtom (screen->display->xdisplay, selection, FALSE);
+ XSetSelectionOwner (screen->display->xdisplay, a,
+ None, screen->wm_cm_timestamp);
+}
+#endif /* HAVE_COMPOSITE_EXTENSIONS */
diff --git a/src/core/session.c b/src/core/session.c
new file mode 100644
index 00000000..80d22365
--- /dev/null
+++ b/src/core/session.c
@@ -0,0 +1,1831 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco Session Management */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington (some code in here from
+ * libmateui, (C) Tom Tromey, Carsten Schaar)
+ * Copyright (C) 2004, 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include "session.h"
+#include <X11/Xatom.h>
+
+#include <time.h>
+#include <sys/wait.h>
+
+#ifndef HAVE_SM
+void
+meta_session_init (const char *client_id,
+ const char *save_file)
+{
+ meta_topic (META_DEBUG_SM, "Compiled without session management support\n");
+}
+
+void
+meta_session_shutdown (void)
+{
+ /* nothing */
+}
+
+const MetaWindowSessionInfo*
+meta_window_lookup_saved_state (MetaWindow *window)
+{
+ return NULL;
+}
+
+void
+meta_window_release_saved_state (const MetaWindowSessionInfo *info)
+{
+ ;
+}
+#else /* HAVE_SM */
+
+#include <X11/ICE/ICElib.h>
+#include <X11/SM/SMlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <glib.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "main.h"
+#include "util.h"
+#include "display-private.h"
+#include "workspace.h"
+
+static void ice_io_error_handler (IceConn connection);
+
+static void new_ice_connection (IceConn connection, IcePointer client_data,
+ Bool opening, IcePointer *watch_data);
+
+static void save_state (void);
+static char* load_state (const char *previous_save_file);
+static void regenerate_save_file (void);
+static const char* full_save_file (void);
+static void warn_about_lame_clients_and_finish_interact (gboolean shutdown);
+static void disconnect (void);
+
+/* This is called when data is available on an ICE connection. */
+static gboolean
+process_ice_messages (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer client_data)
+{
+ IceConn connection = (IceConn) client_data;
+ IceProcessMessagesStatus status;
+
+ /* This blocks infinitely sometimes. I don't know what
+ * to do about it. Checking "condition" just breaks
+ * session management.
+ */
+ status = IceProcessMessages (connection, NULL, NULL);
+
+ if (status == IceProcessMessagesIOError)
+ {
+#if 0
+ IcePointer context = IceGetConnectionContext (connection);
+#endif
+
+ /* We were disconnected; close our connection to the
+ * session manager, this will result in the ICE connection
+ * being cleaned up, since it is owned by libSM.
+ */
+ disconnect ();
+ meta_quit (META_EXIT_SUCCESS);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* This is called when a new ICE connection is made. It arranges for
+ the ICE connection to be handled via the event loop. */
+static void
+new_ice_connection (IceConn connection, IcePointer client_data, Bool opening,
+ IcePointer *watch_data)
+{
+ guint input_id;
+
+ if (opening)
+ {
+ /* Make sure we don't pass on these file descriptors to any
+ * exec'ed children
+ */
+ GIOChannel *channel;
+
+ fcntl (IceConnectionNumber (connection), F_SETFD,
+ fcntl (IceConnectionNumber (connection), F_GETFD, 0) | FD_CLOEXEC);
+
+ channel = g_io_channel_unix_new (IceConnectionNumber (connection));
+
+ input_id = g_io_add_watch (channel,
+ G_IO_IN | G_IO_ERR,
+ process_ice_messages,
+ connection);
+
+ g_io_channel_unref (channel);
+
+ *watch_data = (IcePointer) GUINT_TO_POINTER (input_id);
+ }
+ else
+ {
+ input_id = GPOINTER_TO_UINT ((gpointer) *watch_data);
+
+ g_source_remove (input_id);
+ }
+}
+
+static IceIOErrorHandler ice_installed_handler;
+
+/* We call any handler installed before (or after) mate_ice_init but
+ avoid calling the default libICE handler which does an exit() */
+static void
+ice_io_error_handler (IceConn connection)
+{
+ if (ice_installed_handler)
+ (*ice_installed_handler) (connection);
+}
+
+static void
+ice_init (void)
+{
+ static gboolean ice_initted = FALSE;
+
+ if (! ice_initted)
+ {
+ IceIOErrorHandler default_handler;
+
+ ice_installed_handler = IceSetIOErrorHandler (NULL);
+ default_handler = IceSetIOErrorHandler (ice_io_error_handler);
+
+ if (ice_installed_handler == default_handler)
+ ice_installed_handler = NULL;
+
+ IceAddConnectionWatch (new_ice_connection, NULL);
+
+ ice_initted = TRUE;
+ }
+}
+
+typedef enum
+{
+ STATE_DISCONNECTED,
+ STATE_IDLE,
+ STATE_SAVING_PHASE_1,
+ STATE_WAITING_FOR_PHASE_2,
+ STATE_SAVING_PHASE_2,
+ STATE_WAITING_FOR_INTERACT,
+ STATE_DONE_WITH_INTERACT,
+ STATE_SKIPPING_GLOBAL_SAVE,
+ STATE_FROZEN,
+ STATE_REGISTERING
+} ClientState;
+
+static void save_phase_2_callback (SmcConn smc_conn,
+ SmPointer client_data);
+static void interact_callback (SmcConn smc_conn,
+ SmPointer client_data);
+static void shutdown_cancelled_callback (SmcConn smc_conn,
+ SmPointer client_data);
+static void save_complete_callback (SmcConn smc_conn,
+ SmPointer client_data);
+static void die_callback (SmcConn smc_conn,
+ SmPointer client_data);
+static void save_yourself_callback (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_style,
+ Bool shutdown,
+ int interact_style,
+ Bool fast);
+static void set_clone_restart_commands (void);
+
+static char *client_id = NULL;
+static gpointer session_connection = NULL;
+static ClientState current_state = STATE_DISCONNECTED;
+static gboolean interaction_allowed = FALSE;
+
+void
+meta_session_init (const char *previous_client_id,
+ const char *previous_save_file)
+{
+ /* Some code here from twm */
+ char buf[256];
+ unsigned long mask;
+ SmcCallbacks callbacks;
+ char *saved_client_id;
+
+ meta_topic (META_DEBUG_SM, "Initializing session with save file '%s'\n",
+ previous_save_file ? previous_save_file : "(none)");
+
+ if (previous_save_file)
+ {
+ saved_client_id = load_state (previous_save_file);
+ previous_client_id = saved_client_id;
+ }
+ else if (previous_client_id)
+ {
+ char *save_file = g_strconcat (previous_client_id, ".ms", NULL);
+ saved_client_id = load_state (save_file);
+ g_free (save_file);
+ }
+ else
+ {
+ saved_client_id = NULL;
+ }
+
+ ice_init ();
+
+ mask = SmcSaveYourselfProcMask | SmcDieProcMask |
+ SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask;
+
+ callbacks.save_yourself.callback = save_yourself_callback;
+ callbacks.save_yourself.client_data = NULL;
+
+ callbacks.die.callback = die_callback;
+ callbacks.die.client_data = NULL;
+
+ callbacks.save_complete.callback = save_complete_callback;
+ callbacks.save_complete.client_data = NULL;
+
+ callbacks.shutdown_cancelled.callback = shutdown_cancelled_callback;
+ callbacks.shutdown_cancelled.client_data = NULL;
+
+ session_connection =
+ SmcOpenConnection (NULL, /* use SESSION_MANAGER env */
+ NULL, /* means use existing ICE connection */
+ SmProtoMajor,
+ SmProtoMinor,
+ mask,
+ &callbacks,
+ (char*) previous_client_id,
+ &client_id,
+ 255, buf);
+
+ if (session_connection == NULL)
+ {
+ meta_topic (META_DEBUG_SM,
+ "Failed to a open connection to a session manager, so window positions will not be saved: %s\n",
+ buf);
+
+ goto out;
+ }
+ else
+ {
+ if (client_id == NULL)
+ meta_bug ("Session manager gave us a NULL client ID?");
+ meta_topic (META_DEBUG_SM, "Obtained session ID '%s'\n", client_id);
+ }
+
+ if (previous_client_id && strcmp (previous_client_id, client_id) == 0)
+ current_state = STATE_IDLE;
+ else
+ current_state = STATE_REGISTERING;
+
+ {
+ SmProp prop1, prop2, prop3, prop4, prop5, prop6, *props[6];
+ SmPropValue prop1val, prop2val, prop3val, prop4val, prop5val, prop6val;
+ char pid[32];
+ char hint = SmRestartImmediately;
+ char priority = 20; /* low to run before other apps */
+
+ prop1.name = SmProgram;
+ prop1.type = SmARRAY8;
+ prop1.num_vals = 1;
+ prop1.vals = &prop1val;
+ prop1val.value = "marco";
+ prop1val.length = strlen ("marco");
+
+ /* twm sets getuid() for this, but the SM spec plainly
+ * says pw_name, twm is on crack
+ */
+ prop2.name = SmUserID;
+ prop2.type = SmARRAY8;
+ prop2.num_vals = 1;
+ prop2.vals = &prop2val;
+ prop2val.value = (char*) g_get_user_name ();
+ prop2val.length = strlen (prop2val.value);
+
+ prop3.name = SmRestartStyleHint;
+ prop3.type = SmCARD8;
+ prop3.num_vals = 1;
+ prop3.vals = &prop3val;
+ prop3val.value = &hint;
+ prop3val.length = 1;
+
+ sprintf (pid, "%d", getpid ());
+ prop4.name = SmProcessID;
+ prop4.type = SmARRAY8;
+ prop4.num_vals = 1;
+ prop4.vals = &prop4val;
+ prop4val.value = pid;
+ prop4val.length = strlen (prop4val.value);
+
+ /* Always start in home directory */
+ prop5.name = SmCurrentDirectory;
+ prop5.type = SmARRAY8;
+ prop5.num_vals = 1;
+ prop5.vals = &prop5val;
+ prop5val.value = (char*) g_get_home_dir ();
+ prop5val.length = strlen (prop5val.value);
+
+ prop6.name = "_GSM_Priority";
+ prop6.type = SmCARD8;
+ prop6.num_vals = 1;
+ prop6.vals = &prop6val;
+ prop6val.value = &priority;
+ prop6val.length = 1;
+
+ props[0] = &prop1;
+ props[1] = &prop2;
+ props[2] = &prop3;
+ props[3] = &prop4;
+ props[4] = &prop5;
+ props[5] = &prop6;
+
+ SmcSetProperties (session_connection, 6, props);
+ }
+
+ out:
+ g_free (saved_client_id);
+}
+
+void
+meta_session_shutdown (void)
+{
+ /* Change our restart mode to IfRunning */
+
+ SmProp prop1;
+ SmPropValue prop1val;
+ SmProp *props[1];
+ char hint = SmRestartIfRunning;
+
+ if (!meta_get_display ())
+ {
+ meta_verbose ("Cannot close session because there is no display");
+ return;
+ }
+
+ warn_about_lame_clients_and_finish_interact (FALSE);
+
+ if (session_connection == NULL)
+ return;
+
+ prop1.name = SmRestartStyleHint;
+ prop1.type = SmCARD8;
+ prop1.num_vals = 1;
+ prop1.vals = &prop1val;
+ prop1val.value = &hint;
+ prop1val.length = 1;
+
+ props[0] = &prop1;
+
+ SmcSetProperties (session_connection, 1, props);
+}
+
+static void
+disconnect (void)
+{
+ SmcCloseConnection (session_connection, 0, NULL);
+ session_connection = NULL;
+ current_state = STATE_DISCONNECTED;
+}
+
+static void
+save_yourself_possibly_done (gboolean shutdown,
+ gboolean successful)
+{
+ meta_topic (META_DEBUG_SM,
+ "save possibly done shutdown = %d success = %d\n",
+ shutdown, successful);
+
+ if (current_state == STATE_SAVING_PHASE_1)
+ {
+ Status status;
+
+ status = SmcRequestSaveYourselfPhase2 (session_connection,
+ save_phase_2_callback,
+ GINT_TO_POINTER (shutdown));
+
+ if (status)
+ current_state = STATE_WAITING_FOR_PHASE_2;
+
+ meta_topic (META_DEBUG_SM,
+ "Requested phase 2, status = %d\n", status);
+ }
+
+ if (current_state == STATE_SAVING_PHASE_2 &&
+ interaction_allowed)
+ {
+ Status status;
+
+ status = SmcInteractRequest (session_connection,
+ /* ignore this feature of the protocol by always
+ * claiming normal
+ */
+ SmDialogNormal,
+ interact_callback,
+ GINT_TO_POINTER (shutdown));
+
+ if (status)
+ current_state = STATE_WAITING_FOR_INTERACT;
+
+ meta_topic (META_DEBUG_SM,
+ "Requested interact, status = %d\n", status);
+ }
+
+ if (current_state == STATE_SAVING_PHASE_1 ||
+ current_state == STATE_SAVING_PHASE_2 ||
+ current_state == STATE_DONE_WITH_INTERACT ||
+ current_state == STATE_SKIPPING_GLOBAL_SAVE)
+ {
+ meta_topic (META_DEBUG_SM, "Sending SaveYourselfDone\n");
+
+ SmcSaveYourselfDone (session_connection,
+ successful);
+
+ if (shutdown)
+ current_state = STATE_FROZEN;
+ else
+ current_state = STATE_IDLE;
+ }
+}
+
+static void
+save_phase_2_callback (SmcConn smc_conn, SmPointer client_data)
+{
+ gboolean shutdown;
+
+ meta_topic (META_DEBUG_SM, "Phase 2 save");
+
+ shutdown = GPOINTER_TO_INT (client_data);
+
+ current_state = STATE_SAVING_PHASE_2;
+
+ save_state ();
+
+ save_yourself_possibly_done (shutdown, TRUE);
+}
+
+static void
+save_yourself_callback (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_style,
+ Bool shutdown,
+ int interact_style,
+ Bool fast)
+{
+ gboolean successful;
+
+ meta_topic (META_DEBUG_SM, "SaveYourself received");
+
+ successful = TRUE;
+
+ /* The first SaveYourself after registering for the first time
+ * is a special case (SM specs 7.2).
+ */
+
+#if 0 /* I think the MateClient rationale for this doesn't apply */
+ if (current_state == STATE_REGISTERING)
+ {
+ current_state = STATE_IDLE;
+ /* Double check that this is a section 7.2 SaveYourself: */
+
+ if (save_style == SmSaveLocal &&
+ interact_style == SmInteractStyleNone &&
+ !shutdown && !fast)
+ {
+ /* The protocol requires this even if xsm ignores it. */
+ SmcSaveYourselfDone (session_connection, successful);
+ return;
+ }
+ }
+#endif
+
+ /* ignore Global style saves
+ *
+ * This interpretaion of the Local/Global/Both styles
+ * was discussed extensively on the xdg-list. See:
+ *
+ * https://listman.redhat.com/pipermail/xdg-list/2002-July/000615.html
+ */
+ if (save_style == SmSaveGlobal)
+ {
+ current_state = STATE_SKIPPING_GLOBAL_SAVE;
+ save_yourself_possibly_done (shutdown, successful);
+ return;
+ }
+
+ interaction_allowed = interact_style != SmInteractStyleNone;
+
+ current_state = STATE_SAVING_PHASE_1;
+
+ regenerate_save_file ();
+
+ set_clone_restart_commands ();
+
+ save_yourself_possibly_done (shutdown, successful);
+}
+
+
+static void
+die_callback (SmcConn smc_conn, SmPointer client_data)
+{
+ meta_topic (META_DEBUG_SM, "Exiting at request of session manager\n");
+ disconnect ();
+ meta_quit (META_EXIT_SUCCESS);
+}
+
+static void
+save_complete_callback (SmcConn smc_conn, SmPointer client_data)
+{
+ /* nothing */
+ meta_topic (META_DEBUG_SM, "SaveComplete received\n");
+}
+
+static void
+shutdown_cancelled_callback (SmcConn smc_conn, SmPointer client_data)
+{
+ meta_topic (META_DEBUG_SM, "Shutdown cancelled received\n");
+
+ if (session_connection != NULL &&
+ (current_state != STATE_IDLE && current_state != STATE_FROZEN))
+ {
+ SmcSaveYourselfDone (session_connection, True);
+ current_state = STATE_IDLE;
+ }
+}
+
+static void
+interact_callback (SmcConn smc_conn, SmPointer client_data)
+{
+ /* nothing */
+ gboolean shutdown;
+
+ meta_topic (META_DEBUG_SM, "Interaction permission received\n");
+
+ shutdown = GPOINTER_TO_INT (client_data);
+
+ current_state = STATE_DONE_WITH_INTERACT;
+
+ warn_about_lame_clients_and_finish_interact (shutdown);
+}
+
+static void
+set_clone_restart_commands (void)
+{
+ char *restartv[10];
+ char *clonev[10];
+ char *discardv[10];
+ int i;
+ SmProp prop1, prop2, prop3, *props[3];
+
+ /* Restart (use same client ID) */
+
+ prop1.name = SmRestartCommand;
+ prop1.type = SmLISTofARRAY8;
+
+ g_return_if_fail (client_id);
+
+ i = 0;
+ restartv[i] = "marco";
+ ++i;
+ restartv[i] = "--sm-client-id";
+ ++i;
+ restartv[i] = client_id;
+ ++i;
+ restartv[i] = NULL;
+
+ prop1.vals = g_new (SmPropValue, i);
+ i = 0;
+ while (restartv[i])
+ {
+ prop1.vals[i].value = restartv[i];
+ prop1.vals[i].length = strlen (restartv[i]);
+ ++i;
+ }
+ prop1.num_vals = i;
+
+ /* Clone (no client ID) */
+
+ i = 0;
+ clonev[i] = "marco";
+ ++i;
+ clonev[i] = NULL;
+
+ prop2.name = SmCloneCommand;
+ prop2.type = SmLISTofARRAY8;
+
+ prop2.vals = g_new (SmPropValue, i);
+ i = 0;
+ while (clonev[i])
+ {
+ prop2.vals[i].value = clonev[i];
+ prop2.vals[i].length = strlen (clonev[i]);
+ ++i;
+ }
+ prop2.num_vals = i;
+
+ /* Discard */
+
+ i = 0;
+ discardv[i] = "rm";
+ ++i;
+ discardv[i] = "-f";
+ ++i;
+ discardv[i] = (char*) full_save_file ();
+ ++i;
+ discardv[i] = NULL;
+
+ prop3.name = SmDiscardCommand;
+ prop3.type = SmLISTofARRAY8;
+
+ prop3.vals = g_new (SmPropValue, i);
+ i = 0;
+ while (discardv[i])
+ {
+ prop3.vals[i].value = discardv[i];
+ prop3.vals[i].length = strlen (discardv[i]);
+ ++i;
+ }
+ prop3.num_vals = i;
+
+
+ props[0] = &prop1;
+ props[1] = &prop2;
+ props[2] = &prop3;
+
+ SmcSetProperties (session_connection, 3, props);
+
+ g_free (prop1.vals);
+ g_free (prop2.vals);
+ g_free (prop3.vals);
+}
+
+/* The remaining code in this file actually loads/saves the session,
+ * while the code above this comment handles chatting with the
+ * session manager.
+ */
+
+static const char*
+window_type_to_string (MetaWindowType type)
+{
+ switch (type)
+ {
+ case META_WINDOW_NORMAL:
+ return "normal";
+ case META_WINDOW_DESKTOP:
+ return "desktop";
+ case META_WINDOW_DOCK:
+ return "dock";
+ case META_WINDOW_DIALOG:
+ return "dialog";
+ case META_WINDOW_MODAL_DIALOG:
+ return "modal_dialog";
+ case META_WINDOW_TOOLBAR:
+ return "toolbar";
+ case META_WINDOW_MENU:
+ return "menu";
+ case META_WINDOW_SPLASHSCREEN:
+ return "splashscreen";
+ case META_WINDOW_UTILITY:
+ return "utility";
+ }
+
+ return "";
+}
+
+static MetaWindowType
+window_type_from_string (const char *str)
+{
+ if (strcmp (str, "normal") == 0)
+ return META_WINDOW_NORMAL;
+ else if (strcmp (str, "desktop") == 0)
+ return META_WINDOW_DESKTOP;
+ else if (strcmp (str, "dock") == 0)
+ return META_WINDOW_DOCK;
+ else if (strcmp (str, "dialog") == 0)
+ return META_WINDOW_DIALOG;
+ else if (strcmp (str, "modal_dialog") == 0)
+ return META_WINDOW_MODAL_DIALOG;
+ else if (strcmp (str, "toolbar") == 0)
+ return META_WINDOW_TOOLBAR;
+ else if (strcmp (str, "menu") == 0)
+ return META_WINDOW_MENU;
+ else if (strcmp (str, "utility") == 0)
+ return META_WINDOW_UTILITY;
+ else if (strcmp (str, "splashscreen") == 0)
+ return META_WINDOW_SPLASHSCREEN;
+ else
+ return META_WINDOW_NORMAL;
+}
+
+static int
+window_gravity_from_string (const char *str)
+{
+ if (strcmp (str, "NorthWestGravity") == 0)
+ return NorthWestGravity;
+ else if (strcmp (str, "NorthGravity") == 0)
+ return NorthGravity;
+ else if (strcmp (str, "NorthEastGravity") == 0)
+ return NorthEastGravity;
+ else if (strcmp (str, "WestGravity") == 0)
+ return WestGravity;
+ else if (strcmp (str, "CenterGravity") == 0)
+ return CenterGravity;
+ else if (strcmp (str, "EastGravity") == 0)
+ return EastGravity;
+ else if (strcmp (str, "SouthWestGravity") == 0)
+ return SouthWestGravity;
+ else if (strcmp (str, "SouthGravity") == 0)
+ return SouthGravity;
+ else if (strcmp (str, "SouthEastGravity") == 0)
+ return SouthEastGravity;
+ else if (strcmp (str, "StaticGravity") == 0)
+ return StaticGravity;
+ else
+ return NorthWestGravity;
+}
+
+static char*
+encode_text_as_utf8_markup (const char *text)
+{
+ /* text can be any encoding, and is nul-terminated.
+ * we pretend it's Latin-1 and encode as UTF-8
+ */
+ GString *str;
+ const char *p;
+ char *escaped;
+
+ str = g_string_new ("");
+
+ p = text;
+ while (*p)
+ {
+ g_string_append_unichar (str, *p);
+ ++p;
+ }
+
+ escaped = g_markup_escape_text (str->str, str->len);
+ g_string_free (str, TRUE);
+
+ return escaped;
+}
+
+static char*
+decode_text_from_utf8 (const char *text)
+{
+ /* Convert back from the encoded (but not escaped) UTF-8 */
+ GString *str;
+ const char *p;
+
+ str = g_string_new ("");
+
+ p = text;
+ while (*p)
+ {
+ /* obviously this barfs if the UTF-8 contains chars > 255 */
+ g_string_append_c (str, g_utf8_get_char (p));
+
+ p = g_utf8_next_char (p);
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+static void
+save_state (void)
+{
+ char *marco_dir;
+ char *session_dir;
+ FILE *outfile;
+ GSList *windows;
+ GSList *tmp;
+ int stack_position;
+
+ g_assert (client_id);
+
+ outfile = NULL;
+
+ /*
+ * g_get_user_config_dir() is guaranteed to return an existing directory.
+ * Eventually, if SM stays with the WM, I'd like to make this
+ * something like <config>/window_placement in a standard format.
+ * Future optimisers should note also that by the time we get here
+ * we probably already have full_save_path figured out and therefore
+ * can just use the directory name from that.
+ */
+ marco_dir = g_strconcat (g_get_user_config_dir (),
+ G_DIR_SEPARATOR_S "marco",
+ NULL);
+
+ session_dir = g_strconcat (marco_dir,
+ G_DIR_SEPARATOR_S "sessions",
+ NULL);
+
+ if (mkdir (marco_dir, 0700) < 0 &&
+ errno != EEXIST)
+ {
+ meta_warning (_("Could not create directory '%s': %s\n"),
+ marco_dir, g_strerror (errno));
+ }
+
+ if (mkdir (session_dir, 0700) < 0 &&
+ errno != EEXIST)
+ {
+ meta_warning (_("Could not create directory '%s': %s\n"),
+ session_dir, g_strerror (errno));
+ }
+
+ meta_topic (META_DEBUG_SM, "Saving session to '%s'\n", full_save_file ());
+
+ outfile = fopen (full_save_file (), "w");
+
+ if (outfile == NULL)
+ {
+ meta_warning (_("Could not open session file '%s' for writing: %s\n"),
+ full_save_file (), g_strerror (errno));
+ goto out;
+ }
+
+ /* The file format is:
+ * <marco_session id="foo">
+ * <window id="bar" class="XTerm" name="xterm" title="/foo/bar" role="blah" type="normal" stacking="5">
+ * <workspace index="2"/>
+ * <workspace index="4"/>
+ * <sticky/> <minimized/> <maximized/>
+ * <geometry x="100" y="100" width="200" height="200" gravity="northwest"/>
+ * </window>
+ * </marco_session>
+ *
+ * Note that attributes on <window> are the match info we use to
+ * see if the saved state applies to a restored window, and
+ * child elements are the saved state to be applied.
+ *
+ */
+
+ fprintf (outfile, "<marco_session id=\"%s\">\n",
+ client_id);
+
+ windows = meta_display_list_windows (meta_get_display ());
+ stack_position = 0;
+
+ windows = g_slist_sort (windows, meta_display_stack_cmp);
+ tmp = windows;
+ stack_position = 0;
+
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+
+ window = tmp->data;
+
+ if (window->sm_client_id)
+ {
+ char *sm_client_id;
+ char *res_class;
+ char *res_name;
+ char *role;
+ char *title;
+
+ /* client id, class, name, role are not expected to be
+ * in UTF-8 (I think they are in XPCS which is Latin-1?
+ * in practice they are always ascii though.)
+ */
+
+ sm_client_id = encode_text_as_utf8_markup (window->sm_client_id);
+ res_class = window->res_class ?
+ encode_text_as_utf8_markup (window->res_class) : NULL;
+ res_name = window->res_name ?
+ encode_text_as_utf8_markup (window->res_name) : NULL;
+ role = window->role ?
+ encode_text_as_utf8_markup (window->role) : NULL;
+ if (window->title)
+ title = g_markup_escape_text (window->title, -1);
+ else
+ title = NULL;
+
+ meta_topic (META_DEBUG_SM, "Saving session managed window %s, client ID '%s'\n",
+ window->desc, window->sm_client_id);
+
+ fprintf (outfile,
+ " <window id=\"%s\" class=\"%s\" name=\"%s\" title=\"%s\" role=\"%s\" type=\"%s\" stacking=\"%d\">\n",
+ sm_client_id,
+ res_class ? res_class : "",
+ res_name ? res_name : "",
+ title ? title : "",
+ role ? role : "",
+ window_type_to_string (window->type),
+ stack_position);
+
+ g_free (sm_client_id);
+ g_free (res_class);
+ g_free (res_name);
+ g_free (role);
+ g_free (title);
+
+ /* Sticky */
+ if (window->on_all_workspaces)
+ fputs (" <sticky/>\n", outfile);
+
+ /* Minimized */
+ if (window->minimized)
+ fputs (" <minimized/>\n", outfile);
+
+ /* Maximized */
+ if (META_WINDOW_MAXIMIZED (window))
+ {
+ fprintf (outfile,
+ " <maximized saved_x=\"%d\" saved_y=\"%d\" saved_width=\"%d\" saved_height=\"%d\"/>\n",
+ window->saved_rect.x,
+ window->saved_rect.y,
+ window->saved_rect.width,
+ window->saved_rect.height);
+ }
+
+ /* Workspaces we're on */
+ {
+ int n;
+ n = meta_workspace_index (window->workspace);
+ fprintf (outfile,
+ " <workspace index=\"%d\"/>\n", n);
+ }
+
+ /* Gravity */
+ {
+ int x, y, w, h;
+ meta_window_get_geometry (window, &x, &y, &w, &h);
+
+ fprintf (outfile,
+ " <geometry x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" gravity=\"%s\"/>\n",
+ x, y, w, h,
+ meta_gravity_to_string (window->size_hints.win_gravity));
+ }
+
+ fputs (" </window>\n", outfile);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_SM, "Not saving window '%s', not session managed\n",
+ window->desc);
+ }
+
+ tmp = tmp->next;
+ ++stack_position;
+ }
+
+ g_slist_free (windows);
+
+ fputs ("</marco_session>\n", outfile);
+
+ out:
+ if (outfile)
+ {
+ /* FIXME need a dialog for this */
+ if (ferror (outfile))
+ {
+ meta_warning (_("Error writing session file '%s': %s\n"),
+ full_save_file (), g_strerror (errno));
+ }
+ if (fclose (outfile))
+ {
+ meta_warning (_("Error closing session file '%s': %s\n"),
+ full_save_file (), g_strerror (errno));
+ }
+ }
+
+ g_free (marco_dir);
+ g_free (session_dir);
+}
+
+typedef enum
+{
+ WINDOW_TAG_NONE,
+ WINDOW_TAG_DESKTOP,
+ WINDOW_TAG_STICKY,
+ WINDOW_TAG_MINIMIZED,
+ WINDOW_TAG_MAXIMIZED,
+ WINDOW_TAG_GEOMETRY
+} WindowTag;
+
+typedef struct
+{
+ MetaWindowSessionInfo *info;
+ char *previous_id;
+} ParseData;
+
+static void session_info_free (MetaWindowSessionInfo *info);
+static MetaWindowSessionInfo* session_info_new (void);
+
+static void start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error);
+static void text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+
+static GMarkupParser marco_session_parser = {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ NULL,
+ NULL
+};
+
+static GSList *window_info_list = NULL;
+
+static char*
+load_state (const char *previous_save_file)
+{
+ GMarkupParseContext *context;
+ GError *error;
+ ParseData parse_data;
+ char *text;
+ gsize length;
+ char *session_file;
+
+ session_file = g_strconcat (g_get_user_config_dir (),
+ G_DIR_SEPARATOR_S "marco"
+ G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S,
+ previous_save_file,
+ NULL);
+
+ error = NULL;
+ if (!g_file_get_contents (session_file,
+ &text,
+ &length,
+ &error))
+ {
+ char *canonical_session_file = session_file;
+
+ /* Maybe they were doing it the old way, with ~/.marco */
+ session_file = g_strconcat (g_get_home_dir (),
+ G_DIR_SEPARATOR_S ".marco"
+ G_DIR_SEPARATOR_S "sessions"
+ G_DIR_SEPARATOR_S,
+ previous_save_file,
+ NULL);
+
+ if (!g_file_get_contents (session_file,
+ &text,
+ &length,
+ NULL))
+ {
+ /* oh, just give up */
+
+ g_error_free (error);
+ g_free (session_file);
+ g_free (canonical_session_file);
+ return NULL;
+ }
+
+ g_free (canonical_session_file);
+ }
+
+ meta_topic (META_DEBUG_SM, "Parsing saved session file %s\n", session_file);
+ g_free (session_file);
+ session_file = NULL;
+
+ parse_data.info = NULL;
+ parse_data.previous_id = NULL;
+
+ context = g_markup_parse_context_new (&marco_session_parser,
+ 0, &parse_data, NULL);
+
+ error = NULL;
+ if (!g_markup_parse_context_parse (context,
+ text,
+ length,
+ &error))
+ goto error;
+
+
+ error = NULL;
+ if (!g_markup_parse_context_end_parse (context, &error))
+ goto error;
+
+ g_markup_parse_context_free (context);
+
+ goto out;
+
+ error:
+
+ meta_warning (_("Failed to parse saved session file: %s\n"),
+ error->message);
+ g_error_free (error);
+
+ if (parse_data.info)
+ session_info_free (parse_data.info);
+
+ g_free (parse_data.previous_id);
+ parse_data.previous_id = NULL;
+
+ out:
+
+ g_free (text);
+
+ return parse_data.previous_id;
+}
+
+/* FIXME this isn't very robust against bogus session files */
+static void
+start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ParseData *pd;
+
+ pd = user_data;
+
+ if (strcmp (element_name, "marco_session") == 0)
+ {
+ /* Get previous ID */
+ int i;
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ const char *name;
+ const char *val;
+
+ name = attribute_names[i];
+ val = attribute_values[i];
+
+ if (pd->previous_id)
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("<marco_session> attribute seen but we already have the session ID"));
+ return;
+ }
+
+ if (strcmp (name, "id") == 0)
+ {
+ pd->previous_id = decode_text_from_utf8 (val);
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ _("Unknown attribute %s on <%s> element"),
+ name, "marco_session");
+ return;
+ }
+
+ ++i;
+ }
+ }
+ else if (strcmp (element_name, "window") == 0)
+ {
+ int i;
+
+ if (pd->info)
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("nested <window> tag"));
+ return;
+ }
+
+ pd->info = session_info_new ();
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ const char *name;
+ const char *val;
+
+ name = attribute_names[i];
+ val = attribute_values[i];
+
+ if (strcmp (name, "id") == 0)
+ {
+ if (*val)
+ pd->info->id = decode_text_from_utf8 (val);
+ }
+ else if (strcmp (name, "class") == 0)
+ {
+ if (*val)
+ pd->info->res_class = decode_text_from_utf8 (val);
+ }
+ else if (strcmp (name, "name") == 0)
+ {
+ if (*val)
+ pd->info->res_name = decode_text_from_utf8 (val);
+ }
+ else if (strcmp (name, "title") == 0)
+ {
+ if (*val)
+ pd->info->title = g_strdup (val);
+ }
+ else if (strcmp (name, "role") == 0)
+ {
+ if (*val)
+ pd->info->role = decode_text_from_utf8 (val);
+ }
+ else if (strcmp (name, "type") == 0)
+ {
+ if (*val)
+ pd->info->type = window_type_from_string (val);
+ }
+ else if (strcmp (name, "stacking") == 0)
+ {
+ if (*val)
+ {
+ pd->info->stack_position = atoi (val);
+ pd->info->stack_position_set = TRUE;
+ }
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ _("Unknown attribute %s on <%s> element"),
+ name, "window");
+ session_info_free (pd->info);
+ pd->info = NULL;
+ return;
+ }
+
+ ++i;
+ }
+ }
+ else if (strcmp (element_name, "workspace") == 0)
+ {
+ int i;
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ const char *name;
+
+ name = attribute_names[i];
+
+ if (strcmp (name, "index") == 0)
+ {
+ pd->info->workspace_indices =
+ g_slist_prepend (pd->info->workspace_indices,
+ GINT_TO_POINTER (atoi (attribute_values[i])));
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ _("Unknown attribute %s on <%s> element"),
+ name, "window");
+ session_info_free (pd->info);
+ pd->info = NULL;
+ return;
+ }
+
+ ++i;
+ }
+ }
+ else if (strcmp (element_name, "sticky") == 0)
+ {
+ pd->info->on_all_workspaces = TRUE;
+ pd->info->on_all_workspaces_set = TRUE;
+ }
+ else if (strcmp (element_name, "minimized") == 0)
+ {
+ pd->info->minimized = TRUE;
+ pd->info->minimized_set = TRUE;
+ }
+ else if (strcmp (element_name, "maximized") == 0)
+ {
+ int i;
+
+ i = 0;
+ pd->info->maximized = TRUE;
+ pd->info->maximized_set = TRUE;
+ while (attribute_names[i])
+ {
+ const char *name;
+ const char *val;
+
+ name = attribute_names[i];
+ val = attribute_values[i];
+
+ if (strcmp (name, "saved_x") == 0)
+ {
+ if (*val)
+ {
+ pd->info->saved_rect.x = atoi (val);
+ pd->info->saved_rect_set = TRUE;
+ }
+ }
+ else if (strcmp (name, "saved_y") == 0)
+ {
+ if (*val)
+ {
+ pd->info->saved_rect.y = atoi (val);
+ pd->info->saved_rect_set = TRUE;
+ }
+ }
+ else if (strcmp (name, "saved_width") == 0)
+ {
+ if (*val)
+ {
+ pd->info->saved_rect.width = atoi (val);
+ pd->info->saved_rect_set = TRUE;
+ }
+ }
+ else if (strcmp (name, "saved_height") == 0)
+ {
+ if (*val)
+ {
+ pd->info->saved_rect.height = atoi (val);
+ pd->info->saved_rect_set = TRUE;
+ }
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ _("Unknown attribute %s on <%s> element"),
+ name, "maximized");
+ return;
+ }
+
+ ++i;
+ }
+
+ if (pd->info->saved_rect_set)
+ meta_topic (META_DEBUG_SM, "Saved unmaximized size %d,%d %dx%d \n",
+ pd->info->saved_rect.x,
+ pd->info->saved_rect.y,
+ pd->info->saved_rect.width,
+ pd->info->saved_rect.height);
+ }
+ else if (strcmp (element_name, "geometry") == 0)
+ {
+ int i;
+
+ pd->info->geometry_set = TRUE;
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ const char *name;
+ const char *val;
+
+ name = attribute_names[i];
+ val = attribute_values[i];
+
+ if (strcmp (name, "x") == 0)
+ {
+ if (*val)
+ pd->info->rect.x = atoi (val);
+ }
+ else if (strcmp (name, "y") == 0)
+ {
+ if (*val)
+ pd->info->rect.y = atoi (val);
+ }
+ else if (strcmp (name, "width") == 0)
+ {
+ if (*val)
+ pd->info->rect.width = atoi (val);
+ }
+ else if (strcmp (name, "height") == 0)
+ {
+ if (*val)
+ pd->info->rect.height = atoi (val);
+ }
+ else if (strcmp (name, "gravity") == 0)
+ {
+ if (*val)
+ pd->info->gravity = window_gravity_from_string (val);
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ _("Unknown attribute %s on <%s> element"),
+ name, "geometry");
+ return;
+ }
+
+ ++i;
+ }
+
+ meta_topic (META_DEBUG_SM, "Loaded geometry %d,%d %dx%d gravity %s\n",
+ pd->info->rect.x,
+ pd->info->rect.y,
+ pd->info->rect.width,
+ pd->info->rect.height,
+ meta_gravity_to_string (pd->info->gravity));
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ _("Unknown element %s"),
+ element_name);
+ return;
+ }
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseData *pd;
+
+ pd = user_data;
+
+ if (strcmp (element_name, "window") == 0)
+ {
+ g_assert (pd->info);
+
+ window_info_list = g_slist_prepend (window_info_list,
+ pd->info);
+
+ meta_topic (META_DEBUG_SM, "Loaded window info from session with class: %s name: %s role: %s\n",
+ pd->info->res_class ? pd->info->res_class : "(none)",
+ pd->info->res_name ? pd->info->res_name : "(none)",
+ pd->info->role ? pd->info->role : "(none)");
+
+ pd->info = NULL;
+ }
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ /* Right now we don't have any elements where we care about their
+ * content
+ */
+}
+
+static gboolean
+both_null_or_matching (const char *a,
+ const char *b)
+{
+ if (a == NULL && b == NULL)
+ return TRUE;
+ else if (a && b && strcmp (a, b) == 0)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static GSList*
+get_possible_matches (MetaWindow *window)
+{
+ /* Get all windows with this client ID */
+ GSList *retval;
+ GSList *tmp;
+ gboolean ignore_client_id;
+
+ retval = NULL;
+
+ ignore_client_id = g_getenv ("MARCO_DEBUG_SM") != NULL;
+
+ tmp = window_info_list;
+ while (tmp != NULL)
+ {
+ MetaWindowSessionInfo *info;
+
+ info = tmp->data;
+
+ if ((ignore_client_id ||
+ both_null_or_matching (info->id, window->sm_client_id)) &&
+ both_null_or_matching (info->res_class, window->res_class) &&
+ both_null_or_matching (info->res_name, window->res_name) &&
+ both_null_or_matching (info->role, window->role))
+ {
+ meta_topic (META_DEBUG_SM, "Window %s may match saved window with class: %s name: %s role: %s\n",
+ window->desc,
+ info->res_class ? info->res_class : "(none)",
+ info->res_name ? info->res_name : "(none)",
+ info->role ? info->role : "(none)");
+
+ retval = g_slist_prepend (retval, info);
+ }
+ else
+ {
+ if (meta_is_verbose ())
+ {
+ if (!both_null_or_matching (info->id, window->sm_client_id))
+ meta_topic (META_DEBUG_SM, "Window %s has SM client ID %s, saved state has %s, no match\n",
+ window->desc,
+ window->sm_client_id ? window->sm_client_id : "(none)",
+ info->id ? info->id : "(none)");
+ else if (!both_null_or_matching (info->res_class, window->res_class))
+ meta_topic (META_DEBUG_SM, "Window %s has class %s doesn't match saved class %s, no match\n",
+ window->desc,
+ window->res_class ? window->res_class : "(none)",
+ info->res_class ? info->res_class : "(none)");
+
+ else if (!both_null_or_matching (info->res_name, window->res_name))
+ meta_topic (META_DEBUG_SM, "Window %s has name %s doesn't match saved name %s, no match\n",
+ window->desc,
+ window->res_name ? window->res_name : "(none)",
+ info->res_name ? info->res_name : "(none)");
+ else if (!both_null_or_matching (info->role, window->role))
+ meta_topic (META_DEBUG_SM, "Window %s has role %s doesn't match saved role %s, no match\n",
+ window->desc,
+ window->role ? window->role : "(none)",
+ info->role ? info->role : "(none)");
+ else
+ meta_topic (META_DEBUG_SM, "???? should not happen - window %s doesn't match saved state %s for no good reason\n",
+ window->desc, info->id);
+ }
+ }
+
+ tmp = tmp->next;
+ }
+
+ return retval;
+}
+
+static const MetaWindowSessionInfo*
+find_best_match (GSList *infos,
+ MetaWindow *window)
+{
+ GSList *tmp;
+ const MetaWindowSessionInfo *matching_title;
+ const MetaWindowSessionInfo *matching_type;
+
+ matching_title = NULL;
+ matching_type = NULL;
+
+ tmp = infos;
+ while (tmp != NULL)
+ {
+ MetaWindowSessionInfo *info;
+
+ info = tmp->data;
+
+ if (matching_title == NULL &&
+ both_null_or_matching (info->title, window->title))
+ matching_title = info;
+
+ if (matching_type == NULL &&
+ info->type == window->type)
+ matching_type = info;
+
+ tmp = tmp->next;
+ }
+
+ /* Prefer same title, then same type of window, then
+ * just pick something. Eventually we could enhance this
+ * to e.g. break ties by geometry hint similarity,
+ * or other window features.
+ */
+
+ if (matching_title)
+ return matching_title;
+ else if (matching_type)
+ return matching_type;
+ else
+ return infos->data;
+}
+
+const MetaWindowSessionInfo*
+meta_window_lookup_saved_state (MetaWindow *window)
+{
+ GSList *possibles;
+ const MetaWindowSessionInfo *info;
+
+ /* Window is not session managed.
+ * I haven't yet figured out how to deal with these
+ * in a way that doesn't cause broken side effects in
+ * situations other than on session restore.
+ */
+ if (window->sm_client_id == NULL)
+ {
+ meta_topic (META_DEBUG_SM,
+ "Window %s is not session managed, not checking for saved state\n",
+ window->desc);
+ return NULL;
+ }
+
+ possibles = get_possible_matches (window);
+
+ if (possibles == NULL)
+ {
+ meta_topic (META_DEBUG_SM, "Window %s has no possible matches in the list of saved window states\n",
+ window->desc);
+ return NULL;
+ }
+
+ info = find_best_match (possibles, window);
+
+ g_slist_free (possibles);
+
+ return info;
+}
+
+void
+meta_window_release_saved_state (const MetaWindowSessionInfo *info)
+{
+ /* We don't want to use the same saved state again for another
+ * window.
+ */
+ window_info_list = g_slist_remove (window_info_list, info);
+
+ session_info_free ((MetaWindowSessionInfo*) info);
+}
+
+static void
+session_info_free (MetaWindowSessionInfo *info)
+{
+ g_free (info->id);
+ g_free (info->res_class);
+ g_free (info->res_name);
+ g_free (info->title);
+ g_free (info->role);
+
+ g_slist_free (info->workspace_indices);
+
+ g_free (info);
+}
+
+static MetaWindowSessionInfo*
+session_info_new (void)
+{
+ MetaWindowSessionInfo *info;
+
+ info = g_new0 (MetaWindowSessionInfo, 1);
+
+ info->type = META_WINDOW_NORMAL;
+ info->gravity = NorthWestGravity;
+
+ return info;
+}
+
+static char* full_save_path = NULL;
+
+static void
+regenerate_save_file (void)
+{
+ g_free (full_save_path);
+
+ if (client_id)
+ full_save_path = g_strconcat (g_get_user_config_dir (),
+ G_DIR_SEPARATOR_S "marco"
+ G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S,
+ client_id,
+ ".ms",
+ NULL);
+ else
+ full_save_path = NULL;
+}
+
+static const char*
+full_save_file (void)
+{
+ return full_save_path;
+}
+
+static int
+windows_cmp_by_title (MetaWindow *a,
+ MetaWindow *b)
+{
+ return g_utf8_collate (a->title, b->title);
+}
+
+static void
+finish_interact (gboolean shutdown)
+{
+ if (current_state == STATE_DONE_WITH_INTERACT) /* paranoia */
+ {
+ SmcInteractDone (session_connection, False /* don't cancel logout */);
+
+ save_yourself_possibly_done (shutdown, TRUE);
+ }
+}
+
+static void
+dialog_closed (GPid pid, int status, gpointer user_data)
+{
+ gboolean shutdown = GPOINTER_TO_INT (user_data);
+
+ if (WIFEXITED (status) && WEXITSTATUS (status) == 0) /* pressed "OK" */
+ {
+ finish_interact (shutdown);
+ }
+}
+
+static void
+warn_about_lame_clients_and_finish_interact (gboolean shutdown)
+{
+ GSList *lame = NULL;
+ GSList *windows;
+ GSList *lame_details = NULL;
+ GSList *tmp;
+ GSList *columns = NULL;
+ GPid pid;
+
+ windows = meta_display_list_windows (meta_get_display ());
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+
+ window = tmp->data;
+
+ /* only complain about normal windows, the others
+ * are kind of dumb to worry about
+ */
+ if (window->sm_client_id == NULL &&
+ window->type == META_WINDOW_NORMAL)
+ lame = g_slist_prepend (lame, window);
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (windows);
+
+ if (lame == NULL)
+ {
+ /* No lame apps. */
+ finish_interact (shutdown);
+ return;
+ }
+
+ columns = g_slist_prepend (columns, "Window");
+ columns = g_slist_prepend (columns, "Class");
+
+ lame = g_slist_sort (lame, (GCompareFunc) windows_cmp_by_title);
+
+ tmp = lame;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ lame_details = g_slist_prepend (lame_details,
+ w->res_class ? w->res_class : "");
+ lame_details = g_slist_prepend (lame_details,
+ w->title);
+
+ tmp = tmp->next;
+ }
+ g_slist_free (lame);
+
+ pid = meta_show_dialog("--list",
+ _("These windows do not support &quot;save current setup&quot; "
+ "and will have to be restarted manually next time "
+ "you log in."),
+ "240",
+ meta_screen_get_screen_number (meta_get_display()->active_screen),
+ NULL, NULL,
+ None,
+ columns,
+ lame_details);
+
+ g_slist_free (lame_details);
+
+ g_child_watch_add (pid, dialog_closed, GINT_TO_POINTER (shutdown));
+}
+
+#endif /* HAVE_SM */
diff --git a/src/core/session.h b/src/core/session.h
new file mode 100644
index 00000000..62a9370d
--- /dev/null
+++ b/src/core/session.h
@@ -0,0 +1,91 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file session.h Session management
+ *
+ * Maps windows to information about their placing and state on startup.
+ * This is window matching, which we have a policy of leaving in general
+ * to programs such as Devil's Pie, but the session manager specification
+ * requires us to do it here.
+ */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_SESSION_H
+#define META_SESSION_H
+
+#include "window-private.h"
+
+typedef struct _MetaWindowSessionInfo MetaWindowSessionInfo;
+
+struct _MetaWindowSessionInfo
+{
+ /* Fields we use to match against */
+
+ char *id;
+ char *res_class;
+ char *res_name;
+ char *title;
+ char *role;
+ MetaWindowType type;
+
+ /* Information we restore */
+
+ GSList *workspace_indices;
+
+ int stack_position;
+
+ /* width/height should be multiplied by resize inc and
+ * added to base size; position should be interpreted in
+ * light of gravity. This preserves semantics of the
+ * window size/pos, even if fonts/themes change, etc.
+ */
+ int gravity;
+ MetaRectangle rect;
+ MetaRectangle saved_rect;
+ guint on_all_workspaces : 1;
+ guint minimized : 1;
+ guint maximized : 1;
+
+ guint stack_position_set : 1;
+ guint geometry_set : 1;
+ guint on_all_workspaces_set : 1;
+ guint minimized_set : 1;
+ guint maximized_set : 1;
+ guint saved_rect_set : 1;
+};
+
+/* If lookup_saved_state returns something, it should be used,
+ * and then released when you're done with it.
+ */
+const MetaWindowSessionInfo* meta_window_lookup_saved_state (MetaWindow *window);
+void meta_window_release_saved_state (const MetaWindowSessionInfo *info);
+
+void meta_session_init (const char *client_id,
+ const char *save_file);
+
+
+void meta_session_shutdown (void);
+
+#endif
+
+
+
+
diff --git a/src/core/stack.c b/src/core/stack.c
new file mode 100644
index 00000000..2e108d20
--- /dev/null
+++ b/src/core/stack.c
@@ -0,0 +1,1661 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file stack.c Which windows cover which other windows
+ */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2002, 2003 Red Hat, Inc.
+ * Copyright (C) 2004 Rob Adams
+ * Copyright (C) 2004, 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "stack.h"
+#include "window-private.h"
+#include "errors.h"
+#include "frame-private.h"
+#include "group.h"
+#include "prefs.h"
+#include "workspace.h"
+
+#include <X11/Xatom.h>
+
+#define WINDOW_HAS_TRANSIENT_TYPE(w) \
+ (w->type == META_WINDOW_DIALOG || \
+ w->type == META_WINDOW_MODAL_DIALOG || \
+ w->type == META_WINDOW_TOOLBAR || \
+ w->type == META_WINDOW_MENU || \
+ w->type == META_WINDOW_UTILITY)
+
+#define WINDOW_TRANSIENT_FOR_WHOLE_GROUP(w) \
+ ((w->xtransient_for == None || \
+ w->transient_parent_is_root_window) && \
+ WINDOW_HAS_TRANSIENT_TYPE (w))
+
+#define WINDOW_IN_STACK(w) (w->stack_position >= 0)
+
+static void stack_sync_to_server (MetaStack *stack);
+static void meta_window_set_stack_position_no_sync (MetaWindow *window,
+ int position);
+static void stack_do_window_deletions (MetaStack *stack);
+static void stack_do_window_additions (MetaStack *stack);
+static void stack_do_relayer (MetaStack *stack);
+static void stack_do_constrain (MetaStack *stack);
+static void stack_do_resort (MetaStack *stack);
+
+static void stack_ensure_sorted (MetaStack *stack);
+
+MetaStack*
+meta_stack_new (MetaScreen *screen)
+{
+ MetaStack *stack;
+
+ stack = g_new (MetaStack, 1);
+
+ stack->screen = screen;
+ stack->windows = g_array_new (FALSE, FALSE, sizeof (Window));
+
+ stack->sorted = NULL;
+ stack->added = NULL;
+ stack->removed = NULL;
+
+ stack->freeze_count = 0;
+ stack->last_root_children_stacked = NULL;
+
+ stack->n_positions = 0;
+
+ stack->need_resort = FALSE;
+ stack->need_relayer = FALSE;
+ stack->need_constrain = FALSE;
+
+ return stack;
+}
+
+void
+meta_stack_free (MetaStack *stack)
+{
+ g_array_free (stack->windows, TRUE);
+
+ g_list_free (stack->sorted);
+ g_list_free (stack->added);
+ g_list_free (stack->removed);
+
+ if (stack->last_root_children_stacked)
+ g_array_free (stack->last_root_children_stacked, TRUE);
+
+ g_free (stack);
+}
+
+void
+meta_stack_add (MetaStack *stack,
+ MetaWindow *window)
+{
+ meta_topic (META_DEBUG_STACK, "Adding window %s to the stack\n", window->desc);
+
+ if (window->stack_position >= 0)
+ meta_bug ("Window %s had stack position already\n", window->desc);
+
+ stack->added = g_list_prepend (stack->added, window);
+
+ window->stack_position = stack->n_positions;
+ stack->n_positions += 1;
+ meta_topic (META_DEBUG_STACK,
+ "Window %s has stack_position initialized to %d\n",
+ window->desc, window->stack_position);
+
+ stack_sync_to_server (stack);
+}
+
+void
+meta_stack_remove (MetaStack *stack,
+ MetaWindow *window)
+{
+ meta_topic (META_DEBUG_STACK, "Removing window %s from the stack\n", window->desc);
+
+ if (window->stack_position < 0)
+ meta_bug ("Window %s removed from stack but had no stack position\n",
+ window->desc);
+
+ /* Set window to top position, so removing it will not leave gaps
+ * in the set of positions
+ */
+ meta_window_set_stack_position_no_sync (window,
+ stack->n_positions - 1);
+ window->stack_position = -1;
+ stack->n_positions -= 1;
+
+ /* We don't know if it's been moved from "added" to "stack" yet */
+ stack->added = g_list_remove (stack->added, window);
+ stack->sorted = g_list_remove (stack->sorted, window);
+
+ /* Remember the window ID to remove it from the stack array.
+ * The macro is safe to use: Window is guaranteed to be 32 bits, and
+ * GUINT_TO_POINTER says it only works on 32 bits.
+ */
+ stack->removed = g_list_prepend (stack->removed,
+ GUINT_TO_POINTER (window->xwindow));
+ if (window->frame)
+ stack->removed = g_list_prepend (stack->removed,
+ GUINT_TO_POINTER (window->frame->xwindow));
+
+ stack_sync_to_server (stack);
+}
+
+void
+meta_stack_update_layer (MetaStack *stack,
+ MetaWindow *window)
+{
+ stack->need_relayer = TRUE;
+
+ stack_sync_to_server (stack);
+}
+
+void
+meta_stack_update_transient (MetaStack *stack,
+ MetaWindow *window)
+{
+ stack->need_constrain = TRUE;
+
+ stack_sync_to_server (stack);
+}
+
+/* raise/lower within a layer */
+void
+meta_stack_raise (MetaStack *stack,
+ MetaWindow *window)
+{
+ meta_window_set_stack_position_no_sync (window,
+ stack->n_positions - 1);
+
+ stack_sync_to_server (stack);
+}
+
+void
+meta_stack_lower (MetaStack *stack,
+ MetaWindow *window)
+{
+ meta_window_set_stack_position_no_sync (window, 0);
+
+ stack_sync_to_server (stack);
+}
+
+void
+meta_stack_freeze (MetaStack *stack)
+{
+ stack->freeze_count += 1;
+}
+
+void
+meta_stack_thaw (MetaStack *stack)
+{
+ g_return_if_fail (stack->freeze_count > 0);
+
+ stack->freeze_count -= 1;
+ stack_sync_to_server (stack);
+}
+
+static gboolean
+is_focused_foreach (MetaWindow *window,
+ void *data)
+{
+ if (window == window->display->expected_focus_window)
+ {
+ *((gboolean*) data) = TRUE;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+windows_on_different_xinerama (MetaWindow *a,
+ MetaWindow *b)
+{
+ if (a->screen != b->screen)
+ return TRUE;
+
+ return meta_screen_get_xinerama_for_window (a->screen, a) !=
+ meta_screen_get_xinerama_for_window (b->screen, b);
+}
+
+/* Get layer ignoring any transient or group relationships */
+static MetaStackLayer
+get_standalone_layer (MetaWindow *window)
+{
+ MetaStackLayer layer;
+ gboolean focused_transient = FALSE;
+
+ switch (window->type)
+ {
+ case META_WINDOW_DESKTOP:
+ layer = META_LAYER_DESKTOP;
+ break;
+
+ case META_WINDOW_DOCK:
+ /* still experimenting here */
+ if (window->wm_state_below)
+ layer = META_LAYER_BOTTOM;
+ else
+ layer = META_LAYER_DOCK;
+ break;
+
+ default:
+ meta_window_foreach_transient (window,
+ is_focused_foreach,
+ &focused_transient);
+
+ if (window->wm_state_below)
+ layer = META_LAYER_BOTTOM;
+ else if (window->fullscreen &&
+ (focused_transient ||
+ window == window->display->expected_focus_window ||
+ window->display->expected_focus_window == NULL ||
+ (window->display->expected_focus_window != NULL &&
+ windows_on_different_xinerama (window,
+ window->display->expected_focus_window))))
+ layer = META_LAYER_FULLSCREEN;
+ else if (window->wm_state_above)
+ layer = META_LAYER_TOP;
+ else
+ layer = META_LAYER_NORMAL;
+ break;
+ }
+
+ return layer;
+}
+
+/* Note that this function can never use window->layer only
+ * get_standalone_layer, or we'd have issues.
+ */
+static MetaStackLayer
+get_maximum_layer_in_group (MetaWindow *window)
+{
+ GSList *members;
+ MetaGroup *group;
+ GSList *tmp;
+ MetaStackLayer max;
+ MetaStackLayer layer;
+
+ max = META_LAYER_DESKTOP;
+
+ group = meta_window_get_group (window);
+
+ if (group != NULL)
+ members = meta_group_list_windows (group);
+ else
+ members = NULL;
+
+ tmp = members;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ layer = get_standalone_layer (w);
+ if (layer > max)
+ max = layer;
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (members);
+
+ return max;
+}
+
+static void
+compute_layer (MetaWindow *window)
+{
+ window->layer = get_standalone_layer (window);
+
+ /* We can only do promotion-due-to-group for dialogs and other
+ * transients, or weird stuff happens like the desktop window and
+ * caja windows getting in the same layer, or all mate-terminal
+ * windows getting in fullscreen layer if any terminal is
+ * fullscreen.
+ */
+ if (WINDOW_HAS_TRANSIENT_TYPE(window) &&
+ (window->xtransient_for == None ||
+ window->transient_parent_is_root_window))
+ {
+ /* We only do the group thing if the dialog is NOT transient for
+ * a particular window. Imagine a group with a normal window, a dock,
+ * and a dialog transient for the normal window; you don't want the dialog
+ * above the dock if it wouldn't normally be.
+ */
+
+ MetaStackLayer group_max;
+
+ group_max = get_maximum_layer_in_group (window);
+
+ if (group_max > window->layer)
+ {
+ meta_topic (META_DEBUG_STACK,
+ "Promoting window %s from layer %u to %u due to group membership\n",
+ window->desc, window->layer, group_max);
+ window->layer = group_max;
+ }
+ }
+
+ meta_topic (META_DEBUG_STACK, "Window %s on layer %u type = %u has_focus = %d\n",
+ window->desc, window->layer,
+ window->type, window->has_focus);
+}
+
+/* Front of the layer list is the topmost window,
+ * so the lower stack position is later in the list
+ */
+static int
+compare_window_position (void *a,
+ void *b)
+{
+ MetaWindow *window_a = a;
+ MetaWindow *window_b = b;
+
+ /* Go by layer, then stack_position */
+ if (window_a->layer < window_b->layer)
+ return 1; /* move window_a later in list */
+ else if (window_a->layer > window_b->layer)
+ return -1;
+ else if (window_a->stack_position < window_b->stack_position)
+ return 1; /* move window_a later in list */
+ else if (window_a->stack_position > window_b->stack_position)
+ return -1;
+ else
+ return 0; /* not reached */
+}
+
+/*
+ * Stacking constraints
+ *
+ * Assume constraints of the form "AB" meaning "window A must be
+ * below window B"
+ *
+ * If we have windows stacked from bottom to top
+ * "ABC" then raise A we get "BCA". Say C is
+ * transient for B is transient for A. So
+ * we have constraints AB and BC.
+ *
+ * After raising A, we need to reapply the constraints.
+ * If we do this by raising one window at a time -
+ *
+ * start: BCA
+ * apply AB: CAB
+ * apply BC: ABC
+ *
+ * but apply constraints in the wrong order and it breaks:
+ *
+ * start: BCA
+ * apply BC: BCA
+ * apply AB: CAB
+ *
+ * We make a directed graph of the constraints by linking
+ * from "above windows" to "below windows as follows:
+ *
+ * AB -> BC -> CD
+ * \
+ * CE
+ *
+ * If we then walk that graph and apply the constraints in the order
+ * that they appear, we will apply them correctly. Note that the
+ * graph MAY have cycles, so we have to guard against that.
+ *
+ */
+
+typedef struct Constraint Constraint;
+
+struct Constraint
+{
+ MetaWindow *above;
+ MetaWindow *below;
+
+ /* used to keep the constraint in the
+ * list of constraints for window "below"
+ */
+ Constraint *next;
+
+ /* used to create the graph. */
+ GSList *next_nodes;
+
+ /* constraint has been applied, used
+ * to detect cycles.
+ */
+ unsigned int applied : 1;
+
+ /* constraint has a previous node in the graph,
+ * used to find places to start in the graph.
+ * (I think this also has the side effect
+ * of preventing cycles, since cycles will
+ * have no starting point - so maybe
+ * the "applied" flag isn't needed.)
+ */
+ unsigned int has_prev : 1;
+};
+
+/* We index the array of constraints by window
+ * stack positions, just because the stack
+ * positions are a convenient index.
+ */
+static void
+add_constraint (Constraint **constraints,
+ MetaWindow *above,
+ MetaWindow *below)
+{
+ Constraint *c;
+
+ g_assert (above->screen == below->screen);
+
+ /* check if constraint is a duplicate */
+ c = constraints[below->stack_position];
+ while (c != NULL)
+ {
+ if (c->above == above)
+ return;
+ c = c->next;
+ }
+
+ /* if not, add the constraint */
+ c = g_new (Constraint, 1);
+ c->above = above;
+ c->below = below;
+ c->next = constraints[below->stack_position];
+ c->next_nodes = NULL;
+ c->applied = FALSE;
+ c->has_prev = FALSE;
+
+ constraints[below->stack_position] = c;
+}
+
+static void
+create_constraints (Constraint **constraints,
+ GList *windows)
+{
+ GList *tmp;
+
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ if (!WINDOW_IN_STACK (w))
+ {
+ meta_topic (META_DEBUG_STACK, "Window %s not in the stack, not constraining it\n",
+ w->desc);
+ tmp = tmp->next;
+ continue;
+ }
+
+ if (WINDOW_TRANSIENT_FOR_WHOLE_GROUP (w))
+ {
+ GSList *group_windows;
+ GSList *tmp2;
+ MetaGroup *group;
+
+ group = meta_window_get_group (w);
+
+ if (group != NULL)
+ group_windows = meta_group_list_windows (group);
+ else
+ group_windows = NULL;
+
+ tmp2 = group_windows;
+
+ while (tmp2 != NULL)
+ {
+ MetaWindow *group_window = tmp2->data;
+
+ if (!WINDOW_IN_STACK (group_window) ||
+ w->screen != group_window->screen)
+ {
+ tmp2 = tmp2->next;
+ continue;
+ }
+
+#if 0
+ /* old way of doing it */
+ if (!(meta_window_is_ancestor_of_transient (w, group_window)) &&
+ !WINDOW_TRANSIENT_FOR_WHOLE_GROUP (group_window)) /* note */;/*note*/
+#else
+ /* better way I think, so transient-for-group are constrained
+ * only above non-transient-type windows in their group
+ */
+ if (!WINDOW_HAS_TRANSIENT_TYPE (group_window))
+#endif
+ {
+ meta_topic (META_DEBUG_STACK, "Constraining %s above %s as it's transient for its group\n",
+ w->desc, group_window->desc);
+ add_constraint (constraints, w, group_window);
+ }
+
+ tmp2 = tmp2->next;
+ }
+
+ g_slist_free (group_windows);
+ }
+ else if (w->xtransient_for != None &&
+ !w->transient_parent_is_root_window)
+ {
+ MetaWindow *parent;
+
+ parent =
+ meta_display_lookup_x_window (w->display, w->xtransient_for);
+
+ if (parent && WINDOW_IN_STACK (parent) &&
+ parent->screen == w->screen)
+ {
+ meta_topic (META_DEBUG_STACK, "Constraining %s above %s due to transiency\n",
+ w->desc, parent->desc);
+ add_constraint (constraints, w, parent);
+ }
+ }
+
+ tmp = tmp->next;
+ }
+}
+
+static void
+graph_constraints (Constraint **constraints,
+ int n_constraints)
+{
+ int i;
+
+ i = 0;
+ while (i < n_constraints)
+ {
+ Constraint *c;
+
+ /* If we have "A below B" and "B below C" then AB -> BC so we
+ * add BC to next_nodes in AB.
+ */
+
+ c = constraints[i];
+ while (c != NULL)
+ {
+ Constraint *n;
+
+ g_assert (c->below->stack_position == i);
+
+ /* Constraints where ->above is below are our
+ * next_nodes and we are their previous
+ */
+ n = constraints[c->above->stack_position];
+ while (n != NULL)
+ {
+ c->next_nodes = g_slist_prepend (c->next_nodes,
+ n);
+ /* c is a previous node of n */
+ n->has_prev = TRUE;
+
+ n = n->next;
+ }
+
+ c = c->next;
+ }
+
+ ++i;
+ }
+}
+
+static void
+free_constraints (Constraint **constraints,
+ int n_constraints)
+{
+ int i;
+
+ i = 0;
+ while (i < n_constraints)
+ {
+ Constraint *c;
+
+ c = constraints[i];
+ while (c != NULL)
+ {
+ Constraint *next = c->next;
+
+ g_slist_free (c->next_nodes);
+
+ g_free (c);
+
+ c = next;
+ }
+
+ ++i;
+ }
+}
+
+static void
+ensure_above (MetaWindow *above,
+ MetaWindow *below)
+{
+ if (WINDOW_HAS_TRANSIENT_TYPE(above) &&
+ above->layer < below->layer)
+ {
+ meta_topic (META_DEBUG_STACK,
+ "Promoting window %s from layer %u to %u due to contraint\n",
+ above->desc, above->layer, below->layer);
+ above->layer = below->layer;
+ }
+
+ if (above->stack_position < below->stack_position)
+ {
+ /* move above to below->stack_position bumping below down the stack */
+ meta_window_set_stack_position_no_sync (above, below->stack_position);
+ g_assert (below->stack_position + 1 == above->stack_position);
+ }
+ meta_topic (META_DEBUG_STACK, "%s above at %d > %s below at %d\n",
+ above->desc, above->stack_position,
+ below->desc, below->stack_position);
+}
+
+static void
+traverse_constraint (Constraint *c)
+{
+ GSList *tmp;
+
+ if (c->applied)
+ return;
+
+ ensure_above (c->above, c->below);
+ c->applied = TRUE;
+
+ tmp = c->next_nodes;
+ while (tmp != NULL)
+ {
+ traverse_constraint (tmp->data);
+
+ tmp = tmp->next;
+ }
+}
+
+static void
+apply_constraints (Constraint **constraints,
+ int n_constraints)
+{
+ GSList *heads;
+ GSList *tmp;
+ int i;
+
+ /* List all heads in an ordered constraint chain */
+ heads = NULL;
+ i = 0;
+ while (i < n_constraints)
+ {
+ Constraint *c;
+
+ c = constraints[i];
+ while (c != NULL)
+ {
+ if (!c->has_prev)
+ heads = g_slist_prepend (heads, c);
+
+ c = c->next;
+ }
+
+ ++i;
+ }
+
+ /* Now traverse the chain and apply constraints */
+ tmp = heads;
+ while (tmp != NULL)
+ {
+ Constraint *c = tmp->data;
+
+ traverse_constraint (c);
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (heads);
+}
+
+/**
+ * Go through "deleted" and take the matching windows
+ * out of "windows".
+ */
+static void
+stack_do_window_deletions (MetaStack *stack)
+{
+ /* Do removals before adds, with paranoid idea that we might re-add
+ * the same window IDs.
+ */
+ GList *tmp;
+ int i;
+
+ tmp = stack->removed;
+ while (tmp != NULL)
+ {
+ Window xwindow;
+ xwindow = GPOINTER_TO_UINT (tmp->data);
+
+ /* We go from the end figuring removals are more
+ * likely to be recent.
+ */
+ i = stack->windows->len;
+ while (i > 0)
+ {
+ --i;
+
+ /* there's no guarantee we'll actually find windows to
+ * remove, e.g. the same xwindow could have been
+ * added/removed before we ever synced, and we put
+ * both the window->xwindow and window->frame->xwindow
+ * in the removal list.
+ */
+ if (xwindow == g_array_index (stack->windows, Window, i))
+ {
+ g_array_remove_index (stack->windows, i);
+ goto next;
+ }
+ }
+
+ next:
+ tmp = tmp->next;
+ }
+
+ g_list_free (stack->removed);
+ stack->removed = NULL;
+}
+
+static void
+stack_do_window_additions (MetaStack *stack)
+{
+ GList *tmp;
+ gint i, n_added;
+
+ n_added = g_list_length (stack->added);
+ if (n_added > 0)
+ {
+ Window *end;
+ int old_size;
+
+ meta_topic (META_DEBUG_STACK,
+ "Adding %d windows to sorted list\n",
+ n_added);
+
+ old_size = stack->windows->len;
+ g_array_set_size (stack->windows, old_size + n_added);
+
+ end = &g_array_index (stack->windows, Window, old_size);
+
+ /* stack->added has the most recent additions at the
+ * front of the list, so we need to reverse it
+ */
+ stack->added = g_list_reverse (stack->added);
+
+ i = 0;
+ tmp = stack->added;
+ while (tmp != NULL)
+ {
+ MetaWindow *w;
+
+ w = tmp->data;
+
+ end[i] = w->xwindow;
+
+ /* add to the main list */
+ stack->sorted = g_list_prepend (stack->sorted, w);
+
+ ++i;
+ tmp = tmp->next;
+ }
+
+ stack->need_resort = TRUE; /* may not be needed as we add to top */
+ stack->need_constrain = TRUE;
+ stack->need_relayer = TRUE;
+ }
+
+ g_list_free (stack->added);
+ stack->added = NULL;
+}
+
+/**
+ * Update the layers that windows are in
+ */
+static void
+stack_do_relayer (MetaStack *stack)
+{
+ GList *tmp;
+
+ if (!stack->need_relayer)
+ return;
+
+ meta_topic (META_DEBUG_STACK,
+ "Recomputing layers\n");
+
+ tmp = stack->sorted;
+
+ while (tmp != NULL)
+ {
+ MetaWindow *w;
+ MetaStackLayer old_layer;
+
+ w = tmp->data;
+ old_layer = w->layer;
+
+ compute_layer (w);
+
+ if (w->layer != old_layer)
+ {
+ meta_topic (META_DEBUG_STACK,
+ "Window %s moved from layer %u to %u\n",
+ w->desc, old_layer, w->layer);
+
+ stack->need_resort = TRUE;
+ stack->need_constrain = TRUE;
+ /* don't need to constrain as constraining
+ * purely operates in terms of stack_position
+ * not layer
+ */
+ }
+
+ tmp = tmp->next;
+ }
+
+ stack->need_relayer = FALSE;
+}
+
+/**
+ * Update stack_position and layer to reflect transiency
+ * constraints
+ */
+static void
+stack_do_constrain (MetaStack *stack)
+{
+ Constraint **constraints;
+
+ /* It'd be nice if this were all faster, probably */
+
+ if (!stack->need_constrain)
+ return;
+
+ meta_topic (META_DEBUG_STACK,
+ "Reapplying constraints\n");
+
+ constraints = g_new0 (Constraint*,
+ stack->n_positions);
+
+ create_constraints (constraints, stack->sorted);
+
+ graph_constraints (constraints, stack->n_positions);
+
+ apply_constraints (constraints, stack->n_positions);
+
+ free_constraints (constraints, stack->n_positions);
+ g_free (constraints);
+
+ stack->need_constrain = FALSE;
+}
+
+/**
+ * Sort stack->sorted with layers having priority over stack_position.
+ */
+static void
+stack_do_resort (MetaStack *stack)
+{
+ if (!stack->need_resort)
+ return;
+
+ meta_topic (META_DEBUG_STACK,
+ "Sorting stack list\n");
+
+ stack->sorted = g_list_sort (stack->sorted,
+ (GCompareFunc) compare_window_position);
+
+ stack->need_resort = FALSE;
+}
+
+/**
+ * Puts the stack into canonical form.
+ *
+ * Honour the removed and added lists of the stack, and then recalculate
+ * all the layers (if the flag is set), re-run all the constraint calculations
+ * (if the flag is set), and finally re-sort the stack (if the flag is set,
+ * and if it wasn't already it might have become so during all the previous
+ * activity).
+ */
+static void
+stack_ensure_sorted (MetaStack *stack)
+{
+ stack_do_window_deletions (stack);
+ stack_do_window_additions (stack);
+ stack_do_relayer (stack);
+ stack_do_constrain (stack);
+ stack_do_resort (stack);
+}
+
+/**
+ * This function is used to avoid raising a window above popup
+ * menus and other such things.
+ *
+ * FIXME This is sort of an expensive function, should probably
+ * do something to avoid it. One approach would be to reverse
+ * the stacking algorithm to work by placing each window above
+ * the others, and start by lowering a window to the bottom
+ * (instead of the current way, which works by placing each
+ * window below another and starting with a raise)
+ */
+static void
+raise_window_relative_to_managed_windows (MetaScreen *screen,
+ Window xwindow)
+{
+
+ Window ignored1, ignored2;
+ Window *children;
+ unsigned int n_children;
+ int i;
+
+ /* Normally XQueryTree() means "must grab server" but here
+ * we don't, since we know we won't manage any new windows
+ * or restack any windows before using the XQueryTree results.
+ */
+
+ meta_error_trap_push_with_return (screen->display);
+
+ XQueryTree (screen->display->xdisplay,
+ screen->xroot,
+ &ignored1, &ignored2, &children, &n_children);
+
+ if (meta_error_trap_pop_with_return (screen->display, TRUE) != Success)
+ {
+ meta_topic (META_DEBUG_STACK,
+ "Error querying root children to raise window 0x%lx\n",
+ xwindow);
+ return;
+ }
+
+ /* Children are in order from bottom to top. We want to
+ * find the topmost managed child, then configure
+ * our window to be above it.
+ */
+ i = n_children - 1;
+ while (i >= 0)
+ {
+ if (children[i] == xwindow)
+ {
+ /* Do nothing. This means we're already the topmost managed
+ * window, but it DOES NOT mean we are already just above
+ * the topmost managed window. This is important because if
+ * an override redirect window is up, and we map a new
+ * managed window, the new window is probably above the old
+ * popup by default, and we want to push it below that
+ * popup. So keep looking for a sibling managed window
+ * to be moved below.
+ */
+ }
+ else if (meta_display_lookup_x_window (screen->display,
+ children[i]) != NULL)
+ {
+ XWindowChanges changes;
+
+ /* children[i] is the topmost managed child */
+ meta_topic (META_DEBUG_STACK,
+ "Moving 0x%lx above topmost managed child window 0x%lx\n",
+ xwindow, children[i]);
+
+ changes.sibling = children[i];
+ changes.stack_mode = Above;
+
+ meta_error_trap_push (screen->display);
+ XConfigureWindow (screen->display->xdisplay,
+ xwindow,
+ CWSibling | CWStackMode,
+ &changes);
+ meta_error_trap_pop (screen->display, FALSE);
+
+ break;
+ }
+
+ --i;
+ }
+
+ if (i < 0)
+ {
+ /* No sibling to use, just lower ourselves to the bottom
+ * to be sure we're below any override redirect windows.
+ */
+ meta_error_trap_push (screen->display);
+ XLowerWindow (screen->display->xdisplay,
+ xwindow);
+ meta_error_trap_pop (screen->display, FALSE);
+ }
+
+ if (children)
+ XFree (children);
+}
+
+/**
+ * Order the windows on the X server to be the same as in our structure.
+ * We do this using XRestackWindows if we don't know the previous order,
+ * or XConfigureWindow on a few particular windows if we do and can figure
+ * out the minimum set of changes. After that, we set __NET_CLIENT_LIST
+ * and __NET_CLIENT_LIST_STACKING.
+ */
+static void
+stack_sync_to_server (MetaStack *stack)
+{
+ GArray *stacked;
+ GArray *root_children_stacked;
+ GList *tmp;
+
+ /* Bail out if frozen */
+ if (stack->freeze_count > 0)
+ return;
+
+ meta_topic (META_DEBUG_STACK, "Syncing window stack to server\n");
+
+ stack_ensure_sorted (stack);
+
+ /* Create stacked xwindow arrays.
+ * Painfully, "stacked" is in bottom-to-top order for the
+ * _NET hints, and "root_children_stacked" is in top-to-bottom
+ * order for XRestackWindows()
+ */
+ stacked = g_array_new (FALSE, FALSE, sizeof (Window));
+ root_children_stacked = g_array_new (FALSE, FALSE, sizeof (Window));
+
+ meta_topic (META_DEBUG_STACK, "Top to bottom: ");
+ meta_push_no_msg_prefix ();
+
+ tmp = stack->sorted;
+ while (tmp != NULL)
+ {
+ MetaWindow *w;
+
+ w = tmp->data;
+
+ /* remember, stacked is in reverse order (bottom to top) */
+ g_array_prepend_val (stacked, w->xwindow);
+
+ /* build XRestackWindows() array from top to bottom */
+ if (w->frame)
+ g_array_append_val (root_children_stacked, w->frame->xwindow);
+ else
+ g_array_append_val (root_children_stacked, w->xwindow);
+
+ meta_topic (META_DEBUG_STACK, "%u:%d - %s ", w->layer, w->stack_position, w->desc);
+
+ tmp = tmp->next;
+ }
+
+ meta_topic (META_DEBUG_STACK, "\n");
+ meta_pop_no_msg_prefix ();
+
+ /* All windows should be in some stacking order */
+ if (stacked->len != stack->windows->len)
+ meta_bug ("%u windows stacked, %u windows exist in stack\n",
+ stacked->len, stack->windows->len);
+
+ /* Sync to server */
+
+ meta_topic (META_DEBUG_STACK, "Restacking %u windows\n",
+ root_children_stacked->len);
+
+ meta_error_trap_push (stack->screen->display);
+
+ if (stack->last_root_children_stacked == NULL)
+ {
+ /* Just impose our stack, we don't know the previous state.
+ * This involves a ton of circulate requests and may flicker.
+ */
+ meta_topic (META_DEBUG_STACK, "Don't know last stack state, restacking everything\n");
+
+ if (root_children_stacked->len > 0)
+ XRestackWindows (stack->screen->display->xdisplay,
+ (Window *) root_children_stacked->data,
+ root_children_stacked->len);
+ }
+ else if (root_children_stacked->len > 0)
+ {
+ /* Try to do minimal window moves to get the stack in order */
+ /* A point of note: these arrays include frames not client windows,
+ * so if a client window has changed frame since last_root_children_stacked
+ * was saved, then we may have inefficiency, but I don't think things
+ * break...
+ */
+ const Window *old_stack = (Window *) stack->last_root_children_stacked->data;
+ const Window *new_stack = (Window *) root_children_stacked->data;
+ const int old_len = stack->last_root_children_stacked->len;
+ const int new_len = root_children_stacked->len;
+ const Window *oldp = old_stack;
+ const Window *newp = new_stack;
+ const Window *old_end = old_stack + old_len;
+ const Window *new_end = new_stack + new_len;
+ Window last_window = None;
+
+ while (oldp != old_end &&
+ newp != new_end)
+ {
+ if (*oldp == *newp)
+ {
+ /* Stacks are the same here, move on */
+ ++oldp;
+ last_window = *newp;
+ ++newp;
+ }
+ else if (meta_display_lookup_x_window (stack->screen->display,
+ *oldp) == NULL)
+ {
+ /* *oldp is no longer known to us (probably destroyed),
+ * so we can just skip it
+ */
+ ++oldp;
+ }
+ else
+ {
+ /* Move *newp below last_window */
+ if (last_window == None)
+ {
+ meta_topic (META_DEBUG_STACK, "Using window 0x%lx as topmost (but leaving it in-place)\n", *newp);
+
+ raise_window_relative_to_managed_windows (stack->screen,
+ *newp);
+ }
+ else
+ {
+ /* This means that if last_window is dead, but not
+ * *newp, then we fail to restack *newp; but on
+ * unmanaging last_window, we'll fix it up.
+ */
+
+ XWindowChanges changes;
+
+ changes.sibling = last_window;
+ changes.stack_mode = Below;
+
+ meta_topic (META_DEBUG_STACK, "Placing window 0x%lx below 0x%lx\n",
+ *newp, last_window);
+
+ XConfigureWindow (stack->screen->display->xdisplay,
+ *newp,
+ CWSibling | CWStackMode,
+ &changes);
+ }
+
+ last_window = *newp;
+ ++newp;
+ }
+ }
+
+ if (newp != new_end)
+ {
+ /* Restack remaining windows */
+ meta_topic (META_DEBUG_STACK, "Restacking remaining %d windows\n",
+ (int) (new_end - newp));
+ /* We need to include an already-stacked window
+ * in the restack call, so we get in the proper position
+ * with respect to it.
+ */
+ if (newp != new_stack)
+ --newp;
+ XRestackWindows (stack->screen->display->xdisplay,
+ (Window *) newp, new_end - newp);
+ }
+ }
+
+ meta_error_trap_pop (stack->screen->display, FALSE);
+ /* on error, a window was destroyed; it should eventually
+ * get removed from the stacking list when we unmanage it
+ * and we'll fix stacking at that time.
+ */
+
+ /* Sync _NET_CLIENT_LIST and _NET_CLIENT_LIST_STACKING */
+
+ XChangeProperty (stack->screen->display->xdisplay,
+ stack->screen->xroot,
+ stack->screen->display->atom__NET_CLIENT_LIST,
+ XA_WINDOW,
+ 32, PropModeReplace,
+ (unsigned char *)stack->windows->data,
+ stack->windows->len);
+ XChangeProperty (stack->screen->display->xdisplay,
+ stack->screen->xroot,
+ stack->screen->display->atom__NET_CLIENT_LIST_STACKING,
+ XA_WINDOW,
+ 32, PropModeReplace,
+ (unsigned char *)stacked->data,
+ stacked->len);
+
+ g_array_free (stacked, TRUE);
+
+ if (stack->last_root_children_stacked)
+ g_array_free (stack->last_root_children_stacked, TRUE);
+ stack->last_root_children_stacked = root_children_stacked;
+
+ /* That was scary... */
+}
+
+MetaWindow*
+meta_stack_get_top (MetaStack *stack)
+{
+ stack_ensure_sorted (stack);
+
+ if (stack->sorted)
+ return stack->sorted->data;
+ else
+ return NULL;
+}
+
+MetaWindow*
+meta_stack_get_bottom (MetaStack *stack)
+{
+ GList *link;
+
+ stack_ensure_sorted (stack);
+
+ link = g_list_last (stack->sorted);
+ if (link != NULL)
+ return link->data;
+ else
+ return NULL;
+}
+
+MetaWindow*
+meta_stack_get_above (MetaStack *stack,
+ MetaWindow *window,
+ gboolean only_within_layer)
+{
+ GList *link;
+ MetaWindow *above;
+
+ stack_ensure_sorted (stack);
+
+ link = g_list_find (stack->sorted, window);
+ if (link == NULL)
+ return NULL;
+ if (link->prev == NULL)
+ return NULL;
+
+ above = link->prev->data;
+
+ if (only_within_layer &&
+ above->layer != window->layer)
+ return NULL;
+ else
+ return above;
+}
+
+MetaWindow*
+meta_stack_get_below (MetaStack *stack,
+ MetaWindow *window,
+ gboolean only_within_layer)
+{
+ GList *link;
+ MetaWindow *below;
+
+ stack_ensure_sorted (stack);
+
+ link = g_list_find (stack->sorted, window);
+
+ if (link == NULL)
+ return NULL;
+ if (link->next == NULL)
+ return NULL;
+
+ below = link->next->data;
+
+ if (only_within_layer &&
+ below->layer != window->layer)
+ return NULL;
+ else
+ return below;
+}
+
+static gboolean
+window_contains_point (MetaWindow *window,
+ int root_x,
+ int root_y)
+{
+ MetaRectangle rect;
+
+ meta_window_get_outer_rect (window, &rect);
+
+ return POINT_IN_RECT (root_x, root_y, rect);
+}
+
+static MetaWindow*
+get_default_focus_window (MetaStack *stack,
+ MetaWorkspace *workspace,
+ MetaWindow *not_this_one,
+ gboolean must_be_at_point,
+ int root_x,
+ int root_y)
+{
+ /* Find the topmost, focusable, mapped, window.
+ * not_this_one is being unfocused or going away, so exclude it.
+ * Also, prefer to focus transient parent of not_this_one,
+ * or top window in same group as not_this_one.
+ */
+
+ MetaWindow *topmost_dock;
+ MetaWindow *transient_parent;
+ MetaWindow *topmost_in_group;
+ MetaWindow *topmost_overall;
+ MetaGroup *not_this_one_group;
+ GList *link;
+
+ topmost_dock = NULL;
+ transient_parent = NULL;
+ topmost_in_group = NULL;
+ topmost_overall = NULL;
+ if (not_this_one)
+ not_this_one_group = meta_window_get_group (not_this_one);
+ else
+ not_this_one_group = NULL;
+
+ stack_ensure_sorted (stack);
+
+ /* top of this layer is at the front of the list */
+ link = stack->sorted;
+
+ while (link)
+ {
+ MetaWindow *window = link->data;
+
+ if (window &&
+ window != not_this_one &&
+ (window->unmaps_pending == 0) &&
+ !window->minimized &&
+ (window->input || window->take_focus) &&
+ (workspace == NULL ||
+ meta_window_located_on_workspace (window, workspace)))
+ {
+ if (topmost_dock == NULL &&
+ window->type == META_WINDOW_DOCK)
+ topmost_dock = window;
+
+ if (not_this_one != NULL)
+ {
+ if (transient_parent == NULL &&
+ not_this_one->xtransient_for != None &&
+ not_this_one->xtransient_for == window->xwindow &&
+ (!must_be_at_point ||
+ window_contains_point (window, root_x, root_y)))
+ transient_parent = window;
+
+ if (topmost_in_group == NULL &&
+ not_this_one_group != NULL &&
+ not_this_one_group == meta_window_get_group (window) &&
+ (!must_be_at_point ||
+ window_contains_point (window, root_x, root_y)))
+ topmost_in_group = window;
+ }
+
+ /* Note that DESKTOP windows can be topmost_overall so
+ * we prefer focusing desktop or other windows over
+ * focusing dock, even though docks are stacked higher.
+ */
+ if (topmost_overall == NULL &&
+ window->type != META_WINDOW_DOCK &&
+ (!must_be_at_point ||
+ window_contains_point (window, root_x, root_y)))
+ topmost_overall = window;
+
+ /* We could try to bail out early here for efficiency in
+ * some cases, but it's just not worth the code.
+ */
+ }
+
+ link = link->next;
+ }
+
+ if (transient_parent)
+ return transient_parent;
+ else if (topmost_in_group)
+ return topmost_in_group;
+ else if (topmost_overall)
+ return topmost_overall;
+ else
+ return topmost_dock;
+}
+
+MetaWindow*
+meta_stack_get_default_focus_window_at_point (MetaStack *stack,
+ MetaWorkspace *workspace,
+ MetaWindow *not_this_one,
+ int root_x,
+ int root_y)
+{
+ return get_default_focus_window (stack, workspace, not_this_one,
+ TRUE, root_x, root_y);
+}
+
+MetaWindow*
+meta_stack_get_default_focus_window (MetaStack *stack,
+ MetaWorkspace *workspace,
+ MetaWindow *not_this_one)
+{
+ return get_default_focus_window (stack, workspace, not_this_one,
+ FALSE, 0, 0);
+}
+
+GList*
+meta_stack_list_windows (MetaStack *stack,
+ MetaWorkspace *workspace)
+{
+ GList *workspace_windows = NULL;
+ GList *link;
+
+ stack_ensure_sorted (stack); /* do adds/removes */
+
+ link = stack->sorted;
+
+ while (link)
+ {
+ MetaWindow *window = link->data;
+
+ if (window &&
+ (workspace == NULL || meta_window_located_on_workspace (window, workspace)))
+ {
+ workspace_windows = g_list_prepend (workspace_windows,
+ window);
+ }
+
+ link = link->next;
+ }
+
+ return workspace_windows;
+}
+
+int
+meta_stack_windows_cmp (MetaStack *stack,
+ MetaWindow *window_a,
+ MetaWindow *window_b)
+{
+ g_return_val_if_fail (window_a->screen == window_b->screen, 0);
+
+ /* -1 means a below b */
+
+ stack_ensure_sorted (stack); /* update constraints, layers */
+
+ if (window_a->layer < window_b->layer)
+ return -1;
+ else if (window_a->layer > window_b->layer)
+ return 1;
+ else if (window_a->stack_position < window_b->stack_position)
+ return -1;
+ else if (window_a->stack_position > window_b->stack_position)
+ return 1;
+ else
+ return 0; /* not reached */
+}
+
+static int
+compare_just_window_stack_position (void *a,
+ void *b)
+{
+ MetaWindow *window_a = a;
+ MetaWindow *window_b = b;
+
+ if (window_a->stack_position < window_b->stack_position)
+ return -1; /* move window_a earlier in list */
+ else if (window_a->stack_position > window_b->stack_position)
+ return 1;
+ else
+ return 0; /* not reached */
+}
+
+GList*
+meta_stack_get_positions (MetaStack *stack)
+{
+ GList *tmp;
+
+ /* Make sure to handle any adds or removes */
+ stack_ensure_sorted (stack);
+
+ tmp = g_list_copy (stack->sorted);
+ tmp = g_list_sort (tmp, (GCompareFunc) compare_just_window_stack_position);
+
+ return tmp;
+}
+
+static gint
+compare_pointers (gconstpointer a,
+ gconstpointer b)
+{
+ if (a > b)
+ return 1;
+ else if (a < b)
+ return -1;
+ else
+ return 0;
+}
+
+static gboolean
+lists_contain_same_windows (GList *a,
+ GList *b)
+{
+ GList *copy1, *copy2;
+ GList *tmp1, *tmp2;
+
+ if (g_list_length (a) != g_list_length (b))
+ return FALSE;
+
+ tmp1 = copy1 = g_list_sort (g_list_copy (a), compare_pointers);
+ tmp2 = copy2 = g_list_sort (g_list_copy (b), compare_pointers);
+
+ while (tmp1 && tmp1->data == tmp2->data) /* tmp2 is non-NULL if tmp1 is */
+ {
+ tmp1 = tmp1->next;
+ tmp2 = tmp2->next;
+ }
+
+ g_list_free (copy1);
+ g_list_free (copy2);
+
+ return (tmp1 == NULL); /* tmp2 is non-NULL if tmp1 is */
+}
+
+void
+meta_stack_set_positions (MetaStack *stack,
+ GList *windows)
+{
+ int i;
+ GList *tmp;
+
+ /* Make sure any adds or removes aren't in limbo -- is this needed? */
+ stack_ensure_sorted (stack);
+
+ if (!lists_contain_same_windows (windows, stack->sorted))
+ {
+ meta_warning ("This list of windows has somehow changed; not resetting "
+ "positions of the windows.\n");
+ return;
+ }
+
+ g_list_free (stack->sorted);
+ stack->sorted = g_list_copy (windows);
+
+ stack->need_resort = TRUE;
+ stack->need_constrain = TRUE;
+
+ i = 0;
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+ w->stack_position = i++;
+ tmp = tmp->next;
+ }
+
+ meta_topic (META_DEBUG_STACK,
+ "Reset the stack positions of (nearly) all windows\n");
+
+ stack_sync_to_server (stack);
+}
+
+void
+meta_window_set_stack_position_no_sync (MetaWindow *window,
+ int position)
+{
+ int low, high, delta;
+ GList *tmp;
+
+ g_return_if_fail (window->screen->stack != NULL);
+ g_return_if_fail (window->stack_position >= 0);
+ g_return_if_fail (position >= 0);
+ g_return_if_fail (position < window->screen->stack->n_positions);
+
+ if (position == window->stack_position)
+ {
+ meta_topic (META_DEBUG_STACK, "Window %s already has position %d\n",
+ window->desc, position);
+ return;
+ }
+
+ window->screen->stack->need_resort = TRUE;
+ window->screen->stack->need_constrain = TRUE;
+
+ if (position < window->stack_position)
+ {
+ low = position;
+ high = window->stack_position - 1;
+ delta = 1;
+ }
+ else
+ {
+ low = window->stack_position + 1;
+ high = position;
+ delta = -1;
+ }
+
+ tmp = window->screen->stack->sorted;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ if (w->stack_position >= low &&
+ w->stack_position <= high)
+ w->stack_position += delta;
+
+ tmp = tmp->next;
+ }
+
+ window->stack_position = position;
+
+ meta_topic (META_DEBUG_STACK,
+ "Window %s had stack_position set to %d\n",
+ window->desc, window->stack_position);
+}
+
+void
+meta_window_set_stack_position (MetaWindow *window,
+ int position)
+{
+ meta_window_set_stack_position_no_sync (window, position);
+ stack_sync_to_server (window->screen->stack);
+}
diff --git a/src/core/stack.h b/src/core/stack.h
new file mode 100644
index 00000000..f9693897
--- /dev/null
+++ b/src/core/stack.h
@@ -0,0 +1,402 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file stack.h Which windows cover which other windows
+ *
+ * There are two factors that determine window position.
+ *
+ * One is window->stack_position, which is a unique integer
+ * indicating how windows are ordered with respect to one
+ * another. The ordering here transcends layers; it isn't changed
+ * as the window is moved among layers. This allows us to move several
+ * windows from one layer to another, while preserving the relative
+ * order of the moved windows. Also, it allows us to restore
+ * the stacking order from a saved session.
+ *
+ * However when actually stacking windows on the screen, the
+ * layer overrides the stack_position; windows are first sorted
+ * by layer, then by stack_position within each layer.
+ */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_STACK_H
+#define META_STACK_H
+
+#include "screen-private.h"
+
+/**
+ * Layers a window can be in.
+ * These MUST be in the order of stacking.
+ */
+typedef enum
+{
+ META_LAYER_DESKTOP = 0,
+ META_LAYER_BOTTOM = 1,
+ META_LAYER_NORMAL = 2,
+ META_LAYER_TOP = 4, /* Same as DOCK; see EWMH and bug 330717 */
+ META_LAYER_DOCK = 4,
+ META_LAYER_FULLSCREEN = 5,
+ META_LAYER_FOCUSED_WINDOW = 6,
+ META_LAYER_LAST = 7
+} MetaStackLayer;
+
+/**
+ * A sorted list of windows bearing some level of resemblance to the stack of
+ * windows on the X server.
+ *
+ * (This is only used as a field within a MetaScreen; we treat it as a separate
+ * class for simplicity.)
+ */
+struct _MetaStack
+{
+ /** The MetaScreen containing this stack. */
+ MetaScreen *screen;
+
+ /**
+ * A sequence of all the Windows (X handles, not MetaWindows) of the windows
+ * we manage, sorted in order. Suitable to be passed into _NET_CLIENT_LIST.
+ */
+ GArray *windows;
+
+ /** The MetaWindows of the windows we manage, sorted in order. */
+ GList *sorted;
+
+ /**
+ * MetaWindows waiting to be added to the "sorted" and "windows" list, after
+ * being added by meta_stack_add() and before being assimilated by
+ * stack_ensure_sorted().
+ *
+ * The order of the elements in this list is not important; what is important
+ * is the stack_position element of each window.
+ */
+ GList *added;
+
+ /**
+ * Windows (X handles, not MetaWindows) waiting to be removed from the
+ * "windows" list, after being removed by meta_stack_remove() and before
+ * being assimilated by stack_ensure_sorted(). (We already removed them
+ * from the "sorted" list.)
+ *
+ * The order of the elements in this list is not important.
+ */
+ GList *removed;
+
+ /**
+ * If this is zero, the local stack oughtn't to be brought up to date with
+ * the X server's stack, because it is in the middle of being updated.
+ * If it is positive, the local stack is said to be "frozen", and will need
+ * to be thawed that many times before the stack can be brought up to date
+ * again. You may freeze the stack with meta_stack_freeze() and thaw it
+ * with meta_stack_thaw().
+ */
+ int freeze_count;
+
+ /**
+ * The last-known stack of all windows, bottom to top. We cache it here
+ * so that subsequent times we'll be able to do incremental moves.
+ */
+ GArray *last_root_children_stacked;
+
+ /**
+ * Number of stack positions; same as the length of added, but
+ * kept for quick reference.
+ */
+ gint n_positions;
+
+ /** Is the stack in need of re-sorting? */
+ unsigned int need_resort : 1;
+
+ /**
+ * Are the windows in the stack in need of having their
+ * layers recalculated?
+ */
+ unsigned int need_relayer : 1;
+
+ /**
+ * Are the windows in the stack in need of having their positions
+ * recalculated with respect to transiency (parent and child windows)?
+ */
+ unsigned int need_constrain : 1;
+};
+
+/**
+ * Creates and initialises a MetaStack.
+ *
+ * \param screen The MetaScreen which will be the parent of this stack.
+ * \return The new screen.
+ */
+MetaStack *meta_stack_new (MetaScreen *screen);
+
+/**
+ * Destroys and frees a MetaStack.
+ *
+ * \param stack The stack to destroy.
+ */
+void meta_stack_free (MetaStack *stack);
+
+/**
+ * Adds a window to the local stack. It is a fatal error to call this
+ * function on a window which already exists on the stack of any screen.
+ *
+ * \param window The window to add
+ * \param stack The stack to add it to
+ */
+void meta_stack_add (MetaStack *stack,
+ MetaWindow *window);
+
+/**
+ * Removes a window from the local stack. It is a fatal error to call this
+ * function on a window which exists on the stack of any screen.
+ *
+ * \param window The window to remove
+ * \param stack The stack to remove it from
+ */
+void meta_stack_remove (MetaStack *stack,
+ MetaWindow *window);
+/**
+ * Recalculates the correct layer for all windows in the stack,
+ * and moves them about accordingly.
+ *
+ * \param window Dummy parameter
+ * \param stack The stack to recalculate
+ * \bug What's with the dummy parameter?
+ */
+void meta_stack_update_layer (MetaStack *stack,
+ MetaWindow *window);
+
+/**
+ * Recalculates the correct stacking order for all windows in the stack
+ * according to their transience, and moves them about accordingly.
+ *
+ * \param window Dummy parameter
+ * \param stack The stack to recalculate
+ * \bug What's with the dummy parameter?
+ */
+void meta_stack_update_transient (MetaStack *stack,
+ MetaWindow *window);
+
+/**
+ * Move a window to the top of its layer.
+ *
+ * \param stack The stack to modify.
+ * \param window The window that's making an ascension.
+ * (Amulet of Yendor not required.)
+ */
+void meta_stack_raise (MetaStack *stack,
+ MetaWindow *window);
+/**
+ * Move a window to the bottom of its layer.
+ *
+ * \param stack The stack to modify.
+ * \param window The window that's on the way downwards.
+ */
+void meta_stack_lower (MetaStack *stack,
+ MetaWindow *window);
+
+/**
+ * Prevent syncing to server until the next call of meta_stack_thaw(),
+ * so that we can carry out multiple operations in one go without having
+ * everything halfway reflected on the X server.
+ *
+ * (Calls to meta_stack_freeze() nest, so that multiple calls to
+ * meta_stack_freeze will require multiple calls to meta_stack_thaw().)
+ *
+ * \param stack The stack to freeze.
+ */
+void meta_stack_freeze (MetaStack *stack);
+
+/**
+ * Undoes a meta_stack_freeze(), and processes anything which has become
+ * necessary during the freeze. It is an error to call this function if
+ * the stack has not been frozen.
+ *
+ * \param stack The stack to thaw.
+ */
+void meta_stack_thaw (MetaStack *stack);
+
+/**
+ * Finds the top window on the stack.
+ *
+ * \param stack The stack to examine.
+ * \return The top window on the stack, or NULL in the vanishingly unlikely
+ * event that you have no windows on your screen whatsoever.
+ */
+MetaWindow* meta_stack_get_top (MetaStack *stack);
+
+/**
+ * Finds the window at the bottom of the stack. Since that's pretty much
+ * always the desktop, this isn't the most useful of functions, and nobody
+ * actually calls it. We should probably get rid of it.
+ *
+ * \param stack The stack to search
+ */
+MetaWindow* meta_stack_get_bottom (MetaStack *stack);
+
+/**
+ * Finds the window above a given window in the stack.
+ * It is not an error to pass in a window which does not exist in
+ * the stack; the function will merely return NULL.
+ *
+ * \param stack The stack to search.
+ * \param window The window to look above.
+ * \param only_within_layer If true, will return NULL if "window" is the
+ * top window in its layer.
+ * \return NULL if there is no such window;
+ * the window above "window" otherwise.
+ */
+MetaWindow* meta_stack_get_above (MetaStack *stack,
+ MetaWindow *window,
+ gboolean only_within_layer);
+
+/**
+ * Finds the window below a given window in the stack.
+ * It is not an error to pass in a window which does not exist in
+ * the stack; the function will merely return NULL.
+ *
+ * \param stack The stack to search.
+ * \param window The window to look below.
+ * \param only_within_layer If true, will return NULL if "window" is the
+ * bottom window in its layer.
+ * \return NULL if there is no such window;
+ * the window below "window" otherwise.
+ */
+MetaWindow* meta_stack_get_below (MetaStack *stack,
+ MetaWindow *window,
+ gboolean only_within_layer);
+
+/**
+ * Find the topmost, focusable, mapped, window in a stack. If you supply
+ * a window as "not_this_one", we won't return that one (presumably
+ * because it's going to be going away). But if you do supply "not_this_one"
+ * and we find its parent, we'll return that; and if "not_this_one" is in
+ * a group, we'll return the top window of that group.
+ *
+ * Also, we are prejudiced against dock windows. Every kind of window, even
+ * the desktop, will be returned in preference to a dock window.
+ *
+ * \param stack The stack to search.
+ * \param workspace NULL to search all workspaces; otherwise only windows
+ * from that workspace will be returned.
+ * \param not_this_one Window to ignore because it's being unfocussed or
+ * going away.
+ * \return The window matching all these constraints or NULL if none does.
+ *
+ * \bug Never called!
+ */
+MetaWindow* meta_stack_get_default_focus_window (MetaStack *stack,
+ MetaWorkspace *workspace,
+ MetaWindow *not_this_one);
+
+/**
+ * Find the topmost, focusable, mapped, window in a stack. If you supply
+ * a window as "not_this_one", we won't return that one (presumably
+ * because it's going to be going away). But if you do supply "not_this_one"
+ * and we find its parent, we'll return that; and if "not_this_one" is in
+ * a group, we'll return the top window of that group.
+ *
+ * Also, we are prejudiced against dock windows. Every kind of window, even
+ * the desktop, will be returned in preference to a dock window.
+ *
+ * \param stack The stack to search.
+ * \param workspace NULL to search all workspaces; otherwise only windows
+ * from that workspace will be returned.
+ * \param not_this_one Window to ignore because it's being unfocussed or
+ * going away.
+ * \param root_x The returned window must contain this point,
+ * unless it's a dock.
+ * \param root_y See root_x.
+ * \return The window matching all these constraints or NULL if none does.
+ */
+MetaWindow* meta_stack_get_default_focus_window_at_point (MetaStack *stack,
+ MetaWorkspace *workspace,
+ MetaWindow *not_this_one,
+ int root_x,
+ int root_y);
+
+/**
+ * Finds all the windows in the stack, in order.
+ *
+ * \param stack The stack to examine.
+ * \param workspace If non-NULL, only windows on this workspace will be
+ * returned; otherwise all windows in the stack will be
+ * returned.
+ * \return A list of windows, in stacking order, honouring layers.
+ */
+GList* meta_stack_list_windows (MetaStack *stack,
+ MetaWorkspace *workspace);
+
+/**
+ * Comparison function for windows within a stack. This is not directly
+ * suitable for use within a standard comparison routine, because it takes
+ * an extra parameter; you will need to wrap it.
+ *
+ * (FIXME: We could remove the stack parameter and use the stack of
+ * the screen of window A, and complain if the stack of the screen of
+ * window B differed; then this would be a usable general comparison function.)
+ *
+ * (FIXME: Apparently identical to compare_window_position(). Merge them.)
+ *
+ * \param stack A stack containing both window_a and window_b
+ * \param window_a A window
+ * \param window_b Another window
+ * \return -1 if window_a is below window_b, honouring layers; 1 if it's
+ * above it; 0 if you passed in the same window twice!
+ */
+int meta_stack_windows_cmp (MetaStack *stack,
+ MetaWindow *window_a,
+ MetaWindow *window_b);
+
+/**
+ * Sets the position of a window within the stack. This will only move it
+ * up or down within its layer. It is an error to attempt to move this
+ * below position zero or above the last position in the stack (however, since
+ * we don't provide a simple way to tell the number of windows in the stack,
+ * this requirement may not be easy to fulfil).
+ *
+ * \param window The window which is moving.
+ * \param position Where it should move to (0 is the bottom).
+ */
+void meta_window_set_stack_position (MetaWindow *window,
+ int position);
+
+/**
+ * Returns the current stack state, allowing rudimentary transactions.
+ *
+ * \param stack The stack to examine.
+ * \return An opaque GList representing the current stack sort order;
+ * it is the caller's responsibility to free it.
+ * Pass this to meta_stack_set_positions() later if you want to restore
+ * the state to where it was when you called this function.
+ */
+GList* meta_stack_get_positions (MetaStack *stack);
+
+/**
+ * Rolls back a transaction, given the list returned from
+ * meta_stack_get_positions().
+ *
+ * \param stack The stack to roll back.
+ * \param windows The list returned from meta_stack_get_positions().
+ */
+void meta_stack_set_positions (MetaStack *stack,
+ GList *windows);
+
+#endif
diff --git a/src/core/testasyncgetprop.c b/src/core/testasyncgetprop.c
new file mode 100644
index 00000000..ed044009
--- /dev/null
+++ b/src/core/testasyncgetprop.c
@@ -0,0 +1,497 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation.
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the name of The Open Group shall not be
+ * used in advertising or otherwise to promote the sale, use or other dealings
+ * in this Software without prior written authorization from The Open Group.
+ */
+
+#include "async-getprop.h"
+
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <assert.h>
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef NULL
+#define NULL ((void*) 0)
+#endif
+
+#ifdef HAVE_BACKTRACE
+#include <execinfo.h>
+static void
+print_backtrace (void)
+{
+ void *bt[500];
+ int bt_size;
+ int i;
+ char **syms;
+
+ bt_size = backtrace (bt, 500);
+
+ syms = backtrace_symbols (bt, bt_size);
+
+ i = 0;
+ while (i < bt_size)
+ {
+ fprintf (stderr, " %s\n", syms[i]);
+ ++i;
+ }
+
+ free (syms);
+}
+#else
+static void
+print_backtrace (void)
+{
+ fprintf (stderr, "Not compiled with backtrace support\n");
+}
+#endif
+
+static int error_trap_depth = 0;
+
+static int
+x_error_handler (Display *xdisplay,
+ XErrorEvent *error)
+{
+ char buf[64];
+
+ XGetErrorText (xdisplay, error->error_code, buf, 63);
+
+ if (error_trap_depth == 0)
+ {
+ print_backtrace ();
+
+ fprintf (stderr, "Unexpected X error: %s serial %ld error_code %d request_code %d minor_code %d)\n",
+ buf,
+ error->serial,
+ error->error_code,
+ error->request_code,
+ error->minor_code);
+
+ exit (1);
+ }
+
+ return 1; /* return value is meaningless */
+}
+
+static void
+error_trap_push (Display *xdisplay)
+{
+ ++error_trap_depth;
+}
+
+static void
+error_trap_pop (Display *xdisplay)
+{
+ if (error_trap_depth == 0)
+ {
+ fprintf (stderr, "Error trap underflow!\n");
+ exit (1);
+ }
+
+ XSync (xdisplay, False); /* get all errors out of the queue */
+ --error_trap_depth;
+}
+
+static char*
+my_strdup (const char *str)
+{
+ char *s;
+
+ s = malloc (strlen (str) + 1);
+ if (s == NULL)
+ {
+ fprintf (stderr, "malloc failed\n");
+ exit (1);
+ }
+ strcpy (s, str);
+
+ return s;
+}
+
+static char*
+atom_name (Display *display,
+ Atom atom)
+{
+ if (atom == None)
+ {
+ return my_strdup ("None");
+ }
+ else
+ {
+ char *xname;
+ char *ret;
+
+ error_trap_push (display);
+ xname = XGetAtomName (display, atom);
+ error_trap_pop (display);
+ if (xname == NULL)
+ return my_strdup ("[unknown atom]");
+
+ ret = my_strdup (xname);
+ XFree (xname);
+
+ return ret;
+ }
+}
+
+
+#define ELAPSED(start_time, current_time) \
+ (((((double)current_time.tv_sec - start_time.tv_sec) * 1000000 + \
+ (current_time.tv_usec - start_time.tv_usec))) / 1000.0)
+
+static struct timeval program_start_time;
+
+static Bool
+try_get_reply (Display *xdisplay,
+ AgGetPropertyTask *task)
+{
+ if (ag_task_have_reply (task))
+ {
+ int result;
+ Atom actual_type;
+ int actual_format;
+ unsigned long n_items;
+ unsigned long bytes_after;
+ unsigned char *data;
+ char *name;
+ struct timeval current_time;
+
+ gettimeofday (&current_time, NULL);
+
+ printf (" %gms (we have a reply for property %ld)\n",
+ ELAPSED (program_start_time, current_time),
+ ag_task_get_property (task));
+
+ data = NULL;
+
+ name = atom_name (xdisplay,
+ ag_task_get_property (task));
+ printf (" %s on 0x%lx:\n", name,
+ ag_task_get_window (task));
+ free (name);
+
+ result = ag_task_get_reply_and_free (task,
+ &actual_type,
+ &actual_format,
+ &n_items,
+ &bytes_after,
+ &data);
+ task = NULL;
+
+ if (result != Success)
+ {
+ fprintf (stderr, " error code %d getting reply\n", result);
+ }
+ else
+ {
+ name = atom_name (xdisplay, actual_type);
+ printf (" actual_type = %s\n", name);
+ free (name);
+
+ printf (" actual_format = %d\n", actual_format);
+
+ printf (" n_items = %lu\n", n_items);
+ printf (" bytes_after = %lu\n", bytes_after);
+
+ printf (" data = \"%s\"\n", data ? (char*) data : "NULL");
+ }
+
+ return True;
+ }
+
+ return False;
+}
+
+static void run_speed_comparison (Display *xdisplay,
+ Window window);
+
+int
+main (int argc, char **argv)
+{
+ Display *xdisplay;
+ int i;
+ int n_left;
+ int n_props;
+ Window window;
+ const char *window_str;
+ char *end;
+ Atom *props;
+ struct timeval current_time;
+
+ if (argc < 2)
+ {
+ fprintf (stderr, "specify window ID\n");
+ return 1;
+ }
+
+ window_str = argv[1];
+
+ end = NULL;
+ window = strtoul (window_str, &end, 0);
+ if (end == NULL || *end != '\0')
+ {
+ fprintf (stderr, "\"%s\" does not parse as a window ID\n", window_str);
+ return 1;
+ }
+
+ xdisplay = XOpenDisplay (NULL);
+ if (xdisplay == NULL)
+ {
+ fprintf (stderr, "Could not open display\n");
+ return 1;
+ }
+
+ if (getenv ("MARCO_SYNC") != NULL)
+ XSynchronize (xdisplay, True);
+
+ XSetErrorHandler (x_error_handler);
+
+ n_props = 0;
+ props = XListProperties (xdisplay, window, &n_props);
+ if (n_props == 0 || props == NULL)
+ {
+ fprintf (stderr, "Window has no properties\n");
+ return 1;
+ }
+
+ gettimeofday (&program_start_time, NULL);
+
+ i = 0;
+ while (i < n_props)
+ {
+ gettimeofday (&current_time, NULL);
+ printf (" %gms (sending request for property %ld)\n",
+ ELAPSED (program_start_time, current_time),
+ props[i]);
+ if (ag_task_create (xdisplay,
+ window, props[i],
+ 0, 0xffffffff,
+ False,
+ AnyPropertyType) == NULL)
+ {
+ fprintf (stderr, "Failed to send request\n");
+ return 1;
+ }
+
+ ++i;
+ }
+
+ XFree (props);
+ props = NULL;
+
+ n_left = n_props;
+
+ while (TRUE)
+ {
+ XEvent xevent;
+ int connection;
+ fd_set set;
+ AgGetPropertyTask *task;
+
+ /* Mop up event queue */
+ while (XPending (xdisplay) > 0)
+ {
+ XNextEvent (xdisplay, &xevent);
+ gettimeofday (&current_time, NULL);
+ printf (" %gms (processing event type %d)\n",
+ ELAPSED (program_start_time, current_time),
+ xevent.xany.type);
+ }
+
+ while ((task = ag_get_next_completed_task (xdisplay)))
+ {
+ try_get_reply (xdisplay, task);
+ n_left -= 1;
+ }
+
+ if (n_left == 0)
+ {
+ printf ("All %d replies received.\n", n_props);
+ break;
+ }
+
+ /* Wake up if we may have a reply */
+ connection = ConnectionNumber (xdisplay);
+
+ FD_ZERO (&set);
+ FD_SET (connection, &set);
+
+ gettimeofday (&current_time, NULL);
+ printf (" %gms (blocking for data %d left)\n",
+ ELAPSED (program_start_time, current_time), n_left);
+ select (connection + 1, &set, NULL, NULL, NULL);
+ }
+
+ run_speed_comparison (xdisplay, window);
+
+ return 0;
+}
+
+/* This function doesn't have all the printf's
+ * and other noise, it just compares async to sync
+ */
+static void
+run_speed_comparison (Display *xdisplay,
+ Window window)
+{
+ int i;
+ int n_props;
+ struct timeval start, end;
+ int n_left;
+
+ /* We just use atom values (0 to n_props) % 200, many are probably
+ * BadAtom, that's fine, but the %200 keeps most of them valid. The
+ * async case is about twice as advantageous when using valid atoms
+ * (or the issue may be that it's more advantageous when the
+ * properties are present and data is transmitted).
+ */
+ n_props = 4000;
+ printf ("Timing with %d property requests\n", n_props);
+
+ gettimeofday (&start, NULL);
+
+ i = 0;
+ while (i < n_props)
+ {
+ if (ag_task_create (xdisplay,
+ window, (Atom) i % 200,
+ 0, 0xffffffff,
+ False,
+ AnyPropertyType) == NULL)
+ {
+ fprintf (stderr, "Failed to send request\n");
+ exit (1);
+ }
+
+ ++i;
+ }
+
+ n_left = n_props;
+
+ while (TRUE)
+ {
+ int connection;
+ fd_set set;
+ XEvent xevent;
+ AgGetPropertyTask *task;
+
+ /* Mop up event queue */
+ while (XPending (xdisplay) > 0)
+ XNextEvent (xdisplay, &xevent);
+
+ while ((task = ag_get_next_completed_task (xdisplay)))
+ {
+ int result;
+ Atom actual_type;
+ int actual_format;
+ unsigned long n_items;
+ unsigned long bytes_after;
+ unsigned char *data;
+
+ assert (ag_task_have_reply (task));
+
+ data = NULL;
+ result = ag_task_get_reply_and_free (task,
+ &actual_type,
+ &actual_format,
+ &n_items,
+ &bytes_after,
+ &data);
+
+ if (data)
+ XFree (data);
+
+ n_left -= 1;
+ }
+
+ if (n_left == 0)
+ break;
+
+ /* Wake up if we may have a reply */
+ connection = ConnectionNumber (xdisplay);
+
+ FD_ZERO (&set);
+ FD_SET (connection, &set);
+
+ select (connection + 1, &set, NULL, NULL, NULL);
+ }
+
+ gettimeofday (&end, NULL);
+
+ printf ("Async time: %gms\n",
+ ELAPSED (start, end));
+
+ gettimeofday (&start, NULL);
+
+ error_trap_push (xdisplay);
+
+ i = 0;
+ while (i < n_props)
+ {
+ Atom actual_type;
+ int actual_format;
+ unsigned long n_items;
+ unsigned long bytes_after;
+ unsigned char *data;
+
+ data = NULL;
+ if (XGetWindowProperty (xdisplay, window,
+ (Atom) i % 200,
+ 0, 0xffffffff,
+ False,
+ AnyPropertyType,
+ &actual_type,
+ &actual_format,
+ &n_items,
+ &bytes_after,
+ &data) == Success)
+ {
+ if (data)
+ XFree (data);
+ }
+
+ ++i;
+ }
+
+ error_trap_pop (xdisplay);
+
+ gettimeofday (&end, NULL);
+
+ printf ("Sync time: %gms\n",
+ ELAPSED (start, end));
+}
diff --git a/src/core/testboxes.c b/src/core/testboxes.c
new file mode 100644
index 00000000..fc81f064
--- /dev/null
+++ b/src/core/testboxes.c
@@ -0,0 +1,1416 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco box operation testing program */
+
+/*
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "boxes.h"
+#include <glib.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <X11/Xutil.h> /* Just for the definition of the various gravities */
+#include <time.h> /* To initialize random seed */
+
+#define NUM_RANDOM_RUNS 10000
+
+static void
+init_random_ness ()
+{
+ srand(time(NULL));
+}
+
+static void
+get_random_rect (MetaRectangle *rect)
+{
+ rect->x = rand () % 1600;
+ rect->y = rand () % 1200;
+ rect->width = rand () % 1600 + 1;
+ rect->height = rand () % 1200 + 1;
+}
+
+static MetaRectangle*
+new_meta_rect (int x, int y, int width, int height)
+{
+ MetaRectangle* temporary;
+ temporary = g_new (MetaRectangle, 1);
+ temporary->x = x;
+ temporary->y = y;
+ temporary->width = width;
+ temporary->height = height;
+
+ return temporary;
+}
+
+static MetaStrut*
+new_meta_strut (int x, int y, int width, int height, int side)
+{
+ MetaStrut* temporary;
+ temporary = g_new (MetaStrut, 1);
+ temporary->rect = meta_rect(x, y, width, height);
+ temporary->side = side;
+
+ return temporary;
+}
+
+static MetaEdge*
+new_screen_edge (int x, int y, int width, int height, int side_type)
+{
+ MetaEdge* temporary;
+ temporary = g_new (MetaEdge, 1);
+ temporary->rect.x = x;
+ temporary->rect.y = y;
+ temporary->rect.width = width;
+ temporary->rect.height = height;
+ temporary->side_type = side_type;
+ temporary->edge_type = META_EDGE_SCREEN;
+
+ return temporary;
+}
+
+static MetaEdge*
+new_xinerama_edge (int x, int y, int width, int height, int side_type)
+{
+ MetaEdge* temporary;
+ temporary = g_new (MetaEdge, 1);
+ temporary->rect.x = x;
+ temporary->rect.y = y;
+ temporary->rect.width = width;
+ temporary->rect.height = height;
+ temporary->side_type = side_type;
+ temporary->edge_type = META_EDGE_XINERAMA;
+
+ return temporary;
+}
+
+static void
+test_area ()
+{
+ MetaRectangle temp;
+ int i;
+ for (i = 0; i < NUM_RANDOM_RUNS; i++)
+ {
+ get_random_rect (&temp);
+ g_assert (meta_rectangle_area (&temp) == temp.width * temp.height);
+ }
+
+ temp = meta_rect (0, 0, 5, 7);
+ g_assert (meta_rectangle_area (&temp) == 35);
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+test_intersect ()
+{
+ MetaRectangle a = {100, 200, 50, 40};
+ MetaRectangle b = { 0, 50, 110, 152};
+ MetaRectangle c = { 0, 0, 10, 10};
+ MetaRectangle d = {100, 100, 50, 50};
+ MetaRectangle b_intersect_d = {100, 100, 10, 50};
+ MetaRectangle temp;
+ MetaRectangle temp2;
+
+ meta_rectangle_intersect (&a, &b, &temp);
+ temp2 = meta_rect (100, 200, 10, 2);
+ g_assert (meta_rectangle_equal (&temp, &temp2));
+ g_assert (meta_rectangle_area (&temp) == 20);
+
+ meta_rectangle_intersect (&a, &c, &temp);
+ g_assert (meta_rectangle_area (&temp) == 0);
+
+ meta_rectangle_intersect (&a, &d, &temp);
+ g_assert (meta_rectangle_area (&temp) == 0);
+
+ meta_rectangle_intersect (&b, &d, &b);
+ g_assert (meta_rectangle_equal (&b, &b_intersect_d));
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+test_equal ()
+{
+ MetaRectangle a = {10, 12, 4, 18};
+ MetaRectangle b = a;
+ MetaRectangle c = {10, 12, 4, 19};
+ MetaRectangle d = {10, 12, 7, 18};
+ MetaRectangle e = {10, 62, 4, 18};
+ MetaRectangle f = {27, 12, 4, 18};
+
+ g_assert ( meta_rectangle_equal (&a, &b));
+ g_assert (!meta_rectangle_equal (&a, &c));
+ g_assert (!meta_rectangle_equal (&a, &d));
+ g_assert (!meta_rectangle_equal (&a, &e));
+ g_assert (!meta_rectangle_equal (&a, &f));
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+test_overlap_funcs ()
+{
+ MetaRectangle temp1, temp2;
+ int i;
+ for (i = 0; i < NUM_RANDOM_RUNS; i++)
+ {
+ get_random_rect (&temp1);
+ get_random_rect (&temp2);
+ g_assert (meta_rectangle_overlap (&temp1, &temp2) ==
+ (meta_rectangle_horiz_overlap (&temp1, &temp2) &&
+ meta_rectangle_vert_overlap (&temp1, &temp2)));
+ }
+
+ temp1 = meta_rect ( 0, 0, 10, 10);
+ temp2 = meta_rect (20, 0, 10, 5);
+ g_assert (!meta_rectangle_overlap (&temp1, &temp2));
+ g_assert (!meta_rectangle_horiz_overlap (&temp1, &temp2));
+ g_assert ( meta_rectangle_vert_overlap (&temp1, &temp2));
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+test_basic_fitting ()
+{
+ MetaRectangle temp1, temp2, temp3;
+ int i;
+ /* Four cases:
+ * case temp1 fits temp2 temp1 could fit temp2
+ * 1 Y Y
+ * 2 N Y
+ * 3 Y N
+ * 4 N N
+ * Of the four cases, case 3 is impossible. An alternate way of looking
+ * at this table is that either the middle column must be no, or the last
+ * column must be yes. So we test that. Also, we can repeat the test
+ * reversing temp1 and temp2.
+ */
+ for (i = 0; i < NUM_RANDOM_RUNS; i++)
+ {
+ get_random_rect (&temp1);
+ get_random_rect (&temp2);
+ g_assert (meta_rectangle_contains_rect (&temp1, &temp2) == FALSE ||
+ meta_rectangle_could_fit_rect (&temp1, &temp2) == TRUE);
+ g_assert (meta_rectangle_contains_rect (&temp2, &temp1) == FALSE ||
+ meta_rectangle_could_fit_rect (&temp2, &temp1) == TRUE);
+ }
+
+ temp1 = meta_rect ( 0, 0, 10, 10);
+ temp2 = meta_rect ( 5, 5, 5, 5);
+ temp3 = meta_rect ( 8, 2, 3, 7);
+ g_assert ( meta_rectangle_contains_rect (&temp1, &temp2));
+ g_assert (!meta_rectangle_contains_rect (&temp2, &temp1));
+ g_assert (!meta_rectangle_contains_rect (&temp1, &temp3));
+ g_assert ( meta_rectangle_could_fit_rect (&temp1, &temp3));
+ g_assert (!meta_rectangle_could_fit_rect (&temp3, &temp2));
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+free_strut_list (GSList *struts)
+{
+ GSList *tmp = struts;
+ while (tmp)
+ {
+ g_free (tmp->data);
+ tmp = tmp->next;
+ }
+ g_slist_free (struts);
+}
+
+static GSList*
+get_strut_list (int which)
+{
+ GSList *ans;
+ MetaDirection wc = 0; /* wc == who cares? ;-) */
+
+ ans = NULL;
+
+ g_assert (which >=0 && which <= 6);
+ switch (which)
+ {
+ case 0:
+ break;
+ case 1:
+ ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 1600, 20, wc));
+ ans = g_slist_prepend (ans, new_meta_strut ( 400, 1160, 1600, 40, wc));
+ break;
+ case 2:
+ ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 1600, 20, wc));
+ ans = g_slist_prepend (ans, new_meta_strut ( 800, 1100, 400, 100, wc));
+ ans = g_slist_prepend (ans, new_meta_strut ( 300, 1150, 150, 50, wc));
+ break;
+ case 3:
+ ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 1600, 20, wc));
+ ans = g_slist_prepend (ans, new_meta_strut ( 800, 1100, 400, 100, wc));
+ ans = g_slist_prepend (ans, new_meta_strut ( 300, 1150, 80, 50, wc));
+ ans = g_slist_prepend (ans, new_meta_strut ( 700, 525, 200, 150, wc));
+ break;
+ case 4:
+ ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 800, 1200, wc));
+ ans = g_slist_prepend (ans, new_meta_strut ( 800, 0, 1600, 20, wc));
+ break;
+ case 5:
+ ans = g_slist_prepend (ans, new_meta_strut ( 800, 0, 1600, 20, wc));
+ ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 800, 1200, wc));
+ ans = g_slist_prepend (ans, new_meta_strut ( 800, 10, 800, 1200, wc));
+ break;
+ case 6:
+ ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 1600, 40, wc));
+ ans = g_slist_prepend (ans, new_meta_strut ( 0, 0, 1600, 20, wc));
+ break;
+ }
+
+ return ans;
+}
+
+static GList*
+get_screen_region (int which)
+{
+ GList *ret;
+ GSList *struts;
+ MetaRectangle basic_rect;
+
+ basic_rect = meta_rect (0, 0, 1600, 1200);
+ ret = NULL;
+
+ struts = get_strut_list (which);
+ ret = meta_rectangle_get_minimal_spanning_set_for_region (&basic_rect, struts);
+ free_strut_list (struts);
+
+ return ret;
+}
+
+static GList*
+get_screen_edges (int which)
+{
+ GList *ret;
+ GSList *struts;
+ MetaRectangle basic_rect;
+
+ basic_rect = meta_rect (0, 0, 1600, 1200);
+ ret = NULL;
+
+ struts = get_strut_list (which);
+ ret = meta_rectangle_find_onscreen_edges (&basic_rect, struts);
+ free_strut_list (struts);
+
+ return ret;
+}
+
+static GList*
+get_xinerama_edges (int which_xinerama_set, int which_strut_set)
+{
+ GList *ret;
+ GSList *struts;
+ GList *xins;
+
+ xins = NULL;
+ g_assert (which_xinerama_set >=0 && which_xinerama_set <= 3);
+ switch (which_xinerama_set)
+ {
+ case 0:
+ xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 1600, 1200));
+ break;
+ case 1:
+ xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 800, 1200));
+ xins = g_list_prepend (xins, new_meta_rect (800, 0, 800, 1200));
+ break;
+ case 2:
+ xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 1600, 600));
+ xins = g_list_prepend (xins, new_meta_rect ( 0, 600, 1600, 600));
+ break;
+ case 3:
+ xins = g_list_prepend (xins, new_meta_rect ( 0, 0, 1600, 600));
+ xins = g_list_prepend (xins, new_meta_rect ( 0, 600, 800, 600));
+ xins = g_list_prepend (xins, new_meta_rect (800, 600, 800, 600));
+ break;
+ }
+
+ ret = NULL;
+
+ struts = get_strut_list (which_strut_set);
+ ret = meta_rectangle_find_nonintersected_xinerama_edges (xins, struts);
+
+ free_strut_list (struts);
+ meta_rectangle_free_list_and_elements (xins);
+
+ return ret;
+}
+
+#if 0
+static void
+test_merge_regions ()
+{
+ /* logarithmically distributed random number of struts (range?)
+ * logarithmically distributed random size of struts (up to screen size???)
+ * uniformly distributed location of center of struts (within screen)
+ * merge all regions that are possible
+ * print stats on problem setup
+ * number of (non-completely-occluded?) struts
+ * percentage of screen covered
+ * length of resulting non-minimal spanning set
+ * length of resulting minimal spanning set
+ * print stats on merged regions:
+ * number boxes merged
+ * number of those merges that were of the form A contains B
+ * number of those merges that were of the form A partially contains B
+ * number of those merges that were of the form A is adjacent to B
+ */
+
+ GList* region;
+ GList* compare;
+ int num_contains, num_merged, num_part_contains, num_adjacent;
+
+ num_contains = num_merged = num_part_contains = num_adjacent = 0;
+ compare = region = get_screen_region (2);
+ g_assert (region);
+
+ printf ("Merging stats:\n");
+ printf (" Length of initial list: %d\n", g_list_length (region));
+#ifdef PRINT_DEBUG
+ char rect1[RECT_LENGTH], rect2[RECT_LENGTH];
+ char region_list[(RECT_LENGTH + 2) * g_list_length (region)];
+ meta_rectangle_region_to_string (region, ", ", region_list);
+ printf (" Initial rectangles: %s\n", region_list);
+#endif
+
+ while (compare && compare->next)
+ {
+ MetaRectangle *a = compare->data;
+ GList *other = compare->next;
+
+ g_assert (a->width > 0 && a->height > 0);
+
+ while (other)
+ {
+ MetaRectangle *b = other->data;
+ GList *delete_me = NULL;
+
+ g_assert (b->width > 0 && b->height > 0);
+
+#ifdef PRINT_DEBUG
+ printf (" -- Comparing %s to %s --\n",
+ meta_rectangle_to_string (a, rect1),
+ meta_rectangle_to_string (b, rect2));
+#endif
+
+ /* If a contains b, just remove b */
+ if (meta_rectangle_contains_rect (a, b))
+ {
+ delete_me = other;
+ num_contains++;
+ num_merged++;
+ }
+ /* If b contains a, just remove a */
+ else if (meta_rectangle_contains_rect (a, b))
+ {
+ delete_me = compare;
+ num_contains++;
+ num_merged++;
+ }
+ /* If a and b might be mergeable horizontally */
+ else if (a->y == b->y && a->height == b->height)
+ {
+ /* If a and b overlap */
+ if (meta_rectangle_overlap (a, b))
+ {
+ int new_x = MIN (a->x, b->x);
+ a->width = MAX (a->x + a->width, b->x + b->width) - new_x;
+ a->x = new_x;
+ delete_me = other;
+ num_part_contains++;
+ num_merged++;
+ }
+ /* If a and b are adjacent */
+ else if (a->x + a->width == b->x || a->x == b->x + b->width)
+ {
+ int new_x = MIN (a->x, b->x);
+ a->width = MAX (a->x + a->width, b->x + b->width) - new_x;
+ a->x = new_x;
+ delete_me = other;
+ num_adjacent++;
+ num_merged++;
+ }
+ }
+ /* If a and b might be mergeable vertically */
+ else if (a->x == b->x && a->width == b->width)
+ {
+ /* If a and b overlap */
+ if (meta_rectangle_overlap (a, b))
+ {
+ int new_y = MIN (a->y, b->y);
+ a->height = MAX (a->y + a->height, b->y + b->height) - new_y;
+ a->y = new_y;
+ delete_me = other;
+ num_part_contains++;
+ num_merged++;
+ }
+ /* If a and b are adjacent */
+ else if (a->y + a->height == b->y || a->y == b->y + b->height)
+ {
+ int new_y = MIN (a->y, b->y);
+ a->height = MAX (a->y + a->height, b->y + b->height) - new_y;
+ a->y = new_y;
+ delete_me = other;
+ num_adjacent++;
+ num_merged++;
+ }
+ }
+
+ other = other->next;
+
+ /* Delete any rectangle in the list that is no longer wanted */
+ if (delete_me != NULL)
+ {
+#ifdef PRINT_DEBUG
+ MetaRectangle *bla = delete_me->data;
+ printf (" Deleting rect %s\n",
+ meta_rectangle_to_string (bla, rect1));
+#endif
+
+ /* Deleting the rect we're compare others to is a little tricker */
+ if (compare == delete_me)
+ {
+ compare = compare->next;
+ other = compare->next;
+ a = compare->data;
+ }
+
+ /* Okay, we can free it now */
+ g_free (delete_me->data);
+ region = g_list_delete_link (region, delete_me);
+ }
+
+#ifdef PRINT_DEBUG
+ char region_list[(RECT_LENGTH + 2) * g_list_length (region)];
+ meta_rectangle_region_to_string (region, ", ", region_list);
+ printf (" After comparison, new list is: %s\n", region_list);
+#endif
+ }
+
+ compare = compare->next;
+ }
+
+ printf (" Num rectangles contained in others : %d\n",
+ num_contains);
+ printf (" Num rectangles partially contained in others: %d\n",
+ num_part_contains);
+ printf (" Num rectangles adjacent to others : %d\n",
+ num_adjacent);
+ printf (" Num rectangles merged with others : %d\n",
+ num_merged);
+#ifdef PRINT_DEBUG
+ char region_list2[(RECT_LENGTH + 2) * g_list_length (region)];
+ meta_rectangle_region_to_string (region, ", ", region_list2);
+ printf (" Final rectangles: %s\n", region_list2);
+#endif
+
+ meta_rectangle_free_spanning_set (region);
+ region = NULL;
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+#endif
+
+static void
+verify_lists_are_equal (GList *code, GList *answer)
+{
+ int which = 0;
+
+ while (code && answer)
+ {
+ MetaRectangle *a = code->data;
+ MetaRectangle *b = answer->data;
+
+ if (a->x != b->x ||
+ a->y != b->y ||
+ a->width != b->width ||
+ a->height != b->height)
+ {
+ g_error ("%dth item in code answer answer lists do not match; "
+ "code rect: %d,%d + %d,%d; answer rect: %d,%d + %d,%d\n",
+ which,
+ a->x, a->y, a->width, a->height,
+ b->x, b->y, b->width, b->height);
+ }
+
+ code = code->next;
+ answer = answer->next;
+
+ which++;
+ }
+
+ /* Ought to be at the end of both lists; check if we aren't */
+ if (code)
+ {
+ MetaRectangle *tmp = code->data;
+ g_error ("code list longer than answer list by %d items; "
+ "first extra item: %d,%d +%d,%d\n",
+ g_list_length (code),
+ tmp->x, tmp->y, tmp->width, tmp->height);
+ }
+
+ if (answer)
+ {
+ MetaRectangle *tmp = answer->data;
+ g_error ("answer list longer than code list by %d items; "
+ "first extra item: %d,%d +%d,%d\n",
+ g_list_length (answer),
+ tmp->x, tmp->y, tmp->width, tmp->height);
+ }
+}
+
+static void
+test_regions_okay ()
+{
+ GList* region;
+ GList* tmp;
+
+ /*************************************************************/
+ /* Make sure test region 0 has the right spanning rectangles */
+ /*************************************************************/
+ region = get_screen_region (0);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_meta_rect (0, 0, 1600, 1200));
+ verify_lists_are_equal (region, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (region);
+
+ /*************************************************************/
+ /* Make sure test region 1 has the right spanning rectangles */
+ /*************************************************************/
+ region = get_screen_region (1);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_meta_rect (0, 20, 400, 1180));
+ tmp = g_list_prepend (tmp, new_meta_rect (0, 20, 1600, 1140));
+ verify_lists_are_equal (region, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (region);
+
+ /*************************************************************/
+ /* Make sure test region 2 has the right spanning rectangles */
+ /*************************************************************/
+ region = get_screen_region (2);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 300, 1180));
+ tmp = g_list_prepend (tmp, new_meta_rect ( 450, 20, 350, 1180));
+ tmp = g_list_prepend (tmp, new_meta_rect (1200, 20, 400, 1180));
+ tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 800, 1130));
+ tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 1600, 1080));
+ verify_lists_are_equal (region, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (region);
+
+ /*************************************************************/
+ /* Make sure test region 3 has the right spanning rectangles */
+ /*************************************************************/
+ region = get_screen_region (3);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_meta_rect ( 380, 675, 420, 525)); /* 220500 */
+ tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 300, 1180)); /* 354000 */
+ tmp = g_list_prepend (tmp, new_meta_rect ( 380, 20, 320, 1180)); /* 377600 */
+ tmp = g_list_prepend (tmp, new_meta_rect ( 0, 675, 800, 475)); /* 380000 */
+ tmp = g_list_prepend (tmp, new_meta_rect (1200, 20, 400, 1180)); /* 472000 */
+ tmp = g_list_prepend (tmp, new_meta_rect ( 0, 675, 1600, 425)); /* 680000 */
+ tmp = g_list_prepend (tmp, new_meta_rect ( 900, 20, 700, 1080)); /* 756000 */
+ tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 700, 1130)); /* 791000 */
+ tmp = g_list_prepend (tmp, new_meta_rect ( 0, 20, 1600, 505)); /* 808000 */
+#if 0
+ printf ("Got to here...\n");
+ char region_list[(RECT_LENGTH+2) * g_list_length (region)];
+ char tmp_list[ (RECT_LENGTH+2) * g_list_length (tmp)];
+ meta_rectangle_region_to_string (region, ", ", region_list);
+ meta_rectangle_region_to_string (region, ", ", tmp_list);
+ printf ("%s vs. %s\n", region_list, tmp_list);
+#endif
+ verify_lists_are_equal (region, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (region);
+
+ /*************************************************************/
+ /* Make sure test region 4 has the right spanning rectangles */
+ /*************************************************************/
+ region = get_screen_region (4);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_meta_rect ( 800, 20, 800, 1180));
+ verify_lists_are_equal (region, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (region);
+
+ /*************************************************************/
+ /* Make sure test region 5 has the right spanning rectangles */
+ /*************************************************************/
+ printf ("The next test intentionally causes a warning, "
+ "but it can be ignored.\n");
+ region = get_screen_region (5);
+ verify_lists_are_equal (region, NULL);
+
+ /* FIXME: Still to do:
+ * - Create random struts and check the regions somehow
+ */
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+test_region_fitting ()
+{
+ GList* region;
+ MetaRectangle rect;
+
+ /* See test_basic_fitting() for how/why these automated random tests work */
+ int i;
+ region = get_screen_region (3);
+ for (i = 0; i < NUM_RANDOM_RUNS; i++)
+ {
+ get_random_rect (&rect);
+ g_assert (meta_rectangle_contained_in_region (region, &rect) == FALSE ||
+ meta_rectangle_could_fit_in_region (region, &rect) == TRUE);
+ }
+ meta_rectangle_free_list_and_elements (region);
+
+ /* Do some manual tests too */
+ region = get_screen_region (1);
+
+ rect = meta_rect (50, 50, 400, 400);
+ g_assert (meta_rectangle_could_fit_in_region (region, &rect));
+ g_assert (meta_rectangle_contained_in_region (region, &rect));
+
+ rect = meta_rect (250, 0, 500, 1150);
+ g_assert (!meta_rectangle_could_fit_in_region (region, &rect));
+ g_assert (!meta_rectangle_contained_in_region (region, &rect));
+
+ rect = meta_rect (250, 0, 400, 400);
+ g_assert (meta_rectangle_could_fit_in_region (region, &rect));
+ g_assert (!meta_rectangle_contained_in_region (region, &rect));
+
+ meta_rectangle_free_list_and_elements (region);
+
+ region = get_screen_region (2);
+ rect = meta_rect (1000, 50, 600, 1100);
+ g_assert (meta_rectangle_could_fit_in_region (region, &rect));
+ g_assert (!meta_rectangle_contained_in_region (region, &rect));
+
+ meta_rectangle_free_list_and_elements (region);
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+test_clamping_to_region ()
+{
+ GList* region;
+ MetaRectangle rect;
+ MetaRectangle min_size;
+ FixedDirections fixed_directions;
+ int i;
+
+ min_size.height = min_size.width = 1;
+ fixed_directions = 0;
+
+ region = get_screen_region (3);
+ for (i = 0; i < NUM_RANDOM_RUNS; i++)
+ {
+ MetaRectangle temp;
+ get_random_rect (&rect);
+ temp = rect;
+ meta_rectangle_clamp_to_fit_into_region (region,
+ fixed_directions,
+ &rect,
+ &min_size);
+ g_assert (meta_rectangle_could_fit_in_region (region, &rect) == TRUE);
+ g_assert (rect.x == temp.x && rect.y == temp.y);
+ }
+ meta_rectangle_free_list_and_elements (region);
+
+ /* Do some manual tests too */
+ region = get_screen_region (1);
+
+ rect = meta_rect (50, 50, 10000, 10000);
+ meta_rectangle_clamp_to_fit_into_region (region,
+ fixed_directions,
+ &rect,
+ &min_size);
+ g_assert (rect.width == 1600 && rect.height == 1140);
+
+ rect = meta_rect (275, -50, 410, 10000);
+ meta_rectangle_clamp_to_fit_into_region (region,
+ fixed_directions,
+ &rect,
+ &min_size);
+ g_assert (rect.width == 400 && rect.height == 1180);
+
+ rect = meta_rect (50, 50, 10000, 10000);
+ min_size.height = 1170;
+ meta_rectangle_clamp_to_fit_into_region (region,
+ fixed_directions,
+ &rect,
+ &min_size);
+ g_assert (rect.width == 400 && rect.height == 1180);
+
+ printf ("The next test intentionally causes a warning, "
+ "but it can be ignored.\n");
+ rect = meta_rect (50, 50, 10000, 10000);
+ min_size.width = 600; min_size.height = 1170;
+ meta_rectangle_clamp_to_fit_into_region (region,
+ fixed_directions,
+ &rect,
+ &min_size);
+ g_assert (rect.width == 600 && rect.height == 1170);
+
+ rect = meta_rect (350, 50, 100, 1100);
+ min_size.width = 1; min_size.height = 1;
+ fixed_directions = FIXED_DIRECTION_X;
+ meta_rectangle_clamp_to_fit_into_region (region,
+ fixed_directions,
+ &rect,
+ &min_size);
+ g_assert (rect.width == 100 && rect.height == 1100);
+
+ rect = meta_rect (300, 70, 500, 1100);
+ min_size.width = 1; min_size.height = 1;
+ fixed_directions = FIXED_DIRECTION_Y;
+ meta_rectangle_clamp_to_fit_into_region (region,
+ fixed_directions,
+ &rect,
+ &min_size);
+ g_assert (rect.width == 400 && rect.height == 1100);
+
+ printf ("The next test intentionally causes a warning, "
+ "but it can be ignored.\n");
+ rect = meta_rect (300, 70, 999999, 999999);
+ min_size.width = 100; min_size.height = 200;
+ fixed_directions = FIXED_DIRECTION_Y;
+ meta_rectangle_clamp_to_fit_into_region (region,
+ fixed_directions,
+ &rect,
+ &min_size);
+ g_assert (rect.width == 100 && rect.height == 999999);
+
+ meta_rectangle_free_list_and_elements (region);
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static gboolean
+rect_overlaps_region (const GList *spanning_rects,
+ const MetaRectangle *rect)
+{
+ /* FIXME: Should I move this to boxes.[ch]? */
+ const GList *temp;
+ gboolean overlaps;
+
+ temp = spanning_rects;
+ overlaps = FALSE;
+ while (!overlaps && temp != NULL)
+ {
+ overlaps = overlaps || meta_rectangle_overlap (temp->data, rect);
+ temp = temp->next;
+ }
+
+ return overlaps;
+}
+
+gboolean time_to_print = FALSE;
+
+static void
+test_clipping_to_region ()
+{
+ GList* region;
+ MetaRectangle rect, temp;
+ FixedDirections fixed_directions = 0;
+ int i;
+
+ region = get_screen_region (3);
+ for (i = 0; i < NUM_RANDOM_RUNS; i++)
+ {
+ get_random_rect (&rect);
+ if (rect_overlaps_region (region, &rect))
+ {
+ meta_rectangle_clip_to_region (region, 0, &rect);
+ g_assert (meta_rectangle_contained_in_region (region, &rect) == TRUE);
+ }
+ }
+ meta_rectangle_free_list_and_elements (region);
+
+ /* Do some manual tests too */
+ region = get_screen_region (2);
+
+ rect = meta_rect (-50, -10, 10000, 10000);
+ meta_rectangle_clip_to_region (region,
+ fixed_directions,
+ &rect);
+ g_assert (meta_rectangle_equal (region->data, &rect));
+
+ rect = meta_rect (300, 1000, 400, 200);
+ temp = meta_rect (300, 1000, 400, 150);
+ meta_rectangle_clip_to_region (region,
+ fixed_directions,
+ &rect);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect (400, 1000, 300, 200);
+ temp = meta_rect (450, 1000, 250, 200);
+ meta_rectangle_clip_to_region (region,
+ fixed_directions,
+ &rect);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect (400, 1000, 300, 200);
+ temp = meta_rect (400, 1000, 300, 150);
+ meta_rectangle_clip_to_region (region,
+ FIXED_DIRECTION_X,
+ &rect);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect (400, 1000, 300, 200);
+ temp = meta_rect (400, 1000, 300, 150);
+ meta_rectangle_clip_to_region (region,
+ FIXED_DIRECTION_X,
+ &rect);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ meta_rectangle_free_list_and_elements (region);
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+test_shoving_into_region ()
+{
+ GList* region;
+ MetaRectangle rect, temp;
+ FixedDirections fixed_directions = 0;
+ int i;
+
+ region = get_screen_region (3);
+ for (i = 0; i < NUM_RANDOM_RUNS; i++)
+ {
+ get_random_rect (&rect);
+ if (meta_rectangle_could_fit_in_region (region, &rect))
+ {
+ meta_rectangle_shove_into_region (region, 0, &rect);
+ g_assert (meta_rectangle_contained_in_region (region, &rect));
+ }
+ }
+ meta_rectangle_free_list_and_elements (region);
+
+ /* Do some manual tests too */
+ region = get_screen_region (2);
+
+ rect = meta_rect (300, 1000, 400, 200);
+ temp = meta_rect (300, 950, 400, 200);
+ meta_rectangle_shove_into_region (region,
+ fixed_directions,
+ &rect);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect (425, 1000, 300, 200);
+ temp = meta_rect (450, 1000, 300, 200);
+ meta_rectangle_shove_into_region (region,
+ fixed_directions,
+ &rect);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect (425, 1000, 300, 200);
+ temp = meta_rect (425, 950, 300, 200);
+ meta_rectangle_shove_into_region (region,
+ FIXED_DIRECTION_X,
+ &rect);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect ( 300, 1000, 400, 200);
+ temp = meta_rect (1200, 1000, 400, 200);
+ meta_rectangle_shove_into_region (region,
+ FIXED_DIRECTION_Y,
+ &rect);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect ( 800, 1150, 400, 50); /* Completely "offscreen" :) */
+ temp = meta_rect ( 800, 1050, 400, 50);
+ meta_rectangle_shove_into_region (region,
+ 0,
+ &rect);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect (-1000, 0, 400, 150); /* Offscreen in 2 directions */
+ temp = meta_rect ( 0, 20, 400, 150);
+ meta_rectangle_shove_into_region (region,
+ 0,
+ &rect);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ meta_rectangle_free_list_and_elements (region);
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+verify_edge_lists_are_equal (GList *code, GList *answer)
+{
+ int which = 0;
+
+ while (code && answer)
+ {
+ MetaEdge *a = code->data;
+ MetaEdge *b = answer->data;
+
+ if (!meta_rectangle_equal (&a->rect, &b->rect) ||
+ a->side_type != b->side_type ||
+ a->edge_type != b->edge_type)
+ {
+ g_error ("%dth item in code answer answer lists do not match; "
+ "code rect: %d,%d + %d,%d; answer rect: %d,%d + %d,%d\n",
+ which,
+ a->rect.x, a->rect.y, a->rect.width, a->rect.height,
+ b->rect.x, b->rect.y, b->rect.width, b->rect.height);
+ }
+
+ code = code->next;
+ answer = answer->next;
+
+ which++;
+ }
+
+ /* Ought to be at the end of both lists; check if we aren't */
+ if (code)
+ {
+ MetaEdge *tmp = code->data;
+ g_error ("code list longer than answer list by %d items; "
+ "first extra item rect: %d,%d +%d,%d\n",
+ g_list_length (code),
+ tmp->rect.x, tmp->rect.y, tmp->rect.width, tmp->rect.height);
+ }
+
+ if (answer)
+ {
+ MetaEdge *tmp = answer->data;
+ g_error ("answer list longer than code list by %d items; "
+ "first extra item rect: %d,%d +%d,%d\n",
+ g_list_length (answer),
+ tmp->rect.x, tmp->rect.y, tmp->rect.width, tmp->rect.height);
+ }
+}
+
+static void
+test_find_onscreen_edges ()
+{
+ GList* edges;
+ GList* tmp;
+
+ int left = META_DIRECTION_LEFT;
+ int right = META_DIRECTION_RIGHT;
+ int top = META_DIRECTION_TOP;
+ int bottom = META_DIRECTION_BOTTOM;
+
+ /*************************************************/
+ /* Make sure test region 0 has the correct edges */
+ /*************************************************/
+ edges = get_screen_edges (0);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 1600, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 0, 1600, 0, top));
+ tmp = g_list_prepend (tmp, new_screen_edge (1600, 0, 0, 1200, right));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 0, 0, 1200, left));
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ /*************************************************/
+ /* Make sure test region 1 has the correct edges */
+ /*************************************************/
+ edges = get_screen_edges (1);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 400, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 400, 1160, 1200, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 1600, 0, top));
+ tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1140, right));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 400, 1160, 0, 40, right));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 0, 1180, left));
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ /*************************************************/
+ /* Make sure test region 2 has the correct edges */
+ /*************************************************/
+ edges = get_screen_edges (2);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_screen_edge (1200, 1200, 400, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 450, 1200, 350, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 300, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 150, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 400, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 1600, 0, top));
+ tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1180, right));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 0, 100, right));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 0, 50, right));
+ tmp = g_list_prepend (tmp, new_screen_edge (1200, 1100, 0, 100, left));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 450, 1150, 0, 50, left));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 0, 1180, left));
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ /*************************************************/
+ /* Make sure test region 3 has the correct edges */
+ /*************************************************/
+ edges = get_screen_edges (3);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_screen_edge (1200, 1200, 400, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 380, 1200, 420, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 300, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 80, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 400, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 700, 525, 200, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 700, 675, 200, 0, top));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 1600, 0, top));
+ tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1180, right));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 0, 100, right));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 700, 525, 0, 150, right));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 0, 50, right));
+ tmp = g_list_prepend (tmp, new_screen_edge (1200, 1100, 0, 100, left));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 900, 525, 0, 150, left));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 380, 1150, 0, 50, left));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 20, 0, 1180, left));
+
+#if 0
+ #define FUDGE 50 /* number of edges */
+ char big_buffer1[(EDGE_LENGTH+2)*FUDGE], big_buffer2[(EDGE_LENGTH+2)*FUDGE];
+ meta_rectangle_edge_list_to_string (edges, "\n ", big_buffer1);
+ meta_rectangle_edge_list_to_string (tmp, "\n ", big_buffer2);
+ printf("Generated edge list:\n %s\nComparison edges list:\n %s\n",
+ big_buffer1, big_buffer2);
+#endif
+
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ /*************************************************/
+ /* Make sure test region 4 has the correct edges */
+ /*************************************************/
+ edges = get_screen_edges (4);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1200, 800, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 800, 20, 800, 0, top));
+ tmp = g_list_prepend (tmp, new_screen_edge (1600, 20, 0, 1180, right));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 800, 20, 0, 1180, left));
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ /*************************************************/
+ /* Make sure test region 5 has the correct edges */
+ /*************************************************/
+ edges = get_screen_edges (5);
+ tmp = NULL;
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ /*************************************************/
+ /* Make sure test region 6 has the correct edges */
+ /*************************************************/
+ edges = get_screen_edges (6);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 1200, 1600, 0, bottom));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 40, 1600, 0, top));
+ tmp = g_list_prepend (tmp, new_screen_edge (1600, 40, 0, 1160, right));
+ tmp = g_list_prepend (tmp, new_screen_edge ( 0, 40, 0, 1160, left));
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+test_find_nonintersected_xinerama_edges ()
+{
+ GList* edges;
+ GList* tmp;
+
+ int left = META_DIRECTION_LEFT;
+ int right = META_DIRECTION_RIGHT;
+ int top = META_DIRECTION_TOP;
+ int bottom = META_DIRECTION_BOTTOM;
+
+ /*************************************************************************/
+ /* Make sure test xinerama set 0 for with region 0 has the correct edges */
+ /*************************************************************************/
+ edges = get_xinerama_edges (0, 0);
+ tmp = NULL;
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ /*************************************************************************/
+ /* Make sure test xinerama set 2 for with region 1 has the correct edges */
+ /*************************************************************************/
+ edges = get_xinerama_edges (2, 1);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 1600, 0, bottom));
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 1600, 0, top));
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ /*************************************************************************/
+ /* Make sure test xinerama set 1 for with region 2 has the correct edges */
+ /*************************************************************************/
+ edges = get_xinerama_edges (1, 2);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 20, 0, 1080, right));
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 20, 0, 1180, left));
+#if 0
+ #define FUDGE 50
+ char big_buffer1[(EDGE_LENGTH+2)*FUDGE], big_buffer2[(EDGE_LENGTH+2)*FUDGE];
+ meta_rectangle_edge_list_to_string (edges, "\n ", big_buffer1);
+ meta_rectangle_edge_list_to_string (tmp, "\n ", big_buffer2);
+ printf("Generated edge list:\n %s\nComparison edges list:\n %s\n",
+ big_buffer1, big_buffer2);
+#endif
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ /*************************************************************************/
+ /* Make sure test xinerama set 3 for with region 3 has the correct edges */
+ /*************************************************************************/
+ edges = get_xinerama_edges (3, 3);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 900, 600, 700, 0, bottom));
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 700, 0, bottom));
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 900, 600, 700, 0, top));
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 0, 600, 700, 0, top));
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 675, 0, 425, right));
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 675, 0, 525, left));
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ /*************************************************************************/
+ /* Make sure test xinerama set 3 for with region 4 has the correct edges */
+ /*************************************************************************/
+ edges = get_xinerama_edges (3, 4);
+ tmp = NULL;
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 600, 800, 0, bottom));
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 600, 800, 0, top));
+ tmp = g_list_prepend (tmp, new_xinerama_edge ( 800, 600, 0, 600, right));
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ /*************************************************************************/
+ /* Make sure test xinerama set 3 for with region 5has the correct edges */
+ /*************************************************************************/
+ edges = get_xinerama_edges (3, 5);
+ tmp = NULL;
+ verify_edge_lists_are_equal (edges, tmp);
+ meta_rectangle_free_list_and_elements (tmp);
+ meta_rectangle_free_list_and_elements (edges);
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+test_gravity_resize ()
+{
+ MetaRectangle oldrect, rect, temp;
+
+ rect.x = -500; /* Some random amount not equal to oldrect.x to ensure that
+ * the resize is done with respect to oldrect instead of rect
+ */
+ oldrect = meta_rect ( 50, 300, 250, 400);
+ temp = meta_rect ( 50, 300, 20, 5);
+ meta_rectangle_resize_with_gravity (&oldrect,
+ &rect,
+ NorthWestGravity,
+ 20,
+ 5);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect ( 50, 300, 250, 400);
+ temp = meta_rect (165, 300, 20, 5);
+ meta_rectangle_resize_with_gravity (&rect,
+ &rect,
+ NorthGravity,
+ 20,
+ 5);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect ( 50, 300, 250, 400);
+ temp = meta_rect (280, 300, 20, 5);
+ meta_rectangle_resize_with_gravity (&rect,
+ &rect,
+ NorthEastGravity,
+ 20,
+ 5);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect ( 50, 300, 250, 400);
+ temp = meta_rect ( 50, 695, 50, 5);
+ meta_rectangle_resize_with_gravity (&rect,
+ &rect,
+ SouthWestGravity,
+ 50,
+ 5);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect ( 50, 300, 250, 400);
+ temp = meta_rect (150, 695, 50, 5);
+ meta_rectangle_resize_with_gravity (&rect,
+ &rect,
+ SouthGravity,
+ 50,
+ 5);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect ( 50, 300, 250, 400);
+ temp = meta_rect (250, 695, 50, 5);
+ meta_rectangle_resize_with_gravity (&rect,
+ &rect,
+ SouthEastGravity,
+ 50,
+ 5);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect (167, 738, 237, 843);
+ temp = meta_rect (167, 1113, 832, 93);
+ meta_rectangle_resize_with_gravity (&rect,
+ &rect,
+ WestGravity,
+ 832,
+ 93);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect ( 167, 738, 237, 843);
+ temp = meta_rect (-131, 1113, 833, 93);
+ meta_rectangle_resize_with_gravity (&rect,
+ &rect,
+ CenterGravity,
+ 832,
+ 93);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect (300, 1000, 400, 200);
+ temp = meta_rect (270, 994, 430, 212);
+ meta_rectangle_resize_with_gravity (&rect,
+ &rect,
+ EastGravity,
+ 430,
+ 211);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ rect = meta_rect (300, 1000, 400, 200);
+ temp = meta_rect (300, 1000, 430, 211);
+ meta_rectangle_resize_with_gravity (&rect,
+ &rect,
+ StaticGravity,
+ 430,
+ 211);
+ g_assert (meta_rectangle_equal (&rect, &temp));
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+static void
+test_find_closest_point_to_line ()
+{
+ double x1, y1, x2, y2, px, py, rx, ry;
+ double answer_x, answer_y;
+
+ x1 = 3.0; y1 = 49.0;
+ x2 = 2.0; y2 = - 1.0;
+ px = -2.6; py = 19.1;
+ answer_x = 2.4; answer_y = 19;
+ meta_rectangle_find_linepoint_closest_to_point (x1, y1,
+ x2, y2,
+ px, py,
+ &rx, &ry);
+ g_assert (rx == answer_x && ry == answer_y);
+
+ /* Special test for x1 == x2, so that slop of line is infinite */
+ x1 = 3.0; y1 = 49.0;
+ x2 = 3.0; y2 = - 1.0;
+ px = -2.6; py = 19.1;
+ answer_x = 3.0; answer_y = 19.1;
+ meta_rectangle_find_linepoint_closest_to_point (x1, y1,
+ x2, y2,
+ px, py,
+ &rx, &ry);
+ g_assert (rx == answer_x && ry == answer_y);
+
+ /* Special test for y1 == y2, so perp line has slope of infinity */
+ x1 = 3.14; y1 = 7.0;
+ x2 = 2.718; y2 = 7.0;
+ px = -2.6; py = 19.1;
+ answer_x = -2.6; answer_y = 7;
+ meta_rectangle_find_linepoint_closest_to_point (x1, y1,
+ x2, y2,
+ px, py,
+ &rx, &ry);
+ g_assert (rx == answer_x && ry == answer_y);
+
+ /* Test when we the point we want to be closest to is actually on the line */
+ x1 = 3.0; y1 = 49.0;
+ x2 = 2.0; y2 = - 1.0;
+ px = 2.4; py = 19.0;
+ answer_x = 2.4; answer_y = 19;
+ meta_rectangle_find_linepoint_closest_to_point (x1, y1,
+ x2, y2,
+ px, py,
+ &rx, &ry);
+ g_assert (rx == answer_x && ry == answer_y);
+
+ printf ("%s passed.\n", G_STRFUNC);
+}
+
+int
+main()
+{
+ init_random_ness ();
+ test_area ();
+ test_intersect ();
+ test_equal ();
+ test_overlap_funcs ();
+ test_basic_fitting ();
+
+ test_regions_okay ();
+ test_region_fitting ();
+
+ test_clamping_to_region ();
+ test_clipping_to_region ();
+ test_shoving_into_region ();
+
+ /* And now the functions dealing with edges more than boxes */
+ test_find_onscreen_edges ();
+ test_find_nonintersected_xinerama_edges ();
+
+ /* And now the misfit functions that don't quite fit in anywhere else... */
+ test_gravity_resize ();
+ test_find_closest_point_to_line ();
+
+ printf ("All tests passed.\n");
+ return 0;
+}
diff --git a/src/core/util.c b/src/core/util.c
new file mode 100644
index 00000000..6dd27f8e
--- /dev/null
+++ b/src/core/util.c
@@ -0,0 +1,641 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco utilities */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+#define _POSIX_C_SOURCE 200112L /* for fdopen() */
+
+#include <config.h>
+#include "util.h"
+#include "main.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <X11/Xlib.h> /* must explicitly be included for Solaris; #326746 */
+#include <X11/Xutil.h> /* Just for the definition of the various gravities */
+
+#ifdef HAVE_BACKTRACE
+#include <execinfo.h>
+void
+meta_print_backtrace (void)
+{
+ void *bt[500];
+ int bt_size;
+ int i;
+ char **syms;
+
+ bt_size = backtrace (bt, 500);
+
+ syms = backtrace_symbols (bt, bt_size);
+
+ i = 0;
+ while (i < bt_size)
+ {
+ meta_verbose (" %s\n", syms[i]);
+ ++i;
+ }
+
+ free (syms);
+}
+#else
+void
+meta_print_backtrace (void)
+{
+ meta_verbose ("Not compiled with backtrace support\n");
+}
+#endif
+
+static gboolean is_verbose = FALSE;
+static gboolean is_debugging = FALSE;
+static gboolean replace_current = FALSE;
+static int no_prefix = 0;
+
+#ifdef WITH_VERBOSE_MODE
+static FILE* logfile = NULL;
+
+static void
+ensure_logfile (void)
+{
+ if (logfile == NULL && g_getenv ("MARCO_USE_LOGFILE"))
+ {
+ char *filename = NULL;
+ char *tmpl;
+ int fd;
+ GError *err;
+
+ tmpl = g_strdup_printf ("marco-%d-debug-log-XXXXXX",
+ (int) getpid ());
+
+ err = NULL;
+ fd = g_file_open_tmp (tmpl,
+ &filename,
+ &err);
+
+ g_free (tmpl);
+
+ if (err != NULL)
+ {
+ meta_warning (_("Failed to open debug log: %s\n"),
+ err->message);
+ g_error_free (err);
+ return;
+ }
+
+ logfile = fdopen (fd, "w");
+
+ if (logfile == NULL)
+ {
+ meta_warning (_("Failed to fdopen() log file %s: %s\n"),
+ filename, strerror (errno));
+ close (fd);
+ }
+ else
+ {
+ g_printerr (_("Opened log file %s\n"), filename);
+ }
+
+ g_free (filename);
+ }
+}
+#endif
+
+gboolean
+meta_is_verbose (void)
+{
+ return is_verbose;
+}
+
+void
+meta_set_verbose (gboolean setting)
+{
+#ifndef WITH_VERBOSE_MODE
+ if (setting)
+ meta_fatal (_("Marco was compiled without support for verbose mode\n"));
+#else
+ if (setting)
+ ensure_logfile ();
+#endif
+
+ is_verbose = setting;
+}
+
+gboolean
+meta_is_debugging (void)
+{
+ return is_debugging;
+}
+
+void
+meta_set_debugging (gboolean setting)
+{
+#ifdef WITH_VERBOSE_MODE
+ if (setting)
+ ensure_logfile ();
+#endif
+
+ is_debugging = setting;
+}
+
+gboolean
+meta_get_replace_current_wm (void)
+{
+ return replace_current;
+}
+
+void
+meta_set_replace_current_wm (gboolean setting)
+{
+ replace_current = setting;
+}
+
+char *
+meta_g_utf8_strndup (const gchar *src,
+ gsize n)
+{
+ const gchar *s = src;
+ while (n && *s)
+ {
+ s = g_utf8_next_char (s);
+ n--;
+ }
+
+ return g_strndup (src, s - src);
+}
+
+static int
+utf8_fputs (const char *str,
+ FILE *f)
+{
+ char *l;
+ int retval;
+
+ l = g_locale_from_utf8 (str, -1, NULL, NULL, NULL);
+
+ if (l == NULL)
+ retval = fputs (str, f); /* just print it anyway, better than nothing */
+ else
+ retval = fputs (l, f);
+
+ g_free (l);
+
+ return retval;
+}
+
+void
+meta_free_gslist_and_elements (GSList *list_to_deep_free)
+{
+ g_slist_foreach (list_to_deep_free,
+ (void (*)(gpointer,gpointer))&g_free, /* ew, for ugly */
+ NULL);
+ g_slist_free (list_to_deep_free);
+}
+
+#ifdef WITH_VERBOSE_MODE
+void
+meta_debug_spew_real (const char *format, ...)
+{
+ va_list args;
+ gchar *str;
+ FILE *out;
+
+ g_return_if_fail (format != NULL);
+
+ if (!is_debugging)
+ return;
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ out = logfile ? logfile : stderr;
+
+ if (no_prefix == 0)
+ utf8_fputs (_("Window manager: "), out);
+ utf8_fputs (str, out);
+
+ fflush (out);
+
+ g_free (str);
+}
+#endif /* WITH_VERBOSE_MODE */
+
+#ifdef WITH_VERBOSE_MODE
+void
+meta_verbose_real (const char *format, ...)
+{
+ va_list args;
+ gchar *str;
+ FILE *out;
+
+ g_return_if_fail (format != NULL);
+
+ if (!is_verbose)
+ return;
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ out = logfile ? logfile : stderr;
+
+ if (no_prefix == 0)
+ utf8_fputs ("Window manager: ", out);
+ utf8_fputs (str, out);
+
+ fflush (out);
+
+ g_free (str);
+}
+#endif /* WITH_VERBOSE_MODE */
+
+#ifdef WITH_VERBOSE_MODE
+static const char*
+topic_name (MetaDebugTopic topic)
+{
+ switch (topic)
+ {
+ case META_DEBUG_FOCUS:
+ return "FOCUS";
+ case META_DEBUG_WORKAREA:
+ return "WORKAREA";
+ case META_DEBUG_STACK:
+ return "STACK";
+ case META_DEBUG_THEMES:
+ return "THEMES";
+ case META_DEBUG_SM:
+ return "SM";
+ case META_DEBUG_EVENTS:
+ return "EVENTS";
+ case META_DEBUG_WINDOW_STATE:
+ return "WINDOW_STATE";
+ case META_DEBUG_WINDOW_OPS:
+ return "WINDOW_OPS";
+ case META_DEBUG_PLACEMENT:
+ return "PLACEMENT";
+ case META_DEBUG_GEOMETRY:
+ return "GEOMETRY";
+ case META_DEBUG_PING:
+ return "PING";
+ case META_DEBUG_XINERAMA:
+ return "XINERAMA";
+ case META_DEBUG_KEYBINDINGS:
+ return "KEYBINDINGS";
+ case META_DEBUG_SYNC:
+ return "SYNC";
+ case META_DEBUG_ERRORS:
+ return "ERRORS";
+ case META_DEBUG_STARTUP:
+ return "STARTUP";
+ case META_DEBUG_PREFS:
+ return "PREFS";
+ case META_DEBUG_GROUPS:
+ return "GROUPS";
+ case META_DEBUG_RESIZING:
+ return "RESIZING";
+ case META_DEBUG_SHAPES:
+ return "SHAPES";
+ case META_DEBUG_COMPOSITOR:
+ return "COMPOSITOR";
+ case META_DEBUG_EDGE_RESISTANCE:
+ return "EDGE_RESISTANCE";
+ }
+
+ return "WM";
+}
+
+static int sync_count = 0;
+
+void
+meta_topic_real (MetaDebugTopic topic,
+ const char *format,
+ ...)
+{
+ va_list args;
+ gchar *str;
+ FILE *out;
+
+ g_return_if_fail (format != NULL);
+
+ if (!is_verbose)
+ return;
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ out = logfile ? logfile : stderr;
+
+ if (no_prefix == 0)
+ fprintf (out, "%s: ", topic_name (topic));
+
+ if (topic == META_DEBUG_SYNC)
+ {
+ ++sync_count;
+ fprintf (out, "%d: ", sync_count);
+ }
+
+ utf8_fputs (str, out);
+
+ fflush (out);
+
+ g_free (str);
+}
+#endif /* WITH_VERBOSE_MODE */
+
+void
+meta_bug (const char *format, ...)
+{
+ va_list args;
+ gchar *str;
+ FILE *out;
+
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+#ifdef WITH_VERBOSE_MODE
+ out = logfile ? logfile : stderr;
+#else
+ out = stderr;
+#endif
+
+ if (no_prefix == 0)
+ utf8_fputs (_("Bug in window manager: "), out);
+ utf8_fputs (str, out);
+
+ fflush (out);
+
+ g_free (str);
+
+ meta_print_backtrace ();
+
+ /* stop us in a debugger */
+ abort ();
+}
+
+void
+meta_warning (const char *format, ...)
+{
+ va_list args;
+ gchar *str;
+ FILE *out;
+
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+#ifdef WITH_VERBOSE_MODE
+ out = logfile ? logfile : stderr;
+#else
+ out = stderr;
+#endif
+
+ if (no_prefix == 0)
+ utf8_fputs (_("Window manager warning: "), out);
+ utf8_fputs (str, out);
+
+ fflush (out);
+
+ g_free (str);
+}
+
+void
+meta_fatal (const char *format, ...)
+{
+ va_list args;
+ gchar *str;
+ FILE *out;
+
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+#ifdef WITH_VERBOSE_MODE
+ out = logfile ? logfile : stderr;
+#else
+ out = stderr;
+#endif
+
+ if (no_prefix == 0)
+ utf8_fputs (_("Window manager error: "), out);
+ utf8_fputs (str, out);
+
+ fflush (out);
+
+ g_free (str);
+
+ meta_exit (META_EXIT_ERROR);
+}
+
+void
+meta_push_no_msg_prefix (void)
+{
+ ++no_prefix;
+}
+
+void
+meta_pop_no_msg_prefix (void)
+{
+ g_return_if_fail (no_prefix > 0);
+
+ --no_prefix;
+}
+
+void
+meta_exit (MetaExitCode code)
+{
+
+ exit (code);
+}
+
+gint
+meta_unsigned_long_equal (gconstpointer v1,
+ gconstpointer v2)
+{
+ return *((const gulong*) v1) == *((const gulong*) v2);
+}
+
+guint
+meta_unsigned_long_hash (gconstpointer v)
+{
+ gulong val = * (const gulong *) v;
+
+ /* I'm not sure this works so well. */
+#if GLIB_SIZEOF_LONG > 4
+ return (guint) (val ^ (val >> 32));
+#else
+ return val;
+#endif
+}
+
+const char*
+meta_gravity_to_string (int gravity)
+{
+ switch (gravity)
+ {
+ case NorthWestGravity:
+ return "NorthWestGravity";
+ break;
+ case NorthGravity:
+ return "NorthGravity";
+ break;
+ case NorthEastGravity:
+ return "NorthEastGravity";
+ break;
+ case WestGravity:
+ return "WestGravity";
+ break;
+ case CenterGravity:
+ return "CenterGravity";
+ break;
+ case EastGravity:
+ return "EastGravity";
+ break;
+ case SouthWestGravity:
+ return "SouthWestGravity";
+ break;
+ case SouthGravity:
+ return "SouthGravity";
+ break;
+ case SouthEastGravity:
+ return "SouthEastGravity";
+ break;
+ case StaticGravity:
+ return "StaticGravity";
+ break;
+ default:
+ return "NorthWestGravity";
+ break;
+ }
+}
+
+GPid
+meta_show_dialog (const char *type,
+ const char *message,
+ const char *timeout,
+ const gint screen_number,
+ const char *ok_text,
+ const char *cancel_text,
+ const int transient_for,
+ GSList *columns,
+ GSList *entries)
+{
+ GError *error = NULL;
+ char *screen_number_text = g_strdup_printf("%d", screen_number);
+ GSList *tmp;
+ int i=0;
+ GPid child_pid;
+ const char **argvl = g_malloc(sizeof (char*) *
+ (17 +
+ g_slist_length (columns)*2 +
+ g_slist_length (entries)));
+
+ argvl[i++] = "matedialog";
+ argvl[i++] = type;
+ argvl[i++] = "--screen";
+ argvl[i++] = screen_number_text;
+ argvl[i++] = "--class";
+ argvl[i++] = "marco-dialog";
+ argvl[i++] = "--title";
+ /* Translators: This is the title used on dialog boxes */
+ argvl[i++] = _("Marco");
+ argvl[i++] = "--text";
+ argvl[i++] = message;
+
+ if (timeout)
+ {
+ argvl[i++] = "--timeout";
+ argvl[i++] = timeout;
+ }
+
+ if (ok_text)
+ {
+ argvl[i++] = "--ok-label";
+ argvl[i++] = ok_text;
+ }
+
+ if (cancel_text)
+ {
+ argvl[i++] = "--cancel-label";
+ argvl[i++] = cancel_text;
+ }
+
+ tmp = columns;
+ while (tmp)
+ {
+ argvl[i++] = "--column";
+ argvl[i++] = tmp->data;
+ tmp = tmp->next;
+ }
+
+ tmp = entries;
+ while (tmp)
+ {
+ argvl[i++] = tmp->data;
+ tmp = tmp->next;
+ }
+
+ argvl[i] = NULL;
+
+ if (transient_for)
+ {
+ gchar *env = g_strdup_printf("%d", transient_for);
+ setenv ("WINDOWID", env, 1);
+ g_free (env);
+ }
+
+ g_spawn_async (
+ "/",
+ (gchar**) argvl, /* ugh */
+ NULL,
+ G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL,
+ &child_pid,
+ &error
+ );
+
+ if (transient_for)
+ unsetenv ("WINDOWID");
+
+ g_free (argvl);
+ g_free (screen_number_text);
+
+ if (error)
+ {
+ meta_warning ("%s\n", error->message);
+ g_error_free (error);
+ }
+
+ return child_pid;
+}
+/* eof util.c */
+
diff --git a/src/core/window-private.h b/src/core/window-private.h
new file mode 100644
index 00000000..1a966275
--- /dev/null
+++ b/src/core/window-private.h
@@ -0,0 +1,640 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file window-private.h Windows which Marco manages
+ *
+ * Managing X windows.
+ * This file contains methods on this class which are available to
+ * routines in core but not outside it. (See window.h for the routines
+ * which the rest of the world is allowed to use.)
+ */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2002 Red Hat, Inc.
+ * Copyright (C) 2003, 2004 Rob Adams
+ * Copyright (C) 2004-2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_WINDOW_PRIVATE_H
+#define META_WINDOW_PRIVATE_H
+
+#include <config.h>
+#include "window.h"
+#include "screen-private.h"
+#include "util.h"
+#include "stack.h"
+#include "iconcache.h"
+#include <X11/Xutil.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+typedef struct _MetaGroup MetaGroup;
+typedef struct _MetaWindowQueue MetaWindowQueue;
+
+typedef gboolean (*MetaWindowForeachFunc) (MetaWindow *window,
+ void *data);
+
+typedef enum
+{
+ META_WINDOW_NORMAL,
+ META_WINDOW_DESKTOP,
+ META_WINDOW_DOCK,
+ META_WINDOW_DIALOG,
+ META_WINDOW_MODAL_DIALOG,
+ META_WINDOW_TOOLBAR,
+ META_WINDOW_MENU,
+ META_WINDOW_UTILITY,
+ META_WINDOW_SPLASHSCREEN
+} MetaWindowType;
+
+typedef enum
+{
+ META_MAXIMIZE_HORIZONTAL = 1 << 0,
+ META_MAXIMIZE_VERTICAL = 1 << 1
+} MetaMaximizeFlags;
+
+typedef enum {
+ META_CLIENT_TYPE_UNKNOWN = 0,
+ META_CLIENT_TYPE_APPLICATION = 1,
+ META_CLIENT_TYPE_PAGER = 2,
+ META_CLIENT_TYPE_MAX_RECOGNIZED = 2
+} MetaClientType;
+
+typedef enum {
+ META_QUEUE_CALC_SHOWING = 1 << 0,
+ META_QUEUE_MOVE_RESIZE = 1 << 1,
+ META_QUEUE_UPDATE_ICON = 1 << 2,
+} MetaQueueType;
+
+#define NUMBER_OF_QUEUES 3
+
+struct _MetaWindow
+{
+ MetaDisplay *display;
+ MetaScreen *screen;
+ MetaWorkspace *workspace;
+ Window xwindow;
+ /* may be NULL! not all windows get decorated */
+ MetaFrame *frame;
+ int depth;
+ Visual *xvisual;
+ Colormap colormap;
+ char *desc; /* used in debug spew */
+ char *title;
+
+ char *icon_name;
+ GdkPixbuf *icon;
+ GdkPixbuf *mini_icon;
+ MetaIconCache icon_cache;
+ Pixmap wm_hints_pixmap;
+ Pixmap wm_hints_mask;
+
+ MetaWindowType type;
+ Atom type_atom;
+
+ /* NOTE these five are not in UTF-8, we just treat them as random
+ * binary data
+ */
+ char *res_class;
+ char *res_name;
+ char *role;
+ char *sm_client_id;
+ char *wm_client_machine;
+ char *startup_id;
+
+ int net_wm_pid;
+
+ Window xtransient_for;
+ Window xgroup_leader;
+ Window xclient_leader;
+
+ /* Initial workspace property */
+ int initial_workspace;
+
+ /* Initial timestamp property */
+ guint32 initial_timestamp;
+
+ /* Whether we're maximized */
+ guint maximized_horizontally : 1;
+ guint maximized_vertically : 1;
+
+ /* Whether we have to maximize/minimize after placement */
+ guint maximize_horizontally_after_placement : 1;
+ guint maximize_vertically_after_placement : 1;
+ guint minimize_after_placement : 1;
+
+ /* Whether we're shaded */
+ guint shaded : 1;
+
+ /* Whether we're fullscreen */
+ guint fullscreen : 1;
+
+ /* Whether we have to fullscreen after placement */
+ guint fullscreen_after_placement : 1;
+
+ /* Area to cover when in fullscreen mode. If _NET_WM_FULLSCREEN_MONITORS has
+ * been overridden (via a client message), the window will cover the union of
+ * these monitors. If not, this is the single monitor which the window's
+ * origin is on. */
+ long fullscreen_monitors[4];
+
+ /* Whether we're trying to constrain the window to be fully onscreen */
+ guint require_fully_onscreen : 1;
+
+ /* Whether we're trying to constrain the window to be on a single xinerama */
+ guint require_on_single_xinerama : 1;
+
+ /* Whether we're trying to constrain the window's titlebar to be onscreen */
+ guint require_titlebar_visible : 1;
+
+ /* Whether we're sticky in the multi-workspace sense
+ * (vs. the not-scroll-with-viewport sense, we don't
+ * have no stupid viewports)
+ */
+ guint on_all_workspaces : 1;
+
+ /* Minimize is the state controlled by the minimize button */
+ guint minimized : 1;
+ guint was_minimized : 1;
+ guint tab_unminimized : 1;
+
+ /* Whether the window is mapped; actual server-side state
+ * see also unmaps_pending
+ */
+ guint mapped : 1;
+
+ /* Iconic is the state in WM_STATE; happens for workspaces/shading
+ * in addition to minimize
+ */
+ guint iconic : 1;
+ /* initially_iconic is the WM_HINTS setting when we first manage
+ * the window. It's taken to mean initially minimized.
+ */
+ guint initially_iconic : 1;
+
+ /* whether an initial workspace was explicitly set */
+ guint initial_workspace_set : 1;
+
+ /* whether an initial timestamp was explicitly set */
+ guint initial_timestamp_set : 1;
+
+ /* whether net_wm_user_time has been set yet */
+ guint net_wm_user_time_set : 1;
+
+ /* These are the flags from WM_PROTOCOLS */
+ guint take_focus : 1;
+ guint delete_window : 1;
+ guint net_wm_ping : 1;
+ /* Globally active / No input */
+ guint input : 1;
+
+ /* MWM hints about features of window */
+ guint mwm_decorated : 1;
+ guint mwm_border_only : 1;
+ guint mwm_has_close_func : 1;
+ guint mwm_has_minimize_func : 1;
+ guint mwm_has_maximize_func : 1;
+ guint mwm_has_move_func : 1;
+ guint mwm_has_resize_func : 1;
+
+ /* Computed features of window */
+ guint decorated : 1;
+ guint border_only : 1;
+ guint always_sticky : 1;
+ guint has_close_func : 1;
+ guint has_minimize_func : 1;
+ guint has_maximize_func : 1;
+ guint has_shade_func : 1;
+ guint has_move_func : 1;
+ guint has_resize_func : 1;
+ guint has_fullscreen_func : 1;
+
+ /* Weird "_NET_WM_STATE_MODAL" flag */
+ guint wm_state_modal : 1;
+
+ /* TRUE if the client forced these on */
+ guint wm_state_skip_taskbar : 1;
+ guint wm_state_skip_pager : 1;
+
+ /* Computed whether to skip taskbar or not */
+ guint skip_taskbar : 1;
+ guint skip_pager : 1;
+
+ /* TRUE if client set these */
+ guint wm_state_above : 1;
+ guint wm_state_below : 1;
+
+ /* EWHH demands attention flag */
+ guint wm_state_demands_attention : 1;
+
+ /* this flag tracks receipt of focus_in focus_out and
+ * determines whether we draw the focus
+ */
+ guint has_focus : 1;
+
+ /* Have we placed this window? */
+ guint placed : 1;
+
+ /* Must we force_save_user_window_placement? */
+ guint force_save_user_rect : 1;
+
+ /* Is this not a transient of the focus window which is being denied focus? */
+ guint denied_focus_and_not_transient : 1;
+
+ /* Has this window not ever been shown yet? */
+ guint showing_for_first_time : 1;
+
+ /* Are we in meta_window_free()? */
+ guint unmanaging : 1;
+
+ /* Are we in meta_window_new()? */
+ guint constructing : 1;
+
+ /* Are we in the various queues? (Bitfield: see META_WINDOW_IS_IN_QUEUE) */
+ guint is_in_queues : NUMBER_OF_QUEUES;
+
+ /* Used by keybindings.c */
+ guint keys_grabbed : 1; /* normal keybindings grabbed */
+ guint grab_on_frame : 1; /* grabs are on the frame */
+ guint all_keys_grabbed : 1; /* AnyKey grabbed */
+
+ /* Set if the reason for unmanaging the window is that
+ * it was withdrawn
+ */
+ guint withdrawn : 1;
+
+ /* TRUE if constrain_position should calc placement.
+ * only relevant if !window->placed
+ */
+ guint calc_placement : 1;
+
+ /* Transient parent is a root window */
+ guint transient_parent_is_root_window : 1;
+
+ /* Info on which props we got our attributes from */
+ guint using_net_wm_name : 1; /* vs. plain wm_name */
+ guint using_net_wm_visible_name : 1; /* tracked so we can clear it */
+ guint using_net_wm_icon_name : 1; /* vs. plain wm_icon_name */
+ guint using_net_wm_visible_icon_name : 1; /* tracked so we can clear it */
+
+ /* has a shape mask */
+ guint has_shape : 1;
+
+ /* icon props have changed */
+ guint need_reread_icon : 1;
+
+ /* if TRUE, window was maximized at start of current grab op */
+ guint shaken_loose : 1;
+
+ /* if TRUE we have a grab on the focus click buttons */
+ guint have_focus_click_grab : 1;
+
+ /* if TRUE, application is buggy and SYNC resizing is turned off */
+ guint disable_sync : 1;
+
+ /* Note: can be NULL */
+ GSList *struts;
+
+#ifdef HAVE_XSYNC
+ /* XSync update counter */
+ XSyncCounter sync_request_counter;
+ guint sync_request_serial;
+ GTimeVal sync_request_time;
+#endif
+
+ /* Number of UnmapNotify that are caused by us, if
+ * we get UnmapNotify with none pending then the client
+ * is withdrawing the window.
+ */
+ int unmaps_pending;
+
+ /* set to the most recent user-interaction event timestamp that we
+ know about for this window */
+ guint32 net_wm_user_time;
+
+ /* window that gets updated net_wm_user_time values */
+ Window user_time_window;
+
+ /* The size we set the window to last (i.e. what we believe
+ * to be its actual size on the server). The x, y are
+ * the actual server-side x,y so are relative to the frame
+ * (meaning that they just hold the frame width and height)
+ * or the root window (meaning they specify the location
+ * of the top left of the inner window) as appropriate.
+ */
+ MetaRectangle rect;
+
+ /* The geometry to restore when we unmaximize. The position is in
+ * root window coords, even if there's a frame, which contrasts with
+ * window->rect above. Note that this gives the position and size
+ * of the client window (i.e. ignoring the frame).
+ */
+ MetaRectangle saved_rect;
+
+ /* This is the geometry the window had after the last user-initiated
+ * move/resize operations. We use this whenever we are moving the
+ * implicitly (for example, if we move to avoid a panel, we can snap
+ * back to this position if the panel moves again). Note that this
+ * gives the position and size of the client window (i.e. ignoring
+ * the frame).
+ *
+ * Position valid if user_has_moved, size valid if user_has_resized
+ *
+ * Position always in root coords, unlike window->rect.
+ */
+ MetaRectangle user_rect;
+
+ /* Requested geometry */
+ int border_width;
+ /* x/y/w/h here get filled with ConfigureRequest values */
+ XSizeHints size_hints;
+
+ /* Managed by stack.c */
+ MetaStackLayer layer;
+ int stack_position; /* see comment in stack.h */
+
+ /* Current dialog open for this window */
+ int dialog_pid;
+
+ /* maintained by group.c */
+ MetaGroup *group;
+};
+
+/* These differ from window->has_foo_func in that they consider
+ * the dynamic window state such as "maximized", not just the
+ * window's type
+ */
+#define META_WINDOW_MAXIMIZED(w) ((w)->maximized_horizontally && \
+ (w)->maximized_vertically)
+#define META_WINDOW_MAXIMIZED_VERTICALLY(w) ((w)->maximized_vertically)
+#define META_WINDOW_MAXIMIZED_HORIZONTALLY(w) ((w)->maximized_horizontally)
+#define META_WINDOW_ALLOWS_MOVE(w) ((w)->has_move_func && !(w)->fullscreen)
+#define META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS(w) ((w)->has_resize_func && !META_WINDOW_MAXIMIZED (w) && !(w)->fullscreen && !(w)->shaded)
+#define META_WINDOW_ALLOWS_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && \
+ (((w)->size_hints.min_width < (w)->size_hints.max_width) || \
+ ((w)->size_hints.min_height < (w)->size_hints.max_height)))
+#define META_WINDOW_ALLOWS_HORIZONTAL_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && (w)->size_hints.min_width < (w)->size_hints.max_width)
+#define META_WINDOW_ALLOWS_VERTICAL_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && (w)->size_hints.min_height < (w)->size_hints.max_height)
+
+MetaWindow* meta_window_new (MetaDisplay *display,
+ Window xwindow,
+ gboolean must_be_viewable);
+MetaWindow* meta_window_new_with_attrs (MetaDisplay *display,
+ Window xwindow,
+ gboolean must_be_viewable,
+ XWindowAttributes *attrs);
+void meta_window_free (MetaWindow *window,
+ guint32 timestamp);
+void meta_window_calc_showing (MetaWindow *window);
+void meta_window_queue (MetaWindow *window,
+ guint queuebits);
+void meta_window_minimize (MetaWindow *window);
+void meta_window_unminimize (MetaWindow *window);
+void meta_window_maximize (MetaWindow *window,
+ MetaMaximizeFlags directions);
+void meta_window_maximize_internal (MetaWindow *window,
+ MetaMaximizeFlags directions,
+ MetaRectangle *saved_rect);
+void meta_window_unmaximize (MetaWindow *window,
+ MetaMaximizeFlags directions);
+void meta_window_make_above (MetaWindow *window);
+void meta_window_unmake_above (MetaWindow *window);
+void meta_window_shade (MetaWindow *window,
+ guint32 timestamp);
+void meta_window_unshade (MetaWindow *window,
+ guint32 timestamp);
+void meta_window_change_workspace (MetaWindow *window,
+ MetaWorkspace *workspace);
+void meta_window_stick (MetaWindow *window);
+void meta_window_unstick (MetaWindow *window);
+
+void meta_window_activate (MetaWindow *window,
+ guint32 current_time);
+void meta_window_activate_with_workspace (MetaWindow *window,
+ guint32 current_time,
+ MetaWorkspace *workspace);
+void meta_window_make_fullscreen_internal (MetaWindow *window);
+void meta_window_make_fullscreen (MetaWindow *window);
+void meta_window_unmake_fullscreen (MetaWindow *window);
+void meta_window_update_fullscreen_monitors (MetaWindow *window,
+ unsigned long top,
+ unsigned long bottom,
+ unsigned long left,
+ unsigned long right);
+
+/* args to move are window pos, not frame pos */
+void meta_window_move (MetaWindow *window,
+ gboolean user_op,
+ int root_x_nw,
+ int root_y_nw);
+void meta_window_resize (MetaWindow *window,
+ gboolean user_op,
+ int w,
+ int h);
+void meta_window_move_resize (MetaWindow *window,
+ gboolean user_op,
+ int root_x_nw,
+ int root_y_nw,
+ int w,
+ int h);
+void meta_window_resize_with_gravity (MetaWindow *window,
+ gboolean user_op,
+ int w,
+ int h,
+ int gravity);
+
+
+/* Return whether the window would be showing if we were on its workspace */
+gboolean meta_window_showing_on_its_workspace (MetaWindow *window);
+
+/* Return whether the window should be currently mapped */
+gboolean meta_window_should_be_showing (MetaWindow *window);
+
+/* See warning in window.c about this function */
+gboolean __window_is_terminal (MetaWindow *window);
+
+void meta_window_update_struts (MetaWindow *window);
+
+/* this gets root coords */
+void meta_window_get_position (MetaWindow *window,
+ int *x,
+ int *y);
+
+/* Gets root coords for x, y, width & height of client window; uses
+ * meta_window_get_position for x & y.
+ */
+void meta_window_get_client_root_coords (MetaWindow *window,
+ MetaRectangle *rect);
+
+/* gets position we need to set to stay in current position,
+ * assuming position will be gravity-compensated. i.e.
+ * this is the position a client would send in a configure
+ * request.
+ */
+void meta_window_get_gravity_position (MetaWindow *window,
+ int gravity,
+ int *x,
+ int *y);
+/* Get geometry for saving in the session; x/y are gravity
+ * position, and w/h are in resize inc above the base size.
+ */
+void meta_window_get_geometry (MetaWindow *window,
+ int *x,
+ int *y,
+ int *width,
+ int *height);
+void meta_window_get_outer_rect (const MetaWindow *window,
+ MetaRectangle *rect);
+void meta_window_get_xor_rect (MetaWindow *window,
+ const MetaRectangle *grab_wireframe_rect,
+ MetaRectangle *xor_rect);
+void meta_window_begin_wireframe (MetaWindow *window);
+void meta_window_update_wireframe (MetaWindow *window,
+ int x,
+ int y,
+ int width,
+ int height);
+void meta_window_end_wireframe (MetaWindow *window);
+
+void meta_window_delete (MetaWindow *window,
+ guint32 timestamp);
+void meta_window_kill (MetaWindow *window);
+void meta_window_focus (MetaWindow *window,
+ guint32 timestamp);
+void meta_window_raise (MetaWindow *window);
+void meta_window_lower (MetaWindow *window);
+
+void meta_window_update_unfocused_button_grabs (MetaWindow *window);
+
+/* Sends a client message */
+void meta_window_send_icccm_message (MetaWindow *window,
+ Atom atom,
+ guint32 timestamp);
+
+
+void meta_window_move_resize_request(MetaWindow *window,
+ guint value_mask,
+ int gravity,
+ int x,
+ int y,
+ int width,
+ int height);
+gboolean meta_window_configure_request (MetaWindow *window,
+ XEvent *event);
+gboolean meta_window_property_notify (MetaWindow *window,
+ XEvent *event);
+gboolean meta_window_client_message (MetaWindow *window,
+ XEvent *event);
+gboolean meta_window_notify_focus (MetaWindow *window,
+ XEvent *event);
+
+void meta_window_set_current_workspace_hint (MetaWindow *window);
+
+unsigned long meta_window_get_net_wm_desktop (MetaWindow *window);
+
+void meta_window_show_menu (MetaWindow *window,
+ int root_x,
+ int root_y,
+ int button,
+ guint32 timestamp);
+
+gboolean meta_window_titlebar_is_onscreen (MetaWindow *window);
+void meta_window_shove_titlebar_onscreen (MetaWindow *window);
+
+void meta_window_set_gravity (MetaWindow *window,
+ int gravity);
+
+void meta_window_handle_mouse_grab_op_event (MetaWindow *window,
+ XEvent *event);
+
+GList* meta_window_get_workspaces (MetaWindow *window);
+
+gboolean meta_window_located_on_workspace (MetaWindow *window,
+ MetaWorkspace *workspace);
+
+void meta_window_get_work_area_current_xinerama (MetaWindow *window,
+ MetaRectangle *area);
+void meta_window_get_work_area_for_xinerama (MetaWindow *window,
+ int which_xinerama,
+ MetaRectangle *area);
+void meta_window_get_work_area_all_xineramas (MetaWindow *window,
+ MetaRectangle *area);
+
+
+gboolean meta_window_same_application (MetaWindow *window,
+ MetaWindow *other_window);
+
+#define META_WINDOW_IN_NORMAL_TAB_CHAIN_TYPE(w) \
+ ((w)->type != META_WINDOW_DOCK && (w)->type != META_WINDOW_DESKTOP)
+#define META_WINDOW_IN_NORMAL_TAB_CHAIN(w) \
+ (((w)->input || (w)->take_focus ) && META_WINDOW_IN_NORMAL_TAB_CHAIN_TYPE (w) && (!(w)->skip_taskbar))
+#define META_WINDOW_IN_DOCK_TAB_CHAIN(w) \
+ (((w)->input || (w)->take_focus) && (! META_WINDOW_IN_NORMAL_TAB_CHAIN_TYPE (w) || (w)->skip_taskbar))
+#define META_WINDOW_IN_GROUP_TAB_CHAIN(w, g) \
+ (((w)->input || (w)->take_focus) && (!g || meta_window_get_group(w)==g))
+
+void meta_window_refresh_resize_popup (MetaWindow *window);
+
+void meta_window_free_delete_dialog (MetaWindow *window);
+
+void meta_window_foreach_transient (MetaWindow *window,
+ MetaWindowForeachFunc func,
+ void *data);
+gboolean meta_window_is_ancestor_of_transient (MetaWindow *window,
+ MetaWindow *transient);
+void meta_window_foreach_ancestor (MetaWindow *window,
+ MetaWindowForeachFunc func,
+ void *data);
+MetaWindow* meta_window_find_root_ancestor (MetaWindow *window);
+
+
+void meta_window_begin_grab_op (MetaWindow *window,
+ MetaGrabOp op,
+ gboolean frame_action,
+ guint32 timestamp);
+
+void meta_window_update_keyboard_resize (MetaWindow *window,
+ gboolean update_cursor);
+void meta_window_update_keyboard_move (MetaWindow *window);
+
+void meta_window_update_layer (MetaWindow *window);
+
+gboolean meta_window_get_icon_geometry (MetaWindow *window,
+ MetaRectangle *rect);
+
+const char* meta_window_get_startup_id (MetaWindow *window);
+
+void meta_window_recalc_features (MetaWindow *window);
+void meta_window_recalc_window_type (MetaWindow *window);
+
+void meta_window_stack_just_below (MetaWindow *window,
+ MetaWindow *below_this_one);
+
+void meta_window_set_user_time (MetaWindow *window,
+ guint32 timestamp);
+
+void meta_window_set_demands_attention (MetaWindow *window);
+
+void meta_window_unset_demands_attention (MetaWindow *window);
+
+void meta_window_update_icon_now (MetaWindow *window);
+
+void meta_window_update_role (MetaWindow *window);
+void meta_window_update_net_wm_type (MetaWindow *window);
+
+#endif
diff --git a/src/core/window-props.c b/src/core/window-props.c
new file mode 100644
index 00000000..b7b3e12d
--- /dev/null
+++ b/src/core/window-props.c
@@ -0,0 +1,1553 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file window-props.c MetaWindow property handling
+ *
+ * A system which can inspect sets of properties of given windows
+ * and take appropriate action given their values.
+ *
+ * Note that all the meta_window_reload_propert* functions require a
+ * round trip to the server.
+ *
+ * The guts of this system are in meta_display_init_window_prop_hooks().
+ * Reading this function will give you insight into how this all fits
+ * together.
+ */
+
+/*
+ * Copyright (C) 2001, 2002, 2003 Red Hat, Inc.
+ * Copyright (C) 2004, 2005 Elijah Newren
+ * Copyright (C) 2009 Thomas Thurman
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+#define _SVID_SOURCE /* for gethostname() */
+
+#include <config.h>
+#include "window-props.h"
+#include "errors.h"
+#include "xprops.h"
+#include "frame-private.h"
+#include "group.h"
+#include <X11/Xatom.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#ifdef HAVE_GTOP
+#include <glibtop/procuid.h>
+#include <errno.h>
+#include <pwd.h>
+#endif /* HAVE_GTOP */
+
+#ifndef HOST_NAME_MAX
+/* Solaris headers apparently don't define this so do so manually; #326745 */
+#define HOST_NAME_MAX 255
+#endif
+
+typedef void (* ReloadValueFunc) (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial);
+
+typedef struct MetaWindowPropHooks
+{
+ Atom property;
+ MetaPropValueType type;
+ ReloadValueFunc reload_func;
+} MetaWindowPropHooks;
+
+static MetaWindowPropHooks* find_hooks (MetaDisplay *display,
+ Atom property);
+
+
+void
+meta_window_reload_property (MetaWindow *window,
+ Atom property,
+ gboolean initial)
+{
+ meta_window_reload_properties (window, &property, 1, initial);
+}
+
+void
+meta_window_reload_properties (MetaWindow *window,
+ const Atom *properties,
+ int n_properties,
+ gboolean initial)
+{
+ meta_window_reload_properties_from_xwindow (window,
+ window->xwindow,
+ properties,
+ n_properties,
+ initial);
+}
+
+void
+meta_window_reload_property_from_xwindow (MetaWindow *window,
+ Window xwindow,
+ Atom property,
+ gboolean initial)
+{
+ meta_window_reload_properties_from_xwindow (window, xwindow, &property, 1,
+ initial);
+}
+
+void
+meta_window_reload_properties_from_xwindow (MetaWindow *window,
+ Window xwindow,
+ const Atom *properties,
+ int n_properties,
+ gboolean initial)
+{
+ int i;
+ MetaPropValue *values;
+
+ g_return_if_fail (properties != NULL);
+ g_return_if_fail (n_properties > 0);
+
+ values = g_new0 (MetaPropValue, n_properties);
+
+ for (i=0; i<n_properties; i++)
+ {
+ MetaWindowPropHooks *hooks = find_hooks (window->display,
+ properties[i]);
+
+ if (!hooks || hooks->type == META_PROP_VALUE_INVALID)
+ {
+ values[i].type = META_PROP_VALUE_INVALID;
+ values[i].atom = None;
+ }
+ else
+ {
+ values[i].type = hooks->type;
+ values[i].atom = properties[i];
+ }
+ }
+
+ meta_prop_get_values (window->display, xwindow,
+ values, n_properties);
+
+ for (i=0; i<n_properties; i++)
+ {
+ MetaWindowPropHooks *hooks = find_hooks (window->display,
+ properties[i]);
+
+ if (hooks && hooks->reload_func != NULL)
+ (* hooks->reload_func) (window, &values[i], initial);
+ }
+
+ meta_prop_free_values (values, n_properties);
+
+ g_free (values);
+}
+
+static void
+reload_wm_client_machine (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ g_free (window->wm_client_machine);
+ window->wm_client_machine = NULL;
+
+ if (value->type != META_PROP_VALUE_INVALID)
+ window->wm_client_machine = g_strdup (value->v.str);
+
+ meta_verbose ("Window has client machine \"%s\"\n",
+ window->wm_client_machine ? window->wm_client_machine : "unset");
+}
+
+static void
+complain_about_broken_client (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ meta_warning ("Broken client! Window %s changed client leader window or SM client ID\n",
+ window->desc);
+}
+
+static void
+reload_net_wm_window_type (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ meta_window_update_net_wm_type (window);
+}
+
+static void
+reload_icon (MetaWindow *window,
+ Atom atom)
+{
+ meta_icon_cache_property_changed (&window->icon_cache,
+ window->display,
+ atom);
+ meta_window_queue(window, META_QUEUE_UPDATE_ICON);
+}
+
+static void
+reload_net_wm_icon (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ reload_icon (window, window->display->atom__NET_WM_ICON);
+}
+
+static void
+reload_kwm_win_icon (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ reload_icon (window, window->display->atom__KWM_WIN_ICON);
+}
+
+static void
+reload_struts (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ meta_window_update_struts (window);
+}
+
+static void
+reload_wm_window_role (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ meta_window_update_role (window);
+}
+
+static void
+reload_net_wm_pid (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+ gulong cardinal = (int) value->v.cardinal;
+
+ if (cardinal <= 0)
+ meta_warning (_("Application set a bogus _NET_WM_PID %lu\n"),
+ cardinal);
+ else
+ {
+ window->net_wm_pid = cardinal;
+ meta_verbose ("Window has _NET_WM_PID %d\n",
+ window->net_wm_pid);
+ }
+ }
+}
+
+static void
+reload_net_wm_user_time (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+ gulong cardinal = value->v.cardinal;
+ meta_window_set_user_time (window, cardinal);
+ }
+}
+
+static void
+reload_net_wm_user_time_window (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+ /* Unregister old NET_WM_USER_TIME_WINDOW */
+ if (window->user_time_window != None)
+ {
+ /* See the comment to the meta_display_register_x_window call below. */
+ meta_display_unregister_x_window (window->display,
+ window->user_time_window);
+ /* Don't get events on not-managed windows */
+ XSelectInput (window->display->xdisplay,
+ window->user_time_window,
+ NoEventMask);
+ }
+
+
+ /* Obtain the new NET_WM_USER_TIME_WINDOW and register it */
+ window->user_time_window = value->v.xwindow;
+ if (window->user_time_window != None)
+ {
+ /* Kind of a hack; display.c:event_callback() ignores events
+ * for unknown windows. We make window->user_time_window
+ * known by registering it with window (despite the fact
+ * that window->xwindow is already registered with window).
+ * This basically means that property notifies to either the
+ * window->user_time_window or window->xwindow will be
+ * treated identically and will result in functions for
+ * window being called to update it. Maybe we should ignore
+ * any property notifies to window->user_time_window other
+ * than atom__NET_WM_USER_TIME ones, but I just don't care
+ * and it's not specified in the spec anyway.
+ */
+ meta_display_register_x_window (window->display,
+ &window->user_time_window,
+ window);
+ /* Just listen for property notify events */
+ XSelectInput (window->display->xdisplay,
+ window->user_time_window,
+ PropertyChangeMask);
+
+ /* Manually load the _NET_WM_USER_TIME field from the given window
+ * at this time as well. If the user_time_window ever broadens in
+ * scope, we'll probably want to load all relevant properties here.
+ */
+ meta_window_reload_property_from_xwindow (
+ window,
+ window->user_time_window,
+ window->display->atom__NET_WM_USER_TIME,
+ initial);
+ }
+ }
+}
+
+/**
+ * Finds who owns a particular process, if we can.
+ *
+ * \param process The process's ID.
+ * \result Set to the ID of the user, if we returned true.
+ *
+ * \result True if we could tell.
+ */
+static gboolean
+owner_of_process (pid_t process, uid_t *result)
+{
+#ifdef HAVE_GTOP
+ glibtop_proc_uid process_details;
+
+ glibtop_get_proc_uid (&process_details, process);
+
+ *result = process_details.uid;
+ return TRUE;
+#else
+ /* I don't know, maybe we could do something hairy like see whether
+ * /proc/$PID exists and who owns it, in case they have procfs.
+ */
+ return FALSE;
+#endif /* HAVE_GTOP */
+}
+
+#define MAX_TITLE_LENGTH 512
+
+/**
+ * Called by set_window_title and set_icon_title to set the value of
+ * *target to title. It required and atom is set, it will update the
+ * appropriate property.
+ *
+ * Returns TRUE if a new title was set.
+ */
+static gboolean
+set_title_text (MetaWindow *window,
+ gboolean previous_was_modified,
+ const char *title,
+ Atom atom,
+ char **target)
+{
+ char hostname[HOST_NAME_MAX + 1];
+ gboolean modified = FALSE;
+
+ if (!target)
+ return FALSE;
+
+ g_free (*target);
+
+ if (!title)
+ *target = g_strdup ("");
+ else if (g_utf8_strlen (title, MAX_TITLE_LENGTH + 1) > MAX_TITLE_LENGTH)
+ {
+ *target = meta_g_utf8_strndup (title, MAX_TITLE_LENGTH);
+ modified = TRUE;
+ }
+ /* if WM_CLIENT_MACHINE indicates this machine is on a remote host
+ * let's place that hostname in the title */
+ else if (window->wm_client_machine &&
+ !gethostname (hostname, HOST_NAME_MAX + 1) &&
+ strcmp (hostname, window->wm_client_machine))
+ {
+ /* Translators: the title of a window from another machine */
+ *target = g_strdup_printf (_("%s (on %s)"),
+ title, window->wm_client_machine);
+ modified = TRUE;
+ }
+ else if (window->net_wm_pid != -1)
+ {
+ /* We know the process which owns this window; perhaps we can
+ * find out the name of its owner (if it's not us).
+ */
+
+ char *found_name = NULL;
+
+ uid_t window_owner = 0;
+ gboolean window_owner_known =
+ owner_of_process (window->net_wm_pid, &window_owner);
+
+ /* Assume a window with unknown ownership is ours (call it usufruct!) */
+ gboolean window_owner_is_us =
+ !window_owner_known || window_owner==getuid ();
+
+ if (window_owner_is_us)
+ {
+ /* we own it, so fall back to the simple case */
+ *target = g_strdup (title);
+ }
+ else
+ {
+ /* it belongs to window_owner. So what's their name? */
+
+ if (window_owner==0)
+ {
+ /* Simple case-- don't bother to look it up. It's root. */
+ *target = g_strdup_printf (_("%s (as superuser)"),
+ title);
+ }
+ else
+ {
+ /* Okay, let's look up the name. */
+ struct passwd *pwd;
+
+ errno = 0;
+ pwd = getpwuid (window_owner);
+ if (errno==0 && pwd!=NULL)
+ {
+ found_name = pwd->pw_name;
+ }
+
+ if (found_name)
+ /* Translators: the title of a window owned by another user
+ * on this machine */
+ *target = g_strdup_printf (_("%s (as %s)"),
+ title,
+ found_name);
+ else
+ /* Translators: the title of a window owned by another user
+ * on this machine, whose name we don't know */
+ *target = g_strdup_printf (_("%s (as another user)"),
+ title);
+ }
+ /* either way we changed it */
+ modified = TRUE;
+
+ }
+ }
+ else
+ *target = g_strdup (title);
+
+ if (modified && atom != None)
+ meta_prop_set_utf8_string_hint (window->display,
+ window->xwindow,
+ atom, *target);
+
+ /* Bug 330671 -- Don't forget to clear _NET_WM_VISIBLE_(ICON_)NAME */
+ if (!modified && previous_was_modified)
+ {
+ meta_error_trap_push (window->display);
+ XDeleteProperty (window->display->xdisplay,
+ window->xwindow,
+ atom);
+ meta_error_trap_pop (window->display, FALSE);
+ }
+
+ return modified;
+}
+
+static void
+set_window_title (MetaWindow *window,
+ const char *title)
+{
+ char *str;
+
+ gboolean modified =
+ set_title_text (window,
+ window->using_net_wm_visible_name,
+ title,
+ window->display->atom__NET_WM_VISIBLE_NAME,
+ &window->title);
+ window->using_net_wm_visible_name = modified;
+
+ /* strndup is a hack since GNU libc has broken %.10s */
+ str = g_strndup (window->title, 10);
+ g_free (window->desc);
+ window->desc = g_strdup_printf ("0x%lx (%s)", window->xwindow, str);
+ g_free (str);
+
+ if (window->frame)
+ meta_ui_set_frame_title (window->screen->ui,
+ window->frame->xwindow,
+ window->title);
+}
+
+static void
+reload_net_wm_name (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+ set_window_title (window, value->v.str);
+ window->using_net_wm_name = TRUE;
+
+ meta_verbose ("Using _NET_WM_NAME for new title of %s: \"%s\"\n",
+ window->desc, window->title);
+ }
+ else
+ {
+ set_window_title (window, NULL);
+ window->using_net_wm_name = FALSE;
+ if (!initial)
+ meta_window_reload_property (window, XA_WM_NAME, FALSE);
+ }
+}
+
+static void
+reload_wm_name (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ if (window->using_net_wm_name)
+ {
+ meta_verbose ("Ignoring WM_NAME \"%s\" as _NET_WM_NAME is set\n",
+ value->v.str);
+ return;
+ }
+
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+ set_window_title (window, value->v.str);
+
+ meta_verbose ("Using WM_NAME for new title of %s: \"%s\"\n",
+ window->desc, window->title);
+ }
+ else
+ {
+ set_window_title (window, NULL);
+ }
+}
+
+static void
+set_icon_title (MetaWindow *window,
+ const char *title)
+{
+ gboolean modified =
+ set_title_text (window,
+ window->using_net_wm_visible_icon_name,
+ title,
+ window->display->atom__NET_WM_VISIBLE_ICON_NAME,
+ &window->icon_name);
+ window->using_net_wm_visible_icon_name = modified;
+}
+
+static void
+reload_net_wm_icon_name (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+ set_icon_title (window, value->v.str);
+ window->using_net_wm_icon_name = TRUE;
+
+ meta_verbose ("Using _NET_WM_ICON_NAME for new title of %s: \"%s\"\n",
+ window->desc, window->title);
+ }
+ else
+ {
+ set_icon_title (window, NULL);
+ window->using_net_wm_icon_name = FALSE;
+ if (!initial)
+ meta_window_reload_property (window, XA_WM_ICON_NAME, FALSE);
+ }
+}
+
+static void
+reload_wm_icon_name (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ if (window->using_net_wm_icon_name)
+ {
+ meta_verbose ("Ignoring WM_ICON_NAME \"%s\" as _NET_WM_ICON_NAME is set\n",
+ value->v.str);
+ return;
+ }
+
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+ set_icon_title (window, value->v.str);
+
+ meta_verbose ("Using WM_ICON_NAME for new title of %s: \"%s\"\n",
+ window->desc, window->title);
+ }
+ else
+ {
+ set_icon_title (window, NULL);
+ }
+}
+
+static void
+reload_net_wm_state (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ int i;
+
+ /* We know this is only an initial window creation,
+ * clients don't change the property.
+ */
+
+ if (!initial) {
+ /* no, they DON'T change the property */
+ meta_verbose ("Ignoring _NET_WM_STATE: we should be the one who set "
+ "the property in the first place\n");
+ return;
+ }
+
+ window->shaded = FALSE;
+ window->maximized_horizontally = FALSE;
+ window->maximized_vertically = FALSE;
+ window->fullscreen = FALSE;
+ window->wm_state_modal = FALSE;
+ window->wm_state_skip_taskbar = FALSE;
+ window->wm_state_skip_pager = FALSE;
+ window->wm_state_above = FALSE;
+ window->wm_state_below = FALSE;
+ window->wm_state_demands_attention = FALSE;
+
+ if (value->type == META_PROP_VALUE_INVALID)
+ return;
+
+ i = 0;
+ while (i < value->v.atom_list.n_atoms)
+ {
+ if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_SHADED)
+ window->shaded = TRUE;
+ else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_MAXIMIZED_HORZ)
+ window->maximize_horizontally_after_placement = TRUE;
+ else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_MAXIMIZED_VERT)
+ window->maximize_vertically_after_placement = TRUE;
+ else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_HIDDEN)
+ window->minimize_after_placement = TRUE;
+ else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_MODAL)
+ window->wm_state_modal = TRUE;
+ else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_SKIP_TASKBAR)
+ window->wm_state_skip_taskbar = TRUE;
+ else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_SKIP_PAGER)
+ window->wm_state_skip_pager = TRUE;
+ else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_FULLSCREEN)
+ window->fullscreen_after_placement = TRUE;
+ else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_ABOVE)
+ window->wm_state_above = TRUE;
+ else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_BELOW)
+ window->wm_state_below = TRUE;
+ else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_DEMANDS_ATTENTION)
+ window->wm_state_demands_attention = TRUE;
+ else if (value->v.atom_list.atoms[i] == window->display->atom__NET_WM_STATE_STICKY)
+ window->on_all_workspaces = TRUE;
+
+ ++i;
+ }
+
+ meta_verbose ("Reloaded _NET_WM_STATE for %s\n",
+ window->desc);
+
+ meta_window_recalc_window_type (window);
+}
+
+static void
+reload_mwm_hints (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ MotifWmHints *hints;
+
+ window->mwm_decorated = TRUE;
+ window->mwm_border_only = FALSE;
+ window->mwm_has_close_func = TRUE;
+ window->mwm_has_minimize_func = TRUE;
+ window->mwm_has_maximize_func = TRUE;
+ window->mwm_has_move_func = TRUE;
+ window->mwm_has_resize_func = TRUE;
+
+ if (value->type == META_PROP_VALUE_INVALID)
+ {
+ meta_verbose ("Window %s has no MWM hints\n", window->desc);
+ meta_window_recalc_features (window);
+ return;
+ }
+
+ hints = value->v.motif_hints;
+
+ /* We support those MWM hints deemed non-stupid */
+
+ meta_verbose ("Window %s has MWM hints\n",
+ window->desc);
+
+ if (hints->flags & MWM_HINTS_DECORATIONS)
+ {
+ meta_verbose ("Window %s sets MWM_HINTS_DECORATIONS 0x%lx\n",
+ window->desc, hints->decorations);
+
+ if (hints->decorations == 0)
+ window->mwm_decorated = FALSE;
+ /* some input methods use this */
+ else if (hints->decorations == MWM_DECOR_BORDER)
+ window->mwm_border_only = TRUE;
+ }
+ else
+ meta_verbose ("Decorations flag unset\n");
+
+ if (hints->flags & MWM_HINTS_FUNCTIONS)
+ {
+ gboolean toggle_value;
+
+ meta_verbose ("Window %s sets MWM_HINTS_FUNCTIONS 0x%lx\n",
+ window->desc, hints->functions);
+
+ /* If _ALL is specified, then other flags indicate what to turn off;
+ * if ALL is not specified, flags are what to turn on.
+ * at least, I think so
+ */
+
+ if ((hints->functions & MWM_FUNC_ALL) == 0)
+ {
+ toggle_value = TRUE;
+
+ meta_verbose ("Window %s disables all funcs then reenables some\n",
+ window->desc);
+ window->mwm_has_close_func = FALSE;
+ window->mwm_has_minimize_func = FALSE;
+ window->mwm_has_maximize_func = FALSE;
+ window->mwm_has_move_func = FALSE;
+ window->mwm_has_resize_func = FALSE;
+ }
+ else
+ {
+ meta_verbose ("Window %s enables all funcs then disables some\n",
+ window->desc);
+ toggle_value = FALSE;
+ }
+
+ if ((hints->functions & MWM_FUNC_CLOSE) != 0)
+ {
+ meta_verbose ("Window %s toggles close via MWM hints\n",
+ window->desc);
+ window->mwm_has_close_func = toggle_value;
+ }
+ if ((hints->functions & MWM_FUNC_MINIMIZE) != 0)
+ {
+ meta_verbose ("Window %s toggles minimize via MWM hints\n",
+ window->desc);
+ window->mwm_has_minimize_func = toggle_value;
+ }
+ if ((hints->functions & MWM_FUNC_MAXIMIZE) != 0)
+ {
+ meta_verbose ("Window %s toggles maximize via MWM hints\n",
+ window->desc);
+ window->mwm_has_maximize_func = toggle_value;
+ }
+ if ((hints->functions & MWM_FUNC_MOVE) != 0)
+ {
+ meta_verbose ("Window %s toggles move via MWM hints\n",
+ window->desc);
+ window->mwm_has_move_func = toggle_value;
+ }
+ if ((hints->functions & MWM_FUNC_RESIZE) != 0)
+ {
+ meta_verbose ("Window %s toggles resize via MWM hints\n",
+ window->desc);
+ window->mwm_has_resize_func = toggle_value;
+ }
+ }
+ else
+ meta_verbose ("Functions flag unset\n");
+
+ meta_window_recalc_features (window);
+
+ /* We do all this anyhow at the end of meta_window_new() */
+ if (!window->constructing)
+ {
+ if (window->decorated)
+ meta_window_ensure_frame (window);
+ else
+ meta_window_destroy_frame (window);
+
+ meta_window_queue (window,
+ META_QUEUE_MOVE_RESIZE |
+ /* because ensure/destroy frame may unmap: */
+ META_QUEUE_CALC_SHOWING);
+ }
+}
+
+static void
+reload_wm_class (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ if (window->res_class)
+ g_free (window->res_class);
+ if (window->res_name)
+ g_free (window->res_name);
+
+ window->res_class = NULL;
+ window->res_name = NULL;
+
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+ if (value->v.class_hint.res_name)
+ window->res_name = g_strdup (value->v.class_hint.res_name);
+
+ if (value->v.class_hint.res_class)
+ window->res_class = g_strdup (value->v.class_hint.res_class);
+ }
+
+ meta_verbose ("Window %s class: '%s' name: '%s'\n",
+ window->desc,
+ window->res_class ? window->res_class : "none",
+ window->res_name ? window->res_name : "none");
+}
+
+static void
+reload_net_wm_desktop (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+ window->initial_workspace_set = TRUE;
+ window->initial_workspace = value->v.cardinal;
+ meta_topic (META_DEBUG_PLACEMENT,
+ "Read initial workspace prop %d for %s\n",
+ window->initial_workspace, window->desc);
+ }
+}
+
+static void
+reload_net_startup_id (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ guint32 timestamp = window->net_wm_user_time;
+ MetaWorkspace *workspace = NULL;
+
+ g_free (window->startup_id);
+
+ if (value->type != META_PROP_VALUE_INVALID)
+ window->startup_id = g_strdup (value->v.str);
+ else
+ window->startup_id = NULL;
+
+ /* Update timestamp and workspace on a running window */
+ if (!window->constructing)
+ {
+ window->initial_timestamp_set = 0;
+ window->initial_workspace_set = 0;
+
+ if (meta_screen_apply_startup_properties (window->screen, window))
+ {
+
+ if (window->initial_timestamp_set)
+ timestamp = window->initial_timestamp;
+ if (window->initial_workspace_set)
+ workspace = meta_screen_get_workspace_by_index (window->screen, window->initial_workspace);
+
+ meta_window_activate_with_workspace (window, timestamp, workspace);
+ }
+ }
+
+ meta_verbose ("New _NET_STARTUP_ID \"%s\" for %s\n",
+ window->startup_id ? window->startup_id : "unset",
+ window->desc);
+}
+
+static void
+reload_update_counter (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+#ifdef HAVE_XSYNC
+ XSyncCounter counter = value->v.xcounter;
+
+ window->sync_request_counter = counter;
+ meta_verbose ("Window has _NET_WM_SYNC_REQUEST_COUNTER 0x%lx\n",
+ window->sync_request_counter);
+#endif
+ }
+}
+
+#define FLAG_TOGGLED_ON(old,new,flag) \
+ (((old)->flags & (flag)) == 0 && \
+ ((new)->flags & (flag)) != 0)
+
+#define FLAG_TOGGLED_OFF(old,new,flag) \
+ (((old)->flags & (flag)) != 0 && \
+ ((new)->flags & (flag)) == 0)
+
+#define FLAG_CHANGED(old,new,flag) \
+ (FLAG_TOGGLED_ON(old,new,flag) || FLAG_TOGGLED_OFF(old,new,flag))
+
+static void
+spew_size_hints_differences (const XSizeHints *old,
+ const XSizeHints *new)
+{
+ if (FLAG_CHANGED (old, new, USPosition))
+ meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: USPosition now %s\n",
+ FLAG_TOGGLED_ON (old, new, USPosition) ? "set" : "unset");
+ if (FLAG_CHANGED (old, new, USSize))
+ meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: USSize now %s\n",
+ FLAG_TOGGLED_ON (old, new, USSize) ? "set" : "unset");
+ if (FLAG_CHANGED (old, new, PPosition))
+ meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PPosition now %s\n",
+ FLAG_TOGGLED_ON (old, new, PPosition) ? "set" : "unset");
+ if (FLAG_CHANGED (old, new, PSize))
+ meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PSize now %s\n",
+ FLAG_TOGGLED_ON (old, new, PSize) ? "set" : "unset");
+ if (FLAG_CHANGED (old, new, PMinSize))
+ meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PMinSize now %s (%d x %d -> %d x %d)\n",
+ FLAG_TOGGLED_ON (old, new, PMinSize) ? "set" : "unset",
+ old->min_width, old->min_height,
+ new->min_width, new->min_height);
+ if (FLAG_CHANGED (old, new, PMaxSize))
+ meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PMaxSize now %s (%d x %d -> %d x %d)\n",
+ FLAG_TOGGLED_ON (old, new, PMaxSize) ? "set" : "unset",
+ old->max_width, old->max_height,
+ new->max_width, new->max_height);
+ if (FLAG_CHANGED (old, new, PResizeInc))
+ meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PResizeInc now %s (width_inc %d -> %d height_inc %d -> %d)\n",
+ FLAG_TOGGLED_ON (old, new, PResizeInc) ? "set" : "unset",
+ old->width_inc, new->width_inc,
+ old->height_inc, new->height_inc);
+ if (FLAG_CHANGED (old, new, PAspect))
+ meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PAspect now %s (min %d/%d -> %d/%d max %d/%d -> %d/%d)\n",
+ FLAG_TOGGLED_ON (old, new, PAspect) ? "set" : "unset",
+ old->min_aspect.x, old->min_aspect.y,
+ new->min_aspect.x, new->min_aspect.y,
+ old->max_aspect.x, old->max_aspect.y,
+ new->max_aspect.x, new->max_aspect.y);
+ if (FLAG_CHANGED (old, new, PBaseSize))
+ meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PBaseSize now %s (%d x %d -> %d x %d)\n",
+ FLAG_TOGGLED_ON (old, new, PBaseSize) ? "set" : "unset",
+ old->base_width, old->base_height,
+ new->base_width, new->base_height);
+ if (FLAG_CHANGED (old, new, PWinGravity))
+ meta_topic (META_DEBUG_GEOMETRY, "XSizeHints: PWinGravity now %s (%d -> %d)\n",
+ FLAG_TOGGLED_ON (old, new, PWinGravity) ? "set" : "unset",
+ old->win_gravity, new->win_gravity);
+}
+
+void
+meta_set_normal_hints (MetaWindow *window,
+ XSizeHints *hints)
+{
+ int x, y, w, h;
+ double minr, maxr;
+ /* Some convenience vars */
+ int minw, minh, maxw, maxh; /* min/max width/height */
+ int basew, baseh, winc, hinc; /* base width/height, width/height increment */
+
+ /* Save the last ConfigureRequest, which we put here.
+ * Values here set in the hints are supposed to
+ * be ignored.
+ */
+ x = window->size_hints.x;
+ y = window->size_hints.y;
+ w = window->size_hints.width;
+ h = window->size_hints.height;
+
+ /* as far as I can tell, value->v.size_hints.flags is just to
+ * check whether we had old-style normal hints without gravity,
+ * base size as returned by XGetNormalHints(), so we don't
+ * really use it as we fixup window->size_hints to have those
+ * fields if they're missing.
+ */
+
+ /*
+ * When the window is first created, NULL hints will
+ * be passed in which will initialize all of the fields
+ * as if flags were zero
+ */
+ if (hints)
+ window->size_hints = *hints;
+ else
+ window->size_hints.flags = 0;
+
+ /* Put back saved ConfigureRequest. */
+ window->size_hints.x = x;
+ window->size_hints.y = y;
+ window->size_hints.width = w;
+ window->size_hints.height = h;
+
+ /* Get base size hints */
+ if (window->size_hints.flags & PBaseSize)
+ {
+ meta_topic (META_DEBUG_GEOMETRY, "Window %s sets base size %d x %d\n",
+ window->desc,
+ window->size_hints.base_width,
+ window->size_hints.base_height);
+ }
+ else if (window->size_hints.flags & PMinSize)
+ {
+ window->size_hints.base_width = window->size_hints.min_width;
+ window->size_hints.base_height = window->size_hints.min_height;
+ }
+ else
+ {
+ window->size_hints.base_width = 0;
+ window->size_hints.base_height = 0;
+ }
+ window->size_hints.flags |= PBaseSize;
+
+ /* Get min size hints */
+ if (window->size_hints.flags & PMinSize)
+ {
+ meta_topic (META_DEBUG_GEOMETRY, "Window %s sets min size %d x %d\n",
+ window->desc,
+ window->size_hints.min_width,
+ window->size_hints.min_height);
+ }
+ else if (window->size_hints.flags & PBaseSize)
+ {
+ window->size_hints.min_width = window->size_hints.base_width;
+ window->size_hints.min_height = window->size_hints.base_height;
+ }
+ else
+ {
+ window->size_hints.min_width = 0;
+ window->size_hints.min_height = 0;
+ }
+ window->size_hints.flags |= PMinSize;
+
+ /* Get max size hints */
+ if (window->size_hints.flags & PMaxSize)
+ {
+ meta_topic (META_DEBUG_GEOMETRY, "Window %s sets max size %d x %d\n",
+ window->desc,
+ window->size_hints.max_width,
+ window->size_hints.max_height);
+ }
+ else
+ {
+ window->size_hints.max_width = G_MAXINT;
+ window->size_hints.max_height = G_MAXINT;
+ window->size_hints.flags |= PMaxSize;
+ }
+
+ /* Get resize increment hints */
+ if (window->size_hints.flags & PResizeInc)
+ {
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s sets resize width inc: %d height inc: %d\n",
+ window->desc,
+ window->size_hints.width_inc,
+ window->size_hints.height_inc);
+ }
+ else
+ {
+ window->size_hints.width_inc = 1;
+ window->size_hints.height_inc = 1;
+ window->size_hints.flags |= PResizeInc;
+ }
+
+ /* Get aspect ratio hints */
+ if (window->size_hints.flags & PAspect)
+ {
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s sets min_aspect: %d/%d max_aspect: %d/%d\n",
+ window->desc,
+ window->size_hints.min_aspect.x,
+ window->size_hints.min_aspect.y,
+ window->size_hints.max_aspect.x,
+ window->size_hints.max_aspect.y);
+ }
+ else
+ {
+ window->size_hints.min_aspect.x = 1;
+ window->size_hints.min_aspect.y = G_MAXINT;
+ window->size_hints.max_aspect.x = G_MAXINT;
+ window->size_hints.max_aspect.y = 1;
+ window->size_hints.flags |= PAspect;
+ }
+
+ /* Get gravity hint */
+ if (window->size_hints.flags & PWinGravity)
+ {
+ meta_topic (META_DEBUG_GEOMETRY, "Window %s sets gravity %d\n",
+ window->desc,
+ window->size_hints.win_gravity);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s doesn't set gravity, using NW\n",
+ window->desc);
+ window->size_hints.win_gravity = NorthWestGravity;
+ window->size_hints.flags |= PWinGravity;
+ }
+
+ /*** Lots of sanity checking ***/
+
+ /* Verify all min & max hints are at least 1 pixel */
+ if (window->size_hints.min_width < 1)
+ {
+ /* someone is on crack */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s sets min width to 0, which makes no sense\n",
+ window->desc);
+ window->size_hints.min_width = 1;
+ }
+ if (window->size_hints.max_width < 1)
+ {
+ /* another cracksmoker */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s sets max width to 0, which makes no sense\n",
+ window->desc);
+ window->size_hints.max_width = 1;
+ }
+ if (window->size_hints.min_height < 1)
+ {
+ /* another cracksmoker */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s sets min height to 0, which makes no sense\n",
+ window->desc);
+ window->size_hints.min_height = 1;
+ }
+ if (window->size_hints.max_height < 1)
+ {
+ /* another cracksmoker */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s sets max height to 0, which makes no sense\n",
+ window->desc);
+ window->size_hints.max_height = 1;
+ }
+
+ /* Verify size increment hints are at least 1 pixel */
+ if (window->size_hints.width_inc < 1)
+ {
+ /* app authors find so many ways to smoke crack */
+ window->size_hints.width_inc = 1;
+ meta_topic (META_DEBUG_GEOMETRY, "Corrected 0 width_inc to 1\n");
+ }
+ if (window->size_hints.height_inc < 1)
+ {
+ /* another cracksmoker */
+ window->size_hints.height_inc = 1;
+ meta_topic (META_DEBUG_GEOMETRY, "Corrected 0 height_inc to 1\n");
+ }
+ /* divide by 0 cracksmokers; note that x & y in (min|max)_aspect are
+ * numerator & denominator
+ */
+ if (window->size_hints.min_aspect.y < 1)
+ window->size_hints.min_aspect.y = 1;
+ if (window->size_hints.max_aspect.y < 1)
+ window->size_hints.max_aspect.y = 1;
+
+ minw = window->size_hints.min_width; minh = window->size_hints.min_height;
+ maxw = window->size_hints.max_width; maxh = window->size_hints.max_height;
+ basew = window->size_hints.base_width; baseh = window->size_hints.base_height;
+ winc = window->size_hints.width_inc; hinc = window->size_hints.height_inc;
+
+ /* Make sure min and max size hints are consistent with the base + increment
+ * size hints. If they're not, it's not a real big deal, but it means the
+ * effective min and max size are more restrictive than the application
+ * specified values.
+ */
+ if ((minw - basew) % winc != 0)
+ {
+ /* Take advantage of integer division throwing away the remainder... */
+ window->size_hints.min_width = basew + ((minw - basew)/winc + 1)*winc;
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s has width_inc (%d) that does not evenly divide "
+ "min_width - base_width (%d - %d); thus effective "
+ "min_width is really %d\n",
+ window->desc,
+ winc, minw, basew, window->size_hints.min_width);
+ minw = window->size_hints.min_width;
+ }
+ if (maxw != G_MAXINT && (maxw - basew) % winc != 0)
+ {
+ /* Take advantage of integer division throwing away the remainder... */
+ window->size_hints.max_width = basew + ((maxw - basew)/winc)*winc;
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s has width_inc (%d) that does not evenly divide "
+ "max_width - base_width (%d - %d); thus effective "
+ "max_width is really %d\n",
+ window->desc,
+ winc, maxw, basew, window->size_hints.max_width);
+ maxw = window->size_hints.max_width;
+ }
+ if ((minh - baseh) % hinc != 0)
+ {
+ /* Take advantage of integer division throwing away the remainder... */
+ window->size_hints.min_height = baseh + ((minh - baseh)/hinc + 1)*hinc;
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s has height_inc (%d) that does not evenly divide "
+ "min_height - base_height (%d - %d); thus effective "
+ "min_height is really %d\n",
+ window->desc,
+ hinc, minh, baseh, window->size_hints.min_height);
+ minh = window->size_hints.min_height;
+ }
+ if (maxh != G_MAXINT && (maxh - baseh) % hinc != 0)
+ {
+ /* Take advantage of integer division throwing away the remainder... */
+ window->size_hints.max_height = baseh + ((maxh - baseh)/hinc)*hinc;
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s has height_inc (%d) that does not evenly divide "
+ "max_height - base_height (%d - %d); thus effective "
+ "max_height is really %d\n",
+ window->desc,
+ hinc, maxh, baseh, window->size_hints.max_height);
+ maxh = window->size_hints.max_height;
+ }
+
+ /* make sure maximum size hints are compatible with minimum size hints; min
+ * size hints take precedence.
+ */
+ if (window->size_hints.max_width < window->size_hints.min_width)
+ {
+ /* another cracksmoker */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s sets max width %d less than min width %d, "
+ "disabling resize\n",
+ window->desc,
+ window->size_hints.max_width,
+ window->size_hints.min_width);
+ maxw = window->size_hints.max_width = window->size_hints.min_width;
+ }
+ if (window->size_hints.max_height < window->size_hints.min_height)
+ {
+ /* another cracksmoker */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s sets max height %d less than min height %d, "
+ "disabling resize\n",
+ window->desc,
+ window->size_hints.max_height,
+ window->size_hints.min_height);
+ maxh = window->size_hints.max_height = window->size_hints.min_height;
+ }
+
+ /* Make sure the aspect ratio hints are sane. */
+ minr = window->size_hints.min_aspect.x /
+ (double)window->size_hints.min_aspect.y;
+ maxr = window->size_hints.max_aspect.x /
+ (double)window->size_hints.max_aspect.y;
+ if (minr > maxr)
+ {
+ /* another cracksmoker; not even minimally (self) consistent */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s sets min aspect ratio larger than max aspect "
+ "ratio; disabling aspect ratio constraints.\n",
+ window->desc);
+ window->size_hints.min_aspect.x = 1;
+ window->size_hints.min_aspect.y = G_MAXINT;
+ window->size_hints.max_aspect.x = G_MAXINT;
+ window->size_hints.max_aspect.y = 1;
+ }
+ else /* check consistency of aspect ratio hints with other hints */
+ {
+ if (minh > 0 && minr > (maxw / (double)minh))
+ {
+ /* another cracksmoker */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s sets min aspect ratio larger than largest "
+ "aspect ratio possible given min/max size constraints; "
+ "disabling min aspect ratio constraint.\n",
+ window->desc);
+ window->size_hints.min_aspect.x = 1;
+ window->size_hints.min_aspect.y = G_MAXINT;
+ }
+ if (maxr < (minw / (double)maxh))
+ {
+ /* another cracksmoker */
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Window %s sets max aspect ratio smaller than smallest "
+ "aspect ratio possible given min/max size constraints; "
+ "disabling max aspect ratio constraint.\n",
+ window->desc);
+ window->size_hints.max_aspect.x = G_MAXINT;
+ window->size_hints.max_aspect.y = 1;
+ }
+ /* FIXME: Would be nice to check that aspect ratios are
+ * consistent with base and size increment constraints.
+ */
+ }
+}
+
+static void
+reload_normal_hints (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+ XSizeHints old_hints;
+
+ meta_topic (META_DEBUG_GEOMETRY, "Updating WM_NORMAL_HINTS for %s\n", window->desc);
+
+ old_hints = window->size_hints;
+
+ meta_set_normal_hints (window, value->v.size_hints.hints);
+
+ spew_size_hints_differences (&old_hints, &window->size_hints);
+
+ meta_window_recalc_features (window);
+
+ if (!initial)
+ meta_window_queue(window, META_QUEUE_MOVE_RESIZE);
+ }
+}
+
+static void
+reload_wm_protocols (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ int i;
+
+ window->take_focus = FALSE;
+ window->delete_window = FALSE;
+ window->net_wm_ping = FALSE;
+
+ if (value->type == META_PROP_VALUE_INVALID)
+ return;
+
+ i = 0;
+ while (i < value->v.atom_list.n_atoms)
+ {
+ if (value->v.atom_list.atoms[i] ==
+ window->display->atom_WM_TAKE_FOCUS)
+ window->take_focus = TRUE;
+ else if (value->v.atom_list.atoms[i] ==
+ window->display->atom_WM_DELETE_WINDOW)
+ window->delete_window = TRUE;
+ else if (value->v.atom_list.atoms[i] ==
+ window->display->atom__NET_WM_PING)
+ window->net_wm_ping = TRUE;
+ ++i;
+ }
+
+ meta_verbose ("New _NET_STARTUP_ID \"%s\" for %s\n",
+ window->startup_id ? window->startup_id : "unset",
+ window->desc);
+}
+
+static void
+reload_wm_hints (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ Window old_group_leader;
+
+ old_group_leader = window->xgroup_leader;
+
+ /* Fill in defaults */
+ window->input = TRUE;
+ window->initially_iconic = FALSE;
+ window->xgroup_leader = None;
+ window->wm_hints_pixmap = None;
+ window->wm_hints_mask = None;
+
+ if (value->type != META_PROP_VALUE_INVALID)
+ {
+ const XWMHints *hints = value->v.wm_hints;
+
+ if (hints->flags & InputHint)
+ window->input = hints->input;
+
+ if (hints->flags & StateHint)
+ window->initially_iconic = (hints->initial_state == IconicState);
+
+ if (hints->flags & WindowGroupHint)
+ window->xgroup_leader = hints->window_group;
+
+ if (hints->flags & IconPixmapHint)
+ window->wm_hints_pixmap = hints->icon_pixmap;
+
+ if (hints->flags & IconMaskHint)
+ window->wm_hints_mask = hints->icon_mask;
+
+ meta_verbose ("Read WM_HINTS input: %d iconic: %d group leader: 0x%lx pixmap: 0x%lx mask: 0x%lx\n",
+ window->input, window->initially_iconic,
+ window->xgroup_leader,
+ window->wm_hints_pixmap,
+ window->wm_hints_mask);
+ }
+
+ if (window->xgroup_leader != old_group_leader)
+ {
+ meta_verbose ("Window %s changed its group leader to 0x%lx\n",
+ window->desc, window->xgroup_leader);
+
+ meta_window_group_leader_changed (window);
+ }
+
+ meta_icon_cache_property_changed (&window->icon_cache,
+ window->display,
+ XA_WM_HINTS);
+
+ meta_window_queue (window, META_QUEUE_UPDATE_ICON | META_QUEUE_MOVE_RESIZE);
+}
+
+static void
+reload_transient_for (MetaWindow *window,
+ MetaPropValue *value,
+ gboolean initial)
+{
+ window->xtransient_for = None;
+
+ if (value->type != META_PROP_VALUE_INVALID)
+ window->xtransient_for = value->v.xwindow;
+
+ /* Make sure transient_for is valid */
+ if (window->xtransient_for != None &&
+ meta_display_lookup_x_window (window->display,
+ window->xtransient_for) == NULL)
+ {
+ meta_warning (_("Invalid WM_TRANSIENT_FOR window 0x%lx specified "
+ "for %s.\n"),
+ window->xtransient_for, window->desc);
+ window->xtransient_for = None;
+ }
+
+ window->transient_parent_is_root_window =
+ window->xtransient_for == window->screen->xroot;
+
+ if (window->xtransient_for != None)
+ meta_verbose ("Window %s transient for 0x%lx (root = %d)\n", window->desc,
+ window->xtransient_for, window->transient_parent_is_root_window);
+ else
+ meta_verbose ("Window %s is not transient\n", window->desc);
+
+ /* may now be a dialog */
+ meta_window_recalc_window_type (window);
+
+ /* update stacking constraints */
+ meta_stack_update_transient (window->screen->stack, window);
+
+ /* possibly change its group. We treat being a window's transient as
+ * equivalent to making it your group leader, to work around shortcomings
+ * in programs such as xmms-- see #328211.
+ */
+ if (window->xtransient_for != None &&
+ window->xgroup_leader != None &&
+ window->xtransient_for != window->xgroup_leader)
+ meta_window_group_leader_changed (window);
+
+ if (!window->constructing)
+ meta_window_queue (window, META_QUEUE_MOVE_RESIZE);
+}
+
+/**
+ * Initialises the property hooks system. Each row in the table named "hooks"
+ * represents an action to take when a property is found on a newly-created
+ * window, or when a property changes its value.
+ *
+ * The first column shows which atom the row concerns.
+ * The second gives the type of the property data. The property will be
+ * queried for its new value, unless the type is given as
+ * META_PROP_VALUE_INVALID, in which case nothing will be queried.
+ * The third column gives the name of a callback which gets called with the
+ * new value. (If the new value was not retrieved because the second column
+ * was META_PROP_VALUE_INVALID, the callback still gets called anyway.)
+ * This value may be NULL, in which case no callback will be called.
+ */
+void
+meta_display_init_window_prop_hooks (MetaDisplay *display)
+{
+ MetaWindowPropHooks hooks[] = {
+ { display->atom_WM_STATE, META_PROP_VALUE_INVALID, NULL },
+ { display->atom_WM_CLIENT_MACHINE, META_PROP_VALUE_STRING, reload_wm_client_machine },
+ { display->atom__NET_WM_PID, META_PROP_VALUE_CARDINAL, reload_net_wm_pid },
+ { display->atom__NET_WM_USER_TIME, META_PROP_VALUE_CARDINAL, reload_net_wm_user_time },
+ { display->atom__NET_WM_NAME, META_PROP_VALUE_UTF8, reload_net_wm_name },
+ { XA_WM_NAME, META_PROP_VALUE_TEXT_PROPERTY, reload_wm_name },
+ { display->atom__NET_WM_ICON, META_PROP_VALUE_INVALID, reload_net_wm_icon },
+ { display->atom__KWM_WIN_ICON, META_PROP_VALUE_INVALID, reload_kwm_win_icon },
+ { display->atom__NET_WM_ICON_NAME, META_PROP_VALUE_UTF8, reload_net_wm_icon_name },
+ { XA_WM_ICON_NAME, META_PROP_VALUE_TEXT_PROPERTY, reload_wm_icon_name },
+ { display->atom__NET_WM_STATE, META_PROP_VALUE_ATOM_LIST, reload_net_wm_state },
+ { display->atom__MOTIF_WM_HINTS, META_PROP_VALUE_MOTIF_HINTS, reload_mwm_hints },
+ { display->atom__NET_WM_ICON_GEOMETRY, META_PROP_VALUE_INVALID, NULL },
+ { XA_WM_CLASS, META_PROP_VALUE_CLASS_HINT, reload_wm_class },
+ { display->atom_WM_CLIENT_LEADER, META_PROP_VALUE_INVALID, complain_about_broken_client },
+ { display->atom_SM_CLIENT_ID, META_PROP_VALUE_INVALID, complain_about_broken_client },
+ { display->atom_WM_WINDOW_ROLE, META_PROP_VALUE_INVALID, reload_wm_window_role },
+ { display->atom__NET_WM_WINDOW_TYPE, META_PROP_VALUE_INVALID, reload_net_wm_window_type },
+ { display->atom__NET_WM_DESKTOP, META_PROP_VALUE_CARDINAL, reload_net_wm_desktop },
+ { display->atom__NET_WM_STRUT, META_PROP_VALUE_INVALID, reload_struts },
+ { display->atom__NET_WM_STRUT_PARTIAL, META_PROP_VALUE_INVALID, reload_struts },
+ { display->atom__NET_STARTUP_ID, META_PROP_VALUE_UTF8, reload_net_startup_id },
+ { display->atom__NET_WM_SYNC_REQUEST_COUNTER, META_PROP_VALUE_SYNC_COUNTER, reload_update_counter },
+ { XA_WM_NORMAL_HINTS, META_PROP_VALUE_SIZE_HINTS, reload_normal_hints },
+ { display->atom_WM_PROTOCOLS, META_PROP_VALUE_ATOM_LIST, reload_wm_protocols },
+ { XA_WM_HINTS, META_PROP_VALUE_WM_HINTS, reload_wm_hints },
+ { XA_WM_TRANSIENT_FOR, META_PROP_VALUE_WINDOW, reload_transient_for },
+ { display->atom__NET_WM_USER_TIME_WINDOW, META_PROP_VALUE_WINDOW, reload_net_wm_user_time_window },
+ { 0 },
+ };
+
+ MetaWindowPropHooks *table = g_memdup (hooks, sizeof (hooks)),
+ *cursor = table;
+
+ g_assert (display->prop_hooks == NULL);
+
+ display->prop_hooks_table = (gpointer) table;
+ display->prop_hooks = g_hash_table_new (NULL, NULL);
+
+ while (cursor->property)
+ {
+ /* Atoms are safe to use with GINT_TO_POINTER because it's safe with
+ * anything 32 bits or less, and atoms are 32 bits with the top three
+ * bits clear. (Scheifler & Gettys, 2e, p372)
+ */
+ g_hash_table_insert (display->prop_hooks,
+ GINT_TO_POINTER (cursor->property),
+ cursor);
+ cursor++;
+ }
+}
+
+void
+meta_display_free_window_prop_hooks (MetaDisplay *display)
+{
+ g_hash_table_unref (display->prop_hooks);
+ display->prop_hooks = NULL;
+
+ g_free (display->prop_hooks_table);
+ display->prop_hooks_table = NULL;
+}
+
+/**
+ * Finds the hooks for a particular property.
+ */
+static MetaWindowPropHooks*
+find_hooks (MetaDisplay *display,
+ Atom property)
+{
+ return g_hash_table_lookup (display->prop_hooks,
+ GINT_TO_POINTER (property));
+}
diff --git a/src/core/window-props.h b/src/core/window-props.h
new file mode 100644
index 00000000..1e60eff0
--- /dev/null
+++ b/src/core/window-props.h
@@ -0,0 +1,129 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file window-props.h MetaWindow property handling
+ *
+ * A system which can inspect sets of properties of given windows
+ * and take appropriate action given their values.
+ *
+ * Note that all the meta_window_reload_propert* functions require a
+ * round trip to the server.
+ */
+
+/*
+ * Copyright (C) 2001, 2002 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_WINDOW_PROPS_H
+#define META_WINDOW_PROPS_H
+
+#include "window-private.h"
+
+/**
+ * Requests the current values of a single property for a given
+ * window from the server, and deals with it appropriately.
+ * Does not return it to the caller (it's been dealt with!)
+ *
+ * \param window The window.
+ * \param property A single X atom.
+ */
+void meta_window_reload_property (MetaWindow *window,
+ Atom property,
+ gboolean initial);
+
+
+/**
+ * Requests the current values of a set of properties for a given
+ * window from the server, and deals with them appropriately.
+ * Does not return them to the caller (they've been dealt with!)
+ *
+ * \param window The window.
+ * \param properties A pointer to a list of X atoms, "n_properties" long.
+ * \param n_properties The length of the properties list.
+ */
+void meta_window_reload_properties (MetaWindow *window,
+ const Atom *properties,
+ int n_properties,
+ gboolean initial);
+
+/**
+ * Requests the current values of a single property for a given
+ * window from the server, and deals with it appropriately.
+ * Does not return it to the caller (it's been dealt with!)
+ *
+ * \param window A window on the same display as the one we're
+ * investigating (only used to find the display)
+ * \param xwindow The X handle for the window.
+ * \param property A single X atom.
+ */
+void meta_window_reload_property_from_xwindow
+ (MetaWindow *window,
+ Window xwindow,
+ Atom property,
+ gboolean initial);
+
+/**
+ * Requests the current values of a set of properties for a given
+ * window from the server, and deals with them appropriately.
+ * Does not return them to the caller (they've been dealt with!)
+ *
+ * \param window A window on the same display as the one we're
+ * investigating (only used to find the display)
+ * \param xwindow The X handle for the window.
+ * \param properties A pointer to a list of X atoms, "n_properties" long.
+ * \param n_properties The length of the properties list.
+ */
+void meta_window_reload_properties_from_xwindow
+ (MetaWindow *window,
+ Window xwindow,
+ const Atom *properties,
+ int n_properties,
+ gboolean initial);
+
+/**
+ * Initialises the hooks used for the reload_propert* functions
+ * on a particular display, and stores a pointer to them in the
+ * display.
+ *
+ * \param display The display.
+ */
+void meta_display_init_window_prop_hooks (MetaDisplay *display);
+
+/**
+ * Frees the hooks used for the reload_propert* functions
+ * for a particular display.
+ *
+ * \param display The display.
+ */
+void meta_display_free_window_prop_hooks (MetaDisplay *display);
+
+/**
+ * Sets the size hints for a window. This happens when a
+ * WM_NORMAL_HINTS property is set on a window, but it is public
+ * because the size hints are set to defaults when a window is
+ * created. See
+ * http://tronche.com/gui/x/icccm/sec-4.html#WM_NORMAL_HINTS
+ * for the X details.
+ *
+ * \param window The window to set the size hints on.
+ * \param hints Either some X size hints, or NULL for default.
+ */
+void meta_set_normal_hints (MetaWindow *window,
+ XSizeHints *hints);
+
+#endif /* META_WINDOW_PROPS_H */
diff --git a/src/core/window.c b/src/core/window.c
new file mode 100644
index 00000000..08ab9ec9
--- /dev/null
+++ b/src/core/window.c
@@ -0,0 +1,8178 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco X managed windows */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington, Anders Carlsson
+ * Copyright (C) 2002, 2003 Red Hat, Inc.
+ * Copyright (C) 2003 Rob Adams
+ * Copyright (C) 2004-2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "window-private.h"
+#include "edge-resistance.h"
+#include "util.h"
+#include "frame-private.h"
+#include "errors.h"
+#include "workspace.h"
+#include "stack.h"
+#include "keybindings.h"
+#include "ui.h"
+#include "place.h"
+#include "session.h"
+#include "effects.h"
+#include "prefs.h"
+#include "resizepopup.h"
+#include "xprops.h"
+#include "group.h"
+#include "window-props.h"
+#include "constraints.h"
+#include "compositor.h"
+#include "effects.h"
+
+#include <X11/Xatom.h>
+#include <string.h>
+
+#ifdef HAVE_SHAPE
+#include <X11/extensions/shape.h>
+#endif
+
+static int destroying_windows_disallowed = 0;
+
+
+static void update_sm_hints (MetaWindow *window);
+static void update_net_frame_extents (MetaWindow *window);
+static void recalc_window_type (MetaWindow *window);
+static void recalc_window_features (MetaWindow *window);
+static void invalidate_work_areas (MetaWindow *window);
+static void recalc_window_type (MetaWindow *window);
+static void set_wm_state (MetaWindow *window,
+ int state);
+static void set_net_wm_state (MetaWindow *window);
+
+static void send_configure_notify (MetaWindow *window);
+static gboolean process_property_notify (MetaWindow *window,
+ XPropertyEvent *event);
+static void meta_window_show (MetaWindow *window);
+static void meta_window_hide (MetaWindow *window);
+
+static void meta_window_save_rect (MetaWindow *window);
+static void save_user_window_placement (MetaWindow *window);
+static void force_save_user_window_placement (MetaWindow *window);
+
+static void meta_window_move_resize_internal (MetaWindow *window,
+ MetaMoveResizeFlags flags,
+ int resize_gravity,
+ int root_x_nw,
+ int root_y_nw,
+ int w,
+ int h);
+
+static void ensure_mru_position_after (MetaWindow *window,
+ MetaWindow *after_this_one);
+
+
+static void meta_window_move_resize_now (MetaWindow *window);
+
+static void meta_window_unqueue (MetaWindow *window, guint queuebits);
+
+static void update_move (MetaWindow *window,
+ gboolean snap,
+ int x,
+ int y);
+static gboolean update_move_timeout (gpointer data);
+static void update_resize (MetaWindow *window,
+ gboolean snap,
+ int x,
+ int y,
+ gboolean force);
+static gboolean update_resize_timeout (gpointer data);
+
+
+static void meta_window_flush_calc_showing (MetaWindow *window);
+
+static gboolean queue_calc_showing_func (MetaWindow *window,
+ void *data);
+
+static void meta_window_apply_session_info (MetaWindow *window,
+ const MetaWindowSessionInfo *info);
+
+static void unmaximize_window_before_freeing (MetaWindow *window);
+static void unminimize_window_and_all_transient_parents (MetaWindow *window);
+
+/* Idle handlers for the three queues. The "data" parameter in each case
+ * will be a GINT_TO_POINTER of the index into the queue arrays to use.
+ *
+ * TODO: Possibly there is still some code duplication among these, which we
+ * need to sort out at some point.
+ */
+static gboolean idle_calc_showing (gpointer data);
+static gboolean idle_move_resize (gpointer data);
+static gboolean idle_update_icon (gpointer data);
+
+#ifdef WITH_VERBOSE_MODE
+static const char*
+wm_state_to_string (int state)
+{
+ switch (state)
+ {
+ case NormalState:
+ return "NormalState";
+ case IconicState:
+ return "IconicState";
+ case WithdrawnState:
+ return "WithdrawnState";
+ }
+
+ return "Unknown";
+}
+#endif
+
+static gboolean
+is_desktop_or_dock_foreach (MetaWindow *window,
+ void *data)
+{
+ gboolean *result = data;
+
+ *result =
+ window->type == META_WINDOW_DESKTOP ||
+ window->type == META_WINDOW_DOCK;
+ if (*result)
+ return FALSE; /* stop as soon as we find one */
+ else
+ return TRUE;
+}
+
+/* window is the window that's newly mapped provoking
+ * the possible change
+ */
+static void
+maybe_leave_show_desktop_mode (MetaWindow *window)
+{
+ gboolean is_desktop_or_dock;
+
+ if (!window->screen->active_workspace->showing_desktop)
+ return;
+
+ /* If the window is a transient for the dock or desktop, don't
+ * leave show desktop mode when the window opens. That's
+ * so you can e.g. hide all windows, manipulate a file on
+ * the desktop via a dialog, then unshow windows again.
+ */
+ is_desktop_or_dock = FALSE;
+ is_desktop_or_dock_foreach (window,
+ &is_desktop_or_dock);
+
+ meta_window_foreach_ancestor (window, is_desktop_or_dock_foreach,
+ &is_desktop_or_dock);
+
+ if (!is_desktop_or_dock)
+ {
+ meta_screen_minimize_all_on_active_workspace_except (window->screen,
+ window);
+ meta_screen_unshow_desktop (window->screen);
+ }
+}
+
+MetaWindow*
+meta_window_new (MetaDisplay *display,
+ Window xwindow,
+ gboolean must_be_viewable)
+{
+ XWindowAttributes attrs;
+ MetaWindow *window;
+
+ meta_display_grab (display);
+ meta_error_trap_push (display); /* Push a trap over all of window
+ * creation, to reduce XSync() calls
+ */
+
+ meta_error_trap_push_with_return (display);
+
+ if (XGetWindowAttributes (display->xdisplay,xwindow, &attrs))
+ {
+ if(meta_error_trap_pop_with_return (display, TRUE) != Success)
+ {
+ meta_verbose ("Failed to get attributes for window 0x%lx\n",
+ xwindow);
+ meta_error_trap_pop (display, TRUE);
+ meta_display_ungrab (display);
+ return NULL;
+ }
+ window = meta_window_new_with_attrs (display, xwindow,
+ must_be_viewable, &attrs);
+ }
+ else
+ {
+ meta_error_trap_pop_with_return (display, TRUE);
+ meta_verbose ("Failed to get attributes for window 0x%lx\n",
+ xwindow);
+ meta_error_trap_pop (display, TRUE);
+ meta_display_ungrab (display);
+ return NULL;
+ }
+
+
+ meta_error_trap_pop (display, FALSE);
+ meta_display_ungrab (display);
+
+ return window;
+}
+
+MetaWindow*
+meta_window_new_with_attrs (MetaDisplay *display,
+ Window xwindow,
+ gboolean must_be_viewable,
+ XWindowAttributes *attrs)
+{
+ MetaWindow *window;
+ GSList *tmp;
+ MetaWorkspace *space;
+ gulong existing_wm_state;
+ gulong event_mask;
+ MetaMoveResizeFlags flags;
+#define N_INITIAL_PROPS 19
+ Atom initial_props[N_INITIAL_PROPS];
+ int i;
+ gboolean has_shape;
+
+ g_assert (attrs != NULL);
+ g_assert (N_INITIAL_PROPS == (int) G_N_ELEMENTS (initial_props));
+
+ meta_verbose ("Attempting to manage 0x%lx\n", xwindow);
+
+ if (meta_display_xwindow_is_a_no_focus_window (display, xwindow))
+ {
+ meta_verbose ("Not managing no_focus_window 0x%lx\n",
+ xwindow);
+ return NULL;
+ }
+
+ if (attrs->override_redirect)
+ {
+ meta_verbose ("Deciding not to manage override_redirect window 0x%lx\n", xwindow);
+ return NULL;
+ }
+
+ /* Grab server */
+ meta_display_grab (display);
+ meta_error_trap_push (display); /* Push a trap over all of window
+ * creation, to reduce XSync() calls
+ */
+
+ meta_verbose ("must_be_viewable = %d attrs->map_state = %d (%s)\n",
+ must_be_viewable,
+ attrs->map_state,
+ (attrs->map_state == IsUnmapped) ?
+ "IsUnmapped" :
+ (attrs->map_state == IsViewable) ?
+ "IsViewable" :
+ (attrs->map_state == IsUnviewable) ?
+ "IsUnviewable" :
+ "(unknown)");
+
+ existing_wm_state = WithdrawnState;
+ if (must_be_viewable && attrs->map_state != IsViewable)
+ {
+ /* Only manage if WM_STATE is IconicState or NormalState */
+ gulong state;
+
+ /* WM_STATE isn't a cardinal, it's type WM_STATE, but is an int */
+ if (!(meta_prop_get_cardinal_with_atom_type (display, xwindow,
+ display->atom_WM_STATE,
+ display->atom_WM_STATE,
+ &state) &&
+ (state == IconicState || state == NormalState)))
+ {
+ meta_verbose ("Deciding not to manage unmapped or unviewable window 0x%lx\n", xwindow);
+ meta_error_trap_pop (display, TRUE);
+ meta_display_ungrab (display);
+ return NULL;
+ }
+
+ existing_wm_state = state;
+ meta_verbose ("WM_STATE of %lx = %s\n", xwindow,
+ wm_state_to_string (existing_wm_state));
+ }
+
+ meta_error_trap_push_with_return (display);
+
+ XAddToSaveSet (display->xdisplay, xwindow);
+
+ event_mask =
+ PropertyChangeMask | EnterWindowMask | LeaveWindowMask |
+ FocusChangeMask | ColormapChangeMask;
+
+ XSelectInput (display->xdisplay, xwindow, event_mask);
+
+ has_shape = FALSE;
+#ifdef HAVE_SHAPE
+ if (META_DISPLAY_HAS_SHAPE (display))
+ {
+ int x_bounding, y_bounding, x_clip, y_clip;
+ unsigned w_bounding, h_bounding, w_clip, h_clip;
+ int bounding_shaped, clip_shaped;
+
+ XShapeSelectInput (display->xdisplay, xwindow, ShapeNotifyMask);
+
+ XShapeQueryExtents (display->xdisplay, xwindow,
+ &bounding_shaped, &x_bounding, &y_bounding,
+ &w_bounding, &h_bounding,
+ &clip_shaped, &x_clip, &y_clip,
+ &w_clip, &h_clip);
+
+ has_shape = bounding_shaped != FALSE;
+
+ meta_topic (META_DEBUG_SHAPES,
+ "Window has_shape = %d extents %d,%d %u x %u\n",
+ has_shape, x_bounding, y_bounding,
+ w_bounding, h_bounding);
+ }
+#endif
+
+ /* Get rid of any borders */
+ if (attrs->border_width != 0)
+ XSetWindowBorderWidth (display->xdisplay, xwindow, 0);
+
+ /* Get rid of weird gravities */
+ if (attrs->win_gravity != NorthWestGravity)
+ {
+ XSetWindowAttributes set_attrs;
+
+ set_attrs.win_gravity = NorthWestGravity;
+
+ XChangeWindowAttributes (display->xdisplay,
+ xwindow,
+ CWWinGravity,
+ &set_attrs);
+ }
+
+ if (meta_error_trap_pop_with_return (display, FALSE) != Success)
+ {
+ meta_verbose ("Window 0x%lx disappeared just as we tried to manage it\n",
+ xwindow);
+ meta_error_trap_pop (display, FALSE);
+ meta_display_ungrab (display);
+ return NULL;
+ }
+
+ g_assert (!attrs->override_redirect);
+
+ window = g_new (MetaWindow, 1);
+
+ window->constructing = TRUE;
+
+ window->dialog_pid = -1;
+
+ window->xwindow = xwindow;
+
+ /* this is in window->screen->display, but that's too annoying to
+ * type
+ */
+ window->display = display;
+ window->workspace = NULL;
+
+#ifdef HAVE_XSYNC
+ window->sync_request_counter = None;
+ window->sync_request_serial = 0;
+ window->sync_request_time.tv_sec = 0;
+ window->sync_request_time.tv_usec = 0;
+#endif
+
+ window->screen = NULL;
+ tmp = display->screens;
+ while (tmp != NULL)
+ {
+ MetaScreen *scr = tmp->data;
+
+ if (scr->xroot == attrs->root)
+ {
+ window->screen = tmp->data;
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_assert (window->screen);
+
+ window->desc = g_strdup_printf ("0x%lx", window->xwindow);
+
+ /* avoid tons of stack updates */
+ meta_stack_freeze (window->screen->stack);
+
+ window->has_shape = has_shape;
+
+ window->rect.x = attrs->x;
+ window->rect.y = attrs->y;
+ window->rect.width = attrs->width;
+ window->rect.height = attrs->height;
+
+ /* And border width, size_hints are the "request" */
+ window->border_width = attrs->border_width;
+ window->size_hints.x = attrs->x;
+ window->size_hints.y = attrs->y;
+ window->size_hints.width = attrs->width;
+ window->size_hints.height = attrs->height;
+ /* initialize the remaining size_hints as if size_hints.flags were zero */
+ meta_set_normal_hints (window, NULL);
+
+ /* And this is our unmaximized size */
+ window->saved_rect = window->rect;
+ window->user_rect = window->rect;
+
+ window->depth = attrs->depth;
+ window->xvisual = attrs->visual;
+ window->colormap = attrs->colormap;
+
+ window->title = NULL;
+ window->icon_name = NULL;
+ window->icon = NULL;
+ window->mini_icon = NULL;
+ meta_icon_cache_init (&window->icon_cache);
+ window->wm_hints_pixmap = None;
+ window->wm_hints_mask = None;
+
+ window->frame = NULL;
+ window->has_focus = FALSE;
+
+ window->maximized_horizontally = FALSE;
+ window->maximized_vertically = FALSE;
+ window->maximize_horizontally_after_placement = FALSE;
+ window->maximize_vertically_after_placement = FALSE;
+ window->minimize_after_placement = FALSE;
+ window->fullscreen_after_placement = FALSE;
+ window->fullscreen_monitors[0] = -1;
+ window->require_fully_onscreen = TRUE;
+ window->require_on_single_xinerama = TRUE;
+ window->require_titlebar_visible = TRUE;
+ window->on_all_workspaces = FALSE;
+ window->shaded = FALSE;
+ window->initially_iconic = FALSE;
+ window->minimized = FALSE;
+ window->was_minimized = FALSE;
+ window->tab_unminimized = FALSE;
+ window->iconic = FALSE;
+ window->mapped = attrs->map_state != IsUnmapped;
+ /* if already mapped, no need to worry about focus-on-first-time-showing */
+ window->showing_for_first_time = !window->mapped;
+ /* if already mapped we don't want to do the placement thing */
+ window->placed = window->mapped;
+ if (window->placed)
+ meta_topic (META_DEBUG_PLACEMENT,
+ "Not placing window 0x%lx since it's already mapped\n",
+ xwindow);
+ window->force_save_user_rect = TRUE;
+ window->denied_focus_and_not_transient = FALSE;
+ window->unmanaging = FALSE;
+ window->is_in_queues = 0;
+ window->keys_grabbed = FALSE;
+ window->grab_on_frame = FALSE;
+ window->all_keys_grabbed = FALSE;
+ window->withdrawn = FALSE;
+ window->initial_workspace_set = FALSE;
+ window->initial_timestamp_set = FALSE;
+ window->net_wm_user_time_set = FALSE;
+ window->user_time_window = None;
+ window->calc_placement = FALSE;
+ window->shaken_loose = FALSE;
+ window->have_focus_click_grab = FALSE;
+ window->disable_sync = FALSE;
+
+ window->unmaps_pending = 0;
+
+ window->mwm_decorated = TRUE;
+ window->mwm_border_only = FALSE;
+ window->mwm_has_close_func = TRUE;
+ window->mwm_has_minimize_func = TRUE;
+ window->mwm_has_maximize_func = TRUE;
+ window->mwm_has_move_func = TRUE;
+ window->mwm_has_resize_func = TRUE;
+
+ window->decorated = TRUE;
+ window->has_close_func = TRUE;
+ window->has_minimize_func = TRUE;
+ window->has_maximize_func = TRUE;
+ window->has_move_func = TRUE;
+ window->has_resize_func = TRUE;
+
+ window->has_shade_func = TRUE;
+
+ window->has_fullscreen_func = TRUE;
+
+ window->always_sticky = FALSE;
+
+ window->wm_state_modal = FALSE;
+ window->skip_taskbar = FALSE;
+ window->skip_pager = FALSE;
+ window->wm_state_skip_taskbar = FALSE;
+ window->wm_state_skip_pager = FALSE;
+ window->wm_state_above = FALSE;
+ window->wm_state_below = FALSE;
+ window->wm_state_demands_attention = FALSE;
+
+ window->res_class = NULL;
+ window->res_name = NULL;
+ window->role = NULL;
+ window->sm_client_id = NULL;
+ window->wm_client_machine = NULL;
+ window->startup_id = NULL;
+
+ window->net_wm_pid = -1;
+
+ window->xtransient_for = None;
+ window->xclient_leader = None;
+ window->transient_parent_is_root_window = FALSE;
+
+ window->type = META_WINDOW_NORMAL;
+ window->type_atom = None;
+
+ window->struts = NULL;
+
+ window->using_net_wm_name = FALSE;
+ window->using_net_wm_visible_name = FALSE;
+ window->using_net_wm_icon_name = FALSE;
+ window->using_net_wm_visible_icon_name = FALSE;
+
+ window->need_reread_icon = TRUE;
+
+ window->layer = META_LAYER_LAST; /* invalid value */
+ window->stack_position = -1;
+ window->initial_workspace = 0; /* not used */
+ window->initial_timestamp = 0; /* not used */
+
+ meta_display_register_x_window (display, &window->xwindow, window);
+
+
+ /* assign the window to its group, or create a new group if needed
+ */
+ window->group = NULL;
+ window->xgroup_leader = None;
+ meta_window_compute_group (window);
+
+ /* Fill these in the order we want them to be gotten. we want to
+ * get window name and class first so we can use them in error
+ * messages and such. However, name is modified depending on
+ * wm_client_machine, so push it slightly sooner.
+ */
+ i = 0;
+ initial_props[i++] = display->atom_WM_CLIENT_MACHINE;
+ initial_props[i++] = display->atom__NET_WM_PID;
+ initial_props[i++] = display->atom__NET_WM_NAME;
+ initial_props[i++] = XA_WM_CLASS;
+ initial_props[i++] = XA_WM_NAME;
+ initial_props[i++] = display->atom__NET_WM_ICON_NAME;
+ initial_props[i++] = XA_WM_ICON_NAME;
+ initial_props[i++] = display->atom__NET_WM_DESKTOP;
+ initial_props[i++] = display->atom__NET_STARTUP_ID;
+ initial_props[i++] = display->atom__NET_WM_SYNC_REQUEST_COUNTER;
+ initial_props[i++] = XA_WM_NORMAL_HINTS;
+ initial_props[i++] = display->atom_WM_PROTOCOLS;
+ initial_props[i++] = XA_WM_HINTS;
+ initial_props[i++] = display->atom__NET_WM_USER_TIME;
+ initial_props[i++] = display->atom__NET_WM_STATE;
+ initial_props[i++] = display->atom__MOTIF_WM_HINTS;
+ initial_props[i++] = XA_WM_TRANSIENT_FOR;
+ initial_props[i++] = display->atom__NET_WM_USER_TIME_WINDOW;
+ initial_props[i++] = display->atom__NET_WM_FULLSCREEN_MONITORS;
+ g_assert (N_INITIAL_PROPS == i);
+
+ meta_window_reload_properties (window, initial_props, N_INITIAL_PROPS, TRUE);
+
+ update_sm_hints (window); /* must come after transient_for */
+ meta_window_update_role (window);
+ meta_window_update_net_wm_type (window);
+ meta_window_update_icon_now (window);
+
+ if (window->initially_iconic)
+ {
+ /* WM_HINTS said minimized */
+ window->minimized = TRUE;
+ meta_verbose ("Window %s asked to start out minimized\n", window->desc);
+ }
+
+ if (existing_wm_state == IconicState)
+ {
+ /* WM_STATE said minimized */
+ window->minimized = TRUE;
+ meta_verbose ("Window %s had preexisting WM_STATE = IconicState, minimizing\n",
+ window->desc);
+
+ /* Assume window was previously placed, though perhaps it's
+ * been iconic its whole life, we have no way of knowing.
+ */
+ window->placed = TRUE;
+ }
+
+ /* Apply any window attributes such as initial workspace
+ * based on startup notification
+ */
+ meta_screen_apply_startup_properties (window->screen, window);
+
+ /* Try to get a "launch timestamp" for the window. If the window is
+ * a transient, we'd like to be able to get a last-usage timestamp
+ * from the parent window. If the window has no parent, there isn't
+ * much we can do...except record the current time so that any children
+ * can use this time as a fallback.
+ */
+ if (!window->net_wm_user_time_set) {
+ MetaWindow *parent = NULL;
+ if (window->xtransient_for)
+ parent = meta_display_lookup_x_window (window->display,
+ window->xtransient_for);
+
+ /* First, maybe the app was launched with startup notification using an
+ * obsolete version of the spec; use that timestamp if it exists.
+ */
+ if (window->initial_timestamp_set)
+ /* NOTE: Do NOT toggle net_wm_user_time_set to true; this is just
+ * being recorded as a fallback for potential transients
+ */
+ window->net_wm_user_time = window->initial_timestamp;
+ else if (parent != NULL)
+ meta_window_set_user_time(window, parent->net_wm_user_time);
+ else
+ /* NOTE: Do NOT toggle net_wm_user_time_set to true; this is just
+ * being recorded as a fallback for potential transients
+ */
+ window->net_wm_user_time =
+ meta_display_get_current_time_roundtrip (window->display);
+ }
+
+ if (window->decorated)
+ meta_window_ensure_frame (window);
+
+ meta_window_grab_keys (window);
+ if (window->type != META_WINDOW_DOCK)
+ {
+ meta_display_grab_window_buttons (window->display, window->xwindow);
+ meta_display_grab_focus_window_button (window->display, window);
+ }
+
+ if (window->type == META_WINDOW_DESKTOP ||
+ window->type == META_WINDOW_DOCK)
+ {
+ /* Change the default, but don't enforce this if the user
+ * focuses the dock/desktop and unsticks it using key shortcuts.
+ * Need to set this before adding to the workspaces so the MRU
+ * lists will be updated.
+ */
+ window->on_all_workspaces = TRUE;
+ }
+
+ /* For the workspace, first honor hints,
+ * if that fails put transients with parents,
+ * otherwise put window on active space
+ */
+
+ if (window->initial_workspace_set)
+ {
+ if (window->initial_workspace == (int) 0xFFFFFFFF)
+ {
+ meta_topic (META_DEBUG_PLACEMENT,
+ "Window %s is initially on all spaces\n",
+ window->desc);
+
+ /* need to set on_all_workspaces first so that it will be
+ * added to all the MRU lists
+ */
+ window->on_all_workspaces = TRUE;
+ meta_workspace_add_window (window->screen->active_workspace, window);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_PLACEMENT,
+ "Window %s is initially on space %d\n",
+ window->desc, window->initial_workspace);
+
+ space =
+ meta_screen_get_workspace_by_index (window->screen,
+ window->initial_workspace);
+
+ if (space)
+ meta_workspace_add_window (space, window);
+ }
+ }
+
+ if (window->workspace == NULL &&
+ window->xtransient_for != None)
+ {
+ /* Try putting dialog on parent's workspace */
+ MetaWindow *parent;
+
+ parent = meta_display_lookup_x_window (window->display,
+ window->xtransient_for);
+
+ if (parent && parent->workspace)
+ {
+ meta_topic (META_DEBUG_PLACEMENT,
+ "Putting window %s on same workspace as parent %s\n",
+ window->desc, parent->desc);
+
+ if (parent->on_all_workspaces)
+ window->on_all_workspaces = TRUE;
+
+ /* this will implicitly add to the appropriate MRU lists
+ */
+ meta_workspace_add_window (parent->workspace, window);
+ }
+ }
+
+ if (window->workspace == NULL)
+ {
+ meta_topic (META_DEBUG_PLACEMENT,
+ "Putting window %s on active workspace\n",
+ window->desc);
+
+ space = window->screen->active_workspace;
+
+ meta_workspace_add_window (space, window);
+ }
+
+ /* for the various on_all_workspaces = TRUE possible above */
+ meta_window_set_current_workspace_hint (window);
+
+ meta_window_update_struts (window);
+
+ /* Must add window to stack before doing move/resize, since the
+ * window might have fullscreen size (i.e. should have been
+ * fullscreen'd; acrobat is one such braindead case; it withdraws
+ * and remaps its window whenever trying to become fullscreen...)
+ * and thus constraints may try to auto-fullscreen it which also
+ * means restacking it.
+ */
+ meta_stack_add (window->screen->stack,
+ window);
+
+ /* Put our state back where it should be,
+ * passing TRUE for is_configure_request, ICCCM says
+ * initial map is handled same as configure request
+ */
+ flags =
+ META_IS_CONFIGURE_REQUEST | META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION;
+ meta_window_move_resize_internal (window,
+ flags,
+ window->size_hints.win_gravity,
+ window->size_hints.x,
+ window->size_hints.y,
+ window->size_hints.width,
+ window->size_hints.height);
+
+ /* Now try applying saved stuff from the session */
+ {
+ const MetaWindowSessionInfo *info;
+
+ info = meta_window_lookup_saved_state (window);
+
+ if (info)
+ {
+ meta_window_apply_session_info (window, info);
+ meta_window_release_saved_state (info);
+ }
+ }
+
+ /* FIXME we have a tendency to set this then immediately
+ * change it again.
+ */
+ set_wm_state (window, window->iconic ? IconicState : NormalState);
+ set_net_wm_state (window);
+
+ /* Sync stack changes */
+ meta_stack_thaw (window->screen->stack);
+
+ /* disable show desktop mode unless we're a desktop component */
+ maybe_leave_show_desktop_mode (window);
+
+ meta_window_queue (window, META_QUEUE_CALC_SHOWING);
+ /* See bug 303284; a transient of the given window can already exist, in which
+ * case we think it should probably be shown.
+ */
+ meta_window_foreach_transient (window,
+ queue_calc_showing_func,
+ NULL);
+ /* See bug 334899; the window may have minimized ancestors
+ * which need to be shown.
+ *
+ * However, we shouldn't unminimize windows here when opening
+ * a new display because that breaks passing _NET_WM_STATE_HIDDEN
+ * between window managers when replacing them; see bug 358042.
+ *
+ * And we shouldn't unminimize windows if they were initially
+ * iconic.
+ */
+ if (!display->display_opening && !window->initially_iconic)
+ unminimize_window_and_all_transient_parents (window);
+
+ meta_error_trap_pop (display, FALSE); /* pop the XSync()-reducing trap */
+ meta_display_ungrab (display);
+
+ window->constructing = FALSE;
+
+ return window;
+}
+
+/* This function should only be called from the end of meta_window_new_with_attrs () */
+static void
+meta_window_apply_session_info (MetaWindow *window,
+ const MetaWindowSessionInfo *info)
+{
+ if (info->stack_position_set)
+ {
+ meta_topic (META_DEBUG_SM,
+ "Restoring stack position %d for window %s\n",
+ info->stack_position, window->desc);
+
+ /* FIXME well, I'm not sure how to do this. */
+ }
+
+ if (info->minimized_set)
+ {
+ meta_topic (META_DEBUG_SM,
+ "Restoring minimized state %d for window %s\n",
+ info->minimized, window->desc);
+
+ if (window->has_minimize_func && info->minimized)
+ meta_window_minimize (window);
+ }
+
+ if (info->maximized_set)
+ {
+ meta_topic (META_DEBUG_SM,
+ "Restoring maximized state %d for window %s\n",
+ info->maximized, window->desc);
+
+ if (window->has_maximize_func && info->maximized)
+ {
+ meta_window_maximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+
+ if (info->saved_rect_set)
+ {
+ meta_topic (META_DEBUG_SM,
+ "Restoring saved rect %d,%d %dx%d for window %s\n",
+ info->saved_rect.x,
+ info->saved_rect.y,
+ info->saved_rect.width,
+ info->saved_rect.height,
+ window->desc);
+
+ window->saved_rect.x = info->saved_rect.x;
+ window->saved_rect.y = info->saved_rect.y;
+ window->saved_rect.width = info->saved_rect.width;
+ window->saved_rect.height = info->saved_rect.height;
+ }
+ }
+ }
+
+ if (info->on_all_workspaces_set)
+ {
+ window->on_all_workspaces = info->on_all_workspaces;
+ meta_topic (META_DEBUG_SM,
+ "Restoring sticky state %d for window %s\n",
+ window->on_all_workspaces, window->desc);
+ }
+
+ if (info->workspace_indices)
+ {
+ GSList *tmp;
+ GSList *spaces;
+
+ spaces = NULL;
+
+ tmp = info->workspace_indices;
+ while (tmp != NULL)
+ {
+ MetaWorkspace *space;
+
+ space =
+ meta_screen_get_workspace_by_index (window->screen,
+ GPOINTER_TO_INT (tmp->data));
+
+ if (space)
+ spaces = g_slist_prepend (spaces, space);
+
+ tmp = tmp->next;
+ }
+
+ if (spaces)
+ {
+ /* This briefly breaks the invariant that we are supposed
+ * to always be on some workspace. But we paranoically
+ * ensured that one of the workspaces from the session was
+ * indeed valid, so we know we'll go right back to one.
+ */
+ if (window->workspace)
+ meta_workspace_remove_window (window->workspace, window);
+
+ /* Only restore to the first workspace if the window
+ * happened to be on more than one, since we have replaces
+ * window->workspaces with window->workspace
+ */
+ meta_workspace_add_window (spaces->data, window);
+
+ meta_topic (META_DEBUG_SM,
+ "Restoring saved window %s to workspace %d\n",
+ window->desc,
+ meta_workspace_index (spaces->data));
+
+ g_slist_free (spaces);
+ }
+ }
+
+ if (info->geometry_set)
+ {
+ int x, y, w, h;
+ MetaMoveResizeFlags flags;
+
+ window->placed = TRUE; /* don't do placement algorithms later */
+
+ x = info->rect.x;
+ y = info->rect.y;
+
+ w = window->size_hints.base_width +
+ info->rect.width * window->size_hints.width_inc;
+ h = window->size_hints.base_height +
+ info->rect.height * window->size_hints.height_inc;
+
+ /* Force old gravity, ignoring anything now set */
+ window->size_hints.win_gravity = info->gravity;
+
+ meta_topic (META_DEBUG_SM,
+ "Restoring pos %d,%d size %d x %d for %s\n",
+ x, y, w, h, window->desc);
+
+ flags = META_DO_GRAVITY_ADJUST | META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION;
+ meta_window_move_resize_internal (window,
+ flags,
+ window->size_hints.win_gravity,
+ x, y, w, h);
+ }
+}
+
+void
+meta_window_free (MetaWindow *window,
+ guint32 timestamp)
+{
+ GList *tmp;
+
+ meta_verbose ("Unmanaging 0x%lx\n", window->xwindow);
+
+ if (window->display->compositor)
+ meta_compositor_free_window (window->display->compositor, window);
+
+ if (window->display->window_with_menu == window)
+ {
+ meta_ui_window_menu_free (window->display->window_menu);
+ window->display->window_menu = NULL;
+ window->display->window_with_menu = NULL;
+ }
+
+ if (destroying_windows_disallowed > 0)
+ meta_bug ("Tried to destroy window %s while destruction was not allowed\n",
+ window->desc);
+
+ window->unmanaging = TRUE;
+
+ if (window->fullscreen)
+ {
+ MetaGroup *group;
+
+ /* If the window is fullscreen, it may be forcing
+ * other windows in its group to a higher layer
+ */
+
+ meta_stack_freeze (window->screen->stack);
+ group = meta_window_get_group (window);
+ if (group)
+ meta_group_update_layers (group);
+ meta_stack_thaw (window->screen->stack);
+ }
+
+ meta_window_shutdown_group (window); /* safe to do this early as
+ * group.c won't re-add to the
+ * group if window->unmanaging
+ */
+
+ /* If we have the focus, focus some other window.
+ * This is done first, so that if the unmap causes
+ * an EnterNotify the EnterNotify will have final say
+ * on what gets focused, maintaining sloppy focus
+ * invariants.
+ */
+ if (window->has_focus)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing default window since we're unmanaging %s\n",
+ window->desc);
+ meta_workspace_focus_default_window (window->screen->active_workspace,
+ window,
+ timestamp);
+ }
+ else if (window->display->expected_focus_window == window)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing default window since expected focus window freed %s\n",
+ window->desc);
+ window->display->expected_focus_window = NULL;
+ meta_workspace_focus_default_window (window->screen->active_workspace,
+ window,
+ timestamp);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Unmanaging window %s which doesn't currently have focus\n",
+ window->desc);
+ }
+
+ if (window->struts)
+ {
+ meta_free_gslist_and_elements (window->struts);
+ window->struts = NULL;
+
+ meta_topic (META_DEBUG_WORKAREA,
+ "Unmanaging window %s which has struts, so invalidating work areas\n",
+ window->desc);
+ invalidate_work_areas (window);
+ }
+
+ if (window->display->grab_window == window)
+ meta_display_end_grab_op (window->display, timestamp);
+
+ g_assert (window->display->grab_window != window);
+
+ if (window->display->focus_window == window)
+ {
+ window->display->focus_window = NULL;
+ meta_compositor_set_active_window (window->display->compositor,
+ window->screen, NULL);
+ }
+
+ if (window->maximized_horizontally || window->maximized_vertically)
+ unmaximize_window_before_freeing (window);
+
+ /* The XReparentWindow call in meta_window_destroy_frame() moves the
+ * window so we need to send a configure notify; see bug 399552. (We
+ * also do this just in case a window got unmaximized.)
+ */
+ send_configure_notify (window);
+
+ meta_window_unqueue (window, META_QUEUE_CALC_SHOWING |
+ META_QUEUE_MOVE_RESIZE |
+ META_QUEUE_UPDATE_ICON);
+ meta_window_free_delete_dialog (window);
+
+ if (window->workspace)
+ meta_workspace_remove_window (window->workspace, window);
+
+ g_assert (window->workspace == NULL);
+
+#ifndef G_DISABLE_CHECKS
+ tmp = window->screen->workspaces;
+ while (tmp != NULL)
+ {
+ MetaWorkspace *workspace = tmp->data;
+
+ g_assert (g_list_find (workspace->windows, window) == NULL);
+ g_assert (g_list_find (workspace->mru_list, window) == NULL);
+
+ tmp = tmp->next;
+ }
+#endif
+
+ meta_stack_remove (window->screen->stack, window);
+
+ if (window->frame)
+ meta_window_destroy_frame (window);
+
+ if (window->withdrawn)
+ {
+ /* We need to clean off the window's state so it
+ * won't be restored if the app maps it again.
+ */
+ meta_error_trap_push (window->display);
+ meta_verbose ("Cleaning state from window %s\n", window->desc);
+ XDeleteProperty (window->display->xdisplay,
+ window->xwindow,
+ window->display->atom__NET_WM_DESKTOP);
+ XDeleteProperty (window->display->xdisplay,
+ window->xwindow,
+ window->display->atom__NET_WM_STATE);
+ XDeleteProperty (window->display->xdisplay,
+ window->xwindow,
+ window->display->atom__NET_WM_FULLSCREEN_MONITORS);
+ set_wm_state (window, WithdrawnState);
+ meta_error_trap_pop (window->display, FALSE);
+ }
+ else
+ {
+ /* We need to put WM_STATE so that others will understand it on
+ * restart.
+ */
+ if (!window->minimized)
+ {
+ meta_error_trap_push (window->display);
+ set_wm_state (window, NormalState);
+ meta_error_trap_pop (window->display, FALSE);
+ }
+
+ /* And we need to be sure the window is mapped so other WMs
+ * know that it isn't Withdrawn
+ */
+ meta_error_trap_push (window->display);
+ XMapWindow (window->display->xdisplay,
+ window->xwindow);
+ meta_error_trap_pop (window->display, FALSE);
+ }
+
+ meta_window_ungrab_keys (window);
+ meta_display_ungrab_window_buttons (window->display, window->xwindow);
+ meta_display_ungrab_focus_window_button (window->display, window);
+
+ meta_display_unregister_x_window (window->display, window->xwindow);
+
+
+ meta_error_trap_push (window->display);
+
+ /* Put back anything we messed up */
+ if (window->border_width != 0)
+ XSetWindowBorderWidth (window->display->xdisplay,
+ window->xwindow,
+ window->border_width);
+
+ /* No save set */
+ XRemoveFromSaveSet (window->display->xdisplay,
+ window->xwindow);
+
+ /* Don't get events on not-managed windows */
+ XSelectInput (window->display->xdisplay,
+ window->xwindow,
+ NoEventMask);
+
+ /* Stop getting events for the window's _NET_WM_USER_TIME_WINDOW too */
+ if (window->user_time_window != None)
+ {
+ meta_display_unregister_x_window (window->display,
+ window->user_time_window);
+ XSelectInput (window->display->xdisplay,
+ window->user_time_window,
+ NoEventMask);
+ window->user_time_window = None;
+ }
+
+#ifdef HAVE_SHAPE
+ if (META_DISPLAY_HAS_SHAPE (window->display))
+ XShapeSelectInput (window->display->xdisplay, window->xwindow, NoEventMask);
+#endif
+
+ meta_error_trap_pop (window->display, FALSE);
+
+ if (window->icon)
+ g_object_unref (G_OBJECT (window->icon));
+
+ if (window->mini_icon)
+ g_object_unref (G_OBJECT (window->mini_icon));
+
+ meta_icon_cache_free (&window->icon_cache);
+
+ g_free (window->sm_client_id);
+ g_free (window->wm_client_machine);
+ g_free (window->startup_id);
+ g_free (window->role);
+ g_free (window->res_class);
+ g_free (window->res_name);
+ g_free (window->title);
+ g_free (window->icon_name);
+ g_free (window->desc);
+ g_free (window);
+}
+
+static void
+set_wm_state (MetaWindow *window,
+ int state)
+{
+ unsigned long data[2];
+
+ meta_verbose ("Setting wm state %s on %s\n",
+ wm_state_to_string (state), window->desc);
+
+ /* Marco doesn't use icon windows, so data[1] should be None
+ * according to the ICCCM 2.0 Section 4.1.3.1.
+ */
+ data[0] = state;
+ data[1] = None;
+
+ meta_error_trap_push (window->display);
+ XChangeProperty (window->display->xdisplay, window->xwindow,
+ window->display->atom_WM_STATE,
+ window->display->atom_WM_STATE,
+ 32, PropModeReplace, (guchar*) data, 2);
+ meta_error_trap_pop (window->display, FALSE);
+}
+
+static void
+set_net_wm_state (MetaWindow *window)
+{
+ int i;
+ unsigned long data[12];
+
+ i = 0;
+ if (window->shaded)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_SHADED;
+ ++i;
+ }
+ if (window->wm_state_modal)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_MODAL;
+ ++i;
+ }
+ if (window->skip_pager)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_SKIP_PAGER;
+ ++i;
+ }
+ if (window->skip_taskbar)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_SKIP_TASKBAR;
+ ++i;
+ }
+ if (window->maximized_horizontally)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_MAXIMIZED_HORZ;
+ ++i;
+ }
+ if (window->maximized_vertically)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_MAXIMIZED_VERT;
+ ++i;
+ }
+ if (window->fullscreen)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_FULLSCREEN;
+ ++i;
+ }
+ if (!meta_window_showing_on_its_workspace (window) || window->shaded)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_HIDDEN;
+ ++i;
+ }
+ if (window->wm_state_above)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_ABOVE;
+ ++i;
+ }
+ if (window->wm_state_below)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_BELOW;
+ ++i;
+ }
+ if (window->wm_state_demands_attention)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_DEMANDS_ATTENTION;
+ ++i;
+ }
+ if (window->on_all_workspaces)
+ {
+ data[i] = window->display->atom__NET_WM_STATE_STICKY;
+ ++i;
+ }
+
+ meta_verbose ("Setting _NET_WM_STATE with %d atoms\n", i);
+
+ meta_error_trap_push (window->display);
+ XChangeProperty (window->display->xdisplay, window->xwindow,
+ window->display->atom__NET_WM_STATE,
+ XA_ATOM,
+ 32, PropModeReplace, (guchar*) data, i);
+ meta_error_trap_pop (window->display, FALSE);
+
+ if (window->fullscreen)
+ {
+ data[0] = window->fullscreen_monitors[0];
+ data[1] = window->fullscreen_monitors[1];
+ data[2] = window->fullscreen_monitors[2];
+ data[3] = window->fullscreen_monitors[3];
+
+ meta_verbose ("Setting _NET_WM_FULLSCREEN_MONITORS\n");
+ meta_error_trap_push (window->display);
+ XChangeProperty (window->display->xdisplay,
+ window->xwindow,
+ window->display->atom__NET_WM_FULLSCREEN_MONITORS,
+ XA_CARDINAL, 32, PropModeReplace,
+ (guchar*) data, 4);
+ meta_error_trap_pop (window->display, FALSE);
+ }
+}
+
+gboolean
+meta_window_located_on_workspace (MetaWindow *window,
+ MetaWorkspace *workspace)
+{
+ return (window->on_all_workspaces && window->screen == workspace->screen) ||
+ (window->workspace == workspace);
+}
+
+static gboolean
+is_minimized_foreach (MetaWindow *window,
+ void *data)
+{
+ gboolean *result = data;
+
+ *result = window->minimized;
+ if (*result)
+ return FALSE; /* stop as soon as we find one */
+ else
+ return TRUE;
+}
+
+static gboolean
+ancestor_is_minimized (MetaWindow *window)
+{
+ gboolean is_minimized;
+
+ is_minimized = FALSE;
+
+ meta_window_foreach_ancestor (window, is_minimized_foreach, &is_minimized);
+
+ return is_minimized;
+}
+
+gboolean
+meta_window_showing_on_its_workspace (MetaWindow *window)
+{
+ gboolean showing;
+ gboolean is_desktop_or_dock;
+ MetaWorkspace* workspace_of_window;
+
+ showing = TRUE;
+
+ /* 1. See if we're minimized */
+ if (window->minimized)
+ showing = FALSE;
+
+ /* 2. See if we're in "show desktop" mode */
+ is_desktop_or_dock = FALSE;
+ is_desktop_or_dock_foreach (window,
+ &is_desktop_or_dock);
+
+ meta_window_foreach_ancestor (window, is_desktop_or_dock_foreach,
+ &is_desktop_or_dock);
+
+ if (window->on_all_workspaces)
+ workspace_of_window = window->screen->active_workspace;
+ else if (window->workspace)
+ workspace_of_window = window->workspace;
+ else /* This only seems to be needed for startup */
+ workspace_of_window = NULL;
+
+ if (showing &&
+ workspace_of_window && workspace_of_window->showing_desktop &&
+ !is_desktop_or_dock)
+ {
+ meta_verbose ("We're showing the desktop on the workspace(s) that window %s is on\n",
+ window->desc);
+ showing = FALSE;
+ }
+
+ /* 3. See if an ancestor is minimized (note that
+ * ancestor's "mapped" field may not be up to date
+ * since it's being computed in this same idle queue)
+ */
+
+ if (showing)
+ {
+ if (ancestor_is_minimized (window))
+ showing = FALSE;
+ }
+
+#if 0
+ /* 4. See if we're drawing wireframe
+ */
+ if (window->display->grab_window == window &&
+ window->display->grab_wireframe_active)
+ showing = FALSE;
+#endif
+
+ return showing;
+}
+
+gboolean
+meta_window_should_be_showing (MetaWindow *window)
+{
+ gboolean on_workspace;
+
+ meta_verbose ("Should be showing for window %s\n", window->desc);
+
+ /* See if we're on the workspace */
+ on_workspace = meta_window_located_on_workspace (window,
+ window->screen->active_workspace);
+
+ if (!on_workspace)
+ meta_verbose ("Window %s is not on workspace %d\n",
+ window->desc,
+ meta_workspace_index (window->screen->active_workspace));
+ else
+ meta_verbose ("Window %s is on the active workspace %d\n",
+ window->desc,
+ meta_workspace_index (window->screen->active_workspace));
+
+ if (window->on_all_workspaces)
+ meta_verbose ("Window %s is on all workspaces\n", window->desc);
+
+ return on_workspace && meta_window_showing_on_its_workspace (window);
+}
+
+static void
+finish_minimize (gpointer data)
+{
+ MetaWindow *window = data;
+ /* FIXME: It really sucks to put timestamp pinging here; it'd
+ * probably make more sense in implement_showing() so that it's at
+ * least not duplicated in meta_window_show; but since
+ * finish_minimize is a callback making things just slightly icky, I
+ * haven't done that yet.
+ */
+ guint32 timestamp = meta_display_get_current_time_roundtrip (window->display);
+
+ meta_window_hide (window);
+ if (window->has_focus)
+ {
+ meta_workspace_focus_default_window (window->screen->active_workspace,
+ window,
+ timestamp);
+ }
+}
+
+static void
+implement_showing (MetaWindow *window,
+ gboolean showing)
+{
+ /* Actually show/hide the window */
+ meta_verbose ("Implement showing = %d for window %s\n",
+ showing, window->desc);
+
+ if (!showing)
+ {
+ gboolean on_workspace;
+
+ on_workspace = meta_window_located_on_workspace (window,
+ window->screen->active_workspace);
+
+ /* Really this effects code should probably
+ * be in meta_window_hide so the window->mapped
+ * test isn't duplicated here. Anyhow, we animate
+ * if we are mapped now, we are supposed to
+ * be minimized, and we are on the current workspace.
+ */
+ if (on_workspace && window->minimized && window->mapped &&
+ !meta_prefs_get_reduced_resources ())
+ {
+ MetaRectangle icon_rect, window_rect;
+ gboolean result;
+
+ /* Check if the window has an icon geometry */
+ result = meta_window_get_icon_geometry (window, &icon_rect);
+
+ if (!result)
+ {
+ /* just animate into the corner somehow - maybe
+ * not a good idea...
+ */
+ icon_rect.x = window->screen->rect.width;
+ icon_rect.y = window->screen->rect.height;
+ icon_rect.width = 1;
+ icon_rect.height = 1;
+ }
+
+ meta_window_get_outer_rect (window, &window_rect);
+
+ meta_effect_run_minimize (window,
+ &window_rect,
+ &icon_rect,
+ finish_minimize,
+ window);
+ }
+ else
+ {
+ finish_minimize (window);
+ }
+ }
+ else
+ {
+ meta_window_show (window);
+ }
+}
+
+void
+meta_window_calc_showing (MetaWindow *window)
+{
+ implement_showing (window, meta_window_should_be_showing (window));
+}
+
+static guint queue_idle[NUMBER_OF_QUEUES] = {0, 0, 0};
+static GSList *queue_pending[NUMBER_OF_QUEUES] = {NULL, NULL, NULL};
+
+static int
+stackcmp (gconstpointer a, gconstpointer b)
+{
+ MetaWindow *aw = (gpointer) a;
+ MetaWindow *bw = (gpointer) b;
+
+ if (aw->screen != bw->screen)
+ return 0; /* don't care how they sort with respect to each other */
+ else
+ return meta_stack_windows_cmp (aw->screen->stack,
+ aw, bw);
+}
+
+static gboolean
+idle_calc_showing (gpointer data)
+{
+ GSList *tmp;
+ GSList *copy;
+ GSList *should_show;
+ GSList *should_hide;
+ GSList *unplaced;
+ GSList *displays;
+ MetaWindow *first_window;
+ guint queue_index = GPOINTER_TO_INT (data);
+
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Clearing the calc_showing queue\n");
+
+ /* Work with a copy, for reentrancy. The allowed reentrancy isn't
+ * complete; destroying a window while we're in here would result in
+ * badness. But it's OK to queue/unqueue calc_showings.
+ */
+ copy = g_slist_copy (queue_pending[queue_index]);
+ g_slist_free (queue_pending[queue_index]);
+ queue_pending[queue_index] = NULL;
+ queue_idle[queue_index] = 0;
+
+ destroying_windows_disallowed += 1;
+
+ /* We map windows from top to bottom and unmap from bottom to
+ * top, to avoid extra expose events. The exception is
+ * for unplaced windows, which have to be mapped from bottom to
+ * top so placement works.
+ */
+ should_show = NULL;
+ should_hide = NULL;
+ unplaced = NULL;
+ displays = NULL;
+
+ tmp = copy;
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+
+ window = tmp->data;
+
+ if (!window->placed)
+ unplaced = g_slist_prepend (unplaced, window);
+ else if (meta_window_should_be_showing (window))
+ should_show = g_slist_prepend (should_show, window);
+ else
+ should_hide = g_slist_prepend (should_hide, window);
+
+ tmp = tmp->next;
+ }
+
+ /* bottom to top */
+ unplaced = g_slist_sort (unplaced, stackcmp);
+ should_hide = g_slist_sort (should_hide, stackcmp);
+ /* top to bottom */
+ should_show = g_slist_sort (should_show, stackcmp);
+ should_show = g_slist_reverse (should_show);
+
+ first_window = copy->data;
+
+ meta_display_grab (first_window->display);
+
+ tmp = unplaced;
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+
+ window = tmp->data;
+
+ meta_window_calc_showing (window);
+
+ tmp = tmp->next;
+ }
+
+ tmp = should_show;
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+
+ window = tmp->data;
+
+ implement_showing (window, TRUE);
+
+ tmp = tmp->next;
+ }
+
+ tmp = should_hide;
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+
+ window = tmp->data;
+
+ implement_showing (window, FALSE);
+
+ tmp = tmp->next;
+ }
+
+ tmp = copy;
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+
+ window = tmp->data;
+
+ /* important to set this here for reentrancy -
+ * if we queue a window again while it's in "copy",
+ * then queue_calc_showing will just return since
+ * we are still in the calc_showing queue
+ */
+ window->is_in_queues &= ~META_QUEUE_CALC_SHOWING;
+
+ tmp = tmp->next;
+ }
+
+ if (meta_prefs_get_focus_mode () != META_FOCUS_MODE_CLICK)
+ {
+ /* When display->mouse_mode is false, we want to ignore
+ * EnterNotify events unless they come from mouse motion. To do
+ * that, we set a sentinel property on the root window if we're
+ * not in mouse_mode.
+ */
+ tmp = should_show;
+ while (tmp != NULL)
+ {
+ MetaWindow *window = tmp->data;
+
+ if (!window->display->mouse_mode)
+ meta_display_increment_focus_sentinel (window->display);
+
+ tmp = tmp->next;
+ }
+ }
+
+ meta_display_ungrab (first_window->display);
+
+ g_slist_free (copy);
+
+ g_slist_free (unplaced);
+ g_slist_free (should_show);
+ g_slist_free (should_hide);
+ g_slist_free (displays);
+
+ destroying_windows_disallowed -= 1;
+
+ return FALSE;
+}
+
+#ifdef WITH_VERBOSE_MODE
+static const gchar* meta_window_queue_names[NUMBER_OF_QUEUES] =
+ {"calc_showing", "move_resize", "update_icon"};
+#endif
+
+static void
+meta_window_unqueue (MetaWindow *window, guint queuebits)
+{
+ gint queuenum;
+
+ for (queuenum=0; queuenum<NUMBER_OF_QUEUES; queuenum++)
+ {
+ if ((queuebits & 1<<queuenum) /* they have asked to unqueue */
+ &&
+ (window->is_in_queues & 1<<queuenum)) /* it's in the queue */
+ {
+
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Removing %s from the %s queue\n",
+ window->desc,
+ meta_window_queue_names[queuenum]);
+
+ /* Note that window may not actually be in the queue
+ * because it may have been in "copy" inside the idle handler
+ */
+ queue_pending[queuenum] = g_slist_remove (queue_pending[queuenum], window);
+ window->is_in_queues &= ~(1<<queuenum);
+
+ /* Okay, so maybe we've used up all the entries in the queue.
+ * In that case, we should kill the function that deals with
+ * the queue, because there's nothing left for it to do.
+ */
+ if (queue_pending[queuenum] == NULL && queue_idle[queuenum] != 0)
+ {
+ g_source_remove (queue_idle[queuenum]);
+ queue_idle[queuenum] = 0;
+ }
+ }
+ }
+}
+
+static void
+meta_window_flush_calc_showing (MetaWindow *window)
+{
+ if (window->is_in_queues & META_QUEUE_CALC_SHOWING)
+ {
+ meta_window_unqueue (window, META_QUEUE_CALC_SHOWING);
+ meta_window_calc_showing (window);
+ }
+}
+
+void
+meta_window_queue (MetaWindow *window, guint queuebits)
+{
+ guint queuenum;
+
+ for (queuenum=0; queuenum<NUMBER_OF_QUEUES; queuenum++)
+ {
+ if (queuebits & 1<<queuenum)
+ {
+ /* Data which varies between queues.
+ * Yes, these do look a lot like associative arrays:
+ * I seem to be turning into a Perl programmer.
+ */
+
+ const gint window_queue_idle_priority[NUMBER_OF_QUEUES] =
+ {
+ G_PRIORITY_DEFAULT_IDLE, /* CALC_SHOWING */
+ META_PRIORITY_RESIZE, /* MOVE_RESIZE */
+ G_PRIORITY_DEFAULT_IDLE /* UPDATE_ICON */
+ };
+
+ const GSourceFunc window_queue_idle_handler[NUMBER_OF_QUEUES] =
+ {
+ idle_calc_showing,
+ idle_move_resize,
+ idle_update_icon,
+ };
+
+ /* If we're about to drop the window, there's no point in putting
+ * it on a queue.
+ */
+ if (window->unmanaging)
+ break;
+
+ /* If the window already claims to be in that queue, there's no
+ * point putting it in the queue.
+ */
+ if (window->is_in_queues & 1<<queuenum)
+ break;
+
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Putting %s in the %s queue\n",
+ window->desc,
+ meta_window_queue_names[queuenum]);
+
+ /* So, mark it as being in this queue. */
+ window->is_in_queues |= 1<<queuenum;
+
+ /* There's not a lot of point putting things into a queue if
+ * nobody's on the other end pulling them out. Therefore,
+ * let's check to see whether an idle handler exists to do
+ * that. If not, we'll create one.
+ */
+
+ if (queue_idle[queuenum] == 0)
+ queue_idle[queuenum] = g_idle_add_full
+ (
+ window_queue_idle_priority[queuenum],
+ window_queue_idle_handler[queuenum],
+ GUINT_TO_POINTER(queuenum),
+ NULL
+ );
+
+ /* And now we actually put it on the queue. */
+ queue_pending[queuenum] = g_slist_prepend (queue_pending[queuenum],
+ window);
+ }
+ }
+}
+
+static gboolean
+intervening_user_event_occurred (MetaWindow *window)
+{
+ guint32 compare;
+ MetaWindow *focus_window;
+
+ focus_window = window->display->focus_window;
+
+ meta_topic (META_DEBUG_STARTUP,
+ "COMPARISON:\n"
+ " net_wm_user_time_set : %d\n"
+ " net_wm_user_time : %u\n"
+ " initial_timestamp_set: %d\n"
+ " initial_timestamp : %u\n",
+ window->net_wm_user_time_set,
+ window->net_wm_user_time,
+ window->initial_timestamp_set,
+ window->initial_timestamp);
+ if (focus_window != NULL)
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "COMPARISON (continued):\n"
+ " focus_window : %s\n"
+ " fw->net_wm_user_time_set : %d\n"
+ " fw->net_wm_user_time : %u\n",
+ focus_window->desc,
+ focus_window->net_wm_user_time_set,
+ focus_window->net_wm_user_time);
+ }
+
+ /* We expect the most common case for not focusing a new window
+ * to be when a hint to not focus it has been set. Since we can
+ * deal with that case rapidly, we use special case it--this is
+ * merely a preliminary optimization. :)
+ */
+ if ( ((window->net_wm_user_time_set == TRUE) &&
+ (window->net_wm_user_time == 0))
+ ||
+ ((window->initial_timestamp_set == TRUE) &&
+ (window->initial_timestamp == 0)))
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "window %s explicitly requested no focus\n",
+ window->desc);
+ return TRUE;
+ }
+
+ if (!(window->net_wm_user_time_set) && !(window->initial_timestamp_set))
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "no information about window %s found\n",
+ window->desc);
+ return FALSE;
+ }
+
+ if (focus_window != NULL &&
+ !focus_window->net_wm_user_time_set)
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "focus window, %s, doesn't have a user time set yet!\n",
+ window->desc);
+ return FALSE;
+ }
+
+ /* To determine the "launch" time of an application,
+ * startup-notification can set the TIMESTAMP and the
+ * application (usually via its toolkit such as gtk or qt) can
+ * set the _NET_WM_USER_TIME. If both are set, we need to be
+ * using the newer of the two values.
+ *
+ * See http://bugzilla.gnome.org/show_bug.cgi?id=573922
+ */
+ compare = 0;
+ if (window->net_wm_user_time_set &&
+ window->initial_timestamp_set)
+ compare =
+ XSERVER_TIME_IS_BEFORE (window->net_wm_user_time,
+ window->initial_timestamp) ?
+ window->initial_timestamp : window->net_wm_user_time;
+ else if (window->net_wm_user_time_set)
+ compare = window->net_wm_user_time;
+ else if (window->initial_timestamp_set)
+ compare = window->initial_timestamp;
+
+ if ((focus_window != NULL) &&
+ XSERVER_TIME_IS_BEFORE (compare, focus_window->net_wm_user_time))
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "window %s focus prevented by other activity; %u < %u\n",
+ window->desc,
+ compare,
+ focus_window->net_wm_user_time);
+ return TRUE;
+ }
+ else
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "new window %s with no intervening events\n",
+ window->desc);
+ return FALSE;
+ }
+}
+
+/* This function is an ugly hack. It's experimental in nature and ought to be
+ * replaced by a real hint from the app to the WM if we decide the experimental
+ * behavior is worthwhile. The basic idea is to get more feedback about how
+ * usage scenarios of "strict" focus users and what they expect. See #326159.
+ */
+gboolean
+__window_is_terminal (MetaWindow *window)
+{
+ if (window == NULL || window->res_class == NULL)
+ return FALSE;
+
+ /*
+ * Compare res_class, which is not user-settable, and thus theoretically
+ * a more-reliable indication of term-ness.
+ */
+
+ /* mate-terminal -- if you couldn't guess */
+ if (strcmp (window->res_class, "Mate-terminal") == 0)
+ return TRUE;
+ /* xterm, rxvt, aterm */
+ else if (strcmp (window->res_class, "XTerm") == 0)
+ return TRUE;
+ /* konsole, KDE's terminal program */
+ else if (strcmp (window->res_class, "Konsole") == 0)
+ return TRUE;
+ /* rxvt-unicode */
+ else if (strcmp (window->res_class, "URxvt") == 0)
+ return TRUE;
+ /* eterm */
+ else if (strcmp (window->res_class, "Eterm") == 0)
+ return TRUE;
+ /* KTerm -- some terminal not KDE based; so not like Konsole */
+ else if (strcmp (window->res_class, "KTerm") == 0)
+ return TRUE;
+ /* Multi-mate-terminal */
+ else if (strcmp (window->res_class, "Multi-mate-terminal") == 0)
+ return TRUE;
+ /* mlterm ("multi lingual terminal emulator on X") */
+ else if (strcmp (window->res_class, "mlterm") == 0)
+ return TRUE;
+ /* Terminal -- XFCE Terminal */
+ else if (strcmp (window->res_class, "Terminal") == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+/* This function determines what state the window should have assuming that it
+ * and the focus_window have no relation
+ */
+static void
+window_state_on_map (MetaWindow *window,
+ gboolean *takes_focus,
+ gboolean *places_on_top)
+{
+ gboolean intervening_events;
+
+ intervening_events = intervening_user_event_occurred (window);
+
+ *takes_focus = !intervening_events;
+ *places_on_top = *takes_focus;
+
+ /* don't initially focus windows that are intended to not accept
+ * focus
+ */
+ if (!(window->input || window->take_focus))
+ {
+ *takes_focus = FALSE;
+ return;
+ }
+
+ /* Terminal usage may be different; some users intend to launch
+ * many apps in quick succession or to just view things in the new
+ * window while still interacting with the terminal. In that case,
+ * apps launched from the terminal should not take focus. This
+ * isn't quite the same as not allowing focus to transfer from
+ * terminals due to new window map, but the latter is a much easier
+ * approximation to enforce so we do that.
+ */
+ if (*takes_focus &&
+ meta_prefs_get_focus_new_windows () == META_FOCUS_NEW_WINDOWS_STRICT &&
+ !window->display->allow_terminal_deactivation &&
+ __window_is_terminal (window->display->focus_window) &&
+ !meta_window_is_ancestor_of_transient (window->display->focus_window,
+ window))
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "focus_window is terminal; not focusing new window.\n");
+ *takes_focus = FALSE;
+ *places_on_top = FALSE;
+ }
+
+ switch (window->type)
+ {
+ case META_WINDOW_UTILITY:
+ case META_WINDOW_TOOLBAR:
+ *takes_focus = FALSE;
+ *places_on_top = FALSE;
+ break;
+ case META_WINDOW_DOCK:
+ case META_WINDOW_DESKTOP:
+ case META_WINDOW_SPLASHSCREEN:
+ case META_WINDOW_MENU:
+ /* don't focus any of these; places_on_top may be irrelevant for some of
+ * these (e.g. dock)--but you never know--the focus window might also be
+ * of the same type in some weird situation...
+ */
+ *takes_focus = FALSE;
+ break;
+ case META_WINDOW_NORMAL:
+ case META_WINDOW_DIALOG:
+ case META_WINDOW_MODAL_DIALOG:
+ /* The default is correct for these */
+ break;
+ }
+}
+
+static gboolean
+windows_overlap (const MetaWindow *w1, const MetaWindow *w2)
+{
+ MetaRectangle w1rect, w2rect;
+ meta_window_get_outer_rect (w1, &w1rect);
+ meta_window_get_outer_rect (w2, &w2rect);
+ return meta_rectangle_overlap (&w1rect, &w2rect);
+}
+
+/* Returns whether a new window would be covered by any
+ * existing window on the same workspace that is set
+ * to be "above" ("always on top"). A window that is not
+ * set "above" would be underneath the new window anyway.
+ *
+ * We take "covered" to mean even partially covered, but
+ * some people might prefer entirely covered. I think it
+ * is more useful to behave this way if any part of the
+ * window is covered, because a partial coverage could be
+ * (say) ninety per cent and almost indistinguishable from total.
+ */
+static gboolean
+window_would_be_covered (const MetaWindow *newbie)
+{
+ MetaWorkspace *workspace = newbie->workspace;
+ GList *tmp, *windows;
+
+ windows = meta_workspace_list_windows (workspace);
+
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ if (w->wm_state_above && w != newbie)
+ {
+ /* We have found a window that is "above". Perhaps it overlaps. */
+ if (windows_overlap (w, newbie))
+ {
+ g_list_free (windows); /* clean up... */
+ return TRUE; /* yes, it does */
+ }
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_list_free (windows);
+ return FALSE; /* none found */
+}
+
+/* XXX META_EFFECT_*_MAP */
+void
+meta_window_show (MetaWindow *window)
+{
+ gboolean did_show;
+ gboolean takes_focus_on_map;
+ gboolean place_on_top_on_map;
+ gboolean needs_stacking_adjustment;
+ MetaWindow *focus_window;
+ guint32 timestamp;
+
+ /* FIXME: It really sucks to put timestamp pinging here; it'd
+ * probably make more sense in implement_showing() so that it's at
+ * least not duplicated in finish_minimize. *shrug*
+ */
+ timestamp = meta_display_get_current_time_roundtrip (window->display);
+
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Showing window %s, shaded: %d iconic: %d placed: %d\n",
+ window->desc, window->shaded, window->iconic, window->placed);
+
+ focus_window = window->display->focus_window; /* May be NULL! */
+ did_show = FALSE;
+ window_state_on_map (window, &takes_focus_on_map, &place_on_top_on_map);
+ needs_stacking_adjustment = FALSE;
+
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Window %s %s focus on map, and %s place on top on map.\n",
+ window->desc,
+ takes_focus_on_map ? "does" : "does not",
+ place_on_top_on_map ? "does" : "does not");
+
+ /* Now, in some rare cases we should *not* put a new window on top.
+ * These cases include certain types of windows showing for the first
+ * time, and any window which would be covered because of another window
+ * being set "above" ("always on top").
+ *
+ * FIXME: Although "place_on_top_on_map" and "takes_focus_on_map" are
+ * generally based on the window type, there is a special case when the
+ * focus window is a terminal for them both to be false; this should
+ * probably rather be a term in the "if" condition below.
+ */
+
+ if ( focus_window != NULL && window->showing_for_first_time &&
+ ( (!place_on_top_on_map && !takes_focus_on_map) ||
+ window_would_be_covered (window) )
+ ) {
+ if (meta_window_is_ancestor_of_transient (focus_window, window))
+ {
+ /* This happens for error dialogs or alerts; these need to remain on
+ * top, but it would be confusing to have its ancestor remain
+ * focused.
+ */
+ meta_topic (META_DEBUG_STARTUP,
+ "The focus window %s is an ancestor of the newly mapped "
+ "window %s which isn't being focused. Unfocusing the "
+ "ancestor.\n",
+ focus_window->desc, window->desc);
+
+ meta_display_focus_the_no_focus_window (window->display,
+ window->screen,
+ timestamp);
+ }
+ else
+ {
+ needs_stacking_adjustment = TRUE;
+ if (!window->placed)
+ window->denied_focus_and_not_transient = TRUE;
+ }
+ }
+
+ if (!window->placed)
+ {
+ /* We have to recalc the placement here since other windows may
+ * have been mapped/placed since we last did constrain_position
+ */
+
+ /* calc_placement is an efficiency hack to avoid
+ * multiple placement calculations before we finally
+ * show the window.
+ */
+ window->calc_placement = TRUE;
+ meta_window_move_resize_now (window);
+ window->calc_placement = FALSE;
+
+ /* don't ever do the initial position constraint thing again.
+ * This is toggled here so that initially-iconified windows
+ * still get placed when they are ultimately shown.
+ */
+ window->placed = TRUE;
+
+ /* Don't want to accidentally reuse the fact that we had been denied
+ * focus in any future constraints unless we're denied focus again.
+ */
+ window->denied_focus_and_not_transient = FALSE;
+ }
+
+ if (needs_stacking_adjustment)
+ {
+ gboolean overlap;
+
+ /* This window isn't getting focus on map. We may need to do some
+ * special handing with it in regards to
+ * - the stacking of the window
+ * - the MRU position of the window
+ * - the demands attention setting of the window
+ *
+ * Firstly, set the flag so we don't give the window focus anyway
+ * and confuse people.
+ */
+
+ takes_focus_on_map = FALSE;
+
+ overlap = windows_overlap (window, focus_window);
+
+ /* We want alt tab to go to the denied-focus window */
+ ensure_mru_position_after (window, focus_window);
+
+ /* We don't want the denied-focus window to obscure the focus
+ * window, and if we're in both click-to-focus mode and
+ * raise-on-click mode then we want to maintain the invariant
+ * that MRU order == stacking order. The need for this if
+ * comes from the fact that in sloppy/mouse focus the focus
+ * window may not overlap other windows and also can be
+ * considered "below" them; this combination means that
+ * placing the denied-focus window "below" the focus window
+ * in the stack when it doesn't overlap it confusingly places
+ * that new window below a lot of other windows.
+ */
+ if (overlap ||
+ (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK &&
+ meta_prefs_get_raise_on_click ()))
+ meta_window_stack_just_below (window, focus_window);
+
+ /* If the window will be obscured by the focus window, then the
+ * user might not notice the window appearing so set the
+ * demands attention hint.
+ *
+ * We set the hint ourselves rather than calling
+ * meta_window_set_demands_attention() because that would cause
+ * a recalculation of overlap, and a call to set_net_wm_state()
+ * which we are going to call ourselves here a few lines down.
+ */
+ if (overlap)
+ window->wm_state_demands_attention = TRUE;
+ }
+
+ /* Shaded means the frame is mapped but the window is not */
+
+ if (window->frame && !window->frame->mapped)
+ {
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Frame actually needs map\n");
+ window->frame->mapped = TRUE;
+ meta_ui_map_frame (window->screen->ui, window->frame->xwindow);
+ did_show = TRUE;
+ }
+
+ if (window->shaded)
+ {
+ if (window->mapped)
+ {
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "%s actually needs unmap (shaded)\n", window->desc);
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Incrementing unmaps_pending on %s for shade\n",
+ window->desc);
+ window->mapped = FALSE;
+ window->unmaps_pending += 1;
+ meta_error_trap_push (window->display);
+ XUnmapWindow (window->display->xdisplay, window->xwindow);
+ meta_error_trap_pop (window->display, FALSE);
+ }
+
+ if (!window->iconic)
+ {
+ window->iconic = TRUE;
+ set_wm_state (window, IconicState);
+ }
+ }
+ else
+ {
+ if (!window->mapped)
+ {
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "%s actually needs map\n", window->desc);
+ window->mapped = TRUE;
+ meta_error_trap_push (window->display);
+ XMapWindow (window->display->xdisplay, window->xwindow);
+ meta_error_trap_pop (window->display, FALSE);
+ did_show = TRUE;
+
+ if (window->was_minimized)
+ {
+ MetaRectangle window_rect;
+ MetaRectangle icon_rect;
+
+ window->was_minimized = FALSE;
+
+ if (meta_window_get_icon_geometry (window, &icon_rect))
+ {
+ meta_window_get_outer_rect (window, &window_rect);
+
+ meta_effect_run_unminimize (window,
+ &window_rect,
+ &icon_rect,
+ NULL, NULL);
+ }
+ }
+ }
+
+ if (window->iconic)
+ {
+ window->iconic = FALSE;
+ set_wm_state (window, NormalState);
+ }
+ }
+
+ /* We don't want to worry about all cases from inside
+ * implement_showing(); we only want to worry about focus if this
+ * window has not been shown before.
+ */
+ if (window->showing_for_first_time)
+ {
+ window->showing_for_first_time = FALSE;
+ if (takes_focus_on_map)
+ {
+ meta_window_focus (window, timestamp);
+ }
+ else
+ {
+ /* Prevent EnterNotify events in sloppy/mouse focus from
+ * erroneously focusing the window that had been denied
+ * focus. FIXME: This introduces a race; I have a couple
+ * ideas for a better way to accomplish the same thing, but
+ * they're more involved so do it this way for now.
+ */
+ meta_display_increment_focus_sentinel (window->display);
+ }
+ }
+
+ set_net_wm_state (window);
+
+ if (did_show && window->struts)
+ {
+ meta_topic (META_DEBUG_WORKAREA,
+ "Mapped window %s with struts, so invalidating work areas\n",
+ window->desc);
+ invalidate_work_areas (window);
+ }
+
+ /*
+ * Now that we have shown the window, we no longer want to consider the
+ * initial timestamp in any subsequent deliberations whether to focus this
+ * window or not, so clear the flag.
+ *
+ * See http://bugzilla.gnome.org/show_bug.cgi?id=573922
+ */
+ window->initial_timestamp_set = FALSE;
+}
+
+/* XXX META_EFFECT_*_UNMAP */
+static void
+meta_window_hide (MetaWindow *window)
+{
+ gboolean did_hide;
+
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Hiding window %s\n", window->desc);
+
+ did_hide = FALSE;
+
+ if (window->frame && window->frame->mapped)
+ {
+ meta_topic (META_DEBUG_WINDOW_STATE, "Frame actually needs unmap\n");
+ window->frame->mapped = FALSE;
+ meta_ui_unmap_frame (window->screen->ui, window->frame->xwindow);
+ did_hide = TRUE;
+ }
+
+ if (window->mapped)
+ {
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "%s actually needs unmap\n", window->desc);
+ meta_topic (META_DEBUG_WINDOW_STATE,
+ "Incrementing unmaps_pending on %s for hide\n",
+ window->desc);
+ window->mapped = FALSE;
+ window->unmaps_pending += 1;
+ meta_error_trap_push (window->display);
+ XUnmapWindow (window->display->xdisplay, window->xwindow);
+ meta_error_trap_pop (window->display, FALSE);
+ did_hide = TRUE;
+ }
+
+ if (!window->iconic)
+ {
+ window->iconic = TRUE;
+ set_wm_state (window, IconicState);
+ }
+
+ set_net_wm_state (window);
+
+ if (did_hide && window->struts)
+ {
+ meta_topic (META_DEBUG_WORKAREA,
+ "Unmapped window %s with struts, so invalidating work areas\n",
+ window->desc);
+ invalidate_work_areas (window);
+ }
+}
+
+static gboolean
+queue_calc_showing_func (MetaWindow *window,
+ void *data)
+{
+ meta_window_queue(window, META_QUEUE_CALC_SHOWING);
+ return TRUE;
+}
+
+void
+meta_window_minimize (MetaWindow *window)
+{
+ if (!window->minimized)
+ {
+ window->minimized = TRUE;
+ meta_window_queue(window, META_QUEUE_CALC_SHOWING);
+
+ meta_window_foreach_transient (window,
+ queue_calc_showing_func,
+ NULL);
+
+ if (window->has_focus)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing default window due to minimization of focus window %s\n",
+ window->desc);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Minimizing window %s which doesn't have the focus\n",
+ window->desc);
+ }
+ }
+}
+
+void
+meta_window_unminimize (MetaWindow *window)
+{
+ if (window->minimized)
+ {
+ window->minimized = FALSE;
+ window->was_minimized = TRUE;
+ meta_window_queue(window, META_QUEUE_CALC_SHOWING);
+
+ meta_window_foreach_transient (window,
+ queue_calc_showing_func,
+ NULL);
+ }
+}
+
+static void
+ensure_size_hints_satisfied (MetaRectangle *rect,
+ const XSizeHints *size_hints)
+{
+ int minw, minh, maxw, maxh; /* min/max width/height */
+ int basew, baseh, winc, hinc; /* base width/height, width/height increment */
+ int extra_width, extra_height;
+
+ minw = size_hints->min_width; minh = size_hints->min_height;
+ maxw = size_hints->max_width; maxh = size_hints->max_height;
+ basew = size_hints->base_width; baseh = size_hints->base_height;
+ winc = size_hints->width_inc; hinc = size_hints->height_inc;
+
+ /* First, enforce min/max size constraints */
+ rect->width = CLAMP (rect->width, minw, maxw);
+ rect->height = CLAMP (rect->height, minh, maxh);
+
+ /* Now, verify size increment constraints are satisfied, or make them be */
+ extra_width = (rect->width - basew) % winc;
+ extra_height = (rect->height - baseh) % hinc;
+
+ rect->width -= extra_width;
+ rect->height -= extra_height;
+
+ /* Adjusting width/height down, as done above, may violate minimum size
+ * constraints, so one last fix.
+ */
+ if (rect->width < minw)
+ rect->width += ((minw - rect->width)/winc + 1)*winc;
+ if (rect->height < minh)
+ rect->height += ((minh - rect->height)/hinc + 1)*hinc;
+}
+
+static void
+meta_window_save_rect (MetaWindow *window)
+{
+ if (!(META_WINDOW_MAXIMIZED (window) || window->fullscreen))
+ {
+ /* save size/pos as appropriate args for move_resize */
+ if (!window->maximized_horizontally)
+ {
+ window->saved_rect.x = window->rect.x;
+ window->saved_rect.width = window->rect.width;
+ if (window->frame)
+ window->saved_rect.x += window->frame->rect.x;
+ }
+ if (!window->maximized_vertically)
+ {
+ window->saved_rect.y = window->rect.y;
+ window->saved_rect.height = window->rect.height;
+ if (window->frame)
+ window->saved_rect.y += window->frame->rect.y;
+ }
+ }
+}
+
+/**
+ * Save the user_rect regardless of whether the window is maximized or
+ * fullscreen. See save_user_window_placement() for most uses.
+ *
+ * \param window Store current position of this window for future reference
+ */
+static void
+force_save_user_window_placement (MetaWindow *window)
+{
+ meta_window_get_client_root_coords (window, &window->user_rect);
+}
+
+/**
+ * Save the user_rect, but only if the window is neither maximized nor
+ * fullscreen, otherwise the window may snap back to those dimensions
+ * (bug #461927).
+ *
+ * \param window Store current position of this window for future reference
+ */
+static void
+save_user_window_placement (MetaWindow *window)
+{
+ if (!(META_WINDOW_MAXIMIZED (window) || window->fullscreen))
+ {
+ MetaRectangle user_rect;
+
+ meta_window_get_client_root_coords (window, &user_rect);
+
+ if (!window->maximized_horizontally)
+ {
+ window->user_rect.x = user_rect.x;
+ window->user_rect.width = user_rect.width;
+ }
+ if (!window->maximized_vertically)
+ {
+ window->user_rect.y = user_rect.y;
+ window->user_rect.height = user_rect.height;
+ }
+ }
+}
+
+void
+meta_window_maximize_internal (MetaWindow *window,
+ MetaMaximizeFlags directions,
+ MetaRectangle *saved_rect)
+{
+ /* At least one of the two directions ought to be set */
+ gboolean maximize_horizontally, maximize_vertically;
+ maximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL;
+ maximize_vertically = directions & META_MAXIMIZE_VERTICAL;
+ g_assert (maximize_horizontally || maximize_vertically);
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Maximizing %s%s\n",
+ window->desc,
+ maximize_horizontally && maximize_vertically ? "" :
+ maximize_horizontally ? " horizontally" :
+ maximize_vertically ? " vertically" : "BUGGGGG");
+
+ if (saved_rect != NULL)
+ window->saved_rect = *saved_rect;
+ else
+ meta_window_save_rect (window);
+
+ window->maximized_horizontally =
+ window->maximized_horizontally || maximize_horizontally;
+ window->maximized_vertically =
+ window->maximized_vertically || maximize_vertically;
+ if (maximize_horizontally || maximize_vertically)
+ window->force_save_user_rect = FALSE;
+
+ /* Fix for #336850: If the frame shape isn't reapplied, it is
+ * possible that the frame will retains its rounded corners. That
+ * happens if the client's size when maximized equals the unmaximized
+ * size.
+ */
+ if (window->frame)
+ window->frame->need_reapply_frame_shape = TRUE;
+
+ recalc_window_features (window);
+ set_net_wm_state (window);
+}
+
+void
+meta_window_maximize (MetaWindow *window,
+ MetaMaximizeFlags directions)
+{
+ /* At least one of the two directions ought to be set */
+ gboolean maximize_horizontally, maximize_vertically;
+ maximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL;
+ maximize_vertically = directions & META_MAXIMIZE_VERTICAL;
+ g_assert (maximize_horizontally || maximize_vertically);
+
+ /* Only do something if the window isn't already maximized in the
+ * given direction(s).
+ */
+ if ((maximize_horizontally && !window->maximized_horizontally) ||
+ (maximize_vertically && !window->maximized_vertically))
+ {
+ if (window->shaded && maximize_vertically)
+ {
+ /* Shading sucks anyway; I'm not adding a timestamp argument
+ * to this function just for this niche usage & corner case.
+ */
+ guint32 timestamp =
+ meta_display_get_current_time_roundtrip (window->display);
+ meta_window_unshade (window, timestamp);
+ }
+
+ /* if the window hasn't been placed yet, we'll maximize it then
+ */
+ if (!window->placed)
+ {
+ window->maximize_horizontally_after_placement =
+ window->maximize_horizontally_after_placement ||
+ maximize_horizontally;
+ window->maximize_vertically_after_placement =
+ window->maximize_vertically_after_placement ||
+ maximize_vertically;
+ return;
+ }
+
+ meta_window_maximize_internal (window,
+ directions,
+ NULL);
+
+ /* move_resize with new maximization constraints
+ */
+ meta_window_queue(window, META_QUEUE_MOVE_RESIZE);
+ }
+}
+
+static void
+unmaximize_window_before_freeing (MetaWindow *window)
+{
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Unmaximizing %s just before freeing\n",
+ window->desc);
+
+ window->maximized_horizontally = FALSE;
+ window->maximized_vertically = FALSE;
+
+ if (window->withdrawn) /* See bug #137185 */
+ {
+ window->rect = window->saved_rect;
+ set_net_wm_state (window);
+ }
+ else if (window->screen->closing) /* See bug #358042 */
+ {
+ /* Do NOT update net_wm_state: this screen is closing,
+ * it likely will be managed by another window manager
+ * that will need the current _NET_WM_STATE atoms.
+ * Moreover, it will need to know the unmaximized geometry,
+ * therefore move_resize the window to saved_rect here
+ * before closing it. */
+ meta_window_move_resize (window,
+ FALSE,
+ window->saved_rect.x,
+ window->saved_rect.y,
+ window->saved_rect.width,
+ window->saved_rect.height);
+ }
+}
+
+void
+meta_window_unmaximize (MetaWindow *window,
+ MetaMaximizeFlags directions)
+{
+ /* At least one of the two directions ought to be set */
+ gboolean unmaximize_horizontally, unmaximize_vertically;
+ unmaximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL;
+ unmaximize_vertically = directions & META_MAXIMIZE_VERTICAL;
+ g_assert (unmaximize_horizontally || unmaximize_vertically);
+
+ /* Only do something if the window isn't already maximized in the
+ * given direction(s).
+ */
+ if ((unmaximize_horizontally && window->maximized_horizontally) ||
+ (unmaximize_vertically && window->maximized_vertically))
+ {
+ MetaRectangle target_rect;
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Unmaximizing %s%s\n",
+ window->desc,
+ unmaximize_horizontally && unmaximize_vertically ? "" :
+ unmaximize_horizontally ? " horizontally" :
+ unmaximize_vertically ? " vertically" : "BUGGGGG");
+
+ window->maximized_horizontally =
+ window->maximized_horizontally && !unmaximize_horizontally;
+ window->maximized_vertically =
+ window->maximized_vertically && !unmaximize_vertically;
+
+ /* Unmaximize to the saved_rect position in the direction(s)
+ * being unmaximized.
+ */
+ meta_window_get_client_root_coords (window, &target_rect);
+ if (unmaximize_horizontally)
+ {
+ target_rect.x = window->saved_rect.x;
+ target_rect.width = window->saved_rect.width;
+ }
+ if (unmaximize_vertically)
+ {
+ target_rect.y = window->saved_rect.y;
+ target_rect.height = window->saved_rect.height;
+ }
+
+ /* Window's size hints may have changed while maximized, making
+ * saved_rect invalid. #329152
+ */
+ ensure_size_hints_satisfied (&target_rect, &window->size_hints);
+
+ /* When we unmaximize, if we're doing a mouse move also we could
+ * get the window suddenly jumping to the upper left corner of
+ * the workspace, since that's where it was when the grab op
+ * started. So we need to update the grab state.
+ */
+ if (meta_grab_op_is_moving (window->display->grab_op) &&
+ window->display->grab_window == window)
+ {
+ window->display->grab_anchor_window_pos = target_rect;
+ }
+
+ meta_window_move_resize (window,
+ FALSE,
+ target_rect.x,
+ target_rect.y,
+ target_rect.width,
+ target_rect.height);
+
+ /* Make sure user_rect is current.
+ */
+ force_save_user_window_placement (window);
+
+ if (window->display->grab_wireframe_active)
+ {
+ window->display->grab_wireframe_rect = target_rect;
+ }
+
+ recalc_window_features (window);
+ set_net_wm_state (window);
+ }
+}
+
+void
+meta_window_make_above (MetaWindow *window)
+{
+ window->wm_state_above = TRUE;
+ meta_window_update_layer (window);
+ meta_window_raise (window);
+ set_net_wm_state (window);
+}
+
+void
+meta_window_unmake_above (MetaWindow *window)
+{
+ window->wm_state_above = FALSE;
+ meta_window_raise (window);
+ meta_window_update_layer (window);
+ set_net_wm_state (window);
+}
+
+void
+meta_window_make_fullscreen_internal (MetaWindow *window)
+{
+ if (!window->fullscreen)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Fullscreening %s\n", window->desc);
+
+ if (window->shaded)
+ {
+ /* Shading sucks anyway; I'm not adding a timestamp argument
+ * to this function just for this niche usage & corner case.
+ */
+ guint32 timestamp =
+ meta_display_get_current_time_roundtrip (window->display);
+ meta_window_unshade (window, timestamp);
+ }
+
+ meta_window_save_rect (window);
+
+ window->fullscreen = TRUE;
+ window->force_save_user_rect = FALSE;
+
+ meta_stack_freeze (window->screen->stack);
+ meta_window_update_layer (window);
+
+ meta_window_raise (window);
+ meta_stack_thaw (window->screen->stack);
+
+ recalc_window_features (window);
+ set_net_wm_state (window);
+ }
+}
+
+void
+meta_window_make_fullscreen (MetaWindow *window)
+{
+ if (!window->fullscreen)
+ {
+ meta_window_make_fullscreen_internal (window);
+ /* move_resize with new constraints
+ */
+ meta_window_queue(window, META_QUEUE_MOVE_RESIZE);
+ }
+}
+
+void
+meta_window_unmake_fullscreen (MetaWindow *window)
+{
+ if (window->fullscreen)
+ {
+ MetaRectangle target_rect;
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Unfullscreening %s\n", window->desc);
+
+ window->fullscreen = FALSE;
+ target_rect = window->saved_rect;
+
+ /* Window's size hints may have changed while maximized, making
+ * saved_rect invalid. #329152
+ */
+ ensure_size_hints_satisfied (&target_rect, &window->size_hints);
+
+ meta_window_move_resize (window,
+ FALSE,
+ target_rect.x,
+ target_rect.y,
+ target_rect.width,
+ target_rect.height);
+
+ /* Make sure user_rect is current.
+ */
+ force_save_user_window_placement (window);
+
+ meta_window_update_layer (window);
+
+ recalc_window_features (window);
+ set_net_wm_state (window);
+ }
+}
+
+void
+meta_window_update_fullscreen_monitors (MetaWindow *window,
+ unsigned long top,
+ unsigned long bottom,
+ unsigned long left,
+ unsigned long right)
+{
+ if ((int)top < window->screen->n_xinerama_infos &&
+ (int)bottom < window->screen->n_xinerama_infos &&
+ (int)left < window->screen->n_xinerama_infos &&
+ (int)right < window->screen->n_xinerama_infos)
+ {
+ window->fullscreen_monitors[0] = top;
+ window->fullscreen_monitors[1] = bottom;
+ window->fullscreen_monitors[2] = left;
+ window->fullscreen_monitors[3] = right;
+ }
+ else
+ {
+ window->fullscreen_monitors[0] = -1;
+ }
+
+ if (window->fullscreen)
+ {
+ meta_window_queue(window, META_QUEUE_MOVE_RESIZE);
+ }
+}
+
+void
+meta_window_shade (MetaWindow *window,
+ guint32 timestamp)
+{
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Shading %s\n", window->desc);
+ if (!window->shaded)
+ {
+ window->shaded = TRUE;
+
+ meta_window_queue(window, META_QUEUE_MOVE_RESIZE | META_QUEUE_CALC_SHOWING);
+
+ /* After queuing the calc showing, since _focus flushes it,
+ * and we need to focus the frame
+ */
+ meta_topic (META_DEBUG_FOCUS,
+ "Re-focusing window %s after shading it\n",
+ window->desc);
+ meta_window_focus (window, timestamp);
+
+ set_net_wm_state (window);
+ }
+}
+
+void
+meta_window_unshade (MetaWindow *window,
+ guint32 timestamp)
+{
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Unshading %s\n", window->desc);
+ if (window->shaded)
+ {
+ window->shaded = FALSE;
+ meta_window_queue(window, META_QUEUE_MOVE_RESIZE | META_QUEUE_CALC_SHOWING);
+
+ /* focus the window */
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing window %s after unshading it\n",
+ window->desc);
+ meta_window_focus (window, timestamp);
+
+ set_net_wm_state (window);
+ }
+}
+
+static gboolean
+unminimize_func (MetaWindow *window,
+ void *data)
+{
+ meta_window_unminimize (window);
+ return TRUE;
+}
+
+static void
+unminimize_window_and_all_transient_parents (MetaWindow *window)
+{
+ meta_window_unminimize (window);
+ meta_window_foreach_ancestor (window, unminimize_func, NULL);
+}
+
+static void
+window_activate (MetaWindow *window,
+ guint32 timestamp,
+ MetaClientType source_indication,
+ MetaWorkspace *workspace)
+{
+ gboolean can_ignore_outdated_timestamps;
+ meta_topic (META_DEBUG_FOCUS,
+ "_NET_ACTIVE_WINDOW message sent for %s at time %u "
+ "by client type %u.\n",
+ window->desc, timestamp, source_indication);
+
+ /* Older EWMH spec didn't specify a timestamp; we decide to honor these only
+ * if the app specifies that it is a pager.
+ *
+ * Update: Unconditionally honor 0 timestamps for now; we'll fight
+ * that battle later. Just remove the "FALSE &&" in order to only
+ * honor 0 timestamps for pagers.
+ */
+ can_ignore_outdated_timestamps =
+ (timestamp != 0 || (FALSE && source_indication != META_CLIENT_TYPE_PAGER));
+ if (XSERVER_TIME_IS_BEFORE (timestamp, window->display->last_user_time) &&
+ can_ignore_outdated_timestamps)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "last_user_time (%u) is more recent; ignoring "
+ " _NET_ACTIVE_WINDOW message.\n",
+ window->display->last_user_time);
+ meta_window_set_demands_attention(window);
+ return;
+ }
+
+ /* For those stupid pagers, get a valid timestamp and show a warning */
+ if (timestamp == 0)
+ {
+ meta_warning ("meta_window_activate called by a pager with a 0 timestamp; "
+ "the pager needs to be fixed.\n");
+ timestamp = meta_display_get_current_time_roundtrip (window->display);
+ }
+
+ meta_window_set_user_time (window, timestamp);
+
+ /* disable show desktop mode unless we're a desktop component */
+ maybe_leave_show_desktop_mode (window);
+
+ /* Get window on current or given workspace */
+ if (workspace == NULL)
+ workspace = window->screen->active_workspace;
+
+ /* For non-transient windows, we just set up a pulsing indicator,
+ rather than move windows or workspaces.
+ See http://bugzilla.gnome.org/show_bug.cgi?id=482354 */
+ if (window->xtransient_for == None &&
+ !meta_window_located_on_workspace (window, workspace))
+ {
+ meta_window_set_demands_attention (window);
+ /* We've marked it as demanding, don't need to do anything else. */
+ return;
+ }
+ else if (window->xtransient_for != None)
+ {
+ /* Move transients to current workspace - preference dialogs should appear over
+ the source window. */
+ meta_window_change_workspace (window, workspace);
+ }
+
+ if (window->shaded)
+ meta_window_unshade (window, timestamp);
+
+ unminimize_window_and_all_transient_parents (window);
+
+ if (meta_prefs_get_raise_on_click () ||
+ source_indication == META_CLIENT_TYPE_PAGER)
+ meta_window_raise (window);
+
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing window %s due to activation\n",
+ window->desc);
+ meta_window_focus (window, timestamp);
+}
+
+/* This function exists since most of the functionality in window_activate
+ * is useful for Marco, but Marco shouldn't need to specify a client
+ * type for itself. ;-)
+ */
+void
+meta_window_activate (MetaWindow *window,
+ guint32 timestamp)
+{
+ /* We're not really a pager, but the behavior we want is the same as if
+ * we were such. If we change the pager behavior later, we could revisit
+ * this and just add extra flags to window_activate.
+ */
+ window_activate (window, timestamp, META_CLIENT_TYPE_PAGER, NULL);
+}
+
+void
+meta_window_activate_with_workspace (MetaWindow *window,
+ guint32 timestamp,
+ MetaWorkspace *workspace)
+{
+ /* We're not really a pager, but the behavior we want is the same as if
+ * we were such. If we change the pager behavior later, we could revisit
+ * this and just add extra flags to window_activate.
+ */
+ window_activate (window, timestamp, META_CLIENT_TYPE_APPLICATION, workspace);
+}
+
+/* Manually fix all the weirdness explained in the big comment at the
+ * beginning of meta_window_move_resize_internal() giving positions
+ * expected by meta_window_constrain (i.e. positions & sizes of the
+ * internal or client window).
+ */
+static void
+adjust_for_gravity (MetaWindow *window,
+ MetaFrameGeometry *fgeom,
+ gboolean coords_assume_border,
+ int gravity,
+ MetaRectangle *rect)
+{
+ int ref_x, ref_y;
+ int bw;
+ int child_x, child_y;
+ int frame_width, frame_height;
+
+ if (coords_assume_border)
+ bw = window->border_width;
+ else
+ bw = 0;
+
+ if (fgeom)
+ {
+ child_x = fgeom->left_width;
+ child_y = fgeom->top_height;
+ frame_width = child_x + rect->width + fgeom->right_width;
+ frame_height = child_y + rect->height + fgeom->bottom_height;
+ }
+ else
+ {
+ child_x = 0;
+ child_y = 0;
+ frame_width = rect->width;
+ frame_height = rect->height;
+ }
+
+ /* We're computing position to pass to window_move, which is
+ * the position of the client window (StaticGravity basically)
+ *
+ * (see WM spec description of gravity computation, but note that
+ * their formulas assume we're honoring the border width, rather
+ * than compensating for having turned it off)
+ */
+ switch (gravity)
+ {
+ case NorthWestGravity:
+ ref_x = rect->x;
+ ref_y = rect->y;
+ break;
+ case NorthGravity:
+ ref_x = rect->x + rect->width / 2 + bw;
+ ref_y = rect->y;
+ break;
+ case NorthEastGravity:
+ ref_x = rect->x + rect->width + bw * 2;
+ ref_y = rect->y;
+ break;
+ case WestGravity:
+ ref_x = rect->x;
+ ref_y = rect->y + rect->height / 2 + bw;
+ break;
+ case CenterGravity:
+ ref_x = rect->x + rect->width / 2 + bw;
+ ref_y = rect->y + rect->height / 2 + bw;
+ break;
+ case EastGravity:
+ ref_x = rect->x + rect->width + bw * 2;
+ ref_y = rect->y + rect->height / 2 + bw;
+ break;
+ case SouthWestGravity:
+ ref_x = rect->x;
+ ref_y = rect->y + rect->height + bw * 2;
+ break;
+ case SouthGravity:
+ ref_x = rect->x + rect->width / 2 + bw;
+ ref_y = rect->y + rect->height + bw * 2;
+ break;
+ case SouthEastGravity:
+ ref_x = rect->x + rect->width + bw * 2;
+ ref_y = rect->y + rect->height + bw * 2;
+ break;
+ case StaticGravity:
+ default:
+ ref_x = rect->x;
+ ref_y = rect->y;
+ break;
+ }
+
+ switch (gravity)
+ {
+ case NorthWestGravity:
+ rect->x = ref_x + child_x;
+ rect->y = ref_y + child_y;
+ break;
+ case NorthGravity:
+ rect->x = ref_x - frame_width / 2 + child_x;
+ rect->y = ref_y + child_y;
+ break;
+ case NorthEastGravity:
+ rect->x = ref_x - frame_width + child_x;
+ rect->y = ref_y + child_y;
+ break;
+ case WestGravity:
+ rect->x = ref_x + child_x;
+ rect->y = ref_y - frame_height / 2 + child_y;
+ break;
+ case CenterGravity:
+ rect->x = ref_x - frame_width / 2 + child_x;
+ rect->y = ref_y - frame_height / 2 + child_y;
+ break;
+ case EastGravity:
+ rect->x = ref_x - frame_width + child_x;
+ rect->y = ref_y - frame_height / 2 + child_y;
+ break;
+ case SouthWestGravity:
+ rect->x = ref_x + child_x;
+ rect->y = ref_y - frame_height + child_y;
+ break;
+ case SouthGravity:
+ rect->x = ref_x - frame_width / 2 + child_x;
+ rect->y = ref_y - frame_height + child_y;
+ break;
+ case SouthEastGravity:
+ rect->x = ref_x - frame_width + child_x;
+ rect->y = ref_y - frame_height + child_y;
+ break;
+ case StaticGravity:
+ default:
+ rect->x = ref_x;
+ rect->y = ref_y;
+ break;
+ }
+}
+
+static gboolean
+static_gravity_works (MetaDisplay *display)
+{
+ return display->static_gravity_works;
+}
+
+#ifdef HAVE_XSYNC
+static void
+send_sync_request (MetaWindow *window)
+{
+ XSyncValue value;
+ XClientMessageEvent ev;
+
+ window->sync_request_serial++;
+
+ XSyncIntToValue (&value, window->sync_request_serial);
+
+ ev.type = ClientMessage;
+ ev.window = window->xwindow;
+ ev.message_type = window->display->atom_WM_PROTOCOLS;
+ ev.format = 32;
+ ev.data.l[0] = window->display->atom__NET_WM_SYNC_REQUEST;
+ /* FIXME: meta_display_get_current_time() is bad, but since calls
+ * come from meta_window_move_resize_internal (which in turn come
+ * from all over), I'm not sure what we can do to fix it. Do we
+ * want to use _roundtrip, though?
+ */
+ ev.data.l[1] = meta_display_get_current_time (window->display);
+ ev.data.l[2] = XSyncValueLow32 (value);
+ ev.data.l[3] = XSyncValueHigh32 (value);
+
+ /* We don't need to trap errors here as we are already
+ * inside an error_trap_push()/pop() pair.
+ */
+ XSendEvent (window->display->xdisplay,
+ window->xwindow, False, 0, (XEvent*) &ev);
+
+ g_get_current_time (&window->sync_request_time);
+}
+#endif
+
+static void
+meta_window_move_resize_internal (MetaWindow *window,
+ MetaMoveResizeFlags flags,
+ int gravity,
+ int root_x_nw,
+ int root_y_nw,
+ int w,
+ int h)
+{
+ /* meta_window_move_resize_internal gets called with very different
+ * meanings for root_x_nw and root_y_nw. w & h are always the area
+ * of the inner or client window (i.e. excluding the frame) and
+ * gravity is the relevant gravity associated with the request (note
+ * that gravity is ignored for move-only operations unless its
+ * e.g. a configure request). The location is different for
+ * different cases because of how this function gets called; note
+ * that in all cases what we want to find out is the upper left
+ * corner of the position of the inner window:
+ *
+ * Case | Called from (flags; gravity)
+ * -----+-----------------------------------------------
+ * 1 | A resize only ConfigureRequest
+ * 1 | meta_window_resize
+ * 1 | meta_window_resize_with_gravity
+ * 2 | New window
+ * 2 | Session restore
+ * 2 | A not-resize-only ConfigureRequest/net_moveresize_window request
+ * 3 | meta_window_move
+ * 3 | meta_window_move_resize
+ *
+ * For each of the cases, root_x_nw and root_y_nw must be treated as follows:
+ *
+ * (1) They should be entirely ignored; instead the previous position
+ * and size of the window should be resized according to the given
+ * gravity in order to determine the new position of the window.
+ * (2) Needs to be fixed up by adjust_for_gravity() as these
+ * coordinates are relative to some corner or side of the outer
+ * window (except for the case of StaticGravity) and we want to
+ * know the location of the upper left corner of the inner window.
+ * (3) These values are already the desired positon of the NW corner
+ * of the inner window
+ */
+ XWindowChanges values;
+ unsigned int mask;
+ gboolean need_configure_notify;
+ MetaFrameGeometry fgeom;
+ gboolean need_move_client = FALSE;
+ gboolean need_move_frame = FALSE;
+ gboolean need_resize_client = FALSE;
+ gboolean need_resize_frame = FALSE;
+ int frame_size_dx;
+ int frame_size_dy;
+ int size_dx;
+ int size_dy;
+ gboolean is_configure_request;
+ gboolean do_gravity_adjust;
+ gboolean is_user_action;
+ gboolean configure_frame_first;
+ gboolean use_static_gravity;
+ /* used for the configure request, but may not be final
+ * destination due to StaticGravity etc.
+ */
+ int client_move_x;
+ int client_move_y;
+ MetaRectangle new_rect;
+ MetaRectangle old_rect;
+
+ is_configure_request = (flags & META_IS_CONFIGURE_REQUEST) != 0;
+ do_gravity_adjust = (flags & META_DO_GRAVITY_ADJUST) != 0;
+ is_user_action = (flags & META_IS_USER_ACTION) != 0;
+
+ /* The action has to be a move or a resize or both... */
+ g_assert (flags & (META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION));
+
+ /* We don't need it in the idle queue anymore. */
+ meta_window_unqueue (window, META_QUEUE_MOVE_RESIZE);
+
+ meta_window_get_client_root_coords (window, &old_rect);
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Move/resize %s to %d,%d %dx%d%s%s from %d,%d %dx%d\n",
+ window->desc, root_x_nw, root_y_nw, w, h,
+ is_configure_request ? " (configure request)" : "",
+ is_user_action ? " (user move/resize)" : "",
+ old_rect.x, old_rect.y, old_rect.width, old_rect.height);
+
+ if (window->frame)
+ meta_frame_calc_geometry (window->frame,
+ &fgeom);
+
+ new_rect.x = root_x_nw;
+ new_rect.y = root_y_nw;
+ new_rect.width = w;
+ new_rect.height = h;
+
+ /* If this is a resize only, the position should be ignored and
+ * instead obtained by resizing the old rectangle according to the
+ * relevant gravity.
+ */
+ if ((flags & (META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION)) ==
+ META_IS_RESIZE_ACTION)
+ {
+ meta_rectangle_resize_with_gravity (&old_rect,
+ &new_rect,
+ gravity,
+ new_rect.width,
+ new_rect.height);
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Compensated for gravity in resize action; new pos %d,%d\n",
+ new_rect.x, new_rect.y);
+ }
+ else if (is_configure_request || do_gravity_adjust)
+ {
+ adjust_for_gravity (window,
+ window->frame ? &fgeom : NULL,
+ /* configure request coords assume
+ * the border width existed
+ */
+ is_configure_request,
+ gravity,
+ &new_rect);
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Compensated for configure_request/do_gravity_adjust needing "
+ "weird positioning; new pos %d,%d\n",
+ new_rect.x, new_rect.y);
+ }
+
+ meta_window_constrain (window,
+ window->frame ? &fgeom : NULL,
+ flags,
+ gravity,
+ &old_rect,
+ &new_rect);
+
+ w = new_rect.width;
+ h = new_rect.height;
+ root_x_nw = new_rect.x;
+ root_y_nw = new_rect.y;
+
+ if (w != window->rect.width ||
+ h != window->rect.height)
+ need_resize_client = TRUE;
+
+ window->rect.width = w;
+ window->rect.height = h;
+
+ if (window->frame)
+ {
+ int new_w, new_h;
+
+ new_w = window->rect.width + fgeom.left_width + fgeom.right_width;
+
+ if (window->shaded)
+ new_h = fgeom.top_height;
+ else
+ new_h = window->rect.height + fgeom.top_height + fgeom.bottom_height;
+
+ frame_size_dx = new_w - window->frame->rect.width;
+ frame_size_dy = new_h - window->frame->rect.height;
+
+ need_resize_frame = (frame_size_dx != 0 || frame_size_dy != 0);
+
+ window->frame->rect.width = new_w;
+ window->frame->rect.height = new_h;
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Calculated frame size %dx%d\n",
+ window->frame->rect.width,
+ window->frame->rect.height);
+ }
+ else
+ {
+ frame_size_dx = 0;
+ frame_size_dy = 0;
+ }
+
+ /* For nice effect, when growing the window we want to move/resize
+ * the frame first, when shrinking the window we want to move/resize
+ * the client first. If we grow one way and shrink the other,
+ * see which way we're moving "more"
+ *
+ * Mail from Owen subject "Suggestion: Gravity and resizing from the left"
+ * http://mail.gnome.org/archives/wm-spec-list/1999-November/msg00088.html
+ *
+ * An annoying fact you need to know in this code is that StaticGravity
+ * does nothing if you _only_ resize or _only_ move the frame;
+ * it must move _and_ resize, otherwise you get NorthWestGravity
+ * behavior. The move and resize must actually occur, it is not
+ * enough to set CWX | CWWidth but pass in the current size/pos.
+ */
+
+ if (window->frame)
+ {
+ int new_x, new_y;
+ int frame_pos_dx, frame_pos_dy;
+
+ /* Compute new frame coords */
+ new_x = root_x_nw - fgeom.left_width;
+ new_y = root_y_nw - fgeom.top_height;
+
+ frame_pos_dx = new_x - window->frame->rect.x;
+ frame_pos_dy = new_y - window->frame->rect.y;
+
+ need_move_frame = (frame_pos_dx != 0 || frame_pos_dy != 0);
+
+ window->frame->rect.x = new_x;
+ window->frame->rect.y = new_y;
+
+ /* If frame will both move and resize, then StaticGravity
+ * on the child window will kick in and implicitly move
+ * the child with respect to the frame. The implicit
+ * move will keep the child in the same place with
+ * respect to the root window. If frame only moves
+ * or only resizes, then the child will just move along
+ * with the frame.
+ */
+
+ /* window->rect.x, window->rect.y are relative to frame,
+ * remember they are the server coords
+ */
+
+ new_x = fgeom.left_width;
+ new_y = fgeom.top_height;
+
+ if (need_resize_frame && need_move_frame &&
+ static_gravity_works (window->display))
+ {
+ /* static gravity kicks in because frame
+ * is both moved and resized
+ */
+ /* when we move the frame by frame_pos_dx, frame_pos_dy the
+ * client will implicitly move relative to frame by the
+ * inverse delta.
+ *
+ * When moving client then frame, we move the client by the
+ * frame delta, to be canceled out by the implicit move by
+ * the inverse frame delta, resulting in a client at new_x,
+ * new_y.
+ *
+ * When moving frame then client, we move the client
+ * by the same delta as the frame, because the client
+ * was "left behind" by the frame - resulting in a client
+ * at new_x, new_y.
+ *
+ * In both cases we need to move the client window
+ * in all cases where we had to move the frame window.
+ */
+
+ client_move_x = new_x + frame_pos_dx;
+ client_move_y = new_y + frame_pos_dy;
+
+ if (need_move_frame)
+ need_move_client = TRUE;
+
+ use_static_gravity = TRUE;
+ }
+ else
+ {
+ client_move_x = new_x;
+ client_move_y = new_y;
+
+ if (client_move_x != window->rect.x ||
+ client_move_y != window->rect.y)
+ need_move_client = TRUE;
+
+ use_static_gravity = FALSE;
+ }
+
+ /* This is the final target position, but not necessarily what
+ * we pass to XConfigureWindow, due to StaticGravity implicit
+ * movement.
+ */
+ window->rect.x = new_x;
+ window->rect.y = new_y;
+ }
+ else
+ {
+ if (root_x_nw != window->rect.x ||
+ root_y_nw != window->rect.y)
+ need_move_client = TRUE;
+
+ window->rect.x = root_x_nw;
+ window->rect.y = root_y_nw;
+
+ client_move_x = window->rect.x;
+ client_move_y = window->rect.y;
+
+ use_static_gravity = FALSE;
+ }
+
+ /* If frame extents have changed, fill in other frame fields and
+ change frame's extents property. */
+ if (window->frame &&
+ (window->frame->child_x != fgeom.left_width ||
+ window->frame->child_y != fgeom.top_height ||
+ window->frame->right_width != fgeom.right_width ||
+ window->frame->bottom_height != fgeom.bottom_height))
+ {
+ window->frame->child_x = fgeom.left_width;
+ window->frame->child_y = fgeom.top_height;
+ window->frame->right_width = fgeom.right_width;
+ window->frame->bottom_height = fgeom.bottom_height;
+
+ update_net_frame_extents (window);
+ }
+
+ /* See ICCCM 4.1.5 for when to send ConfigureNotify */
+
+ need_configure_notify = FALSE;
+
+ /* If this is a configure request and we change nothing, then we
+ * must send configure notify.
+ */
+ if (is_configure_request &&
+ !(need_move_client || need_move_frame ||
+ need_resize_client || need_resize_frame ||
+ window->border_width != 0))
+ need_configure_notify = TRUE;
+
+ /* We must send configure notify if we move but don't resize, since
+ * the client window may not get a real event
+ */
+ if ((need_move_client || need_move_frame) &&
+ !(need_resize_client || need_resize_frame))
+ need_configure_notify = TRUE;
+
+ /* MapRequest events with a PPosition or UPosition hint with a frame
+ * are moved by marco without resizing; send a configure notify
+ * in such cases. See #322840. (Note that window->constructing is
+ * only true iff this call is due to a MapRequest, and when
+ * PPosition/UPosition hints aren't set, marco seems to send a
+ * ConfigureNotify anyway due to the above code.)
+ */
+ if (window->constructing && window->frame &&
+ ((window->size_hints.flags & PPosition) ||
+ (window->size_hints.flags & USPosition)))
+ need_configure_notify = TRUE;
+
+ /* The rest of this function syncs our new size/pos with X as
+ * efficiently as possible
+ */
+
+ /* configure frame first if we grow more than we shrink
+ */
+ size_dx = w - window->rect.width;
+ size_dy = h - window->rect.height;
+
+ configure_frame_first = (size_dx + size_dy >= 0);
+
+ if (use_static_gravity)
+ meta_window_set_gravity (window, StaticGravity);
+
+ if (configure_frame_first && window->frame)
+ meta_frame_sync_to_window (window->frame,
+ gravity,
+ need_move_frame, need_resize_frame);
+
+ values.border_width = 0;
+ values.x = client_move_x;
+ values.y = client_move_y;
+ values.width = window->rect.width;
+ values.height = window->rect.height;
+
+ mask = 0;
+ if (is_configure_request && window->border_width != 0)
+ mask |= CWBorderWidth; /* must force to 0 */
+ if (need_move_client)
+ mask |= (CWX | CWY);
+ if (need_resize_client)
+ mask |= (CWWidth | CWHeight);
+
+ if (mask != 0)
+ {
+ {
+ int newx, newy;
+ meta_window_get_position (window, &newx, &newy);
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Syncing new client geometry %d,%d %dx%d, border: %s pos: %s size: %s\n",
+ newx, newy,
+ window->rect.width, window->rect.height,
+ mask & CWBorderWidth ? "true" : "false",
+ need_move_client ? "true" : "false",
+ need_resize_client ? "true" : "false");
+ }
+
+ meta_error_trap_push (window->display);
+
+#ifdef HAVE_XSYNC
+ if (window->sync_request_counter != None &&
+ window->display->grab_sync_request_alarm != None &&
+ window->sync_request_time.tv_usec == 0 &&
+ window->sync_request_time.tv_sec == 0)
+ {
+ /* turn off updating */
+ if (window->display->compositor)
+ meta_compositor_set_updates (window->display->compositor, window, FALSE);
+
+ send_sync_request (window);
+ }
+#endif
+
+ XConfigureWindow (window->display->xdisplay,
+ window->xwindow,
+ mask,
+ &values);
+
+ meta_error_trap_pop (window->display, FALSE);
+ }
+
+ if (!configure_frame_first && window->frame)
+ meta_frame_sync_to_window (window->frame,
+ gravity,
+ need_move_frame, need_resize_frame);
+
+ /* Put gravity back to be nice to lesser window managers */
+ if (use_static_gravity)
+ meta_window_set_gravity (window, NorthWestGravity);
+
+ if (need_configure_notify)
+ send_configure_notify (window);
+
+ if (!window->placed && window->force_save_user_rect && !window->fullscreen)
+ force_save_user_window_placement (window);
+ else if (is_user_action)
+ save_user_window_placement (window);
+
+ if (need_move_frame || need_resize_frame ||
+ need_move_client || need_resize_client)
+ {
+ int newx, newy;
+ meta_window_get_position (window, &newx, &newy);
+ meta_topic (META_DEBUG_GEOMETRY,
+ "New size/position %d,%d %dx%d (user %d,%d %dx%d)\n",
+ newx, newy, window->rect.width, window->rect.height,
+ window->user_rect.x, window->user_rect.y,
+ window->user_rect.width, window->user_rect.height);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_GEOMETRY, "Size/position not modified\n");
+ }
+
+ if (window->display->grab_wireframe_active)
+ meta_window_update_wireframe (window, root_x_nw, root_y_nw, w, h);
+ else
+ meta_window_refresh_resize_popup (window);
+
+ /* Invariants leaving this function are:
+ * a) window->rect and frame->rect reflect the actual
+ * server-side size/pos of window->xwindow and frame->xwindow
+ * b) all constraints are obeyed by window->rect and frame->rect
+ */
+}
+
+void
+meta_window_resize (MetaWindow *window,
+ gboolean user_op,
+ int w,
+ int h)
+{
+ int x, y;
+ MetaMoveResizeFlags flags;
+
+ meta_window_get_position (window, &x, &y);
+
+ flags = (user_op ? META_IS_USER_ACTION : 0) | META_IS_RESIZE_ACTION;
+ meta_window_move_resize_internal (window,
+ flags,
+ NorthWestGravity,
+ x, y, w, h);
+}
+
+void
+meta_window_move (MetaWindow *window,
+ gboolean user_op,
+ int root_x_nw,
+ int root_y_nw)
+{
+ MetaMoveResizeFlags flags =
+ (user_op ? META_IS_USER_ACTION : 0) | META_IS_MOVE_ACTION;
+ meta_window_move_resize_internal (window,
+ flags,
+ NorthWestGravity,
+ root_x_nw, root_y_nw,
+ window->rect.width,
+ window->rect.height);
+}
+
+void
+meta_window_move_resize (MetaWindow *window,
+ gboolean user_op,
+ int root_x_nw,
+ int root_y_nw,
+ int w,
+ int h)
+{
+ MetaMoveResizeFlags flags =
+ (user_op ? META_IS_USER_ACTION : 0) |
+ META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION;
+ meta_window_move_resize_internal (window,
+ flags,
+ NorthWestGravity,
+ root_x_nw, root_y_nw,
+ w, h);
+}
+
+void
+meta_window_resize_with_gravity (MetaWindow *window,
+ gboolean user_op,
+ int w,
+ int h,
+ int gravity)
+{
+ int x, y;
+ MetaMoveResizeFlags flags;
+
+ meta_window_get_position (window, &x, &y);
+
+ flags = (user_op ? META_IS_USER_ACTION : 0) | META_IS_RESIZE_ACTION;
+ meta_window_move_resize_internal (window,
+ flags,
+ gravity,
+ x, y, w, h);
+}
+
+static void
+meta_window_move_resize_now (MetaWindow *window)
+{
+ /* If constraints have changed then we want to snap back to wherever
+ * the user had the window. We use user_rect for this reason. See
+ * also bug 426519 comment 3.
+ */
+ meta_window_move_resize (window, FALSE,
+ window->user_rect.x,
+ window->user_rect.y,
+ window->user_rect.width,
+ window->user_rect.height);
+}
+
+static gboolean
+idle_move_resize (gpointer data)
+{
+ GSList *tmp;
+ GSList *copy;
+ guint queue_index = GPOINTER_TO_INT (data);
+
+ meta_topic (META_DEBUG_GEOMETRY, "Clearing the move_resize queue\n");
+
+ /* Work with a copy, for reentrancy. The allowed reentrancy isn't
+ * complete; destroying a window while we're in here would result in
+ * badness. But it's OK to queue/unqueue move_resizes.
+ */
+ copy = g_slist_copy (queue_pending[queue_index]);
+ g_slist_free (queue_pending[queue_index]);
+ queue_pending[queue_index] = NULL;
+ queue_idle[queue_index] = 0;
+
+ destroying_windows_disallowed += 1;
+
+ tmp = copy;
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+
+ window = tmp->data;
+
+ /* As a side effect, sets window->move_resize_queued = FALSE */
+ meta_window_move_resize_now (window);
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (copy);
+
+ destroying_windows_disallowed -= 1;
+
+ return FALSE;
+}
+
+void
+meta_window_get_position (MetaWindow *window,
+ int *x,
+ int *y)
+{
+ if (window->frame)
+ {
+ if (x)
+ *x = window->frame->rect.x + window->frame->child_x;
+ if (y)
+ *y = window->frame->rect.y + window->frame->child_y;
+ }
+ else
+ {
+ if (x)
+ *x = window->rect.x;
+ if (y)
+ *y = window->rect.y;
+ }
+}
+
+void
+meta_window_get_client_root_coords (MetaWindow *window,
+ MetaRectangle *rect)
+{
+ meta_window_get_position (window, &rect->x, &rect->y);
+ rect->width = window->rect.width;
+ rect->height = window->rect.height;
+}
+
+void
+meta_window_get_gravity_position (MetaWindow *window,
+ int gravity,
+ int *root_x,
+ int *root_y)
+{
+ MetaRectangle frame_extents;
+ int w, h;
+ int x, y;
+
+ w = window->rect.width;
+ h = window->rect.height;
+
+ if (gravity == StaticGravity)
+ {
+ frame_extents = window->rect;
+ if (window->frame)
+ {
+ frame_extents.x = window->frame->rect.x + window->frame->child_x;
+ frame_extents.y = window->frame->rect.y + window->frame->child_y;
+ }
+ }
+ else
+ {
+ if (window->frame == NULL)
+ frame_extents = window->rect;
+ else
+ frame_extents = window->frame->rect;
+ }
+
+ x = frame_extents.x;
+ y = frame_extents.y;
+
+ switch (gravity)
+ {
+ case NorthGravity:
+ case CenterGravity:
+ case SouthGravity:
+ /* Find center of frame. */
+ x += frame_extents.width / 2;
+ /* Center client window on that point. */
+ x -= w / 2;
+ break;
+
+ case SouthEastGravity:
+ case EastGravity:
+ case NorthEastGravity:
+ /* Find right edge of frame */
+ x += frame_extents.width;
+ /* Align left edge of client at that point. */
+ x -= w;
+ break;
+ default:
+ break;
+ }
+
+ switch (gravity)
+ {
+ case WestGravity:
+ case CenterGravity:
+ case EastGravity:
+ /* Find center of frame. */
+ y += frame_extents.height / 2;
+ /* Center client window there. */
+ y -= h / 2;
+ break;
+ case SouthWestGravity:
+ case SouthGravity:
+ case SouthEastGravity:
+ /* Find south edge of frame */
+ y += frame_extents.height;
+ /* Place bottom edge of client there */
+ y -= h;
+ break;
+ default:
+ break;
+ }
+
+ if (root_x)
+ *root_x = x;
+ if (root_y)
+ *root_y = y;
+}
+
+void
+meta_window_get_geometry (MetaWindow *window,
+ int *x,
+ int *y,
+ int *width,
+ int *height)
+{
+ meta_window_get_gravity_position (window,
+ window->size_hints.win_gravity,
+ x, y);
+
+ *width = (window->rect.width - window->size_hints.base_width) /
+ window->size_hints.width_inc;
+ *height = (window->rect.height - window->size_hints.base_height) /
+ window->size_hints.height_inc;
+}
+
+void
+meta_window_get_outer_rect (const MetaWindow *window,
+ MetaRectangle *rect)
+{
+ if (window->frame)
+ *rect = window->frame->rect;
+ else
+ *rect = window->rect;
+}
+
+void
+meta_window_get_xor_rect (MetaWindow *window,
+ const MetaRectangle *grab_wireframe_rect,
+ MetaRectangle *xor_rect)
+{
+ if (window->frame)
+ {
+ xor_rect->x = grab_wireframe_rect->x - window->frame->child_x;
+ xor_rect->y = grab_wireframe_rect->y - window->frame->child_y;
+ xor_rect->width = grab_wireframe_rect->width + window->frame->child_x + window->frame->right_width;
+
+ if (window->shaded)
+ xor_rect->height = window->frame->child_y;
+ else
+ xor_rect->height = grab_wireframe_rect->height + window->frame->child_y + window->frame->bottom_height;
+ }
+ else
+ *xor_rect = *grab_wireframe_rect;
+}
+
+/* Figure out the numbers that show up in the
+ * resize popup when in reduced resources mode.
+ */
+static void
+meta_window_get_wireframe_geometry (MetaWindow *window,
+ int *width,
+ int *height)
+{
+ if (!window->display->grab_wireframe_active)
+ return;
+
+ if ((width == NULL) || (height == NULL))
+ return;
+
+ if ((window->display->grab_window->size_hints.width_inc <= 1) ||
+ (window->display->grab_window->size_hints.height_inc <= 1))
+ {
+ *width = -1;
+ *height = -1;
+ return;
+ }
+
+ *width = window->display->grab_wireframe_rect.width -
+ window->display->grab_window->size_hints.base_width;
+ *width /= window->display->grab_window->size_hints.width_inc;
+
+ *height = window->display->grab_wireframe_rect.height -
+ window->display->grab_window->size_hints.base_height;
+ *height /= window->display->grab_window->size_hints.height_inc;
+}
+
+/* XXX META_EFFECT_ALT_TAB, well, this and others */
+void
+meta_window_begin_wireframe (MetaWindow *window)
+{
+
+ MetaRectangle new_xor;
+ int display_width, display_height;
+
+ meta_window_get_client_root_coords (window,
+ &window->display->grab_wireframe_rect);
+
+ meta_window_get_xor_rect (window, &window->display->grab_wireframe_rect,
+ &new_xor);
+ meta_window_get_wireframe_geometry (window, &display_width, &display_height);
+
+ meta_effects_begin_wireframe (window->screen,
+ &new_xor, display_width, display_height);
+
+ window->display->grab_wireframe_last_xor_rect = new_xor;
+ window->display->grab_wireframe_last_display_width = display_width;
+ window->display->grab_wireframe_last_display_height = display_height;
+}
+
+void
+meta_window_update_wireframe (MetaWindow *window,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+
+ MetaRectangle new_xor;
+ int display_width, display_height;
+
+ window->display->grab_wireframe_rect.x = x;
+ window->display->grab_wireframe_rect.y = y;
+ window->display->grab_wireframe_rect.width = width;
+ window->display->grab_wireframe_rect.height = height;
+
+ meta_window_get_xor_rect (window, &window->display->grab_wireframe_rect,
+ &new_xor);
+ meta_window_get_wireframe_geometry (window, &display_width, &display_height);
+
+ meta_effects_update_wireframe (window->screen,
+ &window->display->grab_wireframe_last_xor_rect,
+ window->display->grab_wireframe_last_display_width,
+ window->display->grab_wireframe_last_display_height,
+ &new_xor, display_width, display_height);
+
+ window->display->grab_wireframe_last_xor_rect = new_xor;
+ window->display->grab_wireframe_last_display_width = display_width;
+ window->display->grab_wireframe_last_display_height = display_height;
+}
+
+void
+meta_window_end_wireframe (MetaWindow *window)
+{
+ meta_effects_end_wireframe (window->display->grab_window->screen,
+ &window->display->grab_wireframe_last_xor_rect,
+ window->display->grab_wireframe_last_display_width,
+ window->display->grab_wireframe_last_display_height);
+}
+
+const char*
+meta_window_get_startup_id (MetaWindow *window)
+{
+ if (window->startup_id == NULL)
+ {
+ MetaGroup *group;
+
+ group = meta_window_get_group (window);
+
+ if (group != NULL)
+ return meta_group_get_startup_id (group);
+ }
+
+ return window->startup_id;
+}
+
+static MetaWindow*
+get_modal_transient (MetaWindow *window)
+{
+ GSList *windows;
+ GSList *tmp;
+ MetaWindow *modal_transient;
+
+ /* A window can't be the transient of itself, but this is just for
+ * convenience in the loop below; we manually fix things up at the
+ * end if no real modal transient was found.
+ */
+ modal_transient = window;
+
+ windows = meta_display_list_windows (window->display);
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *transient = tmp->data;
+
+ if (transient->xtransient_for == modal_transient->xwindow &&
+ transient->wm_state_modal)
+ {
+ modal_transient = transient;
+ tmp = windows;
+ continue;
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (windows);
+
+ if (window == modal_transient)
+ modal_transient = NULL;
+
+ return modal_transient;
+}
+
+/* XXX META_EFFECT_FOCUS */
+void
+meta_window_focus (MetaWindow *window,
+ guint32 timestamp)
+{
+ MetaWindow *modal_transient;
+
+ meta_topic (META_DEBUG_FOCUS,
+ "Setting input focus to window %s, input: %d take_focus: %d\n",
+ window->desc, window->input, window->take_focus);
+
+ if (window->display->grab_window &&
+ window->display->grab_window->all_keys_grabbed)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Current focus window %s has global keygrab, not focusing window %s after all\n",
+ window->display->grab_window->desc, window->desc);
+ return;
+ }
+
+ modal_transient = get_modal_transient (window);
+ if (modal_transient != NULL &&
+ !modal_transient->unmanaging)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "%s has %s as a modal transient, so focusing it instead.\n",
+ window->desc, modal_transient->desc);
+ if (!modal_transient->on_all_workspaces &&
+ modal_transient->workspace != window->screen->active_workspace)
+ meta_window_change_workspace (modal_transient,
+ window->screen->active_workspace);
+ window = modal_transient;
+ }
+
+ meta_window_flush_calc_showing (window);
+
+ if (!window->mapped && !window->shaded)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Window %s is not showing, not focusing after all\n",
+ window->desc);
+ return;
+ }
+
+ /* For output-only or shaded windows, focus the frame.
+ * This seems to result in the client window getting key events
+ * though, so I don't know if it's icccm-compliant.
+ *
+ * Still, we have to do this or keynav breaks for these windows.
+ */
+ if (window->frame &&
+ (window->shaded ||
+ !(window->input || window->take_focus)))
+ {
+ if (window->frame)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing frame of %s\n", window->desc);
+ meta_display_set_input_focus_window (window->display,
+ window,
+ TRUE,
+ timestamp);
+ }
+ }
+ else
+ {
+ if (window->input)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Setting input focus on %s since input = true\n",
+ window->desc);
+ meta_display_set_input_focus_window (window->display,
+ window,
+ FALSE,
+ timestamp);
+ }
+
+ if (window->take_focus)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Sending WM_TAKE_FOCUS to %s since take_focus = true\n",
+ window->desc);
+ meta_window_send_icccm_message (window,
+ window->display->atom_WM_TAKE_FOCUS,
+ timestamp);
+ window->display->expected_focus_window = window;
+ }
+ }
+
+ if (window->wm_state_demands_attention)
+ meta_window_unset_demands_attention(window);
+
+ meta_effect_run_focus(window, NULL, NULL);
+}
+
+static void
+meta_window_change_workspace_without_transients (MetaWindow *window,
+ MetaWorkspace *workspace)
+{
+ meta_verbose ("Changing window %s to workspace %d\n",
+ window->desc, meta_workspace_index (workspace));
+
+ /* unstick if stuck. meta_window_unstick would call
+ * meta_window_change_workspace recursively if the window
+ * is not in the active workspace.
+ */
+ if (window->on_all_workspaces)
+ meta_window_unstick (window);
+
+ /* See if we're already on this space. If not, make sure we are */
+ if (window->workspace != workspace)
+ {
+ meta_workspace_remove_window (window->workspace, window);
+ meta_workspace_add_window (workspace, window);
+ }
+}
+
+static gboolean
+change_workspace_foreach (MetaWindow *window,
+ void *data)
+{
+ meta_window_change_workspace_without_transients (window, data);
+ return TRUE;
+}
+
+void
+meta_window_change_workspace (MetaWindow *window,
+ MetaWorkspace *workspace)
+{
+ meta_window_change_workspace_without_transients (window, workspace);
+
+ meta_window_foreach_transient (window, change_workspace_foreach,
+ workspace);
+ meta_window_foreach_ancestor (window, change_workspace_foreach,
+ workspace);
+}
+
+static void
+window_stick_impl (MetaWindow *window)
+{
+ GList *tmp;
+ MetaWorkspace *workspace;
+
+ meta_verbose ("Sticking window %s current on_all_workspaces = %d\n",
+ window->desc, window->on_all_workspaces);
+
+ if (window->on_all_workspaces)
+ return;
+
+ /* We don't change window->workspaces, because we revert
+ * to that original workspace list if on_all_workspaces is
+ * toggled back off.
+ */
+ window->on_all_workspaces = TRUE;
+
+ /* We do, however, change the MRU lists of all the workspaces
+ */
+ tmp = window->screen->workspaces;
+ while (tmp)
+ {
+ workspace = (MetaWorkspace *) tmp->data;
+ if (!g_list_find (workspace->mru_list, window))
+ workspace->mru_list = g_list_prepend (workspace->mru_list, window);
+
+ tmp = tmp->next;
+ }
+
+ meta_window_set_current_workspace_hint (window);
+
+ meta_window_queue(window, META_QUEUE_CALC_SHOWING);
+}
+
+static void
+window_unstick_impl (MetaWindow *window)
+{
+ GList *tmp;
+ MetaWorkspace *workspace;
+
+ if (!window->on_all_workspaces)
+ return;
+
+ /* Revert to window->workspaces */
+
+ window->on_all_workspaces = FALSE;
+
+ /* Remove window from MRU lists that it doesn't belong in */
+ tmp = window->screen->workspaces;
+ while (tmp)
+ {
+ workspace = (MetaWorkspace *) tmp->data;
+ if (window->workspace != workspace)
+ workspace->mru_list = g_list_remove (workspace->mru_list, window);
+ tmp = tmp->next;
+ }
+
+ /* We change ourselves to the active workspace, since otherwise you'd get
+ * a weird window-vaporization effect. Once we have UI for being
+ * on more than one workspace this should probably be add_workspace
+ * not change_workspace.
+ */
+ if (window->screen->active_workspace != window->workspace)
+ meta_window_change_workspace (window, window->screen->active_workspace);
+
+ meta_window_set_current_workspace_hint (window);
+
+ meta_window_queue(window, META_QUEUE_CALC_SHOWING);
+}
+
+static gboolean
+stick_foreach_func (MetaWindow *window,
+ void *data)
+{
+ gboolean stick;
+
+ stick = *(gboolean*)data;
+ if (stick)
+ window_stick_impl (window);
+ else
+ window_unstick_impl (window);
+ return TRUE;
+}
+
+void
+meta_window_stick (MetaWindow *window)
+{
+ gboolean stick = TRUE;
+ window_stick_impl (window);
+ meta_window_foreach_transient (window,
+ stick_foreach_func,
+ &stick);
+}
+
+void
+meta_window_unstick (MetaWindow *window)
+{
+ gboolean stick = FALSE;
+ window_unstick_impl (window);
+ meta_window_foreach_transient (window,
+ stick_foreach_func,
+ &stick);
+}
+
+unsigned long
+meta_window_get_net_wm_desktop (MetaWindow *window)
+{
+ if (window->on_all_workspaces)
+ return 0xFFFFFFFF;
+ else
+ return meta_workspace_index (window->workspace);
+}
+
+static void
+update_net_frame_extents (MetaWindow *window)
+{
+ unsigned long data[4] = { 0, 0, 0, 0 };
+
+ if (window->frame)
+ {
+ /* Left */
+ data[0] = window->frame->child_x;
+ /* Right */
+ data[1] = window->frame->right_width;
+ /* Top */
+ data[2] = window->frame->child_y;
+ /* Bottom */
+ data[3] = window->frame->bottom_height;
+ }
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Setting _NET_FRAME_EXTENTS on managed window 0x%lx "
+ "to left = %lu, right = %lu, top = %lu, bottom = %lu\n",
+ window->xwindow, data[0], data[1], data[2], data[3]);
+
+ meta_error_trap_push (window->display);
+ XChangeProperty (window->display->xdisplay, window->xwindow,
+ window->display->atom__NET_FRAME_EXTENTS,
+ XA_CARDINAL,
+ 32, PropModeReplace, (guchar*) data, 4);
+ meta_error_trap_pop (window->display, FALSE);
+}
+
+void
+meta_window_set_current_workspace_hint (MetaWindow *window)
+{
+ /* FIXME if on more than one workspace, we claim to be "sticky",
+ * the WM spec doesn't say what to do here.
+ */
+ unsigned long data[1];
+
+ if (window->workspace == NULL)
+ {
+ /* this happens when unmanaging windows */
+ return;
+ }
+
+ data[0] = meta_window_get_net_wm_desktop (window);
+
+ meta_verbose ("Setting _NET_WM_DESKTOP of %s to %lu\n",
+ window->desc, data[0]);
+
+ meta_error_trap_push (window->display);
+ XChangeProperty (window->display->xdisplay, window->xwindow,
+ window->display->atom__NET_WM_DESKTOP,
+ XA_CARDINAL,
+ 32, PropModeReplace, (guchar*) data, 1);
+ meta_error_trap_pop (window->display, FALSE);
+}
+
+static gboolean
+find_root_ancestor (MetaWindow *window,
+ void *data)
+{
+ MetaWindow **ancestor = data;
+
+ /* Overwrite the previously "most-root" ancestor with the new one found */
+ *ancestor = window;
+
+ /* We want this to continue until meta_window_foreach_ancestor quits because
+ * there are no more valid ancestors.
+ */
+ return TRUE;
+}
+
+MetaWindow *
+meta_window_find_root_ancestor (MetaWindow *window)
+{
+ MetaWindow *ancestor;
+ ancestor = window;
+ meta_window_foreach_ancestor (window, find_root_ancestor, &ancestor);
+ return ancestor;
+}
+
+void
+meta_window_raise (MetaWindow *window)
+{
+ MetaWindow *ancestor;
+ ancestor = meta_window_find_root_ancestor (window);
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Raising window %s, ancestor of %s\n",
+ ancestor->desc, window->desc);
+
+ /* Raise the ancestor of the window (if the window has no ancestor,
+ * then ancestor will be set to the window itself); do this because
+ * it's weird to see windows from other apps stacked between a child
+ * and parent window of the currently active app. The stacking
+ * constraints in stack.c then magically take care of raising all
+ * the child windows appropriately.
+ */
+ if (window->screen->stack == ancestor->screen->stack)
+ meta_stack_raise (window->screen->stack, ancestor);
+ else
+ {
+ meta_warning (
+ "Either stacks aren't per screen or some window has a weird "
+ "transient_for hint; window->screen->stack != "
+ "ancestor->screen->stack. window = %s, ancestor = %s.\n",
+ window->desc, ancestor->desc);
+ /* We could raise the window here, but don't want to do that twice and
+ * so we let the case below handle that.
+ */
+ }
+
+ /* Okay, so stacking constraints misses one case: If a window has
+ * two children and we want to raise one of those children, then
+ * raising the ancestor isn't enough; we need to also raise the
+ * correct child. See bug 307875.
+ */
+ if (window != ancestor)
+ meta_stack_raise (window->screen->stack, window);
+}
+
+void
+meta_window_lower (MetaWindow *window)
+{
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Lowering window %s\n", window->desc);
+
+ meta_stack_lower (window->screen->stack, window);
+}
+
+void
+meta_window_send_icccm_message (MetaWindow *window,
+ Atom atom,
+ guint32 timestamp)
+{
+ /* This comment and code are from twm, copyright
+ * Open Group, Evans & Sutherland, etc.
+ */
+
+ /*
+ * ICCCM Client Messages - Section 4.2.8 of the ICCCM dictates that all
+ * client messages will have the following form:
+ *
+ * event type ClientMessage
+ * message type _XA_WM_PROTOCOLS
+ * window tmp->w
+ * format 32
+ * data[0] message atom
+ * data[1] time stamp
+ */
+
+ XClientMessageEvent ev;
+
+ ev.type = ClientMessage;
+ ev.window = window->xwindow;
+ ev.message_type = window->display->atom_WM_PROTOCOLS;
+ ev.format = 32;
+ ev.data.l[0] = atom;
+ ev.data.l[1] = timestamp;
+
+ meta_error_trap_push (window->display);
+ XSendEvent (window->display->xdisplay,
+ window->xwindow, False, 0, (XEvent*) &ev);
+ meta_error_trap_pop (window->display, FALSE);
+}
+
+void
+meta_window_move_resize_request (MetaWindow *window,
+ guint value_mask,
+ int gravity,
+ int new_x,
+ int new_y,
+ int new_width,
+ int new_height)
+{
+ int x, y, width, height;
+ gboolean allow_position_change;
+ gboolean in_grab_op;
+ MetaMoveResizeFlags flags;
+
+ /* We ignore configure requests while the user is moving/resizing
+ * the window, since these represent the app sucking and fighting
+ * the user, most likely due to a bug in the app (e.g. pfaedit
+ * seemed to do this)
+ *
+ * Still have to do the ConfigureNotify and all, but pretend the
+ * app asked for the current size/position instead of the new one.
+ */
+ in_grab_op = FALSE;
+ if (window->display->grab_op != META_GRAB_OP_NONE &&
+ window == window->display->grab_window)
+ {
+ switch (window->display->grab_op)
+ {
+ case META_GRAB_OP_MOVING:
+ case META_GRAB_OP_RESIZING_SE:
+ case META_GRAB_OP_RESIZING_S:
+ case META_GRAB_OP_RESIZING_SW:
+ case META_GRAB_OP_RESIZING_N:
+ case META_GRAB_OP_RESIZING_NE:
+ case META_GRAB_OP_RESIZING_NW:
+ case META_GRAB_OP_RESIZING_W:
+ case META_GRAB_OP_RESIZING_E:
+ in_grab_op = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* it's essential to use only the explicitly-set fields,
+ * and otherwise use our current up-to-date position.
+ *
+ * Otherwise you get spurious position changes when the app changes
+ * size, for example, if window->rect is not in sync with the
+ * server-side position in effect when the configure request was
+ * generated.
+ */
+ meta_window_get_gravity_position (window,
+ gravity,
+ &x, &y);
+
+ allow_position_change = FALSE;
+
+ if (meta_prefs_get_disable_workarounds ())
+ {
+ if (window->type == META_WINDOW_DIALOG ||
+ window->type == META_WINDOW_MODAL_DIALOG ||
+ window->type == META_WINDOW_SPLASHSCREEN)
+ ; /* No position change for these */
+ else if ((window->size_hints.flags & PPosition) ||
+ /* USPosition is just stale if window is placed;
+ * no --geometry involved here.
+ */
+ ((window->size_hints.flags & USPosition) &&
+ !window->placed))
+ allow_position_change = TRUE;
+ }
+ else
+ {
+ allow_position_change = TRUE;
+ }
+
+ if (in_grab_op)
+ allow_position_change = FALSE;
+
+ if (allow_position_change)
+ {
+ if (value_mask & CWX)
+ x = new_x;
+ if (value_mask & CWY)
+ y = new_y;
+ if (value_mask & (CWX | CWY))
+ {
+ /* Once manually positioned, windows shouldn't be placed
+ * by the window manager.
+ */
+ window->placed = TRUE;
+ }
+ }
+ else
+ {
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Not allowing position change for window %s PPosition 0x%lx USPosition 0x%lx type %u\n",
+ window->desc, window->size_hints.flags & PPosition,
+ window->size_hints.flags & USPosition,
+ window->type);
+ }
+
+ width = window->rect.width;
+ height = window->rect.height;
+ if (!in_grab_op)
+ {
+ if (value_mask & CWWidth)
+ width = new_width;
+
+ if (value_mask & CWHeight)
+ height = new_height;
+ }
+
+ /* ICCCM 4.1.5 */
+
+ /* We're ignoring the value_mask here, since sizes
+ * not in the mask will be the current window geometry.
+ */
+ window->size_hints.x = x;
+ window->size_hints.y = y;
+ window->size_hints.width = width;
+ window->size_hints.height = height;
+
+ /* NOTE: We consider ConfigureRequests to be "user" actions in one
+ * way, but not in another. Explanation of the two cases are in the
+ * next two big comments.
+ */
+
+ /* The constraints code allows user actions to move windows
+ * offscreen, etc., and configure request actions would often send
+ * windows offscreen when users don't want it if not constrained
+ * (e.g. hitting a dropdown triangle in a fileselector to show more
+ * options, which makes the window bigger). Thus we do not set
+ * META_IS_USER_ACTION in flags to the
+ * meta_window_move_resize_internal() call.
+ */
+ flags = META_IS_CONFIGURE_REQUEST;
+ if (value_mask & (CWX | CWY))
+ flags |= META_IS_MOVE_ACTION;
+ if (value_mask & (CWWidth | CWHeight))
+ flags |= META_IS_RESIZE_ACTION;
+
+ if (flags & (META_IS_MOVE_ACTION | META_IS_RESIZE_ACTION))
+ meta_window_move_resize_internal (window,
+ flags,
+ gravity,
+ x,
+ y,
+ width,
+ height);
+
+ /* window->user_rect exists to allow "snapping-back" the window if a
+ * new strut is set (causing the window to move) and then the strut
+ * is later removed without the user moving the window in the
+ * interim. We'd like to "snap-back" to the position specified by
+ * ConfigureRequest events (at least the constrained version of the
+ * ConfigureRequest, since that is guaranteed to be onscreen) so we
+ * set user_rect here.
+ *
+ * See also bug 426519.
+ */
+ save_user_window_placement (window);
+}
+
+gboolean
+meta_window_configure_request (MetaWindow *window,
+ XEvent *event)
+{
+ /* Note that x, y is the corner of the window border,
+ * and width, height is the size of the window inside
+ * its border, but that we always deny border requests
+ * and give windows a border of 0. But we save the
+ * requested border here.
+ */
+ if (event->xconfigurerequest.value_mask & CWBorderWidth)
+ window->border_width = event->xconfigurerequest.border_width;
+
+ meta_window_move_resize_request(window,
+ event->xconfigurerequest.value_mask,
+ window->size_hints.win_gravity,
+ event->xconfigurerequest.x,
+ event->xconfigurerequest.y,
+ event->xconfigurerequest.width,
+ event->xconfigurerequest.height);
+
+ /* Handle stacking. We only handle raises/lowers, mostly because
+ * stack.c really can't deal with anything else. I guess we'll fix
+ * that if a client turns up that really requires it. Only a very
+ * few clients even require the raise/lower (and in fact all client
+ * attempts to deal with stacking order are essentially broken,
+ * since they have no idea what other clients are involved or how
+ * the stack looks).
+ *
+ * I'm pretty sure no interesting client uses TopIf, BottomIf, or
+ * Opposite anyway, so the only possible missing thing is
+ * Above/Below with a sibling set. For now we just pretend there's
+ * never a sibling set and always do the full raise/lower instead of
+ * the raise-just-above/below-sibling.
+ */
+ if (event->xconfigurerequest.value_mask & CWStackMode)
+ {
+ MetaWindow *active_window;
+ active_window = window->display->expected_focus_window;
+ if (meta_prefs_get_disable_workarounds () ||
+ !meta_prefs_get_raise_on_click ())
+ {
+ meta_topic (META_DEBUG_STACK,
+ "%s sent an xconfigure stacking request; this is "
+ "broken behavior and the request is being ignored.\n",
+ window->desc);
+ }
+ else if (active_window &&
+ !meta_window_same_application (window, active_window) &&
+ XSERVER_TIME_IS_BEFORE (window->net_wm_user_time,
+ active_window->net_wm_user_time))
+ {
+ meta_topic (META_DEBUG_STACK,
+ "Ignoring xconfigure stacking request from %s (with "
+ "user_time %u); currently active application is %s (with "
+ "user_time %u).\n",
+ window->desc,
+ window->net_wm_user_time,
+ active_window->desc,
+ active_window->net_wm_user_time);
+ if (event->xconfigurerequest.detail == Above)
+ meta_window_set_demands_attention(window);
+ }
+ else
+ {
+ switch (event->xconfigurerequest.detail)
+ {
+ case Above:
+ meta_window_raise (window);
+ break;
+ case Below:
+ meta_window_lower (window);
+ break;
+ case TopIf:
+ case BottomIf:
+ case Opposite:
+ break;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+meta_window_property_notify (MetaWindow *window,
+ XEvent *event)
+{
+ return process_property_notify (window, &event->xproperty);
+}
+
+#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0
+#define _NET_WM_MOVERESIZE_SIZE_TOP 1
+#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2
+#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6
+#define _NET_WM_MOVERESIZE_SIZE_LEFT 7
+#define _NET_WM_MOVERESIZE_MOVE 8
+#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9
+#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10
+#define _NET_WM_MOVERESIZE_CANCEL 11
+
+gboolean
+meta_window_client_message (MetaWindow *window,
+ XEvent *event)
+{
+ MetaDisplay *display;
+
+ display = window->display;
+
+ if (event->xclient.message_type ==
+ display->atom__NET_CLOSE_WINDOW)
+ {
+ guint32 timestamp;
+
+ if (event->xclient.data.l[0] != 0)
+ timestamp = event->xclient.data.l[0];
+ else
+ {
+ meta_warning ("Receiving a NET_CLOSE_WINDOW message for %s without "
+ "a timestamp! This means some buggy (outdated) "
+ "application is on the loose!\n",
+ window->desc);
+ timestamp = meta_display_get_current_time (window->display);
+ }
+
+ meta_window_delete (window, timestamp);
+
+ return TRUE;
+ }
+ else if (event->xclient.message_type ==
+ display->atom__NET_WM_DESKTOP)
+ {
+ int space;
+ MetaWorkspace *workspace;
+
+ space = event->xclient.data.l[0];
+
+ meta_verbose ("Request to move %s to workspace %d\n",
+ window->desc, space);
+
+ workspace =
+ meta_screen_get_workspace_by_index (window->screen,
+ space);
+
+ if (workspace)
+ {
+ if (window->on_all_workspaces)
+ meta_window_unstick (window);
+ meta_window_change_workspace (window, workspace);
+ }
+ else if (space == (int) 0xFFFFFFFF)
+ {
+ meta_window_stick (window);
+ }
+ else
+ {
+ meta_verbose ("No such workspace %d for screen\n", space);
+ }
+
+ meta_verbose ("Window %s now on_all_workspaces = %d\n",
+ window->desc, window->on_all_workspaces);
+
+ return TRUE;
+ }
+ else if (event->xclient.message_type ==
+ display->atom__NET_WM_STATE)
+ {
+ gulong action;
+ Atom first;
+ Atom second;
+
+ action = event->xclient.data.l[0];
+ first = event->xclient.data.l[1];
+ second = event->xclient.data.l[2];
+
+ if (meta_is_verbose ())
+ {
+ char *str1;
+ char *str2;
+
+ meta_error_trap_push_with_return (display);
+ str1 = XGetAtomName (display->xdisplay, first);
+ if (meta_error_trap_pop_with_return (display, TRUE) != Success)
+ str1 = NULL;
+
+ meta_error_trap_push_with_return (display);
+ str2 = XGetAtomName (display->xdisplay, second);
+ if (meta_error_trap_pop_with_return (display, TRUE) != Success)
+ str2 = NULL;
+
+ meta_verbose ("Request to change _NET_WM_STATE action %lu atom1: %s atom2: %s\n",
+ action,
+ str1 ? str1 : "(unknown)",
+ str2 ? str2 : "(unknown)");
+
+ meta_XFree (str1);
+ meta_XFree (str2);
+ }
+
+ if (first == display->atom__NET_WM_STATE_SHADED ||
+ second == display->atom__NET_WM_STATE_SHADED)
+ {
+ gboolean shade;
+ guint32 timestamp;
+
+ /* Stupid protocol has no timestamp; of course, shading
+ * sucks anyway so who really cares that we're forced to do
+ * a roundtrip here?
+ */
+ timestamp = meta_display_get_current_time_roundtrip (window->display);
+
+ shade = (action == _NET_WM_STATE_ADD ||
+ (action == _NET_WM_STATE_TOGGLE && !window->shaded));
+ if (shade && window->has_shade_func)
+ meta_window_shade (window, timestamp);
+ else
+ meta_window_unshade (window, timestamp);
+ }
+
+ if (first == display->atom__NET_WM_STATE_FULLSCREEN ||
+ second == display->atom__NET_WM_STATE_FULLSCREEN)
+ {
+ gboolean make_fullscreen;
+
+ make_fullscreen = (action == _NET_WM_STATE_ADD ||
+ (action == _NET_WM_STATE_TOGGLE && !window->fullscreen));
+ if (make_fullscreen && window->has_fullscreen_func)
+ meta_window_make_fullscreen (window);
+ else
+ meta_window_unmake_fullscreen (window);
+ }
+
+ if (first == display->atom__NET_WM_STATE_MAXIMIZED_HORZ ||
+ second == display->atom__NET_WM_STATE_MAXIMIZED_HORZ)
+ {
+ gboolean max;
+
+ max = (action == _NET_WM_STATE_ADD ||
+ (action == _NET_WM_STATE_TOGGLE &&
+ !window->maximized_horizontally));
+ if (max && window->has_maximize_func)
+ {
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+ meta_window_maximize (window, META_MAXIMIZE_HORIZONTAL);
+ }
+ else
+ {
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+ meta_window_unmaximize (window, META_MAXIMIZE_HORIZONTAL);
+ }
+ }
+
+ if (first == display->atom__NET_WM_STATE_MAXIMIZED_VERT ||
+ second == display->atom__NET_WM_STATE_MAXIMIZED_VERT)
+ {
+ gboolean max;
+
+ max = (action == _NET_WM_STATE_ADD ||
+ (action == _NET_WM_STATE_TOGGLE &&
+ !window->maximized_vertically));
+ if (max && window->has_maximize_func)
+ {
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+ meta_window_maximize (window, META_MAXIMIZE_VERTICAL);
+ }
+ else
+ {
+ if (meta_prefs_get_raise_on_click ())
+ meta_window_raise (window);
+ meta_window_unmaximize (window, META_MAXIMIZE_VERTICAL);
+ }
+ }
+
+ if (first == display->atom__NET_WM_STATE_MODAL ||
+ second == display->atom__NET_WM_STATE_MODAL)
+ {
+ window->wm_state_modal =
+ (action == _NET_WM_STATE_ADD) ||
+ (action == _NET_WM_STATE_TOGGLE && !window->wm_state_modal);
+
+ recalc_window_type (window);
+ meta_window_queue(window, META_QUEUE_MOVE_RESIZE);
+ }
+
+ if (first == display->atom__NET_WM_STATE_SKIP_PAGER ||
+ second == display->atom__NET_WM_STATE_SKIP_PAGER)
+ {
+ window->wm_state_skip_pager =
+ (action == _NET_WM_STATE_ADD) ||
+ (action == _NET_WM_STATE_TOGGLE && !window->skip_pager);
+
+ recalc_window_features (window);
+ set_net_wm_state (window);
+ }
+
+ if (first == display->atom__NET_WM_STATE_SKIP_TASKBAR ||
+ second == display->atom__NET_WM_STATE_SKIP_TASKBAR)
+ {
+ window->wm_state_skip_taskbar =
+ (action == _NET_WM_STATE_ADD) ||
+ (action == _NET_WM_STATE_TOGGLE && !window->skip_taskbar);
+
+ recalc_window_features (window);
+ set_net_wm_state (window);
+ }
+
+ if (first == display->atom__NET_WM_STATE_ABOVE ||
+ second == display->atom__NET_WM_STATE_ABOVE)
+ {
+ window->wm_state_above =
+ (action == _NET_WM_STATE_ADD) ||
+ (action == _NET_WM_STATE_TOGGLE && !window->wm_state_above);
+
+ meta_window_update_layer (window);
+ set_net_wm_state (window);
+ }
+
+ if (first == display->atom__NET_WM_STATE_BELOW ||
+ second == display->atom__NET_WM_STATE_BELOW)
+ {
+ window->wm_state_below =
+ (action == _NET_WM_STATE_ADD) ||
+ (action == _NET_WM_STATE_TOGGLE && !window->wm_state_below);
+
+ meta_window_update_layer (window);
+ set_net_wm_state (window);
+ }
+
+ if (first == display->atom__NET_WM_STATE_DEMANDS_ATTENTION ||
+ second == display->atom__NET_WM_STATE_DEMANDS_ATTENTION)
+ {
+ if ((action == _NET_WM_STATE_ADD) ||
+ (action == _NET_WM_STATE_TOGGLE && !window->wm_state_demands_attention))
+ meta_window_set_demands_attention (window);
+ else
+ meta_window_unset_demands_attention (window);
+ }
+
+ if (first == display->atom__NET_WM_STATE_STICKY ||
+ second == display->atom__NET_WM_STATE_STICKY)
+ {
+ if ((action == _NET_WM_STATE_ADD) ||
+ (action == _NET_WM_STATE_TOGGLE && !window->on_all_workspaces))
+ meta_window_stick (window);
+ else
+ meta_window_unstick (window);
+ }
+
+ return TRUE;
+ }
+ else if (event->xclient.message_type ==
+ display->atom_WM_CHANGE_STATE)
+ {
+ meta_verbose ("WM_CHANGE_STATE client message, state: %ld\n",
+ event->xclient.data.l[0]);
+ if (event->xclient.data.l[0] == IconicState &&
+ window->has_minimize_func)
+ meta_window_minimize (window);
+
+ return TRUE;
+ }
+ else if (event->xclient.message_type ==
+ display->atom__NET_WM_MOVERESIZE)
+ {
+ int x_root;
+ int y_root;
+ int action;
+ MetaGrabOp op;
+ int button;
+ guint32 timestamp;
+
+ /* _NET_WM_MOVERESIZE messages are almost certainly going to come from
+ * clients when users click on the fake "frame" that the client has,
+ * thus we should also treat such messages as though it were a
+ * "frame action".
+ */
+ gboolean const frame_action = TRUE;
+
+ x_root = event->xclient.data.l[0];
+ y_root = event->xclient.data.l[1];
+ action = event->xclient.data.l[2];
+ button = event->xclient.data.l[3];
+
+ /* FIXME: What a braindead protocol; no timestamp?!? */
+ timestamp = meta_display_get_current_time_roundtrip (display);
+ meta_warning ("Received a _NET_WM_MOVERESIZE message for %s; these "
+ "messages lack timestamps and therefore suck.\n",
+ window->desc);
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Received _NET_WM_MOVERESIZE message on %s, %d,%d action = %d, button %d\n",
+ window->desc,
+ x_root, y_root, action, button);
+
+ op = META_GRAB_OP_NONE;
+ switch (action)
+ {
+ case _NET_WM_MOVERESIZE_SIZE_TOPLEFT:
+ op = META_GRAB_OP_RESIZING_NW;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_TOP:
+ op = META_GRAB_OP_RESIZING_N;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
+ op = META_GRAB_OP_RESIZING_NE;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_RIGHT:
+ op = META_GRAB_OP_RESIZING_E;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
+ op = META_GRAB_OP_RESIZING_SE;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOM:
+ op = META_GRAB_OP_RESIZING_S;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
+ op = META_GRAB_OP_RESIZING_SW;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_LEFT:
+ op = META_GRAB_OP_RESIZING_W;
+ break;
+ case _NET_WM_MOVERESIZE_MOVE:
+ op = META_GRAB_OP_MOVING;
+ break;
+ case _NET_WM_MOVERESIZE_SIZE_KEYBOARD:
+ op = META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN;
+ break;
+ case _NET_WM_MOVERESIZE_MOVE_KEYBOARD:
+ op = META_GRAB_OP_KEYBOARD_MOVING;
+ break;
+ case _NET_WM_MOVERESIZE_CANCEL:
+ /* handled below */
+ break;
+ default:
+ break;
+ }
+
+ if (action == _NET_WM_MOVERESIZE_CANCEL)
+ {
+ meta_display_end_grab_op (window->display, timestamp);
+ }
+ else if (op != META_GRAB_OP_NONE &&
+ ((window->has_move_func && op == META_GRAB_OP_KEYBOARD_MOVING) ||
+ (window->has_resize_func && op == META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN)))
+ {
+ meta_window_begin_grab_op (window, op, frame_action, timestamp);
+ }
+ else if (op != META_GRAB_OP_NONE &&
+ ((window->has_move_func && op == META_GRAB_OP_MOVING) ||
+ (window->has_resize_func &&
+ (op != META_GRAB_OP_MOVING &&
+ op != META_GRAB_OP_KEYBOARD_MOVING))))
+ {
+ /*
+ * the button SHOULD already be included in the message
+ */
+ if (button == 0)
+ {
+ int x, y, query_root_x, query_root_y;
+ Window root, child;
+ guint mask;
+
+ /* The race conditions in this _NET_WM_MOVERESIZE thing
+ * are mind-boggling
+ */
+ mask = 0;
+ meta_error_trap_push (window->display);
+ XQueryPointer (window->display->xdisplay,
+ window->xwindow,
+ &root, &child,
+ &query_root_x, &query_root_y,
+ &x, &y,
+ &mask);
+ meta_error_trap_pop (window->display, TRUE);
+
+ if (mask & Button1Mask)
+ button = 1;
+ else if (mask & Button2Mask)
+ button = 2;
+ else if (mask & Button3Mask)
+ button = 3;
+ else
+ button = 0;
+ }
+
+ if (button != 0)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Beginning move/resize with button = %d\n", button);
+ meta_display_begin_grab_op (window->display,
+ window->screen,
+ window,
+ op,
+ FALSE,
+ frame_action,
+ button, 0,
+ timestamp,
+ x_root,
+ y_root);
+ }
+ }
+
+ return TRUE;
+ }
+ else if (event->xclient.message_type ==
+ display->atom__NET_MOVERESIZE_WINDOW)
+ {
+ int gravity, source;
+ guint value_mask;
+
+ gravity = (event->xclient.data.l[0] & 0xff);
+ value_mask = (event->xclient.data.l[0] & 0xf00) >> 8;
+ source = (event->xclient.data.l[0] & 0xf000) >> 12;
+
+ if (gravity == 0)
+ gravity = window->size_hints.win_gravity;
+
+ meta_window_move_resize_request(window,
+ value_mask,
+ gravity,
+ event->xclient.data.l[1], /* x */
+ event->xclient.data.l[2], /* y */
+ event->xclient.data.l[3], /* width */
+ event->xclient.data.l[4]); /* height */
+ }
+ else if (event->xclient.message_type ==
+ display->atom__NET_ACTIVE_WINDOW)
+ {
+ MetaClientType source_indication;
+ guint32 timestamp;
+
+ meta_verbose ("_NET_ACTIVE_WINDOW request for window '%s', activating\n",
+ window->desc);
+
+ source_indication = event->xclient.data.l[0];
+ timestamp = event->xclient.data.l[1];
+
+ if (source_indication > META_CLIENT_TYPE_MAX_RECOGNIZED)
+ source_indication = META_CLIENT_TYPE_UNKNOWN;
+
+ if (timestamp == 0)
+ {
+ /* Client using older EWMH _NET_ACTIVE_WINDOW without a timestamp */
+ meta_warning ("Buggy client sent a _NET_ACTIVE_WINDOW message with a "
+ "timestamp of 0 for %s\n",
+ window->desc);
+ timestamp = meta_display_get_current_time (display);
+ }
+
+ window_activate (window, timestamp, source_indication, NULL);
+ return TRUE;
+ }
+ else if (event->xclient.message_type ==
+ display->atom__NET_WM_FULLSCREEN_MONITORS)
+ {
+ MetaClientType source_indication;
+ gulong top, bottom, left, right;
+
+ meta_verbose ("_NET_WM_FULLSCREEN_MONITORS request for window '%s'\n",
+ window->desc);
+
+ top = event->xclient.data.l[0];
+ bottom = event->xclient.data.l[1];
+ left = event->xclient.data.l[2];
+ right = event->xclient.data.l[3];
+ source_indication = event->xclient.data.l[4];
+
+ meta_window_update_fullscreen_monitors (window, top, bottom, left, right);
+ }
+
+ return FALSE;
+}
+
+gboolean
+meta_window_notify_focus (MetaWindow *window,
+ XEvent *event)
+{
+ /* note the event can be on either the window or the frame,
+ * we focus the frame for shaded windows
+ */
+
+ /* The event can be FocusIn, FocusOut, or UnmapNotify.
+ * On UnmapNotify we have to pretend it's focus out,
+ * because we won't get a focus out if it occurs, apparently.
+ */
+
+ /* We ignore grabs, though this is questionable.
+ * It may be better to increase the intelligence of
+ * the focus window tracking.
+ *
+ * The problem is that keybindings for windows are done with
+ * XGrabKey, which means focus_window disappears and the front of
+ * the MRU list gets confused from what the user expects once a
+ * keybinding is used.
+ */
+ meta_topic (META_DEBUG_FOCUS,
+ "Focus %s event received on %s 0x%lx (%s) "
+ "mode %s detail %s\n",
+ event->type == FocusIn ? "in" :
+ event->type == FocusOut ? "out" :
+ event->type == UnmapNotify ? "unmap" :
+ "???",
+ window->desc, event->xany.window,
+ event->xany.window == window->xwindow ?
+ "client window" :
+ (window->frame && event->xany.window == window->frame->xwindow) ?
+ "frame window" :
+ "unknown window",
+ event->type != UnmapNotify ?
+ meta_event_mode_to_string (event->xfocus.mode) : "n/a",
+ event->type != UnmapNotify ?
+ meta_event_detail_to_string (event->xfocus.detail) : "n/a");
+
+ /* FIXME our pointer tracking is broken; see how
+ * gtk+/gdk/x11/gdkevents-x11.c or XFree86/xc/programs/xterm/misc.c
+ * handle it for the correct way. In brief you need to track
+ * pointer focus and regular focus, and handle EnterNotify in
+ * PointerRoot mode with no window manager. However as noted above,
+ * accurate focus tracking will break things because we want to keep
+ * windows "focused" when using keybindings on them, and also we
+ * sometimes "focus" a window by focusing its frame or
+ * no_focus_window; so this all needs rethinking massively.
+ *
+ * My suggestion is to change it so that we clearly separate
+ * actual keyboard focus tracking using the xterm algorithm,
+ * and marco's "pretend" focus window, and go through all
+ * the code and decide which one should be used in each place;
+ * a hard bit is deciding on a policy for that.
+ *
+ * http://bugzilla.gnome.org/show_bug.cgi?id=90382
+ */
+
+ if ((event->type == FocusIn ||
+ event->type == FocusOut) &&
+ (event->xfocus.mode == NotifyGrab ||
+ event->xfocus.mode == NotifyUngrab ||
+ /* From WindowMaker, ignore all funky pointer root events */
+ event->xfocus.detail > NotifyNonlinearVirtual))
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Ignoring focus event generated by a grab or other weirdness\n");
+ return TRUE;
+ }
+
+ if (event->type == FocusIn)
+ {
+ if (window != window->display->focus_window)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "* Focus --> %s\n", window->desc);
+ window->display->focus_window = window;
+ window->has_focus = TRUE;
+ meta_compositor_set_active_window (window->display->compositor,
+ window->screen, window);
+
+ /* Move to the front of the focusing workspace's MRU list.
+ * We should only be "removing" it from the MRU list if it's
+ * not already there. Note that it's possible that we might
+ * be processing this FocusIn after we've changed to a
+ * different workspace; we should therefore update the MRU
+ * list only if the window is actually on the active
+ * workspace.
+ */
+ if (window->screen->active_workspace &&
+ meta_window_located_on_workspace (window,
+ window->screen->active_workspace))
+ {
+ GList* link;
+ link = g_list_find (window->screen->active_workspace->mru_list,
+ window);
+ g_assert (link);
+
+ window->screen->active_workspace->mru_list =
+ g_list_remove_link (window->screen->active_workspace->mru_list,
+ link);
+ g_list_free (link);
+
+ window->screen->active_workspace->mru_list =
+ g_list_prepend (window->screen->active_workspace->mru_list,
+ window);
+ }
+
+ if (window->frame)
+ meta_frame_queue_draw (window->frame);
+
+ meta_error_trap_push (window->display);
+ XInstallColormap (window->display->xdisplay,
+ window->colormap);
+ meta_error_trap_pop (window->display, FALSE);
+
+ /* move into FOCUSED_WINDOW layer */
+ meta_window_update_layer (window);
+
+ /* Ungrab click to focus button since the sync grab can interfere
+ * with some things you might do inside the focused window, by
+ * causing the client to get funky enter/leave events.
+ *
+ * The reason we usually have a passive grab on the window is
+ * so that we can intercept clicks and raise the window in
+ * response. For click-to-focus we don't need that since the
+ * focused window is already raised. When raise_on_click is
+ * FALSE we also don't need that since we don't do anything
+ * when the window is clicked.
+ *
+ * There is dicussion in bugs 102209, 115072, and 461577
+ */
+ if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK ||
+ !meta_prefs_get_raise_on_click())
+ meta_display_ungrab_focus_window_button (window->display, window);
+ }
+ }
+ else if (event->type == FocusOut ||
+ event->type == UnmapNotify)
+ {
+ if (event->type == FocusOut &&
+ event->xfocus.detail == NotifyInferior)
+ {
+ /* This event means the client moved focus to a subwindow */
+ meta_topic (META_DEBUG_FOCUS,
+ "Ignoring focus out on %s with NotifyInferior\n",
+ window->desc);
+ return TRUE;
+ }
+
+ if (window == window->display->focus_window)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "%s is now the previous focus window due to being focused out or unmapped\n",
+ window->desc);
+
+ meta_topic (META_DEBUG_FOCUS,
+ "* Focus --> NULL (was %s)\n", window->desc);
+
+ window->display->focus_window = NULL;
+ window->has_focus = FALSE;
+ if (window->frame)
+ meta_frame_queue_draw (window->frame);
+
+ meta_compositor_set_active_window (window->display->compositor,
+ window->screen, NULL);
+
+ meta_error_trap_push (window->display);
+ XUninstallColormap (window->display->xdisplay,
+ window->colormap);
+ meta_error_trap_pop (window->display, FALSE);
+
+ /* move out of FOCUSED_WINDOW layer */
+ meta_window_update_layer (window);
+
+ /* Re-grab for click to focus and raise-on-click, if necessary */
+ if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK ||
+ !meta_prefs_get_raise_on_click ())
+ meta_display_grab_focus_window_button (window->display, window);
+ }
+ }
+
+ /* Now set _NET_ACTIVE_WINDOW hint */
+ meta_display_update_active_window_hint (window->display);
+
+ return FALSE;
+}
+
+static gboolean
+process_property_notify (MetaWindow *window,
+ XPropertyEvent *event)
+{
+ Window xid = window->xwindow;
+
+ if (meta_is_verbose ()) /* avoid looking up the name if we don't have to */
+ {
+ char *property_name = XGetAtomName (window->display->xdisplay,
+ event->atom);
+
+ meta_verbose ("Property notify on %s for %s\n",
+ window->desc, property_name);
+ XFree (property_name);
+ }
+
+ if (event->atom == window->display->atom__NET_WM_USER_TIME &&
+ window->user_time_window)
+ {
+ xid = window->user_time_window;
+ }
+
+ meta_window_reload_property (window, event->atom, FALSE);
+
+ return TRUE;
+}
+
+static void
+send_configure_notify (MetaWindow *window)
+{
+ XEvent event;
+
+ /* from twm */
+
+ event.type = ConfigureNotify;
+ event.xconfigure.display = window->display->xdisplay;
+ event.xconfigure.event = window->xwindow;
+ event.xconfigure.window = window->xwindow;
+ event.xconfigure.x = window->rect.x - window->border_width;
+ event.xconfigure.y = window->rect.y - window->border_width;
+ if (window->frame)
+ {
+ if (window->withdrawn)
+ {
+ /* WARNING: x & y need to be set to whatever the XReparentWindow
+ * call in meta_window_destroy_frame will use so that the window
+ * has the right coordinates. Currently, that means no change to
+ * x & y.
+ */
+ }
+ else
+ {
+ /* Need to be in root window coordinates */
+ event.xconfigure.x += window->frame->rect.x;
+ event.xconfigure.y += window->frame->rect.y;
+ }
+ }
+ event.xconfigure.width = window->rect.width;
+ event.xconfigure.height = window->rect.height;
+ event.xconfigure.border_width = window->border_width; /* requested not actual */
+ event.xconfigure.above = None; /* FIXME */
+ event.xconfigure.override_redirect = False;
+
+ meta_topic (META_DEBUG_GEOMETRY,
+ "Sending synthetic configure notify to %s with x: %d y: %d w: %d h: %d\n",
+ window->desc,
+ event.xconfigure.x, event.xconfigure.y,
+ event.xconfigure.width, event.xconfigure.height);
+
+ meta_error_trap_push (window->display);
+ XSendEvent (window->display->xdisplay,
+ window->xwindow,
+ False, StructureNotifyMask, &event);
+ meta_error_trap_pop (window->display, FALSE);
+}
+
+gboolean
+meta_window_get_icon_geometry (MetaWindow *window,
+ MetaRectangle *rect)
+{
+ gulong *geometry = NULL;
+ int nitems;
+
+ if (meta_prop_get_cardinal_list (window->display,
+ window->xwindow,
+ window->display->atom__NET_WM_ICON_GEOMETRY,
+ &geometry, &nitems))
+ {
+ if (nitems != 4)
+ {
+ meta_verbose ("_NET_WM_ICON_GEOMETRY on %s has %d values instead of 4\n",
+ window->desc, nitems);
+ meta_XFree (geometry);
+ return FALSE;
+ }
+
+ if (rect)
+ {
+ rect->x = geometry[0];
+ rect->y = geometry[1];
+ rect->width = geometry[2];
+ rect->height = geometry[3];
+ }
+
+ meta_XFree (geometry);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static Window
+read_client_leader (MetaDisplay *display,
+ Window xwindow)
+{
+ Window retval = None;
+
+ meta_prop_get_window (display, xwindow,
+ display->atom_WM_CLIENT_LEADER,
+ &retval);
+
+ return retval;
+}
+
+typedef struct
+{
+ Window leader;
+} ClientLeaderData;
+
+static gboolean
+find_client_leader_func (MetaWindow *ancestor,
+ void *data)
+{
+ ClientLeaderData *d;
+
+ d = data;
+
+ d->leader = read_client_leader (ancestor->display,
+ ancestor->xwindow);
+
+ /* keep going if no client leader found */
+ return d->leader == None;
+}
+
+static void
+update_sm_hints (MetaWindow *window)
+{
+ Window leader;
+
+ window->xclient_leader = None;
+ window->sm_client_id = NULL;
+
+ /* If not on the current window, we can get the client
+ * leader from transient parents. If we find a client
+ * leader, we read the SM_CLIENT_ID from it.
+ */
+ leader = read_client_leader (window->display, window->xwindow);
+ if (leader == None)
+ {
+ ClientLeaderData d;
+ d.leader = None;
+ meta_window_foreach_ancestor (window, find_client_leader_func,
+ &d);
+ leader = d.leader;
+ }
+
+ if (leader != None)
+ {
+ char *str;
+
+ window->xclient_leader = leader;
+
+ if (meta_prop_get_latin1_string (window->display, leader,
+ window->display->atom_SM_CLIENT_ID,
+ &str))
+ {
+ window->sm_client_id = g_strdup (str);
+ meta_XFree (str);
+ }
+ }
+ else
+ {
+ meta_verbose ("Didn't find a client leader for %s\n", window->desc);
+
+ if (!meta_prefs_get_disable_workarounds ())
+ {
+ /* Some broken apps (kdelibs fault?) set SM_CLIENT_ID on the app
+ * instead of the client leader
+ */
+ char *str;
+
+ str = NULL;
+ if (meta_prop_get_latin1_string (window->display, window->xwindow,
+ window->display->atom_SM_CLIENT_ID,
+ &str))
+ {
+ if (window->sm_client_id == NULL) /* first time through */
+ meta_warning (_("Window %s sets SM_CLIENT_ID on itself, instead of on the WM_CLIENT_LEADER window as specified in the ICCCM.\n"),
+ window->desc);
+
+ window->sm_client_id = g_strdup (str);
+ meta_XFree (str);
+ }
+ }
+ }
+
+ meta_verbose ("Window %s client leader: 0x%lx SM_CLIENT_ID: '%s'\n",
+ window->desc, window->xclient_leader,
+ window->sm_client_id ? window->sm_client_id : "none");
+}
+
+void
+meta_window_update_role (MetaWindow *window)
+{
+ char *str;
+
+ if (window->role)
+ g_free (window->role);
+ window->role = NULL;
+
+ if (meta_prop_get_latin1_string (window->display, window->xwindow,
+ window->display->atom_WM_WINDOW_ROLE,
+ &str))
+ {
+ window->role = g_strdup (str);
+ meta_XFree (str);
+ }
+
+ meta_verbose ("Updated role of %s to '%s'\n",
+ window->desc, window->role ? window->role : "null");
+}
+
+void
+meta_window_update_net_wm_type (MetaWindow *window)
+{
+ int n_atoms;
+ Atom *atoms;
+ int i;
+
+ window->type_atom = None;
+ n_atoms = 0;
+ atoms = NULL;
+
+ meta_prop_get_atom_list (window->display, window->xwindow,
+ window->display->atom__NET_WM_WINDOW_TYPE,
+ &atoms, &n_atoms);
+
+ i = 0;
+ while (i < n_atoms)
+ {
+ /* We break as soon as we find one we recognize,
+ * supposed to prefer those near the front of the list
+ */
+ if (atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_DESKTOP ||
+ atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_DOCK ||
+ atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_TOOLBAR ||
+ atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_MENU ||
+ atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_DIALOG ||
+ atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_NORMAL ||
+ atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_UTILITY ||
+ atoms[i] == window->display->atom__NET_WM_WINDOW_TYPE_SPLASH)
+ {
+ window->type_atom = atoms[i];
+ break;
+ }
+
+ ++i;
+ }
+
+ meta_XFree (atoms);
+
+ if (meta_is_verbose ())
+ {
+ char *str;
+
+ str = NULL;
+ if (window->type_atom != None)
+ {
+ meta_error_trap_push (window->display);
+ str = XGetAtomName (window->display->xdisplay, window->type_atom);
+ meta_error_trap_pop (window->display, TRUE);
+ }
+
+ meta_verbose ("Window %s type atom %s\n", window->desc,
+ str ? str : "(none)");
+
+ if (str)
+ meta_XFree (str);
+ }
+
+ meta_window_recalc_window_type (window);
+}
+
+static void
+redraw_icon (MetaWindow *window)
+{
+ /* We could probably be smart and just redraw the icon here,
+ * instead of the whole frame.
+ */
+ if (window->frame && (window->mapped || window->frame->mapped))
+ meta_ui_queue_frame_draw (window->screen->ui, window->frame->xwindow);
+}
+
+void
+meta_window_update_icon_now (MetaWindow *window)
+{
+ GdkPixbuf *icon;
+ GdkPixbuf *mini_icon;
+
+ icon = NULL;
+ mini_icon = NULL;
+
+ if (meta_read_icons (window->screen,
+ window->xwindow,
+ &window->icon_cache,
+ window->wm_hints_pixmap,
+ window->wm_hints_mask,
+ &icon,
+ META_ICON_WIDTH, META_ICON_HEIGHT,
+ &mini_icon,
+ META_MINI_ICON_WIDTH,
+ META_MINI_ICON_HEIGHT))
+ {
+ if (window->icon)
+ g_object_unref (G_OBJECT (window->icon));
+
+ if (window->mini_icon)
+ g_object_unref (G_OBJECT (window->mini_icon));
+
+ window->icon = icon;
+ window->mini_icon = mini_icon;
+
+ redraw_icon (window);
+ }
+
+ g_assert (window->icon);
+ g_assert (window->mini_icon);
+}
+
+static gboolean
+idle_update_icon (gpointer data)
+{
+ GSList *tmp;
+ GSList *copy;
+ guint queue_index = GPOINTER_TO_INT (data);
+
+ meta_topic (META_DEBUG_GEOMETRY, "Clearing the update_icon queue\n");
+
+ /* Work with a copy, for reentrancy. The allowed reentrancy isn't
+ * complete; destroying a window while we're in here would result in
+ * badness. But it's OK to queue/unqueue update_icons.
+ */
+ copy = g_slist_copy (queue_pending[queue_index]);
+ g_slist_free (queue_pending[queue_index]);
+ queue_pending[queue_index] = NULL;
+ queue_idle[queue_index] = 0;
+
+ destroying_windows_disallowed += 1;
+
+ tmp = copy;
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+
+ window = tmp->data;
+
+ meta_window_update_icon_now (window);
+ window->is_in_queues &= ~META_QUEUE_UPDATE_ICON;
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (copy);
+
+ destroying_windows_disallowed -= 1;
+
+ return FALSE;
+}
+
+GList*
+meta_window_get_workspaces (MetaWindow *window)
+{
+ if (window->on_all_workspaces)
+ return window->screen->workspaces;
+ else
+ return window->workspace->list_containing_self;
+}
+
+static void
+invalidate_work_areas (MetaWindow *window)
+{
+ GList *tmp;
+
+ tmp = meta_window_get_workspaces (window);
+
+ while (tmp != NULL)
+ {
+ meta_workspace_invalidate_work_area (tmp->data);
+ tmp = tmp->next;
+ }
+}
+
+void
+meta_window_update_struts (MetaWindow *window)
+{
+ GSList *old_struts;
+ GSList *new_struts;
+ GSList *old_iter, *new_iter;
+ gulong *struts = NULL;
+ int nitems;
+ gboolean changed;
+
+ meta_verbose ("Updating struts for %s\n", window->desc);
+
+ old_struts = window->struts;
+ new_struts = NULL;
+
+ if (meta_prop_get_cardinal_list (window->display,
+ window->xwindow,
+ window->display->atom__NET_WM_STRUT_PARTIAL,
+ &struts, &nitems))
+ {
+ if (nitems != 12)
+ meta_verbose ("_NET_WM_STRUT_PARTIAL on %s has %d values instead "
+ "of 12\n",
+ window->desc, nitems);
+ else
+ {
+ /* Pull out the strut info for each side in the hint */
+ int i;
+ for (i=0; i<4; i++)
+ {
+ MetaStrut *temp;
+ int thickness, strut_begin, strut_end;
+
+ thickness = struts[i];
+ if (thickness == 0)
+ continue;
+ strut_begin = struts[4+(i*2)];
+ strut_end = struts[4+(i*2)+1];
+
+ temp = g_new (MetaStrut, 1);
+ temp->side = 1 << i; /* See MetaSide def. Matches nicely, eh? */
+ temp->rect = window->screen->rect;
+ switch (temp->side)
+ {
+ case META_SIDE_RIGHT:
+ temp->rect.x = BOX_RIGHT(temp->rect) - thickness;
+ /* Intentionally fall through without breaking */
+ case META_SIDE_LEFT:
+ temp->rect.width = thickness;
+ temp->rect.y = strut_begin;
+ temp->rect.height = strut_end - strut_begin + 1;
+ break;
+ case META_SIDE_BOTTOM:
+ temp->rect.y = BOX_BOTTOM(temp->rect) - thickness;
+ /* Intentionally fall through without breaking */
+ case META_SIDE_TOP:
+ temp->rect.height = thickness;
+ temp->rect.x = strut_begin;
+ temp->rect.width = strut_end - strut_begin + 1;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ new_struts = g_slist_prepend (new_struts, temp);
+ }
+
+ meta_verbose ("_NET_WM_STRUT_PARTIAL struts %lu %lu %lu %lu for "
+ "window %s\n",
+ struts[0], struts[1], struts[2], struts[3],
+ window->desc);
+ }
+ meta_XFree (struts);
+ }
+ else
+ {
+ meta_verbose ("No _NET_WM_STRUT property for %s\n",
+ window->desc);
+ }
+
+ if (!new_struts &&
+ meta_prop_get_cardinal_list (window->display,
+ window->xwindow,
+ window->display->atom__NET_WM_STRUT,
+ &struts, &nitems))
+ {
+ if (nitems != 4)
+ meta_verbose ("_NET_WM_STRUT on %s has %d values instead of 4\n",
+ window->desc, nitems);
+ else
+ {
+ /* Pull out the strut info for each side in the hint */
+ int i;
+ for (i=0; i<4; i++)
+ {
+ MetaStrut *temp;
+ int thickness;
+
+ thickness = struts[i];
+ if (thickness == 0)
+ continue;
+
+ temp = g_new (MetaStrut, 1);
+ temp->side = 1 << i;
+ temp->rect = window->screen->rect;
+ switch (temp->side)
+ {
+ case META_SIDE_RIGHT:
+ temp->rect.x = BOX_RIGHT(temp->rect) - thickness;
+ /* Intentionally fall through without breaking */
+ case META_SIDE_LEFT:
+ temp->rect.width = thickness;
+ break;
+ case META_SIDE_BOTTOM:
+ temp->rect.y = BOX_BOTTOM(temp->rect) - thickness;
+ /* Intentionally fall through without breaking */
+ case META_SIDE_TOP:
+ temp->rect.height = thickness;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ new_struts = g_slist_prepend (new_struts, temp);
+ }
+
+ meta_verbose ("_NET_WM_STRUT struts %lu %lu %lu %lu for window %s\n",
+ struts[0], struts[1], struts[2], struts[3],
+ window->desc);
+ }
+ meta_XFree (struts);
+ }
+ else if (!new_struts)
+ {
+ meta_verbose ("No _NET_WM_STRUT property for %s\n",
+ window->desc);
+ }
+
+ /* Determine whether old_struts and new_struts are the same */
+ old_iter = old_struts;
+ new_iter = new_struts;
+ while (old_iter && new_iter)
+ {
+ MetaStrut *old_strut = (MetaStrut*) old_iter->data;
+ MetaStrut *new_strut = (MetaStrut*) new_iter->data;
+
+ if (old_strut->side != new_strut->side ||
+ !meta_rectangle_equal (&old_strut->rect, &new_strut->rect))
+ break;
+
+ old_iter = old_iter->next;
+ new_iter = new_iter->next;
+ }
+ changed = (old_iter != NULL || new_iter != NULL);
+
+ /* Update appropriately */
+ meta_free_gslist_and_elements (old_struts);
+ window->struts = new_struts;
+ if (changed)
+ {
+ meta_topic (META_DEBUG_WORKAREA,
+ "Invalidating work areas of window %s due to struts update\n",
+ window->desc);
+ invalidate_work_areas (window);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_WORKAREA,
+ "Struts on %s were unchanged\n", window->desc);
+ }
+}
+
+void
+meta_window_recalc_window_type (MetaWindow *window)
+{
+ recalc_window_type (window);
+}
+
+static void
+recalc_window_type (MetaWindow *window)
+{
+ MetaWindowType old_type;
+
+ old_type = window->type;
+
+ if (window->type_atom != None)
+ {
+ if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_DESKTOP)
+ window->type = META_WINDOW_DESKTOP;
+ else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_DOCK)
+ window->type = META_WINDOW_DOCK;
+ else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_TOOLBAR)
+ window->type = META_WINDOW_TOOLBAR;
+ else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_MENU)
+ window->type = META_WINDOW_MENU;
+ else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_DIALOG)
+ window->type = META_WINDOW_DIALOG;
+ else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_NORMAL)
+ window->type = META_WINDOW_NORMAL;
+ else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_UTILITY)
+ window->type = META_WINDOW_UTILITY;
+ else if (window->type_atom == window->display->atom__NET_WM_WINDOW_TYPE_SPLASH)
+ window->type = META_WINDOW_SPLASHSCREEN;
+ else
+ meta_bug ("Set a type atom for %s that wasn't handled in recalc_window_type\n",
+ window->desc);
+ }
+ else if (window->xtransient_for != None)
+ {
+ window->type = META_WINDOW_DIALOG;
+ }
+ else
+ {
+ window->type = META_WINDOW_NORMAL;
+ }
+
+ if (window->type == META_WINDOW_DIALOG &&
+ window->wm_state_modal)
+ window->type = META_WINDOW_MODAL_DIALOG;
+
+ meta_verbose ("Calculated type %u for %s, old type %u\n",
+ window->type, window->desc, old_type);
+
+ if (old_type != window->type)
+ {
+ recalc_window_features (window);
+
+ set_net_wm_state (window);
+
+ /* Update frame */
+ if (window->decorated)
+ meta_window_ensure_frame (window);
+ else
+ meta_window_destroy_frame (window);
+
+ /* update stacking constraints */
+ meta_window_update_layer (window);
+
+ meta_window_grab_keys (window);
+ }
+}
+
+static void
+set_allowed_actions_hint (MetaWindow *window)
+{
+#define MAX_N_ACTIONS 12
+ unsigned long data[MAX_N_ACTIONS];
+ int i;
+
+ i = 0;
+ if (window->has_move_func)
+ {
+ data[i] = window->display->atom__NET_WM_ACTION_MOVE;
+ ++i;
+ }
+ if (window->has_resize_func)
+ {
+ data[i] = window->display->atom__NET_WM_ACTION_RESIZE;
+ ++i;
+ }
+ if (window->has_fullscreen_func)
+ {
+ data[i] = window->display->atom__NET_WM_ACTION_FULLSCREEN;
+ ++i;
+ }
+ if (window->has_minimize_func)
+ {
+ data[i] = window->display->atom__NET_WM_ACTION_MINIMIZE;
+ ++i;
+ }
+ if (window->has_shade_func)
+ {
+ data[i] = window->display->atom__NET_WM_ACTION_SHADE;
+ ++i;
+ }
+ /* sticky according to EWMH is different from marco's sticky;
+ * marco doesn't support EWMH sticky
+ */
+ if (window->has_maximize_func)
+ {
+ data[i] = window->display->atom__NET_WM_ACTION_MAXIMIZE_HORZ;
+ ++i;
+ data[i] = window->display->atom__NET_WM_ACTION_MAXIMIZE_VERT;
+ ++i;
+ }
+ /* We always allow this */
+ data[i] = window->display->atom__NET_WM_ACTION_CHANGE_DESKTOP;
+ ++i;
+ if (window->has_close_func)
+ {
+ data[i] = window->display->atom__NET_WM_ACTION_CLOSE;
+ ++i;
+ }
+
+ /* I guess we always allow above/below operations */
+ data[i] = window->display->atom__NET_WM_ACTION_ABOVE;
+ ++i;
+ data[i] = window->display->atom__NET_WM_ACTION_BELOW;
+ ++i;
+
+ g_assert (i <= MAX_N_ACTIONS);
+
+ meta_verbose ("Setting _NET_WM_ALLOWED_ACTIONS with %d atoms\n", i);
+
+ meta_error_trap_push (window->display);
+ XChangeProperty (window->display->xdisplay, window->xwindow,
+ window->display->atom__NET_WM_ALLOWED_ACTIONS,
+ XA_ATOM,
+ 32, PropModeReplace, (guchar*) data, i);
+ meta_error_trap_pop (window->display, FALSE);
+#undef MAX_N_ACTIONS
+}
+
+void
+meta_window_recalc_features (MetaWindow *window)
+{
+ recalc_window_features (window);
+}
+
+static void
+recalc_window_features (MetaWindow *window)
+{
+ gboolean old_has_close_func;
+ gboolean old_has_minimize_func;
+ gboolean old_has_move_func;
+ gboolean old_has_resize_func;
+ gboolean old_has_shade_func;
+ gboolean old_always_sticky;
+
+ old_has_close_func = window->has_close_func;
+ old_has_minimize_func = window->has_minimize_func;
+ old_has_move_func = window->has_move_func;
+ old_has_resize_func = window->has_resize_func;
+ old_has_shade_func = window->has_shade_func;
+ old_always_sticky = window->always_sticky;
+
+ /* Use MWM hints initially */
+ window->decorated = window->mwm_decorated;
+ window->border_only = window->mwm_border_only;
+ window->has_close_func = window->mwm_has_close_func;
+ window->has_minimize_func = window->mwm_has_minimize_func;
+ window->has_maximize_func = window->mwm_has_maximize_func;
+ window->has_move_func = window->mwm_has_move_func;
+
+ window->has_resize_func = TRUE;
+
+ /* If min_size == max_size, then don't allow resize */
+ if (window->size_hints.min_width == window->size_hints.max_width &&
+ window->size_hints.min_height == window->size_hints.max_height)
+ window->has_resize_func = FALSE;
+ else if (!window->mwm_has_resize_func)
+ {
+ /* We ignore mwm_has_resize_func because WM_NORMAL_HINTS is the
+ * authoritative source for that info. Some apps such as mplayer or
+ * xine disable resize via MWM but not WM_NORMAL_HINTS, but that
+ * leads to e.g. us not fullscreening their windows. Apps that set
+ * MWM but not WM_NORMAL_HINTS are basically broken. We complain
+ * about these apps but make them work.
+ */
+
+ meta_warning (_("Window %s sets an MWM hint indicating it isn't resizable, but sets min size %d x %d and max size %d x %d; this doesn't make much sense.\n"),
+ window->desc,
+ window->size_hints.min_width,
+ window->size_hints.min_height,
+ window->size_hints.max_width,
+ window->size_hints.max_height);
+ }
+
+ window->has_shade_func = TRUE;
+ window->has_fullscreen_func = TRUE;
+
+ window->always_sticky = FALSE;
+
+ /* Semantic category overrides the MWM hints */
+ if (window->type == META_WINDOW_TOOLBAR)
+ window->decorated = FALSE;
+
+ if (window->type == META_WINDOW_DESKTOP ||
+ window->type == META_WINDOW_DOCK)
+ window->always_sticky = TRUE;
+
+ if (window->type == META_WINDOW_DESKTOP ||
+ window->type == META_WINDOW_DOCK ||
+ window->type == META_WINDOW_SPLASHSCREEN)
+ {
+ window->decorated = FALSE;
+ window->has_close_func = FALSE;
+ window->has_shade_func = FALSE;
+
+ /* FIXME this keeps panels and things from using
+ * NET_WM_MOVERESIZE; the problem is that some
+ * panels (edge panels) have fixed possible locations,
+ * and others ("floating panels") do not.
+ *
+ * Perhaps we should require edge panels to explicitly
+ * disable movement?
+ */
+ window->has_move_func = FALSE;
+ window->has_resize_func = FALSE;
+ }
+
+ if (window->type != META_WINDOW_NORMAL)
+ {
+ window->has_minimize_func = FALSE;
+ window->has_maximize_func = FALSE;
+ window->has_fullscreen_func = FALSE;
+ }
+
+ if (!window->has_resize_func)
+ {
+ window->has_maximize_func = FALSE;
+
+ /* don't allow fullscreen if we can't resize, unless the size
+ * is entire screen size (kind of broken, because we
+ * actually fullscreen to xinerama head size not screen size)
+ */
+ if (window->size_hints.min_width == window->screen->rect.width &&
+ window->size_hints.min_height == window->screen->rect.height)
+ ; /* leave fullscreen available */
+ else
+ window->has_fullscreen_func = FALSE;
+ }
+
+ /* We leave fullscreen windows decorated, just push the frame outside
+ * the screen. This avoids flickering to unparent them.
+ *
+ * Note that setting has_resize_func = FALSE here must come after the
+ * above code that may disable fullscreen, because if the window
+ * is not resizable purely due to fullscreen, we don't want to
+ * disable fullscreen mode.
+ */
+ if (window->fullscreen)
+ {
+ window->has_shade_func = FALSE;
+ window->has_move_func = FALSE;
+ window->has_resize_func = FALSE;
+ window->has_maximize_func = FALSE;
+ }
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Window %s fullscreen = %d not resizable, maximizable = %d fullscreenable = %d min size %dx%d max size %dx%d\n",
+ window->desc,
+ window->fullscreen,
+ window->has_maximize_func, window->has_fullscreen_func,
+ window->size_hints.min_width,
+ window->size_hints.min_height,
+ window->size_hints.max_width,
+ window->size_hints.max_height);
+
+ /* no shading if not decorated */
+ if (!window->decorated || window->border_only)
+ window->has_shade_func = FALSE;
+
+ window->skip_taskbar = FALSE;
+ window->skip_pager = FALSE;
+
+ if (window->wm_state_skip_taskbar)
+ window->skip_taskbar = TRUE;
+
+ if (window->wm_state_skip_pager)
+ window->skip_pager = TRUE;
+
+ switch (window->type)
+ {
+ /* Force skip taskbar/pager on these window types */
+ case META_WINDOW_DESKTOP:
+ case META_WINDOW_DOCK:
+ case META_WINDOW_TOOLBAR:
+ case META_WINDOW_MENU:
+ case META_WINDOW_UTILITY:
+ case META_WINDOW_SPLASHSCREEN:
+ window->skip_taskbar = TRUE;
+ window->skip_pager = TRUE;
+ break;
+
+ case META_WINDOW_DIALOG:
+ case META_WINDOW_MODAL_DIALOG:
+ /* only skip taskbar if we have a real transient parent */
+ if (window->xtransient_for != None &&
+ window->xtransient_for != window->screen->xroot)
+ window->skip_taskbar = TRUE;
+ break;
+
+ case META_WINDOW_NORMAL:
+ break;
+ }
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Window %s decorated = %d border_only = %d has_close = %d has_minimize = %d has_maximize = %d has_move = %d has_shade = %d skip_taskbar = %d skip_pager = %d\n",
+ window->desc,
+ window->decorated,
+ window->border_only,
+ window->has_close_func,
+ window->has_minimize_func,
+ window->has_maximize_func,
+ window->has_move_func,
+ window->has_shade_func,
+ window->skip_taskbar,
+ window->skip_pager);
+
+ /* FIXME:
+ * Lame workaround for recalc_window_features
+ * being used overzealously. The fix is to
+ * only recalc_window_features when something
+ * has actually changed.
+ */
+ if (window->constructing ||
+ old_has_close_func != window->has_close_func ||
+ old_has_minimize_func != window->has_minimize_func ||
+ old_has_move_func != window->has_move_func ||
+ old_has_resize_func != window->has_resize_func ||
+ old_has_shade_func != window->has_shade_func ||
+ old_always_sticky != window->always_sticky)
+ set_allowed_actions_hint (window);
+
+ /* FIXME perhaps should ensure if we don't have a shade func,
+ * we aren't shaded, etc.
+ */
+}
+
+static void
+menu_callback (MetaWindowMenu *menu,
+ Display *xdisplay,
+ Window client_xwindow,
+ guint32 timestamp,
+ MetaMenuOp op,
+ int workspace_index,
+ gpointer data)
+{
+ MetaDisplay *display;
+ MetaWindow *window;
+ MetaWorkspace *workspace;
+
+ display = meta_display_for_x_display (xdisplay);
+ window = meta_display_lookup_x_window (display, client_xwindow);
+ workspace = NULL;
+
+ if (window != NULL) /* window can be NULL */
+ {
+ meta_verbose ("Menu op %u on %s\n", op, window->desc);
+
+ switch (op)
+ {
+ case META_MENU_OP_NONE:
+ /* nothing */
+ break;
+
+ case META_MENU_OP_DELETE:
+ meta_window_delete (window, timestamp);
+ break;
+
+ case META_MENU_OP_MINIMIZE:
+ meta_window_minimize (window);
+ break;
+
+ case META_MENU_OP_UNMAXIMIZE:
+ meta_window_unmaximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+ break;
+
+ case META_MENU_OP_MAXIMIZE:
+ meta_window_maximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+ break;
+
+ case META_MENU_OP_UNSHADE:
+ meta_window_unshade (window, timestamp);
+ break;
+
+ case META_MENU_OP_SHADE:
+ meta_window_shade (window, timestamp);
+ break;
+
+ case META_MENU_OP_MOVE_LEFT:
+ workspace = meta_workspace_get_neighbor (window->screen->active_workspace,
+ META_MOTION_LEFT);
+ break;
+
+ case META_MENU_OP_MOVE_RIGHT:
+ workspace = meta_workspace_get_neighbor (window->screen->active_workspace,
+ META_MOTION_RIGHT);
+ break;
+
+ case META_MENU_OP_MOVE_UP:
+ workspace = meta_workspace_get_neighbor (window->screen->active_workspace,
+ META_MOTION_UP);
+ break;
+
+ case META_MENU_OP_MOVE_DOWN:
+ workspace = meta_workspace_get_neighbor (window->screen->active_workspace,
+ META_MOTION_DOWN);
+ break;
+
+ case META_MENU_OP_WORKSPACES:
+ workspace = meta_screen_get_workspace_by_index (window->screen,
+ workspace_index);
+ break;
+
+ case META_MENU_OP_STICK:
+ meta_window_stick (window);
+ break;
+
+ case META_MENU_OP_UNSTICK:
+ meta_window_unstick (window);
+ break;
+
+ case META_MENU_OP_ABOVE:
+ case META_MENU_OP_UNABOVE:
+ if (window->wm_state_above == FALSE)
+ meta_window_make_above (window);
+ else
+ meta_window_unmake_above (window);
+ break;
+
+ case META_MENU_OP_MOVE:
+ meta_window_begin_grab_op (window,
+ META_GRAB_OP_KEYBOARD_MOVING,
+ TRUE,
+ timestamp);
+ break;
+
+ case META_MENU_OP_RESIZE:
+ meta_window_begin_grab_op (window,
+ META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN,
+ TRUE,
+ timestamp);
+ break;
+
+ case META_MENU_OP_RECOVER:
+ meta_window_shove_titlebar_onscreen (window);
+ break;
+
+ default:
+ meta_warning (G_STRLOC": Unknown window op\n");
+ break;
+ }
+
+ if (workspace)
+ {
+ meta_window_change_workspace (window,
+ workspace);
+#if 0
+ meta_workspace_activate (workspace);
+ meta_window_raise (window);
+#endif
+ }
+ }
+ else
+ {
+ meta_verbose ("Menu callback on nonexistent window\n");
+ }
+
+ if (display->window_menu == menu)
+ {
+ display->window_menu = NULL;
+ display->window_with_menu = NULL;
+ }
+
+ meta_ui_window_menu_free (menu);
+}
+
+void
+meta_window_show_menu (MetaWindow *window,
+ int root_x,
+ int root_y,
+ int button,
+ guint32 timestamp)
+{
+ MetaMenuOp ops;
+ MetaMenuOp insensitive;
+ MetaWindowMenu *menu;
+ MetaWorkspaceLayout layout;
+ int n_workspaces;
+ gboolean ltr;
+
+ if (window->display->window_menu)
+ {
+ meta_ui_window_menu_free (window->display->window_menu);
+ window->display->window_menu = NULL;
+ window->display->window_with_menu = NULL;
+ }
+
+ ops = META_MENU_OP_NONE;
+ insensitive = META_MENU_OP_NONE;
+
+ ops |= (META_MENU_OP_DELETE | META_MENU_OP_MINIMIZE | META_MENU_OP_MOVE | META_MENU_OP_RESIZE);
+
+ if (!meta_window_titlebar_is_onscreen (window) &&
+ window->type != META_WINDOW_DOCK &&
+ window->type != META_WINDOW_DESKTOP)
+ ops |= META_MENU_OP_RECOVER;
+
+ n_workspaces = meta_screen_get_n_workspaces (window->screen);
+
+ if (n_workspaces > 1)
+ ops |= META_MENU_OP_WORKSPACES;
+
+ meta_screen_calc_workspace_layout (window->screen,
+ n_workspaces,
+ meta_workspace_index ( window->screen->active_workspace),
+ &layout);
+
+ if (!window->on_all_workspaces)
+ {
+ ltr = meta_ui_get_direction() == META_UI_DIRECTION_LTR;
+
+ if (layout.current_col > 0)
+ ops |= ltr ? META_MENU_OP_MOVE_LEFT : META_MENU_OP_MOVE_RIGHT;
+ if ((layout.current_col < layout.cols - 1) &&
+ (layout.current_row * layout.cols + (layout.current_col + 1) < n_workspaces))
+ ops |= ltr ? META_MENU_OP_MOVE_RIGHT : META_MENU_OP_MOVE_LEFT;
+ if (layout.current_row > 0)
+ ops |= META_MENU_OP_MOVE_UP;
+ if ((layout.current_row < layout.rows - 1) &&
+ ((layout.current_row + 1) * layout.cols + layout.current_col < n_workspaces))
+ ops |= META_MENU_OP_MOVE_DOWN;
+ }
+
+ meta_screen_free_workspace_layout (&layout);
+
+ if (META_WINDOW_MAXIMIZED (window))
+ ops |= META_MENU_OP_UNMAXIMIZE;
+ else
+ ops |= META_MENU_OP_MAXIMIZE;
+
+#if 0
+ if (window->shaded)
+ ops |= META_MENU_OP_UNSHADE;
+ else
+ ops |= META_MENU_OP_SHADE;
+#endif
+
+ ops |= META_MENU_OP_UNSTICK;
+ ops |= META_MENU_OP_STICK;
+
+ if (window->wm_state_above)
+ ops |= META_MENU_OP_UNABOVE;
+ else
+ ops |= META_MENU_OP_ABOVE;
+
+ if (!window->has_maximize_func)
+ insensitive |= META_MENU_OP_UNMAXIMIZE | META_MENU_OP_MAXIMIZE;
+
+ /*if (!window->has_minimize_func)
+ insensitive |= META_MENU_OP_MINIMIZE;*/
+
+ /*if (!window->has_close_func)
+ insensitive |= META_MENU_OP_DELETE;*/
+
+ if (!window->has_shade_func)
+ insensitive |= META_MENU_OP_SHADE | META_MENU_OP_UNSHADE;
+
+ if (!META_WINDOW_ALLOWS_MOVE (window))
+ insensitive |= META_MENU_OP_MOVE;
+
+ if (!META_WINDOW_ALLOWS_RESIZE (window))
+ insensitive |= META_MENU_OP_RESIZE;
+
+ if (window->always_sticky)
+ insensitive |= META_MENU_OP_STICK | META_MENU_OP_UNSTICK | META_MENU_OP_WORKSPACES;
+
+ if ((window->type == META_WINDOW_DESKTOP) ||
+ (window->type == META_WINDOW_DOCK) ||
+ (window->type == META_WINDOW_SPLASHSCREEN))
+ insensitive |= META_MENU_OP_ABOVE | META_MENU_OP_UNABOVE;
+
+ /* If all operations are disabled, just quit without showing the menu.
+ * This is the case, for example, with META_WINDOW_DESKTOP windows.
+ */
+ if ((ops & ~insensitive) == 0)
+ return;
+
+ menu =
+ meta_ui_window_menu_new (window->screen->ui,
+ window->xwindow,
+ ops,
+ insensitive,
+ meta_window_get_net_wm_desktop (window),
+ meta_screen_get_n_workspaces (window->screen),
+ menu_callback,
+ NULL);
+
+ window->display->window_menu = menu;
+ window->display->window_with_menu = window;
+
+ meta_verbose ("Popping up window menu for %s\n", window->desc);
+
+ meta_ui_window_menu_popup (menu, root_x, root_y, button, timestamp);
+}
+
+void
+meta_window_shove_titlebar_onscreen (MetaWindow *window)
+{
+ MetaRectangle outer_rect;
+ GList *onscreen_region;
+ int horiz_amount, vert_amount;
+ int newx, newy;
+
+ /* If there's no titlebar, don't bother */
+ if (!window->frame)
+ return;
+
+ /* Get the basic info we need */
+ meta_window_get_outer_rect (window, &outer_rect);
+ onscreen_region = window->screen->active_workspace->screen_region;
+
+ /* Extend the region (just in case the window is too big to fit on the
+ * screen), then shove the window on screen, then return the region to
+ * normal.
+ */
+ horiz_amount = outer_rect.width;
+ vert_amount = outer_rect.height;
+ meta_rectangle_expand_region (onscreen_region,
+ horiz_amount,
+ horiz_amount,
+ 0,
+ vert_amount);
+ meta_rectangle_shove_into_region(onscreen_region,
+ FIXED_DIRECTION_X,
+ &outer_rect);
+ meta_rectangle_expand_region (onscreen_region,
+ -horiz_amount,
+ -horiz_amount,
+ 0,
+ -vert_amount);
+
+ newx = outer_rect.x + window->frame->child_x;
+ newy = outer_rect.y + window->frame->child_y;
+ meta_window_move_resize (window,
+ FALSE,
+ newx,
+ newy,
+ window->rect.width,
+ window->rect.height);
+}
+
+gboolean
+meta_window_titlebar_is_onscreen (MetaWindow *window)
+{
+ MetaRectangle titlebar_rect;
+ GList *onscreen_region;
+ int titlebar_size;
+ gboolean is_onscreen;
+
+ const int min_height_needed = 8;
+ const int min_width_percent = 0.5;
+ const int min_width_absolute = 50;
+
+ /* Titlebar can't be offscreen if there is no titlebar... */
+ if (!window->frame)
+ return FALSE;
+
+ /* Get the rectangle corresponding to the titlebar */
+ meta_window_get_outer_rect (window, &titlebar_rect);
+ titlebar_rect.height = window->frame->child_y;
+ titlebar_size = meta_rectangle_area (&titlebar_rect);
+
+ /* Run through the spanning rectangles for the screen and see if one of
+ * them overlaps with the titlebar sufficiently to consider it onscreen.
+ */
+ is_onscreen = FALSE;
+ onscreen_region = window->screen->active_workspace->screen_region;
+ while (onscreen_region)
+ {
+ MetaRectangle *spanning_rect = onscreen_region->data;
+ MetaRectangle overlap;
+
+ meta_rectangle_intersect (&titlebar_rect, spanning_rect, &overlap);
+ if (overlap.height > MIN (titlebar_rect.height, min_height_needed) &&
+ overlap.width > MIN (titlebar_rect.width * min_width_percent,
+ min_width_absolute))
+ {
+ is_onscreen = TRUE;
+ break;
+ }
+
+ onscreen_region = onscreen_region->next;
+ }
+
+ return is_onscreen;
+}
+
+static double
+timeval_to_ms (const GTimeVal *timeval)
+{
+ return (timeval->tv_sec * G_USEC_PER_SEC + timeval->tv_usec) / 1000.0;
+}
+
+static double
+time_diff (const GTimeVal *first,
+ const GTimeVal *second)
+{
+ double first_ms = timeval_to_ms (first);
+ double second_ms = timeval_to_ms (second);
+
+ return first_ms - second_ms;
+}
+
+static gboolean
+check_moveresize_frequency (MetaWindow *window,
+ gdouble *remaining)
+{
+ GTimeVal current_time;
+
+ g_get_current_time (&current_time);
+
+#ifdef HAVE_XSYNC
+ if (!window->disable_sync &&
+ window->display->grab_sync_request_alarm != None)
+ {
+ if (window->sync_request_time.tv_sec != 0 ||
+ window->sync_request_time.tv_usec != 0)
+ {
+ double elapsed =
+ time_diff (&current_time, &window->sync_request_time);
+
+ if (elapsed < 1000.0)
+ {
+ /* We want to be sure that the timeout happens at
+ * a time where elapsed will definitely be
+ * greater than 1000, so we can disable sync
+ */
+ if (remaining)
+ *remaining = 1000.0 - elapsed + 100;
+
+ return FALSE;
+ }
+ else
+ {
+ /* We have now waited for more than a second for the
+ * application to respond to the sync request
+ */
+ window->disable_sync = TRUE;
+ return TRUE;
+ }
+ }
+ else
+ {
+ /* No outstanding sync requests. Go ahead and resize
+ */
+ return TRUE;
+ }
+ }
+ else
+#endif /* HAVE_XSYNC */
+ {
+ const double max_resizes_per_second = 25.0;
+ const double ms_between_resizes = 1000.0 / max_resizes_per_second;
+ double elapsed;
+
+ elapsed = time_diff (&current_time, &window->display->grab_last_moveresize_time);
+
+ if (elapsed >= 0.0 && elapsed < ms_between_resizes)
+ {
+ meta_topic (META_DEBUG_RESIZING,
+ "Delaying move/resize as only %g of %g ms elapsed\n",
+ elapsed, ms_between_resizes);
+
+ if (remaining)
+ *remaining = (ms_between_resizes - elapsed);
+
+ return FALSE;
+ }
+
+ meta_topic (META_DEBUG_RESIZING,
+ " Checked moveresize freq, allowing move/resize now (%g of %g seconds elapsed)\n",
+ elapsed / 1000.0, 1.0 / max_resizes_per_second);
+
+ return TRUE;
+ }
+}
+
+static gboolean
+update_move_timeout (gpointer data)
+{
+ MetaWindow *window = data;
+
+ update_move (window,
+ window->display->grab_last_user_action_was_snap,
+ window->display->grab_latest_motion_x,
+ window->display->grab_latest_motion_y);
+
+ return FALSE;
+}
+
+static void
+update_move (MetaWindow *window,
+ gboolean snap,
+ int x,
+ int y)
+{
+ int dx, dy;
+ int new_x, new_y;
+ MetaRectangle old;
+ int shake_threshold;
+ MetaDisplay *display = window->display;
+
+ display->grab_latest_motion_x = x;
+ display->grab_latest_motion_y = y;
+
+ dx = x - display->grab_anchor_root_x;
+ dy = y - display->grab_anchor_root_y;
+
+ new_x = display->grab_anchor_window_pos.x + dx;
+ new_y = display->grab_anchor_window_pos.y + dy;
+
+ meta_verbose ("x,y = %d,%d anchor ptr %d,%d anchor pos %d,%d dx,dy %d,%d\n",
+ x, y,
+ display->grab_anchor_root_x,
+ display->grab_anchor_root_y,
+ display->grab_anchor_window_pos.x,
+ display->grab_anchor_window_pos.y,
+ dx, dy);
+
+ /* Don't bother doing anything if no move has been specified. (This
+ * happens often, even in keyboard moving, due to the warping of the
+ * pointer.
+ */
+ if (dx == 0 && dy == 0)
+ return;
+
+ /* shake loose (unmaximize) maximized window if dragged beyond the threshold
+ * in the Y direction. You can't pull a window loose via X motion.
+ */
+
+#define DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR 6
+ shake_threshold = meta_ui_get_drag_threshold (window->screen->ui) *
+ DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR;
+
+ if (META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold)
+ {
+ double prop;
+
+ /* Shake loose */
+ window->shaken_loose = TRUE;
+
+ /* move the unmaximized window to the cursor */
+ prop =
+ ((double)(x - display->grab_initial_window_pos.x)) /
+ ((double)display->grab_initial_window_pos.width);
+
+ display->grab_initial_window_pos.x =
+ x - window->saved_rect.width * prop;
+ display->grab_initial_window_pos.y = y;
+
+ if (window->frame)
+ {
+ display->grab_initial_window_pos.y += window->frame->child_y / 2;
+ }
+
+ window->saved_rect.x = display->grab_initial_window_pos.x;
+ window->saved_rect.y = display->grab_initial_window_pos.y;
+ display->grab_anchor_root_x = x;
+ display->grab_anchor_root_y = y;
+
+ meta_window_unmaximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+
+ return;
+ }
+ /* remaximize window on an other xinerama monitor if window has
+ * been shaken loose or it is still maximized (then move straight)
+ */
+ else if (window->shaken_loose || META_WINDOW_MAXIMIZED (window))
+ {
+ const MetaXineramaScreenInfo *wxinerama;
+ MetaRectangle work_area;
+ int monitor;
+
+ wxinerama = meta_screen_get_xinerama_for_window (window->screen, window);
+
+ for (monitor = 0; monitor < window->screen->n_xinerama_infos; monitor++)
+ {
+ meta_window_get_work_area_for_xinerama (window, monitor, &work_area);
+
+ /* check if cursor is near the top of a xinerama work area */
+ if (x >= work_area.x &&
+ x < (work_area.x + work_area.width) &&
+ y >= work_area.y &&
+ y < (work_area.y + shake_threshold))
+ {
+ /* move the saved rect if window will become maximized on an
+ * other monitor so user isn't surprised on a later unmaximize
+ */
+ if (wxinerama->number != monitor)
+ {
+ window->saved_rect.x = work_area.x;
+ window->saved_rect.y = work_area.y;
+
+ if (window->frame)
+ {
+ window->saved_rect.x += window->frame->child_x;
+ window->saved_rect.y += window->frame->child_y;
+ }
+
+ window->user_rect.x = window->saved_rect.x;
+ window->user_rect.y = window->saved_rect.y;
+
+ meta_window_unmaximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+ }
+
+ display->grab_initial_window_pos = work_area;
+ display->grab_anchor_root_x = x;
+ display->grab_anchor_root_y = y;
+ window->shaken_loose = FALSE;
+
+ meta_window_maximize (window,
+ META_MAXIMIZE_HORIZONTAL |
+ META_MAXIMIZE_VERTICAL);
+
+ return;
+ }
+ }
+ }
+
+ if (display->grab_wireframe_active)
+ old = display->grab_wireframe_rect;
+ else
+ meta_window_get_client_root_coords (window, &old);
+
+ /* Don't allow movement in the maximized directions */
+ if (window->maximized_horizontally)
+ new_x = old.x;
+ if (window->maximized_vertically)
+ new_y = old.y;
+
+ /* Do any edge resistance/snapping */
+ meta_window_edge_resistance_for_move (window,
+ old.x,
+ old.y,
+ &new_x,
+ &new_y,
+ update_move_timeout,
+ snap,
+ FALSE);
+
+ if (display->compositor)
+ {
+ int root_x = new_x - display->grab_anchor_window_pos.x + display->grab_anchor_root_x;
+ int root_y = new_y - display->grab_anchor_window_pos.y + display->grab_anchor_root_y;
+
+ meta_compositor_update_move (display->compositor,
+ window, root_x, root_y);
+ }
+
+ if (display->grab_wireframe_active)
+ meta_window_update_wireframe (window, new_x, new_y,
+ display->grab_wireframe_rect.width,
+ display->grab_wireframe_rect.height);
+ else
+ meta_window_move (window, TRUE, new_x, new_y);
+}
+
+static gboolean
+update_resize_timeout (gpointer data)
+{
+ MetaWindow *window = data;
+
+ update_resize (window,
+ window->display->grab_last_user_action_was_snap,
+ window->display->grab_latest_motion_x,
+ window->display->grab_latest_motion_y,
+ TRUE);
+ return FALSE;
+}
+
+static void
+update_resize (MetaWindow *window,
+ gboolean snap,
+ int x, int y,
+ gboolean force)
+{
+ int dx, dy;
+ int new_w, new_h;
+ int gravity;
+ MetaRectangle old;
+ int new_x, new_y;
+ double remaining;
+
+ window->display->grab_latest_motion_x = x;
+ window->display->grab_latest_motion_y = y;
+
+ dx = x - window->display->grab_anchor_root_x;
+ dy = y - window->display->grab_anchor_root_y;
+
+ new_w = window->display->grab_anchor_window_pos.width;
+ new_h = window->display->grab_anchor_window_pos.height;
+
+ /* Don't bother doing anything if no move has been specified. (This
+ * happens often, even in keyboard resizing, due to the warping of the
+ * pointer.
+ */
+ if (dx == 0 && dy == 0)
+ return;
+
+ /* FIXME this is only used in wireframe mode */
+ new_x = window->display->grab_anchor_window_pos.x;
+ new_y = window->display->grab_anchor_window_pos.y;
+
+ if (window->display->grab_op == META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN)
+ {
+ if ((dx > 0) && (dy > 0))
+ {
+ window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_SE;
+ meta_window_update_keyboard_resize (window, TRUE);
+ }
+ else if ((dx < 0) && (dy > 0))
+ {
+ window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_SW;
+ meta_window_update_keyboard_resize (window, TRUE);
+ }
+ else if ((dx > 0) && (dy < 0))
+ {
+ window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_NE;
+ meta_window_update_keyboard_resize (window, TRUE);
+ }
+ else if ((dx < 0) && (dy < 0))
+ {
+ window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_NW;
+ meta_window_update_keyboard_resize (window, TRUE);
+ }
+ else if (dx < 0)
+ {
+ window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_W;
+ meta_window_update_keyboard_resize (window, TRUE);
+ }
+ else if (dx > 0)
+ {
+ window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_E;
+ meta_window_update_keyboard_resize (window, TRUE);
+ }
+ else if (dy > 0)
+ {
+ window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_S;
+ meta_window_update_keyboard_resize (window, TRUE);
+ }
+ else if (dy < 0)
+ {
+ window->display->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_N;
+ meta_window_update_keyboard_resize (window, TRUE);
+ }
+ }
+
+ /* FIXME: This stupidity only needed because of wireframe mode and
+ * the fact that wireframe isn't making use of
+ * meta_rectangle_resize_with_gravity(). If we were to use that, we
+ * could just increment new_w and new_h by dx and dy in all cases.
+ */
+ switch (window->display->grab_op)
+ {
+ case META_GRAB_OP_RESIZING_SE:
+ case META_GRAB_OP_RESIZING_NE:
+ case META_GRAB_OP_RESIZING_E:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_E:
+ new_w += dx;
+ break;
+
+ case META_GRAB_OP_RESIZING_NW:
+ case META_GRAB_OP_RESIZING_SW:
+ case META_GRAB_OP_RESIZING_W:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_W:
+ new_w -= dx;
+ new_x += dx;
+ break;
+
+ default:
+ break;
+ }
+
+ switch (window->display->grab_op)
+ {
+ case META_GRAB_OP_RESIZING_SE:
+ case META_GRAB_OP_RESIZING_S:
+ case META_GRAB_OP_RESIZING_SW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_S:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ new_h += dy;
+ break;
+
+ case META_GRAB_OP_RESIZING_N:
+ case META_GRAB_OP_RESIZING_NE:
+ case META_GRAB_OP_RESIZING_NW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_N:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ new_h -= dy;
+ new_y += dy;
+ break;
+ default:
+ break;
+ }
+
+ if (!check_moveresize_frequency (window, &remaining) && !force)
+ {
+ /* we are ignoring an event here, so we schedule a
+ * compensation event when we would otherwise not ignore
+ * an event. Otherwise we can become stuck if the user never
+ * generates another event.
+ */
+ if (!window->display->grab_resize_timeout_id)
+ {
+ window->display->grab_resize_timeout_id =
+ g_timeout_add ((int)remaining, update_resize_timeout, window);
+ }
+
+ return;
+ }
+
+ /* If we get here, it means the client should have redrawn itself */
+ if (window->display->compositor)
+ meta_compositor_set_updates (window->display->compositor, window, TRUE);
+
+ /* Remove any scheduled compensation events */
+ if (window->display->grab_resize_timeout_id)
+ {
+ g_source_remove (window->display->grab_resize_timeout_id);
+ window->display->grab_resize_timeout_id = 0;
+ }
+
+ if (window->display->grab_wireframe_active)
+ old = window->display->grab_wireframe_rect;
+ else
+ old = window->rect; /* Don't actually care about x,y */
+
+ /* One sided resizing ought to actually be one-sided, despite the fact that
+ * aspect ratio windows don't interact nicely with the above stuff. So,
+ * to avoid some nasty flicker, we enforce that.
+ */
+ switch (window->display->grab_op)
+ {
+ case META_GRAB_OP_RESIZING_S:
+ case META_GRAB_OP_RESIZING_N:
+ new_w = old.width;
+ break;
+
+ case META_GRAB_OP_RESIZING_E:
+ case META_GRAB_OP_RESIZING_W:
+ new_h = old.height;
+ break;
+
+ default:
+ break;
+ }
+
+ /* compute gravity of client during operation */
+ gravity = meta_resize_gravity_from_grab_op (window->display->grab_op);
+ g_assert (gravity >= 0);
+
+ /* Do any edge resistance/snapping */
+ meta_window_edge_resistance_for_resize (window,
+ old.width,
+ old.height,
+ &new_w,
+ &new_h,
+ gravity,
+ update_resize_timeout,
+ snap,
+ FALSE);
+
+ if (window->display->grab_wireframe_active)
+ {
+ if ((new_x + new_w <= new_x) || (new_y + new_h <= new_y))
+ return;
+
+ /* FIXME This is crap. For example, the wireframe isn't
+ * constrained in the way that a real resize would be. An
+ * obvious elegant solution is to unmap the window during
+ * wireframe, but still resize it; however, that probably
+ * confuses broken clients that have problems with opaque
+ * resize, they probably don't track their visibility.
+ */
+ meta_window_update_wireframe (window, new_x, new_y, new_w, new_h);
+ }
+ else
+ {
+ /* We don't need to update unless the specified width and height
+ * are actually different from what we had before.
+ */
+ if (old.width != new_w || old.height != new_h)
+ meta_window_resize_with_gravity (window, TRUE, new_w, new_h, gravity);
+ }
+
+ /* Store the latest resize time, if we actually resized. */
+ if (window->rect.width != old.width || window->rect.height != old.height)
+ g_get_current_time (&window->display->grab_last_moveresize_time);
+}
+
+typedef struct
+{
+ const XEvent *current_event;
+ int count;
+ guint32 last_time;
+} EventScannerData;
+
+static Bool
+find_last_time_predicate (Display *display,
+ XEvent *xevent,
+ XPointer arg)
+{
+ EventScannerData *esd = (void*) arg;
+
+ if (esd->current_event->type == xevent->type &&
+ esd->current_event->xany.window == xevent->xany.window)
+ {
+ esd->count += 1;
+ esd->last_time = xevent->xmotion.time;
+ }
+
+ return False;
+}
+
+static gboolean
+check_use_this_motion_notify (MetaWindow *window,
+ XEvent *event)
+{
+ EventScannerData esd;
+ XEvent useless;
+
+ /* This code is copied from Owen's GDK code. */
+
+ if (window->display->grab_motion_notify_time != 0)
+ {
+ /* == is really the right test, but I'm all for paranoia */
+ if (window->display->grab_motion_notify_time <=
+ event->xmotion.time)
+ {
+ meta_topic (META_DEBUG_RESIZING,
+ "Arrived at event with time %u (waiting for %u), using it\n",
+ (unsigned int)event->xmotion.time,
+ window->display->grab_motion_notify_time);
+ window->display->grab_motion_notify_time = 0;
+ return TRUE;
+ }
+ else
+ return FALSE; /* haven't reached the saved timestamp yet */
+ }
+
+ esd.current_event = event;
+ esd.count = 0;
+ esd.last_time = 0;
+
+ /* "useless" isn't filled in because the predicate never returns True */
+ XCheckIfEvent (window->display->xdisplay,
+ &useless,
+ find_last_time_predicate,
+ (XPointer) &esd);
+
+ if (esd.count > 0)
+ meta_topic (META_DEBUG_RESIZING,
+ "Will skip %d motion events and use the event with time %u\n",
+ esd.count, (unsigned int) esd.last_time);
+
+ if (esd.last_time == 0)
+ return TRUE;
+ else
+ {
+ /* Save this timestamp, and ignore all motion notify
+ * until we get to the one with this stamp.
+ */
+ window->display->grab_motion_notify_time = esd.last_time;
+ return FALSE;
+ }
+}
+
+void
+meta_window_handle_mouse_grab_op_event (MetaWindow *window,
+ XEvent *event)
+{
+#ifdef HAVE_XSYNC
+ if (event->type == (window->display->xsync_event_base + XSyncAlarmNotify))
+ {
+ meta_topic (META_DEBUG_RESIZING,
+ "Alarm event received last motion x = %d y = %d\n",
+ window->display->grab_latest_motion_x,
+ window->display->grab_latest_motion_y);
+
+ /* If sync was previously disabled, turn it back on and hope
+ * the application has come to its senses (maybe it was just
+ * busy with a pagefault or a long computation).
+ */
+ window->disable_sync = FALSE;
+ window->sync_request_time.tv_sec = 0;
+ window->sync_request_time.tv_usec = 0;
+
+ /* This means we are ready for another configure. */
+ switch (window->display->grab_op)
+ {
+ case META_GRAB_OP_RESIZING_E:
+ case META_GRAB_OP_RESIZING_W:
+ case META_GRAB_OP_RESIZING_S:
+ case META_GRAB_OP_RESIZING_N:
+ case META_GRAB_OP_RESIZING_SE:
+ case META_GRAB_OP_RESIZING_SW:
+ case META_GRAB_OP_RESIZING_NE:
+ case META_GRAB_OP_RESIZING_NW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_S:
+ case META_GRAB_OP_KEYBOARD_RESIZING_N:
+ case META_GRAB_OP_KEYBOARD_RESIZING_W:
+ case META_GRAB_OP_KEYBOARD_RESIZING_E:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ /* no pointer round trip here, to keep in sync */
+ update_resize (window,
+ window->display->grab_last_user_action_was_snap,
+ window->display->grab_latest_motion_x,
+ window->display->grab_latest_motion_y,
+ TRUE);
+ break;
+
+ default:
+ break;
+ }
+ }
+#endif /* HAVE_XSYNC */
+
+ switch (event->type)
+ {
+ case ButtonRelease:
+ meta_display_check_threshold_reached (window->display,
+ event->xbutton.x_root,
+ event->xbutton.y_root);
+ /* If the user was snap moving then ignore the button release
+ * because they may have let go of shift before releasing the
+ * mouse button and they almost certainly do not want a
+ * non-snapped movement to occur from the button release.
+ */
+ if (!window->display->grab_last_user_action_was_snap)
+ {
+ if (meta_grab_op_is_moving (window->display->grab_op))
+ {
+ if (event->xbutton.root == window->screen->xroot)
+ update_move (window, event->xbutton.state & ShiftMask,
+ event->xbutton.x_root, event->xbutton.y_root);
+ }
+ else if (meta_grab_op_is_resizing (window->display->grab_op))
+ {
+ if (event->xbutton.root == window->screen->xroot)
+ update_resize (window,
+ event->xbutton.state & ShiftMask,
+ event->xbutton.x_root,
+ event->xbutton.y_root,
+ TRUE);
+ if (window->display->compositor)
+ meta_compositor_set_updates (window->display->compositor, window, TRUE);
+ }
+ }
+
+ meta_display_end_grab_op (window->display, event->xbutton.time);
+ break;
+
+ case MotionNotify:
+ meta_display_check_threshold_reached (window->display,
+ event->xmotion.x_root,
+ event->xmotion.y_root);
+ if (meta_grab_op_is_moving (window->display->grab_op))
+ {
+ if (event->xmotion.root == window->screen->xroot)
+ {
+ if (check_use_this_motion_notify (window,
+ event))
+ update_move (window,
+ event->xmotion.state & ShiftMask,
+ event->xmotion.x_root,
+ event->xmotion.y_root);
+ }
+ }
+ else if (meta_grab_op_is_resizing (window->display->grab_op))
+ {
+ if (event->xmotion.root == window->screen->xroot)
+ {
+ if (check_use_this_motion_notify (window,
+ event))
+ update_resize (window,
+ event->xmotion.state & ShiftMask,
+ event->xmotion.x_root,
+ event->xmotion.y_root,
+ FALSE);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void
+meta_window_set_gravity (MetaWindow *window,
+ int gravity)
+{
+ XSetWindowAttributes attrs;
+
+ meta_verbose ("Setting gravity of %s to %d\n", window->desc, gravity);
+
+ attrs.win_gravity = gravity;
+
+ meta_error_trap_push (window->display);
+
+ XChangeWindowAttributes (window->display->xdisplay,
+ window->xwindow,
+ CWWinGravity,
+ &attrs);
+
+ meta_error_trap_pop (window->display, FALSE);
+}
+
+static void
+get_work_area_xinerama (MetaWindow *window,
+ MetaRectangle *area,
+ int which_xinerama)
+{
+ GList *tmp;
+
+ g_assert (which_xinerama >= 0);
+
+ /* Initialize to the whole xinerama */
+ *area = window->screen->xinerama_infos[which_xinerama].rect;
+
+ tmp = meta_window_get_workspaces (window);
+ while (tmp != NULL)
+ {
+ MetaRectangle workspace_work_area;
+ meta_workspace_get_work_area_for_xinerama (tmp->data,
+ which_xinerama,
+ &workspace_work_area);
+ meta_rectangle_intersect (area,
+ &workspace_work_area,
+ area);
+ tmp = tmp->next;
+ }
+
+ meta_topic (META_DEBUG_WORKAREA,
+ "Window %s xinerama %d has work area %d,%d %d x %d\n",
+ window->desc, which_xinerama,
+ area->x, area->y, area->width, area->height);
+}
+
+void
+meta_window_get_work_area_current_xinerama (MetaWindow *window,
+ MetaRectangle *area)
+{
+ const MetaXineramaScreenInfo *xinerama = NULL;
+ xinerama = meta_screen_get_xinerama_for_window (window->screen,
+ window);
+
+ meta_window_get_work_area_for_xinerama (window,
+ xinerama->number,
+ area);
+}
+
+void
+meta_window_get_work_area_for_xinerama (MetaWindow *window,
+ int which_xinerama,
+ MetaRectangle *area)
+{
+ g_return_if_fail (which_xinerama >= 0);
+
+ get_work_area_xinerama (window,
+ area,
+ which_xinerama);
+}
+
+void
+meta_window_get_work_area_all_xineramas (MetaWindow *window,
+ MetaRectangle *area)
+{
+ GList *tmp;
+
+ /* Initialize to the whole screen */
+ *area = window->screen->rect;
+
+ tmp = meta_window_get_workspaces (window);
+ while (tmp != NULL)
+ {
+ MetaRectangle workspace_work_area;
+ meta_workspace_get_work_area_all_xineramas (tmp->data,
+ &workspace_work_area);
+ meta_rectangle_intersect (area,
+ &workspace_work_area,
+ area);
+ tmp = tmp->next;
+ }
+
+ meta_topic (META_DEBUG_WORKAREA,
+ "Window %s has whole-screen work area %d,%d %d x %d\n",
+ window->desc, area->x, area->y, area->width, area->height);
+}
+
+
+gboolean
+meta_window_same_application (MetaWindow *window,
+ MetaWindow *other_window)
+{
+ MetaGroup *group = meta_window_get_group (window);
+ MetaGroup *other_group = meta_window_get_group (other_window);
+
+ return
+ group!=NULL &&
+ other_group!=NULL &&
+ group==other_group;
+}
+
+void
+meta_window_refresh_resize_popup (MetaWindow *window)
+{
+ if (window->display->grab_op == META_GRAB_OP_NONE)
+ return;
+
+ if (window->display->grab_window != window)
+ return;
+
+ /* We shouldn't ever get called when the wireframe is active
+ * because that's handled by a different code path in effects.c
+ */
+ if (window->display->grab_wireframe_active)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "refresh_resize_popup called when wireframe active\n");
+ return;
+ }
+
+ switch (window->display->grab_op)
+ {
+ case META_GRAB_OP_RESIZING_SE:
+ case META_GRAB_OP_RESIZING_S:
+ case META_GRAB_OP_RESIZING_SW:
+ case META_GRAB_OP_RESIZING_N:
+ case META_GRAB_OP_RESIZING_NE:
+ case META_GRAB_OP_RESIZING_NW:
+ case META_GRAB_OP_RESIZING_W:
+ case META_GRAB_OP_RESIZING_E:
+ case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
+ case META_GRAB_OP_KEYBOARD_RESIZING_S:
+ case META_GRAB_OP_KEYBOARD_RESIZING_N:
+ case META_GRAB_OP_KEYBOARD_RESIZING_W:
+ case META_GRAB_OP_KEYBOARD_RESIZING_E:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ break;
+
+ default:
+ /* Not resizing */
+ return;
+ }
+
+ if (window->display->grab_resize_popup == NULL)
+ {
+ if (window->size_hints.width_inc > 1 ||
+ window->size_hints.height_inc > 1)
+ window->display->grab_resize_popup =
+ meta_ui_resize_popup_new (window->display->xdisplay,
+ window->screen->number);
+ }
+
+ if (window->display->grab_resize_popup != NULL)
+ {
+ MetaRectangle rect;
+
+ if (window->display->grab_wireframe_active)
+ rect = window->display->grab_wireframe_rect;
+ else
+ meta_window_get_client_root_coords (window, &rect);
+
+ meta_ui_resize_popup_set (window->display->grab_resize_popup,
+ rect,
+ window->size_hints.base_width,
+ window->size_hints.base_height,
+ window->size_hints.width_inc,
+ window->size_hints.height_inc);
+
+ meta_ui_resize_popup_set_showing (window->display->grab_resize_popup,
+ TRUE);
+ }
+}
+
+void
+meta_window_foreach_transient (MetaWindow *window,
+ MetaWindowForeachFunc func,
+ void *data)
+{
+ GSList *windows;
+ GSList *tmp;
+
+ windows = meta_display_list_windows (window->display);
+
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *transient = tmp->data;
+
+ if (meta_window_is_ancestor_of_transient (window, transient))
+ {
+ if (!(* func) (transient, data))
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (windows);
+}
+
+void
+meta_window_foreach_ancestor (MetaWindow *window,
+ MetaWindowForeachFunc func,
+ void *data)
+{
+ MetaWindow *w;
+ MetaWindow *tortoise;
+
+ w = window;
+ tortoise = window;
+ while (TRUE)
+ {
+ if (w->xtransient_for == None ||
+ w->transient_parent_is_root_window)
+ break;
+
+ w = meta_display_lookup_x_window (w->display, w->xtransient_for);
+
+ if (w == NULL || w == tortoise)
+ break;
+
+ if (!(* func) (w, data))
+ break;
+
+ if (w->xtransient_for == None ||
+ w->transient_parent_is_root_window)
+ break;
+
+ w = meta_display_lookup_x_window (w->display, w->xtransient_for);
+
+ if (w == NULL || w == tortoise)
+ break;
+
+ if (!(* func) (w, data))
+ break;
+
+ tortoise = meta_display_lookup_x_window (tortoise->display,
+ tortoise->xtransient_for);
+
+ /* "w" should have already covered all ground covered by the
+ * tortoise, so the following must hold.
+ */
+ g_assert (tortoise != NULL);
+ g_assert (tortoise->xtransient_for != None);
+ g_assert (!tortoise->transient_parent_is_root_window);
+ }
+}
+
+typedef struct
+{
+ MetaWindow *ancestor;
+ gboolean found;
+} FindAncestorData;
+
+static gboolean
+find_ancestor_func (MetaWindow *window,
+ void *data)
+{
+ FindAncestorData *d = data;
+
+ if (window == d->ancestor)
+ {
+ d->found = TRUE;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+meta_window_is_ancestor_of_transient (MetaWindow *window,
+ MetaWindow *transient)
+{
+ FindAncestorData d;
+
+ d.ancestor = window;
+ d.found = FALSE;
+
+ meta_window_foreach_ancestor (transient, find_ancestor_func, &d);
+
+ return d.found;
+}
+
+/* Warp pointer to location appropriate for grab,
+ * return root coordinates where pointer ended up.
+ */
+static gboolean
+warp_grab_pointer (MetaWindow *window,
+ MetaGrabOp grab_op,
+ int *x,
+ int *y)
+{
+ MetaRectangle rect;
+ MetaDisplay *display;
+
+ display = window->display;
+
+ /* We may not have done begin_grab_op yet, i.e. may not be in a grab
+ */
+
+ if (window == display->grab_window && display->grab_wireframe_active)
+ {
+ meta_window_get_xor_rect (window, &display->grab_wireframe_rect, &rect);
+ }
+ else
+ {
+ meta_window_get_outer_rect (window, &rect);
+ }
+
+ switch (grab_op)
+ {
+ case META_GRAB_OP_KEYBOARD_MOVING:
+ case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
+ *x = rect.width / 2;
+ *y = rect.height / 2;
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_S:
+ *x = rect.width / 2;
+ *y = rect.height - 1;
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_N:
+ *x = rect.width / 2;
+ *y = 0;
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_W:
+ *x = 0;
+ *y = rect.height / 2;
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_E:
+ *x = rect.width - 1;
+ *y = rect.height / 2;
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_SE:
+ *x = rect.width - 1;
+ *y = rect.height - 1;
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_NE:
+ *x = rect.width - 1;
+ *y = 0;
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_SW:
+ *x = 0;
+ *y = rect.height - 1;
+ break;
+
+ case META_GRAB_OP_KEYBOARD_RESIZING_NW:
+ *x = 0;
+ *y = 0;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ *x += rect.x;
+ *y += rect.y;
+
+ /* Avoid weird bouncing at the screen edge; see bug 154706 */
+ *x = CLAMP (*x, 0, window->screen->rect.width-1);
+ *y = CLAMP (*y, 0, window->screen->rect.height-1);
+
+ meta_error_trap_push_with_return (display);
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Warping pointer to %d,%d with window at %d,%d\n",
+ *x, *y, rect.x, rect.y);
+
+ /* Need to update the grab positions so that the MotionNotify and other
+ * events generated by the XWarpPointer() call below don't cause complete
+ * funkiness. See bug 124582 and bug 122670.
+ */
+ display->grab_anchor_root_x = *x;
+ display->grab_anchor_root_y = *y;
+ display->grab_latest_motion_x = *x;
+ display->grab_latest_motion_y = *y;
+ if (display->grab_wireframe_active)
+ display->grab_anchor_window_pos = display->grab_wireframe_rect;
+ else
+ meta_window_get_client_root_coords (window,
+ &display->grab_anchor_window_pos);
+
+ XWarpPointer (display->xdisplay,
+ None,
+ window->screen->xroot,
+ 0, 0, 0, 0,
+ *x, *y);
+
+ if (meta_error_trap_pop_with_return (display, FALSE) != Success)
+ {
+ meta_verbose ("Failed to warp pointer for window %s\n",
+ window->desc);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+meta_window_begin_grab_op (MetaWindow *window,
+ MetaGrabOp op,
+ gboolean frame_action,
+ guint32 timestamp)
+{
+ int x, y;
+
+ warp_grab_pointer (window,
+ op, &x, &y);
+
+ meta_display_begin_grab_op (window->display,
+ window->screen,
+ window,
+ op,
+ FALSE,
+ frame_action,
+ 0 /* button */,
+ 0,
+ timestamp,
+ x, y);
+}
+
+void
+meta_window_update_keyboard_resize (MetaWindow *window,
+ gboolean update_cursor)
+{
+ int x, y;
+
+ warp_grab_pointer (window,
+ window->display->grab_op,
+ &x, &y);
+
+ if (update_cursor)
+ {
+ guint32 timestamp;
+ /* FIXME: Using CurrentTime is really bad mojo */
+ timestamp = CurrentTime;
+ meta_display_set_grab_op_cursor (window->display,
+ NULL,
+ window->display->grab_op,
+ TRUE,
+ window->display->grab_xwindow,
+ timestamp);
+ }
+}
+
+void
+meta_window_update_keyboard_move (MetaWindow *window)
+{
+ int x, y;
+
+ warp_grab_pointer (window,
+ window->display->grab_op,
+ &x, &y);
+}
+
+void
+meta_window_update_layer (MetaWindow *window)
+{
+ MetaGroup *group;
+
+ meta_stack_freeze (window->screen->stack);
+ group = meta_window_get_group (window);
+ if (group)
+ meta_group_update_layers (group);
+ else
+ meta_stack_update_layer (window->screen->stack, window);
+ meta_stack_thaw (window->screen->stack);
+}
+
+/* ensure_mru_position_after ensures that window appears after
+ * below_this_one in the active_workspace's mru_list (i.e. it treats
+ * window as having been less recently used than below_this_one)
+ */
+static void
+ensure_mru_position_after (MetaWindow *window,
+ MetaWindow *after_this_one)
+{
+ /* This is sort of slow since it runs through the entire list more
+ * than once (especially considering the fact that we expect the
+ * windows of interest to be the first two elements in the list),
+ * but it doesn't matter while we're only using it on new window
+ * map.
+ */
+
+ GList* active_mru_list;
+ GList* window_position;
+ GList* after_this_one_position;
+
+ active_mru_list = window->screen->active_workspace->mru_list;
+ window_position = g_list_find (active_mru_list, window);
+ after_this_one_position = g_list_find (active_mru_list, after_this_one);
+
+ /* after_this_one_position is NULL when we switch workspaces, but in
+ * that case we don't need to do any MRU shuffling so we can simply
+ * return.
+ */
+ if (after_this_one_position == NULL)
+ return;
+
+ if (g_list_length (window_position) > g_list_length (after_this_one_position))
+ {
+ window->screen->active_workspace->mru_list =
+ g_list_delete_link (window->screen->active_workspace->mru_list,
+ window_position);
+
+ window->screen->active_workspace->mru_list =
+ g_list_insert_before (window->screen->active_workspace->mru_list,
+ after_this_one_position->next,
+ window);
+ }
+}
+
+void
+meta_window_stack_just_below (MetaWindow *window,
+ MetaWindow *below_this_one)
+{
+ g_return_if_fail (window != NULL);
+ g_return_if_fail (below_this_one != NULL);
+
+ if (window->stack_position > below_this_one->stack_position)
+ {
+ meta_topic (META_DEBUG_STACK,
+ "Setting stack position of window %s to %d (making it below window %s).\n",
+ window->desc,
+ below_this_one->stack_position,
+ below_this_one->desc);
+ meta_window_set_stack_position (window, below_this_one->stack_position);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_STACK,
+ "Window %s was already below window %s.\n",
+ window->desc, below_this_one->desc);
+ }
+}
+
+void
+meta_window_set_user_time (MetaWindow *window,
+ guint32 timestamp)
+{
+ /* FIXME: If Soeren's suggestion in bug 151984 is implemented, it will allow
+ * us to sanity check the timestamp here and ensure it doesn't correspond to
+ * a future time.
+ */
+
+ /* Only update the time if this timestamp is newer... */
+ if (window->net_wm_user_time_set &&
+ XSERVER_TIME_IS_BEFORE (timestamp, window->net_wm_user_time))
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "Window %s _NET_WM_USER_TIME not updated to %u, because it "
+ "is less than %u\n",
+ window->desc, timestamp, window->net_wm_user_time);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_STARTUP,
+ "Window %s has _NET_WM_USER_TIME of %u\n",
+ window->desc, timestamp);
+ window->net_wm_user_time_set = TRUE;
+ window->net_wm_user_time = timestamp;
+ if (XSERVER_TIME_IS_BEFORE (window->display->last_user_time, timestamp))
+ window->display->last_user_time = timestamp;
+
+ /* If this is a terminal, user interaction with it means the user likely
+ * doesn't want to have focus transferred for now due to new windows.
+ */
+ if (meta_prefs_get_focus_new_windows () ==
+ META_FOCUS_NEW_WINDOWS_STRICT &&
+ __window_is_terminal (window))
+ window->display->allow_terminal_deactivation = FALSE;
+ }
+}
+
+/* Sets the demands_attention hint on a window, but only
+ * if it's at least partially obscured (see #305882).
+ */
+void
+meta_window_set_demands_attention (MetaWindow *window)
+{
+ MetaRectangle candidate_rect, other_rect;
+ GList *stack = window->screen->stack->sorted;
+ MetaWindow *other_window;
+ gboolean obscured = FALSE;
+
+ MetaWorkspace *workspace = window->screen->active_workspace;
+ if (workspace!=window->workspace)
+ {
+ /* windows on other workspaces are necessarily obscured */
+ obscured = TRUE;
+ }
+ else if (window->minimized)
+ {
+ obscured = TRUE;
+ }
+ else
+ {
+ meta_window_get_outer_rect (window, &candidate_rect);
+
+ /* The stack is sorted with the top windows first. */
+
+ while (stack != NULL && stack->data != window)
+ {
+ other_window = stack->data;
+ stack = stack->next;
+
+ if (other_window->on_all_workspaces ||
+ window->on_all_workspaces ||
+ other_window->workspace == window->workspace)
+ {
+ meta_window_get_outer_rect (other_window, &other_rect);
+
+ if (meta_rectangle_overlap (&candidate_rect, &other_rect))
+ {
+ obscured = TRUE;
+ break;
+ }
+ }
+ }
+ }
+
+ if (obscured)
+ {
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Marking %s as needing attention\n",
+ window->desc);
+
+ window->wm_state_demands_attention = TRUE;
+ set_net_wm_state (window);
+ }
+ else
+ {
+ /* If the window's in full view, there's no point setting the flag. */
+
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Not marking %s as needing attention because "
+ "it's in full view\n",
+ window->desc);
+ }
+}
+
+void
+meta_window_unset_demands_attention (MetaWindow *window)
+{
+ meta_topic (META_DEBUG_WINDOW_OPS,
+ "Marking %s as not needing attention\n", window->desc);
+
+ window->wm_state_demands_attention = FALSE;
+ set_net_wm_state (window);
+}
+
+MetaFrame *
+meta_window_get_frame (MetaWindow *window)
+{
+ return window->frame;
+}
+
+gboolean
+meta_window_has_focus (MetaWindow *window)
+{
+ return window->has_focus;
+}
+
+gboolean
+meta_window_is_shaded (MetaWindow *window)
+{
+ return window->shaded;
+}
+
+MetaRectangle *
+meta_window_get_rect (MetaWindow *window)
+{
+ return &window->rect;
+}
+
+MetaScreen *
+meta_window_get_screen (MetaWindow *window)
+{
+ return window->screen;
+}
+
+MetaDisplay *
+meta_window_get_display (MetaWindow *window)
+{
+ return window->display;
+}
+
+Window
+meta_window_get_xwindow (MetaWindow *window)
+{
+ return window->xwindow;
+}
diff --git a/src/core/workspace.c b/src/core/workspace.c
new file mode 100644
index 00000000..2b28fe0b
--- /dev/null
+++ b/src/core/workspace.c
@@ -0,0 +1,1038 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco Workspaces */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2003 Rob Adams
+ * Copyright (C) 2004, 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "workspace.h"
+#include "errors.h"
+#include "prefs.h"
+#include <X11/Xatom.h>
+#include <string.h>
+#include <canberra-gtk.h>
+
+void meta_workspace_queue_calc_showing (MetaWorkspace *workspace);
+static void set_active_space_hint (MetaScreen *screen);
+static void focus_ancestor_or_mru_window (MetaWorkspace *workspace,
+ MetaWindow *not_this_one,
+ guint32 timestamp);
+static void free_this (gpointer candidate,
+ gpointer dummy);
+static void workspace_free_struts (MetaWorkspace *workspace);
+
+static void
+maybe_add_to_list (MetaScreen *screen, MetaWindow *window, gpointer data)
+{
+ GList **mru_list = data;
+
+ if (window->on_all_workspaces)
+ *mru_list = g_list_prepend (*mru_list, window);
+}
+
+MetaWorkspace*
+meta_workspace_new (MetaScreen *screen)
+{
+ MetaWorkspace *workspace;
+
+ workspace = g_new (MetaWorkspace, 1);
+
+ workspace->screen = screen;
+ workspace->screen->workspaces =
+ g_list_append (workspace->screen->workspaces, workspace);
+ workspace->windows = NULL;
+ workspace->mru_list = NULL;
+ meta_screen_foreach_window (screen, maybe_add_to_list, &workspace->mru_list);
+
+ workspace->work_areas_invalid = TRUE;
+ workspace->work_area_xinerama = NULL;
+ workspace->work_area_screen.x = 0;
+ workspace->work_area_screen.y = 0;
+ workspace->work_area_screen.width = 0;
+ workspace->work_area_screen.height = 0;
+
+ workspace->screen_region = NULL;
+ workspace->xinerama_region = NULL;
+ workspace->screen_edges = NULL;
+ workspace->xinerama_edges = NULL;
+ workspace->list_containing_self = g_list_prepend (NULL, workspace);
+
+ workspace->all_struts = NULL;
+
+ workspace->showing_desktop = FALSE;
+
+ return workspace;
+}
+
+/** Foreach function for workspace_free_struts() */
+static void
+free_this (gpointer candidate, gpointer dummy)
+{
+ g_free (candidate);
+}
+
+/**
+ * Frees the struts list of a workspace.
+ *
+ * \param workspace The workspace.
+ */
+static void
+workspace_free_struts (MetaWorkspace *workspace)
+{
+ if (workspace->all_struts == NULL)
+ return;
+
+ g_slist_foreach (workspace->all_struts, free_this, NULL);
+ g_slist_free (workspace->all_struts);
+ workspace->all_struts = NULL;
+}
+
+void
+meta_workspace_free (MetaWorkspace *workspace)
+{
+ GList *tmp;
+ MetaScreen *screen;
+ int i;
+
+ g_return_if_fail (workspace != workspace->screen->active_workspace);
+
+ /* Here we assume all the windows are already on another workspace
+ * as well, so they won't be "orphaned"
+ */
+
+ tmp = workspace->windows;
+ while (tmp != NULL)
+ {
+ GList *next;
+ MetaWindow *window = tmp->data;
+ next = tmp->next;
+
+ /* pop front of list we're iterating over */
+ meta_workspace_remove_window (workspace, window);
+ g_assert (window->workspace != NULL);
+
+ tmp = next;
+ }
+
+ g_assert (workspace->windows == NULL);
+
+ screen = workspace->screen;
+
+ workspace->screen->workspaces =
+ g_list_remove (workspace->screen->workspaces, workspace);
+
+ g_free (workspace->work_area_xinerama);
+
+ g_list_free (workspace->mru_list);
+ g_list_free (workspace->list_containing_self);
+
+ /* screen.c:update_num_workspaces(), which calls us, removes windows from
+ * workspaces first, which can cause the workareas on the workspace to be
+ * invalidated (and hence for struts/regions/edges to be freed).
+ * So, no point trying to double free it; that causes a crash
+ * anyway. #361804.
+ */
+
+ if (!workspace->work_areas_invalid)
+ {
+ workspace_free_struts (workspace);
+ for (i = 0; i < screen->n_xinerama_infos; i++)
+ meta_rectangle_free_list_and_elements (workspace->xinerama_region[i]);
+ g_free (workspace->xinerama_region);
+ meta_rectangle_free_list_and_elements (workspace->screen_region);
+ meta_rectangle_free_list_and_elements (workspace->screen_edges);
+ meta_rectangle_free_list_and_elements (workspace->xinerama_edges);
+ }
+
+ g_free (workspace);
+
+ /* don't bother to reset names, pagers can just ignore
+ * extra ones
+ */
+}
+
+void
+meta_workspace_add_window (MetaWorkspace *workspace,
+ MetaWindow *window)
+{
+ g_return_if_fail (window->workspace == NULL);
+
+ /* If the window is on all workspaces, we want to add it to all mru
+ * lists, otherwise just add it to this workspaces mru list
+ */
+ if (window->on_all_workspaces)
+ {
+ if (window->workspace == NULL)
+ {
+ GList* tmp = window->screen->workspaces;
+ while (tmp)
+ {
+ MetaWorkspace* work = (MetaWorkspace*) tmp->data;
+ if (!g_list_find (work->mru_list, window))
+ work->mru_list = g_list_prepend (work->mru_list, window);
+
+ tmp = tmp->next;
+ }
+ }
+ }
+ else
+ {
+ g_assert (g_list_find (workspace->mru_list, window) == NULL);
+ workspace->mru_list = g_list_prepend (workspace->mru_list, window);
+ }
+
+ workspace->windows = g_list_prepend (workspace->windows, window);
+ window->workspace = workspace;
+
+ meta_window_set_current_workspace_hint (window);
+
+ if (window->struts)
+ {
+ meta_topic (META_DEBUG_WORKAREA,
+ "Invalidating work area of workspace %d since we're adding window %s to it\n",
+ meta_workspace_index (workspace), window->desc);
+ meta_workspace_invalidate_work_area (workspace);
+ }
+
+ /* queue a move_resize since changing workspaces may change
+ * the relevant struts
+ */
+ meta_window_queue (window, META_QUEUE_CALC_SHOWING|META_QUEUE_MOVE_RESIZE);
+}
+
+void
+meta_workspace_remove_window (MetaWorkspace *workspace,
+ MetaWindow *window)
+{
+ g_return_if_fail (window->workspace == workspace);
+
+ workspace->windows = g_list_remove (workspace->windows, window);
+ window->workspace = NULL;
+
+ /* If the window is on all workspaces, we don't want to remove it
+ * from the MRU list unless this causes it to be removed from all
+ * workspaces
+ */
+ if (window->on_all_workspaces)
+ {
+ GList* tmp = window->screen->workspaces;
+ while (tmp)
+ {
+ MetaWorkspace* work = (MetaWorkspace*) tmp->data;
+ work->mru_list = g_list_remove (work->mru_list, window);
+
+ tmp = tmp->next;
+ }
+ }
+ else
+ {
+ workspace->mru_list = g_list_remove (workspace->mru_list, window);
+ g_assert (g_list_find (workspace->mru_list, window) == NULL);
+ }
+
+ meta_window_set_current_workspace_hint (window);
+
+ if (window->struts)
+ {
+ meta_topic (META_DEBUG_WORKAREA,
+ "Invalidating work area of workspace %d since we're removing window %s from it\n",
+ meta_workspace_index (workspace), window->desc);
+ meta_workspace_invalidate_work_area (workspace);
+ }
+
+ /* queue a move_resize since changing workspaces may change
+ * the relevant struts
+ */
+ meta_window_queue (window, META_QUEUE_CALC_SHOWING|META_QUEUE_MOVE_RESIZE);
+}
+
+void
+meta_workspace_relocate_windows (MetaWorkspace *workspace,
+ MetaWorkspace *new_home)
+{
+ GList *tmp;
+ GList *copy;
+
+ g_return_if_fail (workspace != new_home);
+
+ /* can't modify list we're iterating over */
+ copy = g_list_copy (workspace->windows);
+
+ tmp = copy;
+ while (tmp != NULL)
+ {
+ MetaWindow *window = tmp->data;
+
+ meta_workspace_remove_window (workspace, window);
+ meta_workspace_add_window (new_home, window);
+
+ tmp = tmp->next;
+ }
+
+ g_list_free (copy);
+
+ g_assert (workspace->windows == NULL);
+}
+
+void
+meta_workspace_queue_calc_showing (MetaWorkspace *workspace)
+{
+ GList *tmp;
+
+ tmp = workspace->windows;
+ while (tmp != NULL)
+ {
+ meta_window_queue (tmp->data, META_QUEUE_CALC_SHOWING);
+
+ tmp = tmp->next;
+ }
+}
+
+static void workspace_switch_sound(MetaWorkspace *from,
+ MetaWorkspace *to) {
+
+ MetaWorkspaceLayout layout;
+ int i, nw, x, y, fi, ti;
+ const char *e;
+
+ nw = meta_screen_get_n_workspaces(from->screen);
+ fi = meta_workspace_index(from);
+ ti = meta_workspace_index(to);
+
+ meta_screen_calc_workspace_layout(from->screen,
+ nw,
+ fi,
+ &layout);
+
+ for (i = 0; i < nw; i++)
+ if (layout.grid[i] == ti)
+ break;
+
+ if (i >= nw) {
+ meta_bug("Failed to find destination workspace in layout\n");
+ goto finish;
+ }
+
+ y = i / layout.cols;
+ x = i % layout.cols;
+
+ /* We priorize horizontal over vertical movements here. The
+ rationale for this is that horizontal movements are probably more
+ interesting for sound effects because speakers are usually
+ positioned on a horizontal and not a vertical axis. i.e. your
+ spatial "Woosh!" effects will easily be able to encode horizontal
+ movement but not such much vertical movement. */
+
+ if (x < layout.current_col)
+ e = "desktop-switch-left";
+ else if (x > layout.current_col)
+ e = "desktop-switch-right";
+ else if (y < layout.current_row)
+ e = "desktop-switch-up";
+ else if (y > layout.current_row)
+ e = "desktop-switch-down";
+ else {
+ meta_bug("Uh, origin and destination workspace at same logic position!\n");
+ goto finish;
+ }
+
+ ca_context_play(ca_gtk_context_get(), 1,
+ CA_PROP_EVENT_ID, e,
+ CA_PROP_EVENT_DESCRIPTION, "Desktop switched",
+ CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
+ NULL);
+
+ finish:
+ meta_screen_free_workspace_layout (&layout);
+}
+
+void
+meta_workspace_activate_with_focus (MetaWorkspace *workspace,
+ MetaWindow *focus_this,
+ guint32 timestamp)
+{
+ MetaWorkspace *old;
+ MetaWindow *move_window;
+
+ meta_verbose ("Activating workspace %d\n",
+ meta_workspace_index (workspace));
+
+ if (workspace->screen->active_workspace == workspace)
+ return;
+
+ if (workspace->screen->active_workspace)
+ workspace_switch_sound(workspace->screen->active_workspace, workspace);
+
+ /* Note that old can be NULL; e.g. when starting up */
+ old = workspace->screen->active_workspace;
+
+ workspace->screen->active_workspace = workspace;
+
+ set_active_space_hint (workspace->screen);
+
+ /* If the "show desktop" mode is active for either the old workspace
+ * or the new one *but not both*, then update the
+ * _net_showing_desktop hint
+ */
+ if (old && (old->showing_desktop ^ workspace->showing_desktop))
+ meta_screen_update_showing_desktop_hint (workspace->screen);
+
+ if (old == NULL)
+ return;
+
+ move_window = NULL;
+ if (workspace->screen->display->grab_op == META_GRAB_OP_MOVING ||
+ workspace->screen->display->grab_op == META_GRAB_OP_KEYBOARD_MOVING)
+ move_window = workspace->screen->display->grab_window;
+
+ if (move_window != NULL)
+ {
+ if (move_window->on_all_workspaces)
+ move_window = NULL; /* don't move it after all */
+
+ /* We put the window on the new workspace, flip spaces,
+ * then remove from old workspace, so the window
+ * never gets unmapped and we maintain the button grab
+ * on it.
+ *
+ * \bug This comment appears to be the reverse of what happens
+ */
+ if (move_window && (move_window->workspace != workspace))
+ {
+ meta_workspace_remove_window (old, move_window);
+ meta_workspace_add_window (workspace, move_window);
+ }
+ }
+
+ meta_workspace_queue_calc_showing (old);
+ meta_workspace_queue_calc_showing (workspace);
+
+ /* FIXME: Why do we need this?!? Isn't it handled in the lines above? */
+ if (move_window)
+ /* Removes window from other spaces */
+ meta_window_change_workspace (move_window, workspace);
+
+ if (focus_this)
+ {
+ meta_window_focus (focus_this, timestamp);
+ meta_window_raise (focus_this);
+ }
+ else if (move_window)
+ {
+ meta_window_raise (move_window);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_FOCUS, "Focusing default window on new workspace\n");
+ meta_workspace_focus_default_window (workspace, NULL, timestamp);
+ }
+}
+
+void
+meta_workspace_activate (MetaWorkspace *workspace,
+ guint32 timestamp)
+{
+ meta_workspace_activate_with_focus (workspace, NULL, timestamp);
+}
+
+int
+meta_workspace_index (MetaWorkspace *workspace)
+{
+ int ret;
+
+ ret = g_list_index (workspace->screen->workspaces, workspace);
+
+ if (ret < 0)
+ meta_bug ("Workspace does not exist to index!\n");
+
+ return ret;
+}
+
+/* get windows contained on workspace, including workspace->windows
+ * and also sticky windows.
+ */
+GList*
+meta_workspace_list_windows (MetaWorkspace *workspace)
+{
+ GSList *display_windows;
+ GSList *tmp;
+ GList *workspace_windows;
+
+ display_windows = meta_display_list_windows (workspace->screen->display);
+
+ workspace_windows = NULL;
+ tmp = display_windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *window = tmp->data;
+
+ if (meta_window_located_on_workspace (window, workspace))
+ workspace_windows = g_list_prepend (workspace_windows,
+ window);
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (display_windows);
+
+ return workspace_windows;
+}
+
+static void
+set_active_space_hint (MetaScreen *screen)
+{
+ unsigned long data[1];
+
+ /* this is because we destroy the spaces in order,
+ * so we always end up setting a current desktop of
+ * 0 when closing a screen, so lose the current desktop
+ * on restart. By doing this we keep the current
+ * desktop on restart.
+ */
+ if (screen->closing > 0)
+ return;
+
+ data[0] = meta_workspace_index (screen->active_workspace);
+
+ meta_verbose ("Setting _NET_CURRENT_DESKTOP to %lu\n", data[0]);
+
+ meta_error_trap_push (screen->display);
+ XChangeProperty (screen->display->xdisplay, screen->xroot,
+ screen->display->atom__NET_CURRENT_DESKTOP,
+ XA_CARDINAL,
+ 32, PropModeReplace, (guchar*) data, 1);
+ meta_error_trap_pop (screen->display, FALSE);
+}
+
+void
+meta_workspace_invalidate_work_area (MetaWorkspace *workspace)
+{
+ GList *tmp;
+ GList *windows;
+ int i;
+
+ if (workspace->work_areas_invalid)
+ {
+ meta_topic (META_DEBUG_WORKAREA,
+ "Work area for workspace %d is already invalid\n",
+ meta_workspace_index (workspace));
+ return;
+ }
+
+ meta_topic (META_DEBUG_WORKAREA,
+ "Invalidating work area for workspace %d\n",
+ meta_workspace_index (workspace));
+
+ g_free (workspace->work_area_xinerama);
+ workspace->work_area_xinerama = NULL;
+
+ workspace_free_struts (workspace);
+
+ for (i = 0; i < workspace->screen->n_xinerama_infos; i++)
+ meta_rectangle_free_list_and_elements (workspace->xinerama_region[i]);
+ g_free (workspace->xinerama_region);
+ meta_rectangle_free_list_and_elements (workspace->screen_region);
+ meta_rectangle_free_list_and_elements (workspace->screen_edges);
+ meta_rectangle_free_list_and_elements (workspace->xinerama_edges);
+ workspace->xinerama_region = NULL;
+ workspace->screen_region = NULL;
+ workspace->screen_edges = NULL;
+ workspace->xinerama_edges = NULL;
+
+ workspace->work_areas_invalid = TRUE;
+
+ /* redo the size/position constraints on all windows */
+ windows = meta_workspace_list_windows (workspace);
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ meta_window_queue (w, META_QUEUE_MOVE_RESIZE);
+
+ tmp = tmp->next;
+ }
+
+ g_list_free (windows);
+
+ meta_screen_queue_workarea_recalc (workspace->screen);
+}
+
+static void
+ensure_work_areas_validated (MetaWorkspace *workspace)
+{
+ GList *windows;
+ GList *tmp;
+ MetaRectangle work_area;
+ int i; /* C89 absolutely sucks... */
+
+ if (!workspace->work_areas_invalid)
+ return;
+
+ g_assert (workspace->all_struts == NULL);
+ g_assert (workspace->xinerama_region == NULL);
+ g_assert (workspace->screen_region == NULL);
+ g_assert (workspace->screen_edges == NULL);
+ g_assert (workspace->xinerama_edges == NULL);
+
+ /* STEP 1: Get the list of struts */
+ windows = meta_workspace_list_windows (workspace);
+ for (tmp = windows; tmp != NULL; tmp = tmp->next)
+ {
+ MetaWindow *win = tmp->data;
+ GSList *s_iter;
+
+ for (s_iter = win->struts; s_iter != NULL; s_iter = s_iter->next) {
+ MetaStrut *cpy = g_new (MetaStrut, 1);
+ *cpy = *((MetaStrut *)s_iter->data);
+ workspace->all_struts = g_slist_prepend (workspace->all_struts,
+ cpy);
+ }
+ }
+ g_list_free (windows);
+
+ /* STEP 2: Get the maximal/spanning rects for the onscreen and
+ * on-single-xinerama regions
+ */
+ g_assert (workspace->xinerama_region == NULL);
+ g_assert (workspace->screen_region == NULL);
+
+ workspace->xinerama_region = g_new (GList*,
+ workspace->screen->n_xinerama_infos);
+ for (i = 0; i < workspace->screen->n_xinerama_infos; i++)
+ {
+ workspace->xinerama_region[i] =
+ meta_rectangle_get_minimal_spanning_set_for_region (
+ &workspace->screen->xinerama_infos[i].rect,
+ workspace->all_struts);
+ }
+ workspace->screen_region =
+ meta_rectangle_get_minimal_spanning_set_for_region (
+ &workspace->screen->rect,
+ workspace->all_struts);
+
+ /* STEP 3: Get the work areas (region-to-maximize-to) for the screen and
+ * xineramas.
+ */
+ work_area = workspace->screen->rect; /* start with the screen */
+ if (workspace->screen_region == NULL)
+ work_area = meta_rect (0, 0, -1, -1);
+ else
+ meta_rectangle_clip_to_region (workspace->screen_region,
+ FIXED_DIRECTION_NONE,
+ &work_area);
+
+ /* Lots of paranoia checks, forcing work_area_screen to be sane */
+#define MIN_SANE_AREA 100
+ if (work_area.width < MIN_SANE_AREA)
+ {
+ meta_warning ("struts occupy an unusually large percentage of the screen; "
+ "available remaining width = %d < %d",
+ work_area.width, MIN_SANE_AREA);
+ if (work_area.width < 1)
+ {
+ work_area.x = (workspace->screen->rect.width - MIN_SANE_AREA)/2;
+ work_area.width = MIN_SANE_AREA;
+ }
+ else
+ {
+ int amount = (MIN_SANE_AREA - work_area.width)/2;
+ work_area.x -= amount;
+ work_area.width += 2*amount;
+ }
+ }
+ if (work_area.height < MIN_SANE_AREA)
+ {
+ meta_warning ("struts occupy an unusually large percentage of the screen; "
+ "available remaining height = %d < %d",
+ work_area.height, MIN_SANE_AREA);
+ if (work_area.height < 1)
+ {
+ work_area.y = (workspace->screen->rect.height - MIN_SANE_AREA)/2;
+ work_area.height = MIN_SANE_AREA;
+ }
+ else
+ {
+ int amount = (MIN_SANE_AREA - work_area.height)/2;
+ work_area.y -= amount;
+ work_area.height += 2*amount;
+ }
+ }
+ workspace->work_area_screen = work_area;
+ meta_topic (META_DEBUG_WORKAREA,
+ "Computed work area for workspace %d: %d,%d %d x %d\n",
+ meta_workspace_index (workspace),
+ workspace->work_area_screen.x,
+ workspace->work_area_screen.y,
+ workspace->work_area_screen.width,
+ workspace->work_area_screen.height);
+
+ /* Now find the work areas for each xinerama */
+ g_free (workspace->work_area_xinerama);
+ workspace->work_area_xinerama = g_new (MetaRectangle,
+ workspace->screen->n_xinerama_infos);
+
+ for (i = 0; i < workspace->screen->n_xinerama_infos; i++)
+ {
+ work_area = workspace->screen->xinerama_infos[i].rect;
+
+ if (workspace->xinerama_region[i] == NULL)
+ /* FIXME: constraints.c untested with this, but it might be nice for
+ * a screen reader or magnifier.
+ */
+ work_area = meta_rect (work_area.x, work_area.y, -1, -1);
+ else
+ meta_rectangle_clip_to_region (workspace->xinerama_region[i],
+ FIXED_DIRECTION_NONE,
+ &work_area);
+
+ workspace->work_area_xinerama[i] = work_area;
+ meta_topic (META_DEBUG_WORKAREA,
+ "Computed work area for workspace %d "
+ "xinerama %d: %d,%d %d x %d\n",
+ meta_workspace_index (workspace),
+ i,
+ workspace->work_area_xinerama[i].x,
+ workspace->work_area_xinerama[i].y,
+ workspace->work_area_xinerama[i].width,
+ workspace->work_area_xinerama[i].height);
+ }
+
+ /* STEP 4: Make sure the screen_region is nonempty (separate from step 2
+ * since it relies on step 3).
+ */
+ if (workspace->screen_region == NULL)
+ {
+ MetaRectangle *nonempty_region;
+ nonempty_region = g_new (MetaRectangle, 1);
+ *nonempty_region = workspace->work_area_screen;
+ workspace->screen_region = g_list_prepend (NULL, nonempty_region);
+ }
+
+ /* STEP 5: Cache screen and xinerama edges for edge resistance and snapping */
+ g_assert (workspace->screen_edges == NULL);
+ g_assert (workspace->xinerama_edges == NULL);
+ workspace->screen_edges =
+ meta_rectangle_find_onscreen_edges (&workspace->screen->rect,
+ workspace->all_struts);
+ tmp = NULL;
+ for (i = 0; i < workspace->screen->n_xinerama_infos; i++)
+ tmp = g_list_prepend (tmp, &workspace->screen->xinerama_infos[i].rect);
+ workspace->xinerama_edges =
+ meta_rectangle_find_nonintersected_xinerama_edges (tmp,
+ workspace->all_struts);
+ g_list_free (tmp);
+
+ /* We're all done, YAAY! Record that everything has been validated. */
+ workspace->work_areas_invalid = FALSE;
+}
+
+void
+meta_workspace_get_work_area_for_xinerama (MetaWorkspace *workspace,
+ int which_xinerama,
+ MetaRectangle *area)
+{
+ g_assert (which_xinerama >= 0);
+
+ ensure_work_areas_validated (workspace);
+ g_assert (which_xinerama < workspace->screen->n_xinerama_infos);
+
+ *area = workspace->work_area_xinerama[which_xinerama];
+}
+
+void
+meta_workspace_get_work_area_all_xineramas (MetaWorkspace *workspace,
+ MetaRectangle *area)
+{
+ ensure_work_areas_validated (workspace);
+
+ *area = workspace->work_area_screen;
+}
+
+GList*
+meta_workspace_get_onscreen_region (MetaWorkspace *workspace)
+{
+ ensure_work_areas_validated (workspace);
+
+ return workspace->screen_region;
+}
+
+GList*
+meta_workspace_get_onxinerama_region (MetaWorkspace *workspace,
+ int which_xinerama)
+{
+ ensure_work_areas_validated (workspace);
+
+ return workspace->xinerama_region[which_xinerama];
+}
+
+#ifdef WITH_VERBOSE_MODE
+static char *
+meta_motion_direction_to_string (MetaMotionDirection direction)
+{
+ switch (direction)
+ {
+ case META_MOTION_UP:
+ return "Up";
+ case META_MOTION_DOWN:
+ return "Down";
+ case META_MOTION_LEFT:
+ return "Left";
+ case META_MOTION_RIGHT:
+ return "Right";
+ }
+
+ return "Unknown";
+}
+#endif /* WITH_VERBOSE_MODE */
+
+MetaWorkspace*
+meta_workspace_get_neighbor (MetaWorkspace *workspace,
+ MetaMotionDirection direction)
+{
+ MetaWorkspaceLayout layout;
+ int i, current_space, num_workspaces;
+ gboolean ltr;
+
+ current_space = meta_workspace_index (workspace);
+ num_workspaces = meta_screen_get_n_workspaces (workspace->screen);
+ meta_screen_calc_workspace_layout (workspace->screen, num_workspaces,
+ current_space, &layout);
+
+ meta_verbose ("Getting neighbor of %d in direction %s\n",
+ current_space, meta_motion_direction_to_string (direction));
+
+ ltr = meta_ui_get_direction() == META_UI_DIRECTION_LTR;
+
+ switch (direction)
+ {
+ case META_MOTION_LEFT:
+ layout.current_col -= ltr ? 1 : -1;
+ break;
+ case META_MOTION_RIGHT:
+ layout.current_col += ltr ? 1 : -1;
+ break;
+ case META_MOTION_UP:
+ layout.current_row -= 1;
+ break;
+ case META_MOTION_DOWN:
+ layout.current_row += 1;
+ break;
+ }
+
+ if (layout.current_col < 0)
+ layout.current_col = 0;
+ if (layout.current_col >= layout.cols)
+ layout.current_col = layout.cols - 1;
+ if (layout.current_row < 0)
+ layout.current_row = 0;
+ if (layout.current_row >= layout.rows)
+ layout.current_row = layout.rows - 1;
+
+ i = layout.grid[layout.current_row * layout.cols + layout.current_col];
+
+ if (i < 0)
+ i = current_space;
+
+ if (i >= num_workspaces)
+ meta_bug ("calc_workspace_layout left an invalid (too-high) workspace number %d in the grid\n",
+ i);
+
+ meta_verbose ("Neighbor workspace is %d at row %d col %d\n",
+ i, layout.current_row, layout.current_col);
+
+ meta_screen_free_workspace_layout (&layout);
+
+ return meta_screen_get_workspace_by_index (workspace->screen, i);
+}
+
+const char*
+meta_workspace_get_name (MetaWorkspace *workspace)
+{
+ return meta_prefs_get_workspace_name (meta_workspace_index (workspace));
+}
+
+void
+meta_workspace_focus_default_window (MetaWorkspace *workspace,
+ MetaWindow *not_this_one,
+ guint32 timestamp)
+{
+ if (timestamp == CurrentTime)
+ {
+ meta_warning ("CurrentTime used to choose focus window; "
+ "focus window may not be correct.\n");
+ }
+
+
+ if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK ||
+ !workspace->screen->display->mouse_mode)
+ focus_ancestor_or_mru_window (workspace, not_this_one, timestamp);
+ else
+ {
+ MetaWindow * window;
+ window = meta_screen_get_mouse_window (workspace->screen, not_this_one);
+ if (window &&
+ window->type != META_WINDOW_DOCK &&
+ window->type != META_WINDOW_DESKTOP)
+ {
+ if (timestamp == CurrentTime)
+ {
+
+ /* We would like for this to never happen. However, if
+ * it does happen then we kludge since using CurrentTime
+ * can mean ugly race conditions--and we can avoid these
+ * by allowing EnterNotify events (which come with
+ * timestamps) to handle focus.
+ */
+
+ meta_topic (META_DEBUG_FOCUS,
+ "Not focusing mouse window %s because EnterNotify events should handle that\n", window->desc);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing mouse window %s\n", window->desc);
+ meta_window_focus (window, timestamp);
+ }
+
+ if (workspace->screen->display->autoraise_window != window &&
+ meta_prefs_get_auto_raise ())
+ {
+ meta_display_queue_autoraise_callback (workspace->screen->display,
+ window);
+ }
+ }
+ else if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_SLOPPY)
+ focus_ancestor_or_mru_window (workspace, not_this_one, timestamp);
+ else if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_MOUSE)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Setting focus to no_focus_window, since no valid "
+ "window to focus found.\n");
+ meta_display_focus_the_no_focus_window (workspace->screen->display,
+ workspace->screen,
+ timestamp);
+ }
+ }
+}
+
+static gboolean
+record_ancestor (MetaWindow *window,
+ void *data)
+{
+ MetaWindow **result = data;
+
+ *result = window;
+ return FALSE; /* quit with the first ancestor we find */
+}
+
+/* Focus ancestor of not_this_one if there is one, otherwise focus the MRU
+ * window on active workspace
+ */
+static void
+focus_ancestor_or_mru_window (MetaWorkspace *workspace,
+ MetaWindow *not_this_one,
+ guint32 timestamp)
+{
+ MetaWindow *window = NULL;
+ MetaWindow *desktop_window = NULL;
+ GList *tmp;
+
+ if (not_this_one)
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing MRU window excluding %s\n", not_this_one->desc);
+ else
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing MRU window\n");
+
+ /* First, check to see if we need to focus an ancestor of a window */
+ if (not_this_one)
+ {
+ MetaWindow *ancestor;
+ ancestor = NULL;
+ meta_window_foreach_ancestor (not_this_one, record_ancestor, &ancestor);
+ if (ancestor != NULL)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing %s, ancestor of %s\n",
+ ancestor->desc, not_this_one->desc);
+
+ meta_window_focus (ancestor, timestamp);
+
+ /* Also raise the window if in click-to-focus */
+ if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK)
+ meta_window_raise (ancestor);
+
+ return;
+ }
+ }
+
+ /* No ancestor, look for the MRU window */
+ tmp = workspace->mru_list;
+
+ while (tmp)
+ {
+ MetaWindow* tmp_window;
+ tmp_window = ((MetaWindow*) tmp->data);
+ if (tmp_window != not_this_one &&
+ meta_window_showing_on_its_workspace (tmp_window) &&
+ tmp_window->type != META_WINDOW_DOCK &&
+ tmp_window->type != META_WINDOW_DESKTOP)
+ {
+ window = tmp->data;
+ break;
+ }
+ else if (tmp_window != not_this_one &&
+ desktop_window == NULL &&
+ meta_window_showing_on_its_workspace (tmp_window) &&
+ tmp_window->type == META_WINDOW_DESKTOP)
+ {
+ /* Found the most recently used desktop window */
+ desktop_window = tmp_window;
+ }
+
+ tmp = tmp->next;
+ }
+
+ /* If no window was found, default to the MRU desktop-window */
+ if (window == NULL)
+ window = desktop_window;
+
+ if (window)
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing workspace MRU window %s\n", window->desc);
+
+ meta_window_focus (window, timestamp);
+
+ /* Also raise the window if in click-to-focus */
+ if (meta_prefs_get_focus_mode () == META_FOCUS_MODE_CLICK)
+ meta_window_raise (window);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_FOCUS, "No MRU window to focus found; focusing no_focus_window.\n");
+ meta_display_focus_the_no_focus_window (workspace->screen->display,
+ workspace->screen,
+ timestamp);
+ }
+}
diff --git a/src/core/workspace.h b/src/core/workspace.h
new file mode 100644
index 00000000..f4079ebb
--- /dev/null
+++ b/src/core/workspace.h
@@ -0,0 +1,113 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/**
+ * \file workspace.h Workspaces
+ *
+ * A workspace is a set of windows which all live on the same
+ * screen. (You may also see the name "desktop" around the place,
+ * which is the EWMH's name for the same thing.) Only one workspace
+ * of a screen may be active at once; all windows on all other workspaces
+ * are unmapped.
+ */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2004, 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_WORKSPACE_H
+#define META_WORKSPACE_H
+
+#include "window-private.h"
+
+/* Negative to avoid conflicting with real workspace
+ * numbers
+ */
+typedef enum
+{
+ META_MOTION_UP = -1,
+ META_MOTION_DOWN = -2,
+ META_MOTION_LEFT = -3,
+ META_MOTION_RIGHT = -4
+} MetaMotionDirection;
+
+struct _MetaWorkspace
+{
+ MetaScreen *screen;
+
+ GList *windows;
+ GList *mru_list;
+
+ GList *list_containing_self;
+
+ MetaRectangle work_area_screen;
+ MetaRectangle *work_area_xinerama;
+ GList *screen_region;
+ GList **xinerama_region;
+ GList *screen_edges;
+ GList *xinerama_edges;
+ GSList *all_struts;
+ guint work_areas_invalid : 1;
+
+ guint showing_desktop : 1;
+};
+
+MetaWorkspace* meta_workspace_new (MetaScreen *screen);
+void meta_workspace_free (MetaWorkspace *workspace);
+void meta_workspace_add_window (MetaWorkspace *workspace,
+ MetaWindow *window);
+void meta_workspace_remove_window (MetaWorkspace *workspace,
+ MetaWindow *window);
+void meta_workspace_relocate_windows (MetaWorkspace *workspace,
+ MetaWorkspace *new_home);
+void meta_workspace_activate_with_focus (MetaWorkspace *workspace,
+ MetaWindow *focus_this,
+ guint32 timestamp);
+void meta_workspace_activate (MetaWorkspace *workspace,
+ guint32 timestamp);
+int meta_workspace_index (MetaWorkspace *workspace);
+GList* meta_workspace_list_windows (MetaWorkspace *workspace);
+
+void meta_workspace_invalidate_work_area (MetaWorkspace *workspace);
+
+
+void meta_workspace_get_work_area_for_xinerama (MetaWorkspace *workspace,
+ int which_xinerama,
+ MetaRectangle *area);
+void meta_workspace_get_work_area_all_xineramas (MetaWorkspace *workspace,
+ MetaRectangle *area);
+GList* meta_workspace_get_onscreen_region (MetaWorkspace *workspace);
+GList* meta_workspace_get_onxinerama_region (MetaWorkspace *workspace,
+ int which_xinerama);
+void meta_workspace_get_work_area_all_xineramas (MetaWorkspace *workspace,
+ MetaRectangle *area);
+
+void meta_workspace_focus_default_window (MetaWorkspace *workspace,
+ MetaWindow *not_this_one,
+ guint32 timestamp);
+
+MetaWorkspace* meta_workspace_get_neighbor (MetaWorkspace *workspace,
+ MetaMotionDirection direction);
+
+const char* meta_workspace_get_name (MetaWorkspace *workspace);
+
+#endif
+
+
+
+
diff --git a/src/core/xprops.c b/src/core/xprops.c
new file mode 100644
index 00000000..71e9eeae
--- /dev/null
+++ b/src/core/xprops.c
@@ -0,0 +1,1238 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco X property convenience routines */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2002 Red Hat Inc.
+ *
+ * Some trivial property-unpacking code from Xlib:
+ * Copyright 1987, 1988, 1998 The Open Group
+ * Copyright 1988 by Wyse Technology, Inc., San Jose, Ca,
+ * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts,
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/***********************************************************
+Copyright 1988 by Wyse Technology, Inc., San Jose, Ca,
+Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts,
+
+ All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name Digital not be
+used in advertising or publicity pertaining to distribution of the
+software without specific, written prior permission.
+
+DIGITAL AND WYSE DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+EVENT SHALL DIGITAL OR WYSE BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
+******************************************************************/
+
+/*
+
+Copyright 1987, 1988, 1998 The Open Group
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation.
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of The Open Group shall
+not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization
+from The Open Group.
+
+*/
+
+
+#include <config.h>
+#include "xprops.h"
+#include "errors.h"
+#include "util.h"
+#include "async-getprop.h"
+#include "ui.h"
+#include "marco-Xatomtype.h"
+#include <X11/Xatom.h>
+#include <string.h>
+#include "window-private.h"
+
+typedef struct
+{
+ MetaDisplay *display;
+ Window xwindow;
+ Atom xatom;
+ Atom type;
+ int format;
+ unsigned long n_items;
+ unsigned long bytes_after;
+ unsigned char *prop;
+} GetPropertyResults;
+
+static gboolean
+validate_or_free_results (GetPropertyResults *results,
+ int expected_format,
+ Atom expected_type,
+ gboolean must_have_items)
+{
+ char *type_name;
+ char *expected_name;
+ char *prop_name;
+ const char *title;
+ const char *res_class;
+ const char *res_name;
+ MetaWindow *w;
+
+ if (expected_format == results->format &&
+ expected_type == results->type &&
+ (!must_have_items || results->n_items > 0))
+ return TRUE;
+
+ meta_error_trap_push (results->display);
+ type_name = XGetAtomName (results->display->xdisplay, results->type);
+ expected_name = XGetAtomName (results->display->xdisplay, expected_type);
+ prop_name = XGetAtomName (results->display->xdisplay, results->xatom);
+ meta_error_trap_pop (results->display, TRUE);
+
+ w = meta_display_lookup_x_window (results->display, results->xwindow);
+
+ if (w != NULL)
+ {
+ title = w->title;
+ res_class = w->res_class;
+ res_name = w->res_name;
+ }
+ else
+ {
+ title = NULL;
+ res_class = NULL;
+ res_name = NULL;
+ }
+
+ if (title == NULL)
+ title = "unknown";
+
+ if (res_class == NULL)
+ res_class = "unknown";
+
+ if (res_name == NULL)
+ res_name = "unknown";
+
+ meta_warning (_("Window 0x%lx has property %s\nthat was expected to have type %s format %d\nand actually has type %s format %d n_items %d.\nThis is most likely an application bug, not a window manager bug.\nThe window has title=\"%s\" class=\"%s\" name=\"%s\"\n"),
+ results->xwindow,
+ prop_name ? prop_name : "(bad atom)",
+ expected_name ? expected_name : "(bad atom)",
+ expected_format,
+ type_name ? type_name : "(bad atom)",
+ results->format, (int) results->n_items,
+ title, res_class, res_name);
+
+ if (type_name)
+ XFree (type_name);
+ if (expected_name)
+ XFree (expected_name);
+ if (prop_name)
+ XFree (prop_name);
+
+ if (results->prop)
+ {
+ XFree (results->prop);
+ results->prop = NULL;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+get_property (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ Atom req_type,
+ GetPropertyResults *results)
+{
+ results->display = display;
+ results->xwindow = xwindow;
+ results->xatom = xatom;
+ results->prop = NULL;
+ results->n_items = 0;
+ results->type = None;
+ results->bytes_after = 0;
+ results->format = 0;
+
+ meta_error_trap_push_with_return (display);
+ if (XGetWindowProperty (display->xdisplay, xwindow, xatom,
+ 0, G_MAXLONG,
+ False, req_type, &results->type, &results->format,
+ &results->n_items,
+ &results->bytes_after,
+ &results->prop) != Success ||
+ results->type == None)
+ {
+ if (results->prop)
+ XFree (results->prop);
+ meta_error_trap_pop_with_return (display, TRUE);
+ return FALSE;
+ }
+
+ if (meta_error_trap_pop_with_return (display, TRUE) != Success)
+ {
+ if (results->prop)
+ XFree (results->prop);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+atom_list_from_results (GetPropertyResults *results,
+ Atom **atoms_p,
+ int *n_atoms_p)
+{
+ if (!validate_or_free_results (results, 32, XA_ATOM, FALSE))
+ return FALSE;
+
+ *atoms_p = (Atom*) results->prop;
+ *n_atoms_p = results->n_items;
+ results->prop = NULL;
+
+ return TRUE;
+}
+
+gboolean
+meta_prop_get_atom_list (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ Atom **atoms_p,
+ int *n_atoms_p)
+{
+ GetPropertyResults results;
+
+ *atoms_p = NULL;
+ *n_atoms_p = 0;
+
+ if (!get_property (display, xwindow, xatom, XA_ATOM,
+ &results))
+ return FALSE;
+
+ return atom_list_from_results (&results, atoms_p, n_atoms_p);
+}
+
+static gboolean
+cardinal_list_from_results (GetPropertyResults *results,
+ gulong **cardinals_p,
+ int *n_cardinals_p)
+{
+ if (!validate_or_free_results (results, 32, XA_CARDINAL, FALSE))
+ return FALSE;
+
+ *cardinals_p = (gulong*) results->prop;
+ *n_cardinals_p = results->n_items;
+ results->prop = NULL;
+
+ return TRUE;
+}
+
+gboolean
+meta_prop_get_cardinal_list (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ gulong **cardinals_p,
+ int *n_cardinals_p)
+{
+ GetPropertyResults results;
+
+ *cardinals_p = NULL;
+ *n_cardinals_p = 0;
+
+ if (!get_property (display, xwindow, xatom, XA_CARDINAL,
+ &results))
+ return FALSE;
+
+ return cardinal_list_from_results (&results, cardinals_p, n_cardinals_p);
+}
+
+static gboolean
+motif_hints_from_results (GetPropertyResults *results,
+ MotifWmHints **hints_p)
+{
+ int real_size, max_size;
+#define MAX_ITEMS sizeof (MotifWmHints)/sizeof (gulong)
+
+ *hints_p = NULL;
+
+ if (results->type == None || results->n_items <= 0)
+ {
+ meta_verbose ("Motif hints had unexpected type or n_items\n");
+ if (results->prop)
+ {
+ XFree (results->prop);
+ results->prop = NULL;
+ }
+ return FALSE;
+ }
+
+ /* The issue here is that some old crufty code will set a smaller
+ * MotifWmHints than the one we expect, apparently. I'm not sure of
+ * the history behind it. See bug #89841 for example.
+ */
+ *hints_p = ag_Xmalloc (sizeof (MotifWmHints));
+ if (*hints_p == NULL)
+ {
+ if (results->prop)
+ {
+ XFree (results->prop);
+ results->prop = NULL;
+ }
+ return FALSE;
+ }
+ real_size = results->n_items * sizeof (gulong);
+ max_size = MAX_ITEMS * sizeof (gulong);
+ memcpy (*hints_p, results->prop, MIN (real_size, max_size));
+
+ if (results->prop)
+ {
+ XFree (results->prop);
+ results->prop = NULL;
+ }
+
+ return TRUE;
+}
+
+gboolean
+meta_prop_get_motif_hints (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ MotifWmHints **hints_p)
+{
+ GetPropertyResults results;
+
+ *hints_p = NULL;
+
+ if (!get_property (display, xwindow, xatom, AnyPropertyType,
+ &results))
+ return FALSE;
+
+ return motif_hints_from_results (&results, hints_p);
+}
+
+static gboolean
+latin1_string_from_results (GetPropertyResults *results,
+ char **str_p)
+{
+ *str_p = NULL;
+
+ if (!validate_or_free_results (results, 8, XA_STRING, FALSE))
+ return FALSE;
+
+ *str_p = (char*) results->prop;
+ results->prop = NULL;
+
+ return TRUE;
+}
+
+gboolean
+meta_prop_get_latin1_string (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ char **str_p)
+{
+ GetPropertyResults results;
+
+ *str_p = NULL;
+
+ if (!get_property (display, xwindow, xatom, XA_STRING,
+ &results))
+ return FALSE;
+
+ return latin1_string_from_results (&results, str_p);
+}
+
+static gboolean
+utf8_string_from_results (GetPropertyResults *results,
+ char **str_p)
+{
+ *str_p = NULL;
+
+ if (!validate_or_free_results (results, 8,
+ results->display->atom_UTF8_STRING, FALSE))
+ return FALSE;
+
+ if (results->n_items > 0 &&
+ !g_utf8_validate ((gchar *)results->prop, results->n_items, NULL))
+ {
+ char *name;
+
+ name = XGetAtomName (results->display->xdisplay, results->xatom);
+ meta_warning (_("Property %s on window 0x%lx contained invalid UTF-8\n"),
+ name, results->xwindow);
+ meta_XFree (name);
+ XFree (results->prop);
+ results->prop = NULL;
+
+ return FALSE;
+ }
+
+ *str_p = (char*) results->prop;
+ results->prop = NULL;
+
+ return TRUE;
+}
+
+gboolean
+meta_prop_get_utf8_string (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ char **str_p)
+{
+ GetPropertyResults results;
+
+ *str_p = NULL;
+
+ if (!get_property (display, xwindow, xatom,
+ display->atom_UTF8_STRING,
+ &results))
+ return FALSE;
+
+ return utf8_string_from_results (&results, str_p);
+}
+
+/* this one freakishly returns g_malloc memory */
+static gboolean
+utf8_list_from_results (GetPropertyResults *results,
+ char ***str_p,
+ int *n_str_p)
+{
+ int i;
+ int n_strings;
+ char **retval;
+ const char *p;
+
+ *str_p = NULL;
+ *n_str_p = 0;
+
+ if (!validate_or_free_results (results, 8,
+ results->display->atom_UTF8_STRING, FALSE))
+ return FALSE;
+
+ /* I'm not sure this is right, but I'm guessing the
+ * property is nul-separated
+ */
+ i = 0;
+ n_strings = 0;
+ while (i < (int) results->n_items)
+ {
+ if (results->prop[i] == '\0')
+ ++n_strings;
+ ++i;
+ }
+
+ if (results->prop[results->n_items - 1] != '\0')
+ ++n_strings;
+
+ /* we're guaranteed that results->prop has a nul on the end
+ * by XGetWindowProperty
+ */
+
+ retval = g_new0 (char*, n_strings + 1);
+
+ p = (char *)results->prop;
+ i = 0;
+ while (i < n_strings)
+ {
+ if (!g_utf8_validate (p, -1, NULL))
+ {
+ char *name;
+
+ meta_error_trap_push (results->display);
+ name = XGetAtomName (results->display->xdisplay, results->xatom);
+ meta_error_trap_pop (results->display, TRUE);
+ meta_warning (_("Property %s on window 0x%lx contained invalid UTF-8 for item %d in the list\n"),
+ name, results->xwindow, i);
+ meta_XFree (name);
+ meta_XFree (results->prop);
+ results->prop = NULL;
+
+ g_strfreev (retval);
+ return FALSE;
+ }
+
+ retval[i] = g_strdup (p);
+
+ p = p + strlen (p) + 1;
+ ++i;
+ }
+
+ *str_p = retval;
+ *n_str_p = i;
+
+ meta_XFree (results->prop);
+ results->prop = NULL;
+
+ return TRUE;
+}
+
+/* returns g_malloc not Xmalloc memory */
+gboolean
+meta_prop_get_utf8_list (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ char ***str_p,
+ int *n_str_p)
+{
+ GetPropertyResults results;
+
+ *str_p = NULL;
+
+ if (!get_property (display, xwindow, xatom,
+ display->atom_UTF8_STRING,
+ &results))
+ return FALSE;
+
+ return utf8_list_from_results (&results, str_p, n_str_p);
+}
+
+void
+meta_prop_set_utf8_string_hint (MetaDisplay *display,
+ Window xwindow,
+ Atom atom,
+ const char *val)
+{
+ meta_error_trap_push (display);
+ XChangeProperty (display->xdisplay,
+ xwindow, atom,
+ display->atom_UTF8_STRING,
+ 8, PropModeReplace, (guchar*) val, strlen (val));
+ meta_error_trap_pop (display, FALSE);
+}
+
+static gboolean
+window_from_results (GetPropertyResults *results,
+ Window *window_p)
+{
+ if (!validate_or_free_results (results, 32, XA_WINDOW, TRUE))
+ return FALSE;
+
+ *window_p = *(Window*) results->prop;
+ XFree (results->prop);
+ results->prop = NULL;
+
+ return TRUE;
+}
+
+#ifdef HAVE_XSYNC
+static gboolean
+counter_from_results (GetPropertyResults *results,
+ XSyncCounter *counter_p)
+{
+ if (!validate_or_free_results (results, 32,
+ XA_CARDINAL,
+ TRUE))
+ return FALSE;
+
+ *counter_p = *(XSyncCounter*) results->prop;
+ XFree (results->prop);
+ results->prop = NULL;
+
+ return TRUE;
+}
+#endif
+
+gboolean
+meta_prop_get_window (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ Window *window_p)
+{
+ GetPropertyResults results;
+
+ *window_p = None;
+
+ if (!get_property (display, xwindow, xatom, XA_WINDOW,
+ &results))
+ return FALSE;
+
+ return window_from_results (&results, window_p);
+}
+
+gboolean
+meta_prop_get_cardinal (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ gulong *cardinal_p)
+{
+ return meta_prop_get_cardinal_with_atom_type (display, xwindow, xatom,
+ XA_CARDINAL, cardinal_p);
+}
+
+static gboolean
+cardinal_with_atom_type_from_results (GetPropertyResults *results,
+ Atom prop_type,
+ gulong *cardinal_p)
+{
+ if (!validate_or_free_results (results, 32, prop_type, TRUE))
+ return FALSE;
+
+ *cardinal_p = *(gulong*) results->prop;
+ XFree (results->prop);
+ results->prop = NULL;
+
+ return TRUE;
+}
+
+gboolean
+meta_prop_get_cardinal_with_atom_type (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ Atom prop_type,
+ gulong *cardinal_p)
+{
+ GetPropertyResults results;
+
+ *cardinal_p = 0;
+
+ if (!get_property (display, xwindow, xatom, prop_type,
+ &results))
+ return FALSE;
+
+ return cardinal_with_atom_type_from_results (&results, prop_type, cardinal_p);
+}
+
+static gboolean
+text_property_from_results (GetPropertyResults *results,
+ char **utf8_str_p)
+{
+ XTextProperty tp;
+
+ *utf8_str_p = NULL;
+
+ tp.value = results->prop;
+ results->prop = NULL;
+ tp.encoding = results->type;
+ tp.format = results->format;
+ tp.nitems = results->n_items;
+
+ *utf8_str_p = meta_text_property_to_utf8 (results->display->xdisplay,
+ &tp);
+
+ if (tp.value != NULL)
+ XFree (tp.value);
+
+ return *utf8_str_p != NULL;
+}
+
+gboolean
+meta_prop_get_text_property (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ char **utf8_str_p)
+{
+ GetPropertyResults results;
+
+ if (!get_property (display, xwindow, xatom, AnyPropertyType,
+ &results))
+ return FALSE;
+
+ return text_property_from_results (&results, utf8_str_p);
+}
+
+/* From Xmd.h */
+#ifndef cvtINT32toInt
+#if SIZEOF_VOID_P == 8
+#define cvtINT8toInt(val) ((((unsigned int)val) & 0x00000080) ? (((unsigned int)val) | 0xffffffffffffff00) : ((unsigned int)val))
+#define cvtINT16toInt(val) ((((unsigned int)val) & 0x00008000) ? (((unsigned int)val) | 0xffffffffffff0000) : ((unsigned int)val))
+#define cvtINT32toInt(val) ((((unsigned int)val) & 0x80000000) ? (((unsigned int)val) | 0xffffffff00000000) : ((unsigned int)val))
+#define cvtINT8toShort(val) cvtINT8toInt(val)
+#define cvtINT16toShort(val) cvtINT16toInt(val)
+#define cvtINT32toShort(val) cvtINT32toInt(val)
+#define cvtINT8toLong(val) cvtINT8toInt(val)
+#define cvtINT16toLong(val) cvtINT16toInt(val)
+#define cvtINT32toLong(val) cvtINT32toInt(val)
+#else
+#define cvtINT8toInt(val) (val)
+#define cvtINT16toInt(val) (val)
+#define cvtINT32toInt(val) (val)
+#define cvtINT8toShort(val) (val)
+#define cvtINT16toShort(val) (val)
+#define cvtINT32toShort(val) (val)
+#define cvtINT8toLong(val) (val)
+#define cvtINT16toLong(val) (val)
+#define cvtINT32toLong(val) (val)
+#endif /* SIZEOF_VOID_P == 8 */
+#endif /* cvtINT32toInt() */
+
+static gboolean
+wm_hints_from_results (GetPropertyResults *results,
+ XWMHints **hints_p)
+{
+ XWMHints *hints;
+ xPropWMHints *raw;
+
+ *hints_p = NULL;
+
+ if (!validate_or_free_results (results, 32, XA_WM_HINTS, TRUE))
+ return FALSE;
+
+ /* pre-R3 bogusly truncated window_group, don't fail on them */
+ if (results->n_items < (NumPropWMHintsElements - 1))
+ {
+ meta_verbose ("WM_HINTS property too short: %d should be %d\n",
+ (int) results->n_items, NumPropWMHintsElements - 1);
+ if (results->prop)
+ {
+ XFree (results->prop);
+ results->prop = NULL;
+ }
+ return FALSE;
+ }
+
+ hints = ag_Xmalloc0 (sizeof (XWMHints));
+
+ raw = (xPropWMHints*) results->prop;
+
+ hints->flags = raw->flags;
+ hints->input = (raw->input ? True : False);
+ hints->initial_state = cvtINT32toInt (raw->initialState);
+ hints->icon_pixmap = raw->iconPixmap;
+ hints->icon_window = raw->iconWindow;
+ hints->icon_x = cvtINT32toInt (raw->iconX);
+ hints->icon_y = cvtINT32toInt (raw->iconY);
+ hints->icon_mask = raw->iconMask;
+ if (results->n_items >= NumPropWMHintsElements)
+ hints->window_group = raw->windowGroup;
+ else
+ hints->window_group = 0;
+
+ if (results->prop)
+ {
+ XFree (results->prop);
+ results->prop = NULL;
+ }
+
+ *hints_p = hints;
+
+ return TRUE;
+}
+
+gboolean
+meta_prop_get_wm_hints (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ XWMHints **hints_p)
+{
+ GetPropertyResults results;
+
+ *hints_p = NULL;
+
+ if (!get_property (display, xwindow, xatom, XA_WM_HINTS,
+ &results))
+ return FALSE;
+
+ return wm_hints_from_results (&results, hints_p);
+}
+
+static gboolean
+class_hint_from_results (GetPropertyResults *results,
+ XClassHint *class_hint)
+{
+ int len_name, len_class;
+
+ class_hint->res_class = NULL;
+ class_hint->res_name = NULL;
+
+ if (!validate_or_free_results (results, 8, XA_STRING, FALSE))
+ return FALSE;
+
+ len_name = strlen ((char *) results->prop);
+ if (! (class_hint->res_name = ag_Xmalloc (len_name+1)))
+ {
+ XFree (results->prop);
+ results->prop = NULL;
+ return FALSE;
+ }
+
+ strcpy (class_hint->res_name, (char *)results->prop);
+
+ if (len_name == (int) results->n_items)
+ len_name--;
+
+ len_class = strlen ((char *)results->prop + len_name + 1);
+
+ if (! (class_hint->res_class = ag_Xmalloc(len_class+1)))
+ {
+ XFree(class_hint->res_name);
+ class_hint->res_name = NULL;
+ XFree (results->prop);
+ results->prop = NULL;
+ return FALSE;
+ }
+
+ strcpy (class_hint->res_class, (char *)results->prop + len_name + 1);
+
+ XFree (results->prop);
+ results->prop = NULL;
+
+ return TRUE;
+}
+
+gboolean
+meta_prop_get_class_hint (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ XClassHint *class_hint)
+{
+ GetPropertyResults results;
+
+ class_hint->res_class = NULL;
+ class_hint->res_name = NULL;
+
+ if (!get_property (display, xwindow, xatom, XA_STRING,
+ &results))
+ return FALSE;
+
+ return class_hint_from_results (&results, class_hint);
+}
+
+static gboolean
+size_hints_from_results (GetPropertyResults *results,
+ XSizeHints **hints_p,
+ gulong *flags_p)
+{
+ xPropSizeHints *raw;
+ XSizeHints *hints;
+
+ *hints_p = NULL;
+ *flags_p = 0;
+
+ if (!validate_or_free_results (results, 32, XA_WM_SIZE_HINTS, FALSE))
+ return FALSE;
+
+ if (results->n_items < OldNumPropSizeElements)
+ return FALSE;
+
+ raw = (xPropSizeHints*) results->prop;
+
+ hints = ag_Xmalloc (sizeof (XSizeHints));
+
+ /* XSizeHints misdeclares these as int instead of long */
+ hints->flags = raw->flags;
+ hints->x = cvtINT32toInt (raw->x);
+ hints->y = cvtINT32toInt (raw->y);
+ hints->width = cvtINT32toInt (raw->width);
+ hints->height = cvtINT32toInt (raw->height);
+ hints->min_width = cvtINT32toInt (raw->minWidth);
+ hints->min_height = cvtINT32toInt (raw->minHeight);
+ hints->max_width = cvtINT32toInt (raw->maxWidth);
+ hints->max_height = cvtINT32toInt (raw->maxHeight);
+ hints->width_inc = cvtINT32toInt (raw->widthInc);
+ hints->height_inc = cvtINT32toInt (raw->heightInc);
+ hints->min_aspect.x = cvtINT32toInt (raw->minAspectX);
+ hints->min_aspect.y = cvtINT32toInt (raw->minAspectY);
+ hints->max_aspect.x = cvtINT32toInt (raw->maxAspectX);
+ hints->max_aspect.y = cvtINT32toInt (raw->maxAspectY);
+
+ *flags_p = (USPosition | USSize | PAllHints);
+ if (results->n_items >= NumPropSizeElements)
+ {
+ hints->base_width= cvtINT32toInt (raw->baseWidth);
+ hints->base_height= cvtINT32toInt (raw->baseHeight);
+ hints->win_gravity= cvtINT32toInt (raw->winGravity);
+ *flags_p |= (PBaseSize | PWinGravity);
+ }
+
+ hints->flags &= (*flags_p); /* get rid of unwanted bits */
+
+ XFree (results->prop);
+ results->prop = NULL;
+
+ *hints_p = hints;
+
+ return TRUE;
+}
+
+gboolean
+meta_prop_get_size_hints (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ XSizeHints **hints_p,
+ gulong *flags_p)
+{
+ GetPropertyResults results;
+
+ *hints_p = NULL;
+ *flags_p = 0;
+
+ if (!get_property (display, xwindow, xatom, XA_WM_SIZE_HINTS,
+ &results))
+ return FALSE;
+
+ return size_hints_from_results (&results, hints_p, flags_p);
+}
+
+static AgGetPropertyTask*
+get_task (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ Atom req_type)
+{
+ return ag_task_create (display->xdisplay,
+ xwindow,
+ xatom, 0, G_MAXLONG,
+ False, req_type);
+}
+
+static char*
+latin1_to_utf8 (const char *text)
+{
+ GString *str;
+ const char *p;
+
+ str = g_string_new ("");
+
+ p = text;
+ while (*p)
+ {
+ g_string_append_unichar (str, *p);
+ ++p;
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+void
+meta_prop_get_values (MetaDisplay *display,
+ Window xwindow,
+ MetaPropValue *values,
+ int n_values)
+{
+ int i;
+ AgGetPropertyTask **tasks;
+
+ meta_verbose ("Requesting %d properties of 0x%lx at once\n",
+ n_values, xwindow);
+
+ if (n_values == 0)
+ return;
+
+ tasks = g_new0 (AgGetPropertyTask*, n_values);
+
+ /* Start up tasks. The "values" array can have values
+ * with atom == None, which means to ignore that element.
+ */
+ i = 0;
+ while (i < n_values)
+ {
+ if (values[i].required_type == None)
+ {
+ switch (values[i].type)
+ {
+ case META_PROP_VALUE_INVALID:
+ /* This means we don't really want a value, e.g. got
+ * property notify on an atom we don't care about.
+ */
+ if (values[i].atom != None)
+ meta_bug ("META_PROP_VALUE_INVALID requested in %s\n", G_STRFUNC);
+ break;
+ case META_PROP_VALUE_UTF8_LIST:
+ case META_PROP_VALUE_UTF8:
+ values[i].required_type = display->atom_UTF8_STRING;
+ break;
+ case META_PROP_VALUE_STRING:
+ case META_PROP_VALUE_STRING_AS_UTF8:
+ values[i].required_type = XA_STRING;
+ break;
+ case META_PROP_VALUE_MOTIF_HINTS:
+ values[i].required_type = AnyPropertyType;
+ break;
+ case META_PROP_VALUE_CARDINAL_LIST:
+ case META_PROP_VALUE_CARDINAL:
+ values[i].required_type = XA_CARDINAL;
+ break;
+ case META_PROP_VALUE_WINDOW:
+ values[i].required_type = XA_WINDOW;
+ break;
+ case META_PROP_VALUE_ATOM_LIST:
+ values[i].required_type = XA_ATOM;
+ break;
+ case META_PROP_VALUE_TEXT_PROPERTY:
+ values[i].required_type = AnyPropertyType;
+ break;
+ case META_PROP_VALUE_WM_HINTS:
+ values[i].required_type = XA_WM_HINTS;
+ break;
+ case META_PROP_VALUE_CLASS_HINT:
+ values[i].required_type = XA_STRING;
+ break;
+ case META_PROP_VALUE_SIZE_HINTS:
+ values[i].required_type = XA_WM_SIZE_HINTS;
+ break;
+ case META_PROP_VALUE_SYNC_COUNTER:
+ values[i].required_type = XA_CARDINAL;
+ break;
+ }
+ }
+
+ if (values[i].atom != None)
+ tasks[i] = get_task (display, xwindow,
+ values[i].atom, values[i].required_type);
+
+ ++i;
+ }
+
+ /* Get replies for all our tasks */
+ meta_topic (META_DEBUG_SYNC, "Syncing to get %d GetProperty replies in %s\n",
+ n_values, G_STRFUNC);
+ XSync (display->xdisplay, False);
+
+ /* Collect results, should arrive in order requested */
+ i = 0;
+ while (i < n_values)
+ {
+ AgGetPropertyTask *task;
+ GetPropertyResults results;
+
+ if (tasks[i] == NULL)
+ {
+ /* Probably values[i].type was None, or ag_task_create()
+ * returned NULL.
+ */
+ values[i].type = META_PROP_VALUE_INVALID;
+ goto next;
+ }
+
+ task = ag_get_next_completed_task (display->xdisplay);
+ g_assert (task != NULL);
+ g_assert (ag_task_have_reply (task));
+
+ results.display = display;
+ results.xwindow = xwindow;
+ results.xatom = values[i].atom;
+ results.prop = NULL;
+ results.n_items = 0;
+ results.type = None;
+ results.bytes_after = 0;
+ results.format = 0;
+
+ if (ag_task_get_reply_and_free (task,
+ &results.type, &results.format,
+ &results.n_items,
+ &results.bytes_after,
+ &results.prop) != Success ||
+ results.type == None)
+ {
+ values[i].type = META_PROP_VALUE_INVALID;
+ if (results.prop)
+ {
+ XFree (results.prop);
+ results.prop = NULL;
+ }
+ goto next;
+ }
+
+ switch (values[i].type)
+ {
+ case META_PROP_VALUE_INVALID:
+ g_assert_not_reached ();
+ break;
+ case META_PROP_VALUE_UTF8_LIST:
+ if (!utf8_list_from_results (&results,
+ &values[i].v.string_list.strings,
+ &values[i].v.string_list.n_strings))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_UTF8:
+ if (!utf8_string_from_results (&results,
+ &values[i].v.str))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_STRING:
+ if (!latin1_string_from_results (&results,
+ &values[i].v.str))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_STRING_AS_UTF8:
+ if (!latin1_string_from_results (&results,
+ &values[i].v.str))
+ values[i].type = META_PROP_VALUE_INVALID;
+ else
+ {
+ char *new_str;
+ char *xmalloc_new_str;
+
+ new_str = latin1_to_utf8 (values[i].v.str);
+ xmalloc_new_str = ag_Xmalloc (strlen (new_str) + 1);
+ if (xmalloc_new_str != NULL)
+ {
+ strcpy (xmalloc_new_str, new_str);
+ meta_XFree (values[i].v.str);
+ values[i].v.str = xmalloc_new_str;
+ }
+
+ g_free (new_str);
+ }
+ break;
+ case META_PROP_VALUE_MOTIF_HINTS:
+ if (!motif_hints_from_results (&results,
+ &values[i].v.motif_hints))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_CARDINAL_LIST:
+ if (!cardinal_list_from_results (&results,
+ &values[i].v.cardinal_list.cardinals,
+ &values[i].v.cardinal_list.n_cardinals))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_CARDINAL:
+ if (!cardinal_with_atom_type_from_results (&results,
+ values[i].required_type,
+ &values[i].v.cardinal))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_WINDOW:
+ if (!window_from_results (&results,
+ &values[i].v.xwindow))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_ATOM_LIST:
+ if (!atom_list_from_results (&results,
+ &values[i].v.atom_list.atoms,
+ &values[i].v.atom_list.n_atoms))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_TEXT_PROPERTY:
+ if (!text_property_from_results (&results, &values[i].v.str))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_WM_HINTS:
+ if (!wm_hints_from_results (&results, &values[i].v.wm_hints))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_CLASS_HINT:
+ if (!class_hint_from_results (&results, &values[i].v.class_hint))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_SIZE_HINTS:
+ if (!size_hints_from_results (&results,
+ &values[i].v.size_hints.hints,
+ &values[i].v.size_hints.flags))
+ values[i].type = META_PROP_VALUE_INVALID;
+ break;
+ case META_PROP_VALUE_SYNC_COUNTER:
+#ifdef HAVE_XSYNC
+ if (!counter_from_results (&results,
+ &values[i].v.xcounter))
+ values[i].type = META_PROP_VALUE_INVALID;
+#else
+ values[i].type = META_PROP_VALUE_INVALID;
+ if (results.prop)
+ {
+ XFree (results.prop);
+ results.prop = NULL;
+ }
+#endif
+ break;
+ }
+
+ next:
+ ++i;
+ }
+
+ g_free (tasks);
+}
+
+static void
+free_value (MetaPropValue *value)
+{
+ switch (value->type)
+ {
+ case META_PROP_VALUE_INVALID:
+ break;
+ case META_PROP_VALUE_UTF8:
+ case META_PROP_VALUE_STRING:
+ case META_PROP_VALUE_STRING_AS_UTF8:
+ meta_XFree (value->v.str);
+ break;
+ case META_PROP_VALUE_MOTIF_HINTS:
+ meta_XFree (value->v.motif_hints);
+ break;
+ case META_PROP_VALUE_CARDINAL:
+ break;
+ case META_PROP_VALUE_WINDOW:
+ break;
+ case META_PROP_VALUE_ATOM_LIST:
+ meta_XFree (value->v.atom_list.atoms);
+ break;
+ case META_PROP_VALUE_TEXT_PROPERTY:
+ meta_XFree (value->v.str);
+ break;
+ case META_PROP_VALUE_WM_HINTS:
+ meta_XFree (value->v.wm_hints);
+ break;
+ case META_PROP_VALUE_CLASS_HINT:
+ meta_XFree (value->v.class_hint.res_class);
+ meta_XFree (value->v.class_hint.res_name);
+ break;
+ case META_PROP_VALUE_SIZE_HINTS:
+ meta_XFree (value->v.size_hints.hints);
+ break;
+ case META_PROP_VALUE_UTF8_LIST:
+ g_strfreev (value->v.string_list.strings);
+ break;
+ case META_PROP_VALUE_CARDINAL_LIST:
+ meta_XFree (value->v.cardinal_list.cardinals);
+ break;
+ case META_PROP_VALUE_SYNC_COUNTER:
+ break;
+ }
+}
+
+void
+meta_prop_free_values (MetaPropValue *values,
+ int n_values)
+{
+ int i;
+
+ i = 0;
+ while (i < n_values)
+ {
+ free_value (&values[i]);
+ ++i;
+ }
+
+ /* Zero the whole thing to quickly detect breakage */
+ memset (values, '\0', sizeof (MetaPropValue) * n_values);
+}
diff --git a/src/include/all-keybindings.h b/src/include/all-keybindings.h
new file mode 100644
index 00000000..ee9b5d37
--- /dev/null
+++ b/src/include/all-keybindings.h
@@ -0,0 +1,386 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2008 Thomas Thurman
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/**
+ * A list of screen keybinding information.
+ *
+ * Each action which can have a keystroke bound to it is listed below.
+ * To use this file, define keybind() to be a seven-argument macro (you can
+ * throw any of the arguments you please away), include this file,
+ * and then undefine the macro again.
+ *
+ * (If you aren't familiar with this technique, sometimes called "x-macros",
+ * see DDJ of May 2001: <http://www.ddj.com/cpp/184401387>.)
+ *
+ * This makes it possible to keep all information about all the keybindings
+ * in the same place. The only exception is the code to run when an action
+ * is actually invoked; while we *could* have put that in this file, it would
+ * have made debugging ridiculously difficult. Instead, each action should
+ * have a corresponding static function named handle_<name>() in
+ * keybindings.c.
+ *
+ * The arguments to keybind() are:
+ * 1) the name of the binding; a bareword identifier
+ * (it's fine if it happens to clash with a C reserved word)
+ * 2) the name of the function which implements it.
+ * Clearly we could have guessed this from the binding very often,
+ * but we choose to write it in full for the benefit of grep.
+ * 3) an integer parameter to pass to the handler
+ * 4) a set of boolean flags, ORed together:
+ * BINDING_PER_WINDOW - this is a window-based binding.
+ * It is only valid if there is a
+ * current window, and will operate in
+ * some way on that window.
+ * BINDING_REVERSES - the binding can reverse if you hold down Shift
+ * BINDING_IS_REVERSED - the same, but the senses are reversed from the
+ * handler's point of view (let me know if I should
+ * explain this better)
+ * or 0 if no flag applies.
+ *
+ * 5) a string representing the default binding.
+ * If this is NULL, the action is unbound by default.
+ * Please use NULL and not "disabled".
+ * 6) a short description.
+ * It must be marked translatable (i.e. inside "_(...)").
+ *
+ * Don't try to do XML entity escaping anywhere in the strings.
+ */
+
+#ifndef keybind
+#error "keybind () must be defined when you include screen-bindings.h"
+#endif
+
+/***********************************/
+
+#ifndef _BINDINGS_DEFINED_CONSTANTS
+#define _BINDINGS_DEFINED_CONSTANTS 1
+
+#define BINDING_PER_WINDOW 0x01
+#define BINDING_REVERSES 0x02
+#define BINDING_IS_REVERSED 0x04
+
+#endif /* _BINDINGS_DEFINED_CONSTANTS */
+
+/***********************************/
+
+/* convenience, since in this file they must always be set together */
+#define REVERSES_AND_REVERSED (BINDING_REVERSES | BINDING_IS_REVERSED)
+
+keybind (switch_to_workspace_1, handle_switch_to_workspace, 0, 0, NULL,
+ _("Switch to workspace 1"))
+keybind (switch_to_workspace_2, handle_switch_to_workspace, 1, 0, NULL,
+ _("Switch to workspace 2"))
+keybind (switch_to_workspace_3, handle_switch_to_workspace, 2, 0, NULL,
+ _("Switch to workspace 3"))
+keybind (switch_to_workspace_4, handle_switch_to_workspace, 3, 0, NULL,
+ _("Switch to workspace 4"))
+keybind (switch_to_workspace_5, handle_switch_to_workspace, 4, 0, NULL,
+ _("Switch to workspace 5"))
+keybind (switch_to_workspace_6, handle_switch_to_workspace, 5, 0, NULL,
+ _("Switch to workspace 6"))
+keybind (switch_to_workspace_7, handle_switch_to_workspace, 6, 0, NULL,
+ _("Switch to workspace 7"))
+keybind (switch_to_workspace_8, handle_switch_to_workspace, 7, 0, NULL,
+ _("Switch to workspace 8"))
+keybind (switch_to_workspace_9, handle_switch_to_workspace, 8, 0, NULL,
+ _("Switch to workspace 9"))
+keybind (switch_to_workspace_10, handle_switch_to_workspace, 9, 0, NULL,
+ _("Switch to workspace 10"))
+keybind (switch_to_workspace_11, handle_switch_to_workspace, 10, 0, NULL,
+ _("Switch to workspace 11"))
+keybind (switch_to_workspace_12, handle_switch_to_workspace, 11, 0, NULL,
+ _("Switch to workspace 12"))
+
+/* META_MOTION_* are negative, and so distinct from workspace numbers,
+ * which are always zero or positive.
+ * If you make use of these constants, you will need to include workspace.h
+ * (which you're probably using already for other reasons anyway).
+ * If your definition of keybind() throws them away, you don't need to include
+ * workspace.h, of course.
+ */
+
+keybind (switch_to_workspace_left, handle_switch_to_workspace,
+ META_MOTION_LEFT, 0, "<Control><Alt>Left",
+ _("Switch to workspace on the left of the current workspace"))
+
+keybind (switch_to_workspace_right, handle_switch_to_workspace,
+ META_MOTION_RIGHT, 0, "<Control><Alt>Right",
+ _("Switch to workspace on the right of the current workspace"))
+
+keybind (switch_to_workspace_up, handle_switch_to_workspace,
+ META_MOTION_UP, 0, "<Control><Alt>Up",
+ _("Switch to workspace above the current workspace"))
+
+keybind (switch_to_workspace_down, handle_switch_to_workspace,
+ META_MOTION_DOWN, 0, "<Control><Alt>Down",
+ _("Switch to workspace below the current workspace"))
+
+/***********************************/
+
+/* The ones which have inverses. These can't be bound to any keystroke
+ * containing Shift because Shift will invert their "backward" state.
+ *
+ * TODO: "NORMAL" and "DOCKS" should be renamed to the same name as their
+ * action, for obviousness.
+ *
+ * TODO: handle_switch and handle_cycle should probably really be the
+ * same function checking a bit in the parameter for difference.
+ */
+
+keybind (switch_group, handle_switch, META_TAB_LIST_GROUP,
+ BINDING_REVERSES, NULL,
+ _("Move between windows of an application, using a popup window"))
+keybind (switch_group_backward, handle_switch, META_TAB_LIST_GROUP,
+ REVERSES_AND_REVERSED, NULL,
+ _("Move backward between windows of an application, "
+ "using a popup window"))
+keybind (switch_windows, handle_switch, META_TAB_LIST_NORMAL,
+ BINDING_REVERSES, "<Alt>Tab",
+ _("Move between windows, using a popup window"))
+keybind (switch_windows_backward, handle_switch, META_TAB_LIST_NORMAL,
+ REVERSES_AND_REVERSED, NULL,
+ _("Move backward between windows, using a popup window"))
+keybind (switch_panels, handle_switch, META_TAB_LIST_DOCKS,
+ BINDING_REVERSES, "<Control><Alt>Tab",
+ _("Move between panels and the desktop, using a popup window"))
+keybind (switch_panels_backward, handle_switch, META_TAB_LIST_DOCKS,
+ REVERSES_AND_REVERSED, NULL,
+ _("Move backward between panels and the desktop, "
+ "using a popup window"))
+
+keybind (cycle_group, handle_cycle, META_TAB_LIST_GROUP,
+ BINDING_REVERSES, "<Alt>F6",
+ _("Move between windows of an application immediately"))
+keybind (cycle_group_backward, handle_cycle, META_TAB_LIST_GROUP,
+ REVERSES_AND_REVERSED, NULL,
+ _("Move backward between windows of an application immediately"))
+keybind (cycle_windows, handle_cycle, META_TAB_LIST_NORMAL,
+ BINDING_REVERSES, "<Alt>Escape",
+ _("Move between windows immediately"))
+keybind (cycle_windows_backward, handle_cycle, META_TAB_LIST_NORMAL,
+ REVERSES_AND_REVERSED, NULL,
+ _("Move backward between windows immediately"))
+keybind (cycle_panels, handle_cycle, META_TAB_LIST_DOCKS,
+ BINDING_REVERSES, "<Control><Alt>Escape",
+ _("Move between panels and the desktop immediately"))
+keybind (cycle_panels_backward, handle_cycle, META_TAB_LIST_DOCKS,
+ REVERSES_AND_REVERSED, NULL,
+ _("Move backward between panels and the desktop immediately"))
+
+/***********************************/
+
+keybind (show_desktop, handle_show_desktop, 0, 0, "<Control><Alt>d",
+ _("Hide all normal windows and set focus to the desktop"))
+keybind (panel_main_menu, handle_panel,
+ META_KEYBINDING_ACTION_PANEL_MAIN_MENU, 0, "<Alt>F1",
+ _("Show the panel's main menu"))
+keybind (panel_run_dialog, handle_panel,
+ META_KEYBINDING_ACTION_PANEL_RUN_DIALOG, 0, "<Alt>F2",
+ _("Show the panel's \"Run Application\" dialog box"))
+
+/* Yes, the param is offset by one. Historical reasons. (Maybe worth fixing
+ * at some point.) The description is NULL here because the stanza is
+ * irregularly shaped in marco.schemas.in. This will probably be fixed
+ * as well.
+ */
+keybind (run_command_1, handle_run_command, 0, 0, NULL, NULL)
+keybind (run_command_2, handle_run_command, 1, 0, NULL, NULL)
+keybind (run_command_3, handle_run_command, 2, 0, NULL, NULL)
+keybind (run_command_4, handle_run_command, 3, 0, NULL, NULL)
+keybind (run_command_5, handle_run_command, 4, 0, NULL, NULL)
+keybind (run_command_6, handle_run_command, 5, 0, NULL, NULL)
+keybind (run_command_7, handle_run_command, 6, 0, NULL, NULL)
+keybind (run_command_8, handle_run_command, 7, 0, NULL, NULL)
+keybind (run_command_9, handle_run_command, 8, 0, NULL, NULL)
+keybind (run_command_10, handle_run_command, 9, 0, NULL, NULL)
+keybind (run_command_11, handle_run_command, 10, 0, NULL, NULL)
+keybind (run_command_12, handle_run_command, 11, 0, NULL, NULL)
+keybind (run_command_13, handle_run_command, 12, 0, NULL, NULL)
+keybind (run_command_14, handle_run_command, 13, 0, NULL, NULL)
+keybind (run_command_15, handle_run_command, 14, 0, NULL, NULL)
+keybind (run_command_16, handle_run_command, 15, 0, NULL, NULL)
+keybind (run_command_17, handle_run_command, 16, 0, NULL, NULL)
+keybind (run_command_18, handle_run_command, 17, 0, NULL, NULL)
+keybind (run_command_19, handle_run_command, 18, 0, NULL, NULL)
+keybind (run_command_20, handle_run_command, 19, 0, NULL, NULL)
+keybind (run_command_21, handle_run_command, 20, 0, NULL, NULL)
+keybind (run_command_22, handle_run_command, 21, 0, NULL, NULL)
+keybind (run_command_23, handle_run_command, 22, 0, NULL, NULL)
+keybind (run_command_24, handle_run_command, 23, 0, NULL, NULL)
+keybind (run_command_25, handle_run_command, 24, 0, NULL, NULL)
+keybind (run_command_26, handle_run_command, 25, 0, NULL, NULL)
+keybind (run_command_27, handle_run_command, 26, 0, NULL, NULL)
+keybind (run_command_28, handle_run_command, 27, 0, NULL, NULL)
+keybind (run_command_29, handle_run_command, 28, 0, NULL, NULL)
+keybind (run_command_30, handle_run_command, 29, 0, NULL, NULL)
+keybind (run_command_31, handle_run_command, 30, 0, NULL, NULL)
+keybind (run_command_32, handle_run_command, 31, 0, NULL, NULL)
+
+keybind (run_command_screenshot, handle_run_command, 32, 0, "Print",
+ _("Take a screenshot"))
+keybind (run_command_window_screenshot, handle_run_command, 33, 0,"<Alt>Print",
+ _("Take a screenshot of a window"))
+
+keybind (run_command_terminal, handle_run_terminal, 0, 0, NULL, _("Run a terminal"))
+
+/* No description because this is undocumented */
+keybind (set_spew_mark, handle_set_spew_mark, 0, 0, NULL, NULL)
+
+#undef REVERSES_AND_REVERSED
+
+/************************ PER WINDOW BINDINGS ************************/
+
+/* These take a window as an extra parameter; they have no effect
+ * if no window is active.
+ */
+
+keybind (activate_window_menu, handle_activate_window_menu, 0,
+ BINDING_PER_WINDOW, "<Alt>space",
+ _("Activate the window menu"))
+keybind (toggle_fullscreen, handle_toggle_fullscreen, 0, BINDING_PER_WINDOW,
+ NULL,
+ _("Toggle fullscreen mode"))
+keybind (toggle_maximized, handle_toggle_maximized, 0, BINDING_PER_WINDOW, "<Alt>F10",
+ _("Toggle maximization state"))
+keybind (toggle_above, handle_toggle_above, 0, BINDING_PER_WINDOW, NULL,
+ _("Toggle whether a window will always be visible over other windows"))
+keybind (maximize, handle_maximize, 0, BINDING_PER_WINDOW, NULL,
+ _("Maximize window"))
+keybind (unmaximize, handle_unmaximize, 0, BINDING_PER_WINDOW, "<Alt>F5",
+ _("Restore window"))
+keybind (toggle_shaded, handle_toggle_shaded, 0, BINDING_PER_WINDOW, NULL,
+ _("Toggle shaded state"))
+keybind (minimize, handle_minimize, 0, BINDING_PER_WINDOW, "<Alt>F9",
+ _("Minimize window"))
+keybind (close, handle_close, 0, BINDING_PER_WINDOW, "<Alt>F4",
+ _("Close window"))
+keybind (begin_move, handle_begin_move, 0, BINDING_PER_WINDOW, "<Alt>F7",
+ _("Move window"))
+keybind (begin_resize, handle_begin_resize, 0, BINDING_PER_WINDOW, "<Alt>F8",
+ _("Resize window"))
+keybind (toggle_on_all_workspaces, handle_toggle_on_all_workspaces, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Toggle whether window is on all workspaces or just one"))
+
+keybind (move_to_workspace_1, handle_move_to_workspace, 0, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 1"))
+keybind (move_to_workspace_2, handle_move_to_workspace, 1, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 2"))
+keybind (move_to_workspace_3, handle_move_to_workspace, 2, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 3"))
+keybind (move_to_workspace_4, handle_move_to_workspace, 3, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 4"))
+keybind (move_to_workspace_5, handle_move_to_workspace, 4, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 5"))
+keybind (move_to_workspace_6, handle_move_to_workspace, 5, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 6"))
+keybind (move_to_workspace_7, handle_move_to_workspace, 6, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 7"))
+keybind (move_to_workspace_8, handle_move_to_workspace, 7, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 8"))
+keybind (move_to_workspace_9, handle_move_to_workspace, 8, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 9"))
+keybind (move_to_workspace_10, handle_move_to_workspace, 9, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 10"))
+keybind (move_to_workspace_11, handle_move_to_workspace, 10, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 11"))
+keybind (move_to_workspace_12, handle_move_to_workspace, 11, BINDING_PER_WINDOW,
+ NULL,
+ _("Move window to workspace 12"))
+
+/* META_MOTION_* are negative, and so distinct from workspace numbers,
+ * which are always zero or positive.
+ * If you make use of these constants, you will need to include workspace.h
+ * (which you're probably using already for other reasons anyway).
+ * If your definition of keybind() throws them away, you don't need to include
+ * workspace.h, of course.
+ */
+
+keybind (move_to_workspace_left, handle_move_to_workspace,
+ META_MOTION_LEFT, BINDING_PER_WINDOW, "<Control><Shift><Alt>Left",
+ _("Move window one workspace to the left"))
+keybind (move_to_workspace_right, handle_move_to_workspace,
+ META_MOTION_RIGHT, BINDING_PER_WINDOW, "<Control><Shift><Alt>Right",
+ _("Move window one workspace to the right"))
+keybind (move_to_workspace_up, handle_move_to_workspace,
+ META_MOTION_UP, BINDING_PER_WINDOW, "<Control><Shift><Alt>Up",
+ _("Move window one workspace up"))
+keybind (move_to_workspace_down, handle_move_to_workspace,
+ META_MOTION_DOWN, BINDING_PER_WINDOW, "<Control><Shift><Alt>Down",
+ _("Move window one workspace down"))
+
+keybind (raise_or_lower, handle_raise_or_lower, 0, BINDING_PER_WINDOW, NULL,
+ _("Raise window if it's covered by another window, otherwise lower it"))
+keybind (raise, handle_raise, 0, BINDING_PER_WINDOW, NULL,
+ _("Raise window above other windows"))
+keybind (lower, handle_lower, 0, BINDING_PER_WINDOW, NULL,
+ _("Lower window below other windows"))
+
+keybind (maximize_vertically, handle_maximize_vertically, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Maximize window vertically"))
+
+keybind (maximize_horizontally, handle_maximize_horizontally, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Maximize window horizontally"))
+
+keybind (move_to_corner_nw, handle_move_to_corner_nw, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Move window to north-west (top left) corner"))
+keybind (move_to_corner_ne, handle_move_to_corner_ne, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Move window to north-east (top right) corner"))
+keybind (move_to_corner_sw, handle_move_to_corner_sw, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Move window to south-west (bottom left) corner"))
+keybind (move_to_corner_se, handle_move_to_corner_se, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Move window to south-east (bottom right) corner"))
+
+keybind (move_to_side_n, handle_move_to_side_n, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Move window to north (top) side of screen"))
+keybind (move_to_side_s, handle_move_to_side_s, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Move window to south (bottom) side of screen"))
+keybind (move_to_side_e, handle_move_to_side_e, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Move window to east (right) side of screen"))
+keybind (move_to_side_w, handle_move_to_side_w, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Move window to west (left) side of screen"))
+keybind (move_to_center, handle_move_to_center, 0,
+ BINDING_PER_WINDOW, NULL,
+ _("Move window to center of screen"))
+
+/* eof all-keybindings.h */
+
diff --git a/src/include/boxes.h b/src/include/boxes.h
new file mode 100644
index 00000000..5c76ed6b
--- /dev/null
+++ b/src/include/boxes.h
@@ -0,0 +1,290 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Simple box operations */
+
+/*
+ * Copyright (C) 2005, 2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_BOXES_H
+#define META_BOXES_H
+
+#include <glib.h>
+#include "common.h"
+
+typedef struct _MetaRectangle MetaRectangle;
+struct _MetaRectangle
+{
+ int x;
+ int y;
+ int width;
+ int height;
+};
+
+typedef struct _MetaStrut MetaStrut;
+struct _MetaStrut
+{
+ MetaRectangle rect;
+ MetaSide side;
+};
+
+#define BOX_LEFT(box) ((box).x) /* Leftmost pixel of rect */
+#define BOX_RIGHT(box) ((box).x + (box).width) /* One pixel past right */
+#define BOX_TOP(box) ((box).y) /* Topmost pixel of rect */
+#define BOX_BOTTOM(box) ((box).y + (box).height) /* One pixel past bottom */
+
+typedef enum
+{
+ FIXED_DIRECTION_NONE = 0,
+ FIXED_DIRECTION_X = 1 << 0,
+ FIXED_DIRECTION_Y = 1 << 1,
+} FixedDirections;
+
+typedef enum
+{
+ META_EDGE_WINDOW,
+ META_EDGE_XINERAMA,
+ META_EDGE_SCREEN
+} MetaEdgeType;
+
+typedef struct _MetaEdge MetaEdge;
+struct _MetaEdge
+{
+ MetaRectangle rect; /* width or height should be 1 */
+ MetaSide side_type;
+ MetaEdgeType edge_type;
+};
+
+/* Output functions -- note that the output buffer had better be big enough:
+ * rect_to_string: RECT_LENGTH
+ * region_to_string: (RECT_LENGTH+strlen(separator_string)) *
+ * g_list_length (region)
+ * edge_to_string: EDGE_LENGTH
+ * edge_list_to_...: (EDGE_LENGTH+strlen(separator_string)) *
+ * g_list_length (edge_list)
+ */
+#define RECT_LENGTH 27
+#define EDGE_LENGTH 37
+char* meta_rectangle_to_string (const MetaRectangle *rect,
+ char *output);
+char* meta_rectangle_region_to_string (GList *region,
+ const char *separator_string,
+ char *output);
+char* meta_rectangle_edge_to_string (const MetaEdge *edge,
+ char *output);
+char* meta_rectangle_edge_list_to_string (
+ GList *edge_list,
+ const char *separator_string,
+ char *output);
+
+/* Function to make initializing a rect with a single line of code easy */
+MetaRectangle meta_rect (int x, int y, int width, int height);
+
+/* Basic comparison functions */
+int meta_rectangle_area (const MetaRectangle *rect);
+gboolean meta_rectangle_intersect (const MetaRectangle *src1,
+ const MetaRectangle *src2,
+ MetaRectangle *dest);
+gboolean meta_rectangle_equal (const MetaRectangle *src1,
+ const MetaRectangle *src2);
+
+/* Find the bounding box of the union of two rectangles */
+void meta_rectangle_union (const MetaRectangle *rect1,
+ const MetaRectangle *rect2,
+ MetaRectangle *dest);
+
+/* overlap is similar to intersect but doesn't provide location of
+ * intersection information.
+ */
+gboolean meta_rectangle_overlap (const MetaRectangle *rect1,
+ const MetaRectangle *rect2);
+
+/* vert_overlap means ignore the horizontal location and ask if the
+ * vertical parts overlap. An alternate way to think of it is "Does there
+ * exist a way to shift either rect horizontally so that the two rects
+ * overlap?" horiz_overlap is similar.
+ */
+gboolean meta_rectangle_vert_overlap (const MetaRectangle *rect1,
+ const MetaRectangle *rect2);
+gboolean meta_rectangle_horiz_overlap (const MetaRectangle *rect1,
+ const MetaRectangle *rect2);
+
+/* could_fit_rect determines whether "outer_rect" is big enough to contain
+ * inner_rect. contains_rect checks whether it actually contains it.
+ */
+gboolean meta_rectangle_could_fit_rect (const MetaRectangle *outer_rect,
+ const MetaRectangle *inner_rect);
+gboolean meta_rectangle_contains_rect (const MetaRectangle *outer_rect,
+ const MetaRectangle *inner_rect);
+
+/* Resize old_rect to the given new_width and new_height, but store the
+ * result in rect. NOTE THAT THIS IS RESIZE ONLY SO IT CANNOT BE USED FOR
+ * A MOVERESIZE OPERATION (that simplies the routine a little bit as it
+ * means there's no difference between NorthWestGravity and StaticGravity.
+ * Also, I lied a little bit--technically, you could use it in a MoveResize
+ * operation if you muck with old_rect just right).
+ */
+void meta_rectangle_resize_with_gravity (const MetaRectangle *old_rect,
+ MetaRectangle *rect,
+ int gravity,
+ int new_width,
+ int new_height);
+
+/* find a list of rectangles with the property that a window is contained
+ * in the given region if and only if it is contained in one of the
+ * rectangles in the list.
+ *
+ * In this case, the region is given by taking basic_rect, removing from
+ * it the intersections with all the rectangles in the all_struts list,
+ * then expanding all the rectangles in the resulting list by the given
+ * amounts on each side.
+ *
+ * See boxes.c for more details.
+ */
+GList* meta_rectangle_get_minimal_spanning_set_for_region (
+ const MetaRectangle *basic_rect,
+ const GSList *all_struts);
+
+/* Expand all rectangles in region by the given amount on each side */
+GList* meta_rectangle_expand_region (GList *region,
+ const int left_expand,
+ const int right_expand,
+ const int top_expand,
+ const int bottom_expand);
+/* Same as for meta_rectangle_expand_region except that rectangles not at
+ * least min_x or min_y in size are not expanded in that direction
+ */
+GList* meta_rectangle_expand_region_conditionally (
+ GList *region,
+ const int left_expand,
+ const int right_expand,
+ const int top_expand,
+ const int bottom_expand,
+ const int min_x,
+ const int min_y);
+
+/* Expand rect in direction to the size of expand_to, and then clip out any
+ * overlapping struts oriented orthognal to the expansion direction. (Think
+ * horizontal or vertical maximization)
+ */
+void meta_rectangle_expand_to_avoiding_struts (
+ MetaRectangle *rect,
+ const MetaRectangle *expand_to,
+ const MetaDirection direction,
+ const GSList *all_struts);
+
+/* Free the list created by
+ * meta_rectangle_get_minimal_spanning_set_for_region()
+ * or
+ * meta_rectangle_find_onscreen_edges ()
+ * or
+ * meta_rectangle_find_nonintersected_xinerama_edges()
+ */
+void meta_rectangle_free_list_and_elements (GList *filled_list);
+
+/* could_fit_in_region determines whether one of the spanning_rects is
+ * big enough to contain rect. contained_in_region checks whether one
+ * actually contains it.
+ */
+gboolean meta_rectangle_could_fit_in_region (
+ const GList *spanning_rects,
+ const MetaRectangle *rect);
+gboolean meta_rectangle_contained_in_region (
+ const GList *spanning_rects,
+ const MetaRectangle *rect);
+gboolean meta_rectangle_overlaps_with_region (
+ const GList *spanning_rects,
+ const MetaRectangle *rect);
+
+/* Make the rectangle small enough to fit into one of the spanning_rects,
+ * but make it no smaller than min_size.
+ */
+void meta_rectangle_clamp_to_fit_into_region (
+ const GList *spanning_rects,
+ FixedDirections fixed_directions,
+ MetaRectangle *rect,
+ const MetaRectangle *min_size);
+
+/* Clip the rectangle so that it fits into one of the spanning_rects, assuming
+ * it overlaps with at least one of them
+ */
+void meta_rectangle_clip_to_region (const GList *spanning_rects,
+ FixedDirections fixed_directions,
+ MetaRectangle *rect);
+
+/* Shove the rectangle into one of the spanning_rects, assuming it fits in
+ * one of them.
+ */
+void meta_rectangle_shove_into_region(
+ const GList *spanning_rects,
+ FixedDirections fixed_directions,
+ MetaRectangle *rect);
+
+/* Finds the point on the line connecting (x1,y1) to (x2,y2) which is closest
+ * to (px, py). Useful for finding an optimal rectangle size when given a
+ * range between two sizes that are all candidates.
+ */
+void meta_rectangle_find_linepoint_closest_to_point (double x1, double y1,
+ double x2, double y2,
+ double px, double py,
+ double *valx, double *valy);
+
+/***************************************************************************/
+/* */
+/* Switching gears to code for edges instead of just rectangles */
+/* */
+/***************************************************************************/
+
+/* Return whether an edge overlaps or is adjacent to the rectangle in the
+ * nonzero-width dimension of the edge.
+ */
+gboolean meta_rectangle_edge_aligns (const MetaRectangle *rect,
+ const MetaEdge *edge);
+
+/* Compare two edges, so that sorting functions can put a list of edges in
+ * canonical order.
+ */
+gint meta_rectangle_edge_cmp (gconstpointer a, gconstpointer b);
+
+/* Compare two edges, so that sorting functions can put a list of edges in
+ * order. This function doesn't separate left edges first, then right edges,
+ * etc., but rather compares only upon location.
+ */
+gint meta_rectangle_edge_cmp_ignore_type (gconstpointer a, gconstpointer b);
+
+/* Removes an parts of edges in the given list that intersect any box in the
+ * given rectangle list. Returns the result.
+ */
+GList* meta_rectangle_remove_intersections_with_boxes_from_edges (
+ GList *edges,
+ const GSList *rectangles);
+
+/* Finds all the edges of an onscreen region, returning a GList* of
+ * MetaEdgeRect's.
+ */
+GList* meta_rectangle_find_onscreen_edges (const MetaRectangle *basic_rect,
+ const GSList *all_struts);
+
+/* Finds edges between adjacent xineramas which are not covered by the given
+ * struts.
+ */
+GList* meta_rectangle_find_nonintersected_xinerama_edges (
+ const GList *xinerama_rects,
+ const GSList *all_struts);
+
+#endif /* META_BOXES_H */
diff --git a/src/include/common.h b/src/include/common.h
new file mode 100644
index 00000000..ccb592f6
--- /dev/null
+++ b/src/include/common.h
@@ -0,0 +1,300 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco common types shared by core.h and ui.h */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_COMMON_H
+#define META_COMMON_H
+
+/* Don't include GTK or core headers here */
+#include <X11/Xlib.h>
+#include <glib.h>
+
+typedef struct _MetaResizePopup MetaResizePopup;
+
+typedef enum
+{
+ META_FRAME_ALLOWS_DELETE = 1 << 0,
+ META_FRAME_ALLOWS_MENU = 1 << 1,
+ META_FRAME_ALLOWS_MINIMIZE = 1 << 2,
+ META_FRAME_ALLOWS_MAXIMIZE = 1 << 3,
+ META_FRAME_ALLOWS_VERTICAL_RESIZE = 1 << 4,
+ META_FRAME_ALLOWS_HORIZONTAL_RESIZE = 1 << 5,
+ META_FRAME_HAS_FOCUS = 1 << 6,
+ META_FRAME_SHADED = 1 << 7,
+ META_FRAME_STUCK = 1 << 8,
+ META_FRAME_MAXIMIZED = 1 << 9,
+ META_FRAME_ALLOWS_SHADE = 1 << 10,
+ META_FRAME_ALLOWS_MOVE = 1 << 11,
+ META_FRAME_FULLSCREEN = 1 << 12,
+ META_FRAME_IS_FLASHING = 1 << 13,
+ META_FRAME_ABOVE = 1 << 14
+} MetaFrameFlags;
+
+typedef enum
+{
+ META_MENU_OP_NONE = 0,
+ META_MENU_OP_DELETE = 1 << 0,
+ META_MENU_OP_MINIMIZE = 1 << 1,
+ META_MENU_OP_UNMAXIMIZE = 1 << 2,
+ META_MENU_OP_MAXIMIZE = 1 << 3,
+ META_MENU_OP_UNSHADE = 1 << 4,
+ META_MENU_OP_SHADE = 1 << 5,
+ META_MENU_OP_UNSTICK = 1 << 6,
+ META_MENU_OP_STICK = 1 << 7,
+ META_MENU_OP_WORKSPACES = 1 << 8,
+ META_MENU_OP_MOVE = 1 << 9,
+ META_MENU_OP_RESIZE = 1 << 10,
+ META_MENU_OP_ABOVE = 1 << 11,
+ META_MENU_OP_UNABOVE = 1 << 12,
+ META_MENU_OP_MOVE_LEFT = 1 << 13,
+ META_MENU_OP_MOVE_RIGHT = 1 << 14,
+ META_MENU_OP_MOVE_UP = 1 << 15,
+ META_MENU_OP_MOVE_DOWN = 1 << 16,
+ META_MENU_OP_RECOVER = 1 << 17
+} MetaMenuOp;
+
+typedef struct _MetaWindowMenu MetaWindowMenu;
+
+typedef void (* MetaWindowMenuFunc) (MetaWindowMenu *menu,
+ Display *xdisplay,
+ Window client_xwindow,
+ guint32 timestamp,
+ MetaMenuOp op,
+ int workspace,
+ gpointer data);
+
+/* when changing this enum, there are various switch statements
+ * you have to update
+ */
+typedef enum
+{
+ META_GRAB_OP_NONE,
+
+ /* Mouse ops */
+ META_GRAB_OP_MOVING,
+ META_GRAB_OP_RESIZING_SE,
+ META_GRAB_OP_RESIZING_S,
+ META_GRAB_OP_RESIZING_SW,
+ META_GRAB_OP_RESIZING_N,
+ META_GRAB_OP_RESIZING_NE,
+ META_GRAB_OP_RESIZING_NW,
+ META_GRAB_OP_RESIZING_W,
+ META_GRAB_OP_RESIZING_E,
+
+ /* Keyboard ops */
+ META_GRAB_OP_KEYBOARD_MOVING,
+ META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN,
+ META_GRAB_OP_KEYBOARD_RESIZING_S,
+ META_GRAB_OP_KEYBOARD_RESIZING_N,
+ META_GRAB_OP_KEYBOARD_RESIZING_W,
+ META_GRAB_OP_KEYBOARD_RESIZING_E,
+ META_GRAB_OP_KEYBOARD_RESIZING_SE,
+ META_GRAB_OP_KEYBOARD_RESIZING_NE,
+ META_GRAB_OP_KEYBOARD_RESIZING_SW,
+ META_GRAB_OP_KEYBOARD_RESIZING_NW,
+
+ /* Alt+Tab */
+ META_GRAB_OP_KEYBOARD_TABBING_NORMAL,
+ META_GRAB_OP_KEYBOARD_TABBING_DOCK,
+
+ /* Alt+Esc */
+ META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL,
+ META_GRAB_OP_KEYBOARD_ESCAPING_DOCK,
+
+ META_GRAB_OP_KEYBOARD_ESCAPING_GROUP,
+
+ /* Alt+F6 */
+ META_GRAB_OP_KEYBOARD_TABBING_GROUP,
+
+ META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING,
+
+ /* Frame button ops */
+ META_GRAB_OP_CLICKING_MINIMIZE,
+ META_GRAB_OP_CLICKING_MAXIMIZE,
+ META_GRAB_OP_CLICKING_UNMAXIMIZE,
+ META_GRAB_OP_CLICKING_DELETE,
+ META_GRAB_OP_CLICKING_MENU,
+ META_GRAB_OP_CLICKING_SHADE,
+ META_GRAB_OP_CLICKING_UNSHADE,
+ META_GRAB_OP_CLICKING_ABOVE,
+ META_GRAB_OP_CLICKING_UNABOVE,
+ META_GRAB_OP_CLICKING_STICK,
+ META_GRAB_OP_CLICKING_UNSTICK
+} MetaGrabOp;
+
+typedef enum
+{
+ META_CURSOR_DEFAULT,
+ META_CURSOR_NORTH_RESIZE,
+ META_CURSOR_SOUTH_RESIZE,
+ META_CURSOR_WEST_RESIZE,
+ META_CURSOR_EAST_RESIZE,
+ META_CURSOR_SE_RESIZE,
+ META_CURSOR_SW_RESIZE,
+ META_CURSOR_NE_RESIZE,
+ META_CURSOR_NW_RESIZE,
+ META_CURSOR_MOVE_OR_RESIZE_WINDOW,
+ META_CURSOR_BUSY
+
+} MetaCursor;
+
+typedef enum
+{
+ META_FOCUS_MODE_CLICK,
+ META_FOCUS_MODE_SLOPPY,
+ META_FOCUS_MODE_MOUSE
+} MetaFocusMode;
+
+typedef enum
+{
+ META_FOCUS_NEW_WINDOWS_SMART,
+ META_FOCUS_NEW_WINDOWS_STRICT
+} MetaFocusNewWindows;
+
+typedef enum
+{
+ META_ACTION_TITLEBAR_TOGGLE_SHADE,
+ META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE,
+ META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_HORIZONTALLY,
+ META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_VERTICALLY,
+ META_ACTION_TITLEBAR_MINIMIZE,
+ META_ACTION_TITLEBAR_NONE,
+ META_ACTION_TITLEBAR_LOWER,
+ META_ACTION_TITLEBAR_MENU,
+ META_ACTION_TITLEBAR_LAST
+} MetaActionTitlebar;
+
+typedef enum
+{
+ META_FRAME_TYPE_NORMAL,
+ META_FRAME_TYPE_DIALOG,
+ META_FRAME_TYPE_MODAL_DIALOG,
+ META_FRAME_TYPE_UTILITY,
+ META_FRAME_TYPE_MENU,
+ META_FRAME_TYPE_BORDER,
+ META_FRAME_TYPE_LAST
+} MetaFrameType;
+
+typedef enum
+{
+ /* Create gratuitous divergence from regular
+ * X mod bits, to be sure we find bugs
+ */
+ META_VIRTUAL_SHIFT_MASK = 1 << 5,
+ META_VIRTUAL_CONTROL_MASK = 1 << 6,
+ META_VIRTUAL_ALT_MASK = 1 << 7,
+ META_VIRTUAL_META_MASK = 1 << 8,
+ META_VIRTUAL_SUPER_MASK = 1 << 9,
+ META_VIRTUAL_HYPER_MASK = 1 << 10,
+ META_VIRTUAL_MOD2_MASK = 1 << 11,
+ META_VIRTUAL_MOD3_MASK = 1 << 12,
+ META_VIRTUAL_MOD4_MASK = 1 << 13,
+ META_VIRTUAL_MOD5_MASK = 1 << 14
+} MetaVirtualModifier;
+
+/* Relative directions or sides seem to come up all over the place... */
+/* FIXME: Replace
+ * screen.[ch]:MetaScreenDirection,
+ * workspace.[ch]:MetaMotionDirection,
+ * with the use of MetaDirection.
+ */
+typedef enum
+{
+ META_DIRECTION_LEFT = 1 << 0,
+ META_DIRECTION_RIGHT = 1 << 1,
+ META_DIRECTION_TOP = 1 << 2,
+ META_DIRECTION_BOTTOM = 1 << 3,
+
+ /* Some aliases for making code more readable for various circumstances. */
+ META_DIRECTION_UP = META_DIRECTION_TOP,
+ META_DIRECTION_DOWN = META_DIRECTION_BOTTOM,
+
+ /* A few more definitions using aliases */
+ META_DIRECTION_HORIZONTAL = META_DIRECTION_LEFT | META_DIRECTION_RIGHT,
+ META_DIRECTION_VERTICAL = META_DIRECTION_UP | META_DIRECTION_DOWN,
+} MetaDirection;
+
+/* Sometimes we want to talk about sides instead of directions; note
+ * that the values must be as follows or meta_window_update_struts()
+ * won't work. Using these values also is a safety blanket since
+ * MetaDirection used to be used as a side.
+ */
+typedef enum
+{
+ META_SIDE_LEFT = META_DIRECTION_LEFT,
+ META_SIDE_RIGHT = META_DIRECTION_RIGHT,
+ META_SIDE_TOP = META_DIRECTION_TOP,
+ META_SIDE_BOTTOM = META_DIRECTION_BOTTOM
+} MetaSide;
+
+/* Function a window button can have. Note, you can't add stuff here
+ * without extending the theme format to draw a new function and
+ * breaking all existing themes.
+ */
+typedef enum
+{
+ META_BUTTON_FUNCTION_MENU,
+ META_BUTTON_FUNCTION_MINIMIZE,
+ META_BUTTON_FUNCTION_MAXIMIZE,
+ META_BUTTON_FUNCTION_CLOSE,
+ META_BUTTON_FUNCTION_SHADE,
+ META_BUTTON_FUNCTION_ABOVE,
+ META_BUTTON_FUNCTION_STICK,
+ META_BUTTON_FUNCTION_UNSHADE,
+ META_BUTTON_FUNCTION_UNABOVE,
+ META_BUTTON_FUNCTION_UNSTICK,
+ META_BUTTON_FUNCTION_LAST
+} MetaButtonFunction;
+
+#define MAX_BUTTONS_PER_CORNER META_BUTTON_FUNCTION_LAST
+
+typedef struct _MetaButtonLayout MetaButtonLayout;
+struct _MetaButtonLayout
+{
+ /* buttons in the group on the left side */
+ MetaButtonFunction left_buttons[MAX_BUTTONS_PER_CORNER];
+ gboolean left_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
+
+ /* buttons in the group on the right side */
+ MetaButtonFunction right_buttons[MAX_BUTTONS_PER_CORNER];
+ gboolean right_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
+};
+
+/* should investigate changing these to whatever most apps use */
+#define META_ICON_WIDTH 32
+#define META_ICON_HEIGHT 32
+#define META_MINI_ICON_WIDTH 16
+#define META_MINI_ICON_HEIGHT 16
+
+#define META_DEFAULT_ICON_NAME "window"
+
+#define META_PRIORITY_PREFS_NOTIFY (G_PRIORITY_DEFAULT_IDLE + 10)
+#define META_PRIORITY_WORK_AREA_HINT (G_PRIORITY_DEFAULT_IDLE + 15)
+
+#define POINT_IN_RECT(xcoord, ycoord, rect) \
+ ((xcoord) >= (rect).x && \
+ (xcoord) < ((rect).x + (rect).width) && \
+ (ycoord) >= (rect).y && \
+ (ycoord) < ((rect).y + (rect).height))
+
+#endif
diff --git a/src/include/compositor.h b/src/include/compositor.h
new file mode 100644
index 00000000..a7f9bf1f
--- /dev/null
+++ b/src/include/compositor.h
@@ -0,0 +1,76 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2008 Iain Holmes
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_COMPOSITOR_H
+#define META_COMPOSITOR_H
+
+#include <glib.h>
+#include <X11/Xlib.h>
+
+#include "types.h"
+#include "boxes.h"
+
+MetaCompositor *meta_compositor_new (MetaDisplay *display);
+void meta_compositor_destroy (MetaCompositor *compositor);
+
+void meta_compositor_manage_screen (MetaCompositor *compositor,
+ MetaScreen *screen);
+void meta_compositor_unmanage_screen (MetaCompositor *compositor,
+ MetaScreen *screen);
+
+void meta_compositor_add_window (MetaCompositor *compositor,
+ MetaWindow *window,
+ Window xwindow,
+ XWindowAttributes *attrs);
+void meta_compositor_remove_window (MetaCompositor *compositor,
+ Window xwindow);
+
+void meta_compositor_set_updates (MetaCompositor *compositor,
+ MetaWindow *window,
+ gboolean updates);
+
+void meta_compositor_process_event (MetaCompositor *compositor,
+ XEvent *event,
+ MetaWindow *window);
+Pixmap meta_compositor_get_window_pixmap (MetaCompositor *compositor,
+ MetaWindow *window);
+void meta_compositor_set_active_window (MetaCompositor *compositor,
+ MetaScreen *screen,
+ MetaWindow *window);
+
+void meta_compositor_begin_move (MetaCompositor *compositor,
+ MetaWindow *window,
+ MetaRectangle *initial,
+ int grab_x, int grab_y);
+void meta_compositor_update_move (MetaCompositor *compositor,
+ MetaWindow *window,
+ int x, int y);
+void meta_compositor_end_move (MetaCompositor *compositor,
+ MetaWindow *window);
+void meta_compositor_free_window (MetaCompositor *compositor,
+ MetaWindow *window);
+
+#endif
+
+
+
+
+
diff --git a/src/include/core.h b/src/include/core.h
new file mode 100644
index 00000000..ded94c4e
--- /dev/null
+++ b/src/include/core.h
@@ -0,0 +1,208 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco interface used by GTK+ UI to talk to core */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_CORE_H
+#define META_CORE_H
+
+/* Don't include core headers here */
+#include <gdk/gdkx.h>
+#include "common.h"
+
+typedef enum
+{
+ META_CORE_GET_END = 0,
+ META_CORE_WINDOW_HAS_FRAME,
+ META_CORE_GET_CLIENT_WIDTH,
+ META_CORE_GET_CLIENT_HEIGHT,
+ META_CORE_IS_TITLEBAR_ONSCREEN,
+ META_CORE_GET_CLIENT_XWINDOW,
+ META_CORE_GET_FRAME_FLAGS,
+ META_CORE_GET_FRAME_TYPE,
+ META_CORE_GET_MINI_ICON,
+ META_CORE_GET_ICON,
+ META_CORE_GET_X,
+ META_CORE_GET_Y,
+ META_CORE_GET_FRAME_WORKSPACE,
+ META_CORE_GET_FRAME_X,
+ META_CORE_GET_FRAME_Y,
+ META_CORE_GET_FRAME_WIDTH,
+ META_CORE_GET_FRAME_HEIGHT,
+ META_CORE_GET_SCREEN_WIDTH,
+ META_CORE_GET_SCREEN_HEIGHT,
+} MetaCoreGetType;
+
+/* General information function about the given window. Pass in a sequence of
+ * pairs of MetaCoreGetTypes and pointers to variables; the variables will be
+ * filled with the requested values. End the list with META_CORE_GET_END.
+ * For example:
+ *
+ * meta_core_get (my_display, my_window,
+ * META_CORE_GET_X, &x,
+ * META_CORE_GET_Y, &y,
+ * META_CORE_GET_END);
+ *
+ * If the window doesn't have a frame, this will raise a meta_bug. To suppress
+ * this behaviour, ask META_CORE_WINDOW_HAS_FRAME as the *first* question in
+ * the list. If the window has no frame, the answer to this question will be
+ * False, and anything else you asked will be undefined. Otherwise, the answer
+ * will be True. The answer will necessarily be True if you ask the question
+ * in any other position. The positions of all other questions don't matter.
+ *
+ * The reason for this function is that some parts of the program don't know
+ * about MetaWindows. But they *can* see core.h. So we used to have a whole
+ * load of functions which took a display and an X window, looked up the
+ * relevant MetaWindow, and returned information about it. The trouble with
+ * that is that looking up the MetaWindow is a nontrivial operation, and
+ * consolidating the calls in this way makes (for example) frame exposes
+ * 33% faster, according to valgrind.
+ *
+ * This function would perhaps be slightly better if the questions were
+ * represented by pointers, perhaps gchar*s, because then we could take
+ * advantage of gcc's automatic sentinel checking. On the other hand, this
+ * immediately suggests string comparison, and that's slow.
+ *
+ * Another possible improvement is that core.h still has a bunch of
+ * functions which can't be described by the formula "give a display and
+ * an X window, get a single value" (meta_core_user_move, for example), but
+ * which could theoretically be handled by this function if we relaxed the
+ * requirement that all questions should have exactly one argument.
+ */
+void meta_core_get (Display *xdisplay,
+ Window window,
+ ...);
+
+void meta_core_queue_frame_resize (Display *xdisplay,
+ Window frame_xwindow);
+
+/* Move as a result of user operation */
+void meta_core_user_move (Display *xdisplay,
+ Window frame_xwindow,
+ int x,
+ int y);
+void meta_core_user_resize (Display *xdisplay,
+ Window frame_xwindow,
+ int gravity,
+ int width,
+ int height);
+
+void meta_core_user_raise (Display *xdisplay,
+ Window frame_xwindow);
+void meta_core_user_lower_and_unfocus (Display *xdisplay,
+ Window frame_xwindow,
+ guint32 timestamp);
+
+void meta_core_user_focus (Display *xdisplay,
+ Window frame_xwindow,
+ guint32 timestamp);
+
+void meta_core_minimize (Display *xdisplay,
+ Window frame_xwindow);
+void meta_core_toggle_maximize (Display *xdisplay,
+ Window frame_xwindow);
+void meta_core_toggle_maximize_horizontally (Display *xdisplay,
+ Window frame_xwindow);
+void meta_core_toggle_maximize_vertically (Display *xdisplay,
+ Window frame_xwindow);
+void meta_core_unmaximize (Display *xdisplay,
+ Window frame_xwindow);
+void meta_core_maximize (Display *xdisplay,
+ Window frame_xwindow);
+void meta_core_delete (Display *xdisplay,
+ Window frame_xwindow,
+ guint32 timestamp);
+void meta_core_unshade (Display *xdisplay,
+ Window frame_xwindow,
+ guint32 timestamp);
+void meta_core_shade (Display *xdisplay,
+ Window frame_xwindow,
+ guint32 timestamp);
+void meta_core_unstick (Display *xdisplay,
+ Window frame_xwindow);
+void meta_core_stick (Display *xdisplay,
+ Window frame_xwindow);
+void meta_core_unmake_above (Display *xdisplay,
+ Window frame_xwindow);
+void meta_core_make_above (Display *xdisplay,
+ Window frame_xwindow);
+void meta_core_change_workspace (Display *xdisplay,
+ Window frame_xwindow,
+ int new_workspace);
+
+int meta_core_get_num_workspaces (Screen *xscreen);
+int meta_core_get_active_workspace (Screen *xscreen);
+int meta_core_get_frame_workspace (Display *xdisplay,
+ Window frame_xwindow);
+const char* meta_core_get_workspace_name_with_index (Display *xdisplay,
+ Window xroot,
+ int index);
+
+void meta_core_show_window_menu (Display *xdisplay,
+ Window frame_xwindow,
+ int root_x,
+ int root_y,
+ int button,
+ guint32 timestamp);
+
+void meta_core_get_menu_accelerator (MetaMenuOp menu_op,
+ int workspace,
+ unsigned int *keysym,
+ MetaVirtualModifier *modifiers);
+
+gboolean meta_core_begin_grab_op (Display *xdisplay,
+ Window frame_xwindow,
+ MetaGrabOp op,
+ gboolean pointer_already_grabbed,
+ gboolean frame_action,
+ int button,
+ gulong modmask,
+ guint32 timestamp,
+ int root_x,
+ int root_y);
+void meta_core_end_grab_op (Display *xdisplay,
+ guint32 timestamp);
+MetaGrabOp meta_core_get_grab_op (Display *xdisplay);
+Window meta_core_get_grab_frame (Display *xdisplay);
+int meta_core_get_grab_button (Display *xdisplay);
+
+
+void meta_core_grab_buttons (Display *xdisplay,
+ Window frame_xwindow);
+
+void meta_core_set_screen_cursor (Display *xdisplay,
+ Window frame_on_screen,
+ MetaCursor cursor);
+
+/* Used because we ignore EnterNotify when a window is unmapped that
+ * really shouldn't cause focus changes, by comparing the event serial
+ * of the EnterNotify and the UnmapNotify.
+ */
+void meta_core_increment_event_serial (Display *display);
+
+void meta_invalidate_default_icons (void);
+
+#endif
+
+
+
+
diff --git a/src/include/display.h b/src/include/display.h
new file mode 100644
index 00000000..53dd9d75
--- /dev/null
+++ b/src/include/display.h
@@ -0,0 +1,48 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2008 Iain Holmes
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_DISPLAY_H
+#define META_DISPLAY_H
+
+#include <glib.h>
+#include <X11/Xlib.h>
+
+#include "types.h"
+
+#define meta_XFree(p) do { if ((p)) XFree ((p)); } while (0)
+
+void meta_display_get_compositor_version (MetaDisplay *display,
+ int *major,
+ int *minor);
+Display *meta_display_get_xdisplay (MetaDisplay *display);
+MetaCompositor *meta_display_get_compositor (MetaDisplay *display);
+GSList *meta_display_get_screens (MetaDisplay *display);
+
+gboolean meta_display_has_shape (MetaDisplay *display);
+
+MetaScreen *meta_display_screen_for_root (MetaDisplay *display,
+ Window xroot);
+MetaWindow *meta_display_get_focus_window (MetaDisplay *display);
+
+int meta_display_get_damage_event_base (MetaDisplay *display);
+int meta_display_get_shape_event_base (MetaDisplay *display);
+
+#endif
diff --git a/src/include/errors.h b/src/include/errors.h
new file mode 100644
index 00000000..d3fa5a96
--- /dev/null
+++ b/src/include/errors.h
@@ -0,0 +1,51 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco X error handling */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_ERRORS_H
+#define META_ERRORS_H
+
+#include <X11/Xlib.h>
+
+#include "util.h"
+#include "display.h"
+
+typedef void (* ErrorHandler) (Display *dpy,
+ XErrorEvent *error,
+ gpointer data);
+
+void meta_errors_init (void);
+void meta_errors_register_foreign_display (Display *foreign_dpy,
+ ErrorHandler handler,
+ gpointer data);
+
+void meta_error_trap_push (MetaDisplay *display);
+void meta_error_trap_pop (MetaDisplay *display,
+ gboolean last_request_was_roundtrip);
+
+void meta_error_trap_push_with_return (MetaDisplay *display);
+/* returns X error code, or 0 for no error */
+int meta_error_trap_pop_with_return (MetaDisplay *display,
+ gboolean last_request_was_roundtrip);
+
+
+#endif
diff --git a/src/include/frame.h b/src/include/frame.h
new file mode 100644
index 00000000..eeb57263
--- /dev/null
+++ b/src/include/frame.h
@@ -0,0 +1,31 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2008 Iain Holmes
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_FRAME_H
+#define META_FRAME_H
+
+#include <X11/Xlib.h>
+
+#include "types.h"
+
+Window meta_frame_get_xwindow (MetaFrame *frame);
+
+#endif
diff --git a/src/include/main.h b/src/include/main.h
new file mode 100644
index 00000000..d5789696
--- /dev/null
+++ b/src/include/main.h
@@ -0,0 +1,43 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco main */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_MAIN_H
+#define META_MAIN_H
+
+#include <glib.h>
+
+typedef enum
+{
+ META_EXIT_SUCCESS,
+ META_EXIT_ERROR
+} MetaExitCode;
+
+/* exit immediately */
+void meta_exit (MetaExitCode code);
+
+/* g_main_loop_quit() then fall out of main() */
+void meta_quit (MetaExitCode code);
+
+void meta_restart (void);
+
+#endif
diff --git a/src/include/prefs.h b/src/include/prefs.h
new file mode 100644
index 00000000..39597f9f
--- /dev/null
+++ b/src/include/prefs.h
@@ -0,0 +1,233 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco preferences */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_PREFS_H
+#define META_PREFS_H
+
+/* This header is a "common" one between the UI and core side */
+#include "common.h"
+#include <pango/pango-font.h>
+
+typedef enum
+{
+ META_PREF_MOUSE_BUTTON_MODS,
+ META_PREF_FOCUS_MODE,
+ META_PREF_FOCUS_NEW_WINDOWS,
+ META_PREF_RAISE_ON_CLICK,
+ META_PREF_ACTION_DOUBLE_CLICK_TITLEBAR,
+ META_PREF_ACTION_MIDDLE_CLICK_TITLEBAR,
+ META_PREF_ACTION_RIGHT_CLICK_TITLEBAR,
+ META_PREF_AUTO_RAISE,
+ META_PREF_AUTO_RAISE_DELAY,
+ META_PREF_THEME,
+ META_PREF_TITLEBAR_FONT,
+ META_PREF_NUM_WORKSPACES,
+ META_PREF_APPLICATION_BASED,
+ META_PREF_KEYBINDINGS,
+ META_PREF_DISABLE_WORKAROUNDS,
+ META_PREF_COMMANDS,
+ META_PREF_TERMINAL_COMMAND,
+ META_PREF_BUTTON_LAYOUT,
+ META_PREF_WORKSPACE_NAMES,
+ META_PREF_VISUAL_BELL,
+ META_PREF_AUDIBLE_BELL,
+ META_PREF_VISUAL_BELL_TYPE,
+ META_PREF_REDUCED_RESOURCES,
+ META_PREF_MATE_ACCESSIBILITY,
+ META_PREF_MATE_ANIMATIONS,
+ META_PREF_CURSOR_THEME,
+ META_PREF_CURSOR_SIZE,
+ META_PREF_COMPOSITING_MANAGER,
+ META_PREF_RESIZE_WITH_RIGHT_BUTTON,
+ META_PREF_FORCE_FULLSCREEN
+} MetaPreference;
+
+typedef void (* MetaPrefsChangedFunc) (MetaPreference pref,
+ gpointer data);
+
+void meta_prefs_add_listener (MetaPrefsChangedFunc func,
+ gpointer data);
+void meta_prefs_remove_listener (MetaPrefsChangedFunc func,
+ gpointer data);
+
+void meta_prefs_init (void);
+const char* meta_preference_to_string (MetaPreference pref);
+
+MetaVirtualModifier meta_prefs_get_mouse_button_mods (void);
+guint meta_prefs_get_mouse_button_resize (void);
+guint meta_prefs_get_mouse_button_menu (void);
+MetaFocusMode meta_prefs_get_focus_mode (void);
+MetaFocusNewWindows meta_prefs_get_focus_new_windows (void);
+gboolean meta_prefs_get_raise_on_click (void);
+const char* meta_prefs_get_theme (void);
+/* returns NULL if GTK default should be used */
+const PangoFontDescription* meta_prefs_get_titlebar_font (void);
+int meta_prefs_get_num_workspaces (void);
+gboolean meta_prefs_get_application_based (void);
+gboolean meta_prefs_get_disable_workarounds (void);
+gboolean meta_prefs_get_auto_raise (void);
+int meta_prefs_get_auto_raise_delay (void);
+gboolean meta_prefs_get_reduced_resources (void);
+gboolean meta_prefs_get_mate_accessibility (void);
+gboolean meta_prefs_get_mate_animations (void);
+
+const char* meta_prefs_get_command (int i);
+
+char* meta_prefs_get_mateconf_key_for_command (int i);
+
+const char* meta_prefs_get_terminal_command (void);
+const char* meta_prefs_get_mateconf_key_for_terminal_command (void);
+
+void meta_prefs_get_button_layout (MetaButtonLayout *button_layout);
+
+/* Double, right, middle click can be configured to any titlebar meta-action */
+MetaActionTitlebar meta_prefs_get_action_double_click_titlebar (void);
+MetaActionTitlebar meta_prefs_get_action_middle_click_titlebar (void);
+MetaActionTitlebar meta_prefs_get_action_right_click_titlebar (void);
+
+void meta_prefs_set_num_workspaces (int n_workspaces);
+
+const char* meta_prefs_get_workspace_name (int i);
+void meta_prefs_change_workspace_name (int i,
+ const char *name);
+
+const char* meta_prefs_get_cursor_theme (void);
+int meta_prefs_get_cursor_size (void);
+gboolean meta_prefs_get_compositing_manager (void);
+gboolean meta_prefs_get_force_fullscreen (void);
+
+/**
+ * Sets whether the compositor is turned on.
+ *
+ * \param whether TRUE to turn on, FALSE to turn off
+ */
+void meta_prefs_set_compositing_manager (gboolean whether);
+
+void meta_prefs_set_force_fullscreen (gboolean whether);
+
+/* XXX FIXME This should be x-macroed, but isn't yet because it would be
+ * difficult (or perhaps impossible) to add the suffixes using the current
+ * system. It needs some more thought, perhaps after the current system
+ * evolves a little.
+ */
+typedef enum _MetaKeyBindingAction
+{
+ META_KEYBINDING_ACTION_NONE = -1,
+ META_KEYBINDING_ACTION_WORKSPACE_1,
+ META_KEYBINDING_ACTION_WORKSPACE_2,
+ META_KEYBINDING_ACTION_WORKSPACE_3,
+ META_KEYBINDING_ACTION_WORKSPACE_4,
+ META_KEYBINDING_ACTION_WORKSPACE_5,
+ META_KEYBINDING_ACTION_WORKSPACE_6,
+ META_KEYBINDING_ACTION_WORKSPACE_7,
+ META_KEYBINDING_ACTION_WORKSPACE_8,
+ META_KEYBINDING_ACTION_WORKSPACE_9,
+ META_KEYBINDING_ACTION_WORKSPACE_10,
+ META_KEYBINDING_ACTION_WORKSPACE_11,
+ META_KEYBINDING_ACTION_WORKSPACE_12,
+ META_KEYBINDING_ACTION_WORKSPACE_LEFT,
+ META_KEYBINDING_ACTION_WORKSPACE_RIGHT,
+ META_KEYBINDING_ACTION_WORKSPACE_UP,
+ META_KEYBINDING_ACTION_WORKSPACE_DOWN,
+ META_KEYBINDING_ACTION_SWITCH_GROUP,
+ META_KEYBINDING_ACTION_SWITCH_GROUP_BACKWARD,
+ META_KEYBINDING_ACTION_SWITCH_WINDOWS,
+ META_KEYBINDING_ACTION_SWITCH_WINDOWS_BACKWARD,
+ META_KEYBINDING_ACTION_SWITCH_PANELS,
+ META_KEYBINDING_ACTION_SWITCH_PANELS_BACKWARD,
+ META_KEYBINDING_ACTION_CYCLE_GROUP,
+ META_KEYBINDING_ACTION_CYCLE_GROUP_BACKWARD,
+ META_KEYBINDING_ACTION_CYCLE_WINDOWS,
+ META_KEYBINDING_ACTION_CYCLE_WINDOWS_BACKWARD,
+ META_KEYBINDING_ACTION_CYCLE_PANELS,
+ META_KEYBINDING_ACTION_CYCLE_PANELS_BACKWARD,
+ META_KEYBINDING_ACTION_SHOW_DESKTOP,
+ META_KEYBINDING_ACTION_PANEL_MAIN_MENU,
+ META_KEYBINDING_ACTION_PANEL_RUN_DIALOG,
+ META_KEYBINDING_ACTION_COMMAND_1,
+ META_KEYBINDING_ACTION_COMMAND_2,
+ META_KEYBINDING_ACTION_COMMAND_3,
+ META_KEYBINDING_ACTION_COMMAND_4,
+ META_KEYBINDING_ACTION_COMMAND_5,
+ META_KEYBINDING_ACTION_COMMAND_6,
+ META_KEYBINDING_ACTION_COMMAND_7,
+ META_KEYBINDING_ACTION_COMMAND_8,
+ META_KEYBINDING_ACTION_COMMAND_9,
+ META_KEYBINDING_ACTION_COMMAND_10,
+ META_KEYBINDING_ACTION_COMMAND_11,
+ META_KEYBINDING_ACTION_COMMAND_12
+} MetaKeyBindingAction;
+
+typedef struct
+{
+ unsigned int keysym;
+ unsigned int keycode;
+ MetaVirtualModifier modifiers;
+} MetaKeyCombo;
+
+typedef struct
+{
+ const char *name;
+ /**
+ * A list of MetaKeyCombos. Each of them is bound to
+ * this keypref. If one has keysym==modifiers==0, it is
+ * ignored. For historical reasons, the first entry is
+ * governed by the pref FOO and the remainder are
+ * governed by the pref FOO_list.
+ */
+ GSList *bindings;
+
+ /** for keybindings that can have shift or not like Alt+Tab */
+ gboolean add_shift:1;
+
+ /** for keybindings that apply only to a window */
+ gboolean per_window:1;
+} MetaKeyPref;
+
+void meta_prefs_get_key_bindings (const MetaKeyPref **bindings,
+ int *n_bindings);
+
+MetaKeyBindingAction meta_prefs_get_keybinding_action (const char *name);
+
+void meta_prefs_get_window_binding (const char *name,
+ unsigned int *keysym,
+ MetaVirtualModifier *modifiers);
+
+typedef enum
+{
+ META_VISUAL_BELL_INVALID = 0,
+ META_VISUAL_BELL_FULLSCREEN_FLASH,
+ META_VISUAL_BELL_FRAME_FLASH
+
+} MetaVisualBellType;
+
+gboolean meta_prefs_get_visual_bell (void);
+gboolean meta_prefs_bell_is_audible (void);
+MetaVisualBellType meta_prefs_get_visual_bell_type (void);
+
+#endif
+
+
+
+
diff --git a/src/include/resizepopup.h b/src/include/resizepopup.h
new file mode 100644
index 00000000..4e03d5d3
--- /dev/null
+++ b/src/include/resizepopup.h
@@ -0,0 +1,47 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco resizing-terminal-window feedback */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_RESIZEPOPUP_H
+#define META_RESIZEPOPUP_H
+
+/* Don't include gtk.h or gdk.h here */
+#include "boxes.h"
+#include "common.h"
+#include <X11/Xlib.h>
+#include <glib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+MetaResizePopup* meta_ui_resize_popup_new (Display *display,
+ int screen_number);
+void meta_ui_resize_popup_free (MetaResizePopup *popup);
+void meta_ui_resize_popup_set (MetaResizePopup *popup,
+ MetaRectangle rect,
+ int base_width,
+ int base_height,
+ int width_inc,
+ int height_inc);
+void meta_ui_resize_popup_set_showing (MetaResizePopup *popup,
+ gboolean showing);
+
+#endif
+
diff --git a/src/include/screen.h b/src/include/screen.h
new file mode 100644
index 00000000..9f842317
--- /dev/null
+++ b/src/include/screen.h
@@ -0,0 +1,48 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2008 Iain Holmes
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_SCREEN_H
+#define META_SCREEN_H
+
+#include <X11/Xlib.h>
+#include <glib.h>
+#include "types.h"
+
+int meta_screen_get_screen_number (MetaScreen *screen);
+MetaDisplay *meta_screen_get_display (MetaScreen *screen);
+
+Window meta_screen_get_xroot (MetaScreen *screen);
+void meta_screen_get_size (MetaScreen *screen,
+ int *width,
+ int *height);
+
+gpointer meta_screen_get_compositor_data (MetaScreen *screen);
+void meta_screen_set_compositor_data (MetaScreen *screen,
+ gpointer info);
+
+MetaScreen *meta_screen_for_x_screen (Screen *xscreen);
+
+#ifdef HAVE_COMPOSITE_EXTENSIONS
+void meta_screen_set_cm_selection (MetaScreen *screen);
+void meta_screen_unset_cm_selection (MetaScreen *screen);
+#endif
+
+#endif
diff --git a/src/include/tabpopup.h b/src/include/tabpopup.h
new file mode 100644
index 00000000..2bee506e
--- /dev/null
+++ b/src/include/tabpopup.h
@@ -0,0 +1,67 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco tab popup window */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_TABPOPUP_H
+#define META_TABPOPUP_H
+
+/* Don't include gtk.h or gdk.h here */
+#include "common.h"
+#include "boxes.h"
+#include <X11/Xlib.h>
+#include <glib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+typedef struct _MetaTabEntry MetaTabEntry;
+typedef struct _MetaTabPopup MetaTabPopup;
+typedef void *MetaTabEntryKey;
+
+struct _MetaTabEntry
+{
+ MetaTabEntryKey key;
+ const char *title;
+ GdkPixbuf *icon;
+ MetaRectangle rect;
+ MetaRectangle inner_rect;
+ guint blank : 1;
+ guint hidden : 1;
+ guint demands_attention : 1;
+};
+
+MetaTabPopup* meta_ui_tab_popup_new (const MetaTabEntry *entries,
+ int screen_number,
+ int entry_count,
+ int width,
+ gboolean outline);
+void meta_ui_tab_popup_free (MetaTabPopup *popup);
+void meta_ui_tab_popup_set_showing (MetaTabPopup *popup,
+ gboolean showing);
+void meta_ui_tab_popup_forward (MetaTabPopup *popup);
+void meta_ui_tab_popup_backward (MetaTabPopup *popup);
+MetaTabEntryKey meta_ui_tab_popup_get_selected (MetaTabPopup *popup);
+void meta_ui_tab_popup_select (MetaTabPopup *popup,
+ MetaTabEntryKey key);
+
+
+#endif
+
diff --git a/src/include/types.h b/src/include/types.h
new file mode 100644
index 00000000..045b102f
--- /dev/null
+++ b/src/include/types.h
@@ -0,0 +1,31 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2008 Iain Holmes
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_TYPES_H
+#define META_TYPES_H
+
+typedef struct _MetaCompositor MetaCompositor;
+typedef struct _MetaDisplay MetaDisplay;
+typedef struct _MetaFrame MetaFrame;
+typedef struct _MetaScreen MetaScreen;
+typedef struct _MetaWindow MetaWindow;
+
+#endif
diff --git a/src/include/ui.h b/src/include/ui.h
new file mode 100644
index 00000000..1fce402c
--- /dev/null
+++ b/src/include/ui.h
@@ -0,0 +1,209 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco interface for talking to GTK+ UI module */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_UI_H
+#define META_UI_H
+
+/* Don't include gtk.h or gdk.h here */
+#include "common.h"
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <glib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+/* This is between GTK_PRIORITY_RESIZE (+10) and GDK_PRIORITY_REDRAW (+20) */
+#define META_PRIORITY_RESIZE (G_PRIORITY_HIGH_IDLE + 15)
+
+typedef struct _MetaUI MetaUI;
+
+typedef struct _MetaImageWindow MetaImageWindow;
+
+typedef gboolean (* MetaEventFunc) (XEvent *xevent, gpointer data);
+
+typedef enum
+{
+ META_UI_DIRECTION_LTR,
+ META_UI_DIRECTION_RTL
+} MetaUIDirection;
+
+void meta_ui_init (int *argc, char ***argv);
+
+Display* meta_ui_get_display (void);
+
+void meta_ui_add_event_func (Display *xdisplay,
+ MetaEventFunc func,
+ gpointer data);
+void meta_ui_remove_event_func (Display *xdisplay,
+ MetaEventFunc func,
+ gpointer data);
+
+MetaUI* meta_ui_new (Display *xdisplay,
+ Screen *screen);
+void meta_ui_free (MetaUI *ui);
+
+void meta_ui_theme_get_frame_borders (MetaUI *ui,
+ MetaFrameType type,
+ MetaFrameFlags flags,
+ int *top_height,
+ int *bottom_height,
+ int *left_width,
+ int *right_width);
+void meta_ui_get_frame_geometry (MetaUI *ui,
+ Window frame_xwindow,
+ int *top_height, int *bottom_height,
+ int *left_width, int *right_width);
+Window meta_ui_create_frame_window (MetaUI *ui,
+ Display *xdisplay,
+ Visual *xvisual,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gint screen_no);
+void meta_ui_destroy_frame_window (MetaUI *ui,
+ Window xwindow);
+void meta_ui_move_resize_frame (MetaUI *ui,
+ Window frame,
+ int x,
+ int y,
+ int width,
+ int height);
+
+/* GDK insists on tracking map/unmap */
+void meta_ui_map_frame (MetaUI *ui,
+ Window xwindow);
+void meta_ui_unmap_frame (MetaUI *ui,
+ Window xwindow);
+
+void meta_ui_unflicker_frame_bg (MetaUI *ui,
+ Window xwindow,
+ int target_width,
+ int target_height);
+void meta_ui_reset_frame_bg (MetaUI *ui,
+ Window xwindow);
+
+void meta_ui_apply_frame_shape (MetaUI *ui,
+ Window xwindow,
+ int new_window_width,
+ int new_window_height,
+ gboolean window_has_shape);
+
+void meta_ui_queue_frame_draw (MetaUI *ui,
+ Window xwindow);
+
+void meta_ui_set_frame_title (MetaUI *ui,
+ Window xwindow,
+ const char *title);
+
+void meta_ui_repaint_frame (MetaUI *ui,
+ Window xwindow);
+
+MetaWindowMenu* meta_ui_window_menu_new (MetaUI *ui,
+ Window client_xwindow,
+ MetaMenuOp ops,
+ MetaMenuOp insensitive,
+ unsigned long active_workspace,
+ int n_workspaces,
+ MetaWindowMenuFunc func,
+ gpointer data);
+void meta_ui_window_menu_popup (MetaWindowMenu *menu,
+ int root_x,
+ int root_y,
+ int button,
+ guint32 timestamp);
+void meta_ui_window_menu_free (MetaWindowMenu *menu);
+
+
+MetaImageWindow* meta_image_window_new (Display *xdisplay,
+ int screen_number,
+ int max_width,
+ int max_height);
+void meta_image_window_free (MetaImageWindow *iw);
+void meta_image_window_set_showing (MetaImageWindow *iw,
+ gboolean showing);
+void meta_image_window_set (MetaImageWindow *iw,
+ GdkPixbuf *pixbuf,
+ int x,
+ int y);
+
+/* FIXME these lack a display arg */
+GdkPixbuf* meta_gdk_pixbuf_get_from_window (GdkPixbuf *dest,
+ Window xwindow,
+ int src_x,
+ int src_y,
+ int dest_x,
+ int dest_y,
+ int width,
+ int height);
+
+GdkPixbuf* meta_gdk_pixbuf_get_from_pixmap (GdkPixbuf *dest,
+ Pixmap xpixmap,
+ int src_x,
+ int src_y,
+ int dest_x,
+ int dest_y,
+ int width,
+ int height);
+
+/* Used when we have a server grab and draw all over everything,
+ * then we need to handle exposes after doing that, instead of
+ * during it
+ */
+void meta_ui_push_delay_exposes (MetaUI *ui);
+void meta_ui_pop_delay_exposes (MetaUI *ui);
+
+GdkPixbuf* meta_ui_get_default_window_icon (MetaUI *ui);
+GdkPixbuf* meta_ui_get_default_mini_icon (MetaUI *ui);
+
+gboolean meta_ui_window_should_not_cause_focus (Display *xdisplay,
+ Window xwindow);
+
+char* meta_text_property_to_utf8 (Display *xdisplay,
+ const XTextProperty *prop);
+
+void meta_ui_set_current_theme (const char *name,
+ gboolean force_reload);
+gboolean meta_ui_have_a_theme (void);
+
+gboolean meta_ui_parse_accelerator (const char *accel,
+ unsigned int *keysym,
+ unsigned int *keycode,
+ MetaVirtualModifier *mask);
+gboolean meta_ui_parse_modifier (const char *accel,
+ MetaVirtualModifier *mask);
+
+/* Caller responsible for freeing return string of meta_ui_accelerator_name! */
+gchar* meta_ui_accelerator_name (unsigned int keysym,
+ MetaVirtualModifier mask);
+gboolean meta_ui_window_is_widget (MetaUI *ui,
+ Window xwindow);
+
+int meta_ui_get_drag_threshold (MetaUI *ui);
+
+MetaUIDirection meta_ui_get_direction (void);
+
+GdkPixbuf *meta_ui_get_pixbuf_from_pixmap (Pixmap pmap);
+
+#include "tabpopup.h"
+
+#endif
diff --git a/src/include/util.h b/src/include/util.h
new file mode 100644
index 00000000..0607c568
--- /dev/null
+++ b/src/include/util.h
@@ -0,0 +1,136 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco utilities */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_UTIL_H
+#define META_UTIL_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+gboolean meta_is_verbose (void);
+void meta_set_verbose (gboolean setting);
+gboolean meta_is_debugging (void);
+void meta_set_debugging (gboolean setting);
+gboolean meta_is_syncing (void);
+void meta_set_syncing (gboolean setting);
+gboolean meta_get_replace_current_wm (void);
+void meta_set_replace_current_wm (gboolean setting);
+
+void meta_debug_spew_real (const char *format,
+ ...) G_GNUC_PRINTF (1, 2);
+void meta_verbose_real (const char *format,
+ ...) G_GNUC_PRINTF (1, 2);
+
+void meta_bug (const char *format,
+ ...) G_GNUC_PRINTF (1, 2);
+void meta_warning (const char *format,
+ ...) G_GNUC_PRINTF (1, 2);
+void meta_fatal (const char *format,
+ ...) G_GNUC_PRINTF (1, 2);
+
+typedef enum
+{
+ META_DEBUG_FOCUS = 1 << 0,
+ META_DEBUG_WORKAREA = 1 << 1,
+ META_DEBUG_STACK = 1 << 2,
+ META_DEBUG_THEMES = 1 << 3,
+ META_DEBUG_SM = 1 << 4,
+ META_DEBUG_EVENTS = 1 << 5,
+ META_DEBUG_WINDOW_STATE = 1 << 6,
+ META_DEBUG_WINDOW_OPS = 1 << 7,
+ META_DEBUG_GEOMETRY = 1 << 8,
+ META_DEBUG_PLACEMENT = 1 << 9,
+ META_DEBUG_PING = 1 << 10,
+ META_DEBUG_XINERAMA = 1 << 11,
+ META_DEBUG_KEYBINDINGS = 1 << 12,
+ META_DEBUG_SYNC = 1 << 13,
+ META_DEBUG_ERRORS = 1 << 14,
+ META_DEBUG_STARTUP = 1 << 15,
+ META_DEBUG_PREFS = 1 << 16,
+ META_DEBUG_GROUPS = 1 << 17,
+ META_DEBUG_RESIZING = 1 << 18,
+ META_DEBUG_SHAPES = 1 << 19,
+ META_DEBUG_COMPOSITOR = 1 << 20,
+ META_DEBUG_EDGE_RESISTANCE = 1 << 21
+} MetaDebugTopic;
+
+void meta_topic_real (MetaDebugTopic topic,
+ const char *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
+void meta_push_no_msg_prefix (void);
+void meta_pop_no_msg_prefix (void);
+
+gint meta_unsigned_long_equal (gconstpointer v1,
+ gconstpointer v2);
+guint meta_unsigned_long_hash (gconstpointer v);
+
+void meta_print_backtrace (void);
+
+const char* meta_gravity_to_string (int gravity);
+
+#include <libintl.h>
+#define _(x) dgettext (GETTEXT_PACKAGE, x)
+#define N_(x) x
+
+char* meta_g_utf8_strndup (const gchar *src, gsize n);
+
+void meta_free_gslist_and_elements (GSList *list_to_deep_free);
+
+GPid meta_show_dialog (const char *type,
+ const char *title,
+ const char *message,
+ gint timeout,
+ const char *ok_text,
+ const char *cancel_text,
+ const int transient_for,
+ GSList *columns,
+ GSList *entries);
+
+/* To disable verbose mode, we make these functions into no-ops */
+#ifdef WITH_VERBOSE_MODE
+
+#define meta_debug_spew meta_debug_spew_real
+#define meta_verbose meta_verbose_real
+#define meta_topic meta_topic_real
+
+#else
+
+# ifdef G_HAVE_ISO_VARARGS
+# define meta_debug_spew(...)
+# define meta_verbose(...)
+# define meta_topic(...)
+# elif defined(G_HAVE_GNUC_VARARGS)
+# define meta_debug_spew(format...)
+# define meta_verbose(format...)
+# define meta_topic(format...)
+# else
+# error "This compiler does not support varargs macros and thus verbose mode can't be disabled meaningfully"
+# endif
+
+#endif /* !WITH_VERBOSE_MODE */
+
+#endif /* META_UTIL_H */
+
+
diff --git a/src/include/window.h b/src/include/window.h
new file mode 100644
index 00000000..8a338660
--- /dev/null
+++ b/src/include/window.h
@@ -0,0 +1,39 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2008 Iain Holmes
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_WINDOW_H
+#define META_WINDOW_H
+
+#include <glib.h>
+#include <X11/Xlib.h>
+
+#include "boxes.h"
+#include "types.h"
+
+MetaFrame *meta_window_get_frame (MetaWindow *window);
+gboolean meta_window_has_focus (MetaWindow *window);
+gboolean meta_window_is_shaded (MetaWindow *window);
+MetaRectangle *meta_window_get_rect (MetaWindow *window);
+MetaScreen *meta_window_get_screen (MetaWindow *window);
+MetaDisplay *meta_window_get_display (MetaWindow *window);
+Window meta_window_get_xwindow (MetaWindow *window);
+
+#endif
diff --git a/src/include/xprops.h b/src/include/xprops.h
new file mode 100644
index 00000000..58ad22c7
--- /dev/null
+++ b/src/include/xprops.h
@@ -0,0 +1,227 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco X property convenience routines */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_XPROPS_H
+#define META_XPROPS_H
+
+#include <config.h>
+
+#include "display.h"
+#include <X11/Xutil.h>
+
+#ifdef HAVE_XSYNC
+#include <X11/extensions/sync.h>
+#endif
+
+/* Copied from Lesstif by way of GTK. Rudimentary docs can be
+ * found in some Motif reference guides online.
+ */
+typedef struct {
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long input_mode;
+ unsigned long status;
+} MotifWmHints, MwmHints;
+
+#define MWM_HINTS_FUNCTIONS (1L << 0)
+#define MWM_HINTS_DECORATIONS (1L << 1)
+#define MWM_HINTS_INPUT_MODE (1L << 2)
+#define MWM_HINTS_STATUS (1L << 3)
+
+#define MWM_FUNC_ALL (1L << 0)
+#define MWM_FUNC_RESIZE (1L << 1)
+#define MWM_FUNC_MOVE (1L << 2)
+#define MWM_FUNC_MINIMIZE (1L << 3)
+#define MWM_FUNC_MAXIMIZE (1L << 4)
+#define MWM_FUNC_CLOSE (1L << 5)
+
+#define MWM_DECOR_ALL (1L << 0)
+#define MWM_DECOR_BORDER (1L << 1)
+#define MWM_DECOR_RESIZEH (1L << 2)
+#define MWM_DECOR_TITLE (1L << 3)
+#define MWM_DECOR_MENU (1L << 4)
+#define MWM_DECOR_MINIMIZE (1L << 5)
+#define MWM_DECOR_MAXIMIZE (1L << 6)
+
+#define MWM_INPUT_MODELESS 0
+#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1
+#define MWM_INPUT_SYSTEM_MODAL 2
+#define MWM_INPUT_FULL_APPLICATION_MODAL 3
+#define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL
+
+#define MWM_TEAROFF_WINDOW (1L<<0)
+
+/* These all return the memory from Xlib, so require an XFree()
+ * when they return TRUE. They return TRUE on success.
+ */
+gboolean meta_prop_get_atom_list (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ Atom **atoms_p,
+ int *n_atoms_p);
+gboolean meta_prop_get_motif_hints (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ MotifWmHints **hints_p);
+gboolean meta_prop_get_cardinal_list (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ gulong **cardinals_p,
+ int *n_cardinals_p);
+gboolean meta_prop_get_latin1_string (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ char **str_p);
+gboolean meta_prop_get_utf8_string (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ char **str_p);
+gboolean meta_prop_get_utf8_list (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ char ***str_p,
+ int *n_str_p);
+void meta_prop_set_utf8_string_hint
+ (MetaDisplay *display,
+ Window xwindow,
+ Atom atom,
+ const char *val);
+gboolean meta_prop_get_window (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ Window *window_p);
+gboolean meta_prop_get_cardinal (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ gulong *cardinal_p);
+gboolean meta_prop_get_cardinal_with_atom_type (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ Atom prop_type,
+ gulong *cardinal_p);
+gboolean meta_prop_get_text_property (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ char **utf8_str_p);
+
+gboolean meta_prop_get_wm_hints (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ XWMHints **hints_p);
+
+gboolean meta_prop_get_class_hint (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ XClassHint *class_hint);
+
+gboolean meta_prop_get_size_hints (MetaDisplay *display,
+ Window xwindow,
+ Atom xatom,
+ XSizeHints **hints_p,
+ gulong *flags_p);
+
+typedef enum
+{
+ META_PROP_VALUE_INVALID,
+ META_PROP_VALUE_UTF8,
+ META_PROP_VALUE_STRING,
+ META_PROP_VALUE_STRING_AS_UTF8,
+ META_PROP_VALUE_MOTIF_HINTS,
+ META_PROP_VALUE_CARDINAL,
+ META_PROP_VALUE_WINDOW,
+ META_PROP_VALUE_CARDINAL_LIST,
+ META_PROP_VALUE_UTF8_LIST,
+ META_PROP_VALUE_ATOM_LIST,
+ META_PROP_VALUE_TEXT_PROPERTY, /* comes back as UTF-8 string */
+ META_PROP_VALUE_WM_HINTS,
+ META_PROP_VALUE_CLASS_HINT,
+ META_PROP_VALUE_SIZE_HINTS,
+ META_PROP_VALUE_SYNC_COUNTER /* comes back as CARDINAL */
+} MetaPropValueType;
+
+/* used to request/return/store property values */
+typedef struct
+{
+ MetaPropValueType type;
+ Atom atom;
+ Atom required_type; /* autofilled if None */
+
+ union
+ {
+ char *str;
+ MotifWmHints *motif_hints;
+ Window xwindow;
+ gulong cardinal;
+ XWMHints *wm_hints;
+ XClassHint class_hint;
+#ifdef HAVE_XSYNC
+ XSyncCounter xcounter;
+#endif
+
+ struct
+ {
+ XSizeHints *hints;
+ unsigned long flags;
+ } size_hints;
+
+ struct
+ {
+ gulong *cardinals;
+ int n_cardinals;
+ } cardinal_list;
+
+ struct
+ {
+ char **strings;
+ int n_strings;
+ } string_list;
+
+ struct
+ {
+ Atom *atoms;
+ int n_atoms;
+ } atom_list;
+
+ } v;
+
+} MetaPropValue;
+
+/* Each value has type and atom initialized. If there's an error,
+ * or property is unset, type comes back as INVALID;
+ * else type comes back as it originated, and the data
+ * is filled in.
+ */
+void meta_prop_get_values (MetaDisplay *display,
+ Window xwindow,
+ MetaPropValue *values,
+ int n_values);
+
+void meta_prop_free_values (MetaPropValue *values,
+ int n_values);
+
+#endif
+
+
+
+
diff --git a/src/libmarco-private.pc.in b/src/libmarco-private.pc.in
new file mode 100644
index 00000000..8b0a5c83
--- /dev/null
+++ b/src/libmarco-private.pc.in
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+libmate_serverdir=@libexecdir@
+
+Name: libmarco-private
+Description: Marco internals shared
+Requires: gtk+-2.0
+Version: @VERSION@
+Libs: -L${libdir} -lmarco-private
+Cflags: -I${includedir}/marco-1
diff --git a/src/marco-wm.desktop.in b/src/marco-wm.desktop.in
new file mode 100644
index 00000000..e1e06575
--- /dev/null
+++ b/src/marco-wm.desktop.in
@@ -0,0 +1,20 @@
+[Desktop Entry]
+Type=Application
+_Name=Marco
+Exec=marco
+# name of loadable control center module
+X-MATE-WMSettingsModule=marco
+# name we put on the WM spec check window
+X-MATE-WMName=Marco
+# back compat only
+X-MateWMSettingsLibrary=marco
+X-MATE-Bugzilla-Bugzilla=MATE
+X-MATE-Bugzilla-Product=marco
+X-MATE-Bugzilla-Component=general
+X-MATE-Autostart-Phase=WindowManager
+X-MATE-Provides=windowmanager
+X-MATE-Autostart-Notify=true
+
+[Window Manager]
+SessionManaged=true
+
diff --git a/src/marco.desktop.in b/src/marco.desktop.in
new file mode 100644
index 00000000..4167dadf
--- /dev/null
+++ b/src/marco.desktop.in
@@ -0,0 +1,17 @@
+[Desktop Entry]
+Type=Application
+_Name=Marco
+Exec=marco
+NoDisplay=true
+# name of loadable control center module
+X-MATE-WMSettingsModule=marco
+# name we put on the WM spec check window
+X-MATE-WMName=Marco
+# back compat only
+X-MateWMSettingsLibrary=marco
+X-MATE-Bugzilla-Bugzilla=MATE
+X-MATE-Bugzilla-Product=marco
+X-MATE-Bugzilla-Component=general
+X-MATE-Autostart-Phase=WindowManager
+X-MATE-Provides=windowmanager
+X-MATE-Autostart-Notify=true
diff --git a/src/marco.schemas.in.in b/src/marco.schemas.in.in
new file mode 100644
index 00000000..acd342b4
--- /dev/null
+++ b/src/marco.schemas.in.in
@@ -0,0 +1,573 @@
+<mateconfschemafile>
+ <schemalist>
+
+ <!-- General preferences -->
+
+ <schema>
+ <key>/schemas/apps/marco/general/mouse_button_modifier</key>
+ <applyto>/apps/marco/general/mouse_button_modifier</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>&lt;Alt&gt;</default>
+ <locale name="C">
+ <short>Modifier to use for modified window click actions</short>
+ <long>
+ Clicking a window while holding down this modifier key
+ will move the window (left click), resize the window
+ (middle click), or show the window menu (right click).
+ The middle and right click operations may be swapped
+ using the "resize_with_right_button" key.
+ Modifier is expressed as "&lt;Alt&gt;" or "&lt;Super&gt;"
+ for example.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/resize_with_right_button</key>
+ <applyto>/apps/marco/general/resize_with_right_button</applyto>
+ <owner>marco</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Whether to resize with the right button</short>
+ <long>
+ Set this to true to resize with the right button and show a menu
+ with the middle button while holding down the key given in
+ "mouse_button_modifier"; set it to false to make it work the
+ opposite way around.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/button_layout</key>
+ <applyto>/apps/marco/general/button_layout</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>menu:minimize,maximize,close</default>
+ <locale name="C">
+ <short>Arrangement of buttons on the titlebar</short>
+ <long>
+ Arrangement of buttons on the titlebar. The
+ value should be a string, such as
+ "menu:minimize,maximize,spacer,close"; the colon separates the
+ left corner of the window from the right corner, and
+ the button names are comma-separated. Duplicate buttons
+ are not allowed. Unknown button names are silently ignored
+ so that buttons can be added in future marco versions
+ without breaking older versions.
+ A special spacer tag can be used to insert some space between
+ two adjacent buttons.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/focus_mode</key>
+ <applyto>/apps/marco/general/focus_mode</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>click</default>
+ <locale name="C">
+ <short>Window focus mode</short>
+ <long>
+ The window focus mode indicates how windows are activated.
+ It has three possible values; "click" means windows must
+ be clicked in order to focus them, "sloppy" means windows
+ are focused when the mouse enters the window, and "mouse" means
+ windows are focused when the mouse enters the window and
+ unfocused when the mouse leaves the window.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/focus_new_windows</key>
+ <applyto>/apps/marco/general/focus_new_windows</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>smart</default>
+ <locale name="C">
+ <short>Control how new windows get focus</short>
+ <long>
+ This option provides additional control over how newly created
+ windows get focus. It has two possible values; "smart" applies
+ the user's normal focus mode, and "strict" results in windows
+ started from a terminal not being given focus.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/raise_on_click</key>
+ <applyto>/apps/marco/general/raise_on_click</applyto>
+ <owner>marco</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Whether raising should be a side-effect of other user
+ interactions</short>
+ <long>
+ Setting this option to false can lead to buggy behavior, so
+ users are strongly discouraged from changing it from the default
+ of true.
+
+ Many actions (e.g. clicking in the client area, moving or resizing the window)
+ normally raise the window as a side-effect. Setting this option to false, which
+ is strongly discouraged, will decouple raising from other user actions, and
+ ignore raise requests generated by applications. See
+ http://bugzilla.gnome.org/show_bug.cgi?id=445447#c6.
+ Even when this option is false, windows can
+ still be raised by an alt-left-click anywhere on the window, a
+ normal click on the window decorations, or by special messages
+ from pagers, such as activation requests from tasklist applets.
+ This option is currently disabled in click-to-focus mode.
+
+ Note that the list of ways to raise windows when raise_on_click
+ is false does not include programmatic requests from
+ applications to raise windows; such requests will be ignored
+ regardless of the reason for the request. If you are an
+ application developer and have a user complaining that your
+ application does not work with this setting disabled, tell them
+ it is _their_ fault for breaking their window manager and that
+ they need to change this option back to true or live with the
+ "bug" they requested.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/action_double_click_titlebar</key>
+ <applyto>/apps/marco/general/action_double_click_titlebar</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>toggle_maximize</default>
+ <locale name="C">
+ <short>Action on title bar double-click</short>
+ <long>
+ This option determines the effects of double-clicking on the
+ title bar. Current valid options are
+ 'toggle_shade', which will shade/unshade the window,
+ 'toggle_maximize' which will maximize/unmaximize the window,
+ 'toggle_maximize_horizontally' and 'toggle_maximize_vertically'
+ which will maximize/unmaximize the window in that direction only,
+ 'minimize' which will minimize the window,
+ 'shade' which will roll the window up,
+ 'menu' which will display the window menu,
+ 'lower' which will put the window behind all the others,
+ and 'none' which will not do anything.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/action_middle_click_titlebar</key>
+ <applyto>/apps/marco/general/action_middle_click_titlebar</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>lower</default>
+ <locale name="C">
+ <short>Action on title bar middle-click</short>
+ <long>
+ This option determines the effects of middle-clicking on the
+ title bar. Current valid options are
+ 'toggle_shade', which will shade/unshade the window,
+ 'toggle_maximize' which will maximize/unmaximize the window,
+ 'toggle_maximize_horizontally' and 'toggle_maximize_vertically'
+ which will maximize/unmaximize the window in that direction only,
+ 'minimize' which will minimize the window,
+ 'shade' which will roll the window up,
+ 'menu' which will display the window menu,
+ 'lower' which will put the window behind all the others,
+ and 'none' which will not do anything.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/action_right_click_titlebar</key>
+ <applyto>/apps/marco/general/action_right_click_titlebar</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>menu</default>
+ <locale name="C">
+ <short>Action on title bar right-click</short>
+ <long>
+ This option determines the effects of right-clicking on the
+ title bar. Current valid options are
+ 'toggle_shade', which will shade/unshade the window,
+ 'toggle_maximize' which will maximize/unmaximize the window,
+ 'toggle_maximize_horizontally' and 'toggle_maximize_vertically'
+ which will maximize/unmaximize the window in that direction only,
+ 'minimize' which will minimize the window,
+ 'shade' which will roll the window up,
+ 'menu' which will display the window menu,
+ 'lower' which will put the window behind all the others,
+ and 'none' which will not do anything.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/auto_raise</key>
+ <applyto>/apps/marco/general/auto_raise</applyto>
+ <owner>marco</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Automatically raises the focused window</short>
+ <long>
+ If set to true, and the focus mode is either "sloppy" or "mouse"
+ then the focused window will be automatically raised after a
+ delay specified by the auto_raise_delay key. This is not related
+ to clicking on a window to raise it, nor to entering a window
+ during drag-and-drop.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/auto_raise_delay</key>
+ <applyto>/apps/marco/general/auto_raise_delay</applyto>
+ <owner>marco</owner>
+ <type>int</type>
+ <default>500</default>
+ <locale name="C">
+ <short>Delay in milliseconds for the auto raise option</short>
+ <long>
+ The time delay before raising a window if auto_raise is set to
+ true. The delay is given in thousandths of a second.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/theme</key>
+ <applyto>/apps/marco/general/theme</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>Spidey-Left</default>
+ <locale name="C">
+ <short>Current theme</short>
+ <long>
+ The theme determines the appearance of window borders,
+ titlebar, and so forth.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/titlebar_uses_system_font</key>
+ <applyto>/apps/marco/general/titlebar_uses_system_font</applyto>
+ <owner>marco</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Use standard system font in window titles</short>
+ <long>
+ If true, ignore the titlebar_font
+ option, and use the standard application font for window
+ titles.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/titlebar_font</key>
+ <applyto>/apps/marco/general/titlebar_font</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>Sans Bold 10</default>
+ <locale name="C">
+ <short>Window title font</short>
+ <long>
+ A font description string describing a font for window
+ titlebars. The size from the description will only be used if the
+ titlebar_font_size option is set to 0. Also, this option is
+ disabled if the titlebar_uses_desktop_font option is set to true.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/num_workspaces</key>
+ <applyto>/apps/marco/general/num_workspaces</applyto>
+ <owner>marco</owner>
+ <type>int</type>
+ <default>4</default>
+ <locale name="C">
+ <short>Number of workspaces</short>
+ <long>
+ Number of workspaces. Must be more than zero, and has a fixed
+ maximum to prevent making the desktop unusable by accidentally
+ asking for too many workspaces.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/visual_bell</key>
+ <applyto>/apps/marco/general/visual_bell</applyto>
+ <owner>marco</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Enable Visual Bell</short>
+ <long>
+ Turns on a visual indication when an application or the system
+ issues a 'bell' or 'beep'; useful for the hard-of-hearing and for
+ use in noisy environments.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/audible_bell</key>
+ <applyto>/apps/marco/general/audible_bell</applyto>
+ <owner>marco</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>System Bell is Audible</short>
+ <long>
+ Determines whether applications or the system can generate
+ audible 'beeps'; may be used in conjunction with 'visual bell' to
+ allow silent 'beeps'.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/visual_bell_type</key>
+ <applyto>/apps/marco/general/visual_bell_type</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>fullscreen</default>
+ <locale name="C">
+ <short>Visual Bell Type</short>
+ <long>
+ Tells Marco how to implement the visual indication that the
+ system bell or another application 'bell' indicator has been
+ rung. Currently there are two valid values, "fullscreen", which
+ causes a fullscreen white-black flash, and "frame_flash" which
+ causes the titlebar of the application which sent the bell signal
+ to flash. If the application which sent the bell is unknown (as
+ is usually the case for the default "system beep"), the currently
+ focused window's titlebar is flashed.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/compositing_manager</key>
+ <applyto>/apps/marco/general/compositing_manager</applyto>
+ <owner>marco</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Compositing Manager</short>
+ <long>
+ Determines whether Marco is a compositing manager.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/workspace_names/name</key>
+ <applyto>/apps/marco/workspace_names/name_1</applyto>
+ <applyto>/apps/marco/workspace_names/name_2</applyto>
+ <applyto>/apps/marco/workspace_names/name_3</applyto>
+ <applyto>/apps/marco/workspace_names/name_4</applyto>
+ <applyto>/apps/marco/workspace_names/name_5</applyto>
+ <applyto>/apps/marco/workspace_names/name_6</applyto>
+ <applyto>/apps/marco/workspace_names/name_7</applyto>
+ <applyto>/apps/marco/workspace_names/name_8</applyto>
+ <applyto>/apps/marco/workspace_names/name_9</applyto>
+ <applyto>/apps/marco/workspace_names/name_10</applyto>
+ <applyto>/apps/marco/workspace_names/name_11</applyto>
+ <applyto>/apps/marco/workspace_names/name_12</applyto>
+ <applyto>/apps/marco/workspace_names/name_13</applyto>
+ <applyto>/apps/marco/workspace_names/name_14</applyto>
+ <applyto>/apps/marco/workspace_names/name_15</applyto>
+ <applyto>/apps/marco/workspace_names/name_16</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>Name of workspace</short>
+ <long>
+ The name of a workspace.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/reduced_resources</key>
+ <applyto>/apps/marco/general/reduced_resources</applyto>
+ <owner>marco</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>If true, trade off usability for less resource usage</short>
+ <long>
+ If true, marco will give the user less feedback by using
+ wireframes, avoiding animations, or other means. This is a
+ significant reduction in usability for many users, but may allow
+ legacy applications to continue working, and may also be a
+ useful tradeoff for terminal servers. However, the wireframe
+ feature is disabled when accessibility is on.
+ </long>
+ </locale>
+ </schema>
+
+ <!-- Keybindings -->
+
+ <schema>
+ <key>/schemas/apps/marco/global_keybindings/run_command</key>
+ <applyto>/apps/marco/global_keybindings/run_command_1</applyto>
+ <applyto>/apps/marco/global_keybindings/run_command_2</applyto>
+ <applyto>/apps/marco/global_keybindings/run_command_3</applyto>
+ <applyto>/apps/marco/global_keybindings/run_command_4</applyto>
+ <applyto>/apps/marco/global_keybindings/run_command_5</applyto>
+ <applyto>/apps/marco/global_keybindings/run_command_6</applyto>
+ <applyto>/apps/marco/global_keybindings/run_command_7</applyto>
+ <applyto>/apps/marco/global_keybindings/run_command_8</applyto>
+ <applyto>/apps/marco/global_keybindings/run_command_9</applyto>
+ <applyto>/apps/marco/global_keybindings/run_command_10</applyto>
+ <applyto>/apps/marco/global_keybindings/run_command_11</applyto>
+ <applyto>/apps/marco/global_keybindings/run_command_12</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>disabled</default>
+ <locale name="C">
+ <short>Run a defined command</short>
+ <long>
+ The keybinding that runs the correspondingly-numbered
+ command in /apps/marco/keybinding_commands
+
+ The format looks like "&lt;Control&gt;a" or
+ "&lt;Shift&gt;&lt;Alt&gt;F1".
+
+ The parser is fairly liberal and allows lower or upper case,
+ and also abbreviations such as "&lt;Ctl&gt;" and
+ "&lt;Ctrl&gt;". If you set the option to the special string
+ "disabled", then there will be no keybinding for this
+ action.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/keybinding_commands/command</key>
+ <applyto>/apps/marco/keybinding_commands/command_1</applyto>
+ <applyto>/apps/marco/keybinding_commands/command_2</applyto>
+ <applyto>/apps/marco/keybinding_commands/command_3</applyto>
+ <applyto>/apps/marco/keybinding_commands/command_4</applyto>
+ <applyto>/apps/marco/keybinding_commands/command_5</applyto>
+ <applyto>/apps/marco/keybinding_commands/command_6</applyto>
+ <applyto>/apps/marco/keybinding_commands/command_7</applyto>
+ <applyto>/apps/marco/keybinding_commands/command_8</applyto>
+ <applyto>/apps/marco/keybinding_commands/command_9</applyto>
+ <applyto>/apps/marco/keybinding_commands/command_10</applyto>
+ <applyto>/apps/marco/keybinding_commands/command_11</applyto>
+ <applyto>/apps/marco/keybinding_commands/command_12</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default> </default>
+ <locale name="C">
+ <short>Commands to run in response to keybindings</short>
+ <long>
+ The /apps/marco/global_keybindings/run_command_N
+ keys define keybindings that correspond to these commands.
+ Pressing the keybinding for run_command_N will
+ execute command_N.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/keybinding_commands/command_screenshot</key>
+ <applyto>/apps/marco/keybinding_commands/command_screenshot</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>mate-screenshot</default>
+ <locale name="C">
+ <short>The screenshot command</short>
+ <long>
+ The /apps/marco/global_keybindings/run_command_screenshot
+ key defines a keybinding which causes the command specified
+ by this setting to be invoked.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/keybinding_commands/command_window_screenshot</key>
+ <applyto>/apps/marco/keybinding_commands/command_window_screenshot</applyto>
+ <owner>marco</owner>
+ <type>string</type>
+ <default>mate-screenshot --window</default>
+ <locale name="C">
+ <short>The window screenshot command</short>
+ <long>
+ The /apps/marco/global_keybindings/run_command_window_screenshot
+ key defines a keybinding which causes the command specified
+ by this setting to be invoked.
+ </long>
+ </locale>
+ </schema>
+
+ <!-- Schemas below are generated by schema-bindings.c when this file
+ becomes marco.schemas.in
+ -->
+ <!-- GENERATED -->
+
+ <!-- Not used and/or crackrock -->
+
+ <schema>
+ <key>/schemas/apps/marco/general/application_based</key>
+ <applyto>/apps/marco/general/application_based</applyto>
+ <owner>marco</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>(Not implemented) Navigation works in terms of applications not windows</short>
+ <long>
+ If true, then Marco works in terms of applications rather than
+ windows. The concept is a bit abstract, but in general an
+ application-based setup is more like the Mac and less like
+ Windows. When you focus a window in application-based mode, all
+ the windows in the application will be raised. Also, in
+ application-based mode, focus clicks are not passed through to
+ windows in other applications. Application-based mode is,
+ however, largely unimplemented at the moment.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/marco/general/disable_workarounds</key>
+ <applyto>/apps/marco/general/disable_workarounds</applyto>
+ <owner>marco</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Disable misfeatures that are required by old or broken
+ applications</short>
+ <long>
+ Some applications disregard specifications in ways that result in
+ window manager misfeatures. This option puts Marco in a
+ rigorously correct mode, which gives a more consistent user
+ interface, provided one does not need to run any misbehaving
+ applications.
+ </long>
+ </locale>
+ </schema>
+
+ </schemalist>
+</mateconfschemafile>
+
+
+
+
diff --git a/src/themes/ClearlooksRe/metacity-theme-1.xml b/src/themes/ClearlooksRe/metacity-theme-1.xml
new file mode 100644
index 00000000..fbc5a174
--- /dev/null
+++ b/src/themes/ClearlooksRe/metacity-theme-1.xml
@@ -0,0 +1,1013 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<metacity_theme>
+<info>
+ <name>ClearlooksRe</name>
+ <author>Daniel Borgmann &lt;[email protected]&gt;, Andrea Cimitan &lt;[email protected]&gt;, Germán Augusto Perugorria &lt;[email protected]&gt;</author>
+ <copyright>&#194; 2005-2007 Daniel Borgmann, Andrea Cimitan. 2010 Perberos</copyright>
+ <date>December, 2010</date>
+ <description>The Clearlooks "Gummy" Metacity Theme</description>
+</info>
+
+<!-- ::: GEOMETRY ::: -->
+<frame_geometry name="normal" rounded_top_left="true" rounded_top_right="true" rounded_bottom_left="false" rounded_bottom_right="false">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="4"/>
+ <distance name="right_titlebar_edge" value="4"/>
+ <aspect_ratio name="button" value="1.0"/>
+ <distance name="title_vertical_pad" value="0"/>
+ <border name="title_border" left="2" right="2" top="4" bottom="3"/>
+ <border name="button_border" left="1" right="1" top="2" bottom="2"/>
+</frame_geometry>
+
+<frame_geometry name="shaded" parent="normal" rounded_top_left="true" rounded_top_right="true" rounded_bottom_left="true" rounded_bottom_right="true"/>
+
+<frame_geometry name="normal_maximized" parent="normal" rounded_top_left="false" rounded_top_right="false" rounded_bottom_left="false" rounded_bottom_right="false">
+ <!-- strip frame spacing off the normal geometry when maximised -->
+ <distance name="left_width" value="0"/>
+ <distance name="right_width" value="0"/>
+ <distance name="bottom_height" value="1"/>
+ <distance name="left_titlebar_edge" value="1"/>
+ <distance name="right_titlebar_edge" value="1"/>
+</frame_geometry>
+
+<frame_geometry name="utility" title_scale="small" rounded_top_left="false" rounded_top_right="false" rounded_bottom_left="false" rounded_bottom_right="false">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="4"/>
+ <distance name="right_titlebar_edge" value="4"/>
+ <distance name="title_vertical_pad" value="0"/>
+ <border name="title_border" left="2" right="2" top="4" bottom="3"/>
+ <border name="button_border" left="0" right="0" top="2" bottom="2"/>
+ <aspect_ratio name="button" value="1"/>
+</frame_geometry>
+
+<frame_geometry name="border" has_title="false">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="0"/>
+ <distance name="right_titlebar_edge" value="0"/>
+ <distance name="button_width" value="0"/>
+ <distance name="button_height" value="0"/>
+ <distance name="title_vertical_pad" value="4"/>
+ <border name="title_border" left="0" right="0" top="0" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="0" bottom="0"/>
+</frame_geometry>
+
+<!-- button minimum size -->
+<constant name="Bmin" value="7"/>
+<!-- button inside padding -->
+<constant name="Bpad" value="6"/>
+
+<!-- ::: CORNERS ::: -->
+<draw_ops name="corners_outline_selected_top">
+ <!-- top left -->
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="1" y1="3" x2="1" y2="3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.73" x1="1" y1="4" x2="1" y2="4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="2" y1="2" x2="2" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="3" y1="1" x2="3" y2="1"/>
+ <line color="shade/gtk:bg[SELECTED]/0.73" x1="4" y1="1" x2="4" y2="1"/>
+
+ <!-- top right -->
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="width-2" y1="3" x2="width-2" y2="3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.73" x1="width-2" y1="4" x2="width-2" y2="4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="width-3" y1="2" x2="width-3" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="width-4" y1="1" x2="width-4" y2="1"/>
+ <line color="shade/gtk:bg[SELECTED]/0.73" x1="width-5" y1="1" x2="width-5" y2="1"/>
+</draw_ops>
+
+<draw_ops name="corners_outline_top">
+ <!-- top left -->
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="1" y1="3" x2="1" y2="3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.68" x1="1" y1="4" x2="1" y2="4"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="2" y1="2" x2="2" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="3" y1="1" x2="3" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.68" x1="4" y1="1" x2="4" y2="1"/>
+
+ <!-- top right -->
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="width-2" y1="3" x2="width-2" y2="3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.68" x1="width-2" y1="4" x2="width-2" y2="4"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="width-3" y1="2" x2="width-3" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="width-4" y1="1" x2="width-4" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.68" x1="width-5" y1="1" x2="width-5" y2="1"/>
+</draw_ops>
+
+<draw_ops name="corners_outline_selected_bottom">
+ <!-- bottom left -->
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="1" y1="height-4" x2="1" y2="height-5"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="2" y1="height-3" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="2" y1="height-2" x2="4" y2="height-2"/>
+
+ <!-- bottom right -->
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="width-2" y1="height-4" x2="width-2" y2="height-5"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="width-3" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="width-4" y1="height-2" x2="width-5" y2="height-2"/>
+</draw_ops>
+
+<draw_ops name="corners_outline_bottom">
+ <!-- bottom left -->
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="1" y1="height-4" x2="1" y2="height-5"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="2" y1="height-3" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="2" y1="height-2" x2="4" y2="height-2"/>
+
+ <!-- bottom right -->
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="width-2" y1="height-4" x2="width-2" y2="height-5"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="width-3" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="width-4" y1="height-2" x2="width-5" y2="height-2"/>
+</draw_ops>
+
+<draw_ops name="corners_highlight">
+ <!-- ** corner highlight for left top ** -->
+ <line color="shade/gtk:bg[SELECTED]/1.18" x1="2" y1="3" x2="2" y2="4"/>
+ <line color="shade/gtk:bg[SELECTED]/1.18" x1="3" y1="2" x2="4" y2="2"/>
+
+ <!-- ** corner highlight for right top ** -->
+ <line color="shade/gtk:bg[SELECTED]/0.98" x1="width-3" y1="3" x2="width-3" y2="4"/>
+ <line color="shade/gtk:bg[SELECTED]/1.16" x1="width-5" y1="2" x2="width-4" y2="2"/>
+
+ <!-- ** corner highlight for left bottom ** -->
+ <!--<line color="shade/gtk:bg[NORMAL]/1.3" x1="2" y1="height-4" x2="2" y2="height-5"/>
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="3" y1="height-3" x2="4" y2="height-3"/>-->
+
+ <!-- ** corner highlight for right bottom ** -->
+ <!--<line color="shade/gtk:bg[NORMAL]/0.88" x1="width-3" y1="height-4" x2="width-3" y2="height-5"/>
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="width-4" y1="height-3" x2="width-5" y2="height-3"/>-->
+</draw_ops>
+
+<draw_ops name="corners_highlight_unfocused">
+ <!-- ** corner highlight for left top ** -->
+ <line color="shade/gtk:bg[NORMAL]/1.05" x1="2" y1="3" x2="2" y2="4"/>
+ <line color="shade/gtk:bg[NORMAL]/1.05" x1="3" y1="2" x2="4" y2="2"/>
+
+ <!-- ** corner highlight for right top ** -->
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="width-3" y1="3" x2="width-3" y2="4"/>
+ <line color="shade/gtk:bg[NORMAL]/1.04" x1="width-5" y1="2" x2="width-4" y2="2"/>
+
+ <!-- ** corner highlight for left bottom ** -->
+ <!--<line color="shade/gtk:bg[NORMAL]/1.3" x1="2" y1="height-4" x2="2" y2="height-5"/>
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="3" y1="height-3" x2="4" y2="height-3"/>-->
+
+ <!-- ** corner highlight for right bottom ** -->
+ <!--<line color="shade/gtk:bg[NORMAL]/0.88" x1="width-3" y1="height-4" x2="width-3" y2="height-5"/>
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="width-4" y1="height-3" x2="width-5" y2="height-3"/>-->
+</draw_ops>
+
+<draw_ops name="corners_highlight_shaded">
+ <!-- ** corner highlight for left top ** -->
+ <line color="shade/gtk:bg[SELECTED]/1.18" x1="2" y1="3" x2="2" y2="4"/>
+ <line color="shade/gtk:bg[SELECTED]/1.18" x1="3" y1="2" x2="4" y2="2"/>
+
+ <!-- ** corner highlight for right top ** -->
+ <line color="shade/gtk:bg[SELECTED]/0.98" x1="width-3" y1="3" x2="width-3" y2="4"/>
+ <line color="shade/gtk:bg[SELECTED]/1.16" x1="width-5" y1="2" x2="width-4" y2="2"/>
+
+ <!-- ** corner highlight for left bottom ** -->
+ <line color="shade/gtk:bg[SELECTED]/1.08" x1="2" y1="height-4" x2="2" y2="height-5"/>
+ <line color="shade/gtk:bg[SELECTED]/0.98" x1="3" y1="height-3" x2="4" y2="height-3"/>
+
+ <!-- ** corner highlight for right bottom ** -->
+ <line color="shade/gtk:bg[SELECTED]/0.98" x1="width-3" y1="height-4" x2="width-3" y2="height-5"/>
+ <line color="shade/gtk:bg[SELECTED]/0.98" x1="width-4" y1="height-3" x2="width-5" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="corners_highlight_shaded_unfocused">
+ <!-- ** corner highlight for left top ** -->
+ <line color="shade/gtk:bg[NORMAL]/1.05" x1="2" y1="3" x2="2" y2="4"/>
+ <line color="shade/gtk:bg[NORMAL]/1.05" x1="3" y1="2" x2="4" y2="2"/>
+
+ <!-- ** corner highlight for right top ** -->
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="width-3" y1="3" x2="width-3" y2="4"/>
+ <line color="shade/gtk:bg[NORMAL]/1.04" x1="width-5" y1="2" x2="width-4" y2="2"/>
+
+ <!-- ** corner highlight for left bottom ** -->
+ <line color="shade/gtk:bg[NORMAL]/1.02" x1="2" y1="height-4" x2="2" y2="height-5"/>
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="3" y1="height-3" x2="4" y2="height-3"/>
+
+ <!-- ** corner highlight for right bottom ** -->
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="width-3" y1="height-4" x2="width-3" y2="height-5"/>
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="width-4" y1="height-3" x2="width-5" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="window_bg">
+ <rectangle color="gtk:bg[NORMAL]" filled="true" x="0" y="0" width="width" height="height"/>
+</draw_ops>
+
+<!-- ::: BEVEL FOCUSED ::: -->
+<draw_ops name="bevel">
+ <include name="window_bg"/>
+ <!-- ** titlebar outline ** -->
+ <rectangle color="shade/gtk:bg[SELECTED]/0.55" filled="false" x="0" y="0" width="width - 1" height="((title_height + 6) `max` (top_height - 2))"/>
+
+ <!-- ** 3d beveled frame ** -->
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="1" y1="height - 2" x2="width - 2" y2="height - 2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="width - 2" y1="3" x2="width - 2" y2="height - 2"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="3" y1="1" x2="width - 4" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="1" y1="3" x2="1" y2="height - 2"/>
+
+ <line color="shade/gtk:bg[SELECTED]/0.94" x1="2" y1="((title_height + 5) `max` (top_height - 3))" x2="width - 3" y2="((title_height + 5) `max` (top_height - 3))"/>
+ <line color="shade/gtk:bg[SELECTED]/0.95" x1="width - 2" y1="2" x2="width - 2" y2="((title_height + 6) `max` (top_height - 2))"/>
+ <line color="shade/gtk:bg[SELECTED]/1.18" x1="1" y1="1" x2="width - 2" y2="1"/>
+ <line color="shade/gtk:bg[SELECTED]/1.1" x1="1" y1="2" x2="1" y2="((title_height + 5) `max` (top_height - 3))"/>
+
+ <!-- ** fancy gradient ** -->
+ <gradient type="vertical" x="2" y="top_height/2" width="width-4" height="top_height/2-1">
+ <color value="shade/gtk:bg[SELECTED]/1.0"/>
+ <color value="shade/gtk:bg[SELECTED]/0.94"/>
+ </gradient>
+ <gradient type="vertical" x="2" y="2" width="width-4" height="top_height/2-2">
+ <color value="shade/gtk:bg[SELECTED]/1.08"/>
+ <color value="shade/gtk:bg[SELECTED]/1.02"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="((title_height + 6) `max` (top_height - 2))" x2="width - 2" y2="((title_height + 6) `max` (top_height - 2))"/>
+
+ <!-- ** border outline ** -->
+ <line color="shade/gtk:bg[NORMAL]/0.45" x1="0" y1="((title_height + 6) `max` (top_height - 2))" x2="0" y2="height"/>
+ <line color="shade/gtk:bg[NORMAL]/0.45" x1="width - 1" y1="((title_height + 6) `max` (top_height - 2))" x2="width - 1" y2="height"/>
+ <line color="shade/gtk:bg[NORMAL]/0.45" x1="1" y1="height - 1" x2="width - 2" y2="height - 1"/>
+</draw_ops>
+
+<draw_ops name="bevel_maximized">
+ <!-- ** 3d beveled frame ** -->
+ <line color="shade/gtk:bg[SELECTED]/0.55" x1="0" y1="0" x2="width" y2="0"/>
+ <line color="shade/gtk:bg[SELECTED]/1.18" x1="0" y1="1" x2="width" y2="1"/>
+ <line color="shade/gtk:bg[SELECTED]/0.94" x1="0" y1="((title_height + 5) `max` (top_height - 3))" x2="width" y2="((title_height + 5) `max` (top_height - 3))"/>
+
+ <!-- ** fancy gradient ** -->
+ <gradient type="vertical" x="0" y="top_height/2" width="width" height="top_height/2-1">
+ <color value="shade/gtk:bg[SELECTED]/1.0"/>
+ <color value="shade/gtk:bg[SELECTED]/0.94"/>
+ </gradient>
+ <gradient type="vertical" x="0" y="1" width="width" height="top_height/2-1">
+ <color value="shade/gtk:bg[SELECTED]/1.08"/>
+ <color value="shade/gtk:bg[SELECTED]/1.02"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="0" y1="((title_height + 6) `max` (top_height - 2))" x2="width" y2="((title_height + 6) `max` (top_height - 2))"/>
+ <line color="shade/gtk:bg[SELECTED]/0.55" x1="0" y1="height-1" x2="width" y2="height-1"/>
+</draw_ops>
+
+<draw_ops name="round_bevel">
+ <include name="bevel"/>
+ <include name="corners_outline_selected_top"/>
+ <!--<include name="corners_outline_bottom"/>-->
+ <include name="corners_highlight"/>
+</draw_ops>
+
+<draw_ops name="bevel_shaded">
+ <include name="bevel"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="0" y1="height-1" x2="width" y2="height-1"/>
+</draw_ops>
+
+<draw_ops name="round_bevel_shaded">
+ <include name="bevel"/>
+ <include name="corners_outline_selected_top"/>
+ <include name="corners_outline_selected_bottom"/>
+ <include name="corners_highlight_shaded"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="5" y1="height-1" x2="width-6" y2="height-1"/>
+</draw_ops>
+
+<!-- ::: BEVEL UNFOCUSED ::: -->
+<draw_ops name="bevel_unfocused">
+ <include name="window_bg"/>
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="1" y1="height - 2" x2="width - 2" y2="height - 2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="width - 2" y1="2" x2="width - 2" y2="height - 2"/>
+ <line color="shade/gtk:bg[NORMAL]/1.05" x1="1" y1="1" x2="width - 2" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/1.03" x1="1" y1="2" x2="1" y2="height - 2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.89" x1="2" y1="((title_height + 5) `max` (top_height - 3))" x2="width - 3" y2="((title_height + 5) `max` (top_height - 3))"/>
+
+ <!-- ** fancy gradient ** -->
+ <gradient type="vertical" x="2" y="top_height/2" width="width-4" height="top_height/2-1">
+ <color value="shade/gtk:bg[NORMAL]/0.93"/>
+ <color value="shade/gtk:bg[NORMAL]/0.89"/>
+ </gradient>
+ <gradient type="vertical" x="2" y="2" width="width-4" height="top_height/2-2">
+ <color value="shade/gtk:bg[NORMAL]/0.99"/>
+ <color value="shade/gtk:bg[NORMAL]/0.95"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/0.65" x1="1" y1="((title_height + 6) `max` (top_height - 2))" x2="width - 2" y2="((title_height + 6) `max` (top_height - 2))"/>
+ <rectangle color="shade/gtk:bg[NORMAL]/0.55" filled="false" x="0" y="0" width="width - 1" height="height - 1"/>
+</draw_ops>
+
+<draw_ops name="bevel_maximized_unfocused">
+ <!-- ** 3d beveled frame ** -->
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="0" y1="0" x2="width" y2="0"/>
+ <line color="shade/gtk:bg[NORMAL]/1.05" x1="0" y1="1" x2="width" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.89" x1="0" y1="((title_height + 5) `max` (top_height - 3))" x2="width" y2="((title_height + 5) `max` (top_height - 3))"/>
+
+ <!-- ** fancy gradient ** -->
+ <gradient type="vertical" x="0" y="top_height/2" width="width" height="top_height/2-1">
+ <color value="shade/gtk:bg[NORMAL]/0.93"/>
+ <color value="shade/gtk:bg[NORMAL]/0.89"/>
+ </gradient>
+ <gradient type="vertical" x="0" y="2" width="width" height="top_height/2-2">
+ <color value="shade/gtk:bg[NORMAL]/0.99"/>
+ <color value="shade/gtk:bg[NORMAL]/0.95"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/0.65" x1="0" y1="((title_height + 6) `max` (top_height - 2))" x2="width" y2="((title_height + 6) `max` (top_height - 2))"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="0" y1="height-1" x2="width" y2="height-1"/>
+</draw_ops>
+
+<draw_ops name="round_bevel_unfocused">
+ <include name="bevel_unfocused"/>
+ <include name="corners_outline_top"/>
+ <!--<include name="corners_outline_bottom"/>-->
+ <include name="corners_highlight_unfocused"/>
+</draw_ops>
+
+<draw_ops name="round_bevel_unfocused_shaded">
+ <include name="bevel_unfocused"/>
+ <include name="corners_outline_top"/>
+ <include name="corners_outline_bottom"/>
+ <include name="corners_highlight_shaded_unfocused"/>
+</draw_ops>
+
+<!-- ::: BORDER ::: -->
+<draw_ops name="border">
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="1" y1="height - 2" x2="width - 2" y2="height - 2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="width - 2" y1="1" x2="width - 2" y2="height - 2"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="1" y1="1" x2="width - 2" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="1" y1="1" x2="1" y2="height - 2"/>
+
+ <rectangle color="shade/gtk:bg[NORMAL]/0.55" filled="false" x="0" y="0" width="width - 1" height="height - 1"/>
+</draw_ops>
+
+<!-- ::: TITLES ::: -->
+<draw_ops name="title_text">
+ <title color="shade/gtk:bg[SELECTED]/0.7" x="((3 `max` (width-title_width)) / 2)" y="(((height - title_height) / 2) `max` 0) + 1"/>
+ <title color="shade/gtk:bg[SELECTED]/0.7" x="((3 `max` (width-title_width)) / 2)+1" y="(((height - title_height) / 2) `max` 0)"/>
+ <title color="shade/gtk:bg[SELECTED]/0.7" x="((3 `max` (width-title_width)) / 2)-1" y="(((height - title_height) / 2) `max` 0)"/>
+ <title color="shade/gtk:bg[SELECTED]/0.7" x="((3 `max` (width-title_width)) / 2)" y="(((height - title_height) / 2) `max` 0)-1"/>
+ <title color="#FFFFFF" x="(3 `max` (width-title_width)) / 2" y="(((height - title_height) / 2) `max` 0)"/>
+</draw_ops>
+
+<draw_ops name="title_text_unfocused">
+ <!--<title color="shade/gtk:bg[NORMAL]/1.07" x="5 `max` (width-title_width)/2+1" y="1 `max` ((height-title_height)/2)+1"/>-->
+ <title color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" x="4 `max` (width-title_width)/2" y="0 `max` ((height-title_height)/2)"/>
+</draw_ops>
+
+<draw_ops name="title">
+ <include name="title_text"/>
+</draw_ops>
+
+<draw_ops name="title_unfocused">
+ <include name="title_text_unfocused"/>
+</draw_ops>
+
+<!-- ::: BUTTONS ::: -->
+<draw_ops name="button_bg">
+ <!-- inset -->
+ <gradient type="vertical" x="0" y="3" width="width" height="height-6">
+ <color value="shade/gtk:bg[SELECTED]/0.96"/>
+ <color value="shade/gtk:bg[SELECTED]/1.05"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[SELECTED]/1.00" x1="2" y1="0" x2="width-3" y2="0"/>
+ <line color="shade/gtk:bg[SELECTED]/0.99" x1="1" y1="1" x2="width-2" y2="1"/>
+ <line color="shade/gtk:bg[SELECTED]/0.99" x1="0" y1="2" x2="width-1" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.98" x1="3" y1="0" x2="width-4" y2="0"/>
+
+ <line color="shade/gtk:bg[SELECTED]/0.91" x1="2" y1="1" x2="width-3" y2="1"/>
+ <line color="shade/gtk:bg[SELECTED]/0.90" x1="1" y1="2" x2="width-2" y2="2"/>
+
+ <line color="shade/gtk:bg[SELECTED]/1.03" x1="2" y1="height-1" x2="width-3" y2="height-1"/>
+ <line color="shade/gtk:bg[SELECTED]/1.00" x1="1" y1="height-2" x2="width-2" y2="height-2"/>
+ <line color="shade/gtk:bg[SELECTED]/1.01" x1="0" y1="height-3" x2="width-1" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/1.06" x1="3" y1="height-1" x2="width-4" y2="height-1"/>
+
+ <line color="shade/gtk:bg[SELECTED]/1.02" x1="2" y1="height-2" x2="width-3" y2="height-2"/>
+ <line color="shade/gtk:bg[SELECTED]/1.03" x1="1" y1="height-3" x2="width-2" y2="height-3"/>
+
+ <!-- border outline -->
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="3" y1="1" x2="width-4" y2="1"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="3" y1="height-2" x2="width-4" y2="height-2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="1" y1="3" x2="1" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="width-2" y1="3" x2="width-2" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="2" y1="2" x2="width-3" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+
+ <!-- border smooth effect -->
+ <line color="shade/gtk:bg[SELECTED]/1.02" x1="3" y1="2" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/1.00" x1="2" y1="3" x2="2" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.90" x1="width-3" y1="3" x2="width-3" y2="height-4"/>
+
+ <!-- inside highlight -->
+ <line color="shade/gtk:bg[SELECTED]/1.18" x1="4" y1="2" x2="width-5" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/1.1" x1="2" y1="4" x2="2" y2="height-5"/>
+ <!-- inside shadow -->
+ <line color="shade/gtk:bg[SELECTED]/1.0" x1="width-3" y1="4" x2="width-3" y2="height-5"/>
+
+ <!-- fill gradient -->
+ <gradient type="vertical" x="3" y="3" width="width-6" height="(height)/2-1">
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ <color value="shade/gtk:bg[SELECTED]/1.02"/>
+ </gradient>
+ <gradient type="vertical" x="3" y="(height)/2" width="width-6" height="(height)/2-2">
+ <color value="shade/gtk:bg[SELECTED]/1.0"/>
+ <color value="shade/gtk:bg[SELECTED]/0.92"/>
+ </gradient>
+
+ <!-- bottom border smooth effect -->
+ <line color="shade/gtk:bg[SELECTED]/0.84" x1="3" y1="height-3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.92" x1="4" y1="height-3" x2="width-5" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="button_bg_unfocused">
+ <!-- inset -->
+ <gradient type="vertical" x="0" y="3" width="width" height="height-6">
+ <color value="shade/gtk:bg[NORMAL]/0.92"/>
+ <color value="shade/gtk:bg[NORMAL]/0.96"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/0.93" x1="2" y1="0" x2="width-3" y2="0"/>
+ <line color="shade/gtk:bg[NORMAL]/0.92" x1="1" y1="1" x2="width-2" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.92" x1="0" y1="2" x2="width-1" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.91" x1="3" y1="0" x2="width-4" y2="0"/>
+
+ <line color="shade/gtk:bg[NORMAL]/0.87" x1="2" y1="1" x2="width-3" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.86" x1="1" y1="2" x2="width-2" y2="2"/>
+
+ <line color="shade/gtk:bg[NORMAL]/0.945" x1="2" y1="height-1" x2="width-3" y2="height-1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.93" x1="1" y1="height-2" x2="width-2" y2="height-2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.935" x1="0" y1="height-3" x2="width-1" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.96" x1="3" y1="height-1" x2="width-4" y2="height-1"/>
+
+ <line color="shade/gtk:bg[NORMAL]/0.94" x1="2" y1="height-2" x2="width-3" y2="height-2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.95" x1="1" y1="height-3" x2="width-2" y2="height-3"/>
+
+ <!-- border outline -->
+ <line color="shade/gtk:bg[NORMAL]/0.6" x1="3" y1="1" x2="width-4" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.6" x1="3" y1="height-2" x2="width-4" y2="height-2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.6" x1="1" y1="3" x2="1" y2="height-4"/>
+ <line color="shade/gtk:bg[NORMAL]/0.6" x1="width-2" y1="3" x2="width-2" y2="height-4"/>
+
+ <line color="shade/gtk:bg[NORMAL]/0.6" x1="2" y1="2" x2="width-3" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.6" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+
+ <!-- border smooth effect -->
+ <line color="shade/gtk:bg[NORMAL]/1.02" x1="3" y1="2" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/1.00" x1="2" y1="3" x2="2" y2="height-4"/>
+ <line color="shade/gtk:bg[NORMAL]/0.95" x1="width-3" y1="3" x2="width-3" y2="height-4"/>
+
+ <!-- inside highlight -->
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="4" y1="2" x2="width-5" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/1.1" x1="2" y1="4" x2="2" y2="height-5"/>
+ <!-- inside shadow -->
+ <line color="shade/gtk:bg[NORMAL]/1.05" x1="width-3" y1="4" x2="width-3" y2="height-5"/>
+
+ <!-- fill gradient -->
+ <gradient type="vertical" x="3" y="3" width="width-6" height="(height)/2-1">
+ <color value="shade/gtk:bg[NORMAL]/1.15"/>
+ <color value="shade/gtk:bg[NORMAL]/1.07"/>
+ </gradient>
+ <gradient type="vertical" x="3" y="(height)/2" width="width-6" height="(height)/2-2">
+ <color value="shade/gtk:bg[NORMAL]/1.05"/>
+ <color value="shade/gtk:bg[NORMAL]/0.97"/>
+ </gradient>
+
+ <!-- bottom border smooth effect -->
+ <line color="shade/gtk:bg[NORMAL]/0.89" x1="3" y1="height-3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.97" x1="4" y1="height-3" x2="width-5" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="button_bg_prelight">
+ <include name="button_bg"/>
+ <tint color="shade/gtk:bg[SELECTED]/1.5" alpha="0.2" x="3" y="3" width="width-5" height="height-5"/>
+ <line color="shade/gtk:bg[SELECTED]/0.6" x1="width-3" y1="height-3" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="button_bg_pressed">
+ <!-- outside highlight -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="height-4">
+ <color value="shade/gtk:bg[SELECTED]/1.2"/>
+ <color value="shade/gtk:bg[SELECTED]/1.0"/>
+ </gradient>
+ <gradient type="vertical" x="width-1" y="3" width="1" height="height-6">
+ <color value="shade/gtk:bg[SELECTED]/1.2"/>
+ <color value="shade/gtk:bg[SELECTED]/1.0"/>
+ </gradient>
+ <line color="shade/gtk:bg[SELECTED]/1.0" x1="2" y1="height-2" x2="width-3" y2="height-2"/>
+ <line color="shade/gtk:bg[SELECTED]/1.0" x1="3" y1="height-1" x2="width-4" y2="height-1"/>
+
+ <!-- border outline -->
+ <line color="shade/gtk:bg[SELECTED]/0.55" x1="3" y1="1" x2="width-4" y2="1"/>
+ <line color="shade/gtk:bg[SELECTED]/0.55" x1="3" y1="height-2" x2="width-4" y2="height-2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.55" x1="1" y1="3" x2="1" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.55" x1="width-2" y1="3" x2="width-2" y2="height-4"/>
+
+ <line color="shade/gtk:bg[SELECTED]/0.55" x1="2" y1="2" x2="width-3" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.55" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+
+ <!-- inside shadow -->
+ <line color="shade/gtk:bg[SELECTED]/0.9" x1="3" y1="2" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="3" x2="2" y2="height-4"/>
+
+ <!-- fill gradient -->
+ <gradient type="vertical" x="3" y="3" width="width-5" height="height-6">
+ <color value="shade/gtk:bg[SELECTED]/0.95"/>
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[SELECTED]/0.9" x1="3" y1="height-3" x2="width-4" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="button_bg_unfocused_prelight">
+ <include name="button_bg_unfocused"/>
+ <tint color="shade/gtk:bg[NORMAL]/1.5" alpha="0.3" x="3" y="3" width="width-5" height="height-5"/>
+ <line color="shade/gtk:bg[NORMAL]/0.6" x1="width-3" y1="height-3" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="button_bg_unfocused_pressed">
+ <!-- outside highlight -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="height-4">
+ <color value="shade/gtk:bg[NORMAL]/1.25"/>
+ <color value="shade/gtk:bg[NORMAL]/1.05"/>
+ </gradient>
+ <gradient type="vertical" x="width-1" y="3" width="1" height="height-6">
+ <color value="shade/gtk:bg[NORMAL]/1.25"/>
+ <color value="shade/gtk:bg[NORMAL]/1.05"/>
+ </gradient>
+ <line color="shade/gtk:bg[NORMAL]/1.05" x1="2" y1="height-2" x2="width-3" y2="height-2"/>
+ <line color="shade/gtk:bg[NORMAL]/1.05" x1="3" y1="height-1" x2="width-4" y2="height-1"/>
+
+ <!-- border outline -->
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="3" y1="1" x2="width-4" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="3" y1="height-2" x2="width-4" y2="height-2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="1" y1="3" x2="1" y2="height-4"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="width-2" y1="3" x2="width-2" y2="height-4"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="2" y1="2" x2="width-3" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.55" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+
+ <!-- inside shadow -->
+ <line color="shade/gtk:bg[NORMAL]/0.8" x1="3" y1="2" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.75" x1="2" y1="3" x2="2" y2="height-4"/>
+
+ <!-- fill gradient -->
+ <gradient type="vertical" x="3" y="3" width="width-5" height="height-6">
+ <color value="shade/gtk:bg[NORMAL]/0.9"/>
+ <color value="shade/gtk:bg[NORMAL]/0.85"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/0.85" x1="3" y1="height-3" x2="width-4" y2="height-3"/>
+</draw_ops>
+
+<!-- ::: ICONS ::: -->
+<!--
+ using a minimum icon size until there is a proper way to specify relative sizes
+ unfortunately it's logically impossible to always center the icons on non-square
+ buttons (utility windows) without distortion.
+
+ icon_size = (Bmin`max`height-Bpad*2)
+ hpadding = (width - icon_size) / 2 = ((width-(Bmin`max`height-Bpad*2))/2)
+ vpadding = (height - icon_size) / 2 = ((height-(Bmin`max`height-Bpad*2))/2)
+-->
+
+<!-- menu icon -->
+<draw_ops name="menu_button_icon">
+ <!--<icon x="0" y="0" width="width" height="height"/>-->
+ <icon x="(width-mini_icon_width)/2" y="(height-mini_icon_height)/2" width="mini_icon_width" height="mini_icon_height"/>
+</draw_ops>
+
+<draw_ops name="menu_button_icon_unfocused">
+ <!--<icon x="0" y="0" width="width" height="height" alpha="0.5"/>-->
+ <icon x="(width-mini_icon_width)/2" y="(height-mini_icon_height)/2" width="mini_icon_width" height="mini_icon_height"/>
+</draw_ops>
+
+<draw_ops name="menu_button_normal">
+ <include name="menu_button_icon"/>
+</draw_ops>
+<draw_ops name="menu_button_pressed">
+ <include name="menu_button_icon"/>
+</draw_ops>
+<draw_ops name="menu_button_unfocused">
+ <include name="menu_button_icon_unfocused"/>
+</draw_ops>
+
+<!-- close icon -->
+<draw_ops name="close_button_icon">
+ <!-- outside border -->
+
+ <!-- main cross -->
+ <line color="shade/gtk:bg[SELECTED]/0.7" width="4"
+ x1="((width-(Bmin`max`height-Bpad*2))/2)" y1="((height-(Bmin`max`height-Bpad*2))/2)"
+ x2="width - ((width-(Bmin`max`height-Bpad*2))/2) - 1" y2="height - ((height-(Bmin`max`height-Bpad*2))/2) - 1"/>
+ <line color="shade/gtk:bg[SELECTED]/0.7" width="4"
+ x1="((width-(Bmin`max`height-Bpad*2))/2)" y1="height - ((height-(Bmin`max`height-Bpad*2))/2) - 1"
+ x2="width - ((width-(Bmin`max`height-Bpad*2))/2) - 1" y2="((height-(Bmin`max`height-Bpad*2))/2)"/>
+ <!-- top-left -->
+ <tint color="shade/gtk:bg[SELECTED]/0.7" alpha="1.0"
+ x="((width-(Bmin`max`height-Bpad*2))/2)" y="((height-(Bmin`max`height-Bpad*2))/2)-1"
+ width="2" height="1"/>
+ <tint color="shade/gtk:bg[SELECTED]/0.7" alpha="1.0"
+ x="((width-(Bmin`max`height-Bpad*2))/2)-1" y="((height-(Bmin`max`height-Bpad*2))/2)/1"
+ width="1" height="2"/>
+ <!-- top-right -->
+ <tint color="shade/gtk:bg[SELECTED]/0.7" alpha="1.0"
+ x="width - ((width-(Bmin`max`height-Bpad*2))/2) -2" y="((height-(Bmin`max`height-Bpad*2))/2)-1"
+ width="2" height="1"/>
+ <tint color="shade/gtk:bg[SELECTED]/0.7" alpha="1.0"
+ x="width - ((width-(Bmin`max`height-Bpad*2))/2)" y="((height-(Bmin`max`height-Bpad*2))/2)"
+ width="1" height="2"/>
+ <!-- bottom-left -->
+ <tint color="shade/gtk:bg[SELECTED]/0.7" alpha="1.0"
+ x="((width-(Bmin`max`height-Bpad*2))/2)" y="height - ((height-(Bmin`max`height-Bpad*2))/2)"
+ width="2" height="1"/>
+ <tint color="shade/gtk:bg[SELECTED]/0.7" alpha="1.0"
+ x="((width-(Bmin`max`height-Bpad*2))/2)-1" y="height - ((height-(Bmin`max`height-Bpad*2))/2)-2"
+ width="1" height="2"/>
+ <!-- bottom-right -->
+ <tint color="shade/gtk:bg[SELECTED]/0.7" alpha="1.0"
+ x="width - ((width-(Bmin`max`height-Bpad*2))/2) -2" y="height - ((height-(Bmin`max`height-Bpad*2))/2)"
+ width="2" height="1"/>
+ <tint color="shade/gtk:bg[SELECTED]/0.7" alpha="1.0"
+ x="width - ((width-(Bmin`max`height-Bpad*2))/2)" y="height - ((height-(Bmin`max`height-Bpad*2))/2)-2"
+ width="1" height="2"/>
+
+ <!-- icon -->
+ <line color="blend/gtk:bg[SELECTED]/#FFFFFF/0.75" width="2"
+ x1="((width-(Bmin`max`height-Bpad*2))/2)" y1="((height-(Bmin`max`height-Bpad*2))/2)"
+ x2="width - ((width-(Bmin`max`height-Bpad*2))/2) - 1" y2="height - ((height-(Bmin`max`height-Bpad*2))/2) - 1"/>
+ <line color="blend/gtk:bg[SELECTED]/#FFFFFF/0.75" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2)" y1="((height-(Bmin`max`height-Bpad*2))/2)"
+ x2="width- ((width-(Bmin`max`height-Bpad*2))/2)" y2="height - ((height-(Bmin`max`height-Bpad*2))/2)"/>
+ <line color="blend/gtk:bg[SELECTED]/#FFFFFF/0.75" width="2"
+ x1="((width-(Bmin`max`height-Bpad*2))/2)" y1="height - ((height-(Bmin`max`height-Bpad*2))/2) - 1"
+ x2="width - ((width-(Bmin`max`height-Bpad*2))/2) - 1" y2="((height-(Bmin`max`height-Bpad*2))/2)"/>
+ <line color="blend/gtk:bg[SELECTED]/#FFFFFF/0.75" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2)" y1="height - ((height-(Bmin`max`height-Bpad*2))/2) - 1"
+ x2="width - ((width-(Bmin`max`height-Bpad*2))/2)" y2="((height-(Bmin`max`height-Bpad*2))/2) - 1"/>
+</draw_ops>
+
+<draw_ops name="close_button_icon_unfocused">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" width="2"
+ x1="((width-(Bmin`max`height-Bpad*2))/2)" y1="((height-(Bmin`max`height-Bpad*2))/2)"
+ x2="width-((width-(Bmin`max`height-Bpad*2))/2)-1" y2="height - ((height-(Bmin`max`height-Bpad*2))/2)-1"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2)" y1="((height-(Bmin`max`height-Bpad*2))/2)"
+ x2="width-((width-(Bmin`max`height-Bpad*2))/2)" y2="height - ((height-(Bmin`max`height-Bpad*2))/2)"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" width="2"
+ x1="((width-(Bmin`max`height-Bpad*2))/2)" y1="height - ((height-(Bmin`max`height-Bpad*2))/2)-1"
+ x2="width-((width-(Bmin`max`height-Bpad*2))/2)-1" y2="((height-(Bmin`max`height-Bpad*2))/2)"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2)" y1="height - ((height-(Bmin`max`height-Bpad*2))/2)-1"
+ x2="width-((width-(Bmin`max`height-Bpad*2))/2)" y2="((height-(Bmin`max`height-Bpad*2))/2) - 1"/>
+</draw_ops>
+
+<draw_ops name="close_button_normal">
+ <include name="button_bg"/>
+ <include name="close_button_icon"/>
+</draw_ops>
+<draw_ops name="close_button_prelight">
+ <include name="button_bg_prelight"/>
+ <include name="close_button_icon"/>
+</draw_ops>
+<draw_ops name="close_button_pressed">
+ <include name="button_bg_pressed"/>
+ <include name="close_button_icon"/>
+</draw_ops>
+<draw_ops name="close_button_unfocused">
+ <include name="button_bg_unfocused"/>
+ <include name="close_button_icon_unfocused"/>
+</draw_ops>
+<draw_ops name="close_button_unfocused_prelight">
+ <include name="button_bg_unfocused_prelight"/>
+ <include name="close_button_icon_unfocused"/>
+</draw_ops>
+<draw_ops name="close_button_unfocused_pressed">
+ <include name="button_bg_unfocused_pressed"/>
+ <include name="close_button_icon_unfocused"/>
+</draw_ops>
+
+<!-- maximize icon -->
+<draw_ops name="maximize_button_icon">
+ <!-- outside border -->
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2)-1" y="((height-(Bmin`max`height-Bpad*2))/2)-1"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2+1" height="height-((height-(Bmin`max`height-Bpad*2))/2)*2+1"/>
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2)+1" y="((height-(Bmin`max`height-Bpad*2))/2)+2"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2-3" height="height-((height-(Bmin`max`height-Bpad*2))/2)*2-4"/>
+
+ <!-- icon -->
+ <rectangle color="blend/gtk:bg[SELECTED]/#FFFFFF/0.75" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2)" y="((height-(Bmin`max`height-Bpad*2))/2)"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2-1" height="height-((height-(Bmin`max`height-Bpad*2))/2)*2-1"/>
+ <line color="blend/gtk:bg[SELECTED]/#FFFFFF/0.75" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2) + 1" y1="((height-(Bmin`max`height-Bpad*2))/2) + 1"
+ x2="width-((width-(Bmin`max`height-Bpad*2))/2)" y2="((height-(Bmin`max`height-Bpad*2))/2) + 1"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_icon_unfocused">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2)" y="((height-(Bmin`max`height-Bpad*2))/2)"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2-1" height="height-((height-(Bmin`max`height-Bpad*2))/2)*2-1"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2) + 1" y1="((height-(Bmin`max`height-Bpad*2))/2) + 1"
+ x2="width-((width-(Bmin`max`height-Bpad*2))/2)" y2="((height-(Bmin`max`height-Bpad*2))/2) + 1"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_normal">
+ <include name="button_bg"/>
+ <include name="maximize_button_icon"/>
+</draw_ops>
+<draw_ops name="maximize_button_prelight">
+ <include name="button_bg_prelight"/>
+ <include name="maximize_button_icon"/>
+</draw_ops>
+<draw_ops name="maximize_button_pressed">
+ <include name="button_bg_pressed"/>
+ <include name="maximize_button_icon"/>
+</draw_ops>
+<draw_ops name="maximize_button_unfocused">
+ <include name="button_bg_unfocused"/>
+ <include name="maximize_button_icon_unfocused"/>
+</draw_ops>
+<draw_ops name="maximize_button_unfocused_prelight">
+ <include name="button_bg_unfocused_prelight"/>
+ <include name="maximize_button_icon_unfocused"/>
+</draw_ops>
+<draw_ops name="maximize_button_unfocused_pressed">
+ <include name="button_bg_unfocused_pressed"/>
+ <include name="maximize_button_icon_unfocused"/>
+</draw_ops>
+
+<!-- restore icon -->
+<draw_ops name="restore_button_icon">
+ <!-- outside border -->
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2)-1" y="((height-(Bmin`max`height-Bpad*2))/2)-1"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2-1" height="height-((height-(Bmin`max`height-Bpad*2))/2)*2-1"/>
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2)+2-1" y="((height-(Bmin`max`height-Bpad*2))/2)+3-1"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2-5" height="height-((height-(Bmin`max`height-Bpad*2))/2)*2-6"/>
+
+
+ <!-- icon -->
+ <rectangle color="blend/gtk:bg[SELECTED]/#FFFFFF/0.75" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2) + 1" y="((height-(Bmin`max`height-Bpad*2))/2) + 1"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2-3" height="height-((height-(Bmin`max`height-Bpad*2))/2)*2-3"/>
+ <line color="blend/gtk:bg[SELECTED]/#FFFFFF/0.75" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2) + 2" y1="((height-(Bmin`max`height-Bpad*2))/2) + 2"
+ x2="width-((width-(Bmin`max`height-Bpad*2))/2) - 2" y2="((height-(Bmin`max`height-Bpad*2))/2) + 2"/>
+
+
+ <!-- second window (perberos was here!) -->
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2) +1"
+ y="((height-(Bmin`max`height-Bpad*2))/2) +1"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2-1"
+ height="height-((height-(Bmin`max`height-Bpad*2))/2)*2-1"/>
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2)+2+1"
+ y="((height-(Bmin`max`height-Bpad*2))/2)+3+1"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2-5"
+ height="height-((height-(Bmin`max`height-Bpad*2))/2)*2-6"/>
+ <rectangle color="blend/gtk:bg[SELECTED]/#FFFFFF/0.75" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2) + 1+1"
+ y="((height-(Bmin`max`height-Bpad*2))/2) + 1+1"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2-3"
+ height="height-((height-(Bmin`max`height-Bpad*2))/2)*2-3"/>
+ <line color="blend/gtk:bg[SELECTED]/#FFFFFF/0.75" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2) + 2+1"
+ y1="((height-(Bmin`max`height-Bpad*2))/2) + 2+1"
+ x2="width-((width-(Bmin`max`height-Bpad*2))/2) - 2+1"
+ y2="((height-(Bmin`max`height-Bpad*2))/2) + 2+1"/>
+ <!-- / second window at the icon button (perberos was here!) -->
+</draw_ops>
+
+<draw_ops name="restore_button_icon_unfocused">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2) + 1+1" y="((height-(Bmin`max`height-Bpad*2))/2) + 1+1"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2-3" height="height-((height-(Bmin`max`height-Bpad*2))/2)*2-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2) + 2+1" y1="((height-(Bmin`max`height-Bpad*2))/2) + 2+1"
+ x2="width-((width-(Bmin`max`height-Bpad*2))/2) - 2+1" y2="((height-(Bmin`max`height-Bpad*2))/2) + 2+1"/>
+
+ <!-- second window at the icon button (perberos was here!) -->
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2) + 1 - 1"
+ y1="((height-(Bmin`max`height-Bpad*2))/2) +1 - 1"
+ x2="((width-(Bmin`max`height-Bpad*2))/2) + 1 - 1 +
+ width-((width-(Bmin`max`height-Bpad*2))/2)*2-3"
+ y2="((height-(Bmin`max`height-Bpad*2))/2) +1 - 1"/>
+
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2) + 1 - 1 +
+ width-((width-(Bmin`max`height-Bpad*2))/2)*2-3"
+ y1="((height-(Bmin`max`height-Bpad*2))/2) +1- 1"
+ x2="((width-(Bmin`max`height-Bpad*2))/2) + 1 - 1 +
+ width-((width-(Bmin`max`height-Bpad*2))/2)*2-3"
+ y2="((height-(Bmin`max`height-Bpad*2))/2) +1- 1 +
+ (height-((height-(Bmin`max`height-Bpad*2))/2)*2-3) / 2"/>
+
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2) + 1- 1"
+ y1="((height-(Bmin`max`height-Bpad*2))/2) +1 - 1"
+ x2="((width-(Bmin`max`height-Bpad*2))/2) + 1 - 1"
+ y2="((height-(Bmin`max`height-Bpad*2))/2) +1 - 1 +
+ height-((height-(Bmin`max`height-Bpad*2))/2)*2-3"/>
+
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" width="1"
+ x1="((width-(Bmin`max`height-Bpad*2))/2) + 1 - 1"
+ y1="((height-(Bmin`max`height-Bpad*2))/2) +1 - 1 +
+ height-((height-(Bmin`max`height-Bpad*2))/2)*2-3"
+ x2="((width-(Bmin`max`height-Bpad*2))/2) + 1 - 1 +
+ (width-((width-(Bmin`max`height-Bpad*2))/2)*2-3) / 2"
+ y2="((height-(Bmin`max`height-Bpad*2))/2) +1 - 1 +
+ height-((height-(Bmin`max`height-Bpad*2))/2)*2-3"/>
+ <!-- / second window at the icon button (perberos was here!) -->
+</draw_ops>
+
+<draw_ops name="restore_button_normal">
+ <include name="button_bg"/>
+ <include name="restore_button_icon"/>
+</draw_ops>
+<draw_ops name="restore_button_prelight">
+ <include name="button_bg_prelight"/>
+ <include name="restore_button_icon"/>
+</draw_ops>
+<draw_ops name="restore_button_pressed">
+ <include name="button_bg_pressed"/>
+ <include name="restore_button_icon"/>
+</draw_ops>
+<draw_ops name="restore_button_unfocused">
+ <include name="button_bg_unfocused"/>
+ <include name="restore_button_icon_unfocused"/>
+</draw_ops>
+<draw_ops name="restore_button_unfocused_prelight">
+ <include name="button_bg_unfocused_prelight"/>
+ <include name="restore_button_icon_unfocused"/>
+</draw_ops>
+<draw_ops name="restore_button_unfocused_pressed">
+ <include name="button_bg_unfocused_pressed"/>
+ <include name="restore_button_icon_unfocused"/>
+</draw_ops>
+
+<!-- minimize icon -->
+<draw_ops name="minimize_button_icon">
+ <!-- outside border -->
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" filled="false"
+ x="((width-(Bmin`max`height-Bpad*2))/2)-1" y="height - ((height-(Bmin`max`height-Bpad*2))/2) - 3"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2+1" height="3"/>
+ <!-- icon -->
+ <rectangle color="blend/gtk:bg[SELECTED]/#FFFFFF/0.75" filled="true"
+ x="((width-(Bmin`max`height-Bpad*2))/2)" y="height - ((height-(Bmin`max`height-Bpad*2))/2) - 2"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2" height="2"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_icon_unfocused">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.45" filled="true"
+ x="((width-(Bmin`max`height-Bpad*2))/2)" y="height - ((height-(Bmin`max`height-Bpad*2))/2) - 2"
+ width="width-((width-(Bmin`max`height-Bpad*2))/2)*2" height="2"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_normal">
+ <include name="button_bg"/>
+ <include name="minimize_button_icon"/>
+</draw_ops>
+<draw_ops name="minimize_button_prelight">
+ <include name="button_bg_prelight"/>
+ <include name="minimize_button_icon"/>
+</draw_ops>
+<draw_ops name="minimize_button_pressed">
+ <include name="button_bg_pressed"/>
+ <include name="minimize_button_icon"/>
+</draw_ops>
+<draw_ops name="minimize_button_unfocused">
+ <include name="button_bg_unfocused"/>
+ <include name="minimize_button_icon_unfocused"/>
+</draw_ops>
+<draw_ops name="minimize_button_unfocused_prelight">
+ <include name="button_bg_unfocused_prelight"/>
+ <include name="minimize_button_icon_unfocused"/>
+</draw_ops>
+<draw_ops name="minimize_button_unfocused_pressed">
+ <include name="button_bg_unfocused_pressed"/>
+ <include name="minimize_button_icon_unfocused"/>
+</draw_ops>
+
+<draw_ops name="blank">
+<!-- nothing -->
+</draw_ops>
+
+<!-- ::: FRAME STYLES ::: -->
+<frame_style name="normal" geometry="normal">
+ <piece position="entire_background" draw_ops="round_bevel_unfocused"/>
+ <piece position="title" draw_ops="title_unfocused"/>
+ <button function="close" state="normal" draw_ops="close_button_unfocused"/>
+ <button function="close" state="pressed" draw_ops="close_button_unfocused_pressed"/>
+ <button function="close" state="prelight" draw_ops="close_button_unfocused_prelight"/>
+ <button function="maximize" state="normal" draw_ops="maximize_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_unfocused_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="maximize_button_unfocused_prelight"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button_unfocused"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_unfocused_pressed"/>
+ <button function="minimize" state="prelight" draw_ops="minimize_button_unfocused_prelight"/>
+ <button function="menu" state="normal" draw_ops="menu_button_normal"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_pressed"/>
+</frame_style>
+
+<frame_style name="normal_shaded" geometry="shaded" parent="normal">
+ <piece position="entire_background" draw_ops="round_bevel_unfocused_shaded"/>
+</frame_style>
+
+<frame_style name="focused" geometry="normal" parent="normal">
+ <piece position="entire_background" draw_ops="round_bevel"/>
+ <piece position="title" draw_ops="title"/>
+ <button function="close" state="normal" draw_ops="close_button_normal"/>
+ <button function="close" state="pressed" draw_ops="close_button_pressed"/>
+ <button function="close" state="prelight" draw_ops="close_button_prelight"/>
+ <button function="maximize" state="normal" draw_ops="maximize_button_normal"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="maximize_button_prelight"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button_normal"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_pressed"/>
+ <button function="minimize" state="prelight" draw_ops="minimize_button_prelight"/>
+</frame_style>
+
+<frame_style name="focused_shaded" geometry="shaded" parent="focused">
+ <piece position="entire_background" draw_ops="round_bevel_shaded"/>
+</frame_style>
+
+<frame_style name="normal_maximized" geometry="normal_maximized" parent="normal">
+ <piece position="entire_background" draw_ops="bevel_maximized_unfocused"/>
+ <button function="maximize" state="normal" draw_ops="restore_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_unfocused_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="restore_button_unfocused_prelight"/>
+</frame_style>
+
+<frame_style name="focused_maximized" geometry="normal_maximized" parent="focused">
+ <piece position="entire_background" draw_ops="bevel_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button_normal"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="restore_button_prelight"/>
+</frame_style>
+
+<frame_style name="border" geometry="border" parent="normal">
+ <piece position="entire_background" draw_ops="border"/>
+ <piece position="title" draw_ops="blank"/>
+</frame_style>
+
+<frame_style name="utility_normal" geometry="utility" parent="normal">
+ <piece position="entire_background" draw_ops="bevel_unfocused"/>
+</frame_style>
+<frame_style name="utility_focused" geometry="utility" parent="focused">
+ <piece position="entire_background" draw_ops="bevel"/>
+</frame_style>
+<frame_style name="utility_focused_shaded" geometry="utility" parent="focused_shaded">
+ <piece position="entire_background" draw_ops="bevel_shaded"/>
+</frame_style>
+
+<frame_style_set name="normal">
+ <frame focus="yes" state="normal" resize="both" style="focused"/>
+ <frame focus="no" state="normal" resize="both" style="normal"/>
+ <frame focus="yes" state="maximized" style="focused_maximized"/>
+ <frame focus="no" state="maximized" style="normal_maximized"/>
+ <frame focus="yes" state="shaded" style="focused_shaded"/>
+ <frame focus="no" state="shaded" style="normal_shaded"/>
+ <frame focus="yes" state="maximized_and_shaded" style="focused_maximized"/>
+ <frame focus="no" state="maximized_and_shaded" style="normal_maximized"/>
+</frame_style_set>
+
+<frame_style_set name="utility" parent="normal">
+ <frame focus="yes" state="normal" resize="both" style="utility_focused"/>
+ <frame focus="no" state="normal" resize="both" style="utility_normal"/>
+ <!-- this is a bunch of crack since utility windows shouldn't be maximized -->
+ <frame focus="yes" state="maximized" style="focused"/>
+ <frame focus="no" state="maximized" style="normal"/>
+ <frame focus="yes" state="shaded" style="utility_focused_shaded"/>
+ <frame focus="no" state="shaded" style="utility_normal"/>
+ <frame focus="yes" state="maximized_and_shaded" style="focused_shaded"/>
+ <frame focus="no" state="maximized_and_shaded" style="normal"/>
+</frame_style_set>
+
+<frame_style_set name="border">
+ <frame focus="yes" state="normal" resize="both" style="border"/>
+ <frame focus="no" state="normal" resize="both" style="border"/>
+ <frame focus="yes" state="maximized" style="border"/>
+ <frame focus="no" state="maximized" style="border"/>
+ <frame focus="yes" state="shaded" style="border"/>
+ <frame focus="no" state="shaded" style="border"/>
+ <frame focus="yes" state="maximized_and_shaded" style="border"/>
+ <frame focus="no" state="maximized_and_shaded" style="border"/>
+</frame_style_set>
+
+<window type="normal" style_set="normal"/>
+<window type="dialog" style_set="normal"/>
+<window type="modal_dialog" style_set="normal"/>
+<window type="menu" style_set="normal"/>
+<window type="utility" style_set="utility"/>
+<window type="border" style_set="border"/>
+
+<menu_icon function="close" state="normal" draw_ops="close_button_icon_unfocused"/>
+<menu_icon function="maximize" state="normal" draw_ops="maximize_button_icon_unfocused"/>
+<menu_icon function="unmaximize" state="normal" draw_ops="restore_button_icon_unfocused"/>
+<menu_icon function="minimize" state="normal" draw_ops="minimize_button_icon_unfocused"/>
+
+</metacity_theme>
diff --git a/src/themes/Dopple-Left/metacity-theme-1.xml b/src/themes/Dopple-Left/metacity-theme-1.xml
new file mode 100644
index 00000000..ec933ca9
--- /dev/null
+++ b/src/themes/Dopple-Left/metacity-theme-1.xml
@@ -0,0 +1,1135 @@
+<?xml version="1.0"?>
+<metacity_theme>
+
+<info>
+ <name>Dopple</name>
+ <author>Brandon Wright</author>
+ <copyright>2009</copyright>
+ <date>January 21, 2009</date>
+ <description>A Clearlooks-styled theme with Tango basics.</description>
+</info>
+
+<frame_geometry name="normal" rounded_top_left="true" rounded_top_right="true">
+ <distance name="left_width" value="3"/>
+ <distance name="right_width" value="3"/>
+ <distance name="bottom_height" value="3"/>
+ <distance name="left_titlebar_edge" value="4"/>
+ <distance name="right_titlebar_edge" value="4"/>
+ <aspect_ratio name="button" value="1"/>
+ <distance name="title_vertical_pad" value="2"/>
+ <border name="title_border" left="3" right="4" top="2" bottom="2"/>
+ <border name="button_border" left="1" right="1" top="3" bottom="3"/>
+</frame_geometry>
+
+<frame_geometry name="normal_small_borders" parent="normal" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_titlebar_edge" value="1" />
+ <distance name="right_titlebar_edge" value="1" />
+ <distance name="left_width" value="0" />
+ <distance name="right_width" value="0" />
+ <distance name="bottom_height" value="0" />
+</frame_geometry>
+
+<frame_geometry name="utility" parent="normal">
+</frame_geometry>
+
+<frame_geometry name="border" has_title="false" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="0"/>
+ <distance name="right_titlebar_edge" value="0"/>
+ <distance name="button_width" value="0"/>
+ <distance name="button_height" value="0"/>
+ <distance name="title_vertical_pad" value="4"/>
+ <border name="title_border" left="0" right="0" top="0" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="0" bottom="0"/>
+</frame_geometry>
+
+<!-- Buttons -->
+<draw_ops name="button_bg_unfocused">
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[NORMAL]/0.8" />
+ <color value="shade/gtk:bg[NORMAL]/0.675" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width - 1" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[NORMAL]/0.8" />
+ <color value="shade/gtk:bg[NORMAL]/0.675" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.90"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ <color value="shade/gtk:bg[NORMAL]/0.90" />
+ </gradient>
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[NORMAL]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ <color value="shade/gtk:bg[NORMAL]/0.90" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.1"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.1"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/0.9"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/0.9"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="button_bg_prelight_unfocused">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/1.15" />
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[NORMAL]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+
+</draw_ops>
+
+<draw_ops name="button_pressed_bg_unfocused">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" /> -->
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+<!-- <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient> -->
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[NORMAL]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.00" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<!-- Buttons -->
+<draw_ops name="button_bg">
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="0" y1="2"
+ x2="0" y2="height-3" /> -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/0.8" />
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ </gradient>
+
+ <!-- Right line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" /> -->
+
+ <gradient type="vertical" x="width-1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/0.8" />
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/1.22"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.07"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.22" />
+ <color value="shade/gtk:bg[SELECTED]/1.07" />
+ </gradient>
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[SELECTED]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.22" />
+ <color value="shade/gtk:bg[SELECTED]/1.07" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/0.975"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/0.975"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="button_bg_prelight">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[SELECTED]/1.15" />
+ <color value="shade/gtk:bg[SELECTED]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.25" />
+ <color value="shade/gtk:bg[SELECTED]/1.05" />
+ </gradient>
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[SELECTED]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.25" />
+ <color value="shade/gtk:bg[SELECTED]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+
+</draw_ops>
+
+<draw_ops name="button_pressed_bg">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[SELECTED]/0.95" />
+ <color value="shade/gtk:bg[SELECTED]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" /> -->
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+<!-- <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.25" />
+ <color value="shade/gtk:bg[SELECTED]/1.05" />
+ </gradient> -->
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[SELECTED]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.00" />
+ <color value="shade/gtk:bg[SELECTED]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="menu_glyph">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" />
+</draw_ops>
+
+<draw_ops name="menu_glyph_unfocused">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" alpha="0.5"/>
+</draw_ops>
+
+<draw_ops name="close_glyph">
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="white" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="white" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_shadow">
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_shadow_dark">
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused_lighter">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph">
+ <rectangle color="white" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="white" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="white" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="white" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+<draw_ops name="maximize_glyph_shadow">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.85" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_shadow_dark">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.8" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+<draw_ops name="maximize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+
+
+</draw_ops>
+
+<draw_ops name="minimize_glyph">
+ <rectangle color="white" x="2" y="height * 2 / 3" width="width-4" height="height - height * 2 / 3 - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_shadow">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.85" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_shadow_dark">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.8" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="menu_button">
+ <include name="menu_glyph" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="menu_button_unfocused">
+ <include name="menu_glyph_unfocused" x="0" y="0" />
+</draw_ops>
+
+<draw_ops name="menu_button_pressed">
+ <include name="menu_glyph_unfocused" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="minimize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_shadow" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4" />
+</draw_ops>
+
+<draw_ops name="minimize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="minimize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+
+<draw_ops name="maximize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="maximize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="mini_window_icon">
+ <rectangle color="gtk:bg[NORMAL]" filled="true"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <rectangle color="gtk:fg[NORMAL]" filled="false"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <line color="gtk:fg[NORMAL]" width="2"
+ x1="0" y1="1" x2="width" y2="1"/>
+</draw_ops>
+
+<draw_ops name="restore_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow" x="3" y="5" width="width-6" height="height-6" />
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_prelight">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow_dark" x="3" y="5" width="width-6" height="height-6"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_unfocused_lighter" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="maximize_glyph_unfocused_lighter" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="close_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_shadow" x="2" y="4" width="width-4" height="height-4" />
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="close_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.5"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel_unfocused">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="title_background">
+
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+<!--
+ <rectangle color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x="1" y="1" width="width-3" height="height-3"/>
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-2" y1="1" x2="width-2" y2="height-2"/>
+-->
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="1" y="1" width="width-3" height="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-2" y1="1" x2="width-2" y2="height-2"/>
+
+ <gradient type="vertical" x="2" y="2" width="width-4" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="3" y1="height-1"
+ x2="width-4" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.975"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="4" y1="1" x2="4" y2="1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+<!--
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="2" y1="3" x2="2" y2="4" />
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="3" y1="2" x2="4" y2="2" />
+
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-5" y1="2" x2="width-4" y2="2" />
+-->
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="3" y1="2" x2="4" y2="2" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+ <!-- <gradient type="vertical" x="1" y="5" width="1" height="height-6">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient>
+
+ <gradient type="vertical" x="width-2" y="5" width="1" height="height-6">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient> -->
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="1" y1="height-1" x2="1" y2="height-1" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-2" y1="height-1" x2="width-2" y2="height-1" />
+</draw_ops>
+
+<draw_ops name="title_background_maximized">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="0" y="1" width="width-2" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-1" y1="1" x2="width-1" y2="height-2"/>
+
+ <gradient type="vertical" x="1" y="2" width="width-2" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ </gradient>
+
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="0" y="1" width="width-1" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="0" y1="height-1"
+ x2="width-1" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.95"
+ x1="0" y1="height-2"
+ x2="width-1" y2="height-2" />
+
+<!-- <gradient type="vertical" x="0" y="1" width="1" height="height-2">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient>
+
+ <gradient type="vertical" x="width-1" y="1" width="1" height="height-2">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient> -->
+</draw_ops>
+
+<draw_ops name="title_unfocused_background">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.05"/>
+ <color value="shade/gtk:bg[NORMAL]/0.85"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/0.945" x1="1" y1="height-2" x2="width-3" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="0" y1="height-1" x2="width-1" y2="height-1" />
+
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="1" y1="1" x2="width-2" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="1" y1="1" x2="1" y2="height-1"/>
+
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-2" y1="1" x2="width-2" y2="height" />
+
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="4" y1="1" x2="4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="3" y1="2" x2="4" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+
+</draw_ops>
+
+<draw_ops name="title_unfocused_background_maximized">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="0" y="1" width="width" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.05"/>
+ <color value="shade/gtk:bg[NORMAL]/0.85"/>
+ </gradient>
+ <line color="shade/gtk:bg[NORMAL]/0.945" x1="0" y1="height-2" x2="width-1" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="0" y1="height-1" x2="width-1" y2="height-1" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="0" y1="1" x2="width-1" y2="1"/>
+
+</draw_ops>
+
+<draw_ops name="blank">
+<!-- nothing -->
+</draw_ops>
+
+<draw_ops name="focus_outline">
+ <rectangle x="0" y="0" width="width-1" height="height-1" color="shade/gtk:bg[SELECTED]/0.7" />
+ <rectangle x="1" y="1" width="width-3" height="height-3" color="shade/gtk:bg[SELECTED]/1.2" />
+ <rectangle x="2" y="2" width="width-5" height="height-5" color="shade/gtk:bg[SELECTED]/0.7" />
+</draw_ops>
+
+<draw_ops name="unfocus_outline">
+ <rectangle x="0" y="0" width="width-1" height="height-1" color="shade/gtk:bg[NORMAL]/0.7" />
+ <rectangle x="1" y="1" width="width-3" height="height-3" color="shade/gtk:bg[NORMAL]/1.2" />
+ <rectangle x="2" y="2" width="width-5" height="height-5" color="shade/gtk:bg[NORMAL]/0.7" />
+</draw_ops>
+
+<draw_ops name="focus_background">
+ <include name="focus_outline"/>
+</draw_ops>
+
+<draw_ops name="unfocus_background">
+ <include name="unfocus_outline" />
+</draw_ops>
+
+<draw_ops name="title_text_focused_with_icon">
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) - 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) - 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) + 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) + 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+ <title color="#ffffff"
+ x="0"
+ y="(((height - title_height) / 2) `max` 0)"/>
+
+</draw_ops>
+
+<draw_ops name="title_text_with_icon">
+ <title color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.4"
+ x="(0)"
+ y="((height - title_height) / 2) `max` 0"/>
+</draw_ops>
+
+<draw_ops name="title_normal">
+ <include name="title_text_with_icon"/>
+</draw_ops>
+
+<draw_ops name="title_focused">
+ <include name="title_text_focused_with_icon"/>
+</draw_ops>
+
+<frame_style name="normal_unfocused" geometry="normal">
+ <piece position="entire_background" draw_ops="unfocus_background"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background"/>
+ <piece position="title" draw_ops="title_normal"/>
+
+ <button function="close" state="normal" draw_ops="close_button_unfocused"/>
+ <button function="close" state="pressed" draw_ops="close_button_unfocused_pressed"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button_unfocused"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_unfocused_pressed"/>
+ <button function="maximize" state="normal" draw_ops="maximize_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_unfocused_pressed"/>
+ <button function="menu" state="normal" draw_ops="menu_button_unfocused"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_unfocused"/>
+</frame_style>
+
+<frame_style name="normal_focused" geometry="normal">
+ <piece position="entire_background" draw_ops="focus_background"/>
+ <piece position="titlebar" draw_ops="title_background"/>
+ <piece position="title" draw_ops="title_focused"/>
+ <button function="close" state="normal" draw_ops="close_button"/>
+ <button function="close" state="pressed" draw_ops="close_button_pressed"/>
+ <button function="close" state="prelight" draw_ops="close_button_focus"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_pressed"/>
+ <button function="minimize" state="prelight" draw_ops="minimize_button_focus" />
+ <button function="maximize" state="normal" draw_ops="maximize_button"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="maximize_button_focus"/>
+ <button function="menu" state="normal" draw_ops="menu_button"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_pressed"/>
+
+</frame_style>
+
+<frame_style name="maximized_unfocused" geometry="normal_small_borders" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="blank"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_unfocused_pressed"/>
+</frame_style>
+
+<frame_style name="maximized_focused" geometry="normal_small_borders" parent="normal_focused">
+ <piece position="entire_background" draw_ops="focus_outline"/>
+ <piece position="titlebar" draw_ops="title_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="restore_button_prelight" />
+</frame_style>
+
+<frame_style name="border" geometry="border" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="outer_bevel"/>
+ <piece position="titlebar" draw_ops="blank"/>
+ <piece position="title" draw_ops="blank"/>
+</frame_style>
+
+<frame_style_set name="normal">
+<frame focus="yes" state="normal" resize="both" style="normal_focused"/>
+<frame focus="no" state="normal" resize="both" style="normal_unfocused"/>
+<frame focus="yes" state="maximized" style="maximized_focused"/>
+<frame focus="no" state="maximized" style="maximized_unfocused"/>
+<frame focus="yes" state="shaded" style="normal_focused"/>
+<frame focus="no" state="shaded" style="normal_unfocused"/>
+<frame focus="yes" state="maximized_and_shaded" style="maximized_focused"/>
+<frame focus="no" state="maximized_and_shaded" style="maximized_unfocused"/>
+</frame_style_set>
+
+<frame_style_set name="border">
+<frame focus="yes" state="normal" resize="both" style="border"/>
+<frame focus="no" state="normal" resize="both" style="border"/>
+<frame focus="yes" state="maximized" style="border"/>
+<frame focus="no" state="maximized" style="border"/>
+<frame focus="yes" state="shaded" style="border"/>
+<frame focus="no" state="shaded" style="border"/>
+<frame focus="yes" state="maximized_and_shaded" style="border"/>
+<frame focus="no" state="maximized_and_shaded" style="border"/>
+</frame_style_set>
+
+<window type="normal" style_set="normal"/>
+<window type="dialog" style_set="normal"/>
+<window type="modal_dialog" style_set="normal"/>
+<window type="menu" style_set="normal"/>
+<window type="utility" style_set="normal"/>
+<window type="border" style_set="normal"/>
+
+<menu_icon function="close" state="normal" draw_ops="close_button"/>
+<menu_icon function="maximize" state="normal" draw_ops="maximize_button"/>
+<menu_icon function="unmaximize" state="normal" draw_ops="restore_button"/>
+<menu_icon function="minimize" state="normal" draw_ops="minimize_button"/>
+
+</metacity_theme>
diff --git a/src/themes/Dopple/metacity-theme-1.xml b/src/themes/Dopple/metacity-theme-1.xml
new file mode 100644
index 00000000..ef3299ae
--- /dev/null
+++ b/src/themes/Dopple/metacity-theme-1.xml
@@ -0,0 +1,1135 @@
+<?xml version="1.0"?>
+<metacity_theme>
+
+<info>
+ <name>Dopple</name>
+ <author>Brandon Wright</author>
+ <copyright>2009</copyright>
+ <date>January 21, 2009</date>
+ <description>A Clearlooks-styled theme with Tango basics.</description>
+</info>
+
+<frame_geometry name="normal" rounded_top_left="true" rounded_top_right="true">
+ <distance name="left_width" value="3"/>
+ <distance name="right_width" value="3"/>
+ <distance name="bottom_height" value="3"/>
+ <distance name="left_titlebar_edge" value="4"/>
+ <distance name="right_titlebar_edge" value="4"/>
+ <aspect_ratio name="button" value="1"/>
+ <distance name="title_vertical_pad" value="2"/>
+ <border name="title_border" left="3" right="4" top="2" bottom="2"/>
+ <border name="button_border" left="1" right="1" top="3" bottom="3"/>
+</frame_geometry>
+
+<frame_geometry name="normal_small_borders" parent="normal" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_titlebar_edge" value="1" />
+ <distance name="right_titlebar_edge" value="1" />
+ <distance name="left_width" value="0" />
+ <distance name="right_width" value="0" />
+ <distance name="bottom_height" value="0" />
+</frame_geometry>
+
+<frame_geometry name="utility" parent="normal">
+</frame_geometry>
+
+<frame_geometry name="border" has_title="false" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="0"/>
+ <distance name="right_titlebar_edge" value="0"/>
+ <distance name="button_width" value="0"/>
+ <distance name="button_height" value="0"/>
+ <distance name="title_vertical_pad" value="4"/>
+ <border name="title_border" left="0" right="0" top="0" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="0" bottom="0"/>
+</frame_geometry>
+
+<!-- Buttons -->
+<draw_ops name="button_bg_unfocused">
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[NORMAL]/0.8" />
+ <color value="shade/gtk:bg[NORMAL]/0.675" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width - 1" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[NORMAL]/0.8" />
+ <color value="shade/gtk:bg[NORMAL]/0.675" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.90"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ <color value="shade/gtk:bg[NORMAL]/0.90" />
+ </gradient>
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[NORMAL]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ <color value="shade/gtk:bg[NORMAL]/0.90" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.1"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.1"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/0.9"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/0.9"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="button_bg_prelight_unfocused">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/1.15" />
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[NORMAL]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+
+</draw_ops>
+
+<draw_ops name="button_pressed_bg_unfocused">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" /> -->
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+<!-- <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient> -->
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[NORMAL]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.00" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<!-- Buttons -->
+<draw_ops name="button_bg">
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="0" y1="2"
+ x2="0" y2="height-3" /> -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/0.8" />
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ </gradient>
+
+ <!-- Right line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" /> -->
+
+ <gradient type="vertical" x="width-1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/0.8" />
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/1.22"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.07"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.22" />
+ <color value="shade/gtk:bg[SELECTED]/1.07" />
+ </gradient>
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[SELECTED]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.22" />
+ <color value="shade/gtk:bg[SELECTED]/1.07" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/0.975"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/0.975"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="button_bg_prelight">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[SELECTED]/1.15" />
+ <color value="shade/gtk:bg[SELECTED]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.25" />
+ <color value="shade/gtk:bg[SELECTED]/1.05" />
+ </gradient>
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[SELECTED]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.25" />
+ <color value="shade/gtk:bg[SELECTED]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+
+</draw_ops>
+
+<draw_ops name="button_pressed_bg">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[SELECTED]/0.95" />
+ <color value="shade/gtk:bg[SELECTED]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" /> -->
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[SELECTED]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+<!-- <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.25" />
+ <color value="shade/gtk:bg[SELECTED]/1.05" />
+ </gradient> -->
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[SELECTED]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[SELECTED]/1.00" />
+ <color value="shade/gtk:bg[SELECTED]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="menu_glyph">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" />
+</draw_ops>
+
+<draw_ops name="menu_glyph_unfocused">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" alpha="0.5"/>
+</draw_ops>
+
+<draw_ops name="close_glyph">
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="white" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="white" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_shadow">
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_shadow_dark">
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused_lighter">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph">
+ <rectangle color="white" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="white" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="white" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="white" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+<draw_ops name="maximize_glyph_shadow">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.85" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_shadow_dark">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.8" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+<draw_ops name="maximize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+
+
+</draw_ops>
+
+<draw_ops name="minimize_glyph">
+ <rectangle color="white" x="2" y="height * 2 / 3" width="width-4" height="height - height * 2 / 3 - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_shadow">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.85" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_shadow_dark">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.8" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="menu_button">
+ <include name="menu_glyph" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="menu_button_unfocused">
+ <include name="menu_glyph_unfocused" x="0" y="0" />
+</draw_ops>
+
+<draw_ops name="menu_button_pressed">
+ <include name="menu_glyph_unfocused" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="minimize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_shadow" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4" />
+</draw_ops>
+
+<draw_ops name="minimize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="minimize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+
+<draw_ops name="maximize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="maximize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="mini_window_icon">
+ <rectangle color="gtk:bg[NORMAL]" filled="true"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <rectangle color="gtk:fg[NORMAL]" filled="false"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <line color="gtk:fg[NORMAL]" width="2"
+ x1="0" y1="1" x2="width" y2="1"/>
+</draw_ops>
+
+<draw_ops name="restore_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow" x="3" y="5" width="width-6" height="height-6" />
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_prelight">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow_dark" x="3" y="5" width="width-6" height="height-6"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_unfocused_lighter" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="maximize_glyph_unfocused_lighter" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="close_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_shadow" x="2" y="4" width="width-4" height="height-4" />
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="close_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.5"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel_unfocused">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="title_background">
+
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+<!--
+ <rectangle color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x="1" y="1" width="width-3" height="height-3"/>
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-2" y1="1" x2="width-2" y2="height-2"/>
+-->
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="1" y="1" width="width-3" height="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-2" y1="1" x2="width-2" y2="height-2"/>
+
+ <gradient type="vertical" x="2" y="2" width="width-4" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="3" y1="height-1"
+ x2="width-4" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.975"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="4" y1="1" x2="4" y2="1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+<!--
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="2" y1="3" x2="2" y2="4" />
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="3" y1="2" x2="4" y2="2" />
+
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-5" y1="2" x2="width-4" y2="2" />
+-->
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="3" y1="2" x2="4" y2="2" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+ <!-- <gradient type="vertical" x="1" y="5" width="1" height="height-6">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient>
+
+ <gradient type="vertical" x="width-2" y="5" width="1" height="height-6">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient> -->
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="1" y1="height-1" x2="1" y2="height-1" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-2" y1="height-1" x2="width-2" y2="height-1" />
+</draw_ops>
+
+<draw_ops name="title_background_maximized">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="0" y="1" width="width-2" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-1" y1="1" x2="width-1" y2="height-2"/>
+
+ <gradient type="vertical" x="1" y="2" width="width-2" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ </gradient>
+
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="0" y="1" width="width-1" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="0" y1="height-1"
+ x2="width-1" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.95"
+ x1="0" y1="height-2"
+ x2="width-1" y2="height-2" />
+
+<!-- <gradient type="vertical" x="0" y="1" width="1" height="height-2">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient>
+
+ <gradient type="vertical" x="width-1" y="1" width="1" height="height-2">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient> -->
+</draw_ops>
+
+<draw_ops name="title_unfocused_background">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.05"/>
+ <color value="shade/gtk:bg[NORMAL]/0.85"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/0.945" x1="1" y1="height-2" x2="width-3" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="0" y1="height-1" x2="width-1" y2="height-1" />
+
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="1" y1="1" x2="width-2" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="1" y1="1" x2="1" y2="height-1"/>
+
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-2" y1="1" x2="width-2" y2="height" />
+
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="4" y1="1" x2="4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="3" y1="2" x2="4" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+
+</draw_ops>
+
+<draw_ops name="title_unfocused_background_maximized">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="0" y="1" width="width" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.05"/>
+ <color value="shade/gtk:bg[NORMAL]/0.85"/>
+ </gradient>
+ <line color="shade/gtk:bg[NORMAL]/0.945" x1="0" y1="height-2" x2="width-1" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="0" y1="height-1" x2="width-1" y2="height-1" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="0" y1="1" x2="width-1" y2="1"/>
+
+</draw_ops>
+
+<draw_ops name="blank">
+<!-- nothing -->
+</draw_ops>
+
+<draw_ops name="focus_outline">
+ <rectangle x="0" y="0" width="width-1" height="height-1" color="shade/gtk:bg[SELECTED]/0.7" />
+ <rectangle x="1" y="1" width="width-3" height="height-3" color="shade/gtk:bg[SELECTED]/1.2" />
+ <rectangle x="2" y="2" width="width-5" height="height-5" color="shade/gtk:bg[SELECTED]/0.7" />
+</draw_ops>
+
+<draw_ops name="unfocus_outline">
+ <rectangle x="0" y="0" width="width-1" height="height-1" color="shade/gtk:bg[NORMAL]/0.7" />
+ <rectangle x="1" y="1" width="width-3" height="height-3" color="shade/gtk:bg[NORMAL]/1.2" />
+ <rectangle x="2" y="2" width="width-5" height="height-5" color="shade/gtk:bg[NORMAL]/0.7" />
+</draw_ops>
+
+<draw_ops name="focus_background">
+ <include name="focus_outline"/>
+</draw_ops>
+
+<draw_ops name="unfocus_background">
+ <include name="unfocus_outline" />
+</draw_ops>
+
+<draw_ops name="title_text_focused_with_icon">
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) - 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) - 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) + 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) + 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+ <title color="#ffffff"
+ x="(((width - title_width) / 2) `max` 0)"
+ y="(((height - title_height) / 2) `max` 0)"/>
+
+</draw_ops>
+
+<draw_ops name="title_text_with_icon">
+ <title color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.4"
+ x="(((width - title_width) / 2) `max` 0)"
+ y="((height - title_height) / 2) `max` 0"/>
+</draw_ops>
+
+<draw_ops name="title_normal">
+ <include name="title_text_with_icon"/>
+</draw_ops>
+
+<draw_ops name="title_focused">
+ <include name="title_text_focused_with_icon"/>
+</draw_ops>
+
+<frame_style name="normal_unfocused" geometry="normal">
+ <piece position="entire_background" draw_ops="unfocus_background"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background"/>
+ <piece position="title" draw_ops="title_normal"/>
+
+ <button function="close" state="normal" draw_ops="close_button_unfocused"/>
+ <button function="close" state="pressed" draw_ops="close_button_unfocused_pressed"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button_unfocused"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_unfocused_pressed"/>
+ <button function="maximize" state="normal" draw_ops="maximize_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_unfocused_pressed"/>
+ <button function="menu" state="normal" draw_ops="menu_button_unfocused"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_unfocused"/>
+</frame_style>
+
+<frame_style name="normal_focused" geometry="normal">
+ <piece position="entire_background" draw_ops="focus_background"/>
+ <piece position="titlebar" draw_ops="title_background"/>
+ <piece position="title" draw_ops="title_focused"/>
+ <button function="close" state="normal" draw_ops="close_button"/>
+ <button function="close" state="pressed" draw_ops="close_button_pressed"/>
+ <button function="close" state="prelight" draw_ops="close_button_focus"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_pressed"/>
+ <button function="minimize" state="prelight" draw_ops="minimize_button_focus" />
+ <button function="maximize" state="normal" draw_ops="maximize_button"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="maximize_button_focus"/>
+ <button function="menu" state="normal" draw_ops="menu_button"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_pressed"/>
+
+</frame_style>
+
+<frame_style name="maximized_unfocused" geometry="normal_small_borders" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="blank"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_unfocused_pressed"/>
+</frame_style>
+
+<frame_style name="maximized_focused" geometry="normal_small_borders" parent="normal_focused">
+ <piece position="entire_background" draw_ops="focus_outline"/>
+ <piece position="titlebar" draw_ops="title_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="restore_button_prelight" />
+</frame_style>
+
+<frame_style name="border" geometry="border" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="outer_bevel"/>
+ <piece position="titlebar" draw_ops="blank"/>
+ <piece position="title" draw_ops="blank"/>
+</frame_style>
+
+<frame_style_set name="normal">
+<frame focus="yes" state="normal" resize="both" style="normal_focused"/>
+<frame focus="no" state="normal" resize="both" style="normal_unfocused"/>
+<frame focus="yes" state="maximized" style="maximized_focused"/>
+<frame focus="no" state="maximized" style="maximized_unfocused"/>
+<frame focus="yes" state="shaded" style="normal_focused"/>
+<frame focus="no" state="shaded" style="normal_unfocused"/>
+<frame focus="yes" state="maximized_and_shaded" style="maximized_focused"/>
+<frame focus="no" state="maximized_and_shaded" style="maximized_unfocused"/>
+</frame_style_set>
+
+<frame_style_set name="border">
+<frame focus="yes" state="normal" resize="both" style="border"/>
+<frame focus="no" state="normal" resize="both" style="border"/>
+<frame focus="yes" state="maximized" style="border"/>
+<frame focus="no" state="maximized" style="border"/>
+<frame focus="yes" state="shaded" style="border"/>
+<frame focus="no" state="shaded" style="border"/>
+<frame focus="yes" state="maximized_and_shaded" style="border"/>
+<frame focus="no" state="maximized_and_shaded" style="border"/>
+</frame_style_set>
+
+<window type="normal" style_set="normal"/>
+<window type="dialog" style_set="normal"/>
+<window type="modal_dialog" style_set="normal"/>
+<window type="menu" style_set="normal"/>
+<window type="utility" style_set="normal"/>
+<window type="border" style_set="normal"/>
+
+<menu_icon function="close" state="normal" draw_ops="close_button"/>
+<menu_icon function="maximize" state="normal" draw_ops="maximize_button"/>
+<menu_icon function="unmaximize" state="normal" draw_ops="restore_button"/>
+<menu_icon function="minimize" state="normal" draw_ops="minimize_button"/>
+
+</metacity_theme>
diff --git a/src/themes/DustBlue/button_close_normal.png b/src/themes/DustBlue/button_close_normal.png
new file mode 100644
index 00000000..3dbfb0e2
--- /dev/null
+++ b/src/themes/DustBlue/button_close_normal.png
Binary files differ
diff --git a/src/themes/DustBlue/button_close_prelight.png b/src/themes/DustBlue/button_close_prelight.png
new file mode 100644
index 00000000..704de834
--- /dev/null
+++ b/src/themes/DustBlue/button_close_prelight.png
Binary files differ
diff --git a/src/themes/DustBlue/button_close_pressed.png b/src/themes/DustBlue/button_close_pressed.png
new file mode 100644
index 00000000..4f2263fb
--- /dev/null
+++ b/src/themes/DustBlue/button_close_pressed.png
Binary files differ
diff --git a/src/themes/DustBlue/button_max_normal.png b/src/themes/DustBlue/button_max_normal.png
new file mode 100644
index 00000000..8bf2f347
--- /dev/null
+++ b/src/themes/DustBlue/button_max_normal.png
Binary files differ
diff --git a/src/themes/DustBlue/button_max_prelight.png b/src/themes/DustBlue/button_max_prelight.png
new file mode 100644
index 00000000..c549bb79
--- /dev/null
+++ b/src/themes/DustBlue/button_max_prelight.png
Binary files differ
diff --git a/src/themes/DustBlue/button_max_pressed.png b/src/themes/DustBlue/button_max_pressed.png
new file mode 100644
index 00000000..4fe6439c
--- /dev/null
+++ b/src/themes/DustBlue/button_max_pressed.png
Binary files differ
diff --git a/src/themes/DustBlue/button_menu_normal.png b/src/themes/DustBlue/button_menu_normal.png
new file mode 100644
index 00000000..28600199
--- /dev/null
+++ b/src/themes/DustBlue/button_menu_normal.png
Binary files differ
diff --git a/src/themes/DustBlue/button_menu_prelight.png b/src/themes/DustBlue/button_menu_prelight.png
new file mode 100644
index 00000000..d16d92dc
--- /dev/null
+++ b/src/themes/DustBlue/button_menu_prelight.png
Binary files differ
diff --git a/src/themes/DustBlue/button_menu_pressed.png b/src/themes/DustBlue/button_menu_pressed.png
new file mode 100644
index 00000000..e7dfc357
--- /dev/null
+++ b/src/themes/DustBlue/button_menu_pressed.png
Binary files differ
diff --git a/src/themes/DustBlue/button_min_normal.png b/src/themes/DustBlue/button_min_normal.png
new file mode 100644
index 00000000..114ed3a5
--- /dev/null
+++ b/src/themes/DustBlue/button_min_normal.png
Binary files differ
diff --git a/src/themes/DustBlue/button_min_prelight.png b/src/themes/DustBlue/button_min_prelight.png
new file mode 100644
index 00000000..be44d1a8
--- /dev/null
+++ b/src/themes/DustBlue/button_min_prelight.png
Binary files differ
diff --git a/src/themes/DustBlue/button_min_pressed.png b/src/themes/DustBlue/button_min_pressed.png
new file mode 100644
index 00000000..045ab77c
--- /dev/null
+++ b/src/themes/DustBlue/button_min_pressed.png
Binary files differ
diff --git a/src/themes/DustBlue/menu.png b/src/themes/DustBlue/menu.png
new file mode 100644
index 00000000..4610e233
--- /dev/null
+++ b/src/themes/DustBlue/menu.png
Binary files differ
diff --git a/src/themes/DustBlue/metacity-theme-1.xml b/src/themes/DustBlue/metacity-theme-1.xml
new file mode 100644
index 00000000..69ad46f4
--- /dev/null
+++ b/src/themes/DustBlue/metacity-theme-1.xml
@@ -0,0 +1,409 @@
+<?xml version="1.0"?>
+
+<metacity_theme>
+<info>
+ <name>Dust-Blue</name>
+ <author>Kido Mariano, Rico Sta. Cruz and Modded by DarkAenima</author>
+ <copyright>GPL</copyright>
+ <date>Jul 28, 2008</date>
+ <description>Metacity theme</description>
+ <!-- Based on the Elegant Brit theme (though now really far away from it :P) -->
+</info>
+
+
+<!--
+ General window layout (geometric stuff)
+-->
+<frame_geometry name="frame_geometry_normal" title_scale="medium" rounded_top_left="true" rounded_top_right="true">
+ <distance name="left_width" value="1"/>
+ <distance name="right_width" value="1"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="2"/>
+ <distance name="right_titlebar_edge" value="1"/>
+ <distance name="button_width" value="28"/>
+ <distance name="button_height" value="20"/>
+ <distance name="title_vertical_pad" value="1"/>
+ <border name="title_border" left="2" right="2" top="3" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="2" bottom="1"/>
+</frame_geometry>
+
+<frame_geometry name="frame_geometry_abnormal" title_scale="medium" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_width" value="0"/>
+ <distance name="right_width" value="0"/>
+ <distance name="bottom_height" value="0"/>
+ <distance name="left_titlebar_edge" value="1"/>
+ <distance name="right_titlebar_edge" value="2"/>
+ <distance name="button_width" value="28"/>
+ <distance name="button_height" value="20"/>
+ <distance name="title_vertical_pad" value="0"/>
+ <border name="title_border" left="0" right="0" top="3" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="2" bottom="1"/>
+</frame_geometry>
+
+
+
+<!--
+ Window Title
+-->
+
+<draw_ops name="draw_title_text_normal">
+ <title x="1" y="(height - title_height) / 2 + 1" color="#2F2E2B"/>
+ <title x="0" y="(height - title_height) / 2" color="#FFFFFF"/>
+</draw_ops>
+
+<draw_ops name="draw_title_text_inactive">
+ <title x="0" y="(height - title_height) / 2" color="#727262"/>
+</draw_ops>
+
+<!--
+ Construct titlebar from rectangles instead so we can pick up the GTK theme
+-->
+
+<draw_ops name="draw_title">
+ <!-- Background gradient -->
+ <gradient type="vertical" x="0" y="0" width="width+9" height="height">
+ <!--<color value="#80594D"/>-->
+ <color value="shade/gtk:bg[SELECTED]/0.6"/>
+ <color value="shade/gtk:bg[NORMAL]/0.29" />
+ <color value="shade/gtk:bg[NORMAL]/0.27" />
+ <color value="shade/gtk:bg[NORMAL]/0.25" />
+ <color value="shade/gtk:bg[NORMAL]/0.24" />
+ </gradient>
+
+ <!-- Topmost dark line -->
+ <line color="shade/gtk:bg[NORMAL]/0.30" x1="0" x2="width" y1="0" y2="0"/>
+
+ <!-- Darkening of the left arch -->
+ <line color="shade/gtk:bg[NORMAL]/0.30" x1="0" x2="4" y1="1" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.30" x1="0" x2="2" y1="2" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.30" x1="0" x2="1" y1="3" y2="3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.30" x1="0" x2="1" y1="4" y2="4"/>
+ <line color="shade/gtk:bg[NORMAL]/0.30" x1="0" x2="0" y1="5" y2="20"/>
+
+ <!-- Top highlight -->
+ <!-- This uses <tint> (which supports alpha) instead of <line> (which doesn't) -->
+ <tint color="#ffffff" alpha="0.35" x="5" y="1" width="width - 10" height="1"/>
+
+ <!-- Upper-left highlight -->
+ <!-- Draw each pixel one by one. Again, using <tint> for alpha support. -->
+ <tint color="#ffffff" alpha="0.30" x="3" y="2" width="2" height="1"/>
+ <tint color="#ffffff" alpha="0.27" x="2" y="3" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.24" x="2" y="4" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.21" x="1" y="5" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.18" x="1" y="6" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.15" x="1" y="7" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.12" x="1" y="8" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.09" x="1" y="9" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.06" x="1" y="10" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.03" x="1" y="11" width="1" height="1"/>
+
+ <!-- Upper-right highlight -->
+ <!-- Same as above. -->
+ <tint color="#ffffff" alpha="0.30" x="width-5" y="2" width="2" height="1"/>
+ <tint color="#ffffff" alpha="0.27" x="width-3" y="3" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.24" x="width-3" y="4" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.21" x="width-2" y="5" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.18" x="width-2" y="6" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.15" x="width-2" y="7" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.12" x="width-2" y="8" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.09" x="width-2" y="9" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.06" x="width-2" y="10" width="1" height="1"/>
+ <tint color="#ffffff" alpha="0.03" x="width-2" y="11" width="1" height="1"/>
+
+</draw_ops>
+
+<draw_ops name="draw_title_inactive">
+ <!-- Background gradient -->
+ <gradient type="vertical" x="0" y="0" width="width+9" height="height">
+ <color value="shade/gtk:bg[NORMAL]/0.27" />
+ <color value="shade/gtk:bg[NORMAL]/0.24" />
+ </gradient>
+
+ <!-- Topmost dark line -->
+ <line color="shade/gtk:bg[NORMAL]/0.25" x1="1" x2="width-2" y1="0" y2="0"/>
+ <!-- Top highlight -->
+ <tint color="#ffffff" alpha="0.2" x="5" y="1" width="width - 10" height="1"/>
+
+ <!-- Lower-left highlight -->
+ <line color="shade/gtk:bg[NORMAL]/0.33" x1="3" x2 ="4" y1="2" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/0.32" x1="2" x2 ="2" y1="3" y2="4" />
+
+ <!-- Lower-left higlight fade -->
+ <gradient type="vertical" x="1" y="5" width="1" height="title_height - 4">
+ <color value="shade/gtk:bg[NORMAL]/0.30"/>
+ <color value="shade/gtk:bg[NORMAL]/0.25"/>
+ </gradient>
+
+ <!-- Upper right highlight -->
+ <line color="shade/gtk:bg[NORMAL]/0.30" x1="width-5" x2 ="width-4" y1="2" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/0.30" x1="width-3" x2 ="width-3" y1="3" y2="4" />
+
+ <!-- Lower-left higlight fade -->
+ <gradient type="vertical" x="width-2" y="5" width="1" height="title_height - 4">
+ <color value="shade/gtk:bg[NORMAL]/0.30"/>
+ <color value="shade/gtk:bg[NORMAL]/0.25"/>
+ </gradient>
+</draw_ops>
+
+<draw_ops name="draw_title_maximized">
+ <!-- Background gradient -->
+ <gradient type="vertical" x="0" y="0" width="width+9" height="height">
+ <!--<color value="#80594D"/>-->
+ <color value="shade/gtk:bg[SELECTED]/0.6"/>
+ <color value="shade/gtk:bg[NORMAL]/0.29" />
+ <color value="shade/gtk:bg[NORMAL]/0.27" />
+ <color value="shade/gtk:bg[NORMAL]/0.25" />
+ <color value="shade/gtk:bg[NORMAL]/0.24" />
+ </gradient>
+
+ <!-- Topmost dark line -->
+ <line color="shade/gtk:bg[NORMAL]/0.30" x1="0" x2="width" y1="0" y2="0"/>
+
+ <!-- Top highlight -->
+ <!-- This uses <tint> (which supports alpha) instead of <line> (which doesn't) -->
+ <tint color="#ffffff" alpha="0.35" x="1" y="1" width="width - 2" height="1"/>
+
+</draw_ops>
+
+<draw_ops name="draw_title_maximized_inactive">
+ <!-- Background gradient -->
+ <gradient type="vertical" x="0" y="0" width="width+9" height="height">
+ <!--<color value="#80594D"/>-->
+ <color value="shade/gtk:bg[NORMAL]/0.27" />
+ <color value="shade/gtk:bg[NORMAL]/0.24" />
+ </gradient>
+
+ <!-- Topmost dark line -->
+ <line color="shade/gtk:bg[NORMAL]/0.30" x1="0" x2="width" y1="0" y2="0"/>
+
+ <!-- Top highlight -->
+ <!-- This uses <tint> (which supports alpha) instead of <line> (which doesn't) -->
+ <tint color="#ffffff" alpha="0.35" x="1" y="1" width="width - 2" height="1"/>
+
+</draw_ops>
+
+<!--
+ The frame around windows
+-->
+
+<draw_ops name="draw_frame">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.25" x="0" y="0" width="width" height="height" filled="true"/>
+ <!-- RSC: Subtle gradient on left/right edges to match the menubar -->
+ <gradient type="vertical" x="0" y="0" width="width" height="100">
+ <color value="shade/gtk:bg[NORMAL]/0.25"/>
+ <color value="shade/gtk:bg[NORMAL]/0.15"/>
+ <color value="shade/gtk:bg[NORMAL]/0.25"/>
+ </gradient>
+</draw_ops>
+
+<!--Borderless only: make bottom border less apparent. We can do this now since our statusbars are light.-->
+<draw_ops name="bottom_edge">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.25" x="0" y="0" width="width" height="height" filled="true"/>
+ <gradient type="vertical" x="1" y="height-6" width="width-2" height="5">
+ <color value="shade/gtk:bg[NORMAL]/0.85"/>
+ <color value="shade/gtk:bg[NORMAL]/0.5"/>
+ </gradient>
+</draw_ops>
+
+
+<!--
+ Buttons
+-->
+<!-- close button-->
+<draw_ops name="button_close_normal">
+ <image filename="button_close_normal.png" x="0" y="1" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_close_prelight">
+ <image filename="button_close_prelight.png" x="0" y="1" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_close_inactive_prelight">
+ <image filename="button_close_prelight.png" x="0" y="1" width="object_width" height="object_height" alpha="0.3"/>
+</draw_ops>
+<draw_ops name="button_close_pressed">
+ <image filename="button_close_pressed.png" x="0" y="1" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_close_inactive">
+ <image filename="button_close_normal.png" x="0" y="1" width="object_width" height="object_height" alpha="0.3"/>
+</draw_ops>
+
+<!-- maximize button-->
+<draw_ops name="button_max_normal">
+ <image filename="button_max_normal.png" x="0" y="1" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_max_prelight">
+ <image filename="button_max_prelight.png" x="0" y="1" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_max_inactive_prelight">
+ <image filename="button_max_prelight.png" x="0" y="1" width="object_width" height="object_height" alpha="0.3"/>
+</draw_ops>
+<draw_ops name="button_max_pressed">
+ <image filename="button_max_pressed.png" x="0" y="1" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_max_inactive">
+ <image filename="button_max_normal.png" x="0" y="1" width="object_width" height="object_height" alpha="0.3"/>
+</draw_ops>
+
+<!-- minimize button-->
+<draw_ops name="button_min_normal">
+ <image filename="button_min_normal.png" x="0" y="1" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_min_prelight">
+ <image filename="button_min_prelight.png" x="0" y="1" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_min_inactive_prelight">
+ <image filename="button_min_prelight.png" x="0" y="1" width="object_width" height="object_height" alpha="0.3"/>
+</draw_ops>
+<draw_ops name="button_min_pressed">
+ <image filename="button_min_pressed.png" x="0" y="1" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_min_inactive">
+ <image filename="button_min_normal.png" x="0" y="1" width="object_width" height="object_height" alpha="0.3"/>
+</draw_ops>
+
+<!-- menu button -->
+<!--
+<draw_ops name="button_menu_normal">
+<image filename="button_menu_normal.png" x="0" y="2" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_menu_prelight">
+<image filename="button_menu_prelight.png" x="0" y="2" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_menu_inactive_prelight">
+<image filename="button_menu_prelight.png" x="0" y="2" width="object_width" height="object_height" alpha="0.3"/>
+</draw_ops>
+<draw_ops name="button_menu_pressed">
+<image filename="button_menu_pressed.png" x="0" y="2" width="object_width" height="object_height"/>
+</draw_ops>
+<draw_ops name="button_menu_inactive">
+<image filename="button_menu_normal.png" x="0" y="2" width="object_width" height="object_height" alpha="0.3"/>
+</draw_ops>-->
+
+<!-- menu icon -->
+<draw_ops name="button_menu_normal">
+<icon x="6" y="2" width="mini_icon_width" height="mini_icon_height"/>
+</draw_ops>
+<draw_ops name="button_menu_prelight">
+<icon x="6" y="2" width="mini_icon_width" height="mini_icon_height"/>
+</draw_ops>
+<draw_ops name="button_menu_inactive_prelight">
+<icon x="6" y="2" width="mini_icon_width" height="mini_icon_height" alpha="0.3"/>
+</draw_ops>
+<draw_ops name="button_menu_pressed">
+<icon x="6" y="2" width="mini_icon_width" height="mini_icon_height"/>
+</draw_ops>
+<draw_ops name="button_menu_inactive">
+<icon x="6" y="2" width="mini_icon_width" height="mini_icon_height" alpha="0.3"/>
+</draw_ops>
+
+
+
+<!--
+ Frame styles
+-->
+
+<!-- normal -->
+<frame_style name="frame_style_normal_focused" geometry="frame_geometry_normal">
+ <piece position="title" draw_ops="draw_title_text_normal"/>
+ <piece position="titlebar" draw_ops="draw_title"/>
+ <piece position="left_edge" draw_ops="draw_frame"/>
+ <piece position="right_edge" draw_ops="draw_frame"/>
+ <piece position="bottom_edge" draw_ops="bottom_edge"/>
+ <button function="close" state="normal" draw_ops="button_close_normal"/>
+ <button function="close" state="prelight" draw_ops="button_close_prelight"/>
+ <button function="close" state="pressed" draw_ops="button_close_pressed"/>
+ <button function="minimize" state="normal" draw_ops="button_min_normal"/>
+ <button function="minimize" state="prelight" draw_ops="button_min_prelight"/>
+ <button function="minimize" state="pressed" draw_ops="button_min_pressed"/>
+ <button function="maximize" state="normal" draw_ops="button_max_normal"/>
+ <button function="maximize" state="prelight" draw_ops="button_max_prelight"/>
+ <button function="maximize" state="pressed" draw_ops="button_max_pressed"/>
+ <button function="menu" state="normal" draw_ops="button_menu_normal"/>
+ <button function="menu" state="prelight" draw_ops="button_menu_prelight"/>
+ <button function="menu" state="pressed" draw_ops="button_menu_pressed"/>
+</frame_style>
+
+<frame_style name="frame_style_normal_unfocused" geometry="frame_geometry_normal">
+ <piece position="title" draw_ops="draw_title_text_inactive"/>
+ <piece position="titlebar" draw_ops="draw_title_inactive"/>
+ <piece position="left_edge" draw_ops="draw_frame"/>
+ <piece position="right_edge" draw_ops="draw_frame"/>
+ <piece position="bottom_edge" draw_ops="bottom_edge"/>
+ <button function="close" state="normal" draw_ops="button_close_inactive"/>
+ <button function="close" state="prelight" draw_ops="button_close_inactive_prelight"/>
+ <button function="close" state="pressed" draw_ops="button_close_inactive"/>
+ <button function="minimize" state="normal" draw_ops="button_min_inactive"/>
+ <button function="minimize" state="prelight" draw_ops="button_min_inactive_prelight"/>
+ <button function="minimize" state="pressed" draw_ops="button_min_inactive"/>
+ <button function="maximize" state="normal" draw_ops="button_max_inactive"/>
+ <button function="maximize" state="prelight" draw_ops="button_max_inactive_prelight"/>
+ <button function="maximize" state="pressed" draw_ops="button_max_inactive"/>
+ <button function="menu" state="normal" draw_ops="button_menu_inactive"/>
+ <button function="menu" state="prelight" draw_ops="button_menu_inactive_prelight"/>
+ <button function="menu" state="pressed" draw_ops="button_menu_inactive"/>
+</frame_style>
+
+<frame_style name="frame_style_maximized_focused" geometry="frame_geometry_abnormal">
+ <piece position="title" draw_ops="draw_title_text_normal"/>
+ <piece position="titlebar" draw_ops="draw_title_maximized"/>
+ <piece position="left_edge" draw_ops="draw_frame"/>
+ <piece position="right_edge" draw_ops="draw_frame"/>
+ <piece position="bottom_edge" draw_ops="bottom_edge"/>
+ <button function="close" state="normal" draw_ops="button_close_normal"/>
+ <button function="close" state="prelight" draw_ops="button_close_prelight"/>
+ <button function="close" state="pressed" draw_ops="button_close_pressed"/>
+ <button function="minimize" state="normal" draw_ops="button_min_normal"/>
+ <button function="minimize" state="prelight" draw_ops="button_min_prelight"/>
+ <button function="minimize" state="pressed" draw_ops="button_min_pressed"/>
+ <button function="maximize" state="normal" draw_ops="button_max_normal"/>
+ <button function="maximize" state="prelight" draw_ops="button_max_prelight"/>
+ <button function="maximize" state="pressed" draw_ops="button_max_pressed"/>
+ <button function="menu" state="normal" draw_ops="button_menu_normal"/>
+ <button function="menu" state="prelight" draw_ops="button_menu_prelight"/>
+ <button function="menu" state="pressed" draw_ops="button_menu_normal"/>
+</frame_style>
+
+<frame_style name="frame_style_maximized_unfocused" geometry="frame_geometry_abnormal">
+ <piece position="title" draw_ops="draw_title_text_inactive"/>
+ <piece position="titlebar" draw_ops="draw_title_maximized_inactive"/>
+ <piece position="left_edge" draw_ops="draw_frame"/>
+ <piece position="right_edge" draw_ops="draw_frame"/>
+ <piece position="bottom_edge" draw_ops="bottom_edge"/>
+ <button function="close" state="normal" draw_ops="button_close_inactive"/>
+ <button function="close" state="prelight" draw_ops="button_close_prelight"/>
+ <button function="close" state="pressed" draw_ops="button_close_inactive"/>
+ <button function="minimize" state="normal" draw_ops="button_min_inactive"/>
+ <button function="minimize" state="prelight" draw_ops="button_min_prelight"/>
+ <button function="minimize" state="pressed" draw_ops="button_min_inactive"/>
+ <button function="maximize" state="normal" draw_ops="button_max_inactive"/>
+ <button function="maximize" state="prelight" draw_ops="button_max_prelight"/>
+ <button function="maximize" state="pressed" draw_ops="button_max_inactive"/>
+ <button function="menu" state="normal" draw_ops="button_menu_inactive"/>
+ <button function="menu" state="prelight" draw_ops="button_menu_inactive"/>
+ <button function="menu" state="pressed" draw_ops="button_menu_inactive"/>
+</frame_style>
+
+<frame_style_set name="frame_style_set_normal">
+ <frame focus="yes" state="normal" resize="both" style="frame_style_normal_focused"/>
+ <frame focus="no" state="normal" resize="both" style="frame_style_normal_unfocused"/>
+ <frame focus="yes" state="maximized" style="frame_style_maximized_focused"/>
+ <frame focus="no" state="maximized" style="frame_style_maximized_unfocused"/>
+ <frame focus="yes" state="shaded" style="frame_style_normal_focused"/>
+ <frame focus="no" state="shaded" style="frame_style_normal_unfocused"/>
+ <frame focus="yes" state="maximized_and_shaded" style="frame_style_maximized_focused"/>
+ <frame focus="no" state="maximized_and_shaded" style="frame_style_maximized_unfocused"/>
+</frame_style_set>
+
+<window type="normal" style_set="frame_style_set_normal"/>
+<window type="dialog" style_set="frame_style_set_normal"/>
+<window type="modal_dialog" style_set="frame_style_set_normal"/>
+<window type="menu" style_set="frame_style_set_normal"/>
+<window type="utility" style_set="frame_style_set_normal"/>
+<window type="border" style_set="frame_style_set_normal"/>
+
+<menu_icon function="close" state="normal" draw_ops="button_close_normal"/>
+<menu_icon function="maximize" state="normal" draw_ops="button_max_normal"/>
+<menu_icon function="unmaximize" state="normal" draw_ops="button_max_normal"/>
+<menu_icon function="minimize" state="normal" draw_ops="button_min_normal"/>
+
+
+</metacity_theme>
diff --git a/src/themes/Makefile.am b/src/themes/Makefile.am
new file mode 100644
index 00000000..a040a804
--- /dev/null
+++ b/src/themes/Makefile.am
@@ -0,0 +1,53 @@
+THEMES= \
+ ClearlooksRe \
+ eOS \
+ DustBlue \
+ WinMe \
+ Splint-Left \
+ Dopple \
+ Spidey-Left \
+ Splint \
+ Dopple-Left \
+ Spidey
+
+THEME_DIR=$(datadir)/themes
+THEME_SUBDIR=metacity-1
+
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)$(THEME_DIR); \
+ for THEME in $(THEMES); do \
+ echo '-- Installing theme '$$THEME; \
+ $(mkinstalldirs) $(DESTDIR)$(THEME_DIR)/$$THEME; \
+ $(mkinstalldirs) $(DESTDIR)$(THEME_DIR)/$$THEME/$(THEME_SUBDIR); \
+ (installfiles=`find $(srcdir)/$$THEME -name "*.png" -o -name "*.xml"`; \
+ for i in $$installfiles; do \
+ echo '-- Installing '$$i ; \
+ $(INSTALL_DATA) $$i $(DESTDIR)$(THEME_DIR)/$$THEME/$(THEME_SUBDIR) ; \
+ done) \
+ done
+
+uninstall-local:
+ for THEME in $(THEMES); do \
+ echo '-- Uninstalling theme '$$THEME; \
+ (uninstallfiles=`find $(srcdir)/$$THEME -name "*.png" -o -name "*.xml"`; \
+ for i in $$uninstallfiles; do \
+ i=`basename $$i`; \
+ echo '-- Removing '$$i ; \
+ rm -f $(DESTDIR)$(THEME_DIR)/$$THEME/$(THEME_SUBDIR)/$$i ; \
+ done); \
+ rmdir $(DESTDIR)$(THEME_DIR)/$$THEME/$(THEME_SUBDIR) || :; \
+ rmdir $(DESTDIR)$(THEME_DIR)/$$THEME || :; \
+ done
+ -rmdir $(DESTDIR)$(THEME_DIR)
+
+dist-hook:
+ mkdir $(distdir)/themes; \
+ for THEME in $(THEMES); do \
+ echo '-- Disting theme '$$THEME; \
+ mkdir $(distdir)/$$THEME; \
+ (installfiles=`find $(srcdir)/$$THEME -name "*.png" -o -name "*.xml"`; \
+ for i in $$installfiles; do \
+ echo '-- Disting '$$i ; \
+ cp $$i $(distdir)/$$THEME; \
+ done) \
+ done
diff --git a/src/themes/Spidey-Left/metacity-theme-1.xml b/src/themes/Spidey-Left/metacity-theme-1.xml
new file mode 100644
index 00000000..0244d458
--- /dev/null
+++ b/src/themes/Spidey-Left/metacity-theme-1.xml
@@ -0,0 +1,1086 @@
+<?xml version="1.0"?>
+<metacity_theme>
+
+<info>
+ <name>Spidey</name>
+ <author>Brandon Wright</author>
+ <copyright>2009</copyright>
+ <date>January 21, 2009</date>
+ <description>A Clearlooks-styled theme with Tango basics.</description>
+</info>
+
+<frame_geometry name="normal" rounded_top_left="true" rounded_top_right="true">
+ <distance name="left_width" value="3"/>
+ <distance name="right_width" value="3"/>
+ <distance name="bottom_height" value="3"/>
+ <distance name="left_titlebar_edge" value="4"/>
+ <distance name="right_titlebar_edge" value="4"/>
+ <aspect_ratio name="button" value="1"/>
+ <distance name="title_vertical_pad" value="2"/>
+ <border name="title_border" left="3" right="4" top="2" bottom="2"/>
+ <border name="button_border" left="1" right="1" top="3" bottom="3"/>
+</frame_geometry>
+
+<frame_geometry name="normal_small_borders" parent="normal" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_titlebar_edge" value="1" />
+ <distance name="right_titlebar_edge" value="1" />
+ <distance name="left_width" value="0" />
+ <distance name="right_width" value="0" />
+ <distance name="bottom_height" value="0" />
+</frame_geometry>
+
+<frame_geometry name="utility" parent="normal">
+</frame_geometry>
+
+<frame_geometry name="border" has_title="false" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="0"/>
+ <distance name="right_titlebar_edge" value="0"/>
+ <distance name="button_width" value="0"/>
+ <distance name="button_height" value="0"/>
+ <distance name="title_vertical_pad" value="4"/>
+ <border name="title_border" left="0" right="0" top="0" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="0" bottom="0"/>
+</frame_geometry>
+
+<!-- Buttons -->
+<draw_ops name="button_bg">
+
+<!-- Background -->
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ <color value="shade/gtk:bg[NORMAL]/0.8" />
+ </gradient>
+
+<!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width - 1" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="0" y1="1"
+ x2="1" y2="0" /> -->
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="width-1" y1="1"
+ x2="width-2" y2="0" /> -->
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="0" y1="height-2"
+ x2="1" y2="height-1" /> -->
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="width-1" y1="height-2"
+ x2="width-2" y2="height-1" /> -->
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.4"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.4" />
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.4" />
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ </gradient>
+
+</draw_ops>
+
+<draw_ops name="button_bg_prelight">
+
+<!-- Background -->
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/1.1" />
+ <color value="shade/gtk:bg[NORMAL]/0.9" />
+ </gradient>
+
+<!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width - 1" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="0" y1="1"
+ x2="1" y2="0" /> -->
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="width-1" y1="1"
+ x2="width-2" y2="0" /> -->
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="0" y1="height-2"
+ x2="1" y2="height-1" /> -->
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="width-1" y1="height-2"
+ x2="width-2" y2="height-1" /> -->
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.5"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.1"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.5" />
+ <color value="shade/gtk:bg[NORMAL]/1.1" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.5" />
+ <color value="shade/gtk:bg[NORMAL]/1.1" />
+ </gradient>
+
+</draw_ops>
+
+<draw_ops name="button_pressed_bg">
+
+<!-- Background -->
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/0.9" />
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ </gradient>
+
+<!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width - 1" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="0" y1="1"
+ x2="1" y2="0" /> -->
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="width-1" y1="1"
+ x2="width-2" y2="0" /> -->
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="0" y1="height-2"
+ x2="1" y2="height-1" /> -->
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="width-1" y1="height-2"
+ x2="width-2" y2="height-1" /> -->
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ </gradient>
+
+</draw_ops>
+<!-- Buttons -->
+<draw_ops name="button_bg_unfocused">
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[NORMAL]/0.8" />
+ <color value="shade/gtk:bg[NORMAL]/0.675" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width - 1" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[NORMAL]/0.8" />
+ <color value="shade/gtk:bg[NORMAL]/0.675" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.90"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ <color value="shade/gtk:bg[NORMAL]/0.90" />
+ </gradient>
+
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ <color value="shade/gtk:bg[NORMAL]/0.90" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.1"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.1"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/0.9"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/0.9"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="button_bg_prelight_unfocused">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/1.15" />
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+
+</draw_ops>
+
+<draw_ops name="button_pressed_bg_unfocused">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" /> -->
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+<!-- <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient> -->
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[NORMAL]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.00" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="menu_glyph">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" />
+</draw_ops>
+
+<draw_ops name="menu_glyph_unfocused">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" alpha="0.5"/>
+</draw_ops>
+
+<draw_ops name="close_glyph">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.1" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.1" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_lighter">
+ <line color="shade/gtk:bg[NORMAL]/1.1" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="shade/gtk:bg[NORMAL]/1.1" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/1.1" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/1.1" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused_lighter">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.2" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.2" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.2" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.2" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_lighter">
+ <rectangle color="shade/gtk:bg[NORMAL]/1.2" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+<draw_ops name="maximize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="minimize_glyph">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.2" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_lighter">
+ <rectangle color="shade/gtk:bg[NORMAL]/1.2" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="menu_button">
+ <include name="menu_glyph" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="menu_button_unfocused">
+ <include name="menu_glyph_unfocused" x="0" y="0" />
+</draw_ops>
+
+<draw_ops name="menu_button_pressed">
+ <include name="menu_glyph_unfocused" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="minimize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_lighter" x="2" y="3" width="width-4" height="height-4" />
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_lighter" x="2" y="3" width="width-4" height="height-4" />
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4" />
+</draw_ops>
+
+<draw_ops name="minimize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="minimize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+
+<draw_ops name="maximize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_lighter" x="2" y="3" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_lighter" x="2" y="3" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="maximize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="mini_window_icon">
+ <rectangle color="gtk:bg[NORMAL]" filled="true"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <rectangle color="gtk:fg[NORMAL]" filled="false"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <line color="gtk:fg[NORMAL]" width="2"
+ x1="0" y1="1" x2="width" y2="1"/>
+</draw_ops>
+
+<draw_ops name="restore_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_lighter" x="3" y="4" width="width-6" height="height-6"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_prelight">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_lighter" x="3" y="4" width="width-6" height="height-6"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_unfocused_lighter" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="maximize_glyph_unfocused_lighter" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="close_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_lighter" x="2" y="3" width="width-4" height="width-4"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_lighter" x="2" y="3" width="width-4" height="width-4"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="close_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.5"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel_unfocused">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="title_background">
+
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+<!--
+ <rectangle color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x="1" y="1" width="width-3" height="height-3"/>
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-2" y1="1" x2="width-2" y2="height-2"/>
+-->
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="1" y="1" width="width-3" height="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-2" y1="1" x2="width-2" y2="height-2"/>
+
+ <gradient type="vertical" x="2" y="2" width="width-4" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="3" y1="height-1"
+ x2="width-4" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.975"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="4" y1="1" x2="4" y2="1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+<!--
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="2" y1="3" x2="2" y2="4" />
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="3" y1="2" x2="4" y2="2" />
+
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-5" y1="2" x2="width-4" y2="2" />
+-->
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="3" y1="2" x2="4" y2="2" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+ <!-- <gradient type="vertical" x="1" y="5" width="1" height="height-6">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient>
+
+ <gradient type="vertical" x="width-2" y="5" width="1" height="height-6">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient> -->
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="1" y1="height-1" x2="1" y2="height-1" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-2" y1="height-1" x2="width-2" y2="height-1" />
+</draw_ops>
+
+<draw_ops name="title_background_maximized">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="0" y="1" width="width-2" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-1" y1="1" x2="width-1" y2="height-2"/>
+
+ <gradient type="vertical" x="1" y="2" width="width-2" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ </gradient>
+
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="0" y="1" width="width-1" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="0" y1="height-1"
+ x2="width-1" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.95"
+ x1="0" y1="height-2"
+ x2="width-1" y2="height-2" />
+
+<!-- <gradient type="vertical" x="0" y="1" width="1" height="height-2">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient>
+
+ <gradient type="vertical" x="width-1" y="1" width="1" height="height-2">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient> -->
+</draw_ops>
+
+<draw_ops name="title_unfocused_background">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.05"/>
+ <color value="shade/gtk:bg[NORMAL]/0.85"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/0.945" x1="1" y1="height-2" x2="width-3" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="0" y1="height-1" x2="width-1" y2="height-1" />
+
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="1" y1="1" x2="width-2" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="1" y1="1" x2="1" y2="height-1"/>
+
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-2" y1="1" x2="width-2" y2="height" />
+
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="4" y1="1" x2="4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="3" y1="2" x2="4" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+
+</draw_ops>
+
+<draw_ops name="title_unfocused_background_maximized">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="0" y="1" width="width" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.05"/>
+ <color value="shade/gtk:bg[NORMAL]/0.85"/>
+ </gradient>
+ <line color="shade/gtk:bg[NORMAL]/0.945" x1="0" y1="height-2" x2="width-1" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="0" y1="height-1" x2="width-1" y2="height-1" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="0" y1="1" x2="width-1" y2="1"/>
+
+</draw_ops>
+
+<draw_ops name="blank">
+<!-- nothing -->
+</draw_ops>
+
+<draw_ops name="focus_outline">
+ <rectangle x="0" y="0" width="width-1" height="height-1" color="shade/gtk:bg[SELECTED]/0.7" />
+ <rectangle x="1" y="1" width="width-3" height="height-3" color="shade/gtk:bg[SELECTED]/1.2" />
+ <rectangle x="2" y="2" width="width-5" height="height-5" color="shade/gtk:bg[SELECTED]/0.7" />
+</draw_ops>
+
+<draw_ops name="unfocus_outline">
+ <rectangle x="0" y="0" width="width-1" height="height-1" color="shade/gtk:bg[NORMAL]/0.7" />
+ <rectangle x="1" y="1" width="width-3" height="height-3" color="shade/gtk:bg[NORMAL]/1.2" />
+ <rectangle x="2" y="2" width="width-5" height="height-5" color="shade/gtk:bg[NORMAL]/0.7" />
+</draw_ops>
+
+<draw_ops name="focus_background">
+ <include name="focus_outline"/>
+</draw_ops>
+
+<draw_ops name="unfocus_background">
+ <include name="unfocus_outline" />
+</draw_ops>
+
+<draw_ops name="title_text_focused_with_icon">
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) - 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) - 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) + 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) + 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+ <title color="#ffffff"
+ x="(0)"
+ y="(((height - title_height) / 2) `max` 0)"/>
+
+</draw_ops>
+
+<draw_ops name="title_text_with_icon">
+ <title color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.4"
+ x="(0)"
+ y="((height - title_height) / 2) `max` 0"/>
+</draw_ops>
+
+<draw_ops name="title_normal">
+ <include name="title_text_with_icon"/>
+</draw_ops>
+
+<draw_ops name="title_focused">
+ <include name="title_text_focused_with_icon"/>
+</draw_ops>
+
+<frame_style name="normal_unfocused" geometry="normal">
+ <piece position="entire_background" draw_ops="unfocus_background"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background"/>
+ <piece position="title" draw_ops="title_normal"/>
+
+ <button function="close" state="normal" draw_ops="close_button_unfocused"/>
+ <button function="close" state="pressed" draw_ops="close_button_unfocused_pressed"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button_unfocused"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_unfocused_pressed"/>
+ <button function="maximize" state="normal" draw_ops="maximize_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_unfocused_pressed"/>
+ <button function="menu" state="normal" draw_ops="menu_button_unfocused"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_unfocused"/>
+</frame_style>
+
+<frame_style name="normal_focused" geometry="normal">
+ <piece position="entire_background" draw_ops="focus_background"/>
+ <piece position="titlebar" draw_ops="title_background"/>
+ <piece position="title" draw_ops="title_focused"/>
+ <button function="close" state="normal" draw_ops="close_button"/>
+ <button function="close" state="pressed" draw_ops="close_button_pressed"/>
+ <button function="close" state="prelight" draw_ops="close_button_focus"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_pressed"/>
+ <button function="minimize" state="prelight" draw_ops="minimize_button_focus" />
+ <button function="maximize" state="normal" draw_ops="maximize_button"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="maximize_button_focus"/>
+ <button function="menu" state="normal" draw_ops="menu_button"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_pressed"/>
+
+</frame_style>
+
+<frame_style name="maximized_unfocused" geometry="normal_small_borders" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="blank"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_unfocused_pressed"/>
+</frame_style>
+
+<frame_style name="maximized_focused" geometry="normal_small_borders" parent="normal_focused">
+ <piece position="entire_background" draw_ops="focus_outline"/>
+ <piece position="titlebar" draw_ops="title_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="restore_button_prelight" />
+</frame_style>
+
+<frame_style name="border" geometry="border" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="outer_bevel"/>
+ <piece position="titlebar" draw_ops="blank"/>
+ <piece position="title" draw_ops="blank"/>
+</frame_style>
+
+<frame_style_set name="normal">
+<frame focus="yes" state="normal" resize="both" style="normal_focused"/>
+<frame focus="no" state="normal" resize="both" style="normal_unfocused"/>
+<frame focus="yes" state="maximized" style="maximized_focused"/>
+<frame focus="no" state="maximized" style="maximized_unfocused"/>
+<frame focus="yes" state="shaded" style="normal_focused"/>
+<frame focus="no" state="shaded" style="normal_unfocused"/>
+<frame focus="yes" state="maximized_and_shaded" style="maximized_focused"/>
+<frame focus="no" state="maximized_and_shaded" style="maximized_unfocused"/>
+</frame_style_set>
+
+<frame_style_set name="border">
+<frame focus="yes" state="normal" resize="both" style="border"/>
+<frame focus="no" state="normal" resize="both" style="border"/>
+<frame focus="yes" state="maximized" style="border"/>
+<frame focus="no" state="maximized" style="border"/>
+<frame focus="yes" state="shaded" style="border"/>
+<frame focus="no" state="shaded" style="border"/>
+<frame focus="yes" state="maximized_and_shaded" style="border"/>
+<frame focus="no" state="maximized_and_shaded" style="border"/>
+</frame_style_set>
+
+<window type="normal" style_set="normal"/>
+<window type="dialog" style_set="normal"/>
+<window type="modal_dialog" style_set="normal"/>
+<window type="menu" style_set="normal"/>
+<window type="utility" style_set="normal"/>
+<window type="border" style_set="normal"/>
+
+<menu_icon function="close" state="normal" draw_ops="close_button"/>
+<menu_icon function="maximize" state="normal" draw_ops="maximize_button"/>
+<menu_icon function="unmaximize" state="normal" draw_ops="restore_button"/>
+<menu_icon function="minimize" state="normal" draw_ops="minimize_button"/>
+
+</metacity_theme>
diff --git a/src/themes/Spidey/metacity-theme-1.xml b/src/themes/Spidey/metacity-theme-1.xml
new file mode 100644
index 00000000..5472f9da
--- /dev/null
+++ b/src/themes/Spidey/metacity-theme-1.xml
@@ -0,0 +1,1086 @@
+<?xml version="1.0"?>
+<metacity_theme>
+
+<info>
+ <name>Spidey</name>
+ <author>Brandon Wright</author>
+ <copyright>2009</copyright>
+ <date>January 21, 2009</date>
+ <description>A Clearlooks-styled theme with Tango basics.</description>
+</info>
+
+<frame_geometry name="normal" rounded_top_left="true" rounded_top_right="true">
+ <distance name="left_width" value="3"/>
+ <distance name="right_width" value="3"/>
+ <distance name="bottom_height" value="3"/>
+ <distance name="left_titlebar_edge" value="4"/>
+ <distance name="right_titlebar_edge" value="4"/>
+ <aspect_ratio name="button" value="1"/>
+ <distance name="title_vertical_pad" value="2"/>
+ <border name="title_border" left="3" right="4" top="2" bottom="2"/>
+ <border name="button_border" left="1" right="1" top="3" bottom="3"/>
+</frame_geometry>
+
+<frame_geometry name="normal_small_borders" parent="normal" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_titlebar_edge" value="1" />
+ <distance name="right_titlebar_edge" value="1" />
+ <distance name="left_width" value="0" />
+ <distance name="right_width" value="0" />
+ <distance name="bottom_height" value="0" />
+</frame_geometry>
+
+<frame_geometry name="utility" parent="normal">
+</frame_geometry>
+
+<frame_geometry name="border" has_title="false" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="0"/>
+ <distance name="right_titlebar_edge" value="0"/>
+ <distance name="button_width" value="0"/>
+ <distance name="button_height" value="0"/>
+ <distance name="title_vertical_pad" value="4"/>
+ <border name="title_border" left="0" right="0" top="0" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="0" bottom="0"/>
+</frame_geometry>
+
+<!-- Buttons -->
+<draw_ops name="button_bg">
+
+<!-- Background -->
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ <color value="shade/gtk:bg[NORMAL]/0.8" />
+ </gradient>
+
+<!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width - 1" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="0" y1="1"
+ x2="1" y2="0" /> -->
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="width-1" y1="1"
+ x2="width-2" y2="0" /> -->
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="0" y1="height-2"
+ x2="1" y2="height-1" /> -->
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="width-1" y1="height-2"
+ x2="width-2" y2="height-1" /> -->
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.4"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.4" />
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.4" />
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ </gradient>
+
+</draw_ops>
+
+<draw_ops name="button_bg_prelight">
+
+<!-- Background -->
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/1.1" />
+ <color value="shade/gtk:bg[NORMAL]/0.9" />
+ </gradient>
+
+<!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width - 1" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="0" y1="1"
+ x2="1" y2="0" /> -->
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="width-1" y1="1"
+ x2="width-2" y2="0" /> -->
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="0" y1="height-2"
+ x2="1" y2="height-1" /> -->
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="width-1" y1="height-2"
+ x2="width-2" y2="height-1" /> -->
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.5"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.1"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.5" />
+ <color value="shade/gtk:bg[NORMAL]/1.1" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.5" />
+ <color value="shade/gtk:bg[NORMAL]/1.1" />
+ </gradient>
+
+</draw_ops>
+
+<draw_ops name="button_pressed_bg">
+
+<!-- Background -->
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/0.9" />
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ </gradient>
+
+<!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width - 1" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[SELECTED]/0.5" />
+ <color value="shade/gtk:bg[SELECTED]/0.7" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="0" y1="1"
+ x2="1" y2="0" /> -->
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.5"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.9"
+ x1="width-1" y1="1"
+ x2="width-2" y2="0" /> -->
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="0" y1="height-2"
+ x2="1" y2="height-1" /> -->
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+<!-- <line color="blend/gtk:bg[SELECTED]/gtk:bg[SELECTED]/0.95"
+ x1="width-1" y1="height-2"
+ x2="width-2" y2="height-1" /> -->
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.0" />
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ </gradient>
+
+</draw_ops>
+<!-- Buttons -->
+<draw_ops name="button_bg_unfocused">
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <gradient type="vertical" x="0" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[NORMAL]/0.8" />
+ <color value="shade/gtk:bg[NORMAL]/0.675" />
+ </gradient>
+
+ <!-- Right line -->
+ <gradient type="vertical" x="width - 1" y="2" width="1" height="(height-4)">
+ <color value="shade/gtk:bg[NORMAL]/0.8" />
+ <color value="shade/gtk:bg[NORMAL]/0.675" />
+ </gradient>
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.675"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.90"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ <color value="shade/gtk:bg[NORMAL]/0.90" />
+ </gradient>
+
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.2" />
+ <color value="shade/gtk:bg[NORMAL]/0.90" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.1"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.1"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/0.9"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/0.9"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="button_bg_prelight_unfocused">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/1.15" />
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.2"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+
+</draw_ops>
+
+<draw_ops name="button_pressed_bg_unfocused">
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ <color value="shade/gtk:bg[NORMAL]/0.95" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[NORMAL]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/1.25"
+ x1="2" y1="1"
+ x2="width-3" y2="1" /> -->
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[NORMAL]/1.05"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+<!-- <line color="shade/gtk:bg[NORMAL]/"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+<!-- <gradient type="vertical" x="1" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.25" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient> -->
+
+ <!-- Right line -->
+<!-- <line color="blend/gtk:bg[NORMAL]/#ffffff/0.18"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+ <gradient type="vertical" x="width-2" y="2" width="1" height="(height-3)-1">
+ <color value="shade/gtk:bg[NORMAL]/1.00" />
+ <color value="shade/gtk:bg[NORMAL]/1.05" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[NORMAL]/1.0"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="menu_glyph">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" />
+</draw_ops>
+
+<draw_ops name="menu_glyph_unfocused">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" alpha="0.5"/>
+</draw_ops>
+
+<draw_ops name="close_glyph">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.1" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.1" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_lighter">
+ <line color="shade/gtk:bg[NORMAL]/1.1" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="shade/gtk:bg[NORMAL]/1.1" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/1.1" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/1.1" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused_lighter">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.2" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.2" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.2" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.2" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_lighter">
+ <rectangle color="shade/gtk:bg[NORMAL]/1.2" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+<draw_ops name="maximize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="minimize_glyph">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.2" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_lighter">
+ <rectangle color="shade/gtk:bg[NORMAL]/1.2" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="menu_button">
+ <include name="menu_glyph" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="menu_button_unfocused">
+ <include name="menu_glyph_unfocused" x="0" y="0" />
+</draw_ops>
+
+<draw_ops name="menu_button_pressed">
+ <include name="menu_glyph_unfocused" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="minimize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_lighter" x="2" y="3" width="width-4" height="height-4" />
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_lighter" x="2" y="3" width="width-4" height="height-4" />
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4" />
+</draw_ops>
+
+<draw_ops name="minimize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="minimize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+
+<draw_ops name="maximize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_lighter" x="2" y="3" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_lighter" x="2" y="3" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="maximize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="mini_window_icon">
+ <rectangle color="gtk:bg[NORMAL]" filled="true"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <rectangle color="gtk:fg[NORMAL]" filled="false"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <line color="gtk:fg[NORMAL]" width="2"
+ x1="0" y1="1" x2="width" y2="1"/>
+</draw_ops>
+
+<draw_ops name="restore_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_lighter" x="3" y="4" width="width-6" height="height-6"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_prelight">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_lighter" x="3" y="4" width="width-6" height="height-6"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_unfocused_lighter" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="maximize_glyph_unfocused_lighter" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="close_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_lighter" x="2" y="3" width="width-4" height="width-4"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_lighter" x="2" y="3" width="width-4" height="width-4"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused">
+ <include name="button_bg_unfocused" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused_pressed">
+ <include name="button_pressed_bg_unfocused"/>
+ <include name="close_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.5"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel_unfocused">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="title_background">
+
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+<!--
+ <rectangle color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x="1" y="1" width="width-3" height="height-3"/>
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-2" y1="1" x2="width-2" y2="height-2"/>
+-->
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="1" y="1" width="width-3" height="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-2" y1="1" x2="width-2" y2="height-2"/>
+
+ <gradient type="vertical" x="2" y="2" width="width-4" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="3" y1="height-1"
+ x2="width-4" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.975"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="4" y1="1" x2="4" y2="1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+<!--
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="2" y1="3" x2="2" y2="4" />
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="3" y1="2" x2="4" y2="2" />
+
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="blend/gtk:bg[SELECTED]/#ffffff/0.27" x1="width-5" y1="2" x2="width-4" y2="2" />
+-->
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="3" y1="2" x2="4" y2="2" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+ <!-- <gradient type="vertical" x="1" y="5" width="1" height="height-6">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient>
+
+ <gradient type="vertical" x="width-2" y="5" width="1" height="height-6">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient> -->
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="1" y1="height-1" x2="1" y2="height-1" />
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-2" y1="height-1" x2="width-2" y2="height-1" />
+</draw_ops>
+
+<draw_ops name="title_background_maximized">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="0" y="1" width="width-2" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/1.2" x1="width-1" y1="1" x2="width-1" y2="height-2"/>
+
+ <gradient type="vertical" x="1" y="2" width="width-2" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ </gradient>
+
+ <rectangle color="shade/gtk:bg[SELECTED]/1.2" x="0" y="1" width="width-1" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/0.7"
+ x1="0" y1="height-1"
+ x2="width-1" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.95"
+ x1="0" y1="height-2"
+ x2="width-1" y2="height-2" />
+
+<!-- <gradient type="vertical" x="0" y="1" width="1" height="height-2">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient>
+
+ <gradient type="vertical" x="width-1" y="1" width="1" height="height-2">
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.27" />
+ <color value="blend/gtk:bg[SELECTED]/#ffffff/0.09" />
+ </gradient> -->
+</draw_ops>
+
+<draw_ops name="title_unfocused_background">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.05"/>
+ <color value="shade/gtk:bg[NORMAL]/0.85"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/0.945" x1="1" y1="height-2" x2="width-3" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="0" y1="height-1" x2="width-1" y2="height-1" />
+
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="1" y1="1" x2="width-2" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="1" y1="1" x2="1" y2="height-1"/>
+
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-2" y1="1" x2="width-2" y2="height" />
+
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="4" y1="1" x2="4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="3" y1="2" x2="4" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+
+</draw_ops>
+
+<draw_ops name="title_unfocused_background_maximized">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="0" y="1" width="width" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.05"/>
+ <color value="shade/gtk:bg[NORMAL]/0.85"/>
+ </gradient>
+ <line color="shade/gtk:bg[NORMAL]/0.945" x1="0" y1="height-2" x2="width-1" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="0" y1="height-1" x2="width-1" y2="height-1" />
+ <line color="shade/gtk:bg[NORMAL]/1.2" x1="0" y1="1" x2="width-1" y2="1"/>
+
+</draw_ops>
+
+<draw_ops name="blank">
+<!-- nothing -->
+</draw_ops>
+
+<draw_ops name="focus_outline">
+ <rectangle x="0" y="0" width="width-1" height="height-1" color="shade/gtk:bg[SELECTED]/0.7" />
+ <rectangle x="1" y="1" width="width-3" height="height-3" color="shade/gtk:bg[SELECTED]/1.2" />
+ <rectangle x="2" y="2" width="width-5" height="height-5" color="shade/gtk:bg[SELECTED]/0.7" />
+</draw_ops>
+
+<draw_ops name="unfocus_outline">
+ <rectangle x="0" y="0" width="width-1" height="height-1" color="shade/gtk:bg[NORMAL]/0.7" />
+ <rectangle x="1" y="1" width="width-3" height="height-3" color="shade/gtk:bg[NORMAL]/1.2" />
+ <rectangle x="2" y="2" width="width-5" height="height-5" color="shade/gtk:bg[NORMAL]/0.7" />
+</draw_ops>
+
+<draw_ops name="focus_background">
+ <include name="focus_outline"/>
+</draw_ops>
+
+<draw_ops name="unfocus_background">
+ <include name="unfocus_outline" />
+</draw_ops>
+
+<draw_ops name="title_text_focused_with_icon">
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) - 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) - 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) + 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) + 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+ <title color="#ffffff"
+ x="(((width - title_width) / 2) `max` 0)"
+ y="(((height - title_height) / 2) `max` 0)"/>
+
+</draw_ops>
+
+<draw_ops name="title_text_with_icon">
+ <title color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.4"
+ x="(((width - title_width) / 2) `max` 0)"
+ y="((height - title_height) / 2) `max` 0"/>
+</draw_ops>
+
+<draw_ops name="title_normal">
+ <include name="title_text_with_icon"/>
+</draw_ops>
+
+<draw_ops name="title_focused">
+ <include name="title_text_focused_with_icon"/>
+</draw_ops>
+
+<frame_style name="normal_unfocused" geometry="normal">
+ <piece position="entire_background" draw_ops="unfocus_background"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background"/>
+ <piece position="title" draw_ops="title_normal"/>
+
+ <button function="close" state="normal" draw_ops="close_button_unfocused"/>
+ <button function="close" state="pressed" draw_ops="close_button_unfocused_pressed"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button_unfocused"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_unfocused_pressed"/>
+ <button function="maximize" state="normal" draw_ops="maximize_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_unfocused_pressed"/>
+ <button function="menu" state="normal" draw_ops="menu_button_unfocused"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_unfocused"/>
+</frame_style>
+
+<frame_style name="normal_focused" geometry="normal">
+ <piece position="entire_background" draw_ops="focus_background"/>
+ <piece position="titlebar" draw_ops="title_background"/>
+ <piece position="title" draw_ops="title_focused"/>
+ <button function="close" state="normal" draw_ops="close_button"/>
+ <button function="close" state="pressed" draw_ops="close_button_pressed"/>
+ <button function="close" state="prelight" draw_ops="close_button_focus"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_pressed"/>
+ <button function="minimize" state="prelight" draw_ops="minimize_button_focus" />
+ <button function="maximize" state="normal" draw_ops="maximize_button"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="maximize_button_focus"/>
+ <button function="menu" state="normal" draw_ops="menu_button"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_pressed"/>
+
+</frame_style>
+
+<frame_style name="maximized_unfocused" geometry="normal_small_borders" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="blank"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_unfocused_pressed"/>
+</frame_style>
+
+<frame_style name="maximized_focused" geometry="normal_small_borders" parent="normal_focused">
+ <piece position="entire_background" draw_ops="focus_outline"/>
+ <piece position="titlebar" draw_ops="title_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="restore_button_prelight" />
+</frame_style>
+
+<frame_style name="border" geometry="border" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="outer_bevel"/>
+ <piece position="titlebar" draw_ops="blank"/>
+ <piece position="title" draw_ops="blank"/>
+</frame_style>
+
+<frame_style_set name="normal">
+<frame focus="yes" state="normal" resize="both" style="normal_focused"/>
+<frame focus="no" state="normal" resize="both" style="normal_unfocused"/>
+<frame focus="yes" state="maximized" style="maximized_focused"/>
+<frame focus="no" state="maximized" style="maximized_unfocused"/>
+<frame focus="yes" state="shaded" style="normal_focused"/>
+<frame focus="no" state="shaded" style="normal_unfocused"/>
+<frame focus="yes" state="maximized_and_shaded" style="maximized_focused"/>
+<frame focus="no" state="maximized_and_shaded" style="maximized_unfocused"/>
+</frame_style_set>
+
+<frame_style_set name="border">
+<frame focus="yes" state="normal" resize="both" style="border"/>
+<frame focus="no" state="normal" resize="both" style="border"/>
+<frame focus="yes" state="maximized" style="border"/>
+<frame focus="no" state="maximized" style="border"/>
+<frame focus="yes" state="shaded" style="border"/>
+<frame focus="no" state="shaded" style="border"/>
+<frame focus="yes" state="maximized_and_shaded" style="border"/>
+<frame focus="no" state="maximized_and_shaded" style="border"/>
+</frame_style_set>
+
+<window type="normal" style_set="normal"/>
+<window type="dialog" style_set="normal"/>
+<window type="modal_dialog" style_set="normal"/>
+<window type="menu" style_set="normal"/>
+<window type="utility" style_set="normal"/>
+<window type="border" style_set="normal"/>
+
+<menu_icon function="close" state="normal" draw_ops="close_button"/>
+<menu_icon function="maximize" state="normal" draw_ops="maximize_button"/>
+<menu_icon function="unmaximize" state="normal" draw_ops="restore_button"/>
+<menu_icon function="minimize" state="normal" draw_ops="minimize_button"/>
+
+</metacity_theme>
diff --git a/src/themes/Splint-Left/metacity-theme-1.xml b/src/themes/Splint-Left/metacity-theme-1.xml
new file mode 100644
index 00000000..f81ad5a3
--- /dev/null
+++ b/src/themes/Splint-Left/metacity-theme-1.xml
@@ -0,0 +1,802 @@
+<?xml version="1.0"?>
+<metacity_theme>
+
+<info>
+ <name>Splint-Left</name>
+ <author>Brandon Wright</author>
+ <copyright>2009</copyright>
+ <date>January 21, 2009</date>
+ <description>A theme designed around the Tango style.</description>
+</info>
+
+<frame_geometry name="normal" rounded_top_left="true" rounded_top_right="true">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="4"/>
+ <distance name="right_titlebar_edge" value="4"/>
+ <aspect_ratio name="button" value="1"/>
+ <distance name="title_vertical_pad" value="2"/>
+ <border name="title_border" left="3" right="4" top="2" bottom="2"/>
+ <border name="button_border" left="1" right="1" top="3" bottom="3"/>
+</frame_geometry>
+
+<frame_geometry name="normal_small_borders" parent="normal" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_titlebar_edge" value="1" />
+ <distance name="right_titlebar_edge" value="1" />
+ <distance name="left_width" value="0" />
+ <distance name="right_width" value="0" />
+ <distance name="bottom_height" value="0" />
+</frame_geometry>
+
+<frame_geometry name="utility" parent="normal">
+</frame_geometry>
+
+<frame_geometry name="border" has_title="false" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="0"/>
+ <distance name="right_titlebar_edge" value="0"/>
+ <distance name="button_width" value="0"/>
+ <distance name="button_height" value="0"/>
+ <distance name="title_vertical_pad" value="4"/>
+ <border name="title_border" left="0" right="0" top="0" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="0" bottom="0"/>
+</frame_geometry>
+
+<!-- Buttons -->
+<draw_ops name="button_bg">
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="1" y1="2"
+ x2="1" y2="height-3" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" />
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="button_bg_prelight">
+
+ <!-- Do a fill -->
+ <rectangle color="shade/gtk:bg[SELECTED]/1.0" x="1" y="1" width="width-2" height = "height-2" filled="true" />
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="1" y1="2"
+ x2="1" y2="height-3" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" />
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+
+</draw_ops>
+
+<draw_ops name="button_pressed_bg">
+
+ <!-- Do a fill -->
+<!-- <rectangle color="shade/gtk:bg[SELECTED]/0.8" x="1" y="1" width="width-2" height = "height-2" filled="true" /> -->
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[SELECTED]/0.9" />
+ <color value="shade/gtk:bg[SELECTED]/1.2" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <!-- <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+
+ <gradient type="vertical" x="1" y="2" width="1" height="height-4">
+ <color value="shade/gtk:bg[SELECTED]/1.1" />
+ <color value="shade/gtk:bg[SELECTED]/1.2" />
+ </gradient>
+
+ <!-- Left line -->
+ <!-- <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+
+
+ <gradient type="vertical" x="width-2" y="2" width="1" height="height-4">
+ <color value="shade/gtk:bg[SELECTED]/1.1" />
+ <color value="shade/gtk:bg[SELECTED]/1.2" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+
+</draw_ops>
+
+<draw_ops name="menu_glyph">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" />
+</draw_ops>
+
+<draw_ops name="menu_glyph_unfocused">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" alpha="0.5"/>
+</draw_ops>
+
+<draw_ops name="close_glyph">
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="white" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="white" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_shadow">
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_shadow_dark">
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused_lighter">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph">
+ <rectangle color="white" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="white" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="white" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="white" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+<draw_ops name="maximize_glyph_shadow">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.85" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_shadow_dark">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.8" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+<draw_ops name="maximize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+
+
+</draw_ops>
+
+<draw_ops name="minimize_glyph">
+ <rectangle color="white" x="2" y="height * 2 / 3" width="width-4" height="height - height * 2 / 3 - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_shadow">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.85" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_shadow_dark">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.8" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="menu_button">
+ <include name="menu_glyph" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="menu_button_unfocused">
+ <include name="menu_glyph_unfocused" x="0" y="0" />
+</draw_ops>
+
+<draw_ops name="menu_button_pressed">
+ <include name="menu_glyph_unfocused" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="minimize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_shadow" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused">
+ <include name="minimize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4" />
+</draw_ops>
+
+<draw_ops name="minimize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused_pressed">
+ <include name="minimize_glyph_unfocused" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+
+<draw_ops name="maximize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused">
+ <include name="maximize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused_pressed">
+ <include name="maximize_glyph_unfocused" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="mini_window_icon">
+ <rectangle color="gtk:bg[NORMAL]" filled="true"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <rectangle color="gtk:fg[NORMAL]" filled="false"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <line color="gtk:fg[NORMAL]" width="2"
+ x1="0" y1="1" x2="width" y2="1"/>
+</draw_ops>
+
+<draw_ops name="restore_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow" x="3" y="5" width="width-6" height="height-6" />
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_prelight">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow_dark" x="3" y="5" width="width-6" height="height-6"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused">
+ <include name="maximize_glyph_unfocused_lighter" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused_pressed">
+ <include name="maximize_glyph_unfocused" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="close_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_shadow" x="2" y="4" width="width-4" height="height-4" />
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused">
+ <include name="close_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused_pressed">
+ <include name="close_glyph_unfocused" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.5"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel_unfocused">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="title_background">
+
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+ <rectangle color="shade/gtk:bg[SELECTED]/1.3" x="1" y="1" width="width-3" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="width-2" y1="1" x2="width-2" y2="height-2"/>
+
+ <gradient type="vertical" x="2" y="2" width="width-4" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-1"
+ x2="width-2" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.25"
+ x1="1" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="4" y1="1" x2="4" y2="1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="3" y1="2" x2="4" y2="2" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+</draw_ops>
+
+<draw_ops name="title_background_maximized">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+ <rectangle color="shade/gtk:bg[SELECTED]/1.3" x="0" y="1" width="width-2" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="width-1" y1="1" x2="width-1" y2="height-2"/>
+
+ <gradient type="vertical" x="1" y="2" width="width-2" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ </gradient>
+
+ <rectangle color="shade/gtk:bg[SELECTED]/1.3" x="0" y="1" width="width-1" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="height-1"
+ x2="width-1" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.25"
+ x1="0" y1="height-2"
+ x2="width-1" y2="height-2" />
+</draw_ops>
+
+<draw_ops name="title_unfocused_background">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.0"/>
+ <color value="shade/gtk:bg[NORMAL]/1.0"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="1" y1="1" x2="width-2" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="1" y1="1" x2="1" y2="height-1"/>
+
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="width-2" y1="1" x2="width-2" y2="height" />
+
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="4" y1="1" x2="4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="3" y1="2" x2="4" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+</draw_ops>
+
+<draw_ops name="title_unfocused_background_maximized">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="0" y="1" width="width" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.0"/>
+ <color value="shade/gtk:bg[NORMAL]/1.0"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="0" y1="1" x2="width-1" y2="1"/>
+
+</draw_ops>
+
+<draw_ops name="blank">
+<!-- nothing -->
+</draw_ops>
+
+<draw_ops name="focus_outline">
+ <line color="shade/gtk:bg[NORMAL]/1.5"
+ x1="1" y1="title_height" x2="1" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/1.5"
+ x1="width-2" y1="title_height" x2="width-2" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/1.5"
+ x1="2" y1="height-2" x2="width-2" y2="height-2" />
+</draw_ops>
+
+<draw_ops name="focus_background">
+ <include name="outer_bevel"/>
+ <include name="focus_outline"/>
+</draw_ops>
+
+<draw_ops name="unfocus_background">
+ <include name="outer_bevel_unfocused" />
+ <include name="focus_outline" />
+</draw_ops>
+
+<draw_ops name="title_text_focused_with_icon">
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) - 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) - 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) + 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(0) + 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+ <title color="#ffffff"
+ x="(0)"
+ y="(((height - title_height) / 2) `max` 0)"/>
+
+</draw_ops>
+
+<draw_ops name="title_text_with_icon">
+ <title color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5"
+ x="(0)"
+ y="((height - title_height) / 2) `max` 0"/>
+</draw_ops>
+
+<draw_ops name="title_normal">
+ <include name="title_text_with_icon"/>
+</draw_ops>
+
+<draw_ops name="title_focused">
+ <include name="title_text_focused_with_icon"/>
+</draw_ops>
+
+<frame_style name="normal_unfocused" geometry="normal">
+ <piece position="entire_background" draw_ops="unfocus_background"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background"/>
+ <piece position="title" draw_ops="title_normal"/>
+
+ <button function="close" state="normal" draw_ops="close_button_unfocused"/>
+ <button function="close" state="pressed" draw_ops="close_button_unfocused_pressed"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button_unfocused"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_unfocused_pressed"/>
+ <button function="maximize" state="normal" draw_ops="maximize_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_unfocused_pressed"/>
+ <button function="menu" state="normal" draw_ops="menu_button_unfocused"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_unfocused"/>
+</frame_style>
+
+<frame_style name="normal_focused" geometry="normal">
+ <piece position="entire_background" draw_ops="focus_background"/>
+ <piece position="titlebar" draw_ops="title_background"/>
+ <piece position="title" draw_ops="title_focused"/>
+ <button function="close" state="normal" draw_ops="close_button"/>
+ <button function="close" state="pressed" draw_ops="close_button_pressed"/>
+ <button function="close" state="prelight" draw_ops="close_button_focus"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_pressed"/>
+ <button function="minimize" state="prelight" draw_ops="minimize_button_focus" />
+ <button function="maximize" state="normal" draw_ops="maximize_button"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="maximize_button_focus"/>
+ <button function="menu" state="normal" draw_ops="menu_button"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_pressed"/>
+
+</frame_style>
+
+<frame_style name="maximized_unfocused" geometry="normal_small_borders" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="blank"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_unfocused_pressed"/>
+</frame_style>
+
+<frame_style name="maximized_focused" geometry="normal_small_borders" parent="normal_focused">
+ <piece position="entire_background" draw_ops="focus_outline"/>
+ <piece position="titlebar" draw_ops="title_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="restore_button_prelight" />
+</frame_style>
+
+<frame_style name="border" geometry="border" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="outer_bevel"/>
+ <piece position="titlebar" draw_ops="blank"/>
+ <piece position="title" draw_ops="blank"/>
+</frame_style>
+
+<frame_style_set name="normal">
+<frame focus="yes" state="normal" resize="both" style="normal_focused"/>
+<frame focus="no" state="normal" resize="both" style="normal_unfocused"/>
+<frame focus="yes" state="maximized" style="maximized_focused"/>
+<frame focus="no" state="maximized" style="maximized_unfocused"/>
+<frame focus="yes" state="shaded" style="normal_focused"/>
+<frame focus="no" state="shaded" style="normal_unfocused"/>
+<frame focus="yes" state="maximized_and_shaded" style="maximized_focused"/>
+<frame focus="no" state="maximized_and_shaded" style="maximized_unfocused"/>
+</frame_style_set>
+
+<frame_style_set name="border">
+<frame focus="yes" state="normal" resize="both" style="border"/>
+<frame focus="no" state="normal" resize="both" style="border"/>
+<frame focus="yes" state="maximized" style="border"/>
+<frame focus="no" state="maximized" style="border"/>
+<frame focus="yes" state="shaded" style="border"/>
+<frame focus="no" state="shaded" style="border"/>
+<frame focus="yes" state="maximized_and_shaded" style="border"/>
+<frame focus="no" state="maximized_and_shaded" style="border"/>
+</frame_style_set>
+
+<window type="normal" style_set="normal"/>
+<window type="dialog" style_set="normal"/>
+<window type="modal_dialog" style_set="normal"/>
+<window type="menu" style_set="normal"/>
+<window type="utility" style_set="normal"/>
+<window type="border" style_set="normal"/>
+
+<menu_icon function="close" state="normal" draw_ops="close_button"/>
+<menu_icon function="maximize" state="normal" draw_ops="maximize_button"/>
+<menu_icon function="unmaximize" state="normal" draw_ops="restore_button"/>
+<menu_icon function="minimize" state="normal" draw_ops="minimize_button"/>
+
+</metacity_theme>
diff --git a/src/themes/Splint/metacity-theme-1.xml b/src/themes/Splint/metacity-theme-1.xml
new file mode 100644
index 00000000..d10865bb
--- /dev/null
+++ b/src/themes/Splint/metacity-theme-1.xml
@@ -0,0 +1,802 @@
+<?xml version="1.0"?>
+<metacity_theme>
+
+<info>
+ <name>Splint</name>
+ <author>Brandon Wright</author>
+ <copyright>2009</copyright>
+ <date>January 21, 2009</date>
+ <description>A theme designed around the Tango style.</description>
+</info>
+
+<frame_geometry name="normal" rounded_top_left="true" rounded_top_right="true">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="4"/>
+ <distance name="right_titlebar_edge" value="4"/>
+ <aspect_ratio name="button" value="1"/>
+ <distance name="title_vertical_pad" value="2"/>
+ <border name="title_border" left="3" right="4" top="2" bottom="2"/>
+ <border name="button_border" left="1" right="1" top="3" bottom="3"/>
+</frame_geometry>
+
+<frame_geometry name="normal_small_borders" parent="normal" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_titlebar_edge" value="1" />
+ <distance name="right_titlebar_edge" value="1" />
+ <distance name="left_width" value="0" />
+ <distance name="right_width" value="0" />
+ <distance name="bottom_height" value="0" />
+</frame_geometry>
+
+<frame_geometry name="utility" parent="normal">
+</frame_geometry>
+
+<frame_geometry name="border" has_title="false" rounded_top_left="false" rounded_top_right="false">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="0"/>
+ <distance name="right_titlebar_edge" value="0"/>
+ <distance name="button_width" value="0"/>
+ <distance name="button_height" value="0"/>
+ <distance name="title_vertical_pad" value="4"/>
+ <border name="title_border" left="0" right="0" top="0" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="0" bottom="0"/>
+</frame_geometry>
+
+<!-- Buttons -->
+<draw_ops name="button_bg">
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="1" y1="2"
+ x2="1" y2="height-3" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" />
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+</draw_ops>
+
+<draw_ops name="button_bg_prelight">
+
+ <!-- Do a fill -->
+ <rectangle color="shade/gtk:bg[SELECTED]/1.0" x="1" y="1" width="width-2" height = "height-2" filled="true" />
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="1" y1="2"
+ x2="1" y2="height-3" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" />
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+
+</draw_ops>
+
+<draw_ops name="button_pressed_bg">
+
+ <!-- Do a fill -->
+<!-- <rectangle color="shade/gtk:bg[SELECTED]/0.8" x="1" y="1" width="width-2" height = "height-2" filled="true" /> -->
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-2">
+ <color value="shade/gtk:bg[SELECTED]/0.9" />
+ <color value="shade/gtk:bg[SELECTED]/1.2" />
+ </gradient>
+
+ <!-- Dark lines -->
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="0"
+ x2="width-3" y2="0" />
+
+ <!-- Left line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="2"
+ x2="0" y2="height-3" />
+
+ <!-- Right line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-1" y1="2"
+ x2="width-1" y2="height-3" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="2" y1="height-1"
+ x2="width-3" y2="height-1" />
+
+ <!-- TL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="1"
+ x2="1" y2="1" />
+
+ <!-- TR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="1"
+ x2="width-2" y2="1" />
+
+ <!-- LL Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-2"
+ x2="1" y2="height-2" />
+
+ <!-- LR Corner -->
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="width-2" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <!-- Lighter Inside -->
+
+ <!-- Top line -->
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="1"
+ x2="width-3" y2="1" />
+
+ <!-- Bottom line -->
+ <line color="shade/gtk:bg[SELECTED]/1.2"
+ x1="2" y1="height-2"
+ x2="width-3" y2="height-2" />
+
+ <!-- Left line -->
+ <!-- <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="1" y1="2"
+ x2="1" y2="height-3" /> -->
+
+ <gradient type="vertical" x="1" y="2" width="1" height="height-4">
+ <color value="shade/gtk:bg[SELECTED]/1.1" />
+ <color value="shade/gtk:bg[SELECTED]/1.2" />
+ </gradient>
+
+ <!-- Left line -->
+ <!-- <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-2" y1="2"
+ x2="width-2" y2="height-3" /> -->
+
+
+ <gradient type="vertical" x="width-2" y="2" width="1" height="height-4">
+ <color value="shade/gtk:bg[SELECTED]/1.1" />
+ <color value="shade/gtk:bg[SELECTED]/1.2" />
+ </gradient>
+
+ <!-- Corners -->
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="2" y1="2"
+ x2="2" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.0"
+ x1="width-3" y1="2"
+ x2="width-3" y2="2" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="2" y1="height-3"
+ x2="2" y2="height-3" />
+ <line color="shade/gtk:bg[SELECTED]/1.1"
+ x1="width-3" y1="height-3"
+ x2="width-3" y2="height-3" />
+
+</draw_ops>
+
+<draw_ops name="menu_glyph">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" />
+</draw_ops>
+
+<draw_ops name="menu_glyph_unfocused">
+ <icon x="(width-mini_icon_width)/2"
+ y="(height-mini_icon_height)/2"
+ width="mini_icon_width" height="mini_icon_height" alpha="0.5"/>
+</draw_ops>
+
+<draw_ops name="close_glyph">
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/white/gtk:bg[SELECTED]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="white" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="white" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_shadow">
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_shadow_dark">
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="close_glyph_unfocused_lighter">
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="2" x2="width-3" y2="height-4"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="3" x2="width-4" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="2" y1="height-4" x2="width-4" y2="2"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.75" x1="3" y1="height-3" x2="width-3" y2="3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="2"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph">
+ <rectangle color="white" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="white" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="white" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="white" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+<draw_ops name="maximize_glyph_shadow">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.85" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.85" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_shadow_dark">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.8" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="shade/gtk:bg[SELECTED]/0.8" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+
+<draw_ops name="maximize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="gtk:fg[NORMAL]" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+</draw_ops>
+<draw_ops name="maximize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="2" width="width-4" height="height/4" filled="true"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="2" x2="2" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="2" y1="height-3" x2="width-3" y2="height-3"/>
+ <line color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x1="width-3" y1="2" x2="width-3" y2="height-3"/>
+
+
+</draw_ops>
+
+<draw_ops name="minimize_glyph">
+ <rectangle color="white" x="2" y="height * 2 / 3" width="width-4" height="height - height * 2 / 3 - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_shadow">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.85" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_shadow_dark">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.8" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused">
+ <rectangle color="gtk:fg[NORMAL]" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="minimize_glyph_unfocused_lighter">
+ <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5" x="2" y="height * 2 / 3" width="width-4" height="height - (height * 2 / 3) - 2" filled="true" />
+</draw_ops>
+
+<draw_ops name="menu_button">
+ <include name="menu_glyph" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="menu_button_unfocused">
+ <include name="menu_glyph_unfocused" x="0" y="0" />
+</draw_ops>
+
+<draw_ops name="menu_button_pressed">
+ <include name="menu_glyph_unfocused" x="0" y="0"/>
+</draw_ops>
+
+<draw_ops name="minimize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_shadow" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="minimize_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused">
+ <include name="minimize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4" />
+</draw_ops>
+
+<draw_ops name="minimize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="minimize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="minimize_button_unfocused_pressed">
+ <include name="minimize_glyph_unfocused" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+
+<draw_ops name="maximize_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused">
+ <include name="maximize_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_unfocused_pressed">
+ <include name="maximize_glyph_unfocused" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="maximize_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="mini_window_icon">
+ <rectangle color="gtk:bg[NORMAL]" filled="true"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <rectangle color="gtk:fg[NORMAL]" filled="false"
+ x="0" y="0" width="width-1" height="height-1"/>
+ <line color="gtk:fg[NORMAL]" width="2"
+ x1="0" y1="1" x2="width" y2="1"/>
+</draw_ops>
+
+<draw_ops name="restore_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow" x="3" y="5" width="width-6" height="height-6" />
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_prelight">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="maximize_glyph_shadow_dark" x="3" y="5" width="width-6" height="height-6"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused">
+ <include name="maximize_glyph_unfocused_lighter" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="maximize_glyph" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="restore_button_unfocused_pressed">
+ <include name="maximize_glyph_unfocused" x="3" y="3" width="width-6" height="height-6"/>
+</draw_ops>
+
+<draw_ops name="close_button">
+ <include name="button_bg" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_shadow" x="2" y="4" width="width-4" height="height-4" />
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_focus">
+ <include name="button_bg_prelight" x="0" y="0" width="width" height="height" />
+ <include name="close_glyph_shadow_dark" x="2" y="4" width="width-4" height="height-4"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused">
+ <include name="close_glyph_unfocused_lighter" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_unfocused_pressed">
+ <include name="close_glyph_unfocused" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="close_button_pressed">
+ <include name="button_pressed_bg"/>
+ <include name="close_glyph" x="2" y="2" width="width-4" height="height-4"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.5"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="outer_bevel_unfocused">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7"
+ x="0" y="0" width="width-1" height="height-1"/>
+</draw_ops>
+
+<draw_ops name="title_background">
+
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+ <rectangle color="shade/gtk:bg[SELECTED]/1.3" x="1" y="1" width="width-3" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="width-2" y1="1" x2="width-2" y2="height-2"/>
+
+ <gradient type="vertical" x="2" y="2" width="width-4" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="1" y1="height-1"
+ x2="width-2" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.25"
+ x1="1" y1="height-2"
+ x2="width-2" y2="height-2" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="4" y1="1" x2="4" y2="1" />
+
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[SELECTED]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="3" y1="2" x2="4" y2="2" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+</draw_ops>
+
+<draw_ops name="title_background_maximized">
+ <rectangle color="shade/gtk:bg[SELECTED]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+ <rectangle color="shade/gtk:bg[SELECTED]/1.3" x="0" y="1" width="width-2" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/1.3" x1="width-1" y1="1" x2="width-1" y2="height-2"/>
+
+ <gradient type="vertical" x="1" y="2" width="width-2" height="height-3">
+ <color value="shade/gtk:bg[SELECTED]/0.9"/>
+ <color value="shade/gtk:bg[SELECTED]/1.1"/>
+ </gradient>
+
+ <rectangle color="shade/gtk:bg[SELECTED]/1.3" x="0" y="1" width="width-1" height="height-3"/>
+
+ <line color="shade/gtk:bg[SELECTED]/0.8"
+ x1="0" y1="height-1"
+ x2="width-1" y2="height-1" />
+
+ <line color="shade/gtk:bg[SELECTED]/1.25"
+ x1="0" y1="height-2"
+ x2="width-1" y2="height-2" />
+</draw_ops>
+
+<draw_ops name="title_unfocused_background">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="1" y="1" width="width-2" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.0"/>
+ <color value="shade/gtk:bg[NORMAL]/1.0"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="1" y1="1" x2="width-2" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="1" y1="1" x2="1" y2="height-1"/>
+
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="width-2" y1="1" x2="width-2" y2="height" />
+
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="3" x2="3" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="1" y1="4" x2="1" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="4" y1="1" x2="4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-4" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-5" y1="1" x2="width-5" y2="1" />
+ <line color="shade/gtk:bg[NORMAL]/0.7" x1="width-2" y1="3" x2="width-2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="2" y1="3" x2="2" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="3" y1="2" x2="4" y2="2" />
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="width-3" y1="3" x2="width-3" y2="4" />
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="width-5" y1="2" x2="width-4" y2="2" />
+
+</draw_ops>
+
+<draw_ops name="title_unfocused_background_maximized">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.7" x="0" y="0" width="width" height="height" filled="true"/>
+
+ <gradient type="vertical" x="0" y="1" width="width" height="height-1">
+ <color value="shade/gtk:bg[NORMAL]/1.0"/>
+ <color value="shade/gtk:bg[NORMAL]/1.0"/>
+ </gradient>
+
+ <line color="shade/gtk:bg[NORMAL]/1.5" x1="0" y1="1" x2="width-1" y2="1"/>
+
+</draw_ops>
+
+<draw_ops name="blank">
+<!-- nothing -->
+</draw_ops>
+
+<draw_ops name="focus_outline">
+ <line color="shade/gtk:bg[NORMAL]/1.5"
+ x1="1" y1="title_height" x2="1" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/1.5"
+ x1="width-2" y1="title_height" x2="width-2" y2="height-2" />
+ <line color="shade/gtk:bg[NORMAL]/1.5"
+ x1="2" y1="height-2" x2="width-2" y2="height-2" />
+</draw_ops>
+
+<draw_ops name="focus_background">
+ <include name="outer_bevel"/>
+ <include name="focus_outline"/>
+</draw_ops>
+
+<draw_ops name="unfocus_background">
+ <include name="outer_bevel_unfocused" />
+ <include name="focus_outline" />
+</draw_ops>
+
+<draw_ops name="title_text_focused_with_icon">
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) - 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) - 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) + 1"
+ y="(((height - title_height) / 2) `max` 0) + 1"/>
+
+ <title color="shade/gtk:bg[SELECTED]/0.8"
+ x="(((width - title_width) / 2) `max` 0) + 1"
+ y="(((height - title_height) / 2) `max` 0) - 1"/>
+
+ <title color="#ffffff"
+ x="(((width - title_width) / 2) `max` 0)"
+ y="(((height - title_height) / 2) `max` 0)"/>
+
+</draw_ops>
+
+<draw_ops name="title_text_with_icon">
+ <title color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.5"
+ x="(((width - title_width) / 2) `max` 0)"
+ y="((height - title_height) / 2) `max` 0"/>
+</draw_ops>
+
+<draw_ops name="title_normal">
+ <include name="title_text_with_icon"/>
+</draw_ops>
+
+<draw_ops name="title_focused">
+ <include name="title_text_focused_with_icon"/>
+</draw_ops>
+
+<frame_style name="normal_unfocused" geometry="normal">
+ <piece position="entire_background" draw_ops="unfocus_background"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background"/>
+ <piece position="title" draw_ops="title_normal"/>
+
+ <button function="close" state="normal" draw_ops="close_button_unfocused"/>
+ <button function="close" state="pressed" draw_ops="close_button_unfocused_pressed"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button_unfocused"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_unfocused_pressed"/>
+ <button function="maximize" state="normal" draw_ops="maximize_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_unfocused_pressed"/>
+ <button function="menu" state="normal" draw_ops="menu_button_unfocused"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_unfocused"/>
+</frame_style>
+
+<frame_style name="normal_focused" geometry="normal">
+ <piece position="entire_background" draw_ops="focus_background"/>
+ <piece position="titlebar" draw_ops="title_background"/>
+ <piece position="title" draw_ops="title_focused"/>
+ <button function="close" state="normal" draw_ops="close_button"/>
+ <button function="close" state="pressed" draw_ops="close_button_pressed"/>
+ <button function="close" state="prelight" draw_ops="close_button_focus"/>
+ <button function="minimize" state="normal" draw_ops="minimize_button"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_button_pressed"/>
+ <button function="minimize" state="prelight" draw_ops="minimize_button_focus" />
+ <button function="maximize" state="normal" draw_ops="maximize_button"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="maximize_button_focus"/>
+ <button function="menu" state="normal" draw_ops="menu_button"/>
+ <button function="menu" state="pressed" draw_ops="menu_button_pressed"/>
+
+</frame_style>
+
+<frame_style name="maximized_unfocused" geometry="normal_small_borders" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="blank"/>
+ <piece position="titlebar" draw_ops="title_unfocused_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button_unfocused"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_unfocused_pressed"/>
+</frame_style>
+
+<frame_style name="maximized_focused" geometry="normal_small_borders" parent="normal_focused">
+ <piece position="entire_background" draw_ops="focus_outline"/>
+ <piece position="titlebar" draw_ops="title_background_maximized"/>
+ <button function="maximize" state="normal" draw_ops="restore_button"/>
+ <button function="maximize" state="pressed" draw_ops="restore_button_pressed"/>
+ <button function="maximize" state="prelight" draw_ops="restore_button_prelight" />
+</frame_style>
+
+<frame_style name="border" geometry="border" parent="normal_unfocused">
+ <piece position="entire_background" draw_ops="outer_bevel"/>
+ <piece position="titlebar" draw_ops="blank"/>
+ <piece position="title" draw_ops="blank"/>
+</frame_style>
+
+<frame_style_set name="normal">
+<frame focus="yes" state="normal" resize="both" style="normal_focused"/>
+<frame focus="no" state="normal" resize="both" style="normal_unfocused"/>
+<frame focus="yes" state="maximized" style="maximized_focused"/>
+<frame focus="no" state="maximized" style="maximized_unfocused"/>
+<frame focus="yes" state="shaded" style="normal_focused"/>
+<frame focus="no" state="shaded" style="normal_unfocused"/>
+<frame focus="yes" state="maximized_and_shaded" style="maximized_focused"/>
+<frame focus="no" state="maximized_and_shaded" style="maximized_unfocused"/>
+</frame_style_set>
+
+<frame_style_set name="border">
+<frame focus="yes" state="normal" resize="both" style="border"/>
+<frame focus="no" state="normal" resize="both" style="border"/>
+<frame focus="yes" state="maximized" style="border"/>
+<frame focus="no" state="maximized" style="border"/>
+<frame focus="yes" state="shaded" style="border"/>
+<frame focus="no" state="shaded" style="border"/>
+<frame focus="yes" state="maximized_and_shaded" style="border"/>
+<frame focus="no" state="maximized_and_shaded" style="border"/>
+</frame_style_set>
+
+<window type="normal" style_set="normal"/>
+<window type="dialog" style_set="normal"/>
+<window type="modal_dialog" style_set="normal"/>
+<window type="menu" style_set="normal"/>
+<window type="utility" style_set="normal"/>
+<window type="border" style_set="normal"/>
+
+<menu_icon function="close" state="normal" draw_ops="close_button"/>
+<menu_icon function="maximize" state="normal" draw_ops="maximize_button"/>
+<menu_icon function="unmaximize" state="normal" draw_ops="restore_button"/>
+<menu_icon function="minimize" state="normal" draw_ops="minimize_button"/>
+
+</metacity_theme>
diff --git a/src/themes/WinMe/close_normal.png b/src/themes/WinMe/close_normal.png
new file mode 100644
index 00000000..644378f9
--- /dev/null
+++ b/src/themes/WinMe/close_normal.png
Binary files differ
diff --git a/src/themes/WinMe/close_normal_small.png b/src/themes/WinMe/close_normal_small.png
new file mode 100644
index 00000000..bd7495c8
--- /dev/null
+++ b/src/themes/WinMe/close_normal_small.png
Binary files differ
diff --git a/src/themes/WinMe/close_pressed.png b/src/themes/WinMe/close_pressed.png
new file mode 100644
index 00000000..42970c2d
--- /dev/null
+++ b/src/themes/WinMe/close_pressed.png
Binary files differ
diff --git a/src/themes/WinMe/close_pressed_small.png b/src/themes/WinMe/close_pressed_small.png
new file mode 100644
index 00000000..c7c98a1e
--- /dev/null
+++ b/src/themes/WinMe/close_pressed_small.png
Binary files differ
diff --git a/src/themes/WinMe/maximize_normal.png b/src/themes/WinMe/maximize_normal.png
new file mode 100644
index 00000000..41b7694a
--- /dev/null
+++ b/src/themes/WinMe/maximize_normal.png
Binary files differ
diff --git a/src/themes/WinMe/maximize_pressed.png b/src/themes/WinMe/maximize_pressed.png
new file mode 100644
index 00000000..68137306
--- /dev/null
+++ b/src/themes/WinMe/maximize_pressed.png
Binary files differ
diff --git a/src/themes/WinMe/metacity-theme-1.xml b/src/themes/WinMe/metacity-theme-1.xml
new file mode 100644
index 00000000..29d3aef0
--- /dev/null
+++ b/src/themes/WinMe/metacity-theme-1.xml
@@ -0,0 +1,375 @@
+<?xml version="1.0"?>
+<!--
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+// MA 02110-1301, USA.
+
+Notes
+ #d4d0c8 was replaced with gtk:bg[NORMAL]
+
+-->
+<metacity_theme>
+ <info>
+ <name>WinMe</name>
+ <author>Perberos</author>
+ <copyright>&#194; 2007 Srivatsa Kanchi &lt;[email protected]&gt;, 2010 Perberos</copyright>
+ <date>April 22, 2010</date>
+ <description>Windows NT serie style theme</description>
+ </info>
+
+ <frame_geometry name="normal_geometry" has_title="true" rounded_top_left="false" rounded_top_right="false" title_scale="medium" rounded_bottom_left="false" rounded_bottom_right="false">
+ <distance name="left_width" value="4"/>
+ <distance name="right_width" value="4"/>
+ <distance name="bottom_height" value="4"/>
+ <distance name="left_titlebar_edge" value="5"/>
+ <distance name="right_titlebar_edge" value="6"/>
+ <distance name="title_vertical_pad" value="5"/>
+ <distance name="button_width" value="17"/>
+ <distance name="button_height" value="16"/>
+ <border name="title_border" left="0" right="0" top="4" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="3" bottom="0"/>
+ </frame_geometry>
+
+ <frame_geometry name="borderless_geometry" has_title="true" rounded_top_left="false" rounded_top_right="false" title_scale="medium" rounded_bottom_left="false" rounded_bottom_right="false">
+ <distance name="left_width" value="0"/>
+ <distance name="right_width" value="0"/>
+ <distance name="bottom_height" value="0"/>
+ <distance name="left_titlebar_edge" value="1"/>
+ <distance name="right_titlebar_edge" value="2"/>
+ <distance name="title_vertical_pad" value="2"/>
+ <distance name="button_width" value="17"/>
+ <distance name="button_height" value="16"/>
+ <border name="title_border" left="0" right="0" top="0" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="1" bottom="1"/>
+ </frame_geometry>
+
+ <frame_geometry name="utility_geometry" has_title="true" rounded_top_left="false" rounded_top_right="false" title_scale="small" rounded_bottom_left="false" rounded_bottom_right="false">
+ <distance name="left_width" value="3"/>
+ <distance name="right_width" value="3"/>
+ <distance name="bottom_height" value="3"/>
+ <distance name="left_titlebar_edge" value="4"/>
+ <distance name="right_titlebar_edge" value="4"/>
+ <distance name="title_vertical_pad" value="4"/>
+ <distance name="button_width" value="10"/>
+ <distance name="button_height" value="11"/>
+ <border name="title_border" left="0" right="0" top="4" bottom="2"/>
+ <border name="button_border" left="0" right="0" top="2" bottom="0"/>
+ </frame_geometry>
+
+ <frame_geometry name="border_geometry" has_title="false">
+ <distance name="left_width" value="0" />
+ <distance name="right_width" value="0" />
+ <distance name="bottom_height" value="0" />
+ <distance name="left_titlebar_edge" value="0" />
+ <distance name="right_titlebar_edge" value="0" />
+ <distance name="button_width" value="0"/>
+ <distance name="button_height" value="0"/>
+ <distance name="title_vertical_pad" value="0" />
+ <border name="title_border" left="0" right="0" top="0" bottom="0" />
+ <border name="button_border" left="0" right="0" top="0" bottom="0" />
+ </frame_geometry>
+
+ <frame_style name="normal_focused_style" geometry="normal_geometry">
+ <piece position="left_edge">
+ <draw_ops>
+ <rectangle color="gtk:bg[NORMAL]" x="0" y="0" width="width" height="height" filled="true"/>
+ <line color="#ffffff" x1="1" y1="0" x2="1" y2="height" width="1"/>
+ </draw_ops>
+ </piece>
+ <piece position="right_edge">
+ <draw_ops>
+ <rectangle color="gtk:bg[NORMAL]" x="0" y="0" width="width" height="height" filled="true"/>
+ <line color="#808080" x1="width-2" y1="0" x2="width-2" y2="height" width="1"/>
+ <line color="#404040" x1="width-1" y1="0" x2="width-1" y2="height" width="1"/>
+ </draw_ops>
+ </piece>
+ <piece position="bottom_edge">
+ <draw_ops>
+ <rectangle color="gtk:bg[NORMAL]" x="0" y="0" width="width" height="height" filled="true"/>
+ <line color="#808080" x1="1" y1="height-2" x2="width" y2="height-2" width="1"/>
+ <line color="#404040" x1="0" y1="height-1" x2="width" y2="height-1" width="1"/>
+ <line color="#ffffff" x1="1" y1="0" x2="1" y2="height-2" width="1"/>
+ <line color="#404040" x1="width-1" y1="0" x2="width-1" y2="height" width="1"/>
+ <line color="#808080" x1="width-2" y1="0" x2="width-2" y2="height-2" width="1"/>
+ </draw_ops>
+ </piece>
+ <piece position="titlebar">
+ <draw_ops>
+ <rectangle color="gtk:bg[NORMAL]" x="0" y="0" width="width" height="height" filled="true"/>
+ <line color="#ffffff" x1="1" y1="1" x2="1" y2="height" width="1"/>
+ <line color="#ffffff" x1="1" y1="1" x2="width-2" y2="1" width="1"/>
+ <line color="#404040" x1="width-1" y1="0" x2="width-1" y2="height" width="1"/>
+ <line color="#808080" x1="width-2" y1="1" x2="width-2" y2="height" width="1"/>
+ <gradient type="horizontal" x="4" y="4" width="width-8" height="height-5">
+ <color value="#0a246a"/> <!-- #0a246a (10, 36, 106)-->
+ <color value="#526bad"/> <!-- #526bad (82, 107, 173) is a cute color -->
+ <color value="#94b5dc"/> <!-- #94b5dc (148, 181, 220)-->
+ <color value="#a6caf0"/> <!-- #a6caf0 (166, 202, 240) -->
+ </gradient>
+ </draw_ops>
+ </piece>
+
+ <button function="close" state="normal">
+ <draw_ops>
+ <image filename="close_normal.png" x="0" y="0" width="object_width" height="height"/>
+ </draw_ops>
+ </button>
+ <button function="close" state="pressed">
+ <draw_ops>
+ <image filename="close_pressed.png" x="0" y="0" width="width" height="height"/>
+ </draw_ops>
+ </button>
+ <button function="maximize" state="normal">
+ <draw_ops>
+ <image filename="maximize_normal.png" x="0" y="0" width="width" height="height"/>
+ </draw_ops>
+ </button>
+ <button function="maximize" state="pressed">
+ <draw_ops>
+ <image filename="maximize_pressed.png" x="0" y="0" width="width" height="height"/>
+ </draw_ops>
+ </button>
+ <button function="minimize" state="normal">
+ <draw_ops>
+ <image filename="minimize_normal.png" x="0" y="0" width="width" height="height"/>
+ </draw_ops>
+ </button>
+ <button function="minimize" state="pressed">
+ <draw_ops>
+ <image filename="minimize_pressed.png" x="0" y="0" width="width" height="height"/>
+ </draw_ops>
+ </button>
+ <button function="menu" state="normal">
+ <draw_ops>
+ <icon x="1" y="0" width="16" height="16"/>
+ </draw_ops>
+ </button>
+ <button function="menu" state="pressed">
+ <draw_ops>
+ <icon x="1" y="0" width="16" height="16"/>
+ </draw_ops>
+ </button>
+ <piece position="title">
+ <draw_ops>
+ <title x="3" y="2" color="#ffffff"/>
+ </draw_ops>
+ </piece>
+ </frame_style>
+
+ <frame_style name="normal(maximized)_focused_style" geometry="borderless_geometry" parent="normal_focused_style">
+ <piece position="titlebar">
+ <draw_ops>
+ <gradient type="horizontal" x="0" y="0" width="width-0"
+ height="height-0">
+ <color value="#0a246a"/>
+ <color value="#526bad"/>
+ <color value="#94b5dc"/>
+ <color value="#a6caf0"/>
+ </gradient>
+ </draw_ops>
+ </piece>
+ <button function="maximize" state="normal">
+
+ <draw_ops>
+ <image filename="restore_normal.png" x="0" y="0" width="width" height="height"/>
+ </draw_ops>
+ </button>
+ <button function="maximize" state="pressed">
+ <draw_ops>
+ <image filename="restore_pressed.png" x="0" y="0" width="width" height="height"/>
+ </draw_ops>
+ </button>
+ </frame_style>
+
+ <frame_style name="normal_unfocused_style" geometry="normal_geometry" parent="normal_focused_style">
+ <piece position="titlebar">
+ <draw_ops>
+ <rectangle color="gtk:bg[NORMAL]" x="0" y="0" width="width" height="height" filled="true"/>
+ <line color="#ffffff" x1="1" y1="1" x2="1" y2="height" width="1"/>
+ <line color="#ffffff" x1="1" y1="1" x2="width-2" y2="1" width="1"/>
+ <line color="#404040" x1="width-1" y1="0" x2="width-1" y2="height" width="1"/>
+ <line color="#808080" x1="width-2" y1="1" x2="width-2" y2="height" width="1"/>
+ <gradient type="horizontal" x="4" y="4" width="width-8" height="height-5">
+ <color value="#808080"/>
+ <color value="#8c8c8c"/>
+ <color value="#a5a5a5"/>
+ <color value="#c6bdc6"/>
+ <color value="#c0c0c0"/>
+ </gradient>
+ </draw_ops>
+ </piece>
+ <piece position="title">
+ <draw_ops>
+ <title x="3" y="2" color="gtk:bg[NORMAL]"/>
+ </draw_ops>
+ </piece>
+ </frame_style>
+
+ <frame_style name="normal(maximized)_unfocused_style" geometry="borderless_geometry" parent="normal_unfocused_style">
+ <piece position="titlebar">
+ <draw_ops>
+ <gradient type="horizontal" x="0" y="0" width="width-0" height="height-0">
+ <color value="#808080"/>
+ <color value="#8c8c8c"/>
+ <color value="#a5a5a5"/>
+ <color value="#c6bdc6"/>
+ <color value="#c0c0c0"/>
+ </gradient>
+ </draw_ops>
+ </piece>
+ <button function="maximize" state="normal">
+ <draw_ops>
+ <image filename="restore_normal.png" x="0" y="0" width="width" height="height"/>
+ </draw_ops>
+ </button>
+ </frame_style>
+
+ <frame_style name="utility_focused_style" geometry="utility_geometry">
+ <piece position="entire_background">
+ <draw_ops>
+ <rectangle color="gtk:bg[NORMAL]" x="0" y="0" width="width" height="height" filled="true"/>
+ <rectangle color="#ffffff" x="1" y="1" width="width-3" height="height-3" filled="false"/>
+ <line color="#808080" x1="width-2" y1="1" x2="width-2" y2="height-3"/>
+ <line color="#808080" x1="1" y1="height-2" x2="width-2" y2="height-2"/>
+ <line color="#404040" x1="width-1" y1="0" x2="width-1" y2="height-1"/>
+ <line color="#404040" x1="0" y1="height-1" x2="width-1" y2="height-1"/>
+ </draw_ops>
+ </piece>
+
+ <piece position="titlebar">
+ <draw_ops>
+ <gradient type="horizontal" x="3" y="3" width="width-6" height="height-4">
+ <color value="#0a246a"/>
+ <color value="#526bad"/>
+ <color value="#94b5dc"/>
+ <color value="#a6caf0"/>
+ </gradient>
+ </draw_ops>
+ </piece>
+ <button function="menu" state="normal"><draw_ops/></button>
+ <button function="menu" state="pressed"><draw_ops/></button>
+
+ <piece position="title">
+ <draw_ops>
+ <title x="3" y="2" color="#ffffff"/>
+ </draw_ops>
+ </piece>
+
+ <button function="close" state="normal">
+ <draw_ops>
+ <image filename="close_normal_small.png" x="0" y="0" width="width" height="height"/>
+ </draw_ops>
+ </button>
+ <button function="close" state="pressed">
+ <draw_ops>
+ <image filename="close_pressed_small.png" x="0" y="0" width="width" height="height"/>
+ </draw_ops>
+ </button>
+ <button function="maximize" state="normal"><draw_ops/></button>
+ <button function="maximize" state="pressed"><draw_ops/></button>
+ <button function="minimize" state="normal"><draw_ops/></button>
+ <button function="minimize" state="pressed"><draw_ops/></button>
+ </frame_style>
+
+ <frame_style name="utility_unfocused_style" geometry="utility_geometry" parent="utility_focused_style">
+ <piece position="titlebar">
+ <draw_ops>
+ <gradient type="horizontal" x="3" y="3" width="width-6" height="height-4">
+ <color value="#808080"/>
+ <color value="#8c8c8c"/>
+ <color value="#a5a5a5"/>
+ <color value="#c6bdc6"/>
+ <color value="#c0c0c0"/>
+ </gradient>
+ </draw_ops>
+ </piece>
+ <piece position="title">
+ <draw_ops>
+ <title x="3" y="2" color="gtk:bg[NORMAL]"/>
+ </draw_ops>
+ </piece>
+ </frame_style>
+
+
+ <frame_style name="border_style" geometry="border_geometry">
+ <button function="close" state="normal"><draw_ops/></button>
+ <button function="close" state="pressed"><draw_ops/></button>
+ <button function="maximize" state="normal"><draw_ops/></button>
+ <button function="maximize" state="pressed"><draw_ops/></button>
+ <button function="minimize" state="normal"><draw_ops/></button>
+ <button function="minimize" state="pressed"><draw_ops/></button>
+ <button function="menu" state="normal"><draw_ops/></button>
+ <button function="menu" state="pressed"><draw_ops/></button>
+ </frame_style>
+
+ <!-- system menu icon : default drawn by metacity -->
+ <menu_icon function="close" state="normal">
+ <draw_ops/>
+ </menu_icon>
+
+ <menu_icon function="maximize" state="normal">
+ <draw_ops/>
+ </menu_icon>
+
+ <menu_icon function="minimize" state="normal">
+ <draw_ops/>
+ </menu_icon>
+
+ <menu_icon function="unmaximize" state="normal">
+ <draw_ops/>
+ </menu_icon>
+
+ <frame_style_set name="normal_set">
+ <frame focus="yes" state="normal" resize="both" style="normal_focused_style"/>
+ <frame focus="no" state="normal" resize="both" style="normal_unfocused_style"/>
+ <frame focus="yes" state="normal" resize="none" style="normal_focused_style"/>
+ <frame focus="no" state="normal" resize="none" style="normal_unfocused_style"/>
+ <frame focus="yes" state="maximized" style="normal(maximized)_focused_style"/>
+ <frame focus="no" state="maximized" style="normal(maximized)_unfocused_style"/>
+ <frame focus="yes" state="shaded" style="normal_focused_style"/>
+ <frame focus="no" state="shaded" style="normal_unfocused_style"/>
+ <frame focus="yes" state="maximized_and_shaded" style="normal(maximized)_focused_style"/>
+ <frame focus="no" state="maximized_and_shaded" style="normal_unfocused_style"/>
+ </frame_style_set>
+
+
+ <frame_style_set name="border_set">
+ <frame focus="yes" state="normal" resize="both" style="border_style" />
+ <frame focus="no" state="normal" resize="both" style="border_style" />
+ <frame focus="yes" state="normal" resize="none" style="border_style" />
+ <frame focus="no" state="normal" resize="none" style="border_style" />
+ <frame focus="yes" state="maximized" style="border_style" />
+ <frame focus="no" state="maximized" style="border_style" />
+ <frame focus="yes" state="shaded" style="border_style" />
+ <frame focus="no" state="shaded" style="border_style" />
+ <frame focus="yes" state="maximized_and_shaded" style="border_style" />
+ <frame focus="no" state="maximized_and_shaded" style="border_style" />
+ </frame_style_set>
+
+ <frame_style_set name="utility_set" parent="border_set">
+ <frame focus="yes" state="normal" resize="both" style="utility_focused_style" />
+ <frame focus="no" state="normal" resize="both" style="utility_unfocused_style" />
+ <frame focus="yes" state="normal" resize="none" style="utility_focused_style" />
+ <frame focus="no" state="normal" resize="none" style="utility_unfocused_style" />
+ </frame_style_set>
+
+
+ <window type="normal" style_set="normal_set"/>
+ <window type="dialog" style_set="normal_set"/>
+ <window type="modal_dialog" style_set="normal_set"/>
+ <window type="menu" style_set="utility_set"/>
+ <window type="utility" style_set="utility_set"/>
+ <window type="border" style_set="border_set"/>
+
+</metacity_theme>
diff --git a/src/themes/WinMe/minimize_normal.png b/src/themes/WinMe/minimize_normal.png
new file mode 100644
index 00000000..cfa36387
--- /dev/null
+++ b/src/themes/WinMe/minimize_normal.png
Binary files differ
diff --git a/src/themes/WinMe/minimize_pressed.png b/src/themes/WinMe/minimize_pressed.png
new file mode 100644
index 00000000..4b873d15
--- /dev/null
+++ b/src/themes/WinMe/minimize_pressed.png
Binary files differ
diff --git a/src/themes/WinMe/restore_normal.png b/src/themes/WinMe/restore_normal.png
new file mode 100644
index 00000000..95ab85ec
--- /dev/null
+++ b/src/themes/WinMe/restore_normal.png
Binary files differ
diff --git a/src/themes/WinMe/restore_pressed.png b/src/themes/WinMe/restore_pressed.png
new file mode 100644
index 00000000..2b117058
--- /dev/null
+++ b/src/themes/WinMe/restore_pressed.png
Binary files differ
diff --git a/src/themes/eOS/close.png b/src/themes/eOS/close.png
new file mode 100644
index 00000000..6ba9495e
--- /dev/null
+++ b/src/themes/eOS/close.png
Binary files differ
diff --git a/src/themes/eOS/close_unfocused.png b/src/themes/eOS/close_unfocused.png
new file mode 100644
index 00000000..bdff535c
--- /dev/null
+++ b/src/themes/eOS/close_unfocused.png
Binary files differ
diff --git a/src/themes/eOS/close_unfocused_over.png b/src/themes/eOS/close_unfocused_over.png
new file mode 100644
index 00000000..6ba9495e
--- /dev/null
+++ b/src/themes/eOS/close_unfocused_over.png
Binary files differ
diff --git a/src/themes/eOS/maximize.png b/src/themes/eOS/maximize.png
new file mode 100644
index 00000000..785aa133
--- /dev/null
+++ b/src/themes/eOS/maximize.png
Binary files differ
diff --git a/src/themes/eOS/maximize_unfocused.png b/src/themes/eOS/maximize_unfocused.png
new file mode 100644
index 00000000..4c76ed67
--- /dev/null
+++ b/src/themes/eOS/maximize_unfocused.png
Binary files differ
diff --git a/src/themes/eOS/maximize_unfocused_over.png b/src/themes/eOS/maximize_unfocused_over.png
new file mode 100644
index 00000000..785aa133
--- /dev/null
+++ b/src/themes/eOS/maximize_unfocused_over.png
Binary files differ
diff --git a/src/themes/eOS/menu.png b/src/themes/eOS/menu.png
new file mode 100644
index 00000000..1e02701c
--- /dev/null
+++ b/src/themes/eOS/menu.png
Binary files differ
diff --git a/src/themes/eOS/menu_prelight.png b/src/themes/eOS/menu_prelight.png
new file mode 100644
index 00000000..c7dbd0a2
--- /dev/null
+++ b/src/themes/eOS/menu_prelight.png
Binary files differ
diff --git a/src/themes/eOS/metacity-theme-1.xml b/src/themes/eOS/metacity-theme-1.xml
new file mode 100644
index 00000000..62463cc9
--- /dev/null
+++ b/src/themes/eOS/metacity-theme-1.xml
@@ -0,0 +1,537 @@
+<?xml version="1.0"?>
+
+<metacity_theme>
+<info>
+ <name>eOS based on radiance</name>
+ <author>original authors: Kenneth Wimer, James Schriver, edited by Uhave2pay4</author>
+ <copyright>Canonical Ltd.</copyright>
+ <date>Mar 19, 2010</date>
+ <description>Metacity theme</description>
+</info>
+
+<!-- General window layout -->
+<frame_geometry name="frame_geometry_normal" title_scale="medium" rounded_top_left="true" rounded_top_right="true" rounded_bottom_left="false" rounded_bottom_right="false">
+ <distance name="left_width" value="1"/>
+ <distance name="right_width" value="1"/>
+ <distance name="bottom_height" value="1"/>
+ <distance name="left_titlebar_edge" value="10"/>
+ <distance name="right_titlebar_edge" value="10"/>
+ <distance name="button_width" value="20"/>
+ <distance name="button_height" value="20"/>
+ <distance name="title_vertical_pad" value="8"/>
+ <border name="title_border" left="2" right="2" top="2" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="2" bottom="1"/>
+</frame_geometry>
+
+<frame_geometry name="geometry_maximized" rounded_top_left="false" rounded_top_right="false" rounded_bottom_left="false" rounded_bottom_right="false">
+ <distance name="left_width" value="0"/>
+ <distance name="right_width" value="0"/>
+ <distance name="bottom_height" value="0"/>
+ <distance name="left_titlebar_edge" value="10"/>
+ <distance name="right_titlebar_edge" value="10"/>
+ <distance name="button_width" value="20"/>
+ <distance name="button_height" value="20"/>
+ <distance name="title_vertical_pad" value="8"/>
+ <border name="title_border" left="2" right="2" top="2" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="2" bottom="1"/>
+</frame_geometry>
+
+<frame_geometry name="border" has_title="false">
+ <distance name="left_width" value="3"/>
+ <distance name="right_width" value="3"/>
+ <distance name="bottom_height" value="3"/>
+ <distance name="left_titlebar_edge" value="0"/>
+ <distance name="right_titlebar_edge" value="0"/>
+ <distance name="button_width" value="0"/>
+ <distance name="button_height" value="0"/>
+ <distance name="title_vertical_pad" value="3"/>
+ <border name="title_border" left="0" right="0" top="0" bottom="0"/>
+ <border name="button_border" left="0" right="0" top="0" bottom="0"/>
+</frame_geometry>
+
+<!-- Window Title -->
+
+<draw_ops name="draw_title_text_normal">
+ <title color="#f7f9fa"
+ x="((3 `max` (width-title_width)) / 2)"
+ y="(((height - title_height) / 2) `max` 0)+1"/>
+ <title color="#f7f9fa"
+ x="((3 `max` (width-title_width)) / 2)"
+ y="(((height - title_height) / 2) `max` 0)-1"/>
+ <title color="#f7f9fa"
+ x="((3 `max` (width-title_width)) / 2)-1"
+ y="(((height - title_height) / 2) `max` 0)"/>
+ <title color="#f7f9fa"
+ x="((3 `max` (width-title_width)) / 2)+1"
+ y="(((height - title_height) / 2) `max` 0)"/>
+ <title color="#3c3c3c"
+ x="((3 `max` (width-title_width)) / 2)"
+ y="(((height - title_height) / 2) `max` 0)"/>
+</draw_ops>
+
+<draw_ops name="draw_title_text_inactive">
+ <title color="#e8e5dd"
+ x="((3 `max` (width-title_width)) / 2)"
+ y="(((height - title_height) / 2) `max` 0)+1"/>
+ <title color="#e8e5dd"
+ x="((3 `max` (width-title_width)) / 2)"
+ y="(((height - title_height) / 2) `max` 0)-1"/>
+ <title color="#e8e5dd"
+ x="((3 `max` (width-title_width)) / 2)-1"
+ y="(((height - title_height) / 2) `max` 0)"/>
+ <title color="#e8e5dd"
+ x="((3 `max` (width-title_width)) / 2)+1"
+ y="(((height - title_height) / 2) `max` 0)"/>
+ <title color="#666"
+ x="((3 `max` (width-title_width)) / 2)"
+ y="(((height - title_height) / 2) `max` 0)"/>
+</draw_ops>
+
+<draw_ops name="draw_title">
+ <!-- Background gradient -->
+ <gradient type="vertical" x="0" y="0" width="width+9" height="height">
+ <color value="#f4f4f4" />
+ <color value="#d6d6d6" />
+ <color value="#d6d6d6" />
+ </gradient>
+
+ <!-- Topmost dark line -->
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="width" y1="0" y2="0"/>
+
+ <!-- Darkening of the left arch -->
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="4" y1="1" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="2" y1="2" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="1" y1="3" y2="3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="1" y1="4" y2="4"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="0" y1="5" y2="40"/>
+
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-5" x2="width" y1="1" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-3" x2="width" y1="2" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-2" x2="width" y1="3" y2="3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-2" x2="width" y1="4" y2="4"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-1" x2="width" y1="5" y2="90"/>
+
+ <!-- Top highlight -->
+ <!-- This uses <tint> (which supports alpha) instead of <line> (which doesn't) -->
+ <tint color="#ffffff" alpha="0.55" x="5" y="1" width="width - 10" height="1"/>
+ <tint color="#ffffff" alpha="0.15" x="5" y="2" width="width - 10" height="1"/>
+
+</draw_ops>
+
+<draw_ops name="draw_title_inactive">
+ <!-- Background gradient -->
+ <gradient type="vertical" x="0" y="0" width="width+9" height="height">
+ <color value="#f4f4f4" />
+ <color value="#d6d6d6" />
+ </gradient>
+
+ <!-- Topmost dark line -->
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="width" y1="0" y2="0"/>
+
+ <!-- Darkening of the left arch -->
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="4" y1="1" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="2" y1="2" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="1" y1="3" y2="3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="1" y1="4" y2="4"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="0" y1="5" y2="40"/>
+
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-5" x2="width" y1="1" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-3" x2="width" y1="2" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-2" x2="width" y1="3" y2="3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-2" x2="width" y1="4" y2="4"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-1" x2="width" y1="5" y2="90"/>
+
+ <!-- Top highlight -->
+ <tint color="#ffffff" alpha="0.55" x="5" y="1" width="width - 10" height="1"/>
+ <tint color="#ffffff" alpha="0.15" x="5" y="2" width="width - 10" height="1"/>
+</draw_ops>
+
+
+<draw_ops name="draw_title_maximized">
+ <!-- Background gradient -->
+ <gradient type="vertical" x="0" y="0" width="width+9" height="height">
+ <color value="#f4f4f4" />
+ <color value="#d6d6d6" />
+ <color value="#d6d6d6" />
+ </gradient>
+
+ <!-- Topmost dark line -->
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="width" y1="0" y2="0"/>
+
+ <!-- Top highlight -->
+ <tint color="#ffffff" alpha="0.55" x="0" y="1" width="width" height="1"/>
+ <tint color="#ffffff" alpha="0.15" x="0" y="2" width="width" height="1"/>
+</draw_ops>
+
+<draw_ops name="draw_title_maximized_inactive">
+ <include name="draw_title_maximized" />
+
+ <!-- Background gradient -->
+ <gradient type="vertical" x="0" y="0" width="width+9" height="height">
+ <color value="#f4f4f4" />
+ <color value="#d6d6d6" />
+ </gradient>
+</draw_ops>
+
+<!-- Window Frames -->
+
+<draw_ops name="draw_frame">
+ <rectangle color="shade/gtk:bg[NORMAL]/0.74" x="0" y="0" width="width" height="height" filled="true"/>
+</draw_ops>
+
+<!--bottom border-->
+<draw_ops name="bottom_edge">
+<line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="width" y1="height-1" y2="height-1"/>
+<!--bottom left rounding-->
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="1" y1="0" y2="0"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="1" y1="1" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="2" y1="2" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="4" y1="3" y2="3"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="0" x2="10" y1="4" y2="4"/>
+<!--bottom right rounding-->
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-2" x2="width-1" y1="0" y2="0"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-2" x2="width-1" y1="1" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-3" x2="width-2" y1="2" y2="2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.74" x1="width-5" x2="width-3" y1="3" y2="3"/>
+</draw_ops>
+
+<draw_ops name="border">
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="1" y1="height - 2" x2="width - 2" y2="height - 2"/>
+ <line color="shade/gtk:bg[NORMAL]/0.88" x1="width - 2" y1="1" x2="width - 2" y2="height - 2"/>
+ <line color="shade/gtk:bg[NORMAL]/1.4" x1="1" y1="1" x2="width - 2" y2="1"/>
+ <line color="shade/gtk:bg[NORMAL]/1.4" x1="1" y1="1" x2="1" y2="height - 2"/>
+
+ <rectangle color="shade/gtk:bg[NORMAL]/0.25" filled="false"
+ x="0" y="0"
+ width="width - 1"
+ height="height - 1"/>
+</draw_ops>
+
+<!-- BUTTONS -->
+
+<!-- Button trough -->
+ <draw_ops name="left_left_background_focused_normal">
+ <image filename="trough_left.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="left_left_background_focused_pressed">
+ <image filename="trough_left.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="left_middle_background_focused_normal">
+ <image filename="trough_middle.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="left_middle_background_focused_pressed">
+ <image filename="trough_middle.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="left_right_background_focused_normal">
+ <image filename="trough_right.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="left_right_background_focused_pressed">
+ <image filename="trough_right.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="left_left_background_unfocused_normal">
+ <image filename="trough_left_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="left_left_background_unfocused_pressed">
+ <image filename="trough_left_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="left_middle_background_unfocused_normal">
+ <image filename="trough_middle_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="left_middle_background_unfocused_pressed">
+ <image filename="trough_middle_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="left_right_background_unfocused_normal">
+ <image filename="trough_right_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="left_right_background_unfocused_pressed">
+ <image filename="trough_right_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+
+
+<!-- Button Trough Right -->
+ <draw_ops name="right_left_background_focused_normal">
+ <image filename="trough_left.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="right_left_background_focused_pressed">
+ <image filename="trough_left.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="right_middle_background_focused_normal">
+ <image filename="trough_middle.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="right_middle_background_focused_pressed">
+ <image filename="trough_middle.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="right_right_background_focused_normal">
+ <image filename="trough_right.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="right_right_background_focused_pressed">
+ <image filename="trough_right.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="right_left_background_unfocused_normal">
+ <image filename="trough_left_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="right_left_background_unfocused_pressed">
+ <image filename="trough_left_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="right_middle_background_unfocused_normal">
+ <image filename="trough_middle_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="right_middle_background_unfocused_pressed">
+ <image filename="trough_middle_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="right_right_background_unfocused_normal">
+ <image filename="trough_right_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="right_right_background_unfocused_pressed">
+ <image filename="trough_right_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+
+<!-- Button Overlays -->
+ <draw_ops name="menu_focused_normal">
+ <image filename="menu.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="menu_focused_prelight">
+ <image filename="menu_prelight.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="menu_unfocused_normal">
+ <image filename="menu.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="menu_unfocused_prelight">
+ <image filename="menu_prelight.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="minimize_focused_normal">
+ <image filename="minimize.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="minimize_focused_prelight">
+ <image filename="minimize.png" x="0" y="1" width="object_width" height="object_height" colorize="shade/gtk:bg[SELECTED]/.7"/>
+ </draw_ops>
+ <draw_ops name="minimize_focused_pressed">
+ <image filename="minimize.png" x="0" y="1" width="object_width" height="object_height" alpha=".2"/>
+ </draw_ops>
+ <draw_ops name="minimize_unfocused_normal">
+ <image filename="minimize_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="minimize_unfocused_prelight">
+ <image filename="minimize_unfocused_over.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="minimize_unfocused_pressed">
+ <image filename="minimize.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="maximize_focused_normal">
+ <image filename="maximize.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="maximize_focused_prelight">
+ <image filename="maximize.png" x="0" y="1" width="object_width" height="object_height" colorize="shade/gtk:bg[SELECTED]/.7"/>
+ </draw_ops>
+ <draw_ops name="maximize_focused_pressed">
+ <image filename="maximize.png" x="0" y="1" width="object_width" height="object_height" alpha=".2"/>
+ </draw_ops>
+ <draw_ops name="maximize_unfocused_normal">
+ <image filename="maximize_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="maximize_unfocused_prelight">
+ <image filename="maximize_unfocused_over.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="maximize_unfocused_pressed">
+ <image filename="maximize.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="unmaximize_focused_normal">
+ <image filename="unmaximize.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="unmaximize_focused_prelight">
+ <image filename="unmaximize.png" x="0" y="1" width="object_width" height="object_height" colorize="shade/gtk:bg[SELECTED]/.7"/>
+ </draw_ops>
+ <draw_ops name="unmaximize_focused_pressed">
+ <image filename="unmaximize.png" x="0" y="1" width="object_width" height="object_height" alpha=".2"/>
+ </draw_ops>
+ <draw_ops name="unmaximize_unfocused_normal">
+ <image filename="unmaximize_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="unmaximize_unfocused_prelight">
+ <image filename="unmaximize_unfocused_over.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="unmaximize_unfocused_pressed">
+ <image filename="unmaximize.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="close_focused_normal">
+ <image filename="close.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="close_focused_prelight">
+ <image filename="close.png" x="0" y="1" width="object_width" height="object_height" alpha=".7"/>
+ </draw_ops>
+ <draw_ops name="close_focused_pressed">
+ <image filename="close.png" x="0" y="1" width="object_width" height="object_height" alpha=".9"/>
+ </draw_ops>
+ <draw_ops name="close_unfocused_normal">
+ <image filename="close_unfocused.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="close_unfocused_prelight">
+ <image filename="close_unfocused_over.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+ <draw_ops name="close_unfocused_pressed">
+ <image filename="close.png" x="0" y="1" width="object_width" height="object_height"/>
+ </draw_ops>
+
+<!-- FRAME STYLE -->
+<frame_style name="normal_focused" geometry="frame_geometry_normal">
+ <piece position="title" draw_ops="draw_title_text_normal"/>
+ <piece position="titlebar" draw_ops="draw_title"/>
+ <piece position="left_edge" draw_ops="draw_frame"/>
+ <piece position="right_edge" draw_ops="draw_frame"/>
+ <piece position="bottom_edge" draw_ops="bottom_edge"/>
+ <button function="left_left_background" state="normal" draw_ops="left_left_background_focused_normal"/>
+ <button function="left_middle_background" state="normal" draw_ops="left_middle_background_focused_normal"/>
+ <button function="left_right_background" state="normal" draw_ops="left_right_background_focused_normal"/>
+ <button function="left_left_background" state="prelight" draw_ops="left_left_background_focused_normal"/>
+ <button function="left_middle_background" state="prelight" draw_ops="left_middle_background_focused_normal"/>
+ <button function="left_right_background" state="prelight" draw_ops="left_right_background_focused_normal"/>
+ <button function="left_left_background" state="pressed" draw_ops="left_left_background_focused_pressed"/>
+ <button function="left_middle_background" state="pressed" draw_ops="left_middle_background_focused_pressed"/>
+ <button function="left_right_background" state="pressed" draw_ops="left_right_background_focused_pressed"/>
+ <button function="right_left_background" state="normal" draw_ops="right_left_background_focused_normal"/>
+ <button function="right_middle_background" state="normal" draw_ops="right_middle_background_focused_normal"/>
+ <button function="right_right_background" state="normal" draw_ops="right_right_background_focused_normal"/>
+ <button function="right_left_background" state="prelight" draw_ops="right_left_background_focused_normal"/>
+ <button function="right_middle_background" state="prelight" draw_ops="right_middle_background_focused_normal"/>
+ <button function="right_right_background" state="prelight" draw_ops="right_right_background_focused_normal"/>
+ <button function="right_left_background" state="pressed" draw_ops="right_left_background_focused_pressed"/>
+ <button function="right_middle_background" state="pressed" draw_ops="right_middle_background_focused_pressed"/>
+ <button function="right_right_background" state="pressed" draw_ops="right_right_background_focused_pressed"/>
+ <button function="menu" state="normal" draw_ops="menu_focused_normal"/>
+ <button function="menu" state="prelight" draw_ops="menu_focused_prelight"/>
+ <button function="menu" state="pressed" draw_ops="menu_focused_normal"/>
+ <button function="minimize" state="normal" draw_ops="minimize_focused_normal"/>
+ <button function="minimize" state="prelight" draw_ops="minimize_focused_prelight"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_focused_pressed"/>
+ <button function="maximize" state="normal" draw_ops="maximize_focused_normal"/>
+ <button function="maximize" state="prelight" draw_ops="maximize_focused_prelight"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_focused_pressed"/>
+ <button function="close" state="normal" draw_ops="close_focused_normal"/>
+ <button function="close" state="prelight" draw_ops="close_focused_prelight"/>
+ <button function="close" state="pressed" draw_ops="close_focused_pressed"/>
+</frame_style>
+
+<frame_style name="normal_unfocused" geometry="frame_geometry_normal">
+ <piece position="title" draw_ops="draw_title_text_inactive"/>
+ <piece position="titlebar" draw_ops="draw_title_inactive"/>
+ <piece position="left_edge" draw_ops="draw_frame"/>
+ <piece position="right_edge" draw_ops="draw_frame"/>
+ <piece position="bottom_edge" draw_ops="bottom_edge"/>
+ <button function="left_left_background" state="normal" draw_ops="left_left_background_unfocused_normal"/>
+ <button function="left_middle_background" state="normal" draw_ops="left_middle_background_unfocused_normal"/>
+ <button function="left_right_background" state="normal" draw_ops="left_right_background_unfocused_normal"/>
+ <button function="left_left_background" state="prelight" draw_ops="left_left_background_unfocused_normal"/>
+ <button function="left_middle_background" state="prelight" draw_ops="left_middle_background_unfocused_normal"/>
+ <button function="left_right_background" state="prelight" draw_ops="left_right_background_unfocused_normal"/>
+ <button function="left_left_background" state="pressed" draw_ops="left_left_background_unfocused_pressed"/>
+ <button function="left_middle_background" state="pressed" draw_ops="left_middle_background_unfocused_pressed"/>
+ <button function="left_right_background" state="pressed" draw_ops="left_right_background_unfocused_pressed"/>
+ <button function="right_left_background" state="normal" draw_ops="right_left_background_unfocused_normal"/>
+ <button function="right_middle_background" state="normal" draw_ops="right_middle_background_unfocused_normal"/>
+ <button function="right_right_background" state="normal" draw_ops="right_right_background_unfocused_normal"/>
+ <button function="right_left_background" state="prelight" draw_ops="right_left_background_unfocused_normal"/>
+ <button function="right_middle_background" state="prelight" draw_ops="right_middle_background_unfocused_normal"/>
+ <button function="right_right_background" state="prelight" draw_ops="right_right_background_unfocused_normal"/>
+ <button function="right_left_background" state="pressed" draw_ops="right_left_background_unfocused_pressed"/>
+ <button function="right_middle_background" state="pressed" draw_ops="right_middle_background_unfocused_pressed"/>
+ <button function="right_right_background" state="pressed" draw_ops="right_right_background_unfocused_pressed"/>
+ <button function="menu" state="normal" draw_ops="menu_unfocused_normal"/>
+ <button function="menu" state="prelight" draw_ops="menu_unfocused_prelight"/>
+ <button function="menu" state="pressed" draw_ops="menu_focused_normal"/>
+ <button function="minimize" state="normal" draw_ops="minimize_unfocused_normal"/>
+ <button function="minimize" state="prelight" draw_ops="minimize_unfocused_prelight"/>
+ <button function="minimize" state="pressed" draw_ops="minimize_unfocused_pressed"/>
+ <button function="maximize" state="normal" draw_ops="maximize_unfocused_normal"/>
+ <button function="maximize" state="prelight" draw_ops="maximize_unfocused_prelight"/>
+ <button function="maximize" state="pressed" draw_ops="maximize_unfocused_pressed"/>
+ <button function="close" state="normal" draw_ops="close_unfocused_normal"/>
+ <button function="close" state="prelight" draw_ops="close_unfocused_prelight"/>
+ <button function="close" state="pressed" draw_ops="close_unfocused_pressed"/>
+</frame_style>
+
+<frame_style name="maximized_focused" geometry="geometry_maximized" parent="normal_focused">
+ <piece position="title" draw_ops="draw_title_text_normal"/>
+ <piece position="titlebar" draw_ops="draw_title_maximized"/>
+ <button function="maximize" state="normal" draw_ops="unmaximize_focused_normal"/>
+ <button function="maximize" state="prelight" draw_ops="unmaximize_focused_prelight"/>
+ <button function="maximize" state="pressed" draw_ops="unmaximize_focused_pressed"/>
+</frame_style>
+
+<frame_style name="maximized_unfocused" geometry="geometry_maximized" parent="normal_unfocused">
+ <piece position="title" draw_ops="draw_title_text_inactive"/>
+ <piece position="titlebar" draw_ops="draw_title_maximized_inactive"/>
+ <button function="maximize" state="normal" draw_ops="unmaximize_unfocused_normal"/>
+ <button function="maximize" state="prelight" draw_ops="unmaximize_unfocused_prelight"/>
+ <button function="maximize" state="pressed" draw_ops="unmaximize_unfocused_pressed"/>
+</frame_style>
+
+<frame_style name="utility_focused" parent="normal_focused">
+ <piece position="title" draw_ops="draw_title_text_normal"/>
+ <piece position="titlebar" draw_ops="draw_title"/>
+ <button function="menu" state="normal" draw_ops="menu_focused_normal"/>
+ <button function="menu" state="prelight" draw_ops="menu_focused_prelight"/>
+ <button function="menu" state="pressed" draw_ops="menu_focused_normal"/>
+ <button function="close" state="normal" draw_ops="close_focused_normal"/>
+ <button function="close" state="prelight" draw_ops="close_focused_prelight"/>
+ <button function="close" state="pressed" draw_ops="close_focused_pressed"/>
+</frame_style>
+
+<frame_style name="utility_unfocused" parent="normal_unfocused">
+ <piece position="title" draw_ops="draw_title_text_inactive"/>
+ <piece position="titlebar" draw_ops="draw_title_inactive"/>
+ <button function="menu" state="normal" draw_ops="menu_unfocused_normal"/>
+ <button function="menu" state="prelight" draw_ops="menu_unfocused_prelight"/>
+ <button function="menu" state="pressed" draw_ops="menu_focused_normal"/>
+ <button function="close" state="normal" draw_ops="close_unfocused_normal"/>
+ <button function="close" state="prelight" draw_ops="close_unfocused_prelight"/>
+ <button function="close" state="pressed" draw_ops="close_unfocused_pressed"/>
+</frame_style>
+
+<frame_style name="border" geometry="border" parent="normal_focused">
+ <piece position="title" draw_ops="draw_title"/>
+ <piece position="titlebar" draw_ops="draw_title_inactive"/>
+</frame_style>
+
+<!-- STYLE SET -->
+<frame_style_set name="normal">
+ <frame focus="yes" state="normal" resize="both" style="normal_focused"/>
+ <frame focus="no" state="normal" resize="both" style="normal_unfocused"/>
+ <frame focus="yes" state="maximized" style="maximized_focused"/>
+ <frame focus="no" state="maximized" style="maximized_unfocused"/>
+ <frame focus="yes" state="shaded" style="normal_focused"/><!-- todo -->
+ <frame focus="no" state="shaded" style="normal_unfocused"/><!-- todo -->
+ <frame focus="yes" state="maximized_and_shaded" style="maximized_focused"/><!-- todo -->
+ <frame focus="no" state="maximized_and_shaded" style="maximized_unfocused"/><!-- todo -->
+</frame_style_set>
+
+<frame_style_set name="utility">
+ <frame focus="yes" state="normal" resize="both" style="utility_focused"/>
+ <frame focus="no" state="normal" resize="both" style="utility_unfocused"/>
+ <frame focus="yes" state="maximized" style="maximized_focused"/>
+ <frame focus="no" state="maximized" style="normal_focused"/>
+ <frame focus="yes" state="shaded" style="normal_focused"/><!-- todo -->
+ <frame focus="no" state="shaded" style="normal_unfocused"/><!-- todo -->
+ <frame focus="yes" state="maximized_and_shaded" style="maximized_focused"/><!-- todo -->
+ <frame focus="no" state="maximized_and_shaded" style="maximized_unfocused"/><!-- todo -->
+</frame_style_set>
+
+<frame_style_set name="border">
+ <frame focus="yes" state="normal" resize="both" style="border"/>
+ <frame focus="no" state="normal" resize="both" style="border"/>
+ <frame focus="yes" state="maximized" style="maximized_focused"/>
+ <frame focus="no" state="maximized" style="normal_focused"/>
+ <frame focus="yes" state="shaded" style="normal_focused"/><!-- todo -->
+ <frame focus="no" state="shaded" style="normal_unfocused"/><!-- todo -->
+ <frame focus="yes" state="maximized_and_shaded" style="maximized_focused"/><!-- todo -->
+ <frame focus="no" state="maximized_and_shaded" style="maximized_unfocused"/><!-- todo -->
+</frame_style_set>
+
+<!-- WINDOW -->
+<window type="normal" style_set="normal"/>
+<window type="dialog" style_set="utility"/>
+<window type="modal_dialog" style_set="utility"/>
+<window type="menu" style_set="utility"/>
+<window type="utility" style_set="utility"/>
+<window type="border" style_set="border"/>
+
+</metacity_theme>
diff --git a/src/themes/eOS/minimize.png b/src/themes/eOS/minimize.png
new file mode 100644
index 00000000..5083d1e2
--- /dev/null
+++ b/src/themes/eOS/minimize.png
Binary files differ
diff --git a/src/themes/eOS/minimize_unfocused.png b/src/themes/eOS/minimize_unfocused.png
new file mode 100644
index 00000000..9bd4d273
--- /dev/null
+++ b/src/themes/eOS/minimize_unfocused.png
Binary files differ
diff --git a/src/themes/eOS/minimize_unfocused_over.png b/src/themes/eOS/minimize_unfocused_over.png
new file mode 100644
index 00000000..5083d1e2
--- /dev/null
+++ b/src/themes/eOS/minimize_unfocused_over.png
Binary files differ
diff --git a/src/themes/eOS/trough_left.png b/src/themes/eOS/trough_left.png
new file mode 100644
index 00000000..070d3da0
--- /dev/null
+++ b/src/themes/eOS/trough_left.png
Binary files differ
diff --git a/src/themes/eOS/trough_left_unfocused.png b/src/themes/eOS/trough_left_unfocused.png
new file mode 100644
index 00000000..51ed9ab1
--- /dev/null
+++ b/src/themes/eOS/trough_left_unfocused.png
Binary files differ
diff --git a/src/themes/eOS/trough_middle.png b/src/themes/eOS/trough_middle.png
new file mode 100644
index 00000000..28238835
--- /dev/null
+++ b/src/themes/eOS/trough_middle.png
Binary files differ
diff --git a/src/themes/eOS/trough_middle_unfocused.png b/src/themes/eOS/trough_middle_unfocused.png
new file mode 100644
index 00000000..961e4f48
--- /dev/null
+++ b/src/themes/eOS/trough_middle_unfocused.png
Binary files differ
diff --git a/src/themes/eOS/trough_right.png b/src/themes/eOS/trough_right.png
new file mode 100644
index 00000000..84bfad11
--- /dev/null
+++ b/src/themes/eOS/trough_right.png
Binary files differ
diff --git a/src/themes/eOS/trough_right_unfocused.png b/src/themes/eOS/trough_right_unfocused.png
new file mode 100644
index 00000000..acab4faf
--- /dev/null
+++ b/src/themes/eOS/trough_right_unfocused.png
Binary files differ
diff --git a/src/themes/eOS/unmaximize.png b/src/themes/eOS/unmaximize.png
new file mode 100644
index 00000000..785aa133
--- /dev/null
+++ b/src/themes/eOS/unmaximize.png
Binary files differ
diff --git a/src/themes/eOS/unmaximize_unfocused.png b/src/themes/eOS/unmaximize_unfocused.png
new file mode 100644
index 00000000..d7c35fc1
--- /dev/null
+++ b/src/themes/eOS/unmaximize_unfocused.png
Binary files differ
diff --git a/src/themes/eOS/unmaximize_unfocused_over.png b/src/themes/eOS/unmaximize_unfocused_over.png
new file mode 100644
index 00000000..785aa133
--- /dev/null
+++ b/src/themes/eOS/unmaximize_unfocused_over.png
Binary files differ
diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am
new file mode 100644
index 00000000..031ef961
--- /dev/null
+++ b/src/tools/Makefile.am
@@ -0,0 +1,33 @@
+@INTLTOOL_DESKTOP_RULE@
+
+icondir=$(pkgdatadir)/icons
+icon_DATA=marco-window-demo.png
+
+INCLUDES=@MARCO_WINDOW_DEMO_CFLAGS@ @MARCO_MESSAGE_CFLAGS@ \
+ -DMARCO_ICON_DIR=\"$(pkgdatadir)/icons\" \
+ -DMARCO_LOCALEDIR=\"$(prefix)/@DATADIRNAME@/locale\"
+
+marco_message_SOURCES= \
+ marco-message.c
+
+marco_window_demo_SOURCES= \
+ marco-window-demo.c
+
+marco_mag_SOURCES= \
+ marco-mag.c
+
+marco_grayscale_SOURCES= \
+ marco-grayscale.c
+
+bin_PROGRAMS=marco-message marco-window-demo
+
+## cheesy hacks I use, don't really have any business existing. ;-)
+noinst_PROGRAMS=marco-mag marco-grayscale
+
+marco_message_LDADD= @MARCO_MESSAGE_LIBS@
+marco_window_demo_LDADD= @MARCO_WINDOW_DEMO_LIBS@
+marco_mag_LDADD= @MARCO_WINDOW_DEMO_LIBS@ -lm
+marco_grayscale_LDADD = @MARCO_WINDOW_DEMO_LIBS@
+
+EXTRA_DIST=$(icon_DATA)
+
diff --git a/src/tools/marco-grayscale.c b/src/tools/marco-grayscale.c
new file mode 100644
index 00000000..8d0cd68c
--- /dev/null
+++ b/src/tools/marco-grayscale.c
@@ -0,0 +1,109 @@
+/* Hack for grayscaling an image */
+
+/*
+ * Copyright (C) 2002 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+
+#define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
+
+static GdkPixbuf*
+grayscale_pixbuf (GdkPixbuf *pixbuf)
+{
+ GdkPixbuf *gray;
+ guchar *pixels;
+ int rowstride;
+ int pixstride;
+ int row;
+ int n_rows;
+ int width;
+
+ gray = gdk_pixbuf_copy (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (gray);
+ pixstride = gdk_pixbuf_get_has_alpha (gray) ? 4 : 3;
+
+ pixels = gdk_pixbuf_get_pixels (gray);
+ n_rows = gdk_pixbuf_get_height (gray);
+ width = gdk_pixbuf_get_width (gray);
+
+ row = 0;
+ while (row < n_rows)
+ {
+ guchar *p = pixels + row * rowstride;
+ guchar *end = p + (pixstride * width);
+
+ while (p != end)
+ {
+ double v = INTENSITY (p[0], p[1], p[2]);
+
+ p[0] = (guchar) v;
+ p[1] = (guchar) v;
+ p[2] = (guchar) v;
+
+ p += pixstride;
+ }
+
+ ++row;
+ }
+
+ return gray;
+}
+
+int
+main (int argc, char **argv)
+{
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *gray;
+ GError *err;
+
+ if (argc != 2)
+ {
+ g_printerr ("specify a single image on the command line\n");
+ return 1;
+ }
+
+ g_type_init ();
+
+ err = NULL;
+ pixbuf = gdk_pixbuf_new_from_file (argv[1], &err);
+ if (err != NULL)
+ {
+ g_printerr ("failed to load image: %s\n", err->message);
+ g_error_free (err);
+ return 1;
+ }
+
+ gray = grayscale_pixbuf (pixbuf);
+
+ err = NULL;
+ gdk_pixbuf_save (gray, "grayscale.png", "png", &err, NULL);
+ if (err != NULL)
+ {
+ g_printerr ("failed to save image: %s\n", err->message);
+ g_error_free (err);
+ return 1;
+ }
+
+ g_print ("wrote grayscale.png\n");
+
+ return 0;
+}
diff --git a/src/tools/marco-mag.c b/src/tools/marco-mag.c
new file mode 100644
index 00000000..22c7cc3d
--- /dev/null
+++ b/src/tools/marco-mag.c
@@ -0,0 +1,278 @@
+/* Hack for use instead of xmag */
+
+/*
+ * Copyright (C) 2002 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+#define _XOPEN_SOURCE 600 /* C99 -- for rint() */
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <gdk/gdkkeysyms.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <math.h>
+
+static GtkWidget *grab_widget = NULL;
+static GtkWidget *display_window = NULL;
+static int last_grab_x = 0;
+static int last_grab_y = 0;
+static int last_grab_width = 150;
+static int last_grab_height = 150;
+static GtkAllocation last_grab_allocation;
+static double width_factor = 4.0;
+static double height_factor = 4.0;
+static GdkInterpType interp_mode = GDK_INTERP_NEAREST;
+static guint regrab_idle_id = 0;
+
+static GdkPixbuf*
+get_pixbuf (void)
+{
+ GdkPixbuf *screenshot;
+ GdkPixbuf *magnified;
+
+#if 0
+ g_print ("Size %d x %d\n",
+ last_grab_width, last_grab_height);
+#endif
+
+ screenshot = gdk_pixbuf_get_from_drawable (NULL, gdk_get_default_root_window (),
+ NULL,
+ last_grab_x, last_grab_y, 0, 0,
+ last_grab_width, last_grab_height);
+
+ if (screenshot == NULL)
+ {
+ g_printerr ("Screenshot failed\n");
+ exit (1);
+ }
+
+ magnified = gdk_pixbuf_scale_simple (screenshot, last_grab_width * width_factor,
+ last_grab_height * height_factor,
+ interp_mode);
+
+
+ g_object_unref (G_OBJECT (screenshot));
+
+ return magnified;
+}
+
+static gboolean
+regrab_idle (GtkWidget *image)
+{
+ GdkPixbuf *magnified;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (image, &allocation);
+
+ if (allocation.width != last_grab_allocation.width ||
+ allocation.height != last_grab_allocation.height)
+ {
+ last_grab_width = rint (allocation.width / width_factor);
+ last_grab_height = rint (allocation.height / height_factor);
+ last_grab_allocation = allocation;
+
+ magnified = get_pixbuf ();
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (image), magnified);
+
+ g_object_unref (G_OBJECT (magnified));
+ }
+
+ regrab_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+image_resized (GtkWidget *image)
+{
+ if (regrab_idle_id == 0)
+ regrab_idle_id = g_idle_add_full (G_PRIORITY_LOW + 100, (GSourceFunc) regrab_idle,
+ image, NULL);
+}
+
+static void
+grab_area_at_mouse (GtkWidget *invisible,
+ int x_root,
+ int y_root)
+{
+ GdkPixbuf *magnified;
+ int width, height;
+ GtkWidget *widget;
+
+ width = last_grab_width;
+ height = last_grab_height;
+
+ last_grab_x = x_root;
+ last_grab_y = y_root;
+ last_grab_width = width;
+ last_grab_height = height;
+
+ magnified = get_pixbuf ();
+
+ display_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size (GTK_WINDOW (display_window),
+ last_grab_width, last_grab_height);
+ widget = gtk_image_new_from_pixbuf (magnified);
+ gtk_widget_set_size_request (widget, 40, 40);
+ gtk_container_add (GTK_CONTAINER (display_window), widget);
+ g_object_unref (G_OBJECT (magnified));
+
+ g_object_add_weak_pointer (G_OBJECT (display_window),
+ (gpointer) &display_window);
+
+ g_signal_connect (G_OBJECT (display_window), "destroy",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ g_signal_connect_after (G_OBJECT (widget), "size_allocate", G_CALLBACK (image_resized), NULL);
+
+ gtk_widget_show_all (display_window);
+}
+
+static void
+shutdown_grab (void)
+{
+ gdk_keyboard_ungrab (gtk_get_current_event_time ());
+ gdk_pointer_ungrab (gtk_get_current_event_time ());
+ gtk_grab_remove (grab_widget);
+}
+
+static void
+mouse_motion (GtkWidget *invisible,
+ GdkEventMotion *event,
+ gpointer data)
+{
+
+}
+
+static gboolean
+mouse_release (GtkWidget *invisible,
+ GdkEventButton *event,
+ gpointer data)
+{
+ if (event->button != 1)
+ return FALSE;
+
+ grab_area_at_mouse (invisible, event->x_root, event->y_root);
+
+ shutdown_grab ();
+
+ g_signal_handlers_disconnect_by_func (invisible, mouse_motion, NULL);
+ g_signal_handlers_disconnect_by_func (invisible, mouse_release, NULL);
+
+ return TRUE;
+}
+
+/* Helper Functions */
+
+static gboolean mouse_press (GtkWidget *invisible,
+ GdkEventButton *event,
+ gpointer data);
+
+static gboolean
+key_press (GtkWidget *invisible,
+ GdkEventKey *event,
+ gpointer data)
+{
+ if (event->keyval == GDK_Escape)
+ {
+ shutdown_grab ();
+
+ g_signal_handlers_disconnect_by_func (invisible, mouse_press, NULL);
+ g_signal_handlers_disconnect_by_func (invisible, key_press, NULL);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+mouse_press (GtkWidget *invisible,
+ GdkEventButton *event,
+ gpointer data)
+{
+ if (event->type == GDK_BUTTON_PRESS &&
+ event->button == 1)
+ {
+ g_signal_connect (invisible, "motion_notify_event",
+ G_CALLBACK (mouse_motion), NULL);
+ g_signal_connect (invisible, "button_release_event",
+ G_CALLBACK (mouse_release), NULL);
+ g_signal_handlers_disconnect_by_func (invisible, mouse_press, NULL);
+ g_signal_handlers_disconnect_by_func (invisible, key_press, NULL);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+begin_area_grab (void)
+{
+ if (grab_widget == NULL)
+ {
+ grab_widget = gtk_invisible_new ();
+
+ gtk_widget_add_events (grab_widget,
+ GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
+
+ gtk_widget_show (grab_widget);
+ }
+
+ if (gdk_keyboard_grab (gtk_widget_get_window (grab_widget),
+ FALSE,
+ gtk_get_current_event_time ()) != GDK_GRAB_SUCCESS)
+ {
+ g_warning ("Failed to grab keyboard to do eyedropper");
+ return;
+ }
+
+ if (gdk_pointer_grab (gtk_widget_get_window (grab_widget),
+ FALSE,
+ GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK,
+ NULL,
+ NULL,
+ gtk_get_current_event_time ()) != GDK_GRAB_SUCCESS)
+ {
+ gdk_keyboard_ungrab (GDK_CURRENT_TIME);
+ g_warning ("Failed to grab pointer to do eyedropper");
+ return;
+ }
+
+ gtk_grab_add (grab_widget);
+
+ g_signal_connect (grab_widget, "button_press_event",
+ G_CALLBACK (mouse_press), NULL);
+ g_signal_connect (grab_widget, "key_press_event",
+ G_CALLBACK (key_press), NULL);
+}
+
+int
+main (int argc, char **argv)
+{
+ gtk_init (&argc, &argv);
+
+ begin_area_grab ();
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/src/tools/marco-message.c b/src/tools/marco-message.c
new file mode 100644
index 00000000..78bebeb2
--- /dev/null
+++ b/src/tools/marco-message.c
@@ -0,0 +1,187 @@
+/* Marco send-magic-messages app */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libintl.h>
+#define _(x) dgettext (GETTEXT_PACKAGE, x)
+#define N_(x) x
+
+
+static void
+send_restart (void)
+{
+ XEvent xev;
+
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+ xev.xclient.window = gdk_x11_get_default_root_xwindow ();
+ xev.xclient.message_type = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ "_MARCO_RESTART_MESSAGE",
+ False);
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = 0;
+ xev.xclient.data.l[1] = 0;
+ xev.xclient.data.l[2] = 0;
+
+ XSendEvent (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ gdk_x11_get_default_root_xwindow (),
+ False,
+ SubstructureRedirectMask | SubstructureNotifyMask,
+ &xev);
+
+ XFlush (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+ XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), False);
+}
+
+static void
+send_reload_theme (void)
+{
+ XEvent xev;
+
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+ xev.xclient.window = gdk_x11_get_default_root_xwindow ();
+ xev.xclient.message_type = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ "_MARCO_RELOAD_THEME_MESSAGE",
+ False);
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = 0;
+ xev.xclient.data.l[1] = 0;
+ xev.xclient.data.l[2] = 0;
+
+ XSendEvent (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ gdk_x11_get_default_root_xwindow (),
+ False,
+ SubstructureRedirectMask | SubstructureNotifyMask,
+ &xev);
+
+ XFlush (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+ XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), False);
+}
+
+static void
+send_set_keybindings (gboolean enabled)
+{
+ XEvent xev;
+
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+ xev.xclient.window = gdk_x11_get_default_root_xwindow ();
+ xev.xclient.message_type = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ "_MARCO_SET_KEYBINDINGS_MESSAGE",
+ False);
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = enabled;
+ xev.xclient.data.l[1] = 0;
+ xev.xclient.data.l[2] = 0;
+
+ XSendEvent (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ gdk_x11_get_default_root_xwindow (),
+ False,
+ SubstructureRedirectMask | SubstructureNotifyMask,
+ &xev);
+
+ XFlush (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+ XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), False);
+}
+
+#ifdef WITH_VERBOSE_MODE
+static void
+send_toggle_verbose (void)
+{
+ XEvent xev;
+
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+ xev.xclient.window = gdk_x11_get_default_root_xwindow ();
+ xev.xclient.message_type = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ "_MARCO_TOGGLE_VERBOSE",
+ False);
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = 0;
+ xev.xclient.data.l[1] = 0;
+ xev.xclient.data.l[2] = 0;
+
+ XSendEvent (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ gdk_x11_get_default_root_xwindow (),
+ False,
+ SubstructureRedirectMask | SubstructureNotifyMask,
+ &xev);
+
+ XFlush (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+ XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), False);
+}
+#endif
+
+static void
+usage (void)
+{
+ g_printerr (_("Usage: %s\n"),
+ "marco-message (restart|reload-theme|enable-keybindings|disable-keybindings|toggle-verbose)");
+ exit (1);
+}
+
+int
+main (int argc, char **argv)
+{
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ gtk_init (&argc, &argv);
+
+ if (argc < 2)
+ usage ();
+
+ if (strcmp (argv[1], "restart") == 0)
+ send_restart ();
+ else if (strcmp (argv[1], "reload-theme") == 0)
+ send_reload_theme ();
+ else if (strcmp (argv[1], "enable-keybindings") == 0)
+ send_set_keybindings (TRUE);
+ else if (strcmp (argv[1], "disable-keybindings") == 0)
+ send_set_keybindings (FALSE);
+ else if (strcmp (argv[1], "toggle-verbose") == 0)
+ {
+#ifndef WITH_VERBOSE_MODE
+ g_printerr (_("Marco was compiled without support for verbose mode\n"));
+ return 1;
+#else
+ send_toggle_verbose ();
+#endif
+ }
+ else
+ usage ();
+
+ return 0;
+}
+
diff --git a/src/tools/marco-window-demo.c b/src/tools/marco-window-demo.c
new file mode 100644
index 00000000..876a2585
--- /dev/null
+++ b/src/tools/marco-window-demo.c
@@ -0,0 +1,1016 @@
+/* Marco window types/properties demo app */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+#include <unistd.h>
+
+static GtkWidget* do_appwindow (void);
+
+static gboolean aspect_on;
+
+static void
+set_gdk_window_struts (GdkWindow *window,
+ int left,
+ int right,
+ int top,
+ int bottom)
+{
+ long vals[12];
+
+ vals[0] = left;
+ vals[1] = right;
+ vals[2] = top;
+ vals[3] = bottom;
+ vals[4] = 000;
+ vals[5] = 400;
+ vals[6] = 200;
+ vals[7] = 600;
+ vals[8] = 76;
+ vals[9] = 676;
+ vals[10] = 200;
+ vals[11] = 800;
+
+ XChangeProperty (GDK_WINDOW_XDISPLAY (window),
+ GDK_WINDOW_XWINDOW (window),
+ XInternAtom (GDK_WINDOW_XDISPLAY (window),
+ "_NET_WM_STRUT_PARTIAL", False),
+ XA_CARDINAL, 32, PropModeReplace,
+ (guchar *)vals, 12);
+}
+
+static void
+on_realize_set_struts (GtkWindow *window,
+ gpointer data)
+{
+ int left;
+ int right;
+ int top;
+ int bottom;
+
+ g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (window)));
+
+ left = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window), "meta-strut-left"));
+ right = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window), "meta-strut-right"));
+ top = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window), "meta-strut-top"));
+ bottom = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (window), "meta-strut-bottom"));
+
+ set_gdk_window_struts (gtk_widget_get_window (GTK_WIDGET (window)),
+ left, right, top, bottom);
+}
+
+static void
+set_gtk_window_struts (GtkWidget *window,
+ int left,
+ int right,
+ int top,
+ int bottom)
+{
+ g_object_set_data (G_OBJECT (window), "meta-strut-left",
+ GINT_TO_POINTER (left));
+ g_object_set_data (G_OBJECT (window), "meta-strut-right",
+ GINT_TO_POINTER (right));
+ g_object_set_data (G_OBJECT (window), "meta-strut-top",
+ GINT_TO_POINTER (top));
+ g_object_set_data (G_OBJECT (window), "meta-strut-bottom",
+ GINT_TO_POINTER (bottom));
+
+ g_signal_handlers_disconnect_by_func (G_OBJECT (window),
+ on_realize_set_struts,
+ NULL);
+
+ g_signal_connect_after (G_OBJECT (window),
+ "realize",
+ G_CALLBACK (on_realize_set_struts),
+ NULL);
+
+ if (gtk_widget_get_realized (GTK_WIDGET (window)))
+ set_gdk_window_struts (gtk_widget_get_window (GTK_WIDGET (window)),
+ left, right, top, bottom);
+}
+
+static void
+set_gdk_window_type (GdkWindow *window,
+ const char *type)
+{
+ Atom atoms[2] = { None, None };
+
+ atoms[0] = XInternAtom (GDK_WINDOW_XDISPLAY (window),
+ type, False);
+
+ XChangeProperty (GDK_WINDOW_XDISPLAY (window),
+ GDK_WINDOW_XWINDOW (window),
+ XInternAtom (GDK_WINDOW_XDISPLAY (window), "_NET_WM_WINDOW_TYPE", False),
+ XA_ATOM, 32, PropModeReplace,
+ (guchar *)atoms,
+ 1);
+}
+
+static void
+on_realize_set_type (GtkWindow *window,
+ gpointer data)
+{
+ const char *type;
+
+ g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (window)));
+
+ type = g_object_get_data (G_OBJECT (window), "meta-window-type");
+
+ g_return_if_fail (type != NULL);
+
+ set_gdk_window_type (gtk_widget_get_window (GTK_WIDGET (window)),
+ type);
+}
+
+static void
+set_gtk_window_type (GtkWindow *window,
+ const char *type)
+{
+ g_object_set_data (G_OBJECT (window), "meta-window-type", (char*) type);
+
+ g_signal_handlers_disconnect_by_func (G_OBJECT (window),
+ on_realize_set_type,
+ NULL);
+
+ g_signal_connect_after (G_OBJECT (window),
+ "realize",
+ G_CALLBACK (on_realize_set_type),
+ NULL);
+
+ if (gtk_widget_get_realized (GTK_WIDGET (window)))
+ set_gdk_window_type (gtk_widget_get_window (GTK_WIDGET (window)),
+ type);
+}
+
+static void
+set_gdk_window_border_only (GdkWindow *window)
+{
+ gdk_window_set_decorations (window, GDK_DECOR_BORDER);
+}
+
+static void
+on_realize_set_border_only (GtkWindow *window,
+ gpointer data)
+{
+ g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (window)));
+
+ set_gdk_window_border_only (gtk_widget_get_window (GTK_WIDGET (window)));
+}
+
+static void
+set_gtk_window_border_only (GtkWindow *window)
+{
+ g_signal_handlers_disconnect_by_func (G_OBJECT (window),
+ on_realize_set_border_only,
+ NULL);
+
+ g_signal_connect_after (G_OBJECT (window),
+ "realize",
+ G_CALLBACK (on_realize_set_border_only),
+ NULL);
+
+ if (gtk_widget_get_realized (GTK_WIDGET (window)))
+ set_gdk_window_border_only (gtk_widget_get_window (GTK_WIDGET (window)));
+}
+
+int
+main (int argc, char **argv)
+{
+ GList *list;
+ GdkPixbuf *pixbuf;
+ GError *err;
+
+ gtk_init (&argc, &argv);
+
+ err = NULL;
+ pixbuf = gdk_pixbuf_new_from_file (MARCO_ICON_DIR"/marco-window-demo.png",
+ &err);
+ if (pixbuf)
+ {
+ list = g_list_prepend (NULL, pixbuf);
+
+ gtk_window_set_default_icon_list (list);
+ g_list_free (list);
+ g_object_unref (G_OBJECT (pixbuf));
+ }
+ else
+ {
+ g_printerr ("Could not load icon: %s\n", err->message);
+ g_error_free (err);
+ }
+
+ do_appwindow ();
+
+ gtk_main ();
+
+ return 0;
+}
+
+static void
+response_cb (GtkDialog *dialog,
+ int response_id,
+ void *data);
+
+static void
+make_dialog (GtkWidget *parent,
+ int depth)
+{
+ GtkWidget *dialog;
+ char *str;
+
+ dialog = gtk_message_dialog_new (parent ? GTK_WINDOW (parent) : NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CLOSE,
+ parent ? "Here is a dialog %d" :
+ "Here is a dialog %d with no transient parent",
+ depth);
+
+ str = g_strdup_printf ("%d dialog", depth);
+ gtk_window_set_title (GTK_WINDOW (dialog), str);
+ g_free (str);
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ "Open child dialog",
+ GTK_RESPONSE_ACCEPT);
+
+ /* Close dialog on user response */
+ g_signal_connect (G_OBJECT (dialog),
+ "response",
+ G_CALLBACK (response_cb),
+ NULL);
+
+ g_object_set_data (G_OBJECT (dialog), "depth",
+ GINT_TO_POINTER (depth));
+
+ gtk_widget_show (dialog);
+}
+
+static void
+response_cb (GtkDialog *dialog,
+ int response_id,
+ void *data)
+{
+ switch (response_id)
+ {
+ case GTK_RESPONSE_ACCEPT:
+ make_dialog (GTK_WIDGET (dialog),
+ GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dialog),
+ "depth")) + 1);
+ break;
+
+ default:
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ break;
+ }
+}
+
+static void
+dialog_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ make_dialog (GTK_WIDGET (callback_data), 1);
+}
+
+static void
+modal_dialog_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (callback_data),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CLOSE,
+ "Here is a MODAL dialog");
+
+ set_gtk_window_type (GTK_WINDOW (dialog), "_NET_WM_WINDOW_TYPE_MODAL_DIALOG");
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+no_parent_dialog_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ make_dialog (NULL, 1);
+}
+
+static void
+utility_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ GtkWidget *window;
+ GtkWidget *vbox;
+ GtkWidget *button;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ set_gtk_window_type (GTK_WINDOW (window), "_NET_WM_WINDOW_TYPE_UTILITY");
+ gtk_window_set_title (GTK_WINDOW (window), "Utility");
+
+ gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (callback_data));
+
+ vbox = gtk_vbox_new (FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+
+ button = gtk_button_new_with_mnemonic ("_A button");
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+
+ button = gtk_button_new_with_mnemonic ("_B button");
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+
+ button = gtk_button_new_with_mnemonic ("_C button");
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+
+ button = gtk_button_new_with_mnemonic ("_D button");
+ gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (window);
+}
+
+static void
+toolbar_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ GtkWidget *window;
+ GtkWidget *vbox;
+ GtkWidget *label;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ set_gtk_window_type (GTK_WINDOW (window), "_NET_WM_WINDOW_TYPE_TOOLBAR");
+ gtk_window_set_title (GTK_WINDOW (window), "Toolbar");
+
+ gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (callback_data));
+
+ vbox = gtk_vbox_new (FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+
+ label = gtk_label_new ("FIXME this needs a resize grip, etc.");
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (window);
+}
+
+static void
+menu_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ GtkWidget *window;
+ GtkWidget *vbox;
+ GtkWidget *label;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ set_gtk_window_type (GTK_WINDOW (window), "_NET_WM_WINDOW_TYPE_MENU");
+ gtk_window_set_title (GTK_WINDOW (window), "Menu");
+
+ gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (callback_data));
+
+ vbox = gtk_vbox_new (FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+
+ label = gtk_label_new ("FIXME this isn't a menu.");
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (window);
+}
+
+static void
+override_redirect_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ GtkWidget *window;
+ GtkWidget *vbox;
+ GtkWidget *label;
+
+ window = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_window_set_title (GTK_WINDOW (window), "Override Redirect");
+
+ vbox = gtk_vbox_new (FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+
+ label = gtk_label_new ("This is an override\nredirect window\nand should not be managed");
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (window);
+}
+
+static void
+border_only_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ GtkWidget *window;
+ GtkWidget *vbox;
+ GtkWidget *label;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ set_gtk_window_border_only (GTK_WINDOW (window));
+ gtk_window_set_title (GTK_WINDOW (window), "Border only");
+
+ gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (callback_data));
+
+ vbox = gtk_vbox_new (FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+
+ label = gtk_label_new ("This window is supposed to have a border but no titlebar.");
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (window);
+}
+
+#if 0
+static void
+changing_icon_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ GtkWidget *window;
+ GtkWidget *vbox;
+ GtkWidget *label;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), "Changing Icon");
+
+ vbox = gtk_vbox_new (FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+
+ label = gtk_label_new ("This window has an icon that changes over time");
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (window);
+}
+#endif
+
+static gboolean
+focus_in_event_cb (GtkWidget *window,
+ GdkEvent *event,
+ gpointer data)
+{
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (data);
+
+ gtk_label_set_text (GTK_LABEL (widget), "Has focus");
+
+ return TRUE;
+}
+
+
+static gboolean
+focus_out_event_cb (GtkWidget *window,
+ GdkEvent *event,
+ gpointer data)
+{
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (data);
+
+ gtk_label_set_text (GTK_LABEL (widget), "Not focused");
+
+ return TRUE;
+}
+
+static GtkWidget*
+focus_label (GtkWidget *window)
+{
+ GtkWidget *label;
+
+ label = gtk_label_new ("Not focused");
+
+ g_signal_connect (G_OBJECT (window), "focus_in_event",
+ G_CALLBACK (focus_in_event_cb), label);
+
+ g_signal_connect (G_OBJECT (window), "focus_out_event",
+ G_CALLBACK (focus_out_event_cb), label);
+
+ return label;
+}
+
+static void
+splashscreen_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ GtkWidget *window;
+ GtkWidget *image;
+ GtkWidget *vbox;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ set_gtk_window_type (GTK_WINDOW (window), "_NET_WM_WINDOW_TYPE_SPLASHSCREEN");
+ gtk_window_set_title (GTK_WINDOW (window), "Splashscreen");
+
+ vbox = gtk_vbox_new (FALSE, 0);
+
+ image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (vbox), image, FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (vbox), focus_label (window), FALSE, FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+
+ gtk_widget_show_all (window);
+}
+
+enum
+{
+ DOCK_TOP = 1,
+ DOCK_BOTTOM = 2,
+ DOCK_LEFT = 3,
+ DOCK_RIGHT = 4,
+ DOCK_ALL = 5
+};
+
+static void
+make_dock (int type)
+{
+ GtkWidget *window;
+ GtkWidget *image;
+ GtkWidget *box;
+ GtkWidget *button;
+
+ g_return_if_fail (type != DOCK_ALL);
+
+ box = NULL;
+ switch (type)
+ {
+ case DOCK_LEFT:
+ case DOCK_RIGHT:
+ box = gtk_vbox_new (FALSE, 0);
+ break;
+ case DOCK_TOP:
+ case DOCK_BOTTOM:
+ box = gtk_hbox_new (FALSE, 0);
+ break;
+ case DOCK_ALL:
+ break;
+ }
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ set_gtk_window_type (GTK_WINDOW (window), "_NET_WM_WINDOW_TYPE_DOCK");
+
+ image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (box), focus_label (window), FALSE, FALSE, 0);
+
+ button = gtk_button_new_with_label ("Close");
+ gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+ g_signal_connect_swapped (G_OBJECT (button), "clicked",
+ G_CALLBACK (gtk_widget_destroy), window);
+
+ gtk_container_add (GTK_CONTAINER (window), box);
+
+#define DOCK_SIZE 48
+ switch (type)
+ {
+ case DOCK_LEFT:
+ gtk_widget_set_size_request (window, DOCK_SIZE, 400);
+ gtk_window_move (GTK_WINDOW (window), 0, 000);
+ set_gtk_window_struts (window, DOCK_SIZE, 0, 0, 0);
+ gtk_window_set_title (GTK_WINDOW (window), "LeftDock");
+ break;
+ case DOCK_RIGHT:
+ gtk_widget_set_size_request (window, DOCK_SIZE, 400);
+ gtk_window_move (GTK_WINDOW (window), gdk_screen_width () - DOCK_SIZE, 200);
+ set_gtk_window_struts (window, 0, DOCK_SIZE, 0, 0);
+ gtk_window_set_title (GTK_WINDOW (window), "RightDock");
+ break;
+ case DOCK_TOP:
+ gtk_widget_set_size_request (window, 600, DOCK_SIZE);
+ gtk_window_move (GTK_WINDOW (window), 76, 0);
+ set_gtk_window_struts (window, 0, 0, DOCK_SIZE, 0);
+ gtk_window_set_title (GTK_WINDOW (window), "TopDock");
+ break;
+ case DOCK_BOTTOM:
+ gtk_widget_set_size_request (window, 600, DOCK_SIZE);
+ gtk_window_move (GTK_WINDOW (window), 200, gdk_screen_height () - DOCK_SIZE);
+ set_gtk_window_struts (window, 0, 0, 0, DOCK_SIZE);
+ gtk_window_set_title (GTK_WINDOW (window), "BottomDock");
+ break;
+ case DOCK_ALL:
+ break;
+ }
+
+ gtk_widget_show_all (window);
+}
+
+static void
+dock_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ if (callback_action == DOCK_ALL)
+ {
+ make_dock (DOCK_TOP);
+ make_dock (DOCK_BOTTOM);
+ make_dock (DOCK_LEFT);
+ make_dock (DOCK_RIGHT);
+ }
+ else
+ {
+ make_dock (callback_action);
+ }
+}
+
+static void
+desktop_cb (gpointer callback_data,
+ guint callback_action,
+ GtkWidget *widget)
+{
+ GtkWidget *window;
+ GtkWidget *label;
+ GdkColor desktop_color;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ set_gtk_window_type (GTK_WINDOW (window), "_NET_WM_WINDOW_TYPE_DESKTOP");
+ gtk_window_set_title (GTK_WINDOW (window), "Desktop");
+ gtk_widget_set_size_request (window,
+ gdk_screen_width (), gdk_screen_height ());
+ gtk_window_move (GTK_WINDOW (window), 0, 0);
+
+ desktop_color.red = 0x5144;
+ desktop_color.green = 0x75D6;
+ desktop_color.blue = 0xA699;
+
+ gtk_widget_modify_bg (window, GTK_STATE_NORMAL, &desktop_color);
+
+ label = focus_label (window);
+
+ gtk_container_add (GTK_CONTAINER (window), label);
+
+ gtk_widget_show_all (window);
+}
+
+static GtkItemFactoryEntry menu_items[] =
+{
+ { "/_Windows", NULL, NULL, 0, "<Branch>" },
+ { "/Windows/tearoff", NULL, NULL, 0, "<Tearoff>" },
+ { "/Windows/_Dialog", "<control>d", dialog_cb, 0, NULL },
+ { "/Windows/_Modal dialog", NULL, modal_dialog_cb, 0, NULL },
+ { "/Windows/_Parentless dialog", NULL, no_parent_dialog_cb, 0, NULL },
+ { "/Windows/_Utility", "<control>u", utility_cb, 0, NULL },
+ { "/Windows/_Splashscreen", "<control>s", splashscreen_cb, 0, NULL },
+ { "/Windows/_Top dock", NULL, dock_cb, DOCK_TOP, NULL },
+ { "/Windows/_Bottom dock", NULL, dock_cb, DOCK_BOTTOM, NULL },
+ { "/Windows/_Left dock", NULL, dock_cb, DOCK_LEFT, NULL },
+ { "/Windows/_Right dock", NULL, dock_cb, DOCK_RIGHT, NULL },
+ { "/Windows/_All docks", NULL, dock_cb, DOCK_ALL, NULL },
+ { "/Windows/Des_ktop", NULL, desktop_cb, 0, NULL },
+ { "/Windows/Me_nu", NULL, menu_cb, 0, NULL },
+ { "/Windows/Tool_bar", NULL, toolbar_cb, 0, NULL },
+ { "/Windows/Override Redirect", NULL, override_redirect_cb, 0, NULL },
+ { "/Windows/Border Only", NULL, border_only_cb, 0, NULL }
+};
+
+static void
+sleep_cb (GtkWidget *button,
+ gpointer data)
+{
+ sleep (1000);
+}
+
+static void
+toggle_aspect_ratio (GtkWidget *button,
+ gpointer data)
+{
+ GtkWidget *window;
+ GdkGeometry geom;
+
+ if (aspect_on)
+ {
+ geom.min_aspect = 0;
+ geom.max_aspect = 65535;
+ }
+ else
+ {
+ geom.min_aspect = 1.777778;
+ geom.max_aspect = 1.777778;
+ }
+
+ aspect_on = !aspect_on;
+
+ window = gtk_widget_get_ancestor (button, GTK_TYPE_WINDOW);
+ if (window)
+ gtk_window_set_geometry_hints (GTK_WINDOW (window),
+ GTK_WIDGET (data),
+ &geom,
+ GDK_HINT_ASPECT);
+
+}
+
+static void
+toggle_decorated_cb (GtkWidget *button,
+ gpointer data)
+{
+ GtkWidget *window;
+ window = gtk_widget_get_ancestor (button, GTK_TYPE_WINDOW);
+ if (window)
+ gtk_window_set_decorated (GTK_WINDOW (window),
+ !gtk_window_get_decorated (GTK_WINDOW (window)));
+}
+
+static void
+clicked_toolbar_cb (GtkWidget *button,
+ gpointer data)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (data),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CLOSE,
+ "Clicking the toolbar buttons doesn't do anything");
+
+ /* Close dialog on user response */
+ g_signal_connect (G_OBJECT (dialog),
+ "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+update_statusbar (GtkTextBuffer *buffer,
+ GtkStatusbar *statusbar)
+{
+ gchar *msg;
+ gint row, col;
+ gint count;
+ GtkTextIter iter;
+
+ gtk_statusbar_pop (statusbar, 0); /* clear any previous message, underflow is allowed */
+
+ count = gtk_text_buffer_get_char_count (buffer);
+
+ gtk_text_buffer_get_iter_at_mark (buffer,
+ &iter,
+ gtk_text_buffer_get_insert (buffer));
+
+ row = gtk_text_iter_get_line (&iter);
+ col = gtk_text_iter_get_line_offset (&iter);
+
+ msg = g_strdup_printf ("Cursor at row %d column %d - %d chars in document",
+ row, col, count);
+
+ gtk_statusbar_push (statusbar, 0, msg);
+
+ g_free (msg);
+}
+
+static void
+mark_set_callback (GtkTextBuffer *buffer,
+ const GtkTextIter *new_location,
+ GtkTextMark *mark,
+ gpointer data)
+{
+ update_statusbar (buffer, GTK_STATUSBAR (data));
+}
+
+static int window_count = 0;
+
+static void
+destroy_cb (GtkWidget *w, gpointer data)
+{
+ --window_count;
+ if (window_count == 0)
+ gtk_main_quit ();
+}
+
+static GtkWidget *
+do_appwindow (void)
+{
+ GtkWidget *window;
+ GtkWidget *table;
+ GtkWidget *toolbar;
+ GtkWidget *handlebox;
+ GtkWidget *statusbar;
+ GtkWidget *contents;
+ GtkWidget *sw;
+ GtkTextBuffer *buffer;
+ GtkAccelGroup *accel_group;
+ GtkItemFactory *item_factory;
+
+
+ /* Create the toplevel window
+ */
+
+ ++window_count;
+
+ aspect_on = FALSE;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), "Application Window");
+
+ g_signal_connect (G_OBJECT (window), "destroy",
+ G_CALLBACK (destroy_cb), NULL);
+
+ table = gtk_table_new (1, 4, FALSE);
+
+ gtk_container_add (GTK_CONTAINER (window), table);
+
+ /* Create the menubar
+ */
+
+ accel_group = gtk_accel_group_new ();
+ gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);
+ g_object_unref (accel_group);
+
+ item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", accel_group);
+
+ /* Set up item factory to go away with the window */
+ g_object_ref (item_factory);
+ g_object_ref_sink (item_factory);
+ g_object_unref (item_factory);
+ g_object_set_data_full (G_OBJECT (window),
+ "<main>",
+ item_factory,
+ (GDestroyNotify) g_object_unref);
+
+ /* create menu items */
+ gtk_item_factory_create_items (item_factory, G_N_ELEMENTS (menu_items),
+ menu_items, window);
+
+ gtk_table_attach (GTK_TABLE (table),
+ gtk_item_factory_get_widget (item_factory, "<main>"),
+ /* X direction */ /* Y direction */
+ 0, 1, 0, 1,
+ GTK_EXPAND | GTK_FILL, 0,
+ 0, 0);
+
+ /* Create document
+ */
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_IN);
+
+ gtk_table_attach (GTK_TABLE (table),
+ sw,
+ /* X direction */ /* Y direction */
+ 0, 1, 2, 3,
+ GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
+ 0, 0);
+
+ gtk_window_set_default_size (GTK_WINDOW (window),
+ 200, 200);
+
+ contents = gtk_text_view_new ();
+ gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (contents),
+ PANGO_WRAP_WORD);
+
+ gtk_container_add (GTK_CONTAINER (sw),
+ contents);
+
+ /* Create the toolbar
+ */
+ toolbar = gtk_toolbar_new ();
+
+ GtkToolItem *newButton = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
+ gtk_tool_item_set_tooltip_text(newButton,
+ "Open another one of these windows");
+ g_signal_connect(G_OBJECT(newButton),
+ "clicked",
+ G_CALLBACK(do_appwindow),
+ window);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar),
+ newButton,
+ -1); /*-1 means append to end of toolbar*/
+
+
+ GtkToolItem *lockButton = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN);
+ gtk_tool_item_set_tooltip_text(lockButton,
+ "This is a demo button that locks up the demo");
+ g_signal_connect(G_OBJECT(lockButton),
+ "clicked",
+ G_CALLBACK(sleep_cb),
+ window);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar),
+ lockButton,
+ -1); /*-1 means append to end of toolbar*/
+
+
+ GtkToolItem *decoButton = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN);
+ gtk_tool_item_set_tooltip_text(decoButton,
+ "This is a demo button that toggles window decorations");
+ g_signal_connect(G_OBJECT(decoButton),
+ "clicked",
+ G_CALLBACK(toggle_decorated_cb),
+ window);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar),
+ decoButton,
+ -1); /*-1 means append to end of toolbar*/
+
+ GtkToolItem *lockRatioButton = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN);
+ gtk_tool_item_set_tooltip_text(lockRatioButton,
+ "This is a demo button that locks the aspect ratio using a hint");
+ g_signal_connect(G_OBJECT(lockRatioButton),
+ "clicked",
+ G_CALLBACK(toggle_aspect_ratio),
+ window);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar),
+ lockRatioButton,
+ -1); /*-1 means append to end of toolbar*/
+
+ GtkToolItem *quitButton = gtk_tool_button_new_from_stock(GTK_STOCK_QUIT);
+ gtk_tool_item_set_tooltip_text(quitButton,
+ "This is a demo button with a 'quit' icon");
+ g_signal_connect(G_OBJECT(quitButton),
+ "clicked",
+ G_CALLBACK(clicked_toolbar_cb),
+ window);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar),
+ quitButton,
+ -1); /*-1 means append to end of toolbar*/
+
+ handlebox = gtk_handle_box_new ();
+
+ gtk_container_add (GTK_CONTAINER (handlebox), toolbar);
+
+ gtk_table_attach (GTK_TABLE (table),
+ handlebox,
+ /* X direction */ /* Y direction */
+ 0, 1, 1, 2,
+ GTK_EXPAND | GTK_FILL, 0,
+ 0, 0);
+
+ /* Create statusbar */
+
+ statusbar = gtk_statusbar_new ();
+ gtk_table_attach (GTK_TABLE (table),
+ statusbar,
+ /* X direction */ /* Y direction */
+ 0, 1, 3, 4,
+ GTK_EXPAND | GTK_FILL, 0,
+ 0, 0);
+
+ /* Show text widget info in the statusbar */
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (contents));
+
+ gtk_text_buffer_set_text (buffer,
+ "This demo demonstrates various kinds of windows that "
+ "window managers and window manager themes should handle. "
+ "Be sure to tear off the menu and toolbar, those are also "
+ "a special kind of window.",
+ -1);
+
+ g_signal_connect_object (buffer,
+ "changed",
+ G_CALLBACK (update_statusbar),
+ statusbar,
+ 0);
+
+ g_signal_connect_object (buffer,
+ "mark_set", /* cursor moved */
+ G_CALLBACK (mark_set_callback),
+ statusbar,
+ 0);
+
+ update_statusbar (buffer, GTK_STATUSBAR (statusbar));
+
+ gtk_widget_show_all (window);
+
+ return window;
+}
+
+
diff --git a/src/tools/marco-window-demo.png b/src/tools/marco-window-demo.png
new file mode 100644
index 00000000..d87f8296
--- /dev/null
+++ b/src/tools/marco-window-demo.png
Binary files differ
diff --git a/src/ui/draw-workspace.c b/src/ui/draw-workspace.c
new file mode 100644
index 00000000..b1a7c733
--- /dev/null
+++ b/src/ui/draw-workspace.c
@@ -0,0 +1,229 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Draw a workspace */
+
+/* This file should not be modified to depend on other files in
+ * libwnck or marco, since it's used in both of them
+ */
+
+/*
+ * Copyright (C) 2002 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "draw-workspace.h"
+
+
+static void
+get_window_rect (const WnckWindowDisplayInfo *win,
+ int screen_width,
+ int screen_height,
+ const GdkRectangle *workspace_rect,
+ GdkRectangle *rect)
+{
+ double width_ratio, height_ratio;
+ int x, y, width, height;
+
+ width_ratio = (double) workspace_rect->width / (double) screen_width;
+ height_ratio = (double) workspace_rect->height / (double) screen_height;
+
+ x = win->x;
+ y = win->y;
+ width = win->width;
+ height = win->height;
+
+ x *= width_ratio;
+ y *= height_ratio;
+ width *= width_ratio;
+ height *= height_ratio;
+
+ x += workspace_rect->x;
+ y += workspace_rect->y;
+
+ if (width < 3)
+ width = 3;
+ if (height < 3)
+ height = 3;
+
+ rect->x = x;
+ rect->y = y;
+ rect->width = width;
+ rect->height = height;
+}
+
+static void
+draw_window (GtkWidget *widget,
+ GdkDrawable *drawable,
+ const WnckWindowDisplayInfo *win,
+ const GdkRectangle *winrect,
+ GtkStateType state)
+{
+ cairo_t *cr;
+ GdkPixbuf *icon;
+ int icon_x, icon_y, icon_w, icon_h;
+ gboolean is_active;
+ GdkColor *color;
+ GtkStyle *style;
+
+ is_active = win->is_active;
+
+ cr = gdk_cairo_create (drawable);
+ cairo_rectangle (cr, winrect->x, winrect->y, winrect->width, winrect->height);
+ cairo_clip (cr);
+
+ style = gtk_widget_get_style (widget);
+ if (is_active)
+ color = &style->light[state];
+ else
+ color = &style->bg[state];
+ cairo_set_source_rgb (cr,
+ color->red / 65535.,
+ color->green / 65535.,
+ color->blue / 65535.);
+
+ cairo_rectangle (cr,
+ winrect->x + 1, winrect->y + 1,
+ MAX (0, winrect->width - 2), MAX (0, winrect->height - 2));
+ cairo_fill (cr);
+
+
+ icon = win->icon;
+
+ icon_w = icon_h = 0;
+
+ if (icon)
+ {
+ icon_w = gdk_pixbuf_get_width (icon);
+ icon_h = gdk_pixbuf_get_height (icon);
+
+ /* If the icon is too big, fall back to mini icon.
+ * We don't arbitrarily scale the icon, because it's
+ * just too slow on my Athlon 850.
+ */
+ if (icon_w > (winrect->width - 2) ||
+ icon_h > (winrect->height - 2))
+ {
+ icon = win->mini_icon;
+ if (icon)
+ {
+ icon_w = gdk_pixbuf_get_width (icon);
+ icon_h = gdk_pixbuf_get_height (icon);
+
+ /* Give up. */
+ if (icon_w > (winrect->width - 2) ||
+ icon_h > (winrect->height - 2))
+ icon = NULL;
+ }
+ }
+ }
+
+ if (icon)
+ {
+ icon_x = winrect->x + (winrect->width - icon_w) / 2;
+ icon_y = winrect->y + (winrect->height - icon_h) / 2;
+
+ cairo_save (cr);
+ gdk_cairo_set_source_pixbuf (cr, icon, icon_x, icon_y);
+ cairo_rectangle (cr, icon_x, icon_y, icon_w, icon_h);
+ cairo_clip (cr);
+ cairo_paint (cr);
+ cairo_restore (cr);
+ }
+
+ if (is_active)
+ color = &style->fg[state];
+ else
+ color = &style->fg[state];
+
+ cairo_set_source_rgb (cr,
+ color->red / 65535.,
+ color->green / 65535.,
+ color->blue / 65535.);
+ cairo_set_line_width (cr, 1.0);
+ cairo_rectangle (cr,
+ winrect->x + 0.5, winrect->y + 0.5,
+ MAX (0, winrect->width - 1), MAX (0, winrect->height - 1));
+ cairo_stroke (cr);
+
+ cairo_destroy (cr);
+}
+
+void
+wnck_draw_workspace (GtkWidget *widget,
+ GdkDrawable *drawable,
+ int x,
+ int y,
+ int width,
+ int height,
+ int screen_width,
+ int screen_height,
+ GdkPixbuf *workspace_background,
+ gboolean is_active,
+ const WnckWindowDisplayInfo *windows,
+ int n_windows)
+{
+ int i;
+ GdkRectangle workspace_rect;
+ GtkStateType state;
+ cairo_t *cr;
+
+ workspace_rect.x = x;
+ workspace_rect.y = y;
+ workspace_rect.width = width;
+ workspace_rect.height = height;
+
+ if (is_active)
+ state = GTK_STATE_SELECTED;
+ else if (workspace_background)
+ state = GTK_STATE_PRELIGHT;
+ else
+ state = GTK_STATE_NORMAL;
+
+ cr = gdk_cairo_create (drawable);
+
+ if (workspace_background)
+ {
+ gdk_cairo_set_source_pixbuf (cr, workspace_background, x, y);
+ cairo_paint (cr);
+ }
+ else
+ {
+ gdk_cairo_set_source_color (cr, &gtk_widget_get_style (widget)->dark[state]);
+ cairo_rectangle (cr, x, y, width, height);
+ cairo_fill (cr);
+ }
+
+ cairo_destroy (cr);
+
+ i = 0;
+ while (i < n_windows)
+ {
+ const WnckWindowDisplayInfo *win = &windows[i];
+ GdkRectangle winrect;
+
+ get_window_rect (win, screen_width,
+ screen_height, &workspace_rect, &winrect);
+
+ draw_window (widget,
+ drawable,
+ win,
+ &winrect,
+ state);
+
+ ++i;
+ }
+}
diff --git a/src/ui/draw-workspace.h b/src/ui/draw-workspace.h
new file mode 100644
index 00000000..a8b77cab
--- /dev/null
+++ b/src/ui/draw-workspace.h
@@ -0,0 +1,61 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Draw a workspace */
+
+/* This file should not be modified to depend on other files in
+ * libwnck or marco, since it's used in both of them
+ */
+
+/*
+ * Copyright (C) 2002 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef WNCK_DRAW_WORKSPACE_H
+#define WNCK_DRAW_WORKSPACE_H
+
+#include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+
+typedef struct
+{
+ GdkPixbuf *icon;
+ GdkPixbuf *mini_icon;
+ int x;
+ int y;
+ int width;
+ int height;
+
+ guint is_active : 1;
+
+} WnckWindowDisplayInfo;
+
+void wnck_draw_workspace (GtkWidget *widget,
+ GdkDrawable *drawable,
+ int x,
+ int y,
+ int width,
+ int height,
+ int screen_width,
+ int screen_height,
+ GdkPixbuf *workspace_background,
+ gboolean is_active,
+ const WnckWindowDisplayInfo *windows,
+ int n_windows);
+
+#endif
diff --git a/src/ui/fixedtip.c b/src/ui/fixedtip.c
new file mode 100644
index 00000000..93370636
--- /dev/null
+++ b/src/ui/fixedtip.c
@@ -0,0 +1,133 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco fixed tooltip routine */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "fixedtip.h"
+#include "ui.h"
+
+/**
+ * The floating rectangle. This is a GtkWindow, and it contains
+ * the "label" widget, below.
+ */
+static GtkWidget *tip = NULL;
+
+/**
+ * The actual text that gets displayed.
+ */
+static GtkWidget *label = NULL;
+/*
+ * X coordinate of the right-hand edge of the screen.
+ *
+ * \bug This appears to be a bug; screen_right_edge is calculated only when
+ * the window is redrawn. Actually we should never cache it because
+ * different windows are different sizes.
+ */
+static int screen_right_edge = 0;
+/*
+ * Y coordinate of the bottom edge of the screen.
+ *
+ * \bug As with screen_right_edge.
+ */
+static int screen_bottom_edge = 0;
+
+static gint
+expose_handler (GtkTooltips *tooltips)
+{
+ gtk_paint_flat_box (gtk_widget_get_style (tip), gtk_widget_get_window (tip),
+ GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ NULL, tip, "tooltip",
+ 0, 0, -1, -1);
+
+ return FALSE;
+}
+
+void
+meta_fixed_tip_show (Display *xdisplay, int screen_number,
+ int root_x, int root_y,
+ const char *markup_text)
+{
+ int w, h;
+
+ if (tip == NULL)
+ {
+ tip = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_window_set_type_hint (GTK_WINDOW(tip), GDK_WINDOW_TYPE_HINT_TOOLTIP);
+
+ {
+ GdkScreen *gdk_screen;
+ GdkRectangle monitor;
+ gint mon_num;
+
+ gdk_screen = gdk_display_get_screen (gdk_display_get_default (),
+ screen_number);
+ gtk_window_set_screen (GTK_WINDOW (tip),
+ gdk_screen);
+ mon_num = gdk_screen_get_monitor_at_point (gdk_screen, root_x, root_y);
+ gdk_screen_get_monitor_geometry (gdk_screen, mon_num, &monitor);
+ screen_right_edge = monitor.x + monitor.width;
+ screen_bottom_edge = monitor.y + monitor.height;
+ }
+
+ gtk_widget_set_app_paintable (tip, TRUE);
+ gtk_window_set_resizable (GTK_WINDOW (tip), FALSE);
+ gtk_widget_set_name (tip, "gtk-tooltips");
+ gtk_container_set_border_width (GTK_CONTAINER (tip), 4);
+
+ g_signal_connect_swapped (tip, "expose_event",
+ G_CALLBACK (expose_handler), NULL);
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5);
+ gtk_widget_show (label);
+
+ gtk_container_add (GTK_CONTAINER (tip), label);
+
+ g_signal_connect (tip, "destroy",
+ G_CALLBACK (gtk_widget_destroyed), &tip);
+ }
+
+ gtk_label_set_markup (GTK_LABEL (label), markup_text);
+
+ gtk_window_get_size (GTK_WINDOW (tip), &w, &h);
+
+ if (meta_ui_get_direction() == META_UI_DIRECTION_RTL)
+ root_x = MAX(0, root_x - w);
+
+ if ((root_x + w) > screen_right_edge)
+ root_x -= (root_x + w) - screen_right_edge;
+
+ gtk_window_move (GTK_WINDOW (tip), root_x, root_y);
+
+ gtk_widget_show (tip);
+}
+
+void
+meta_fixed_tip_hide (void)
+{
+ if (tip)
+ {
+ gtk_widget_destroy (tip);
+ tip = NULL;
+ }
+}
diff --git a/src/ui/fixedtip.h b/src/ui/fixedtip.h
new file mode 100644
index 00000000..28a618d9
--- /dev/null
+++ b/src/ui/fixedtip.h
@@ -0,0 +1,69 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/**
+ * \file fixedtip.h Marco fixed tooltip routine
+ *
+ * Sometimes we want to display a small floating rectangle with helpful
+ * text near the pointer. For example, if the user holds the mouse over
+ * the maximise button, we can display a tooltip saying "Maximize".
+ * The text is localised, of course.
+ *
+ * This file contains the functions to create and delete these tooltips.
+ *
+ * \todo Since we now consider MetaDisplay a singleton, there can be
+ * only one tooltip per display; this might quite simply live in
+ * display.c. Alternatively, it could move to frames.c, which
+ * is the only place this business is called anyway.
+ *
+ * \todo Apparently some UI needs changing (check bugzilla)
+ */
+
+#ifndef META_FIXED_TIP_H
+#define META_FIXED_TIP_H
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+
+/**
+ * Displays a tooltip. There can be only one across the entire system.
+ * This function behaves identically whether or not a tooltip is already
+ * displayed, but if it is the window will be reused rather than destroyed
+ * and recreated.
+ *
+ * \param xdisplay An X display.
+ * \param screen_number The number of the screen.
+ * \param root_x The X coordinate where the tooltip should appear
+ * \param root_y The Y coordinate where the tooltip should appear
+ * \param markup_text Text to display in the tooltip; can contain markup
+ */
+void meta_fixed_tip_show (Display *xdisplay, int screen_number,
+ int root_x, int root_y,
+ const char *markup_text);
+
+/**
+ * Removes the tooltip that was created by meta_fixed_tip_show(). If there
+ * is no tooltip currently visible, this is a no-op.
+ */
+void meta_fixed_tip_hide (void);
+
+
+#endif
diff --git a/src/ui/frames.c b/src/ui/frames.c
new file mode 100644
index 00000000..bb4c863c
--- /dev/null
+++ b/src/ui/frames.c
@@ -0,0 +1,2940 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco window frame manager widget */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2005, 2006 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include "boxes.h"
+#include "frames.h"
+#include "util.h"
+#include "core.h"
+#include "menu.h"
+#include "fixedtip.h"
+#include "theme.h"
+#include "prefs.h"
+#include "ui.h"
+
+#ifdef HAVE_SHAPE
+#include <X11/extensions/shape.h>
+#endif
+
+#define DEFAULT_INNER_BUTTON_BORDER 3
+
+static void meta_frames_class_init (MetaFramesClass *klass);
+static void meta_frames_init (MetaFrames *frames);
+static void meta_frames_destroy (GtkObject *object);
+static void meta_frames_finalize (GObject *object);
+static void meta_frames_style_set (GtkWidget *widget,
+ GtkStyle *prev_style);
+static void meta_frames_realize (GtkWidget *widget);
+static void meta_frames_unrealize (GtkWidget *widget);
+
+static void meta_frames_update_prelit_control (MetaFrames *frames,
+ MetaUIFrame *frame,
+ MetaFrameControl control);
+static gboolean meta_frames_button_press_event (GtkWidget *widget,
+ GdkEventButton *event);
+static gboolean meta_frames_button_release_event (GtkWidget *widget,
+ GdkEventButton *event);
+static gboolean meta_frames_motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event);
+static gboolean meta_frames_destroy_event (GtkWidget *widget,
+ GdkEventAny *event);
+static gboolean meta_frames_expose_event (GtkWidget *widget,
+ GdkEventExpose *event);
+static gboolean meta_frames_enter_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event);
+static gboolean meta_frames_leave_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event);
+
+static void meta_frames_attach_style (MetaFrames *frames,
+ MetaUIFrame *frame);
+
+static void meta_frames_paint_to_drawable (MetaFrames *frames,
+ MetaUIFrame *frame,
+ GdkDrawable *drawable,
+ GdkRegion *region,
+ int x_offset,
+ int y_offset);
+
+static void meta_frames_set_window_background (MetaFrames *frames,
+ MetaUIFrame *frame);
+
+static void meta_frames_calc_geometry (MetaFrames *frames,
+ MetaUIFrame *frame,
+ MetaFrameGeometry *fgeom);
+
+static void meta_frames_ensure_layout (MetaFrames *frames,
+ MetaUIFrame *frame);
+
+static MetaUIFrame* meta_frames_lookup_window (MetaFrames *frames,
+ Window xwindow);
+
+static void meta_frames_font_changed (MetaFrames *frames);
+static void meta_frames_button_layout_changed (MetaFrames *frames);
+
+
+static GdkRectangle* control_rect (MetaFrameControl control,
+ MetaFrameGeometry *fgeom);
+static MetaFrameControl get_control (MetaFrames *frames,
+ MetaUIFrame *frame,
+ int x,
+ int y);
+static void clear_tip (MetaFrames *frames);
+static void invalidate_all_caches (MetaFrames *frames);
+static void invalidate_whole_window (MetaFrames *frames,
+ MetaUIFrame *frame);
+
+static GtkWidgetClass *parent_class = NULL;
+
+GType
+meta_frames_get_type (void)
+{
+ static GType frames_type = 0;
+
+ if (!frames_type)
+ {
+ static const GtkTypeInfo frames_info =
+ {
+ "MetaFrames",
+ sizeof (MetaFrames),
+ sizeof (MetaFramesClass),
+ (GtkClassInitFunc) meta_frames_class_init,
+ (GtkObjectInitFunc) meta_frames_init,
+ /* reserved_1 */ NULL,
+ /* reserved_2 */ NULL,
+ (GtkClassInitFunc) NULL,
+ };
+
+ frames_type = gtk_type_unique (GTK_TYPE_WINDOW, &frames_info);
+ }
+
+ return frames_type;
+}
+
+static void
+meta_frames_class_init (MetaFramesClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ object_class = (GtkObjectClass*) class;
+ widget_class = (GtkWidgetClass*) class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gobject_class->finalize = meta_frames_finalize;
+ object_class->destroy = meta_frames_destroy;
+
+ widget_class->style_set = meta_frames_style_set;
+
+ widget_class->realize = meta_frames_realize;
+ widget_class->unrealize = meta_frames_unrealize;
+
+ widget_class->expose_event = meta_frames_expose_event;
+ widget_class->destroy_event = meta_frames_destroy_event;
+ widget_class->button_press_event = meta_frames_button_press_event;
+ widget_class->button_release_event = meta_frames_button_release_event;
+ widget_class->motion_notify_event = meta_frames_motion_notify_event;
+ widget_class->enter_notify_event = meta_frames_enter_notify_event;
+ widget_class->leave_notify_event = meta_frames_leave_notify_event;
+}
+
+static gint
+unsigned_long_equal (gconstpointer v1,
+ gconstpointer v2)
+{
+ return *((const gulong*) v1) == *((const gulong*) v2);
+}
+
+static guint
+unsigned_long_hash (gconstpointer v)
+{
+ gulong val = * (const gulong *) v;
+
+ /* I'm not sure this works so well. */
+#if GLIB_SIZEOF_LONG > 4
+ return (guint) (val ^ (val >> 32));
+#else
+ return val;
+#endif
+}
+
+static void
+prefs_changed_callback (MetaPreference pref,
+ void *data)
+{
+ switch (pref)
+ {
+ case META_PREF_TITLEBAR_FONT:
+ meta_frames_font_changed (META_FRAMES (data));
+ break;
+ case META_PREF_BUTTON_LAYOUT:
+ meta_frames_button_layout_changed (META_FRAMES (data));
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+meta_frames_init (MetaFrames *frames)
+{
+ GTK_WINDOW (frames)->type = GTK_WINDOW_POPUP;
+
+ frames->text_heights = g_hash_table_new (NULL, NULL);
+
+ frames->frames = g_hash_table_new (unsigned_long_hash, unsigned_long_equal);
+
+ frames->tooltip_timeout = 0;
+
+ frames->expose_delay_count = 0;
+
+ frames->invalidate_cache_timeout_id = 0;
+ frames->invalidate_frames = NULL;
+ frames->cache = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ gtk_widget_set_double_buffered (GTK_WIDGET (frames), FALSE);
+
+ meta_prefs_add_listener (prefs_changed_callback, frames);
+}
+
+static void
+listify_func (gpointer key, gpointer value, gpointer data)
+{
+ GSList **listp;
+
+ listp = data;
+ *listp = g_slist_prepend (*listp, value);
+}
+
+static void
+meta_frames_destroy (GtkObject *object)
+{
+ GSList *winlist;
+ GSList *tmp;
+ MetaFrames *frames;
+
+ frames = META_FRAMES (object);
+
+ clear_tip (frames);
+
+ winlist = NULL;
+ g_hash_table_foreach (frames->frames, listify_func, &winlist);
+
+ /* Unmanage all frames */
+ for (tmp = winlist; tmp != NULL; tmp = tmp->next)
+ {
+ MetaUIFrame *frame;
+
+ frame = tmp->data;
+
+ meta_frames_unmanage_window (frames, frame->xwindow);
+ }
+ g_slist_free (winlist);
+
+ GTK_OBJECT_CLASS (parent_class)->destroy (object);
+}
+
+static void
+meta_frames_finalize (GObject *object)
+{
+ MetaFrames *frames;
+
+ frames = META_FRAMES (object);
+
+ meta_prefs_remove_listener (prefs_changed_callback, frames);
+
+ g_hash_table_destroy (frames->text_heights);
+
+ invalidate_all_caches (frames);
+ if (frames->invalidate_cache_timeout_id)
+ g_source_remove (frames->invalidate_cache_timeout_id);
+
+ g_assert (g_hash_table_size (frames->frames) == 0);
+ g_hash_table_destroy (frames->frames);
+ g_hash_table_destroy (frames->cache);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+typedef struct
+{
+ MetaRectangle rect;
+ GdkPixmap *pixmap;
+} CachedFramePiece;
+
+typedef struct
+{
+ /* Caches of the four rendered sides in a MetaFrame.
+ * Order: top (titlebar), left, right, bottom.
+ */
+ CachedFramePiece piece[4];
+} CachedPixels;
+
+static CachedPixels *
+get_cache (MetaFrames *frames,
+ MetaUIFrame *frame)
+{
+ CachedPixels *pixels;
+
+ pixels = g_hash_table_lookup (frames->cache, frame);
+
+ if (!pixels)
+ {
+ pixels = g_new0 (CachedPixels, 1);
+ g_hash_table_insert (frames->cache, frame, pixels);
+ }
+
+ return pixels;
+}
+
+static void
+invalidate_cache (MetaFrames *frames,
+ MetaUIFrame *frame)
+{
+ CachedPixels *pixels = get_cache (frames, frame);
+ int i;
+
+ for (i = 0; i < 4; i++)
+ if (pixels->piece[i].pixmap)
+ g_object_unref (pixels->piece[i].pixmap);
+
+ g_free (pixels);
+ g_hash_table_remove (frames->cache, frame);
+}
+
+static void
+invalidate_all_caches (MetaFrames *frames)
+{
+ GList *l;
+
+ for (l = frames->invalidate_frames; l; l = l->next)
+ {
+ MetaUIFrame *frame = l->data;
+
+ invalidate_cache (frames, frame);
+ }
+
+ g_list_free (frames->invalidate_frames);
+ frames->invalidate_frames = NULL;
+}
+
+static gboolean
+invalidate_cache_timeout (gpointer data)
+{
+ MetaFrames *frames = data;
+
+ invalidate_all_caches (frames);
+ frames->invalidate_cache_timeout_id = 0;
+ return FALSE;
+}
+
+static void
+queue_recalc_func (gpointer key, gpointer value, gpointer data)
+{
+ MetaUIFrame *frame;
+ MetaFrames *frames;
+
+ frames = META_FRAMES (data);
+ frame = value;
+
+ /* If a resize occurs it will cause a redraw, but the
+ * resize may not actually be needed so we always redraw
+ * in case of color change.
+ */
+ meta_frames_set_window_background (frames, frame);
+
+ invalidate_whole_window (frames, frame);
+ meta_core_queue_frame_resize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow);
+ if (frame->layout)
+ {
+ /* save title to recreate layout */
+ g_free (frame->title);
+
+ frame->title = g_strdup (pango_layout_get_text (frame->layout));
+
+ g_object_unref (G_OBJECT (frame->layout));
+ frame->layout = NULL;
+ }
+}
+
+static void
+meta_frames_font_changed (MetaFrames *frames)
+{
+ if (g_hash_table_size (frames->text_heights) > 0)
+ {
+ g_hash_table_destroy (frames->text_heights);
+ frames->text_heights = g_hash_table_new (NULL, NULL);
+ }
+
+ /* Queue a draw/resize on all frames */
+ g_hash_table_foreach (frames->frames,
+ queue_recalc_func, frames);
+
+}
+
+static void
+queue_draw_func (gpointer key, gpointer value, gpointer data)
+{
+ MetaUIFrame *frame;
+ MetaFrames *frames;
+
+ frames = META_FRAMES (data);
+ frame = value;
+
+ /* If a resize occurs it will cause a redraw, but the
+ * resize may not actually be needed so we always redraw
+ * in case of color change.
+ */
+ meta_frames_set_window_background (frames, frame);
+
+ invalidate_whole_window (frames, frame);
+}
+
+static void
+meta_frames_button_layout_changed (MetaFrames *frames)
+{
+ g_hash_table_foreach (frames->frames,
+ queue_draw_func, frames);
+}
+
+static void
+reattach_style_func (gpointer key, gpointer value, gpointer data)
+{
+ MetaUIFrame *frame;
+ MetaFrames *frames;
+
+ frames = META_FRAMES (data);
+ frame = value;
+
+ meta_frames_attach_style (frames, frame);
+}
+
+static void
+meta_frames_style_set (GtkWidget *widget,
+ GtkStyle *prev_style)
+{
+ MetaFrames *frames;
+
+ frames = META_FRAMES (widget);
+
+ meta_frames_font_changed (frames);
+
+ g_hash_table_foreach (frames->frames,
+ reattach_style_func, frames);
+
+ GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style);
+}
+
+static void
+meta_frames_ensure_layout (MetaFrames *frames,
+ MetaUIFrame *frame)
+{
+ GtkWidget *widget;
+ MetaFrameFlags flags;
+ MetaFrameType type;
+ MetaFrameStyle *style;
+
+ g_return_if_fail (GTK_WIDGET_REALIZED (frames));
+
+ widget = GTK_WIDGET (frames);
+
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_FRAME_TYPE, &type,
+ META_CORE_GET_END);
+
+ style = meta_theme_get_frame_style (meta_theme_get_current (),
+ type, flags);
+
+ if (style != frame->cache_style)
+ {
+ if (frame->layout)
+ {
+ /* save title to recreate layout */
+ g_free (frame->title);
+
+ frame->title = g_strdup (pango_layout_get_text (frame->layout));
+
+ g_object_unref (G_OBJECT (frame->layout));
+ frame->layout = NULL;
+ }
+ }
+
+ frame->cache_style = style;
+
+ if (frame->layout == NULL)
+ {
+ gpointer key, value;
+ PangoFontDescription *font_desc;
+ double scale;
+ int size;
+
+ scale = meta_theme_get_title_scale (meta_theme_get_current (),
+ type,
+ flags);
+
+ frame->layout = gtk_widget_create_pango_layout (widget, frame->title);
+
+ pango_layout_set_auto_dir (frame->layout, FALSE);
+
+ font_desc = meta_gtk_widget_get_font_desc (widget, scale,
+ meta_prefs_get_titlebar_font ());
+
+ size = pango_font_description_get_size (font_desc);
+
+ if (g_hash_table_lookup_extended (frames->text_heights,
+ GINT_TO_POINTER (size),
+ &key, &value))
+ {
+ frame->text_height = GPOINTER_TO_INT (value);
+ }
+ else
+ {
+ frame->text_height =
+ meta_pango_font_desc_get_text_height (font_desc,
+ gtk_widget_get_pango_context (widget));
+
+ g_hash_table_replace (frames->text_heights,
+ GINT_TO_POINTER (size),
+ GINT_TO_POINTER (frame->text_height));
+ }
+
+ pango_layout_set_font_description (frame->layout,
+ font_desc);
+
+ pango_font_description_free (font_desc);
+
+ /* Save some RAM */
+ g_free (frame->title);
+ frame->title = NULL;
+ }
+}
+
+static void
+meta_frames_calc_geometry (MetaFrames *frames,
+ MetaUIFrame *frame,
+ MetaFrameGeometry *fgeom)
+{
+ int width, height;
+ MetaFrameFlags flags;
+ MetaFrameType type;
+ MetaButtonLayout button_layout;
+
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_CLIENT_WIDTH, &width,
+ META_CORE_GET_CLIENT_HEIGHT, &height,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_FRAME_TYPE, &type,
+ META_CORE_GET_END);
+
+ meta_frames_ensure_layout (frames, frame);
+
+ meta_prefs_get_button_layout (&button_layout);
+
+ meta_theme_calc_geometry (meta_theme_get_current (),
+ type,
+ frame->text_height,
+ flags,
+ width, height,
+ &button_layout,
+ fgeom);
+}
+
+MetaFrames*
+meta_frames_new (int screen_number)
+{
+ GdkScreen *screen;
+
+ screen = gdk_display_get_screen (gdk_display_get_default (),
+ screen_number);
+
+ return g_object_new (META_TYPE_FRAMES,
+ "screen", screen,
+ NULL);
+}
+
+/* In order to use a style with a window it has to be attached to that
+ * window. Actually, the colormaps just have to match, but since GTK+
+ * already takes care of making sure that its cheap to attach a style
+ * to multiple windows with the same colormap, we can just go ahead
+ * and attach separately for each window.
+ */
+static void
+meta_frames_attach_style (MetaFrames *frames,
+ MetaUIFrame *frame)
+{
+ if (frame->style != NULL)
+ gtk_style_detach (frame->style);
+
+ /* Weirdly, gtk_style_attach() steals a reference count from the style passed in */
+ g_object_ref (GTK_WIDGET (frames)->style);
+ frame->style = gtk_style_attach (GTK_WIDGET (frames)->style, frame->window);
+}
+
+void
+meta_frames_manage_window (MetaFrames *frames,
+ Window xwindow,
+ GdkWindow *window)
+{
+ MetaUIFrame *frame;
+
+ g_assert (window);
+
+ frame = g_new (MetaUIFrame, 1);
+
+ frame->window = window;
+
+ gdk_window_set_user_data (frame->window, frames);
+
+ frame->style = NULL;
+ meta_frames_attach_style (frames, frame);
+
+ /* Don't set event mask here, it's in frame.c */
+
+ frame->xwindow = xwindow;
+ frame->cache_style = NULL;
+ frame->layout = NULL;
+ frame->text_height = -1;
+ frame->title = NULL;
+ frame->expose_delayed = FALSE;
+ frame->shape_applied = FALSE;
+ frame->prelit_control = META_FRAME_CONTROL_NONE;
+
+ /* Don't set the window background yet; we need frame->xwindow to be
+ * registered with its MetaWindow, which happens after this function
+ * and meta_ui_create_frame_window() return to meta_window_ensure_frame().
+ */
+
+ meta_core_grab_buttons (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+
+ g_hash_table_replace (frames->frames, &frame->xwindow, frame);
+}
+
+void
+meta_frames_unmanage_window (MetaFrames *frames,
+ Window xwindow)
+{
+ MetaUIFrame *frame;
+
+ clear_tip (frames);
+
+ frame = g_hash_table_lookup (frames->frames, &xwindow);
+
+ if (frame)
+ {
+ /* invalidating all caches ensures the frame
+ * is not actually referenced anymore
+ */
+ invalidate_all_caches (frames);
+
+ /* restore the cursor */
+ meta_core_set_screen_cursor (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ META_CURSOR_DEFAULT);
+
+ gdk_window_set_user_data (frame->window, NULL);
+
+ if (frames->last_motion_frame == frame)
+ frames->last_motion_frame = NULL;
+
+ g_hash_table_remove (frames->frames, &frame->xwindow);
+
+ gtk_style_detach (frame->style);
+
+ gdk_window_destroy (frame->window);
+
+ if (frame->layout)
+ g_object_unref (G_OBJECT (frame->layout));
+
+ if (frame->title)
+ g_free (frame->title);
+
+ g_free (frame);
+ }
+ else
+ meta_warning ("Frame 0x%lx not managed, can't unmanage\n", xwindow);
+}
+
+static void
+meta_frames_realize (GtkWidget *widget)
+{
+ if (GTK_WIDGET_CLASS (parent_class)->realize)
+ GTK_WIDGET_CLASS (parent_class)->realize (widget);
+}
+
+static void
+meta_frames_unrealize (GtkWidget *widget)
+{
+ if (GTK_WIDGET_CLASS (parent_class)->unrealize)
+ GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+static MetaUIFrame*
+meta_frames_lookup_window (MetaFrames *frames,
+ Window xwindow)
+{
+ MetaUIFrame *frame;
+
+ frame = g_hash_table_lookup (frames->frames, &xwindow);
+
+ return frame;
+}
+
+void
+meta_frames_get_geometry (MetaFrames *frames,
+ Window xwindow,
+ int *top_height, int *bottom_height,
+ int *left_width, int *right_width)
+{
+ MetaFrameFlags flags;
+ MetaUIFrame *frame;
+ MetaFrameType type;
+
+ frame = meta_frames_lookup_window (frames, xwindow);
+
+ if (frame == NULL)
+ meta_bug ("No such frame 0x%lx\n", xwindow);
+
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_FRAME_TYPE, &type,
+ META_CORE_GET_END);
+
+ g_return_if_fail (type < META_FRAME_TYPE_LAST);
+
+ meta_frames_ensure_layout (frames, frame);
+
+ /* We can't get the full geometry, because that depends on
+ * the client window size and probably we're being called
+ * by the core move/resize code to decide on the client
+ * window size
+ */
+ meta_theme_get_frame_borders (meta_theme_get_current (),
+ type,
+ frame->text_height,
+ flags,
+ top_height, bottom_height,
+ left_width, right_width);
+}
+
+void
+meta_frames_reset_bg (MetaFrames *frames,
+ Window xwindow)
+{
+ MetaUIFrame *frame;
+
+ frame = meta_frames_lookup_window (frames, xwindow);
+
+ meta_frames_set_window_background (frames, frame);
+}
+
+static void
+set_background_none (Display *xdisplay,
+ Window xwindow)
+{
+ XSetWindowAttributes attrs;
+
+ attrs.background_pixmap = None;
+ XChangeWindowAttributes (xdisplay, xwindow,
+ CWBackPixmap, &attrs);
+}
+
+void
+meta_frames_unflicker_bg (MetaFrames *frames,
+ Window xwindow,
+ int target_width,
+ int target_height)
+{
+ MetaUIFrame *frame;
+
+ frame = meta_frames_lookup_window (frames, xwindow);
+ g_return_if_fail (frame != NULL);
+
+#if 0
+ pixmap = gdk_pixmap_new (frame->window,
+ width, height,
+ -1);
+
+ /* Oops, no way to get the background here */
+
+ meta_frames_paint_to_drawable (frames, frame, pixmap);
+#endif
+
+ set_background_none (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+}
+
+void
+meta_frames_apply_shapes (MetaFrames *frames,
+ Window xwindow,
+ int new_window_width,
+ int new_window_height,
+ gboolean window_has_shape)
+{
+#ifdef HAVE_SHAPE
+ /* Apply shapes as if window had new_window_width, new_window_height */
+ MetaUIFrame *frame;
+ MetaFrameGeometry fgeom;
+ XRectangle xrect;
+ Region corners_xregion;
+ Region window_xregion;
+
+ frame = meta_frames_lookup_window (frames, xwindow);
+ g_return_if_fail (frame != NULL);
+
+ meta_frames_calc_geometry (frames, frame, &fgeom);
+
+ if (!(fgeom.top_left_corner_rounded_radius != 0 ||
+ fgeom.top_right_corner_rounded_radius != 0 ||
+ fgeom.bottom_left_corner_rounded_radius != 0 ||
+ fgeom.bottom_right_corner_rounded_radius != 0 ||
+ window_has_shape))
+ {
+ if (frame->shape_applied)
+ {
+ meta_topic (META_DEBUG_SHAPES,
+ "Unsetting shape mask on frame 0x%lx\n",
+ frame->xwindow);
+
+ XShapeCombineMask (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ ShapeBounding, 0, 0, None, ShapeSet);
+ frame->shape_applied = FALSE;
+ }
+ else
+ {
+ meta_topic (META_DEBUG_SHAPES,
+ "Frame 0x%lx still doesn't need a shape mask\n",
+ frame->xwindow);
+ }
+
+ return; /* nothing to do */
+ }
+
+ corners_xregion = XCreateRegion ();
+
+ if (fgeom.top_left_corner_rounded_radius != 0)
+ {
+ const int corner = fgeom.top_left_corner_rounded_radius;
+ const float radius = sqrt(corner) + corner;
+ int i;
+
+ for (i=0; i<corner; i++)
+ {
+ const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
+ xrect.x = 0;
+ xrect.y = i;
+ xrect.width = width;
+ xrect.height = 1;
+
+ XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion);
+ }
+ }
+
+ if (fgeom.top_right_corner_rounded_radius != 0)
+ {
+ const int corner = fgeom.top_right_corner_rounded_radius;
+ const float radius = sqrt(corner) + corner;
+ int i;
+
+ for (i=0; i<corner; i++)
+ {
+ const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
+ xrect.x = new_window_width - width;
+ xrect.y = i;
+ xrect.width = width;
+ xrect.height = 1;
+
+ XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion);
+ }
+ }
+
+ if (fgeom.bottom_left_corner_rounded_radius != 0)
+ {
+ const int corner = fgeom.bottom_left_corner_rounded_radius;
+ const float radius = sqrt(corner) + corner;
+ int i;
+
+ for (i=0; i<corner; i++)
+ {
+ const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
+ xrect.x = 0;
+ xrect.y = new_window_height - i - 1;
+ xrect.width = width;
+ xrect.height = 1;
+
+ XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion);
+ }
+ }
+
+ if (fgeom.bottom_right_corner_rounded_radius != 0)
+ {
+ const int corner = fgeom.bottom_right_corner_rounded_radius;
+ const float radius = sqrt(corner) + corner;
+ int i;
+
+ for (i=0; i<corner; i++)
+ {
+ const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
+ xrect.x = new_window_width - width;
+ xrect.y = new_window_height - i - 1;
+ xrect.width = width;
+ xrect.height = 1;
+
+ XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion);
+ }
+ }
+
+ window_xregion = XCreateRegion ();
+
+ xrect.x = 0;
+ xrect.y = 0;
+ xrect.width = new_window_width;
+ xrect.height = new_window_height;
+
+ XUnionRectWithRegion (&xrect, window_xregion, window_xregion);
+
+ XSubtractRegion (window_xregion, corners_xregion, window_xregion);
+
+ XDestroyRegion (corners_xregion);
+
+ if (window_has_shape)
+ {
+ /* The client window is oclock or something and has a shape
+ * mask. To avoid a round trip to get its shape region, we
+ * create a fake window that's never mapped, build up our shape
+ * on that, then combine. Wasting the window is assumed cheaper
+ * than a round trip, but who really knows for sure.
+ */
+ XSetWindowAttributes attrs;
+ Window shape_window;
+ Window client_window;
+ Region client_xregion;
+ GdkScreen *screen;
+ int screen_number;
+
+ meta_topic (META_DEBUG_SHAPES,
+ "Frame 0x%lx needs to incorporate client shape\n",
+ frame->xwindow);
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (frames));
+ screen_number = gdk_x11_screen_get_screen_number (screen);
+
+ attrs.override_redirect = True;
+
+ shape_window = XCreateWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ RootWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), screen_number),
+ -5000, -5000,
+ new_window_width,
+ new_window_height,
+ 0,
+ CopyFromParent,
+ CopyFromParent,
+ (Visual *)CopyFromParent,
+ CWOverrideRedirect,
+ &attrs);
+
+ /* Copy the client's shape to the temporary shape_window */
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_CLIENT_XWINDOW, &client_window,
+ META_CORE_GET_END);
+
+ XShapeCombineShape (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), shape_window, ShapeBounding,
+ fgeom.left_width,
+ fgeom.top_height,
+ client_window,
+ ShapeBounding,
+ ShapeSet);
+
+ /* Punch the client area out of the normal frame shape,
+ * then union it with the shape_window's existing shape
+ */
+ client_xregion = XCreateRegion ();
+
+ xrect.x = fgeom.left_width;
+ xrect.y = fgeom.top_height;
+ xrect.width = new_window_width - fgeom.right_width - xrect.x;
+ xrect.height = new_window_height - fgeom.bottom_height - xrect.y;
+
+ XUnionRectWithRegion (&xrect, client_xregion, client_xregion);
+
+ XSubtractRegion (window_xregion, client_xregion, window_xregion);
+
+ XDestroyRegion (client_xregion);
+
+ XShapeCombineRegion (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), shape_window,
+ ShapeBounding, 0, 0, window_xregion, ShapeUnion);
+
+ /* Now copy shape_window shape to the real frame */
+ XShapeCombineShape (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, ShapeBounding,
+ 0, 0,
+ shape_window,
+ ShapeBounding,
+ ShapeSet);
+
+ XDestroyWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), shape_window);
+ }
+ else
+ {
+ /* No shape on the client, so just do simple stuff */
+
+ meta_topic (META_DEBUG_SHAPES,
+ "Frame 0x%lx has shaped corners\n",
+ frame->xwindow);
+
+ XShapeCombineRegion (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ ShapeBounding, 0, 0, window_xregion, ShapeSet);
+ }
+
+ frame->shape_applied = TRUE;
+
+ XDestroyRegion (window_xregion);
+#endif /* HAVE_SHAPE */
+}
+
+void
+meta_frames_move_resize_frame (MetaFrames *frames,
+ Window xwindow,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ MetaUIFrame *frame = meta_frames_lookup_window (frames, xwindow);
+ int old_x, old_y, old_width, old_height;
+
+ #if GTK_CHECK_VERSION(3, 0, 0)
+ old_width = gdk_window_get_width(GDK_WINDOW(frame->window));
+ old_height = gdk_window_get_height(GDK_WINDOW(frame->window));
+ #else
+ gdk_drawable_get_size(frame->window, &old_width, &old_height);
+ #endif
+
+ gdk_window_get_position (frame->window, &old_x, &old_y);
+
+ gdk_window_move_resize (frame->window, x, y, width, height);
+
+ if (old_width != width || old_height != height)
+ invalidate_whole_window (frames, frame);
+}
+
+void
+meta_frames_queue_draw (MetaFrames *frames,
+ Window xwindow)
+{
+ MetaUIFrame *frame;
+
+ frame = meta_frames_lookup_window (frames, xwindow);
+
+ invalidate_whole_window (frames, frame);
+}
+
+void
+meta_frames_set_title (MetaFrames *frames,
+ Window xwindow,
+ const char *title)
+{
+ MetaUIFrame *frame;
+
+ frame = meta_frames_lookup_window (frames, xwindow);
+
+ g_assert (frame);
+
+ g_free (frame->title);
+ frame->title = g_strdup (title);
+
+ if (frame->layout)
+ {
+ g_object_unref (frame->layout);
+ frame->layout = NULL;
+ }
+
+ invalidate_whole_window (frames, frame);
+}
+
+void
+meta_frames_repaint_frame (MetaFrames *frames,
+ Window xwindow)
+{
+ MetaUIFrame *frame;
+
+ frame = meta_frames_lookup_window (frames, xwindow);
+
+ g_assert (frame);
+
+ /* repaint everything, so the other frame don't
+ * lag behind if they are exposed
+ */
+ gdk_window_process_all_updates ();
+}
+
+static void
+show_tip_now (MetaFrames *frames)
+{
+ const char *tiptext;
+ MetaUIFrame *frame;
+ int x, y, root_x, root_y;
+ Window root, child;
+ guint mask;
+ MetaFrameControl control;
+
+ frame = frames->last_motion_frame;
+ if (frame == NULL)
+ return;
+
+ XQueryPointer (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ &root, &child,
+ &root_x, &root_y,
+ &x, &y,
+ &mask);
+
+ control = get_control (frames, frame, x, y);
+
+ tiptext = NULL;
+ switch (control)
+ {
+ case META_FRAME_CONTROL_TITLE:
+ break;
+ case META_FRAME_CONTROL_DELETE:
+ tiptext = _("Close Window");
+ break;
+ case META_FRAME_CONTROL_MENU:
+ tiptext = _("Window Menu");
+ break;
+ case META_FRAME_CONTROL_MINIMIZE:
+ tiptext = _("Minimize Window");
+ break;
+ case META_FRAME_CONTROL_MAXIMIZE:
+ tiptext = _("Maximize Window");
+ break;
+ case META_FRAME_CONTROL_UNMAXIMIZE:
+ tiptext = _("Restore Window");
+ break;
+ case META_FRAME_CONTROL_SHADE:
+ tiptext = _("Roll Up Window");
+ break;
+ case META_FRAME_CONTROL_UNSHADE:
+ tiptext = _("Unroll Window");
+ break;
+ case META_FRAME_CONTROL_ABOVE:
+ tiptext = _("Keep Window On Top");
+ break;
+ case META_FRAME_CONTROL_UNABOVE:
+ tiptext = _("Remove Window From Top");
+ break;
+ case META_FRAME_CONTROL_STICK:
+ tiptext = _("Always On Visible Workspace");
+ break;
+ case META_FRAME_CONTROL_UNSTICK:
+ tiptext = _("Put Window On Only One Workspace");
+ break;
+ case META_FRAME_CONTROL_RESIZE_SE:
+ break;
+ case META_FRAME_CONTROL_RESIZE_S:
+ break;
+ case META_FRAME_CONTROL_RESIZE_SW:
+ break;
+ case META_FRAME_CONTROL_RESIZE_N:
+ break;
+ case META_FRAME_CONTROL_RESIZE_NE:
+ break;
+ case META_FRAME_CONTROL_RESIZE_NW:
+ break;
+ case META_FRAME_CONTROL_RESIZE_W:
+ break;
+ case META_FRAME_CONTROL_RESIZE_E:
+ break;
+ case META_FRAME_CONTROL_NONE:
+ break;
+ case META_FRAME_CONTROL_CLIENT_AREA:
+ break;
+ }
+
+ if (tiptext)
+ {
+ MetaFrameGeometry fgeom;
+ GdkRectangle *rect;
+ int dx, dy;
+ int screen_number;
+
+ meta_frames_calc_geometry (frames, frame, &fgeom);
+
+ rect = control_rect (control, &fgeom);
+
+ /* get conversion delta for root-to-frame coords */
+ dx = root_x - x;
+ dy = root_y - y;
+
+ /* Align the tooltip to the button right end if RTL */
+ if (meta_ui_get_direction() == META_UI_DIRECTION_RTL)
+ dx += rect->width;
+
+ screen_number = gdk_screen_get_number (gtk_widget_get_screen (GTK_WIDGET (frames)));
+
+ meta_fixed_tip_show (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ screen_number,
+ rect->x + dx,
+ rect->y + rect->height + 2 + dy,
+ tiptext);
+ }
+}
+
+static gboolean
+tip_timeout_func (gpointer data)
+{
+ MetaFrames *frames;
+
+ frames = data;
+
+ show_tip_now (frames);
+
+ return FALSE;
+}
+
+#define TIP_DELAY 450
+static void
+queue_tip (MetaFrames *frames)
+{
+ clear_tip (frames);
+
+ frames->tooltip_timeout = g_timeout_add (TIP_DELAY,
+ tip_timeout_func,
+ frames);
+}
+
+static void
+clear_tip (MetaFrames *frames)
+{
+ if (frames->tooltip_timeout)
+ {
+ g_source_remove (frames->tooltip_timeout);
+ frames->tooltip_timeout = 0;
+ }
+ meta_fixed_tip_hide ();
+}
+
+static void
+redraw_control (MetaFrames *frames,
+ MetaUIFrame *frame,
+ MetaFrameControl control)
+{
+ MetaFrameGeometry fgeom;
+ GdkRectangle *rect;
+
+ meta_frames_calc_geometry (frames, frame, &fgeom);
+
+ rect = control_rect (control, &fgeom);
+
+ gdk_window_invalidate_rect (frame->window, rect, FALSE);
+ invalidate_cache (frames, frame);
+}
+
+static gboolean
+meta_frame_titlebar_event (MetaUIFrame *frame,
+ GdkEventButton *event,
+ int action)
+{
+ MetaFrameFlags flags;
+
+ switch (action)
+ {
+ case META_ACTION_TITLEBAR_TOGGLE_SHADE:
+ {
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_END);
+
+ if (flags & META_FRAME_ALLOWS_SHADE)
+ {
+ if (flags & META_FRAME_SHADED)
+ meta_core_unshade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ event->time);
+ else
+ meta_core_shade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ event->time);
+ }
+ }
+ break;
+
+ case META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE:
+ {
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_END);
+
+ if (flags & META_FRAME_ALLOWS_MAXIMIZE)
+ {
+ meta_core_toggle_maximize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+ }
+ }
+ break;
+
+ case META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_HORIZONTALLY:
+ {
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_END);
+
+ if (flags & META_FRAME_ALLOWS_MAXIMIZE)
+ {
+ meta_core_toggle_maximize_horizontally (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+ }
+ }
+ break;
+
+ case META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_VERTICALLY:
+ {
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_END);
+
+ if (flags & META_FRAME_ALLOWS_MAXIMIZE)
+ {
+ meta_core_toggle_maximize_vertically (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+ }
+ }
+ break;
+
+ case META_ACTION_TITLEBAR_MINIMIZE:
+ {
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_END);
+
+ if (flags & META_FRAME_ALLOWS_MINIMIZE)
+ {
+ meta_core_minimize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+ }
+ }
+ break;
+
+ case META_ACTION_TITLEBAR_NONE:
+ /* Yaay, a sane user that doesn't use that other weird crap! */
+ break;
+
+ case META_ACTION_TITLEBAR_LOWER:
+ meta_core_user_lower_and_unfocus (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ event->time);
+ break;
+
+ case META_ACTION_TITLEBAR_MENU:
+ meta_core_show_window_menu (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ event->x_root,
+ event->y_root,
+ event->button,
+ event->time);
+ break;
+
+ case META_ACTION_TITLEBAR_LAST:
+ break;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+meta_frame_double_click_event (MetaUIFrame *frame,
+ GdkEventButton *event)
+{
+ int action = meta_prefs_get_action_double_click_titlebar ();
+
+ return meta_frame_titlebar_event (frame, event, action);
+}
+
+static gboolean
+meta_frame_middle_click_event (MetaUIFrame *frame,
+ GdkEventButton *event)
+{
+ int action = meta_prefs_get_action_middle_click_titlebar();
+
+ return meta_frame_titlebar_event (frame, event, action);
+}
+
+static gboolean
+meta_frame_right_click_event(MetaUIFrame *frame,
+ GdkEventButton *event)
+{
+ int action = meta_prefs_get_action_right_click_titlebar();
+
+ return meta_frame_titlebar_event (frame, event, action);
+}
+
+static gboolean
+meta_frames_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ MetaUIFrame *frame;
+ MetaFrames *frames;
+ MetaFrameControl control;
+
+ frames = META_FRAMES (widget);
+
+ /* Remember that the display may have already done something with this event.
+ * If so there's probably a GrabOp in effect.
+ */
+
+ frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window));
+ if (frame == NULL)
+ return FALSE;
+
+ clear_tip (frames);
+
+ control = get_control (frames, frame, event->x, event->y);
+
+ /* focus on click, even if click was on client area */
+ if (event->button == 1 &&
+ !(control == META_FRAME_CONTROL_MINIMIZE ||
+ control == META_FRAME_CONTROL_DELETE ||
+ control == META_FRAME_CONTROL_MAXIMIZE))
+ {
+ meta_topic (META_DEBUG_FOCUS,
+ "Focusing window with frame 0x%lx due to button 1 press\n",
+ frame->xwindow);
+ meta_core_user_focus (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ event->time);
+ }
+
+ /* don't do the rest of this if on client area */
+ if (control == META_FRAME_CONTROL_CLIENT_AREA)
+ return FALSE; /* not on the frame, just passed through from client */
+
+ /* We want to shade even if we have a GrabOp, since we'll have a move grab
+ * if we double click the titlebar.
+ */
+ if (control == META_FRAME_CONTROL_TITLE &&
+ event->button == 1 &&
+ event->type == GDK_2BUTTON_PRESS)
+ {
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ return meta_frame_double_click_event (frame, event);
+ }
+
+ if (meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())) !=
+ META_GRAB_OP_NONE)
+ return FALSE; /* already up to something */
+
+ if (event->button == 1 &&
+ (control == META_FRAME_CONTROL_MAXIMIZE ||
+ control == META_FRAME_CONTROL_UNMAXIMIZE ||
+ control == META_FRAME_CONTROL_MINIMIZE ||
+ control == META_FRAME_CONTROL_DELETE ||
+ control == META_FRAME_CONTROL_SHADE ||
+ control == META_FRAME_CONTROL_UNSHADE ||
+ control == META_FRAME_CONTROL_ABOVE ||
+ control == META_FRAME_CONTROL_UNABOVE ||
+ control == META_FRAME_CONTROL_STICK ||
+ control == META_FRAME_CONTROL_UNSTICK ||
+ control == META_FRAME_CONTROL_MENU))
+ {
+ MetaGrabOp op = META_GRAB_OP_NONE;
+
+ switch (control)
+ {
+ case META_FRAME_CONTROL_MINIMIZE:
+ op = META_GRAB_OP_CLICKING_MINIMIZE;
+ break;
+ case META_FRAME_CONTROL_MAXIMIZE:
+ op = META_GRAB_OP_CLICKING_MAXIMIZE;
+ break;
+ case META_FRAME_CONTROL_UNMAXIMIZE:
+ op = META_GRAB_OP_CLICKING_UNMAXIMIZE;
+ break;
+ case META_FRAME_CONTROL_DELETE:
+ op = META_GRAB_OP_CLICKING_DELETE;
+ break;
+ case META_FRAME_CONTROL_MENU:
+ op = META_GRAB_OP_CLICKING_MENU;
+ break;
+ case META_FRAME_CONTROL_SHADE:
+ op = META_GRAB_OP_CLICKING_SHADE;
+ break;
+ case META_FRAME_CONTROL_UNSHADE:
+ op = META_GRAB_OP_CLICKING_UNSHADE;
+ break;
+ case META_FRAME_CONTROL_ABOVE:
+ op = META_GRAB_OP_CLICKING_ABOVE;
+ break;
+ case META_FRAME_CONTROL_UNABOVE:
+ op = META_GRAB_OP_CLICKING_UNABOVE;
+ break;
+ case META_FRAME_CONTROL_STICK:
+ op = META_GRAB_OP_CLICKING_STICK;
+ break;
+ case META_FRAME_CONTROL_UNSTICK:
+ op = META_GRAB_OP_CLICKING_UNSTICK;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ meta_core_begin_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ op,
+ TRUE,
+ TRUE,
+ event->button,
+ 0,
+ event->time,
+ event->x_root,
+ event->y_root);
+
+ frame->prelit_control = control;
+ redraw_control (frames, frame, control);
+
+ if (op == META_GRAB_OP_CLICKING_MENU)
+ {
+ MetaFrameGeometry fgeom;
+ GdkRectangle *rect;
+ int dx, dy;
+
+ meta_frames_calc_geometry (frames, frame, &fgeom);
+
+ rect = control_rect (META_FRAME_CONTROL_MENU, &fgeom);
+
+ /* get delta to convert to root coords */
+ dx = event->x_root - event->x;
+ dy = event->y_root - event->y;
+
+ /* Align to the right end of the menu rectangle if RTL */
+ if (meta_ui_get_direction() == META_UI_DIRECTION_RTL)
+ dx += rect->width;
+
+ meta_core_show_window_menu (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ rect->x + dx,
+ rect->y + rect->height + dy,
+ event->button,
+ event->time);
+ }
+ }
+ else if (event->button == 1 &&
+ (control == META_FRAME_CONTROL_RESIZE_SE ||
+ control == META_FRAME_CONTROL_RESIZE_S ||
+ control == META_FRAME_CONTROL_RESIZE_SW ||
+ control == META_FRAME_CONTROL_RESIZE_NE ||
+ control == META_FRAME_CONTROL_RESIZE_N ||
+ control == META_FRAME_CONTROL_RESIZE_NW ||
+ control == META_FRAME_CONTROL_RESIZE_E ||
+ control == META_FRAME_CONTROL_RESIZE_W))
+ {
+ MetaGrabOp op;
+ gboolean titlebar_is_onscreen;
+
+ op = META_GRAB_OP_NONE;
+
+ switch (control)
+ {
+ case META_FRAME_CONTROL_RESIZE_SE:
+ op = META_GRAB_OP_RESIZING_SE;
+ break;
+ case META_FRAME_CONTROL_RESIZE_S:
+ op = META_GRAB_OP_RESIZING_S;
+ break;
+ case META_FRAME_CONTROL_RESIZE_SW:
+ op = META_GRAB_OP_RESIZING_SW;
+ break;
+ case META_FRAME_CONTROL_RESIZE_NE:
+ op = META_GRAB_OP_RESIZING_NE;
+ break;
+ case META_FRAME_CONTROL_RESIZE_N:
+ op = META_GRAB_OP_RESIZING_N;
+ break;
+ case META_FRAME_CONTROL_RESIZE_NW:
+ op = META_GRAB_OP_RESIZING_NW;
+ break;
+ case META_FRAME_CONTROL_RESIZE_E:
+ op = META_GRAB_OP_RESIZING_E;
+ break;
+ case META_FRAME_CONTROL_RESIZE_W:
+ op = META_GRAB_OP_RESIZING_W;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_IS_TITLEBAR_ONSCREEN, &titlebar_is_onscreen,
+ META_CORE_GET_END);
+
+ if (!titlebar_is_onscreen)
+ meta_core_show_window_menu (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ event->x_root,
+ event->y_root,
+ event->button,
+ event->time);
+ else
+ meta_core_begin_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ op,
+ TRUE,
+ TRUE,
+ event->button,
+ 0,
+ event->time,
+ event->x_root,
+ event->y_root);
+ }
+ else if (control == META_FRAME_CONTROL_TITLE &&
+ event->button == 1)
+ {
+ MetaFrameFlags flags;
+
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_END);
+
+ if (flags & META_FRAME_ALLOWS_MOVE)
+ {
+ meta_core_begin_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ META_GRAB_OP_MOVING,
+ TRUE,
+ TRUE,
+ event->button,
+ 0,
+ event->time,
+ event->x_root,
+ event->y_root);
+ }
+ }
+ else if (event->button == 2)
+ {
+ return meta_frame_middle_click_event (frame, event);
+ }
+ else if (event->button == 3)
+ {
+ return meta_frame_right_click_event (frame, event);
+ }
+
+ return TRUE;
+}
+
+void
+meta_frames_notify_menu_hide (MetaFrames *frames)
+{
+ if (meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())) ==
+ META_GRAB_OP_CLICKING_MENU)
+ {
+ Window grab_frame;
+
+ grab_frame = meta_core_get_grab_frame (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+
+ if (grab_frame != None)
+ {
+ MetaUIFrame *frame;
+
+ frame = meta_frames_lookup_window (frames, grab_frame);
+
+ if (frame)
+ {
+ redraw_control (frames, frame,
+ META_FRAME_CONTROL_MENU);
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), CurrentTime);
+ }
+ }
+ }
+}
+
+static gboolean
+meta_frames_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ MetaUIFrame *frame;
+ MetaFrames *frames;
+ MetaGrabOp op;
+
+ frames = META_FRAMES (widget);
+
+ frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window));
+ if (frame == NULL)
+ return FALSE;
+
+ clear_tip (frames);
+
+ op = meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+
+ if (op == META_GRAB_OP_NONE)
+ return FALSE;
+
+ /* We only handle the releases we handled the presses for (things
+ * involving frame controls). Window ops that don't require a
+ * frame are handled in the Xlib part of the code, display.c/window.c
+ */
+ if (frame->xwindow == meta_core_get_grab_frame (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())) &&
+ ((int) event->button) == meta_core_get_grab_button (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())))
+ {
+ MetaFrameControl control;
+
+ control = get_control (frames, frame, event->x, event->y);
+
+ switch (op)
+ {
+ case META_GRAB_OP_CLICKING_MINIMIZE:
+ if (control == META_FRAME_CONTROL_MINIMIZE)
+ meta_core_minimize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_MAXIMIZE:
+ if (control == META_FRAME_CONTROL_MAXIMIZE)
+ {
+ /* Focus the window on the maximize */
+ meta_core_user_focus (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ event->time);
+ meta_core_maximize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+ }
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_UNMAXIMIZE:
+ if (control == META_FRAME_CONTROL_UNMAXIMIZE)
+ meta_core_unmaximize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_DELETE:
+ if (control == META_FRAME_CONTROL_DELETE)
+ meta_core_delete (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, event->time);
+
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_MENU:
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_SHADE:
+ if (control == META_FRAME_CONTROL_SHADE)
+ meta_core_shade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, event->time);
+
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_UNSHADE:
+ if (control == META_FRAME_CONTROL_UNSHADE)
+ meta_core_unshade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, event->time);
+
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_ABOVE:
+ if (control == META_FRAME_CONTROL_ABOVE)
+ meta_core_make_above (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_UNABOVE:
+ if (control == META_FRAME_CONTROL_UNABOVE)
+ meta_core_unmake_above (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_STICK:
+ if (control == META_FRAME_CONTROL_STICK)
+ meta_core_stick (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_UNSTICK:
+ if (control == META_FRAME_CONTROL_UNSTICK)
+ meta_core_unstick (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow);
+
+ meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time);
+ break;
+
+ default:
+ break;
+ }
+
+ /* Update the prelit control regardless of what button the mouse
+ * was released over; needed so that the new button can become
+ * prelit so to let the user know that it can now be pressed.
+ * :)
+ */
+ meta_frames_update_prelit_control (frames, frame, control);
+ }
+
+ return TRUE;
+}
+
+static void
+meta_frames_update_prelit_control (MetaFrames *frames,
+ MetaUIFrame *frame,
+ MetaFrameControl control)
+{
+ MetaFrameControl old_control;
+ MetaCursor cursor;
+
+
+ meta_verbose ("Updating prelit control from %u to %u\n",
+ frame->prelit_control, control);
+
+ cursor = META_CURSOR_DEFAULT;
+
+ switch (control)
+ {
+ case META_FRAME_CONTROL_CLIENT_AREA:
+ break;
+ case META_FRAME_CONTROL_NONE:
+ break;
+ case META_FRAME_CONTROL_TITLE:
+ break;
+ case META_FRAME_CONTROL_DELETE:
+ break;
+ case META_FRAME_CONTROL_MENU:
+ break;
+ case META_FRAME_CONTROL_MINIMIZE:
+ break;
+ case META_FRAME_CONTROL_MAXIMIZE:
+ break;
+ case META_FRAME_CONTROL_UNMAXIMIZE:
+ break;
+ case META_FRAME_CONTROL_SHADE:
+ break;
+ case META_FRAME_CONTROL_UNSHADE:
+ break;
+ case META_FRAME_CONTROL_ABOVE:
+ break;
+ case META_FRAME_CONTROL_UNABOVE:
+ break;
+ case META_FRAME_CONTROL_STICK:
+ break;
+ case META_FRAME_CONTROL_UNSTICK:
+ break;
+ case META_FRAME_CONTROL_RESIZE_SE:
+ cursor = META_CURSOR_SE_RESIZE;
+ break;
+ case META_FRAME_CONTROL_RESIZE_S:
+ cursor = META_CURSOR_SOUTH_RESIZE;
+ break;
+ case META_FRAME_CONTROL_RESIZE_SW:
+ cursor = META_CURSOR_SW_RESIZE;
+ break;
+ case META_FRAME_CONTROL_RESIZE_N:
+ cursor = META_CURSOR_NORTH_RESIZE;
+ break;
+ case META_FRAME_CONTROL_RESIZE_NE:
+ cursor = META_CURSOR_NE_RESIZE;
+ break;
+ case META_FRAME_CONTROL_RESIZE_NW:
+ cursor = META_CURSOR_NW_RESIZE;
+ break;
+ case META_FRAME_CONTROL_RESIZE_W:
+ cursor = META_CURSOR_WEST_RESIZE;
+ break;
+ case META_FRAME_CONTROL_RESIZE_E:
+ cursor = META_CURSOR_EAST_RESIZE;
+ break;
+ }
+
+ /* set/unset the prelight cursor */
+ meta_core_set_screen_cursor (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
+ frame->xwindow,
+ cursor);
+
+ switch (control)
+ {
+ case META_FRAME_CONTROL_MENU:
+ case META_FRAME_CONTROL_MINIMIZE:
+ case META_FRAME_CONTROL_MAXIMIZE:
+ case META_FRAME_CONTROL_DELETE:
+ case META_FRAME_CONTROL_SHADE:
+ case META_FRAME_CONTROL_UNSHADE:
+ case META_FRAME_CONTROL_ABOVE:
+ case META_FRAME_CONTROL_UNABOVE:
+ case META_FRAME_CONTROL_STICK:
+ case META_FRAME_CONTROL_UNSTICK:
+ case META_FRAME_CONTROL_UNMAXIMIZE:
+ /* leave control set */
+ break;
+ default:
+ /* Only prelight buttons */
+ control = META_FRAME_CONTROL_NONE;
+ break;
+ }
+
+ if (control == frame->prelit_control)
+ return;
+
+ /* Save the old control so we can unprelight it */
+ old_control = frame->prelit_control;
+
+ frame->prelit_control = control;
+
+ redraw_control (frames, frame, old_control);
+ redraw_control (frames, frame, control);
+}
+
+static gboolean
+meta_frames_motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ MetaUIFrame *frame;
+ MetaFrames *frames;
+ MetaGrabOp grab_op;
+
+ frames = META_FRAMES (widget);
+
+ frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window));
+ if (frame == NULL)
+ return FALSE;
+
+ clear_tip (frames);
+
+ frames->last_motion_frame = frame;
+
+ grab_op = meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+
+ switch (grab_op)
+ {
+ case META_GRAB_OP_CLICKING_MENU:
+ case META_GRAB_OP_CLICKING_DELETE:
+ case META_GRAB_OP_CLICKING_MINIMIZE:
+ case META_GRAB_OP_CLICKING_MAXIMIZE:
+ case META_GRAB_OP_CLICKING_UNMAXIMIZE:
+ case META_GRAB_OP_CLICKING_SHADE:
+ case META_GRAB_OP_CLICKING_UNSHADE:
+ case META_GRAB_OP_CLICKING_ABOVE:
+ case META_GRAB_OP_CLICKING_UNABOVE:
+ case META_GRAB_OP_CLICKING_STICK:
+ case META_GRAB_OP_CLICKING_UNSTICK:
+ {
+ MetaFrameControl control;
+ int x, y;
+
+ gdk_window_get_pointer (frame->window, &x, &y, NULL);
+
+ /* Control is set to none unless it matches
+ * the current grab
+ */
+ control = get_control (frames, frame, x, y);
+ if (! ((control == META_FRAME_CONTROL_MENU &&
+ grab_op == META_GRAB_OP_CLICKING_MENU) ||
+ (control == META_FRAME_CONTROL_DELETE &&
+ grab_op == META_GRAB_OP_CLICKING_DELETE) ||
+ (control == META_FRAME_CONTROL_MINIMIZE &&
+ grab_op == META_GRAB_OP_CLICKING_MINIMIZE) ||
+ ((control == META_FRAME_CONTROL_MAXIMIZE ||
+ control == META_FRAME_CONTROL_UNMAXIMIZE) &&
+ (grab_op == META_GRAB_OP_CLICKING_MAXIMIZE ||
+ grab_op == META_GRAB_OP_CLICKING_UNMAXIMIZE)) ||
+ (control == META_FRAME_CONTROL_SHADE &&
+ grab_op == META_GRAB_OP_CLICKING_SHADE) ||
+ (control == META_FRAME_CONTROL_UNSHADE &&
+ grab_op == META_GRAB_OP_CLICKING_UNSHADE) ||
+ (control == META_FRAME_CONTROL_ABOVE &&
+ grab_op == META_GRAB_OP_CLICKING_ABOVE) ||
+ (control == META_FRAME_CONTROL_UNABOVE &&
+ grab_op == META_GRAB_OP_CLICKING_UNABOVE) ||
+ (control == META_FRAME_CONTROL_STICK &&
+ grab_op == META_GRAB_OP_CLICKING_STICK) ||
+ (control == META_FRAME_CONTROL_UNSTICK &&
+ grab_op == META_GRAB_OP_CLICKING_UNSTICK)))
+ control = META_FRAME_CONTROL_NONE;
+
+ /* Update prelit control and cursor */
+ meta_frames_update_prelit_control (frames, frame, control);
+
+ /* No tooltip while in the process of clicking */
+ }
+ break;
+ case META_GRAB_OP_NONE:
+ {
+ MetaFrameControl control;
+ int x, y;
+
+ gdk_window_get_pointer (frame->window, &x, &y, NULL);
+
+ control = get_control (frames, frame, x, y);
+
+ /* Update prelit control and cursor */
+ meta_frames_update_prelit_control (frames, frame, control);
+
+ queue_tip (frames);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+meta_frames_destroy_event (GtkWidget *widget,
+ GdkEventAny *event)
+{
+ MetaUIFrame *frame;
+ MetaFrames *frames;
+
+ frames = META_FRAMES (widget);
+
+ frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window));
+ if (frame == NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+#if !GTK_CHECK_VERSION(2,21,6)
+/* Copied from GDK */
+static cairo_surface_t *
+_gdk_drawable_ref_cairo_surface (GdkDrawable *drawable)
+{
+ g_return_val_if_fail (GDK_IS_DRAWABLE (drawable), NULL);
+
+ return GDK_DRAWABLE_GET_CLASS (drawable)->ref_cairo_surface (drawable);
+}
+
+static cairo_pattern_t *
+gdk_window_get_background_pattern (GdkWindow *window)
+{
+ GdkWindowObject *private = (GdkWindowObject *) window;
+ cairo_pattern_t *pattern;
+
+ g_return_val_if_fail (GDK_IS_WINDOW (window), NULL);
+
+ if (private->bg_pixmap == GDK_PARENT_RELATIVE_BG)
+ pattern = NULL;
+ else if (private->bg_pixmap != GDK_NO_BG &&
+ private->bg_pixmap != NULL)
+ {
+ static cairo_user_data_key_t key;
+ cairo_surface_t *surface;
+
+ surface = _gdk_drawable_ref_cairo_surface (private->bg_pixmap);
+ pattern = cairo_pattern_create_for_surface (surface);
+ cairo_surface_destroy (surface);
+
+ cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+ cairo_pattern_set_user_data (pattern,
+ &key,
+ g_object_ref (private->bg_pixmap),
+ g_object_unref);
+ }
+ else
+ {
+ pattern =
+ cairo_pattern_create_rgb (private->bg_color.red / 65535.,
+ private->bg_color.green / 65535.,
+ private->bg_color.blue / 65535.);
+ }
+
+ return pattern;
+}
+#endif
+
+static void
+setup_bg_cr (cairo_t *cr, GdkWindow *window, int x_offset, int y_offset)
+{
+ GdkWindow *parent = gdk_window_get_parent (window);
+ cairo_pattern_t *bg_pattern;
+
+ bg_pattern = gdk_window_get_background_pattern (window);
+ if (bg_pattern == NULL && parent)
+ {
+ gint window_x, window_y;
+
+ gdk_window_get_position (window, &window_x, &window_y);
+ setup_bg_cr (cr, parent, x_offset + window_x, y_offset + window_y);
+ }
+ else if (bg_pattern)
+ {
+ cairo_translate (cr, - x_offset, - y_offset);
+ cairo_set_source (cr, bg_pattern);
+ cairo_translate (cr, x_offset, y_offset);
+ }
+}
+
+static void
+clear_backing (GdkPixmap *pixmap,
+ GdkWindow *window,
+ int xoffset, int yoffset)
+{
+ int width, height;
+ cairo_t *cr = gdk_cairo_create (pixmap);
+
+ setup_bg_cr (cr, window, xoffset, yoffset);
+
+ #if GTK_CHECK_VERSION(3, 0, 0)
+ width = gdk_window_get_width(GDK_WINDOW(pixmap));
+ height = gdk_window_get_height(GDK_WINDOW(pixmap));
+ #else
+ gdk_drawable_get_size(GDK_DRAWABLE(pixmap), &width, &height);
+ #endif
+
+ cairo_rectangle (cr, 0, 0, width, height);
+ cairo_fill (cr);
+
+ cairo_destroy (cr);
+}
+
+/* Returns a pixmap with a piece of the windows frame painted on it.
+*/
+
+static GdkPixmap *
+generate_pixmap (MetaFrames *frames,
+ MetaUIFrame *frame,
+ MetaRectangle rect)
+{
+ GdkRectangle rectangle;
+ GdkRegion *region;
+ GdkPixmap *result;
+
+ rectangle.x = rect.x;
+ rectangle.y = rect.y;
+ rectangle.width = MAX (rect.width, 1);
+ rectangle.height = MAX (rect.height, 1);
+
+ result = gdk_pixmap_new (frame->window,
+ rectangle.width, rectangle.height, -1);
+
+ clear_backing (result, frame->window, rectangle.x, rectangle.y);
+
+ region = gdk_region_rectangle (&rectangle);
+
+ meta_frames_paint_to_drawable (frames, frame, result, region,
+ -rectangle.x, -rectangle.y);
+
+ gdk_region_destroy (region);
+
+ return result;
+}
+
+
+static void
+populate_cache (MetaFrames *frames,
+ MetaUIFrame *frame)
+{
+ int top, bottom, left, right;
+ int width, height;
+ int frame_width, frame_height, screen_width, screen_height;
+ CachedPixels *pixels;
+ MetaFrameType frame_type;
+ MetaFrameFlags frame_flags;
+ int i;
+
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_WIDTH, &frame_width,
+ META_CORE_GET_FRAME_HEIGHT, &frame_height,
+ META_CORE_GET_SCREEN_WIDTH, &screen_width,
+ META_CORE_GET_SCREEN_HEIGHT, &screen_height,
+ META_CORE_GET_CLIENT_WIDTH, &width,
+ META_CORE_GET_CLIENT_HEIGHT, &height,
+ META_CORE_GET_FRAME_TYPE, &frame_type,
+ META_CORE_GET_FRAME_FLAGS, &frame_flags,
+ META_CORE_GET_END);
+
+ /* don't cache extremely large windows */
+ if (frame_width > 2 * screen_width ||
+ frame_height > 2 * screen_height)
+ {
+ return;
+ }
+
+ meta_theme_get_frame_borders (meta_theme_get_current (),
+ frame_type,
+ frame->text_height,
+ frame_flags,
+ &top, &bottom, &left, &right);
+
+ pixels = get_cache (frames, frame);
+
+ /* Setup the rectangles for the four frame borders. First top, then
+ left, right and bottom. */
+ pixels->piece[0].rect.x = 0;
+ pixels->piece[0].rect.y = 0;
+ pixels->piece[0].rect.width = left + width + right;
+ pixels->piece[0].rect.height = top;
+
+ pixels->piece[1].rect.x = 0;
+ pixels->piece[1].rect.y = top;
+ pixels->piece[1].rect.width = left;
+ pixels->piece[1].rect.height = height;
+
+ pixels->piece[2].rect.x = left + width;
+ pixels->piece[2].rect.y = top;
+ pixels->piece[2].rect.width = right;
+ pixels->piece[2].rect.height = height;
+
+ pixels->piece[3].rect.x = 0;
+ pixels->piece[3].rect.y = top + height;
+ pixels->piece[3].rect.width = left + width + right;
+ pixels->piece[3].rect.height = bottom;
+
+ for (i = 0; i < 4; i++)
+ {
+ CachedFramePiece *piece = &pixels->piece[i];
+ if (!piece->pixmap)
+ piece->pixmap = generate_pixmap (frames, frame, piece->rect);
+ }
+
+ if (frames->invalidate_cache_timeout_id)
+ g_source_remove (frames->invalidate_cache_timeout_id);
+
+ frames->invalidate_cache_timeout_id = g_timeout_add (1000, invalidate_cache_timeout, frames);
+
+ if (!g_list_find (frames->invalidate_frames, frame))
+ frames->invalidate_frames =
+ g_list_prepend (frames->invalidate_frames, frame);
+}
+
+static void
+clip_to_screen (GdkRegion *region, MetaUIFrame *frame)
+{
+ GdkRectangle frame_area;
+ GdkRectangle screen_area = { 0, 0, 0, 0 };
+ GdkRegion *tmp_region;
+
+ /* Chop off stuff outside the screen; this optimization
+ * is crucial to handle huge client windows,
+ * like "xterm -geometry 1000x1000"
+ */
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_X, &frame_area.x,
+ META_CORE_GET_FRAME_Y, &frame_area.y,
+ META_CORE_GET_FRAME_WIDTH, &frame_area.width,
+ META_CORE_GET_FRAME_HEIGHT, &frame_area.height,
+ META_CORE_GET_SCREEN_WIDTH, &screen_area.height,
+ META_CORE_GET_SCREEN_HEIGHT, &screen_area.height,
+ META_CORE_GET_END);
+
+ gdk_region_offset (region, frame_area.x, frame_area.y);
+
+ tmp_region = gdk_region_rectangle (&frame_area);
+ gdk_region_intersect (region, tmp_region);
+ gdk_region_destroy (tmp_region);
+
+ gdk_region_offset (region, - frame_area.x, - frame_area.y);
+}
+
+static void
+subtract_from_region (GdkRegion *region, GdkDrawable *drawable,
+ gint x, gint y)
+{
+ GdkRectangle rect;
+ GdkRegion *reg_rect;
+
+ #if GTK_CHECK_VERSION(3, 0, 0)
+ rect.width = gdk_window_get_width(GDK_WINDOW(drawable));
+ rect.height = gdk_window_get_height(GDK_WINDOW(drawable));
+ #else
+ gdk_drawable_get_size (drawable, &rect.width, &rect.height);
+ #endif
+
+ rect.x = x;
+ rect.y = y;
+
+ reg_rect = gdk_region_rectangle (&rect);
+ gdk_region_subtract (region, reg_rect);
+ gdk_region_destroy (reg_rect);
+}
+
+static void
+cached_pixels_draw (CachedPixels *pixels,
+ GdkWindow *window,
+ GdkRegion *region)
+{
+ cairo_t *cr;
+ int i;
+
+ cr = gdk_cairo_create (window);
+
+ for (i = 0; i < 4; i++)
+ {
+ CachedFramePiece *piece;
+ piece = &pixels->piece[i];
+
+ if (piece->pixmap)
+ {
+ gdk_cairo_set_source_pixmap (cr, piece->pixmap,
+ piece->rect.x, piece->rect.y);
+ cairo_paint (cr);
+ subtract_from_region (region, piece->pixmap,
+ piece->rect.x, piece->rect.y);
+ }
+ }
+
+ cairo_destroy (cr);
+}
+
+static gboolean
+meta_frames_expose_event (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ MetaUIFrame *frame;
+ MetaFrames *frames;
+ GdkRegion *region;
+ CachedPixels *pixels;
+
+ frames = META_FRAMES (widget);
+
+ frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window));
+ if (frame == NULL)
+ return FALSE;
+
+ if (frames->expose_delay_count > 0)
+ {
+ /* Redraw this entire frame later */
+ frame->expose_delayed = TRUE;
+ return TRUE;
+ }
+
+ populate_cache (frames, frame);
+
+ region = gdk_region_copy (event->region);
+
+ pixels = get_cache (frames, frame);
+
+ cached_pixels_draw (pixels, frame->window, region);
+
+ clip_to_screen (region, frame);
+ meta_frames_paint_to_drawable (frames, frame, frame->window, region, 0, 0);
+
+ gdk_region_destroy (region);
+
+ return TRUE;
+}
+
+/* How far off the screen edge the window decorations should
+ * be drawn. Used only in meta_frames_paint_to_drawable, below.
+ */
+#define DECORATING_BORDER 100
+
+static void
+meta_frames_paint_to_drawable (MetaFrames *frames,
+ MetaUIFrame *frame,
+ GdkDrawable *drawable,
+ GdkRegion *region,
+ int x_offset,
+ int y_offset)
+{
+ GtkWidget *widget;
+ MetaFrameFlags flags;
+ MetaFrameType type;
+ GdkPixbuf *mini_icon;
+ GdkPixbuf *icon;
+ int w, h;
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST];
+ Window grab_frame;
+ int i;
+ MetaButtonLayout button_layout;
+ MetaGrabOp grab_op;
+
+ widget = GTK_WIDGET (frames);
+
+ for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
+ button_states[i] = META_BUTTON_STATE_NORMAL;
+
+ grab_frame = meta_core_get_grab_frame (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+ grab_op = meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+ if (grab_frame != frame->xwindow)
+ grab_op = META_GRAB_OP_NONE;
+
+ /* Set prelight state */
+ switch (frame->prelit_control)
+ {
+ case META_FRAME_CONTROL_MENU:
+ if (grab_op == META_GRAB_OP_CLICKING_MENU)
+ button_states[META_BUTTON_TYPE_MENU] = META_BUTTON_STATE_PRESSED;
+ else
+ button_states[META_BUTTON_TYPE_MENU] = META_BUTTON_STATE_PRELIGHT;
+ break;
+ case META_FRAME_CONTROL_MINIMIZE:
+ if (grab_op == META_GRAB_OP_CLICKING_MINIMIZE)
+ button_states[META_BUTTON_TYPE_MINIMIZE] = META_BUTTON_STATE_PRESSED;
+ else
+ button_states[META_BUTTON_TYPE_MINIMIZE] = META_BUTTON_STATE_PRELIGHT;
+ break;
+ case META_FRAME_CONTROL_MAXIMIZE:
+ if (grab_op == META_GRAB_OP_CLICKING_MAXIMIZE)
+ button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRESSED;
+ else
+ button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRELIGHT;
+ break;
+ case META_FRAME_CONTROL_UNMAXIMIZE:
+ if (grab_op == META_GRAB_OP_CLICKING_UNMAXIMIZE)
+ button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRESSED;
+ else
+ button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRELIGHT;
+ break;
+ case META_FRAME_CONTROL_SHADE:
+ if (grab_op == META_GRAB_OP_CLICKING_SHADE)
+ button_states[META_BUTTON_TYPE_SHADE] = META_BUTTON_STATE_PRESSED;
+ else
+ button_states[META_BUTTON_TYPE_SHADE] = META_BUTTON_STATE_PRELIGHT;
+ break;
+ case META_FRAME_CONTROL_UNSHADE:
+ if (grab_op == META_GRAB_OP_CLICKING_UNSHADE)
+ button_states[META_BUTTON_TYPE_UNSHADE] = META_BUTTON_STATE_PRESSED;
+ else
+ button_states[META_BUTTON_TYPE_UNSHADE] = META_BUTTON_STATE_PRELIGHT;
+ break;
+ case META_FRAME_CONTROL_ABOVE:
+ if (grab_op == META_GRAB_OP_CLICKING_ABOVE)
+ button_states[META_BUTTON_TYPE_ABOVE] = META_BUTTON_STATE_PRESSED;
+ else
+ button_states[META_BUTTON_TYPE_ABOVE] = META_BUTTON_STATE_PRELIGHT;
+ break;
+ case META_FRAME_CONTROL_UNABOVE:
+ if (grab_op == META_GRAB_OP_CLICKING_UNABOVE)
+ button_states[META_BUTTON_TYPE_UNABOVE] = META_BUTTON_STATE_PRESSED;
+ else
+ button_states[META_BUTTON_TYPE_UNABOVE] = META_BUTTON_STATE_PRELIGHT;
+ break;
+ case META_FRAME_CONTROL_STICK:
+ if (grab_op == META_GRAB_OP_CLICKING_STICK)
+ button_states[META_BUTTON_TYPE_STICK] = META_BUTTON_STATE_PRESSED;
+ else
+ button_states[META_BUTTON_TYPE_STICK] = META_BUTTON_STATE_PRELIGHT;
+ break;
+ case META_FRAME_CONTROL_UNSTICK:
+ if (grab_op == META_GRAB_OP_CLICKING_UNSTICK)
+ button_states[META_BUTTON_TYPE_UNSTICK] = META_BUTTON_STATE_PRESSED;
+ else
+ button_states[META_BUTTON_TYPE_UNSTICK] = META_BUTTON_STATE_PRELIGHT;
+ break;
+ case META_FRAME_CONTROL_DELETE:
+ if (grab_op == META_GRAB_OP_CLICKING_DELETE)
+ button_states[META_BUTTON_TYPE_CLOSE] = META_BUTTON_STATE_PRESSED;
+ else
+ button_states[META_BUTTON_TYPE_CLOSE] = META_BUTTON_STATE_PRELIGHT;
+ break;
+ default:
+ break;
+ }
+
+ /* Map button function states to button position states */
+ button_states[META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND] =
+ button_states[META_BUTTON_TYPE_MENU];
+ button_states[META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND] =
+ META_BUTTON_STATE_NORMAL;
+ button_states[META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND] =
+ META_BUTTON_STATE_NORMAL;
+ button_states[META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND] =
+ button_states[META_BUTTON_TYPE_MINIMIZE];
+ button_states[META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND] =
+ button_states[META_BUTTON_TYPE_MAXIMIZE];
+ button_states[META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND] =
+ button_states[META_BUTTON_TYPE_CLOSE];
+
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_FRAME_TYPE, &type,
+ META_CORE_GET_MINI_ICON, &mini_icon,
+ META_CORE_GET_ICON, &icon,
+ META_CORE_GET_CLIENT_WIDTH, &w,
+ META_CORE_GET_CLIENT_HEIGHT, &h,
+ META_CORE_GET_END);
+
+ meta_frames_ensure_layout (frames, frame);
+
+ meta_prefs_get_button_layout (&button_layout);
+
+ if (G_LIKELY (GDK_IS_WINDOW (drawable)))
+ {
+ /* A window; happens about 2/3 of the time */
+
+ GdkRectangle area, *areas;
+ int n_areas;
+ int screen_width, screen_height;
+ GdkRegion *edges, *tmp_region;
+ int top, bottom, left, right;
+
+ /* Repaint each side of the frame */
+
+ meta_theme_get_frame_borders (meta_theme_get_current (),
+ type, frame->text_height, flags,
+ &top, &bottom, &left, &right);
+
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_SCREEN_WIDTH, &screen_width,
+ META_CORE_GET_SCREEN_HEIGHT, &screen_height,
+ META_CORE_GET_END);
+
+ edges = gdk_region_copy (region);
+
+ /* Punch out the client area */
+
+ area.x = left;
+ area.y = top;
+ area.width = w;
+ area.height = h;
+ tmp_region = gdk_region_rectangle (&area);
+ gdk_region_subtract (edges, tmp_region);
+ gdk_region_destroy (tmp_region);
+
+ /* Now draw remaining portion of region */
+
+ gdk_region_get_rectangles (edges, &areas, &n_areas);
+
+ for (i = 0; i < n_areas; i++)
+ {
+ /* Bug 399529: clamp areas[i] so that it doesn't go too far
+ * off the edge of the screen. This works around a GDK bug
+ * which makes gdk_window_begin_paint_rect cause an X error
+ * if the window is insanely huge. If the client is a GDK program
+ * and does this, it will still probably cause an X error in that
+ * program, but the last thing we want is for Marco to crash
+ * because it attempted to decorate the silly window.
+ */
+
+ areas[i].x = MAX (areas[i].x, -DECORATING_BORDER);
+ areas[i].y = MAX (areas[i].y, -DECORATING_BORDER);
+ if (areas[i].x+areas[i].width > screen_width + DECORATING_BORDER)
+ areas[i].width = MIN (0, screen_width - areas[i].x);
+ if (areas[i].y+areas[i].height > screen_height + DECORATING_BORDER)
+ areas[i].height = MIN (0, screen_height - areas[i].y);
+
+ /* Okay, so let's start painting. */
+
+ gdk_window_begin_paint_rect (drawable, &areas[i]);
+
+ meta_theme_draw_frame_with_style (meta_theme_get_current (),
+ frame->style,
+ widget,
+ drawable,
+ NULL, /* &areas[i], */
+ x_offset, y_offset,
+ type,
+ flags,
+ w, h,
+ frame->layout,
+ frame->text_height,
+ &button_layout,
+ button_states,
+ mini_icon, icon);
+
+ gdk_window_end_paint (drawable);
+ }
+
+ g_free (areas);
+ gdk_region_destroy (edges);
+
+ }
+ else
+ {
+ /* Not a window; happens about 1/3 of the time */
+
+ meta_theme_draw_frame_with_style (meta_theme_get_current (),
+ frame->style,
+ widget,
+ drawable,
+ NULL,
+ x_offset, y_offset,
+ type,
+ flags,
+ w, h,
+ frame->layout,
+ frame->text_height,
+ &button_layout,
+ button_states,
+ mini_icon, icon);
+ }
+
+}
+
+static void
+meta_frames_set_window_background (MetaFrames *frames,
+ MetaUIFrame *frame)
+{
+ MetaFrameFlags flags;
+ MetaFrameType type;
+ MetaFrameStyle *style;
+ gboolean frame_exists;
+
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_WINDOW_HAS_FRAME, &frame_exists,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_FRAME_TYPE, &type,
+ META_CORE_GET_END);
+
+ if (frame_exists)
+ {
+ style = meta_theme_get_frame_style (meta_theme_get_current (),
+ type, flags);
+ }
+
+ if (frame_exists && style->window_background_color != NULL)
+ {
+ GdkColor color;
+ GdkVisual *visual;
+
+ meta_color_spec_render (style->window_background_color,
+ GTK_WIDGET (frames),
+ &color);
+
+ /* Set A in ARGB to window_background_alpha, if we have ARGB */
+
+ visual = gtk_widget_get_visual (GTK_WIDGET (frames));
+ if (visual->depth == 32) /* we have ARGB */
+ {
+ color.pixel = (color.pixel & 0xffffff) &
+ style->window_background_alpha << 24;
+ }
+
+ gdk_window_set_background (frame->window, &color);
+ }
+ else
+ {
+ gtk_style_set_background (frame->style,
+ frame->window, GTK_STATE_NORMAL);
+ }
+ }
+
+static gboolean
+meta_frames_enter_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ MetaUIFrame *frame;
+ MetaFrames *frames;
+ MetaFrameControl control;
+
+ frames = META_FRAMES (widget);
+
+ frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window));
+ if (frame == NULL)
+ return FALSE;
+
+ control = get_control (frames, frame, event->x, event->y);
+ meta_frames_update_prelit_control (frames, frame, control);
+
+ return TRUE;
+}
+
+static gboolean
+meta_frames_leave_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ MetaUIFrame *frame;
+ MetaFrames *frames;
+
+ frames = META_FRAMES (widget);
+
+ frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window));
+ if (frame == NULL)
+ return FALSE;
+
+ meta_frames_update_prelit_control (frames, frame, META_FRAME_CONTROL_NONE);
+
+ clear_tip (frames);
+
+ return TRUE;
+}
+
+static GdkRectangle*
+control_rect (MetaFrameControl control,
+ MetaFrameGeometry *fgeom)
+{
+ GdkRectangle *rect;
+
+ rect = NULL;
+ switch (control)
+ {
+ case META_FRAME_CONTROL_TITLE:
+ rect = &fgeom->title_rect;
+ break;
+ case META_FRAME_CONTROL_DELETE:
+ rect = &fgeom->close_rect.visible;
+ break;
+ case META_FRAME_CONTROL_MENU:
+ rect = &fgeom->menu_rect.visible;
+ break;
+ case META_FRAME_CONTROL_MINIMIZE:
+ rect = &fgeom->min_rect.visible;
+ break;
+ case META_FRAME_CONTROL_MAXIMIZE:
+ case META_FRAME_CONTROL_UNMAXIMIZE:
+ rect = &fgeom->max_rect.visible;
+ break;
+ case META_FRAME_CONTROL_SHADE:
+ rect = &fgeom->shade_rect.visible;
+ break;
+ case META_FRAME_CONTROL_UNSHADE:
+ rect = &fgeom->unshade_rect.visible;
+ break;
+ case META_FRAME_CONTROL_ABOVE:
+ rect = &fgeom->above_rect.visible;
+ break;
+ case META_FRAME_CONTROL_UNABOVE:
+ rect = &fgeom->unabove_rect.visible;
+ break;
+ case META_FRAME_CONTROL_STICK:
+ rect = &fgeom->stick_rect.visible;
+ break;
+ case META_FRAME_CONTROL_UNSTICK:
+ rect = &fgeom->unstick_rect.visible;
+ break;
+ case META_FRAME_CONTROL_RESIZE_SE:
+ break;
+ case META_FRAME_CONTROL_RESIZE_S:
+ break;
+ case META_FRAME_CONTROL_RESIZE_SW:
+ break;
+ case META_FRAME_CONTROL_RESIZE_N:
+ break;
+ case META_FRAME_CONTROL_RESIZE_NE:
+ break;
+ case META_FRAME_CONTROL_RESIZE_NW:
+ break;
+ case META_FRAME_CONTROL_RESIZE_W:
+ break;
+ case META_FRAME_CONTROL_RESIZE_E:
+ break;
+ case META_FRAME_CONTROL_NONE:
+ break;
+ case META_FRAME_CONTROL_CLIENT_AREA:
+ break;
+ }
+
+ return rect;
+}
+
+#define RESIZE_EXTENDS 15
+#define TOP_RESIZE_HEIGHT 2
+static MetaFrameControl
+get_control (MetaFrames *frames,
+ MetaUIFrame *frame,
+ int x, int y)
+{
+ MetaFrameGeometry fgeom;
+ MetaFrameFlags flags;
+ gboolean has_vert, has_horiz;
+ GdkRectangle client;
+
+ meta_frames_calc_geometry (frames, frame, &fgeom);
+
+ client.x = fgeom.left_width;
+ client.y = fgeom.top_height;
+ client.width = fgeom.width - fgeom.left_width - fgeom.right_width;
+ client.height = fgeom.height - fgeom.top_height - fgeom.bottom_height;
+
+ if (POINT_IN_RECT (x, y, client))
+ return META_FRAME_CONTROL_CLIENT_AREA;
+
+ if (POINT_IN_RECT (x, y, fgeom.close_rect.clickable))
+ return META_FRAME_CONTROL_DELETE;
+
+ if (POINT_IN_RECT (x, y, fgeom.min_rect.clickable))
+ return META_FRAME_CONTROL_MINIMIZE;
+
+ if (POINT_IN_RECT (x, y, fgeom.menu_rect.clickable))
+ return META_FRAME_CONTROL_MENU;
+
+ meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_END);
+
+ has_vert = (flags & META_FRAME_ALLOWS_VERTICAL_RESIZE) != 0;
+ has_horiz = (flags & META_FRAME_ALLOWS_HORIZONTAL_RESIZE) != 0;
+
+ if (POINT_IN_RECT (x, y, fgeom.title_rect))
+ {
+ if (has_vert && y <= TOP_RESIZE_HEIGHT)
+ return META_FRAME_CONTROL_RESIZE_N;
+ else
+ return META_FRAME_CONTROL_TITLE;
+ }
+
+ if (POINT_IN_RECT (x, y, fgeom.max_rect.clickable))
+ {
+ if (flags & META_FRAME_MAXIMIZED)
+ return META_FRAME_CONTROL_UNMAXIMIZE;
+ else
+ return META_FRAME_CONTROL_MAXIMIZE;
+ }
+
+ if (POINT_IN_RECT (x, y, fgeom.shade_rect.clickable))
+ {
+ return META_FRAME_CONTROL_SHADE;
+ }
+
+ if (POINT_IN_RECT (x, y, fgeom.unshade_rect.clickable))
+ {
+ return META_FRAME_CONTROL_UNSHADE;
+ }
+
+ if (POINT_IN_RECT (x, y, fgeom.above_rect.clickable))
+ {
+ return META_FRAME_CONTROL_ABOVE;
+ }
+
+ if (POINT_IN_RECT (x, y, fgeom.unabove_rect.clickable))
+ {
+ return META_FRAME_CONTROL_UNABOVE;
+ }
+
+ if (POINT_IN_RECT (x, y, fgeom.stick_rect.clickable))
+ {
+ return META_FRAME_CONTROL_STICK;
+ }
+
+ if (POINT_IN_RECT (x, y, fgeom.unstick_rect.clickable))
+ {
+ return META_FRAME_CONTROL_UNSTICK;
+ }
+
+ /* South resize always has priority over north resize,
+ * in case of overlap.
+ */
+
+ if (y >= (fgeom.height - fgeom.bottom_height - RESIZE_EXTENDS) &&
+ x >= (fgeom.width - fgeom.right_width - RESIZE_EXTENDS))
+ {
+ if (has_vert && has_horiz)
+ return META_FRAME_CONTROL_RESIZE_SE;
+ else if (has_vert)
+ return META_FRAME_CONTROL_RESIZE_S;
+ else if (has_horiz)
+ return META_FRAME_CONTROL_RESIZE_E;
+ }
+ else if (y >= (fgeom.height - fgeom.bottom_height - RESIZE_EXTENDS) &&
+ x <= (fgeom.left_width + RESIZE_EXTENDS))
+ {
+ if (has_vert && has_horiz)
+ return META_FRAME_CONTROL_RESIZE_SW;
+ else if (has_vert)
+ return META_FRAME_CONTROL_RESIZE_S;
+ else if (has_horiz)
+ return META_FRAME_CONTROL_RESIZE_W;
+ }
+ else if (y < (fgeom.top_height + RESIZE_EXTENDS) &&
+ x < RESIZE_EXTENDS)
+ {
+ if (has_vert && has_horiz)
+ return META_FRAME_CONTROL_RESIZE_NW;
+ else if (has_vert)
+ return META_FRAME_CONTROL_RESIZE_N;
+ else if (has_horiz)
+ return META_FRAME_CONTROL_RESIZE_W;
+ }
+ else if (y < (fgeom.top_height + RESIZE_EXTENDS) &&
+ x >= (fgeom.width - RESIZE_EXTENDS))
+ {
+ if (has_vert && has_horiz)
+ return META_FRAME_CONTROL_RESIZE_NE;
+ else if (has_vert)
+ return META_FRAME_CONTROL_RESIZE_N;
+ else if (has_horiz)
+ return META_FRAME_CONTROL_RESIZE_E;
+ }
+ else if (y >= (fgeom.height - fgeom.bottom_height - RESIZE_EXTENDS))
+ {
+ if (has_vert)
+ return META_FRAME_CONTROL_RESIZE_S;
+ }
+ else if (y <= TOP_RESIZE_HEIGHT)
+ {
+ if (has_vert)
+ return META_FRAME_CONTROL_RESIZE_N;
+ else if (has_horiz)
+ return META_FRAME_CONTROL_TITLE;
+ }
+ else if (x <= fgeom.left_width)
+ {
+ if (has_horiz)
+ return META_FRAME_CONTROL_RESIZE_W;
+ }
+ else if (x >= (fgeom.width - fgeom.right_width))
+ {
+ if (has_horiz)
+ return META_FRAME_CONTROL_RESIZE_E;
+ }
+
+ if (y >= fgeom.top_height)
+ return META_FRAME_CONTROL_NONE;
+ else
+ return META_FRAME_CONTROL_TITLE;
+}
+
+void
+meta_frames_push_delay_exposes (MetaFrames *frames)
+{
+ if (frames->expose_delay_count == 0)
+ {
+ /* Make sure we've repainted things */
+ gdk_window_process_all_updates ();
+ XFlush (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+ }
+
+ frames->expose_delay_count += 1;
+}
+
+static void
+queue_pending_exposes_func (gpointer key, gpointer value, gpointer data)
+{
+ MetaUIFrame *frame;
+ MetaFrames *frames;
+
+ frames = META_FRAMES (data);
+ frame = value;
+
+ if (frame->expose_delayed)
+ {
+ invalidate_whole_window (frames, frame);
+ frame->expose_delayed = FALSE;
+ }
+}
+
+void
+meta_frames_pop_delay_exposes (MetaFrames *frames)
+{
+ g_return_if_fail (frames->expose_delay_count > 0);
+
+ frames->expose_delay_count -= 1;
+
+ if (frames->expose_delay_count == 0)
+ {
+ g_hash_table_foreach (frames->frames,
+ queue_pending_exposes_func,
+ frames);
+ }
+}
+
+static void
+invalidate_whole_window (MetaFrames *frames,
+ MetaUIFrame *frame)
+{
+ gdk_window_invalidate_rect (frame->window, NULL, FALSE);
+ invalidate_cache (frames, frame);
+}
diff --git a/src/ui/frames.h b/src/ui/frames.h
new file mode 100644
index 00000000..e9da4714
--- /dev/null
+++ b/src/ui/frames.h
@@ -0,0 +1,163 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco window frame manager widget */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_FRAMES_H
+#define META_FRAMES_H
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include "common.h"
+#include "theme.h"
+
+typedef enum
+{
+ META_FRAME_CONTROL_NONE,
+ META_FRAME_CONTROL_TITLE,
+ META_FRAME_CONTROL_DELETE,
+ META_FRAME_CONTROL_MENU,
+ META_FRAME_CONTROL_MINIMIZE,
+ META_FRAME_CONTROL_MAXIMIZE,
+ META_FRAME_CONTROL_UNMAXIMIZE,
+ META_FRAME_CONTROL_SHADE,
+ META_FRAME_CONTROL_UNSHADE,
+ META_FRAME_CONTROL_ABOVE,
+ META_FRAME_CONTROL_UNABOVE,
+ META_FRAME_CONTROL_STICK,
+ META_FRAME_CONTROL_UNSTICK,
+ META_FRAME_CONTROL_RESIZE_SE,
+ META_FRAME_CONTROL_RESIZE_S,
+ META_FRAME_CONTROL_RESIZE_SW,
+ META_FRAME_CONTROL_RESIZE_N,
+ META_FRAME_CONTROL_RESIZE_NE,
+ META_FRAME_CONTROL_RESIZE_NW,
+ META_FRAME_CONTROL_RESIZE_W,
+ META_FRAME_CONTROL_RESIZE_E,
+ META_FRAME_CONTROL_CLIENT_AREA
+} MetaFrameControl;
+
+/* This is one widget that manages all the window frames
+ * as subwindows.
+ */
+
+#define META_TYPE_FRAMES (meta_frames_get_type ())
+#define META_FRAMES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_FRAMES, MetaFrames))
+#define META_FRAMES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_FRAMES, MetaFramesClass))
+#define META_IS_FRAMES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_FRAMES))
+#define META_IS_FRAMES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_FRAMES))
+#define META_FRAMES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_FRAMES, MetaFramesClass))
+
+typedef struct _MetaFrames MetaFrames;
+typedef struct _MetaFramesClass MetaFramesClass;
+
+typedef struct _MetaUIFrame MetaUIFrame;
+
+struct _MetaUIFrame
+{
+ Window xwindow;
+ GdkWindow *window;
+ GtkStyle *style;
+ MetaFrameStyle *cache_style;
+ PangoLayout *layout;
+ int text_height;
+ char *title; /* NULL once we have a layout */
+ guint expose_delayed : 1;
+ guint shape_applied : 1;
+
+ /* FIXME get rid of this, it can just be in the MetaFrames struct */
+ MetaFrameControl prelit_control;
+};
+
+struct _MetaFrames
+{
+ GtkWindow parent_instance;
+
+ GHashTable *text_heights;
+
+ GHashTable *frames;
+
+ guint tooltip_timeout;
+ MetaUIFrame *last_motion_frame;
+
+ int expose_delay_count;
+
+ int invalidate_cache_timeout_id;
+ GList *invalidate_frames;
+ GHashTable *cache;
+};
+
+struct _MetaFramesClass
+{
+ GtkWindowClass parent_class;
+
+};
+
+GType meta_frames_get_type (void) G_GNUC_CONST;
+
+MetaFrames *meta_frames_new (int screen_number);
+
+void meta_frames_manage_window (MetaFrames *frames,
+ Window xwindow,
+ GdkWindow *window);
+void meta_frames_unmanage_window (MetaFrames *frames,
+ Window xwindow);
+void meta_frames_set_title (MetaFrames *frames,
+ Window xwindow,
+ const char *title);
+
+void meta_frames_repaint_frame (MetaFrames *frames,
+ Window xwindow);
+
+void meta_frames_get_geometry (MetaFrames *frames,
+ Window xwindow,
+ int *top_height, int *bottom_height,
+ int *left_width, int *right_width);
+
+void meta_frames_reset_bg (MetaFrames *frames,
+ Window xwindow);
+void meta_frames_unflicker_bg (MetaFrames *frames,
+ Window xwindow,
+ int target_width,
+ int target_height);
+
+void meta_frames_apply_shapes (MetaFrames *frames,
+ Window xwindow,
+ int new_window_width,
+ int new_window_height,
+ gboolean window_has_shape);
+void meta_frames_move_resize_frame (MetaFrames *frames,
+ Window xwindow,
+ int x,
+ int y,
+ int width,
+ int height);
+void meta_frames_queue_draw (MetaFrames *frames,
+ Window xwindow);
+
+void meta_frames_notify_menu_hide (MetaFrames *frames);
+
+Window meta_frames_get_moving_frame (MetaFrames *frames);
+
+void meta_frames_push_delay_exposes (MetaFrames *frames);
+void meta_frames_pop_delay_exposes (MetaFrames *frames);
+
+#endif
diff --git a/src/ui/gradient.c b/src/ui/gradient.c
new file mode 100644
index 00000000..0e739817
--- /dev/null
+++ b/src/ui/gradient.c
@@ -0,0 +1,842 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco gradient rendering */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington, 99% copied from wrlib in
+ * WindowMaker, Copyright (C) 1997-2000 Dan Pascu and Alfredo Kojima
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA. */
+
+#include "gradient.h"
+#include "util.h"
+#include <string.h>
+
+/* This is all Alfredo's and Dan's usual very nice WindowMaker code,
+ * slightly GTK-ized
+ */
+static GdkPixbuf* meta_gradient_create_horizontal (int width,
+ int height,
+ const GdkColor *from,
+ const GdkColor *to);
+static GdkPixbuf* meta_gradient_create_vertical (int width,
+ int height,
+ const GdkColor *from,
+ const GdkColor *to);
+static GdkPixbuf* meta_gradient_create_diagonal (int width,
+ int height,
+ const GdkColor *from,
+ const GdkColor *to);
+static GdkPixbuf* meta_gradient_create_multi_horizontal (int width,
+ int height,
+ const GdkColor *colors,
+ int count);
+static GdkPixbuf* meta_gradient_create_multi_vertical (int width,
+ int height,
+ const GdkColor *colors,
+ int count);
+static GdkPixbuf* meta_gradient_create_multi_diagonal (int width,
+ int height,
+ const GdkColor *colors,
+ int count);
+
+
+/* Used as the destroy notification function for gdk_pixbuf_new() */
+static void
+free_buffer (guchar *pixels, gpointer data)
+{
+ g_free (pixels);
+}
+
+static GdkPixbuf*
+blank_pixbuf (int width, int height, gboolean no_padding)
+{
+ guchar *buf;
+ int rowstride;
+
+ g_return_val_if_fail (width > 0, NULL);
+ g_return_val_if_fail (height > 0, NULL);
+
+ if (no_padding)
+ rowstride = width * 3;
+ else
+ /* Always align rows to 32-bit boundaries */
+ rowstride = 4 * ((3 * width + 3) / 4);
+
+ buf = g_try_malloc (height * rowstride);
+ if (!buf)
+ return NULL;
+
+ return gdk_pixbuf_new_from_data (buf, GDK_COLORSPACE_RGB,
+ FALSE, 8,
+ width, height, rowstride,
+ free_buffer, NULL);
+}
+
+GdkPixbuf*
+meta_gradient_create_simple (int width,
+ int height,
+ const GdkColor *from,
+ const GdkColor *to,
+ MetaGradientType style)
+{
+ switch (style)
+ {
+ case META_GRADIENT_HORIZONTAL:
+ return meta_gradient_create_horizontal (width, height,
+ from, to);
+ case META_GRADIENT_VERTICAL:
+ return meta_gradient_create_vertical (width, height,
+ from, to);
+
+ case META_GRADIENT_DIAGONAL:
+ return meta_gradient_create_diagonal (width, height,
+ from, to);
+ case META_GRADIENT_LAST:
+ break;
+ }
+ g_assert_not_reached ();
+ return NULL;
+}
+
+GdkPixbuf*
+meta_gradient_create_multi (int width,
+ int height,
+ const GdkColor *colors,
+ int n_colors,
+ MetaGradientType style)
+{
+
+ if (n_colors > 2)
+ {
+ switch (style)
+ {
+ case META_GRADIENT_HORIZONTAL:
+ return meta_gradient_create_multi_horizontal (width, height, colors, n_colors);
+ case META_GRADIENT_VERTICAL:
+ return meta_gradient_create_multi_vertical (width, height, colors, n_colors);
+ case META_GRADIENT_DIAGONAL:
+ return meta_gradient_create_multi_diagonal (width, height, colors, n_colors);
+ case META_GRADIENT_LAST:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ else if (n_colors > 1)
+ {
+ return meta_gradient_create_simple (width, height, &colors[0], &colors[1],
+ style);
+ }
+ else if (n_colors > 0)
+ {
+ return meta_gradient_create_simple (width, height, &colors[0], &colors[0],
+ style);
+ }
+ g_assert_not_reached ();
+ return NULL;
+}
+
+/* Interwoven essentially means we have two vertical gradients,
+ * cut into horizontal strips of the given thickness, and then the strips
+ * are alternated. I'm not sure what it's good for, just copied since
+ * WindowMaker had it.
+ */
+GdkPixbuf*
+meta_gradient_create_interwoven (int width,
+ int height,
+ const GdkColor colors1[2],
+ int thickness1,
+ const GdkColor colors2[2],
+ int thickness2)
+{
+
+ int i, j, k, l, ll;
+ long r1, g1, b1, dr1, dg1, db1;
+ long r2, g2, b2, dr2, dg2, db2;
+ GdkPixbuf *pixbuf;
+ unsigned char *ptr;
+ unsigned char *pixels;
+ int rowstride;
+
+ pixbuf = blank_pixbuf (width, height, FALSE);
+ if (pixbuf == NULL)
+ return NULL;
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ r1 = colors1[0].red<<8;
+ g1 = colors1[0].green<<8;
+ b1 = colors1[0].blue<<8;
+
+ r2 = colors2[0].red<<8;
+ g2 = colors2[0].green<<8;
+ b2 = colors2[0].blue<<8;
+
+ dr1 = ((colors1[1].red-colors1[0].red)<<8)/(int)height;
+ dg1 = ((colors1[1].green-colors1[0].green)<<8)/(int)height;
+ db1 = ((colors1[1].blue-colors1[0].blue)<<8)/(int)height;
+
+ dr2 = ((colors2[1].red-colors2[0].red)<<8)/(int)height;
+ dg2 = ((colors2[1].green-colors2[0].green)<<8)/(int)height;
+ db2 = ((colors2[1].blue-colors2[0].blue)<<8)/(int)height;
+
+ for (i=0,k=0,l=0,ll=thickness1; i<height; i++)
+ {
+ ptr = pixels + i * rowstride;
+
+ if (k == 0)
+ {
+ ptr[0] = (unsigned char) (r1>>16);
+ ptr[1] = (unsigned char) (g1>>16);
+ ptr[2] = (unsigned char) (b1>>16);
+ }
+ else
+ {
+ ptr[0] = (unsigned char) (r2>>16);
+ ptr[1] = (unsigned char) (g2>>16);
+ ptr[2] = (unsigned char) (b2>>16);
+ }
+
+ for (j=1; j <= width/2; j *= 2)
+ memcpy (&(ptr[j*3]), ptr, j*3);
+ memcpy (&(ptr[j*3]), ptr, (width - j)*3);
+
+ if (++l == ll)
+ {
+ if (k == 0)
+ {
+ k = 1;
+ ll = thickness2;
+ }
+ else
+ {
+ k = 0;
+ ll = thickness1;
+ }
+ l = 0;
+ }
+ r1+=dr1;
+ g1+=dg1;
+ b1+=db1;
+
+ r2+=dr2;
+ g2+=dg2;
+ b2+=db2;
+ }
+
+ return pixbuf;
+}
+
+/*
+ *----------------------------------------------------------------------
+ * meta_gradient_create_horizontal--
+ * Renders a horizontal linear gradient of the specified size in the
+ * GdkPixbuf format with a border of the specified type.
+ *
+ * Returns:
+ * A 24bit GdkPixbuf with the gradient (no alpha channel).
+ *
+ * Side effects:
+ * None
+ *----------------------------------------------------------------------
+ */
+static GdkPixbuf*
+meta_gradient_create_horizontal (int width, int height,
+ const GdkColor *from,
+ const GdkColor *to)
+{
+ int i;
+ long r, g, b, dr, dg, db;
+ GdkPixbuf *pixbuf;
+ unsigned char *ptr;
+ unsigned char *pixels;
+ int r0, g0, b0;
+ int rf, gf, bf;
+ int rowstride;
+
+ pixbuf = blank_pixbuf (width, height, FALSE);
+ if (pixbuf == NULL)
+ return NULL;
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ ptr = pixels;
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ r0 = (guchar) (from->red / 256.0);
+ g0 = (guchar) (from->green / 256.0);
+ b0 = (guchar) (from->blue / 256.0);
+ rf = (guchar) (to->red / 256.0);
+ gf = (guchar) (to->green / 256.0);
+ bf = (guchar) (to->blue / 256.0);
+
+ r = r0 << 16;
+ g = g0 << 16;
+ b = b0 << 16;
+
+ dr = ((rf-r0)<<16)/(int)width;
+ dg = ((gf-g0)<<16)/(int)width;
+ db = ((bf-b0)<<16)/(int)width;
+ /* render the first line */
+ for (i=0; i<width; i++)
+ {
+ *(ptr++) = (unsigned char)(r>>16);
+ *(ptr++) = (unsigned char)(g>>16);
+ *(ptr++) = (unsigned char)(b>>16);
+ r += dr;
+ g += dg;
+ b += db;
+ }
+
+ /* copy the first line to the other lines */
+ for (i=1; i<height; i++)
+ {
+ memcpy (&(pixels[i*rowstride]), pixels, rowstride);
+ }
+ return pixbuf;
+}
+
+/*
+ *----------------------------------------------------------------------
+ * meta_gradient_create_vertical--
+ * Renders a vertical linear gradient of the specified size in the
+ * GdkPixbuf format with a border of the specified type.
+ *
+ * Returns:
+ * A 24bit GdkPixbuf with the gradient (no alpha channel).
+ *
+ * Side effects:
+ * None
+ *----------------------------------------------------------------------
+ */
+static GdkPixbuf*
+meta_gradient_create_vertical (int width, int height,
+ const GdkColor *from,
+ const GdkColor *to)
+{
+ int i, j;
+ long r, g, b, dr, dg, db;
+ GdkPixbuf *pixbuf;
+ unsigned char *ptr;
+ int r0, g0, b0;
+ int rf, gf, bf;
+ int rowstride;
+ unsigned char *pixels;
+
+ pixbuf = blank_pixbuf (width, height, FALSE);
+ if (pixbuf == NULL)
+ return NULL;
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ r0 = (guchar) (from->red / 256.0);
+ g0 = (guchar) (from->green / 256.0);
+ b0 = (guchar) (from->blue / 256.0);
+ rf = (guchar) (to->red / 256.0);
+ gf = (guchar) (to->green / 256.0);
+ bf = (guchar) (to->blue / 256.0);
+
+ r = r0<<16;
+ g = g0<<16;
+ b = b0<<16;
+
+ dr = ((rf-r0)<<16)/(int)height;
+ dg = ((gf-g0)<<16)/(int)height;
+ db = ((bf-b0)<<16)/(int)height;
+
+ for (i=0; i<height; i++)
+ {
+ ptr = pixels + i * rowstride;
+
+ ptr[0] = (unsigned char)(r>>16);
+ ptr[1] = (unsigned char)(g>>16);
+ ptr[2] = (unsigned char)(b>>16);
+
+ for (j=1; j <= width/2; j *= 2)
+ memcpy (&(ptr[j*3]), ptr, j*3);
+ memcpy (&(ptr[j*3]), ptr, (width - j)*3);
+
+ r+=dr;
+ g+=dg;
+ b+=db;
+ }
+ return pixbuf;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ * meta_gradient_create_diagonal--
+ * Renders a diagonal linear gradient of the specified size in the
+ * GdkPixbuf format with a border of the specified type.
+ *
+ * Returns:
+ * A 24bit GdkPixbuf with the gradient (no alpha channel).
+ *
+ * Side effects:
+ * None
+ *----------------------------------------------------------------------
+ */
+
+
+static GdkPixbuf*
+meta_gradient_create_diagonal (int width, int height,
+ const GdkColor *from,
+ const GdkColor *to)
+{
+ GdkPixbuf *pixbuf, *tmp;
+ int j;
+ float a, offset;
+ unsigned char *ptr;
+ unsigned char *pixels;
+ int rowstride;
+
+ if (width == 1)
+ return meta_gradient_create_vertical (width, height, from, to);
+ else if (height == 1)
+ return meta_gradient_create_horizontal (width, height, from, to);
+
+ pixbuf = blank_pixbuf (width, height, FALSE);
+ if (pixbuf == NULL)
+ return NULL;
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ tmp = meta_gradient_create_horizontal (2*width-1, 1, from, to);
+ if (!tmp)
+ {
+ g_object_unref (G_OBJECT (pixbuf));
+ return NULL;
+ }
+
+ ptr = gdk_pixbuf_get_pixels (tmp);
+
+ a = ((float)(width - 1))/((float)(height - 1));
+ width = width * 3;
+
+ /* copy the first line to the other lines with corresponding offset */
+ for (j=0, offset=0.0; j<rowstride*height; j += rowstride)
+ {
+ memcpy (&(pixels[j]), &ptr[3*(int)offset], width);
+ offset += a;
+ }
+
+ g_object_unref (G_OBJECT (tmp));
+ return pixbuf;
+}
+
+
+static GdkPixbuf*
+meta_gradient_create_multi_horizontal (int width, int height,
+ const GdkColor *colors,
+ int count)
+{
+ int i, j, k;
+ long r, g, b, dr, dg, db;
+ GdkPixbuf *pixbuf;
+ unsigned char *ptr;
+ unsigned char *pixels;
+ int width2;
+ int rowstride;
+
+ g_return_val_if_fail (count > 2, NULL);
+
+ pixbuf = blank_pixbuf (width, height, FALSE);
+ if (pixbuf == NULL)
+ return NULL;
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ ptr = pixels;
+
+ if (count > width)
+ count = width;
+
+ if (count > 1)
+ width2 = width/(count-1);
+ else
+ width2 = width;
+
+ k = 0;
+
+ r = colors[0].red << 8;
+ g = colors[0].green << 8;
+ b = colors[0].blue << 8;
+
+ /* render the first line */
+ for (i=1; i<count; i++)
+ {
+ dr = ((int)(colors[i].red - colors[i-1].red) <<8)/(int)width2;
+ dg = ((int)(colors[i].green - colors[i-1].green)<<8)/(int)width2;
+ db = ((int)(colors[i].blue - colors[i-1].blue) <<8)/(int)width2;
+ for (j=0; j<width2; j++)
+ {
+ *ptr++ = (unsigned char)(r>>16);
+ *ptr++ = (unsigned char)(g>>16);
+ *ptr++ = (unsigned char)(b>>16);
+ r += dr;
+ g += dg;
+ b += db;
+ k++;
+ }
+ r = colors[i].red << 8;
+ g = colors[i].green << 8;
+ b = colors[i].blue << 8;
+ }
+ for (j=k; j<width; j++)
+ {
+ *ptr++ = (unsigned char)(r>>16);
+ *ptr++ = (unsigned char)(g>>16);
+ *ptr++ = (unsigned char)(b>>16);
+ }
+
+ /* copy the first line to the other lines */
+ for (i=1; i<height; i++)
+ {
+ memcpy (&(pixels[i*rowstride]), pixels, rowstride);
+ }
+ return pixbuf;
+}
+
+static GdkPixbuf*
+meta_gradient_create_multi_vertical (int width, int height,
+ const GdkColor *colors,
+ int count)
+{
+ int i, j, k;
+ long r, g, b, dr, dg, db;
+ GdkPixbuf *pixbuf;
+ unsigned char *ptr, *tmp, *pixels;
+ int height2;
+ int x;
+ int rowstride;
+
+ g_return_val_if_fail (count > 2, NULL);
+
+ pixbuf = blank_pixbuf (width, height, FALSE);
+ if (pixbuf == NULL)
+ return NULL;
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ ptr = pixels;
+
+ if (count > height)
+ count = height;
+
+ if (count > 1)
+ height2 = height/(count-1);
+ else
+ height2 = height;
+
+ k = 0;
+
+ r = colors[0].red << 8;
+ g = colors[0].green << 8;
+ b = colors[0].blue << 8;
+
+ for (i=1; i<count; i++)
+ {
+ dr = ((int)(colors[i].red - colors[i-1].red) <<8)/(int)height2;
+ dg = ((int)(colors[i].green - colors[i-1].green)<<8)/(int)height2;
+ db = ((int)(colors[i].blue - colors[i-1].blue) <<8)/(int)height2;
+
+ for (j=0; j<height2; j++)
+ {
+ ptr[0] = (unsigned char)(r>>16);
+ ptr[1] = (unsigned char)(g>>16);
+ ptr[2] = (unsigned char)(b>>16);
+
+ for (x=1; x <= width/2; x *= 2)
+ memcpy (&(ptr[x*3]), ptr, x*3);
+ memcpy (&(ptr[x*3]), ptr, (width - x)*3);
+
+ ptr += rowstride;
+
+ r += dr;
+ g += dg;
+ b += db;
+ k++;
+ }
+ r = colors[i].red << 8;
+ g = colors[i].green << 8;
+ b = colors[i].blue << 8;
+ }
+
+ if (k<height)
+ {
+ tmp = ptr;
+
+ ptr[0] = (unsigned char) (r>>16);
+ ptr[1] = (unsigned char) (g>>16);
+ ptr[2] = (unsigned char) (b>>16);
+
+ for (x=1; x <= width/2; x *= 2)
+ memcpy (&(ptr[x*3]), ptr, x*3);
+ memcpy (&(ptr[x*3]), ptr, (width - x)*3);
+
+ ptr += rowstride;
+
+ for (j=k+1; j<height; j++)
+ {
+ memcpy (ptr, tmp, rowstride);
+ ptr += rowstride;
+ }
+ }
+
+ return pixbuf;
+}
+
+
+static GdkPixbuf*
+meta_gradient_create_multi_diagonal (int width, int height,
+ const GdkColor *colors,
+ int count)
+{
+ GdkPixbuf *pixbuf, *tmp;
+ float a, offset;
+ int j;
+ unsigned char *ptr;
+ unsigned char *pixels;
+ int rowstride;
+
+ g_return_val_if_fail (count > 2, NULL);
+
+ if (width == 1)
+ return meta_gradient_create_multi_vertical (width, height, colors, count);
+ else if (height == 1)
+ return meta_gradient_create_multi_horizontal (width, height, colors, count);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
+ width, height);
+ if (pixbuf == NULL)
+ return NULL;
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ if (count > width)
+ count = width;
+ if (count > height)
+ count = height;
+
+ if (count > 2)
+ tmp = meta_gradient_create_multi_horizontal (2*width-1, 1, colors, count);
+ else
+ /* wrlib multiplies these colors by 256 before passing them in, but
+ * I think it's a bug in wrlib, so changed here. I could be wrong
+ * though, if we notice two-color multi diagonals not working.
+ */
+ tmp = meta_gradient_create_horizontal (2*width-1, 1,
+ &colors[0], &colors[1]);
+
+ if (!tmp)
+ {
+ g_object_unref (G_OBJECT (pixbuf));
+ return NULL;
+ }
+ ptr = gdk_pixbuf_get_pixels (tmp);
+
+ a = ((float)(width - 1))/((float)(height - 1));
+ width = width * 3;
+
+ /* copy the first line to the other lines with corresponding offset */
+ for (j=0, offset=0; j<rowstride*height; j += rowstride)
+ {
+ memcpy (&(pixels[j]), &ptr[3*(int)offset], width);
+ offset += a;
+ }
+
+ g_object_unref (G_OBJECT (tmp));
+ return pixbuf;
+}
+
+static void
+simple_multiply_alpha (GdkPixbuf *pixbuf,
+ guchar alpha)
+{
+ guchar *pixels;
+ int rowstride;
+ int height;
+ int row;
+
+ g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
+
+ if (alpha == 255)
+ return;
+
+ g_assert (gdk_pixbuf_get_has_alpha (pixbuf));
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ row = 0;
+ while (row < height)
+ {
+ guchar *p;
+ guchar *end;
+
+ p = pixels + row * rowstride;
+ end = p + rowstride;
+
+ while (p != end)
+ {
+ p += 3; /* skip RGB */
+
+ /* multiply the two alpha channels. not sure this is right.
+ * but some end cases are that if the pixbuf contains 255,
+ * then it should be modified to contain "alpha"; if the
+ * pixbuf contains 0, it should remain 0.
+ */
+ /* ((*p / 255.0) * (alpha / 255.0)) * 255; */
+ *p = (guchar) (((int) *p * (int) alpha) / (int) 255);
+
+ ++p; /* skip A */
+ }
+
+ ++row;
+ }
+}
+
+static void
+meta_gradient_add_alpha_horizontal (GdkPixbuf *pixbuf,
+ const unsigned char *alphas,
+ int n_alphas)
+{
+ int i, j;
+ long a, da;
+ unsigned char *p;
+ unsigned char *pixels;
+ int width2;
+ int rowstride;
+ int width, height;
+ unsigned char *gradient;
+ unsigned char *gradient_p;
+ unsigned char *gradient_end;
+
+ g_return_if_fail (n_alphas > 0);
+
+ if (n_alphas == 1)
+ {
+ /* Optimize this */
+ simple_multiply_alpha (pixbuf, alphas[0]);
+ return;
+ }
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ gradient = g_new (unsigned char, width);
+ gradient_end = gradient + width;
+
+ if (n_alphas > width)
+ n_alphas = width;
+
+ if (n_alphas > 1)
+ width2 = width / (n_alphas - 1);
+ else
+ width2 = width;
+
+ a = alphas[0] << 8;
+ gradient_p = gradient;
+
+ /* render the gradient into an array */
+ for (i = 1; i < n_alphas; i++)
+ {
+ da = (((int)(alphas[i] - (int) alphas[i-1])) << 8) / (int) width2;
+
+ for (j = 0; j < width2; j++)
+ {
+ *gradient_p++ = (a >> 8);
+
+ a += da;
+ }
+
+ a = alphas[i] << 8;
+ }
+
+ /* get leftover pixels */
+ while (gradient_p != gradient_end)
+ {
+ *gradient_p++ = a >> 8;
+ }
+
+ /* Now for each line of the pixbuf, fill in with the gradient */
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ p = pixels;
+ i = 0;
+ while (i < height)
+ {
+ unsigned char *row_end = p + rowstride;
+ gradient_p = gradient;
+
+ p += 3;
+ while (gradient_p != gradient_end)
+ {
+ /* multiply the two alpha channels. not sure this is right.
+ * but some end cases are that if the pixbuf contains 255,
+ * then it should be modified to contain "alpha"; if the
+ * pixbuf contains 0, it should remain 0.
+ */
+ /* ((*p / 255.0) * (alpha / 255.0)) * 255; */
+ *p = (guchar) (((int) *p * (int) *gradient_p) / (int) 255);
+
+ p += 4;
+ ++gradient_p;
+ }
+
+ p = row_end;
+ ++i;
+ }
+
+ g_free (gradient);
+}
+
+void
+meta_gradient_add_alpha (GdkPixbuf *pixbuf,
+ const guchar *alphas,
+ int n_alphas,
+ MetaGradientType type)
+{
+ g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
+ g_return_if_fail (gdk_pixbuf_get_has_alpha (pixbuf));
+ g_return_if_fail (n_alphas > 0);
+
+ switch (type)
+ {
+ case META_GRADIENT_HORIZONTAL:
+ meta_gradient_add_alpha_horizontal (pixbuf, alphas, n_alphas);
+ break;
+
+ case META_GRADIENT_VERTICAL:
+ g_printerr ("marco: vertical alpha channel gradient not implemented yet\n");
+ break;
+
+ case META_GRADIENT_DIAGONAL:
+ g_printerr ("marco: diagonal alpha channel gradient not implemented yet\n");
+ break;
+
+ case META_GRADIENT_LAST:
+ g_assert_not_reached ();
+ break;
+ }
+}
diff --git a/src/ui/gradient.h b/src/ui/gradient.h
new file mode 100644
index 00000000..196fd6a0
--- /dev/null
+++ b/src/ui/gradient.h
@@ -0,0 +1,65 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco gradient rendering */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington, 99% copied from wrlib in
+ * WindowMaker, Copyright (C) 1997-2000 Dan Pascu and Alfredo Kojima
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA. */
+
+#ifndef META_GRADIENT_H
+#define META_GRADIENT_H
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk/gdk.h>
+
+typedef enum
+{
+ META_GRADIENT_VERTICAL,
+ META_GRADIENT_HORIZONTAL,
+ META_GRADIENT_DIAGONAL,
+ META_GRADIENT_LAST
+} MetaGradientType;
+
+GdkPixbuf* meta_gradient_create_simple (int width,
+ int height,
+ const GdkColor *from,
+ const GdkColor *to,
+ MetaGradientType style);
+GdkPixbuf* meta_gradient_create_multi (int width,
+ int height,
+ const GdkColor *colors,
+ int n_colors,
+ MetaGradientType style);
+GdkPixbuf* meta_gradient_create_interwoven (int width,
+ int height,
+ const GdkColor colors1[2],
+ int thickness1,
+ const GdkColor colors2[2],
+ int thickness2);
+
+
+/* Generate an alpha gradient and multiply it with the existing alpha
+ * channel of the given pixbuf
+ */
+void meta_gradient_add_alpha (GdkPixbuf *pixbuf,
+ const guchar *alphas,
+ int n_alphas,
+ MetaGradientType type);
+
+
+#endif
diff --git a/src/ui/menu.c b/src/ui/menu.c
new file mode 100644
index 00000000..5207876f
--- /dev/null
+++ b/src/ui/menu.c
@@ -0,0 +1,509 @@
+/* Marco window menu */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2004 Rob Adams
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include "menu.h"
+#include "main.h"
+#include "util.h"
+#include "core.h"
+#include "themewidget.h"
+#include "metaaccellabel.h"
+#include "ui.h"
+
+typedef struct _MenuItem MenuItem;
+typedef struct _MenuData MenuData;
+
+typedef enum {
+ MENU_ITEM_SEPARATOR = 0,
+ MENU_ITEM_NORMAL,
+ MENU_ITEM_IMAGE,
+ MENU_ITEM_CHECKBOX,
+ MENU_ITEM_RADIOBUTTON,
+ MENU_ITEM_WORKSPACE_LIST,
+} MetaMenuItemType;
+
+struct _MenuItem {
+ MetaMenuOp op;
+ MetaMenuItemType type;
+ const char* stock_id;
+ const gboolean checked;
+ const char* label;
+};
+
+
+struct _MenuData {
+ MetaWindowMenu* menu;
+ MetaMenuOp op;
+};
+
+static void activate_cb(GtkWidget* menuitem, gpointer data);
+
+static MenuItem menuitems[] = {
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_MINIMIZE, MENU_ITEM_IMAGE, MARCO_STOCK_MINIMIZE, FALSE, N_("Mi_nimize")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_MAXIMIZE, MENU_ITEM_IMAGE, MARCO_STOCK_MAXIMIZE, FALSE, N_("Ma_ximize")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_UNMAXIMIZE, MENU_ITEM_IMAGE, MARCO_STOCK_RESTORE, FALSE, N_("Unma_ximize")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_SHADE, MENU_ITEM_NORMAL, NULL, FALSE, N_("Roll _Up")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_UNSHADE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Unroll")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_MOVE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Move") },
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_RESIZE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Resize")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_RECOVER, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move Titlebar On_screen")},
+ {META_MENU_OP_WORKSPACES, MENU_ITEM_SEPARATOR, NULL, FALSE, NULL}, /* separator */
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_ABOVE, MENU_ITEM_CHECKBOX, NULL, FALSE, N_("Always on _Top")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_UNABOVE, MENU_ITEM_CHECKBOX, NULL, TRUE, N_("Always on _Top")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_STICK, MENU_ITEM_RADIOBUTTON, NULL, FALSE, N_("_Always on Visible Workspace")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_UNSTICK, MENU_ITEM_RADIOBUTTON, NULL, FALSE, N_("_Only on This Workspace")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_MOVE_LEFT, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Left")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_MOVE_RIGHT, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace R_ight")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_MOVE_UP, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Up")},
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_MOVE_DOWN, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Down")},
+ {0, MENU_ITEM_WORKSPACE_LIST, NULL, FALSE, NULL},
+ {0, MENU_ITEM_SEPARATOR, NULL, FALSE, NULL}, /* separator */
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ {META_MENU_OP_DELETE, MENU_ITEM_IMAGE, MARCO_STOCK_DELETE, FALSE, N_("_Close")}
+};
+
+static void popup_position_func(GtkMenu* menu, gint* x, gint* y, gboolean* push_in, gpointer user_data)
+{
+ GtkRequisition req;
+ GdkPoint* pos;
+
+ pos = user_data;
+
+ gtk_widget_size_request(GTK_WIDGET(menu), &req);
+
+ *x = pos->x;
+ *y = pos->y;
+
+ if (meta_ui_get_direction() == META_UI_DIRECTION_RTL)
+ {
+ *x = MAX (0, *x - req.width);
+ }
+
+ /* Ensure onscreen */
+ *x = CLAMP (*x, 0, MAX(0, gdk_screen_width() - req.width));
+ *y = CLAMP (*y, 0, MAX(0, gdk_screen_height() - req.height));
+}
+
+static void menu_closed(GtkMenu* widget, gpointer data)
+{
+ MetaWindowMenu *menu;
+
+ menu = data;
+
+ meta_frames_notify_menu_hide (menu->frames);
+
+ (*menu->func)(
+ menu,
+ GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
+ menu->client_xwindow,
+ gtk_get_current_event_time (),
+ 0, 0,
+ menu->data);
+
+ /* menu may now be freed */
+}
+
+static void activate_cb(GtkWidget* menuitem, gpointer data)
+{
+ MenuData* md;
+
+ g_return_if_fail (GTK_IS_WIDGET (menuitem));
+
+ md = data;
+
+ meta_frames_notify_menu_hide(md->menu->frames);
+
+ (*md->menu->func)(
+ md->menu,
+ GDK_DISPLAY_XDISPLAY (gdk_display_get_default()),
+ md->menu->client_xwindow,
+ gtk_get_current_event_time(),
+ md->op,
+ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), "workspace")),
+ md->menu->data);
+
+ /* menu may now be freed */
+}
+
+/*
+ * Given a Display and an index, get the workspace name and add any
+ * accelerators. At the moment this means adding a _ if the name is of
+ * the form "Workspace n" where n is less than 10, and escaping any
+ * other '_'s so they do not create inadvertant accelerators.
+ *
+ * The calling code owns the string, and is reponsible to free the
+ * memory after use.
+ *
+ * See also http://mail.gnome.org/archives/mate-i18n/2008-March/msg00380.html
+ * which discusses possible i18n concerns.
+ */
+static char*
+get_workspace_name_with_accel (Display *display,
+ Window xroot,
+ int index)
+{
+ const char *name;
+ int number;
+ int charcount=0;
+
+ name = meta_core_get_workspace_name_with_index (display, xroot, index);
+
+ g_assert (name != NULL);
+
+ /*
+ * If the name is of the form "Workspace x" where x is an unsigned
+ * integer, insert a '_' before the number if it is less than 10 and
+ * return it
+ */
+ number = 0;
+ if (sscanf (name, _("Workspace %d%n"), &number, &charcount) != 0 &&
+ *(name + charcount)=='\0')
+ {
+ char *new_name;
+
+ /*
+ * Above name is a pointer into the Workspace struct. Here we make
+ * a copy copy so we can have our wicked way with it.
+ */
+ if (number == 10)
+ new_name = g_strdup_printf (_("Workspace 1_0"));
+ else
+ new_name = g_strdup_printf (_("Workspace %s%d"),
+ number < 10 ? "_" : "",
+ number);
+ return new_name;
+ }
+ else
+ {
+ /*
+ * Otherwise this is just a normal name. Escape any _ characters so that
+ * the user's workspace names do not get mangled. If the number is less
+ * than 10 we provide an accelerator.
+ */
+ char *new_name;
+ const char *source;
+ char *dest;
+
+ /*
+ * Assume the worst case, that every character is a _. We also
+ * provide memory for " (_#)"
+ */
+ new_name = g_malloc0 (strlen (name) * 2 + 6 + 1);
+
+ /*
+ * Now iterate down the strings, adding '_' to escape as we go
+ */
+ dest = new_name;
+ source = name;
+ while (*source != '\0')
+ {
+ if (*source == '_')
+ *dest++ = '_';
+ *dest++ = *source++;
+ }
+
+ /* People don't start at workspace 0, but workspace 1 */
+ if (index < 9)
+ {
+ g_snprintf (dest, 6, " (_%d)", index + 1);
+ }
+ else if (index == 9)
+ {
+ g_snprintf (dest, 6, " (_0)");
+ }
+
+ return new_name;
+ }
+}
+
+static GtkWidget* menu_item_new(MenuItem* menuitem, int workspace_id)
+{
+ unsigned int key;
+ MetaVirtualModifier mods;
+ const char* i18n_label;
+ GtkWidget* mi;
+ GtkWidget* accel_label;
+
+ if (menuitem->type == MENU_ITEM_NORMAL)
+ {
+ mi = gtk_menu_item_new ();
+ }
+ else if (menuitem->type == MENU_ITEM_IMAGE)
+ {
+ GtkWidget* image = gtk_image_new_from_icon_name(menuitem->stock_id, GTK_ICON_SIZE_MENU);
+
+ mi = gtk_image_menu_item_new();
+
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), image);
+ gtk_widget_show(image);
+ }
+ else if (menuitem->type == MENU_ITEM_CHECKBOX)
+ {
+ mi = gtk_check_menu_item_new ();
+
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mi), menuitem->checked);
+ }
+ else if (menuitem->type == MENU_ITEM_RADIOBUTTON)
+ {
+ mi = gtk_check_menu_item_new ();
+
+ gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (mi), TRUE);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi), menuitem->checked);
+ }
+ else if (menuitem->type == MENU_ITEM_WORKSPACE_LIST)
+ {
+ return NULL;
+ }
+ else
+ {
+ return gtk_separator_menu_item_new();
+ }
+
+ i18n_label = _(menuitem->label);
+ meta_core_get_menu_accelerator (menuitem->op, workspace_id, &key, &mods);
+
+ accel_label = meta_accel_label_new_with_mnemonic (i18n_label);
+ gtk_misc_set_alignment (GTK_MISC (accel_label), 0.0, 0.5);
+
+ gtk_container_add (GTK_CONTAINER (mi), accel_label);
+ gtk_widget_show (accel_label);
+
+ meta_accel_label_set_accelerator (META_ACCEL_LABEL (accel_label), key, mods);
+
+ return mi;
+}
+
+MetaWindowMenu*
+meta_window_menu_new (MetaFrames *frames,
+ MetaMenuOp ops,
+ MetaMenuOp insensitive,
+ Window client_xwindow,
+ unsigned long active_workspace,
+ int n_workspaces,
+ MetaWindowMenuFunc func,
+ gpointer data)
+{
+ int i;
+ MetaWindowMenu *menu;
+
+ /* FIXME: Modifications to 'ops' should happen in meta_window_show_menu */
+ if (n_workspaces < 2)
+ ops &= ~(META_MENU_OP_STICK | META_MENU_OP_UNSTICK | META_MENU_OP_WORKSPACES);
+ else if (n_workspaces == 2)
+ /* #151183: If we only have two workspaces, disable the menu listing them. */
+ ops &= ~(META_MENU_OP_WORKSPACES);
+
+ menu = g_new (MetaWindowMenu, 1);
+ menu->frames = frames;
+ menu->client_xwindow = client_xwindow;
+ menu->func = func;
+ menu->data = data;
+ menu->ops = ops;
+ menu->insensitive = insensitive;
+
+ menu->menu = gtk_menu_new ();
+
+ gtk_menu_set_screen (GTK_MENU (menu->menu),
+ gtk_widget_get_screen (GTK_WIDGET (frames)));
+
+ for (i = 0; i < (int) G_N_ELEMENTS (menuitems); i++)
+ {
+ MenuItem menuitem = menuitems[i];
+ if (ops & menuitem.op || menuitem.op == 0)
+ {
+ GtkWidget *mi;
+ MenuData *md;
+ unsigned int key;
+ MetaVirtualModifier mods;
+
+ mi = menu_item_new (&menuitem, -1);
+
+ /* Set the activeness of radiobuttons. */
+ switch (menuitem.op)
+ {
+ case META_MENU_OP_STICK:
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi),
+ active_workspace == 0xFFFFFFFF);
+ break;
+ case META_MENU_OP_UNSTICK:
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi),
+ active_workspace != 0xFFFFFFFF);
+ break;
+ default:
+ break;
+ }
+
+ if (menuitem.type == MENU_ITEM_WORKSPACE_LIST)
+ {
+ if (ops & META_MENU_OP_WORKSPACES)
+ {
+ Display *display;
+ Window xroot;
+ GdkScreen *screen;
+ GtkWidget *submenu;
+ int j;
+
+ MenuItem to_another_workspace = {
+ 0, MENU_ITEM_NORMAL,
+ NULL, FALSE,
+ N_("Move to Another _Workspace")
+ };
+
+ meta_verbose ("Creating %d-workspace menu current space %lu\n",
+ n_workspaces, active_workspace);
+
+ display = gdk_x11_drawable_get_xdisplay (GTK_WIDGET (frames)->window);
+
+ screen = gdk_drawable_get_screen (GTK_WIDGET (frames)->window);
+ xroot = GDK_DRAWABLE_XID (gdk_screen_get_root_window (screen));
+
+ submenu = gtk_menu_new ();
+
+ g_assert (mi==NULL);
+ mi = menu_item_new (&to_another_workspace, -1);
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), submenu);
+
+ for (j = 0; j < n_workspaces; j++)
+ {
+ char *label;
+ MenuData *md;
+ unsigned int key;
+ MetaVirtualModifier mods;
+ MenuItem moveitem;
+ GtkWidget *submi;
+
+ meta_core_get_menu_accelerator (META_MENU_OP_WORKSPACES,
+ j + 1,
+ &key, &mods);
+
+ label = get_workspace_name_with_accel (display, xroot, j);
+
+ moveitem.type = MENU_ITEM_NORMAL;
+ moveitem.op = META_MENU_OP_WORKSPACES;
+ moveitem.label = label;
+ submi = menu_item_new (&moveitem, j + 1);
+
+ g_free (label);
+
+ if ((active_workspace == (unsigned)j) && (ops & META_MENU_OP_UNSTICK))
+ gtk_widget_set_sensitive (submi, FALSE);
+
+ md = g_new (MenuData, 1);
+
+ md->menu = menu;
+ md->op = META_MENU_OP_WORKSPACES;
+
+ g_object_set_data (G_OBJECT (submi),
+ "workspace",
+ GINT_TO_POINTER (j));
+
+ gtk_signal_connect_full (GTK_OBJECT (submi),
+ "activate",
+ G_CALLBACK (activate_cb),
+ NULL,
+ md,
+ g_free, FALSE, FALSE);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (submenu), submi);
+
+ gtk_widget_show (submi);
+ }
+ }
+ else
+ meta_verbose ("not creating workspace menu\n");
+ }
+ else if (menuitem.type != MENU_ITEM_SEPARATOR)
+ {
+ meta_core_get_menu_accelerator (menuitems[i].op, -1,
+ &key, &mods);
+
+ if (insensitive & menuitem.op)
+ gtk_widget_set_sensitive (mi, FALSE);
+
+ md = g_new (MenuData, 1);
+
+ md->menu = menu;
+ md->op = menuitem.op;
+
+ gtk_signal_connect_full (GTK_OBJECT (mi),
+ "activate",
+ G_CALLBACK (activate_cb),
+ NULL,
+ md,
+ g_free, FALSE, FALSE);
+ }
+
+ if (mi)
+ {
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), mi);
+
+ gtk_widget_show (mi);
+ }
+ }
+ }
+
+ g_signal_connect (menu->menu, "selection_done", G_CALLBACK(menu_closed), menu);
+
+ return menu;
+}
+
+void meta_window_menu_popup(MetaWindowMenu* menu, int root_x, int root_y, int button, guint32 timestamp)
+{
+ GdkPoint* pt = g_new(GdkPoint, 1);
+
+ g_object_set_data_full(G_OBJECT(menu->menu), "destroy-point", pt, g_free);
+
+ pt->x = root_x;
+ pt->y = root_y;
+
+ gtk_menu_popup(GTK_MENU (menu->menu), NULL, NULL, popup_position_func, pt, button, timestamp);
+
+ if (!GTK_MENU_SHELL(menu->menu)->have_xgrab)
+ {
+ meta_warning("GtkMenu failed to grab the pointer\n");
+ }
+}
+
+void meta_window_menu_free(MetaWindowMenu* menu)
+{
+ gtk_widget_destroy(menu->menu);
+ g_free(menu);
+}
diff --git a/src/ui/menu.h b/src/ui/menu.h
new file mode 100644
index 00000000..29e85a5b
--- /dev/null
+++ b/src/ui/menu.h
@@ -0,0 +1,51 @@
+/* Marco window menu */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_MENU_H
+#define META_MENU_H
+
+#include <gtk/gtk.h>
+#include "frames.h"
+
+/* Stock icons */
+#define MARCO_STOCK_DELETE "window-close"
+#define MARCO_STOCK_RESTORE "view-restore"
+#define MARCO_STOCK_MINIMIZE "go-down"
+#define MARCO_STOCK_MAXIMIZE "view-fullscreen"
+
+
+
+struct _MetaWindowMenu {
+ MetaFrames* frames;
+ Window client_xwindow;
+ GtkWidget* menu;
+ MetaWindowMenuFunc func;
+ gpointer data;
+ MetaMenuOp ops;
+ MetaMenuOp insensitive;
+};
+
+MetaWindowMenu* meta_window_menu_new(MetaFrames* frames, MetaMenuOp ops, MetaMenuOp insensitive, Window client_xwindow, unsigned long active_workspace, int n_workspaces, MetaWindowMenuFunc func, gpointer data);
+void meta_window_menu_popup(MetaWindowMenu* menu, int root_x, int root_y, int button, guint32 timestamp);
+void meta_window_menu_free(MetaWindowMenu* menu);
+
+
+#endif
diff --git a/src/ui/metaaccellabel.c b/src/ui/metaaccellabel.c
new file mode 100644
index 00000000..5efd7d2e
--- /dev/null
+++ b/src/ui/metaaccellabel.c
@@ -0,0 +1,456 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco hacked-up GtkAccelLabel */
+/* Copyright (C) 2002 Red Hat, Inc. */
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * MetaAccelLabel: GtkLabel with accelerator monitoring facilities.
+ * Copyright (C) 1998 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2001. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include <config.h>
+#include "metaaccellabel.h"
+#include <gtk/gtk.h>
+#include <string.h>
+#include "util.h"
+
+static void meta_accel_label_class_init (MetaAccelLabelClass *klass);
+static void meta_accel_label_init (MetaAccelLabel *accel_label);
+static void meta_accel_label_destroy (GtkObject *object);
+static void meta_accel_label_finalize (GObject *object);
+static void meta_accel_label_size_request (GtkWidget *widget,
+ GtkRequisition *requisition);
+static gboolean meta_accel_label_expose_event (GtkWidget *widget,
+ GdkEventExpose *event);
+
+static void meta_accel_label_update (MetaAccelLabel *accel_label);
+static int meta_accel_label_get_accel_width (MetaAccelLabel *accel_label);
+
+
+static GtkLabelClass *parent_class = NULL;
+
+
+GType
+meta_accel_label_get_type (void)
+{
+ static GType accel_label_type = 0;
+
+ if (!accel_label_type)
+ {
+ static const GtkTypeInfo accel_label_info =
+ {
+ "MetaAccelLabel",
+ sizeof (MetaAccelLabel),
+ sizeof (MetaAccelLabelClass),
+ (GtkClassInitFunc) meta_accel_label_class_init,
+ (GtkObjectInitFunc) meta_accel_label_init,
+ /* reserved_1 */ NULL,
+ /* reserved_2 */ NULL,
+ (GtkClassInitFunc) NULL,
+ };
+
+ accel_label_type = gtk_type_unique (GTK_TYPE_LABEL, &accel_label_info);
+ }
+
+ return accel_label_type;
+}
+
+static void
+meta_accel_label_class_init (MetaAccelLabelClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+ GtkObjectClass *object_class = GTK_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gobject_class->finalize = meta_accel_label_finalize;
+
+ object_class->destroy = meta_accel_label_destroy;
+
+ widget_class->size_request = meta_accel_label_size_request;
+ widget_class->expose_event = meta_accel_label_expose_event;
+
+ class->signal_quote1 = g_strdup ("<:");
+ class->signal_quote2 = g_strdup (":>");
+ /* This is the text that should appear next to menu accelerators
+ * that use the shift key. If the text on this key isn't typically
+ * translated on keyboards used for your language, don't translate
+ * this.
+ */
+ class->mod_name_shift = g_strdup (_("Shift"));
+ /* This is the text that should appear next to menu accelerators
+ * that use the control key. If the text on this key isn't typically
+ * translated on keyboards used for your language, don't translate
+ * this.
+ */
+ class->mod_name_control = g_strdup (_("Ctrl"));
+ /* This is the text that should appear next to menu accelerators
+ * that use the alt key. If the text on this key isn't typically
+ * translated on keyboards used for your language, don't translate
+ * this.
+ */
+ class->mod_name_alt = g_strdup (_("Alt"));
+ /* This is the text that should appear next to menu accelerators
+ * that use the meta key. If the text on this key isn't typically
+ * translated on keyboards used for your language, don't translate
+ * this.
+ */
+ class->mod_name_meta = g_strdup (_("Meta"));
+ /* This is the text that should appear next to menu accelerators
+ * that use the super key. If the text on this key isn't typically
+ * translated on keyboards used for your language, don't translate
+ * this.
+ */
+ class->mod_name_super = g_strdup (_("Super"));
+ /* This is the text that should appear next to menu accelerators
+ * that use the hyper key. If the text on this key isn't typically
+ * translated on keyboards used for your language, don't translate
+ * this.
+ */
+ class->mod_name_hyper = g_strdup (_("Hyper"));
+ /* This is the text that should appear next to menu accelerators
+ * that use the mod2 key. If the text on this key isn't typically
+ * translated on keyboards used for your language, don't translate
+ * this.
+ */
+ class->mod_name_mod2 = g_strdup (_("Mod2"));
+ /* This is the text that should appear next to menu accelerators
+ * that use the mod3 key. If the text on this key isn't typically
+ * translated on keyboards used for your language, don't translate
+ * this.
+ */
+ class->mod_name_mod3 = g_strdup (_("Mod3"));
+ /* This is the text that should appear next to menu accelerators
+ * that use the mod4 key. If the text on this key isn't typically
+ * translated on keyboards used for your language, don't translate
+ * this.
+ */
+ class->mod_name_mod4 = g_strdup (_("Mod4"));
+ /* This is the text that should appear next to menu accelerators
+ * that use the mod5 key. If the text on this key isn't typically
+ * translated on keyboards used for your language, don't translate
+ * this.
+ */
+ class->mod_name_mod5 = g_strdup (_("Mod5"));
+
+ class->mod_separator = g_strdup ("+");
+ class->accel_seperator = g_strdup (" / ");
+ class->latin1_to_char = TRUE;
+}
+
+static void
+meta_accel_label_init (MetaAccelLabel *accel_label)
+{
+ accel_label->accel_padding = 3;
+ accel_label->accel_string = NULL;
+
+ meta_accel_label_update (accel_label);
+}
+
+GtkWidget*
+meta_accel_label_new_with_mnemonic (const gchar *string)
+{
+ MetaAccelLabel *accel_label;
+
+ g_return_val_if_fail (string != NULL, NULL);
+
+ accel_label = g_object_new (META_TYPE_ACCEL_LABEL, NULL);
+
+ gtk_label_set_text_with_mnemonic (GTK_LABEL (accel_label), string);
+
+ return GTK_WIDGET (accel_label);
+}
+
+static void
+meta_accel_label_destroy (GtkObject *object)
+{
+ MetaAccelLabel *accel_label = META_ACCEL_LABEL (object);
+
+
+ g_free (accel_label->accel_string);
+ accel_label->accel_string = NULL;
+
+ accel_label->accel_mods = 0;
+ accel_label->accel_key = 0;
+
+ GTK_OBJECT_CLASS (parent_class)->destroy (object);
+}
+
+static void
+meta_accel_label_finalize (GObject *object)
+{
+ MetaAccelLabel *accel_label = META_ACCEL_LABEL (object);
+
+ g_free (accel_label->accel_string);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+void
+meta_accel_label_set_accelerator (MetaAccelLabel *accel_label,
+ guint accelerator_key,
+ MetaVirtualModifier accelerator_mods)
+{
+ g_return_if_fail (META_IS_ACCEL_LABEL (accel_label));
+
+ if (accelerator_key != accel_label->accel_key ||
+ accelerator_mods != accel_label->accel_mods)
+ {
+ accel_label->accel_mods = accelerator_mods;
+ accel_label->accel_key = accelerator_key;
+
+ meta_accel_label_update (accel_label);
+ }
+}
+
+static int
+meta_accel_label_get_accel_width (MetaAccelLabel *accel_label)
+{
+ g_return_val_if_fail (META_IS_ACCEL_LABEL (accel_label), 0);
+
+ return (accel_label->accel_string_width +
+ (accel_label->accel_string_width ? accel_label->accel_padding : 0));
+}
+
+static void
+meta_accel_label_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ MetaAccelLabel *accel_label = META_ACCEL_LABEL (widget);
+ PangoLayout *layout;
+ gint width;
+
+ if (GTK_WIDGET_CLASS (parent_class)->size_request)
+ GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition);
+
+ layout = gtk_widget_create_pango_layout (widget, accel_label->accel_string);
+ pango_layout_get_pixel_size (layout, &width, NULL);
+ accel_label->accel_string_width = width;
+
+ g_object_unref (G_OBJECT (layout));
+}
+
+static gboolean
+meta_accel_label_expose_event (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ MetaAccelLabel *accel_label = META_ACCEL_LABEL (widget);
+ GtkMisc *misc = GTK_MISC (accel_label);
+ PangoLayout *layout;
+
+ if (GTK_WIDGET_DRAWABLE (accel_label))
+ {
+ int ac_width;
+
+ ac_width = meta_accel_label_get_accel_width (accel_label);
+
+ if (widget->allocation.width >= widget->requisition.width + ac_width)
+ {
+ GtkTextDirection direction = gtk_widget_get_direction (widget);
+ gint x;
+ gint y;
+
+ if (direction == GTK_TEXT_DIR_RTL)
+ {
+ widget->allocation.x += ac_width;
+ }
+ widget->allocation.width -= ac_width;
+
+ if (GTK_WIDGET_CLASS (parent_class)->expose_event)
+ GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
+
+ if (direction == GTK_TEXT_DIR_RTL)
+ {
+ widget->allocation.x -= ac_width;
+ }
+ widget->allocation.width += ac_width;
+
+ if (direction == GTK_TEXT_DIR_RTL)
+ {
+ x = widget->allocation.x + misc->xpad;
+ }
+ else
+ {
+ x = widget->allocation.x + widget->allocation.width - misc->xpad - ac_width;
+ }
+
+ y = (widget->allocation.y * (1.0 - misc->yalign) +
+ (widget->allocation.y + widget->allocation.height -
+ (widget->requisition.height - misc->ypad * 2)) *
+ misc->yalign) + 1.5;
+
+ layout = gtk_widget_create_pango_layout (widget, accel_label->accel_string);
+
+ gtk_paint_layout (widget->style,
+ widget->window,
+ GTK_WIDGET_STATE (widget),
+ FALSE,
+ &event->area,
+ widget,
+ "accellabel",
+ x, y,
+ layout);
+
+ g_object_unref (G_OBJECT (layout));
+ }
+ else
+ {
+ if (GTK_WIDGET_CLASS (parent_class)->expose_event)
+ GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+meta_accel_label_update (MetaAccelLabel *accel_label)
+{
+ MetaAccelLabelClass *class;
+ GString *gstring;
+ gboolean seen_mod = FALSE;
+ gunichar ch;
+
+ g_return_if_fail (META_IS_ACCEL_LABEL (accel_label));
+
+ class = META_ACCEL_LABEL_GET_CLASS (accel_label);
+
+ g_free (accel_label->accel_string);
+ accel_label->accel_string = NULL;
+
+ gstring = g_string_new (accel_label->accel_string);
+ g_string_append (gstring, gstring->len ? class->accel_seperator : " ");
+
+ if (accel_label->accel_mods & META_VIRTUAL_SHIFT_MASK)
+ {
+ g_string_append (gstring, class->mod_name_shift);
+ seen_mod = TRUE;
+ }
+ if (accel_label->accel_mods & META_VIRTUAL_CONTROL_MASK)
+ {
+ if (seen_mod)
+ g_string_append (gstring, class->mod_separator);
+ g_string_append (gstring, class->mod_name_control);
+ seen_mod = TRUE;
+ }
+ if (accel_label->accel_mods & META_VIRTUAL_ALT_MASK)
+ {
+ if (seen_mod)
+ g_string_append (gstring, class->mod_separator);
+ g_string_append (gstring, class->mod_name_alt);
+ seen_mod = TRUE;
+ }
+ if (accel_label->accel_mods & META_VIRTUAL_META_MASK)
+ {
+ if (seen_mod)
+ g_string_append (gstring, class->mod_separator);
+ g_string_append (gstring, class->mod_name_meta);
+ seen_mod = TRUE;
+ }
+ if (accel_label->accel_mods & META_VIRTUAL_SUPER_MASK)
+ {
+ if (seen_mod)
+ g_string_append (gstring, class->mod_separator);
+ g_string_append (gstring, class->mod_name_super);
+ seen_mod = TRUE;
+ }
+ if (accel_label->accel_mods & META_VIRTUAL_HYPER_MASK)
+ {
+ if (seen_mod)
+ g_string_append (gstring, class->mod_separator);
+ g_string_append (gstring, class->mod_name_hyper);
+ seen_mod = TRUE;
+ }
+ if (accel_label->accel_mods & META_VIRTUAL_MOD2_MASK)
+ {
+ if (seen_mod)
+ g_string_append (gstring, class->mod_separator);
+ g_string_append (gstring, class->mod_name_mod2);
+ seen_mod = TRUE;
+ }
+ if (accel_label->accel_mods & META_VIRTUAL_MOD3_MASK)
+ {
+ if (seen_mod)
+ g_string_append (gstring, class->mod_separator);
+ g_string_append (gstring, class->mod_name_mod3);
+ seen_mod = TRUE;
+ }
+ if (accel_label->accel_mods & META_VIRTUAL_MOD4_MASK)
+ {
+ if (seen_mod)
+ g_string_append (gstring, class->mod_separator);
+ g_string_append (gstring, class->mod_name_mod4);
+ seen_mod = TRUE;
+ }
+ if (accel_label->accel_mods & META_VIRTUAL_MOD5_MASK)
+ {
+ if (seen_mod)
+ g_string_append (gstring, class->mod_separator);
+ g_string_append (gstring, class->mod_name_mod5);
+ seen_mod = TRUE;
+ }
+
+ if (seen_mod)
+ g_string_append (gstring, class->mod_separator);
+
+ ch = gdk_keyval_to_unicode (accel_label->accel_key);
+ if (ch && (g_unichar_isgraph (ch) || ch == ' ') &&
+ (ch < 0x80 || class->latin1_to_char))
+ {
+ switch (ch)
+ {
+ case ' ':
+ g_string_append (gstring, "Space");
+ break;
+ case '\\':
+ g_string_append (gstring, "Backslash");
+ break;
+ default:
+ g_string_append_unichar (gstring, g_unichar_toupper (ch));
+ break;
+ }
+ }
+ else
+ {
+ gchar *tmp;
+
+ tmp = gtk_accelerator_name (accel_label->accel_key, 0);
+ if (tmp[0] != 0 && tmp[1] == 0)
+ tmp[0] = g_ascii_toupper (tmp[0]);
+ g_string_append (gstring, tmp);
+ g_free (tmp);
+ }
+
+ g_free (accel_label->accel_string);
+ accel_label->accel_string = gstring->str;
+ g_string_free (gstring, FALSE);
+
+ g_assert (accel_label->accel_string);
+ /* accel_label->accel_string = g_strdup ("-/-"); */
+
+ gtk_widget_queue_resize (GTK_WIDGET (accel_label));
+}
diff --git a/src/ui/metaaccellabel.h b/src/ui/metaaccellabel.h
new file mode 100644
index 00000000..34914c92
--- /dev/null
+++ b/src/ui/metaaccellabel.h
@@ -0,0 +1,106 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco hacked-up GtkAccelLabel */
+/* Copyright (C) 2002 Red Hat, Inc. */
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * MetaAccelLabel: GtkLabel with accelerator monitoring facilities.
+ * Copyright (C) 1998 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2001. See the AUTHORS
+ * file for a list of people on the GTK+ Team. See the ChangeLog
+ * files for a list of changes. These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#ifndef __META_ACCEL_LABEL_H__
+#define __META_ACCEL_LABEL_H__
+
+#include <gtk/gtk.h>
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define META_TYPE_ACCEL_LABEL (meta_accel_label_get_type ())
+#define META_ACCEL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_ACCEL_LABEL, MetaAccelLabel))
+#define META_ACCEL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_ACCEL_LABEL, MetaAccelLabelClass))
+#define META_IS_ACCEL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_ACCEL_LABEL))
+#define META_IS_ACCEL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_ACCEL_LABEL))
+#define META_ACCEL_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_ACCEL_LABEL, MetaAccelLabelClass))
+
+
+typedef struct _MetaAccelLabel MetaAccelLabel;
+typedef struct _MetaAccelLabelClass MetaAccelLabelClass;
+
+struct _MetaAccelLabel
+{
+ GtkLabel label;
+
+ MetaVirtualModifier accel_mods;
+ guint accel_key;
+ guint accel_padding;
+ gchar *accel_string;
+ guint16 accel_string_width;
+};
+
+struct _MetaAccelLabelClass
+{
+ GtkLabelClass parent_class;
+
+ gchar *signal_quote1;
+ gchar *signal_quote2;
+ gchar *mod_name_shift;
+ gchar *mod_name_control;
+ gchar *mod_name_alt;
+ gchar *mod_name_meta;
+ gchar *mod_name_super;
+ gchar *mod_name_hyper;
+ gchar *mod_name_mod2;
+ gchar *mod_name_mod3;
+ gchar *mod_name_mod4;
+ gchar *mod_name_mod5;
+ gchar *mod_separator;
+ gchar *accel_seperator;
+ guint latin1_to_char : 1;
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType meta_accel_label_get_type (void) G_GNUC_CONST;
+GtkWidget* meta_accel_label_new_with_mnemonic (const gchar *string);
+void meta_accel_label_set_accelerator (MetaAccelLabel *accel_label,
+ guint accelerator_key,
+ MetaVirtualModifier accelerator_mods);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __META_ACCEL_LABEL_H__ */
diff --git a/src/ui/preview-widget.c b/src/ui/preview-widget.c
new file mode 100644
index 00000000..8fcd2517
--- /dev/null
+++ b/src/ui/preview-widget.c
@@ -0,0 +1,601 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco theme preview widget */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+#define _XOPEN_SOURCE 600 /* for the maths routines over floats */
+
+#include <math.h>
+#include <gtk/gtk.h>
+#include "preview-widget.h"
+
+static void meta_preview_class_init (MetaPreviewClass *klass);
+static void meta_preview_init (MetaPreview *preview);
+static void meta_preview_size_request (GtkWidget *widget,
+ GtkRequisition *req);
+static void meta_preview_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static gboolean meta_preview_expose (GtkWidget *widget,
+ GdkEventExpose *event);
+static void meta_preview_finalize (GObject *object);
+
+static GtkWidgetClass *parent_class;
+
+GType
+meta_preview_get_type (void)
+{
+ static GType preview_type = 0;
+
+ if (!preview_type)
+ {
+ static const GtkTypeInfo preview_info =
+ {
+ "MetaPreview",
+ sizeof (MetaPreview),
+ sizeof (MetaPreviewClass),
+ (GtkClassInitFunc) meta_preview_class_init,
+ (GtkObjectInitFunc) meta_preview_init,
+ /* reserved_1 */ NULL,
+ /* reserved_2 */ NULL,
+ (GtkClassInitFunc) NULL,
+ };
+
+ preview_type = gtk_type_unique (GTK_TYPE_BIN, &preview_info);
+ }
+
+ return preview_type;
+}
+
+static void
+meta_preview_class_init (MetaPreviewClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class;
+
+ widget_class = (GtkWidgetClass*) class;
+ parent_class = g_type_class_peek (GTK_TYPE_BIN);
+
+ gobject_class->finalize = meta_preview_finalize;
+
+ widget_class->expose_event = meta_preview_expose;
+ widget_class->size_request = meta_preview_size_request;
+ widget_class->size_allocate = meta_preview_size_allocate;
+}
+
+static void
+meta_preview_init (MetaPreview *preview)
+{
+ int i;
+
+ gtk_widget_set_has_window (GTK_WIDGET (preview), FALSE);
+
+ i = 0;
+ while (i < MAX_BUTTONS_PER_CORNER)
+ {
+ preview->button_layout.left_buttons[i] = META_BUTTON_FUNCTION_LAST;
+ preview->button_layout.right_buttons[i] = META_BUTTON_FUNCTION_LAST;
+ ++i;
+ }
+
+ preview->button_layout.left_buttons[0] = META_BUTTON_FUNCTION_MENU;
+
+ preview->button_layout.right_buttons[0] = META_BUTTON_FUNCTION_MINIMIZE;
+ preview->button_layout.right_buttons[1] = META_BUTTON_FUNCTION_MAXIMIZE;
+ preview->button_layout.right_buttons[2] = META_BUTTON_FUNCTION_CLOSE;
+
+ preview->type = META_FRAME_TYPE_NORMAL;
+ preview->flags =
+ META_FRAME_ALLOWS_DELETE |
+ META_FRAME_ALLOWS_MENU |
+ META_FRAME_ALLOWS_MINIMIZE |
+ META_FRAME_ALLOWS_MAXIMIZE |
+ META_FRAME_ALLOWS_VERTICAL_RESIZE |
+ META_FRAME_ALLOWS_HORIZONTAL_RESIZE |
+ META_FRAME_HAS_FOCUS |
+ META_FRAME_ALLOWS_SHADE |
+ META_FRAME_ALLOWS_MOVE;
+
+ preview->left_width = -1;
+ preview->right_width = -1;
+ preview->top_height = -1;
+ preview->bottom_height = -1;
+}
+
+GtkWidget*
+meta_preview_new (void)
+{
+ MetaPreview *preview;
+
+ preview = gtk_type_new (META_TYPE_PREVIEW);
+
+ return GTK_WIDGET (preview);
+}
+
+static void
+meta_preview_finalize (GObject *object)
+{
+ MetaPreview *preview;
+
+ preview = META_PREVIEW (object);
+
+ g_free (preview->title);
+ preview->title = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+ensure_info (MetaPreview *preview)
+{
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (preview);
+
+ if (preview->layout == NULL)
+ {
+ PangoFontDescription *font_desc;
+ double scale;
+ PangoAttrList *attrs;
+ PangoAttribute *attr;
+
+ if (preview->theme)
+ scale = meta_theme_get_title_scale (preview->theme,
+ preview->type,
+ preview->flags);
+ else
+ scale = 1.0;
+
+ preview->layout = gtk_widget_create_pango_layout (widget,
+ preview->title);
+
+ font_desc = meta_gtk_widget_get_font_desc (widget, scale, NULL);
+
+ preview->text_height =
+ meta_pango_font_desc_get_text_height (font_desc,
+ gtk_widget_get_pango_context (widget));
+
+ attrs = pango_attr_list_new ();
+
+ attr = pango_attr_size_new (pango_font_description_get_size (font_desc));
+ attr->start_index = 0;
+ attr->end_index = G_MAXINT;
+
+ pango_attr_list_insert (attrs, attr);
+
+ pango_layout_set_attributes (preview->layout, attrs);
+
+ pango_attr_list_unref (attrs);
+
+ pango_font_description_free (font_desc);
+ }
+
+ if (preview->top_height < 0)
+ {
+ if (preview->theme)
+ {
+ meta_theme_get_frame_borders (preview->theme,
+ preview->type,
+ preview->text_height,
+ preview->flags,
+ &preview->top_height,
+ &preview->bottom_height,
+ &preview->left_width,
+ &preview->right_width);
+ }
+ else
+ {
+ preview->top_height = 0;
+ preview->bottom_height = 0;
+ preview->left_width = 0;
+ preview->right_width = 0;
+ }
+ }
+}
+
+static gboolean
+meta_preview_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ MetaPreview *preview;
+ GtkAllocation allocation;
+ int border_width;
+ int client_width;
+ int client_height;
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST] =
+ {
+ META_BUTTON_STATE_NORMAL,
+ META_BUTTON_STATE_NORMAL,
+ META_BUTTON_STATE_NORMAL,
+ META_BUTTON_STATE_NORMAL
+ };
+
+ g_return_val_if_fail (META_IS_PREVIEW (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ preview = META_PREVIEW (widget);
+
+ ensure_info (preview);
+
+ border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
+
+ gtk_widget_get_allocation (widget, &allocation);
+ client_width = allocation.width - preview->left_width - preview->right_width - border_width * 2;
+ client_height = allocation.height - preview->top_height - preview->bottom_height - border_width * 2;
+
+ if (client_width < 0)
+ client_width = 1;
+ if (client_height < 0)
+ client_height = 1;
+
+ if (preview->theme)
+ {
+ border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
+
+ meta_theme_draw_frame (preview->theme,
+ widget,
+ gtk_widget_get_window (widget),
+ &event->area,
+ allocation.x + border_width,
+ allocation.y + border_width,
+ preview->type,
+ preview->flags,
+ client_width, client_height,
+ preview->layout,
+ preview->text_height,
+ &preview->button_layout,
+ button_states,
+ meta_preview_get_mini_icon (),
+ meta_preview_get_icon ());
+ }
+
+ /* draw child */
+ return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
+}
+
+static void
+meta_preview_size_request (GtkWidget *widget,
+ GtkRequisition *req)
+{
+ MetaPreview *preview;
+ GtkWidget *child;
+ guint border_width;
+
+ preview = META_PREVIEW (widget);
+
+ ensure_info (preview);
+
+ req->width = preview->left_width + preview->right_width;
+ req->height = preview->top_height + preview->bottom_height;
+
+ child = gtk_bin_get_child (GTK_BIN (preview));
+ if (child &&
+ gtk_widget_get_visible (child))
+ {
+ GtkRequisition child_requisition;
+
+ gtk_widget_size_request (child, &child_requisition);
+
+ req->width += child_requisition.width;
+ req->height += child_requisition.height;
+ }
+ else
+ {
+#define NO_CHILD_WIDTH 80
+#define NO_CHILD_HEIGHT 20
+ req->width += NO_CHILD_WIDTH;
+ req->height += NO_CHILD_HEIGHT;
+ }
+
+ border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
+ req->width += border_width * 2;
+ req->height += border_width * 2;
+}
+
+static void
+meta_preview_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ MetaPreview *preview;
+ int border_width;
+ GtkAllocation child_allocation;
+ GtkWidget *child;
+
+ preview = META_PREVIEW (widget);
+
+ ensure_info (preview);
+
+ widget->allocation = *allocation;
+
+ border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (child &&
+ gtk_widget_get_visible (child))
+ {
+ child_allocation.x = widget->allocation.x + border_width + preview->left_width;
+ child_allocation.y = widget->allocation.y + border_width + preview->top_height;
+
+ child_allocation.width = MAX (1, widget->allocation.width - border_width * 2 - preview->left_width - preview->right_width);
+ child_allocation.height = MAX (1, widget->allocation.height - border_width * 2 - preview->top_height - preview->bottom_height);
+
+ gtk_widget_size_allocate (gtk_bin_get_child (GTK_BIN (widget)), &child_allocation);
+ }
+}
+
+static void
+clear_cache (MetaPreview *preview)
+{
+ if (preview->layout)
+ {
+ g_object_unref (G_OBJECT (preview->layout));
+ preview->layout = NULL;
+ }
+
+ preview->left_width = -1;
+ preview->right_width = -1;
+ preview->top_height = -1;
+ preview->bottom_height = -1;
+}
+
+void
+meta_preview_set_theme (MetaPreview *preview,
+ MetaTheme *theme)
+{
+ g_return_if_fail (META_IS_PREVIEW (preview));
+
+ preview->theme = theme;
+
+ clear_cache (preview);
+
+ gtk_widget_queue_resize (GTK_WIDGET (preview));
+}
+
+void
+meta_preview_set_title (MetaPreview *preview,
+ const char *title)
+{
+ g_return_if_fail (META_IS_PREVIEW (preview));
+
+ g_free (preview->title);
+ preview->title = g_strdup (title);
+
+ clear_cache (preview);
+
+ gtk_widget_queue_resize (GTK_WIDGET (preview));
+}
+
+void
+meta_preview_set_frame_type (MetaPreview *preview,
+ MetaFrameType type)
+{
+ g_return_if_fail (META_IS_PREVIEW (preview));
+
+ preview->type = type;
+
+ clear_cache (preview);
+
+ gtk_widget_queue_resize (GTK_WIDGET (preview));
+}
+
+void
+meta_preview_set_frame_flags (MetaPreview *preview,
+ MetaFrameFlags flags)
+{
+ g_return_if_fail (META_IS_PREVIEW (preview));
+
+ preview->flags = flags;
+
+ clear_cache (preview);
+
+ gtk_widget_queue_resize (GTK_WIDGET (preview));
+}
+
+void
+meta_preview_set_button_layout (MetaPreview *preview,
+ const MetaButtonLayout *button_layout)
+{
+ g_return_if_fail (META_IS_PREVIEW (preview));
+
+ preview->button_layout = *button_layout;
+
+ gtk_widget_queue_draw (GTK_WIDGET (preview));
+}
+
+GdkPixbuf*
+meta_preview_get_icon (void)
+{
+ static GdkPixbuf *default_icon = NULL;
+
+ if (default_icon == NULL)
+ {
+ GtkIconTheme *theme;
+ gboolean icon_exists;
+
+ theme = gtk_icon_theme_get_default ();
+
+ icon_exists = gtk_icon_theme_has_icon (theme, META_DEFAULT_ICON_NAME);
+
+ if (icon_exists)
+ default_icon = gtk_icon_theme_load_icon (theme,
+ META_DEFAULT_ICON_NAME,
+ META_ICON_WIDTH,
+ 0,
+ NULL);
+ else
+ default_icon = gtk_icon_theme_load_icon (theme,
+ "gtk-missing-image",
+ META_ICON_WIDTH,
+ 0,
+ NULL);
+
+ g_assert (default_icon);
+ }
+
+ return default_icon;
+}
+
+GdkPixbuf*
+meta_preview_get_mini_icon (void)
+{
+ static GdkPixbuf *default_icon = NULL;
+
+ if (default_icon == NULL)
+ {
+ GtkIconTheme *theme;
+ gboolean icon_exists;
+
+ theme = gtk_icon_theme_get_default ();
+
+ icon_exists = gtk_icon_theme_has_icon (theme, META_DEFAULT_ICON_NAME);
+
+ if (icon_exists)
+ default_icon = gtk_icon_theme_load_icon (theme,
+ META_DEFAULT_ICON_NAME,
+ META_MINI_ICON_WIDTH,
+ 0,
+ NULL);
+ else
+ default_icon = gtk_icon_theme_load_icon (theme,
+ "gtk-missing-image",
+ META_MINI_ICON_WIDTH,
+ 0,
+ NULL);
+
+ g_assert (default_icon);
+ }
+
+ return default_icon;
+}
+
+GdkRegion *
+meta_preview_get_clip_region (MetaPreview *preview, gint new_window_width, gint new_window_height)
+{
+ GdkRectangle xrect;
+ GdkRegion *corners_xregion, *window_xregion;
+ gint flags;
+ MetaFrameLayout *fgeom;
+ MetaFrameStyle *frame_style;
+
+ g_return_val_if_fail (META_IS_PREVIEW (preview), NULL);
+
+ flags = (META_PREVIEW (preview)->flags);
+
+ window_xregion = gdk_region_new ();
+
+ xrect.x = 0;
+ xrect.y = 0;
+ xrect.width = new_window_width;
+ xrect.height = new_window_height;
+
+ gdk_region_union_with_rect (window_xregion, &xrect);
+
+ if (preview->theme == NULL)
+ return window_xregion;
+
+ /* Otherwise, we do have a theme, so calculate the corners */
+ frame_style = meta_theme_get_frame_style (preview->theme,
+ META_FRAME_TYPE_NORMAL, flags);
+
+ fgeom = frame_style->layout;
+
+ corners_xregion = gdk_region_new ();
+
+ if (fgeom->top_left_corner_rounded_radius != 0)
+ {
+ const int corner = fgeom->top_left_corner_rounded_radius;
+ const float radius = sqrt(corner) + corner;
+ int i;
+
+ for (i=0; i<corner; i++)
+ {
+
+ const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
+ xrect.x = 0;
+ xrect.y = i;
+ xrect.width = width;
+ xrect.height = 1;
+
+ gdk_region_union_with_rect (corners_xregion, &xrect);
+ }
+ }
+
+ if (fgeom->top_right_corner_rounded_radius != 0)
+ {
+ const int corner = fgeom->top_right_corner_rounded_radius;
+ const float radius = sqrt(corner) + corner;
+ int i;
+
+ for (i=0; i<corner; i++)
+ {
+ const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
+ xrect.x = new_window_width - width;
+ xrect.y = i;
+ xrect.width = width;
+ xrect.height = 1;
+
+ gdk_region_union_with_rect (corners_xregion, &xrect);
+ }
+ }
+
+ if (fgeom->bottom_left_corner_rounded_radius != 0)
+ {
+ const int corner = fgeom->bottom_left_corner_rounded_radius;
+ const float radius = sqrt(corner) + corner;
+ int i;
+
+ for (i=0; i<corner; i++)
+ {
+ const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
+ xrect.x = 0;
+ xrect.y = new_window_height - i - 1;
+ xrect.width = width;
+ xrect.height = 1;
+
+ gdk_region_union_with_rect (corners_xregion, &xrect);
+ }
+ }
+
+ if (fgeom->bottom_right_corner_rounded_radius != 0)
+ {
+ const int corner = fgeom->bottom_right_corner_rounded_radius;
+ const float radius = sqrt(corner) + corner;
+ int i;
+
+ for (i=0; i<corner; i++)
+ {
+ const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5))));
+ xrect.x = new_window_width - width;
+ xrect.y = new_window_height - i - 1;
+ xrect.width = width;
+ xrect.height = 1;
+
+ gdk_region_union_with_rect (corners_xregion, &xrect);
+ }
+ }
+
+ gdk_region_subtract (window_xregion, corners_xregion);
+ gdk_region_destroy (corners_xregion);
+
+ return window_xregion;
+}
+
+
diff --git a/src/ui/preview-widget.h b/src/ui/preview-widget.h
new file mode 100644
index 00000000..4b213714
--- /dev/null
+++ b/src/ui/preview-widget.h
@@ -0,0 +1,87 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco theme preview widget */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "theme.h"
+#include <gtk/gtk.h>
+
+#ifndef META_PREVIEW_WIDGET_H
+#define META_PREVIEW_WIDGET_H
+
+#define META_TYPE_PREVIEW (meta_preview_get_type ())
+#define META_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_PREVIEW, MetaPreview))
+#define META_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_PREVIEW, MetaPreviewClass))
+#define META_IS_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_PREVIEW))
+#define META_IS_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_PREVIEW))
+#define META_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_PREVIEW, MetaPreviewClass))
+
+typedef struct _MetaPreview MetaPreview;
+typedef struct _MetaPreviewClass MetaPreviewClass;
+
+struct _MetaPreview
+{
+ GtkBin bin;
+
+ MetaTheme *theme;
+ char *title;
+ MetaFrameType type;
+ MetaFrameFlags flags;
+
+ PangoLayout *layout;
+ int text_height;
+
+ int left_width;
+ int right_width;
+ int top_height;
+ int bottom_height;
+
+ MetaButtonLayout button_layout;
+};
+
+struct _MetaPreviewClass
+{
+ GtkBinClass parent_class;
+};
+
+
+GType meta_preview_get_type (void) G_GNUC_CONST;
+GtkWidget* meta_preview_new (void);
+
+void meta_preview_set_theme (MetaPreview *preview,
+ MetaTheme *theme);
+void meta_preview_set_title (MetaPreview *preview,
+ const char *title);
+void meta_preview_set_frame_type (MetaPreview *preview,
+ MetaFrameType type);
+void meta_preview_set_frame_flags (MetaPreview *preview,
+ MetaFrameFlags flags);
+void meta_preview_set_button_layout (MetaPreview *preview,
+ const MetaButtonLayout *button_layout);
+
+GdkRegion * meta_preview_get_clip_region (MetaPreview *preview,
+ gint new_window_width,
+ gint new_window_height);
+
+GdkPixbuf* meta_preview_get_icon (void);
+GdkPixbuf* meta_preview_get_mini_icon (void);
+
+#endif
diff --git a/src/ui/resizepopup.c b/src/ui/resizepopup.c
new file mode 100644
index 00000000..67f39505
--- /dev/null
+++ b/src/ui/resizepopup.c
@@ -0,0 +1,217 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco resizing-terminal-window feedback */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "resizepopup.h"
+#include "util.h"
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+
+struct _MetaResizePopup
+{
+ GtkWidget *size_window;
+ GtkWidget *size_label;
+ Display *display;
+ int screen_number;
+
+ int vertical_size;
+ int horizontal_size;
+
+ gboolean showing;
+
+ MetaRectangle rect;
+};
+
+MetaResizePopup*
+meta_ui_resize_popup_new (Display *display,
+ int screen_number)
+{
+ MetaResizePopup *popup;
+
+ popup = g_new0 (MetaResizePopup, 1);
+
+ popup->display = display;
+ popup->screen_number = screen_number;
+
+ return popup;
+}
+
+void
+meta_ui_resize_popup_free (MetaResizePopup *popup)
+{
+ g_return_if_fail (popup != NULL);
+
+ if (popup->size_window)
+ gtk_widget_destroy (popup->size_window);
+
+ g_free (popup);
+}
+
+static void
+ensure_size_window (MetaResizePopup *popup)
+{
+ GtkWidget *frame;
+
+ if (popup->size_window)
+ return;
+
+ popup->size_window = gtk_window_new (GTK_WINDOW_POPUP);
+
+ gtk_window_set_screen (GTK_WINDOW (popup->size_window),
+ gdk_display_get_screen (gdk_x11_lookup_xdisplay (popup->display),
+ popup->screen_number));
+
+ /* never shrink the size window */
+ gtk_window_set_resizable (GTK_WINDOW (popup->size_window),
+ TRUE);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+
+ gtk_container_add (GTK_CONTAINER (popup->size_window), frame);
+
+ popup->size_label = gtk_label_new ("");
+ gtk_misc_set_padding (GTK_MISC (popup->size_label), 3, 3);
+
+ gtk_container_add (GTK_CONTAINER (frame), popup->size_label);
+
+ gtk_widget_show_all (frame);
+}
+
+static void
+update_size_window (MetaResizePopup *popup)
+{
+ char *str;
+ int x, y;
+ int width, height;
+
+ g_return_if_fail (popup->size_window != NULL);
+
+ /* Translators: This represents the size of a window. The first number is
+ * the width of the window and the second is the height.
+ */
+ str = g_strdup_printf (_("%d x %d"),
+ popup->horizontal_size,
+ popup->vertical_size);
+
+ gtk_label_set_text (GTK_LABEL (popup->size_label), str);
+
+ g_free (str);
+
+ gtk_window_get_size (GTK_WINDOW (popup->size_window), &width, &height);
+
+ x = popup->rect.x + (popup->rect.width - width) / 2;
+ y = popup->rect.y + (popup->rect.height - height) / 2;
+
+ if (GTK_WIDGET_REALIZED (popup->size_window))
+ {
+ /* using move_resize to avoid jumpiness */
+ gdk_window_move_resize (popup->size_window->window,
+ x, y,
+ width, height);
+ }
+ else
+ {
+ gtk_window_move (GTK_WINDOW (popup->size_window),
+ x, y);
+ }
+}
+
+static void
+sync_showing (MetaResizePopup *popup)
+{
+ if (popup->showing)
+ {
+ if (popup->size_window)
+ gtk_widget_show (popup->size_window);
+
+ if (popup->size_window && GTK_WIDGET_REALIZED (popup->size_window))
+ gdk_window_raise (popup->size_window->window);
+ }
+ else
+ {
+ if (popup->size_window)
+ gtk_widget_hide (popup->size_window);
+ }
+}
+
+void
+meta_ui_resize_popup_set (MetaResizePopup *popup,
+ MetaRectangle rect,
+ int base_width,
+ int base_height,
+ int width_inc,
+ int height_inc)
+{
+ gboolean need_update_size;
+ int display_w, display_h;
+
+ g_return_if_fail (popup != NULL);
+
+ need_update_size = FALSE;
+
+ display_w = rect.width - base_width;
+ if (width_inc > 0)
+ display_w /= width_inc;
+
+ display_h = rect.height - base_height;
+ if (height_inc > 0)
+ display_h /= height_inc;
+
+ if (!meta_rectangle_equal(&popup->rect, &rect) ||
+ display_w != popup->horizontal_size ||
+ display_h != popup->vertical_size)
+ need_update_size = TRUE;
+
+ popup->rect = rect;
+ popup->vertical_size = display_h;
+ popup->horizontal_size = display_w;
+
+ if (need_update_size)
+ {
+ ensure_size_window (popup);
+ update_size_window (popup);
+ }
+
+ sync_showing (popup);
+}
+
+void
+meta_ui_resize_popup_set_showing (MetaResizePopup *popup,
+ gboolean showing)
+{
+ g_return_if_fail (popup != NULL);
+
+ if (showing == popup->showing)
+ return;
+
+ popup->showing = !!showing;
+
+ if (popup->showing)
+ {
+ ensure_size_window (popup);
+ update_size_window (popup);
+ }
+
+ sync_showing (popup);
+}
diff --git a/src/ui/tabpopup.c b/src/ui/tabpopup.c
new file mode 100644
index 00000000..e0a55447
--- /dev/null
+++ b/src/ui/tabpopup.c
@@ -0,0 +1,967 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco popup window thing showing windows you can tab to */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2002 Red Hat, Inc.
+ * Copyright (C) 2005 Elijah Newren
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include "util.h"
+#include "core.h"
+#include "tabpopup.h"
+/* FIXME these two includes are 100% broken ...
+ */
+#include "../core/workspace.h"
+#include "../core/frame-private.h"
+#include "draw-workspace.h"
+#include <gtk/gtk.h>
+#include <math.h>
+
+#define OUTSIDE_SELECT_RECT 2
+#define INSIDE_SELECT_RECT 2
+
+typedef struct _TabEntry TabEntry;
+
+struct _TabEntry
+{
+ MetaTabEntryKey key;
+ char *title;
+ GdkPixbuf *icon, *dimmed_icon;
+ GtkWidget *widget;
+ GdkRectangle rect;
+ GdkRectangle inner_rect;
+ guint blank : 1;
+};
+
+struct _MetaTabPopup
+{
+ GtkWidget *window;
+ GtkWidget *label;
+ GList *current;
+ GList *entries;
+ TabEntry *current_selected_entry;
+ GtkWidget *outline_window;
+ gboolean outline;
+};
+
+static GtkWidget* selectable_image_new (GdkPixbuf *pixbuf);
+static void select_image (GtkWidget *widget);
+static void unselect_image (GtkWidget *widget);
+
+static GtkWidget* selectable_workspace_new (MetaWorkspace *workspace);
+static void select_workspace (GtkWidget *widget);
+static void unselect_workspace (GtkWidget *widget);
+
+static gboolean
+outline_window_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ gpointer data)
+{
+ MetaTabPopup *popup;
+ TabEntry *te;
+ GtkStyle *style;
+ GdkWindow *window;
+ cairo_t *cr;
+
+ popup = data;
+
+ if (!popup->outline || popup->current_selected_entry == NULL)
+ return FALSE;
+
+ te = popup->current_selected_entry;
+ window = gtk_widget_get_window (widget);
+ style = gtk_widget_get_style (widget);
+ cr = gdk_cairo_create (window);
+
+ cairo_set_line_width (cr, 1.0);
+ gdk_cairo_set_source_color (cr, &style->white);
+
+ cairo_rectangle (cr,
+ 0.5, 0.5,
+ te->rect.width - 1,
+ te->rect.height - 1);
+ cairo_stroke (cr);
+
+ cairo_rectangle (cr,
+ te->inner_rect.x - 0.5, te->inner_rect.y - 0.5,
+ te->inner_rect.width + 1,
+ te->inner_rect.height + 1);
+ cairo_stroke (cr);
+
+ cairo_destroy (cr);
+
+ return FALSE;
+}
+
+static GdkPixbuf*
+dimm_icon (GdkPixbuf *pixbuf)
+{
+ int x, y, pixel_stride, row_stride;
+ guchar *row, *pixels;
+ int w, h;
+ GdkPixbuf *dimmed_pixbuf;
+
+ if (gdk_pixbuf_get_has_alpha (pixbuf))
+ {
+ dimmed_pixbuf = gdk_pixbuf_copy (pixbuf);
+ }
+ else
+ {
+ dimmed_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
+ }
+
+ w = gdk_pixbuf_get_width (dimmed_pixbuf);
+ h = gdk_pixbuf_get_height (dimmed_pixbuf);
+
+ pixel_stride = 4;
+
+ row = gdk_pixbuf_get_pixels (dimmed_pixbuf);
+ row_stride = gdk_pixbuf_get_rowstride (dimmed_pixbuf);
+
+ for (y = 0; y < h; y++)
+ {
+ pixels = row;
+ for (x = 0; x < w; x++)
+ {
+ pixels[3] /= 2;
+ pixels += pixel_stride;
+ }
+ row += row_stride;
+ }
+ return dimmed_pixbuf;
+}
+
+static TabEntry*
+tab_entry_new (const MetaTabEntry *entry,
+ gint screen_width,
+ gboolean outline)
+{
+ TabEntry *te;
+
+ te = g_new (TabEntry, 1);
+ te->key = entry->key;
+ te->title = NULL;
+ if (entry->title)
+ {
+ gchar *str;
+ gchar *tmp;
+ gchar *formatter = "%s";
+
+ str = meta_g_utf8_strndup (entry->title, 4096);
+
+ if (entry->hidden)
+ {
+ formatter = "[%s]";
+ }
+
+ tmp = g_markup_printf_escaped (formatter, str);
+ g_free (str);
+ str = tmp;
+
+ if (entry->demands_attention)
+ {
+ /* Escape the whole line of text then markup the text and
+ * copy it back into the original buffer.
+ */
+ tmp = g_strdup_printf ("<b>%s</b>", str);
+ g_free (str);
+ str = tmp;
+ }
+
+ te->title=g_strdup(str);
+
+ g_free (str);
+ }
+ te->widget = NULL;
+ te->icon = entry->icon;
+ te->blank = entry->blank;
+ te->dimmed_icon = NULL;
+ if (te->icon)
+ {
+ g_object_ref (G_OBJECT (te->icon));
+ if (entry->hidden)
+ te->dimmed_icon = dimm_icon (entry->icon);
+ }
+
+ if (outline)
+ {
+ te->rect.x = entry->rect.x;
+ te->rect.y = entry->rect.y;
+ te->rect.width = entry->rect.width;
+ te->rect.height = entry->rect.height;
+
+ te->inner_rect.x = entry->inner_rect.x;
+ te->inner_rect.y = entry->inner_rect.y;
+ te->inner_rect.width = entry->inner_rect.width;
+ te->inner_rect.height = entry->inner_rect.height;
+ }
+ return te;
+}
+
+MetaTabPopup*
+meta_ui_tab_popup_new (const MetaTabEntry *entries,
+ int screen_number,
+ int entry_count,
+ int width,
+ gboolean outline)
+{
+ MetaTabPopup *popup;
+ int i, left, right, top, bottom;
+ int height;
+ GtkWidget *table;
+ GtkWidget *vbox;
+ GtkWidget *align;
+ GList *tmp;
+ GtkWidget *frame;
+ int max_label_width; /* the actual max width of the labels we create */
+ AtkObject *obj;
+ GdkScreen *screen;
+ int screen_width;
+
+ popup = g_new (MetaTabPopup, 1);
+
+ popup->outline_window = gtk_window_new (GTK_WINDOW_POPUP);
+
+ screen = gdk_display_get_screen (gdk_display_get_default (),
+ screen_number);
+ gtk_window_set_screen (GTK_WINDOW (popup->outline_window),
+ screen);
+
+ gtk_widget_set_app_paintable (popup->outline_window, TRUE);
+ gtk_widget_realize (popup->outline_window);
+
+ g_signal_connect (G_OBJECT (popup->outline_window), "expose_event",
+ G_CALLBACK (outline_window_expose), popup);
+
+ popup->window = gtk_window_new (GTK_WINDOW_POPUP);
+
+ gtk_window_set_screen (GTK_WINDOW (popup->window),
+ screen);
+
+ gtk_window_set_position (GTK_WINDOW (popup->window),
+ GTK_WIN_POS_CENTER_ALWAYS);
+ /* enable resizing, to get never-shrink behavior */
+ gtk_window_set_resizable (GTK_WINDOW (popup->window),
+ TRUE);
+ popup->current = NULL;
+ popup->entries = NULL;
+ popup->current_selected_entry = NULL;
+ popup->outline = outline;
+
+ screen_width = gdk_screen_get_width (screen);
+ for (i = 0; i < entry_count; ++i)
+ {
+ TabEntry* new_entry = tab_entry_new (&entries[i], screen_width, outline);
+ popup->entries = g_list_prepend (popup->entries, new_entry);
+ }
+
+ popup->entries = g_list_reverse (popup->entries);
+
+ g_assert (width > 0);
+ height = i / width;
+ if (i % width)
+ height += 1;
+
+ table = gtk_table_new (height, width, FALSE);
+ vbox = gtk_vbox_new (FALSE, 0);
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+ gtk_container_set_border_width (GTK_CONTAINER (table), 1);
+ gtk_container_add (GTK_CONTAINER (popup->window),
+ frame);
+ gtk_container_add (GTK_CONTAINER (frame),
+ vbox);
+
+ align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
+
+ gtk_box_pack_start (GTK_BOX (vbox), align, TRUE, TRUE, 0);
+
+ gtk_container_add (GTK_CONTAINER (align),
+ table);
+
+ popup->label = gtk_label_new ("");
+
+ /* Set the accessible role of the label to a status bar so it
+ * will emit name changed events that can be used by screen
+ * readers.
+ */
+ obj = gtk_widget_get_accessible (popup->label);
+ atk_object_set_role (obj, ATK_ROLE_STATUSBAR);
+
+ gtk_misc_set_padding (GTK_MISC (popup->label), 3, 3);
+
+ gtk_box_pack_end (GTK_BOX (vbox), popup->label, FALSE, FALSE, 0);
+
+ max_label_width = 0;
+ top = 0;
+ bottom = 1;
+ tmp = popup->entries;
+
+ while (tmp && top < height)
+ {
+ left = 0;
+ right = 1;
+
+ while (tmp && left < width)
+ {
+ GtkWidget *image;
+ GtkRequisition req;
+
+ TabEntry *te;
+
+ te = tmp->data;
+
+ if (te->blank)
+ {
+ /* just stick a widget here to avoid special cases */
+ image = gtk_alignment_new (0.0, 0.0, 0.0, 0.0);
+ }
+ else if (outline)
+ {
+ if (te->dimmed_icon)
+ {
+ image = selectable_image_new (te->dimmed_icon);
+ }
+ else
+ {
+ image = selectable_image_new (te->icon);
+ }
+
+ gtk_misc_set_padding (GTK_MISC (image),
+ INSIDE_SELECT_RECT + OUTSIDE_SELECT_RECT + 1,
+ INSIDE_SELECT_RECT + OUTSIDE_SELECT_RECT + 1);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.5);
+ }
+ else
+ {
+ image = selectable_workspace_new ((MetaWorkspace *) te->key);
+ }
+
+ te->widget = image;
+
+ gtk_table_attach (GTK_TABLE (table),
+ te->widget,
+ left, right, top, bottom,
+ 0, 0,
+ 0, 0);
+
+ /* Efficiency rules! */
+ gtk_label_set_markup (GTK_LABEL (popup->label),
+ te->title);
+ gtk_widget_size_request (popup->label, &req);
+ max_label_width = MAX (max_label_width, req.width);
+
+ tmp = tmp->next;
+
+ ++left;
+ ++right;
+ }
+
+ ++top;
+ ++bottom;
+ }
+
+ /* remove all the temporary text */
+ gtk_label_set_text (GTK_LABEL (popup->label), "");
+ /* Make it so that we ellipsize if the text is too long */
+ gtk_label_set_ellipsize (GTK_LABEL (popup->label), PANGO_ELLIPSIZE_END);
+
+ /* Limit the window size to no bigger than screen_width/4 */
+ if (max_label_width>(screen_width/4))
+ {
+ max_label_width = screen_width/4;
+ }
+
+ max_label_width += 20; /* add random padding */
+
+ gtk_window_set_default_size (GTK_WINDOW (popup->window),
+ max_label_width,
+ -1);
+
+ return popup;
+}
+
+static void
+free_tab_entry (gpointer data, gpointer user_data)
+{
+ TabEntry *te;
+
+ te = data;
+
+ g_free (te->title);
+ if (te->icon)
+ g_object_unref (G_OBJECT (te->icon));
+ if (te->dimmed_icon)
+ g_object_unref (G_OBJECT (te->dimmed_icon));
+
+ g_free (te);
+}
+
+void
+meta_ui_tab_popup_free (MetaTabPopup *popup)
+{
+ meta_verbose ("Destroying tab popup window\n");
+
+ gtk_widget_destroy (popup->outline_window);
+ gtk_widget_destroy (popup->window);
+
+ g_list_foreach (popup->entries, free_tab_entry, NULL);
+
+ g_list_free (popup->entries);
+
+ g_free (popup);
+}
+
+void
+meta_ui_tab_popup_set_showing (MetaTabPopup *popup,
+ gboolean showing)
+{
+ if (showing)
+ {
+ gtk_widget_show_all (popup->window);
+ }
+ else
+ {
+ if (GTK_WIDGET_VISIBLE (popup->window))
+ {
+ meta_verbose ("Hiding tab popup window\n");
+ gtk_widget_hide (popup->window);
+ meta_core_increment_event_serial (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+ }
+ }
+}
+
+static void
+display_entry (MetaTabPopup *popup,
+ TabEntry *te)
+{
+ GdkRectangle rect;
+ GdkRegion *region;
+ GdkRegion *inner_region;
+
+
+ if (popup->current_selected_entry)
+ {
+ if (popup->outline)
+ unselect_image (popup->current_selected_entry->widget);
+ else
+ unselect_workspace (popup->current_selected_entry->widget);
+ }
+
+ gtk_label_set_markup (GTK_LABEL (popup->label), te->title);
+
+ if (popup->outline)
+ select_image (te->widget);
+ else
+ select_workspace (te->widget);
+
+ if (popup->outline)
+ {
+ /* Do stuff behind gtk's back */
+ gdk_window_hide (popup->outline_window->window);
+ meta_core_increment_event_serial (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+
+ rect = te->rect;
+ rect.x = 0;
+ rect.y = 0;
+
+ gdk_window_move_resize (popup->outline_window->window,
+ te->rect.x, te->rect.y,
+ te->rect.width, te->rect.height);
+
+ gdk_window_set_background (popup->outline_window->window,
+ &popup->outline_window->style->black);
+
+ region = gdk_region_rectangle (&rect);
+ inner_region = gdk_region_rectangle (&te->inner_rect);
+ gdk_region_subtract (region, inner_region);
+ gdk_region_destroy (inner_region);
+
+ gdk_window_shape_combine_region (popup->outline_window->window,
+ region,
+ 0, 0);
+
+ gdk_region_destroy (region);
+
+ /* This should piss off gtk a bit, but we don't want to raise
+ * above the tab popup. So, instead of calling gtk_widget_show,
+ * we manually set the window as mapped and then manually map it
+ * with gdk functions.
+ */
+ GTK_WIDGET_SET_FLAGS (popup->outline_window, GTK_MAPPED);
+ gdk_window_show_unraised (popup->outline_window->window);
+ }
+
+ /* Must be before we handle an expose for the outline window */
+ popup->current_selected_entry = te;
+}
+
+void
+meta_ui_tab_popup_forward (MetaTabPopup *popup)
+{
+ if (popup->current != NULL)
+ popup->current = popup->current->next;
+
+ if (popup->current == NULL)
+ popup->current = popup->entries;
+
+ if (popup->current != NULL)
+ {
+ TabEntry *te;
+
+ te = popup->current->data;
+
+ display_entry (popup, te);
+ }
+}
+
+void
+meta_ui_tab_popup_backward (MetaTabPopup *popup)
+{
+ if (popup->current != NULL)
+ popup->current = popup->current->prev;
+
+ if (popup->current == NULL)
+ popup->current = g_list_last (popup->entries);
+
+ if (popup->current != NULL)
+ {
+ TabEntry *te;
+
+ te = popup->current->data;
+
+ display_entry (popup, te);
+ }
+}
+
+MetaTabEntryKey
+meta_ui_tab_popup_get_selected (MetaTabPopup *popup)
+{
+ if (popup->current)
+ {
+ TabEntry *te;
+
+ te = popup->current->data;
+
+ return te->key;
+ }
+ else
+ return (MetaTabEntryKey)None;
+}
+
+void
+meta_ui_tab_popup_select (MetaTabPopup *popup,
+ MetaTabEntryKey key)
+{
+ GList *tmp;
+
+ /* Note, "key" may not be in the list of entries; other code assumes
+ * it's OK to pass in a key that isn't.
+ */
+
+ tmp = popup->entries;
+ while (tmp != NULL)
+ {
+ TabEntry *te;
+
+ te = tmp->data;
+
+ if (te->key == key)
+ {
+ popup->current = tmp;
+
+ display_entry (popup, te);
+
+ return;
+ }
+
+ tmp = tmp->next;
+ }
+}
+
+#define META_TYPE_SELECT_IMAGE (meta_select_image_get_type ())
+#define META_SELECT_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_SELECT_IMAGE, MetaSelectImage))
+
+typedef struct _MetaSelectImage MetaSelectImage;
+typedef struct _MetaSelectImageClass MetaSelectImageClass;
+
+struct _MetaSelectImage
+{
+ GtkImage parent_instance;
+ guint selected : 1;
+};
+
+struct _MetaSelectImageClass
+{
+ GtkImageClass parent_class;
+};
+
+
+static GType meta_select_image_get_type (void) G_GNUC_CONST;
+
+static GtkWidget*
+selectable_image_new (GdkPixbuf *pixbuf)
+{
+ GtkWidget *w;
+
+ w = g_object_new (meta_select_image_get_type (), NULL);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (w), pixbuf);
+
+ return w;
+}
+
+static void
+select_image (GtkWidget *widget)
+{
+ META_SELECT_IMAGE (widget)->selected = TRUE;
+ gtk_widget_queue_draw (widget);
+}
+
+static void
+unselect_image (GtkWidget *widget)
+{
+ META_SELECT_IMAGE (widget)->selected = FALSE;
+ gtk_widget_queue_draw (widget);
+}
+
+static void meta_select_image_class_init (MetaSelectImageClass *klass);
+static gboolean meta_select_image_expose_event (GtkWidget *widget,
+ GdkEventExpose *event);
+
+static GtkImageClass *parent_class;
+
+GType
+meta_select_image_get_type (void)
+{
+ static GType image_type = 0;
+
+ if (!image_type)
+ {
+ static const GTypeInfo image_info =
+ {
+ sizeof (MetaSelectImageClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) meta_select_image_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (MetaSelectImage),
+ 16, /* n_preallocs */
+ (GInstanceInitFunc) NULL,
+ };
+
+ image_type = g_type_register_static (GTK_TYPE_IMAGE, "MetaSelectImage", &image_info, 0);
+ }
+
+ return image_type;
+}
+
+static void
+meta_select_image_class_init (MetaSelectImageClass *klass)
+{
+ GtkWidgetClass *widget_class;
+
+ parent_class = g_type_class_peek (gtk_image_get_type ());
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+
+ widget_class->expose_event = meta_select_image_expose_event;
+}
+
+static gboolean
+meta_select_image_expose_event (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ if (META_SELECT_IMAGE (widget)->selected)
+ {
+ int x, y, w, h;
+ GtkMisc *misc;
+ GtkStyle *style;
+ GtkStateType state;
+ cairo_t *cr;
+
+ misc = GTK_MISC (widget);
+
+ x = (widget->allocation.x * (1.0 - misc->xalign) +
+ (widget->allocation.x + widget->allocation.width
+ - (widget->requisition.width - misc->xpad * 2)) *
+ misc->xalign) + 0.5;
+ y = (widget->allocation.y * (1.0 - misc->yalign) +
+ (widget->allocation.y + widget->allocation.height
+ - (widget->requisition.height - misc->ypad * 2)) *
+ misc->yalign) + 0.5;
+
+ x -= INSIDE_SELECT_RECT + 1;
+ y -= INSIDE_SELECT_RECT + 1;
+
+ w = widget->requisition.width - OUTSIDE_SELECT_RECT * 2 - 1;
+ h = widget->requisition.height - OUTSIDE_SELECT_RECT * 2 - 1;
+
+ style = gtk_widget_get_style (widget);
+ state = gtk_widget_get_state (widget);
+ cr = gdk_cairo_create (widget->window);
+
+ cairo_set_line_width (cr, 2.0);
+ gdk_cairo_set_source_color (cr, &style->fg[state]);
+
+ cairo_rectangle (cr, x, y, w + 1, h + 1);
+ cairo_stroke (cr);
+
+ cairo_set_line_width (cr, 1.0);
+#if 0
+ gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_SELECTED]);
+ cairo_rectangle (cr, x, y, w, h);
+ cairo_fill (cr);
+#endif
+
+#if 0
+ gtk_paint_focus (widget->style, widget->window,
+ &event->area, widget, "meta-tab-image",
+ x, y, w, h);
+#endif
+
+ cairo_destroy (cr);
+ }
+
+ return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
+}
+
+#define META_TYPE_SELECT_WORKSPACE (meta_select_workspace_get_type ())
+#define META_SELECT_WORKSPACE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_SELECT_WORKSPACE, MetaSelectWorkspace))
+
+typedef struct _MetaSelectWorkspace MetaSelectWorkspace;
+typedef struct _MetaSelectWorkspaceClass MetaSelectWorkspaceClass;
+
+struct _MetaSelectWorkspace
+{
+ GtkDrawingArea parent_instance;
+ MetaWorkspace *workspace;
+ guint selected : 1;
+};
+
+struct _MetaSelectWorkspaceClass
+{
+ GtkDrawingAreaClass parent_class;
+};
+
+
+static GType meta_select_workspace_get_type (void) G_GNUC_CONST;
+
+#define SELECT_OUTLINE_WIDTH 2
+#define MINI_WORKSPACE_WIDTH 48
+
+static GtkWidget*
+selectable_workspace_new (MetaWorkspace *workspace)
+{
+ GtkWidget *widget;
+ double screen_aspect;
+
+ widget = g_object_new (meta_select_workspace_get_type (), NULL);
+
+ screen_aspect = (double) workspace->screen->rect.height /
+ (double) workspace->screen->rect.width;
+
+ /* account for select rect */
+ gtk_widget_set_size_request (widget,
+ MINI_WORKSPACE_WIDTH + SELECT_OUTLINE_WIDTH * 2,
+ MINI_WORKSPACE_WIDTH * screen_aspect + SELECT_OUTLINE_WIDTH * 2);
+
+ META_SELECT_WORKSPACE (widget)->workspace = workspace;
+
+ return widget;
+}
+
+static void
+select_workspace (GtkWidget *widget)
+{
+ META_SELECT_WORKSPACE(widget)->selected = TRUE;
+ gtk_widget_queue_draw (widget);
+}
+
+static void
+unselect_workspace (GtkWidget *widget)
+{
+ META_SELECT_WORKSPACE (widget)->selected = FALSE;
+ gtk_widget_queue_draw (widget);
+}
+
+static void meta_select_workspace_class_init (MetaSelectWorkspaceClass *klass);
+
+static gboolean meta_select_workspace_expose_event (GtkWidget *widget,
+ GdkEventExpose *event);
+
+GType
+meta_select_workspace_get_type (void)
+{
+ static GType workspace_type = 0;
+
+ if (!workspace_type)
+ {
+ static const GTypeInfo workspace_info =
+ {
+ sizeof (MetaSelectWorkspaceClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) meta_select_workspace_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (MetaSelectWorkspace),
+ 16, /* n_preallocs */
+ (GInstanceInitFunc) NULL,
+ };
+
+ workspace_type = g_type_register_static (GTK_TYPE_DRAWING_AREA,
+ "MetaSelectWorkspace",
+ &workspace_info,
+ 0);
+ }
+
+ return workspace_type;
+}
+
+static void
+meta_select_workspace_class_init (MetaSelectWorkspaceClass *klass)
+{
+ GtkWidgetClass *widget_class;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+
+ widget_class->expose_event = meta_select_workspace_expose_event;
+}
+
+/**
+ * meta_convert_meta_to_wnck() converts a MetaWindow to a
+ * WnckWindowDisplayInfo window that is used to build a thumbnail of a
+ * workspace.
+ **/
+static WnckWindowDisplayInfo
+meta_convert_meta_to_wnck (MetaWindow *window, MetaScreen *screen)
+{
+ WnckWindowDisplayInfo wnck_window;
+ wnck_window.icon = window->icon;
+ wnck_window.mini_icon = window->mini_icon;
+
+ wnck_window.is_active = FALSE;
+ if (window == window->display->expected_focus_window)
+ wnck_window.is_active = TRUE;
+
+ if (window->frame)
+ {
+ wnck_window.x = window->frame->rect.x;
+ wnck_window.y = window->frame->rect.y;
+ wnck_window.width = window->frame->rect.width;
+ wnck_window.height = window->frame->rect.height;
+ }
+ else
+ {
+ wnck_window.x = window->rect.x;
+ wnck_window.y = window->rect.y;
+ wnck_window.width = window->rect.width;
+ wnck_window.height = window->rect.height;
+ }
+ return wnck_window;
+}
+
+
+static gboolean
+meta_select_workspace_expose_event (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ MetaWorkspace *workspace;
+ WnckWindowDisplayInfo *windows;
+ GtkStyle *style;
+ cairo_t *cr;
+ int i, n_windows;
+ GList *tmp, *list;
+
+ workspace = META_SELECT_WORKSPACE (widget)->workspace;
+
+ list = meta_stack_list_windows (workspace->screen->stack, workspace);
+ n_windows = g_list_length (list);
+ windows = g_new (WnckWindowDisplayInfo, n_windows);
+
+ tmp = list;
+ i = 0;
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+ gboolean ignoreable_sticky;
+
+ window = tmp->data;
+
+ ignoreable_sticky = window->on_all_workspaces &&
+ workspace != workspace->screen->active_workspace;
+
+ if (window->skip_pager ||
+ !meta_window_showing_on_its_workspace (window) ||
+ window->unmaps_pending ||
+ ignoreable_sticky)
+ {
+ --n_windows;
+ }
+ else
+ {
+ windows[i] = meta_convert_meta_to_wnck (window, workspace->screen);
+ i++;
+ }
+ tmp = tmp->next;
+ }
+
+ g_list_free (list);
+
+ wnck_draw_workspace (widget,
+ widget->window,
+ SELECT_OUTLINE_WIDTH,
+ SELECT_OUTLINE_WIDTH,
+ widget->allocation.width - SELECT_OUTLINE_WIDTH * 2,
+ widget->allocation.height - SELECT_OUTLINE_WIDTH * 2,
+ workspace->screen->rect.width,
+ workspace->screen->rect.height,
+ NULL,
+ (workspace->screen->active_workspace == workspace),
+ windows,
+ n_windows);
+
+ g_free (windows);
+
+ if (META_SELECT_WORKSPACE (widget)->selected)
+ {
+ style = gtk_widget_get_style (widget);
+ cr = gdk_cairo_create (widget->window);
+
+ gdk_cairo_set_source_color (cr,
+ &style->fg[gtk_widget_get_state (widget)]);
+ cairo_set_line_width (cr, SELECT_OUTLINE_WIDTH);
+
+ cairo_rectangle (cr,
+ SELECT_OUTLINE_WIDTH / 2.0, SELECT_OUTLINE_WIDTH / 2.0,
+ widget->allocation.width - SELECT_OUTLINE_WIDTH,
+ widget->allocation.height - SELECT_OUTLINE_WIDTH);
+ cairo_stroke (cr);
+
+ cairo_destroy (cr);
+ }
+
+ return TRUE;
+}
+
diff --git a/src/ui/testgradient.c b/src/ui/testgradient.c
new file mode 100644
index 00000000..fd1b09c7
--- /dev/null
+++ b/src/ui/testgradient.c
@@ -0,0 +1,336 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco gradient test program */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA. */
+
+#include "gradient.h"
+#include <gtk/gtk.h>
+
+typedef void (* RenderGradientFunc) (GdkDrawable *drawable,
+ cairo_t *cr,
+ int width,
+ int height);
+
+static void
+draw_checkerboard (GdkDrawable *drawable,
+ int width,
+ int height)
+{
+ gint i, j, xcount, ycount;
+ GdkColor color1, color2;
+ cairo_t *cr;
+
+#define CHECK_SIZE 10
+#define SPACING 2
+
+ color1.red = 30000;
+ color1.green = 30000;
+ color1.blue = 30000;
+
+ color2.red = 50000;
+ color2.green = 50000;
+ color2.blue = 50000;
+
+ cr = gdk_cairo_create (drawable);
+
+ xcount = 0;
+ i = SPACING;
+ while (i < width)
+ {
+ j = SPACING;
+ ycount = xcount % 2; /* start with even/odd depending on row */
+ while (j < height)
+ {
+ if (ycount % 2)
+ gdk_cairo_set_source_color (cr, &color1);
+ else
+ gdk_cairo_set_source_color (cr, &color2);
+
+ /* If we're outside event->area, this will do nothing.
+ * It might be mildly more efficient if we handled
+ * the clipping ourselves, but again we're feeling lazy.
+ */
+ cairo_rectangle (cr, i, j, CHECK_SIZE, CHECK_SIZE);
+ cairo_fill (cr);
+
+ j += CHECK_SIZE + SPACING;
+ ++ycount;
+ }
+
+ i += CHECK_SIZE + SPACING;
+ ++xcount;
+ }
+
+ cairo_destroy (cr);
+}
+
+static void
+render_simple (GdkDrawable *drawable,
+ cairo_t *cr,
+ int width, int height,
+ MetaGradientType type,
+ gboolean with_alpha)
+{
+ GdkPixbuf *pixbuf;
+ GdkColor from, to;
+
+ gdk_color_parse ("blue", &from);
+ gdk_color_parse ("green", &to);
+
+ pixbuf = meta_gradient_create_simple (width, height,
+ &from, &to,
+ type);
+
+ if (with_alpha)
+ {
+ const unsigned char alphas[] = { 0xff, 0xaa, 0x2f, 0x0, 0xcc, 0xff, 0xff };
+
+ if (!gdk_pixbuf_get_has_alpha (pixbuf))
+ {
+ GdkPixbuf *new_pixbuf;
+
+ new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
+ g_object_unref (G_OBJECT (pixbuf));
+ pixbuf = new_pixbuf;
+ }
+
+ meta_gradient_add_alpha (pixbuf,
+ alphas, G_N_ELEMENTS (alphas),
+ META_GRADIENT_HORIZONTAL);
+
+ draw_checkerboard (drawable, width, height);
+ }
+
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_rectangle (cr, 0, 0, width, height);
+ cairo_fill (cr);
+
+ g_object_unref (G_OBJECT (pixbuf));
+}
+
+static void
+render_vertical_func (GdkDrawable *drawable,
+ cairo_t *cr,
+ int width, int height)
+{
+ render_simple (drawable, cr, width, height, META_GRADIENT_VERTICAL, FALSE);
+}
+
+static void
+render_horizontal_func (GdkDrawable *drawable,
+ cairo_t *cr,
+ int width, int height)
+{
+ render_simple (drawable, cr, width, height, META_GRADIENT_HORIZONTAL, FALSE);
+}
+
+static void
+render_diagonal_func (GdkDrawable *drawable,
+ cairo_t *cr,
+ int width, int height)
+{
+ render_simple (drawable, cr, width, height, META_GRADIENT_DIAGONAL, FALSE);
+}
+
+static void
+render_diagonal_alpha_func (GdkDrawable *drawable,
+ cairo_t *cr,
+ int width, int height)
+{
+ render_simple (drawable, cr, width, height, META_GRADIENT_DIAGONAL, TRUE);
+}
+
+static void
+render_multi (GdkDrawable *drawable,
+ cairo_t *cr,
+ int width, int height,
+ MetaGradientType type)
+{
+ GdkPixbuf *pixbuf;
+#define N_COLORS 5
+ GdkColor colors[N_COLORS];
+
+ gdk_color_parse ("red", &colors[0]);
+ gdk_color_parse ("blue", &colors[1]);
+ gdk_color_parse ("orange", &colors[2]);
+ gdk_color_parse ("pink", &colors[3]);
+ gdk_color_parse ("green", &colors[4]);
+
+ pixbuf = meta_gradient_create_multi (width, height,
+ colors, N_COLORS,
+ type);
+
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_rectangle (cr, 0, 0, width, height);
+ cairo_fill (cr);
+
+ g_object_unref (G_OBJECT (pixbuf));
+#undef N_COLORS
+}
+
+static void
+render_vertical_multi_func (GdkDrawable *drawable,
+ cairo_t *cr,
+ int width, int height)
+{
+ render_multi (drawable, cr, width, height, META_GRADIENT_VERTICAL);
+}
+
+static void
+render_horizontal_multi_func (GdkDrawable *drawable,
+ cairo_t *cr,
+ int width, int height)
+{
+ render_multi (drawable, cr, width, height, META_GRADIENT_HORIZONTAL);
+}
+
+static void
+render_diagonal_multi_func (GdkDrawable *drawable,
+ cairo_t *cr,
+ int width, int height)
+{
+ render_multi (drawable, cr, width, height, META_GRADIENT_DIAGONAL);
+}
+
+static void
+render_interwoven_func (GdkDrawable *drawable,
+ cairo_t *cr,
+ int width, int height)
+{
+ GdkPixbuf *pixbuf;
+#define N_COLORS 4
+ GdkColor colors[N_COLORS];
+
+ gdk_color_parse ("red", &colors[0]);
+ gdk_color_parse ("blue", &colors[1]);
+ gdk_color_parse ("pink", &colors[2]);
+ gdk_color_parse ("green", &colors[3]);
+
+ pixbuf = meta_gradient_create_interwoven (width, height,
+ colors, height / 10,
+ colors + 2, height / 14);
+
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_rectangle (cr, 0, 0, width, height);
+ cairo_fill (cr);
+
+ g_object_unref (G_OBJECT (pixbuf));
+}
+
+static gboolean
+expose_callback (GtkWidget *widget,
+ GdkEventExpose *event,
+ gpointer data)
+{
+ RenderGradientFunc func = data;
+ GdkWindow *window;
+ GtkAllocation allocation;
+ GtkStyle *style;
+ cairo_t *cr;
+
+ style = gtk_widget_get_style (widget);
+ gtk_widget_get_allocation (widget, &allocation);
+
+ window = gtk_widget_get_window (widget);
+ cr = gdk_cairo_create (window);
+ gdk_cairo_set_source_color (cr, &style->fg[gtk_widget_get_state (widget)]);
+
+ (* func) (window,
+ cr,
+ allocation.width,
+ allocation.height);
+
+ cairo_destroy (cr);
+
+ return TRUE;
+}
+
+static GtkWidget*
+create_gradient_window (const char *title,
+ RenderGradientFunc func)
+{
+ GtkWidget *window;
+ GtkWidget *drawing_area;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ gtk_window_set_title (GTK_WINDOW (window), title);
+
+ drawing_area = gtk_drawing_area_new ();
+
+ gtk_widget_set_size_request (drawing_area, 1, 1);
+
+ gtk_window_set_default_size (GTK_WINDOW (window), 175, 175);
+
+ g_signal_connect (G_OBJECT (drawing_area),
+ "expose_event",
+ G_CALLBACK (expose_callback),
+ func);
+
+ gtk_container_add (GTK_CONTAINER (window), drawing_area);
+
+ gtk_widget_show_all (window);
+
+ return window;
+}
+
+static void
+meta_gradient_test (void)
+{
+ GtkWidget *window;
+
+ window = create_gradient_window ("Simple vertical",
+ render_vertical_func);
+
+ window = create_gradient_window ("Simple horizontal",
+ render_horizontal_func);
+
+ window = create_gradient_window ("Simple diagonal",
+ render_diagonal_func);
+
+ window = create_gradient_window ("Multi vertical",
+ render_vertical_multi_func);
+
+ window = create_gradient_window ("Multi horizontal",
+ render_horizontal_multi_func);
+
+ window = create_gradient_window ("Multi diagonal",
+ render_diagonal_multi_func);
+
+ window = create_gradient_window ("Interwoven",
+ render_interwoven_func);
+
+ window = create_gradient_window ("Simple diagonal with horizontal multi alpha",
+ render_diagonal_alpha_func);
+
+}
+
+int
+main (int argc, char **argv)
+{
+ gtk_init (&argc, &argv);
+
+ meta_gradient_test ();
+
+ gtk_main ();
+
+ return 0;
+}
+
diff --git a/src/ui/theme-parser.c b/src/ui/theme-parser.c
new file mode 100644
index 00000000..12f01061
--- /dev/null
+++ b/src/ui/theme-parser.c
@@ -0,0 +1,4212 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco theme parsing */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "theme-parser.h"
+#include "util.h"
+#include <string.h>
+#include <stdlib.h>
+
+/* This is a hack for Ubuntu's metacity themes
+ * Its safe for unable or not.
+ * I am using this define to know what code was added */
+#define USE_UBUNTU_CODE 1
+
+typedef enum
+{
+ STATE_START,
+ STATE_THEME,
+ /* info section */
+ STATE_INFO,
+ STATE_NAME,
+ STATE_AUTHOR,
+ STATE_COPYRIGHT,
+ STATE_DATE,
+ STATE_DESCRIPTION,
+ /* constants */
+ STATE_CONSTANT,
+ /* geometry */
+ STATE_FRAME_GEOMETRY,
+ STATE_DISTANCE,
+ STATE_BORDER,
+ STATE_ASPECT_RATIO,
+ /* draw ops */
+ STATE_DRAW_OPS,
+ STATE_LINE,
+ STATE_RECTANGLE,
+ STATE_ARC,
+ STATE_CLIP,
+ STATE_TINT,
+ STATE_GRADIENT,
+ STATE_IMAGE,
+ STATE_GTK_ARROW,
+ STATE_GTK_BOX,
+ STATE_GTK_VLINE,
+ STATE_ICON,
+ STATE_TITLE,
+ STATE_INCLUDE, /* include another draw op list */
+ STATE_TILE, /* tile another draw op list */
+ /* sub-parts of gradient */
+ STATE_COLOR,
+ /* frame style */
+ STATE_FRAME_STYLE,
+ STATE_PIECE,
+ STATE_BUTTON,
+#ifdef USE_UBUNTU_CODE
+ STATE_SHADOW,
+ STATE_PADDING,
+#endif
+ /* style set */
+ STATE_FRAME_STYLE_SET,
+ STATE_FRAME,
+ /* assigning style sets to windows */
+ STATE_WINDOW,
+ /* things we don't use any more but we can still parse: */
+ STATE_MENU_ICON,
+ STATE_FALLBACK
+} ParseState;
+
+typedef struct
+{
+ GSList *states;
+
+ const char *theme_name; /* name of theme (directory it's in) */
+ char *theme_file; /* theme filename */
+ char *theme_dir; /* dir the theme is inside */
+ MetaTheme *theme; /* theme being parsed */
+ guint format_version; /* version of format of theme file */
+ char *name; /* name of named thing being parsed */
+ MetaFrameLayout *layout; /* layout being parsed if any */
+ MetaDrawOpList *op_list; /* op list being parsed if any */
+ MetaDrawOp *op; /* op being parsed if any */
+ MetaFrameStyle *style; /* frame style being parsed if any */
+ MetaFrameStyleSet *style_set; /* frame style set being parsed if any */
+ MetaFramePiece piece; /* position of piece being parsed */
+ MetaButtonType button_type; /* type of button/menuitem being parsed */
+ MetaButtonState button_state; /* state of button being parsed */
+} ParseInfo;
+
+static void set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...) G_GNUC_PRINTF (5, 6);
+
+static void add_context_to_error (GError **err,
+ GMarkupParseContext *context);
+
+static void parse_info_init (ParseInfo *info);
+static void parse_info_free (ParseInfo *info);
+
+static void push_state (ParseInfo *info,
+ ParseState state);
+static void pop_state (ParseInfo *info);
+static ParseState peek_state (ParseInfo *info);
+
+
+static void parse_toplevel_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+static void parse_info_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+static void parse_geometry_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+static void parse_draw_op_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+static void parse_gradient_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+static void parse_style_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+static void parse_style_set_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+
+static void parse_piece_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+
+static void parse_button_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+
+#ifdef USE_UBUNTU_CODE
+static void parse_shadow_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+
+static void parse_padding_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+#endif
+static void parse_menu_icon_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+
+static void start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error);
+static void text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+
+/* Translators: This means that an attribute which should have been found
+ * on an XML element was not in fact found.
+ */
+#define ATTRIBUTE_NOT_FOUND _("No \"%s\" attribute on element <%s>")
+
+static GMarkupParser marco_theme_parser = {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ NULL,
+ NULL
+};
+
+static void
+set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...)
+{
+ int line, ch;
+ va_list args;
+ char *str;
+
+ g_markup_parse_context_get_position (context, &line, &ch);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ g_set_error (err, error_domain, error_code,
+ _("Line %d character %d: %s"),
+ line, ch, str);
+
+ g_free (str);
+}
+
+static void
+add_context_to_error (GError **err,
+ GMarkupParseContext *context)
+{
+ int line, ch;
+ char *str;
+
+ if (err == NULL || *err == NULL)
+ return;
+
+ g_markup_parse_context_get_position (context, &line, &ch);
+
+ str = g_strdup_printf (_("Line %d character %d: %s"),
+ line, ch, (*err)->message);
+ g_free ((*err)->message);
+ (*err)->message = str;
+}
+
+static void
+parse_info_init (ParseInfo *info)
+{
+ info->theme_file = NULL;
+ info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
+ info->theme = NULL;
+ info->name = NULL;
+ info->layout = NULL;
+ info->op_list = NULL;
+ info->op = NULL;
+ info->style = NULL;
+ info->style_set = NULL;
+ info->piece = META_FRAME_PIECE_LAST;
+ info->button_type = META_BUTTON_TYPE_LAST;
+ info->button_state = META_BUTTON_STATE_LAST;
+}
+
+static void
+parse_info_free (ParseInfo *info)
+{
+ g_free (info->theme_file);
+ g_free (info->theme_dir);
+
+ g_slist_free (info->states);
+
+ if (info->theme)
+ meta_theme_free (info->theme);
+
+ if (info->layout)
+ meta_frame_layout_unref (info->layout);
+
+ if (info->op_list)
+ meta_draw_op_list_unref (info->op_list);
+
+ if (info->op)
+ meta_draw_op_free (info->op);
+
+ if (info->style)
+ meta_frame_style_unref (info->style);
+
+ if (info->style_set)
+ meta_frame_style_set_unref (info->style_set);
+}
+
+static void
+push_state (ParseInfo *info,
+ ParseState state)
+{
+ info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
+}
+
+static void
+pop_state (ParseInfo *info)
+{
+ g_return_if_fail (info->states != NULL);
+
+ info->states = g_slist_remove (info->states, info->states->data);
+}
+
+static ParseState
+peek_state (ParseInfo *info)
+{
+ g_return_val_if_fail (info->states != NULL, STATE_START);
+
+ return GPOINTER_TO_INT (info->states->data);
+}
+
+#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
+
+typedef struct
+{
+ const char *name;
+ const char **retloc;
+ gboolean required;
+} LocateAttr;
+
+/* Attribute names can have a leading '!' to indicate that they are
+ * required.
+ */
+static gboolean
+locate_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error,
+ const char *first_attribute_name,
+ const char **first_attribute_retloc,
+ ...)
+{
+ va_list args;
+ const char *name;
+ const char **retloc;
+ int n_attrs;
+#define MAX_ATTRS 24
+ LocateAttr attrs[MAX_ATTRS];
+ gboolean retval;
+ int i;
+
+ g_return_val_if_fail (first_attribute_name != NULL, FALSE);
+ g_return_val_if_fail (first_attribute_retloc != NULL, FALSE);
+
+ retval = TRUE;
+
+ /* FIXME: duplicated code; refactor loop */
+ n_attrs = 1;
+ attrs[0].name = first_attribute_name;
+ attrs[0].retloc = first_attribute_retloc;
+ attrs[0].required = attrs[0].name[0]=='!';
+ if (attrs[0].required)
+ attrs[0].name++; /* skip past it */
+ *first_attribute_retloc = NULL;
+
+ va_start (args, first_attribute_retloc);
+
+ name = va_arg (args, const char*);
+ retloc = va_arg (args, const char**);
+
+ while (name != NULL)
+ {
+ g_return_val_if_fail (retloc != NULL, FALSE);
+
+ g_assert (n_attrs < MAX_ATTRS);
+
+ attrs[n_attrs].name = name;
+ attrs[n_attrs].retloc = retloc;
+ attrs[n_attrs].required = attrs[n_attrs].name[0]=='!';
+ if (attrs[n_attrs].required)
+ attrs[n_attrs].name++; /* skip past it */
+
+ n_attrs += 1;
+ *retloc = NULL;
+
+ name = va_arg (args, const char*);
+ retloc = va_arg (args, const char**);
+ }
+
+ va_end (args);
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ int j;
+ gboolean found;
+
+ found = FALSE;
+ j = 0;
+ while (j < n_attrs)
+ {
+ if (strcmp (attrs[j].name, attribute_names[i]) == 0)
+ {
+ retloc = attrs[j].retloc;
+
+ if (*retloc != NULL)
+ {
+
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Attribute \"%s\" repeated twice on the same <%s> element"),
+ attrs[j].name, element_name);
+ retval = FALSE;
+ goto out;
+ }
+
+ *retloc = attribute_values[i];
+ found = TRUE;
+ }
+
+ ++j;
+ }
+
+ if (!found)
+ {
+ j = 0;
+ while (j < n_attrs)
+ {
+ g_warning ("It could have been %s.\n", attrs[j++].name);
+ }
+
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Attribute \"%s\" is invalid on <%s> element in this context"),
+ attribute_names[i], element_name);
+ retval = FALSE;
+ goto out;
+ }
+
+ ++i;
+ }
+
+ /* Did we catch them all? */
+ i = 0;
+ while (i < n_attrs)
+ {
+ if (attrs[i].required && *(attrs[i].retloc)==NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ ATTRIBUTE_NOT_FOUND,
+ attrs[i].name, element_name);
+ retval = FALSE;
+ goto out;
+ }
+
+ ++i;
+ }
+
+ out:
+ return retval;
+}
+
+static gboolean
+check_no_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (attribute_names[0] != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Attribute \"%s\" is invalid on <%s> element in this context"),
+ attribute_names[0], element_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#define MAX_REASONABLE 4096
+static gboolean
+parse_positive_integer (const char *str,
+ int *val,
+ GMarkupParseContext *context,
+ MetaTheme *theme,
+ GError **error)
+{
+ char *end;
+ long l;
+ int j;
+
+ *val = 0;
+
+ end = NULL;
+
+ /* Is str a constant? */
+
+ if (META_THEME_ALLOWS (theme, META_THEME_UBIQUITOUS_CONSTANTS) &&
+ meta_theme_lookup_int_constant (theme, str, &j))
+ {
+ /* Yes. */
+ l = j;
+ }
+ else
+ {
+ /* No. Let's try parsing it instead. */
+
+ l = strtol (str, &end, 10);
+
+ if (end == NULL || end == str)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Could not parse \"%s\" as an integer"),
+ str);
+ return FALSE;
+ }
+
+ if (*end != '\0')
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Did not understand trailing characters \"%s\" in string \"%s\""),
+ end, str);
+ return FALSE;
+ }
+ }
+
+ if (l < 0)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Integer %ld must be positive"), l);
+ return FALSE;
+ }
+
+ if (l > MAX_REASONABLE)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Integer %ld is too large, current max is %d"),
+ l, MAX_REASONABLE);
+ return FALSE;
+ }
+
+ *val = (int) l;
+
+ return TRUE;
+}
+
+static gboolean
+parse_double (const char *str,
+ double *val,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ char *end;
+
+ *val = 0;
+
+ end = NULL;
+
+ *val = g_ascii_strtod (str, &end);
+
+ if (end == NULL || end == str)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Could not parse \"%s\" as a floating point number"),
+ str);
+ return FALSE;
+ }
+
+ if (*end != '\0')
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Did not understand trailing characters \"%s\" in string \"%s\""),
+ end, str);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+parse_boolean (const char *str,
+ gboolean *val,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ if (strcmp ("true", str) == 0)
+ *val = TRUE;
+ else if (strcmp ("false", str) == 0)
+ *val = FALSE;
+ else
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Boolean values must be \"true\" or \"false\" not \"%s\""),
+ str);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+parse_rounding (const char *str,
+ guint *val,
+ GMarkupParseContext *context,
+ MetaTheme *theme,
+ GError **error)
+{
+ if (strcmp ("true", str) == 0)
+ *val = 5; /* historical "true" value */
+ else if (strcmp ("false", str) == 0)
+ *val = 0;
+ else
+ {
+ int tmp;
+ gboolean result;
+ if (!META_THEME_ALLOWS (theme, META_THEME_VARIED_ROUND_CORNERS))
+ {
+ /* Not known in this version, so bail. */
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Boolean values must be \"true\" or \"false\" not \"%s\""),
+ str);
+ return FALSE;
+ }
+
+ result = parse_positive_integer (str, &tmp, context, theme, error);
+
+ *val = tmp;
+
+ return result;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+parse_angle (const char *str,
+ double *val,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ if (!parse_double (str, val, context, error))
+ return FALSE;
+
+ if (*val < (0.0 - 1e6) || *val > (360.0 + 1e6))
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Angle must be between 0.0 and 360.0, was %g\n"),
+ *val);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+parse_alpha (const char *str,
+ MetaAlphaGradientSpec **spec_ret,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ char **split;
+ int i;
+ int n_alphas;
+ MetaAlphaGradientSpec *spec;
+
+ *spec_ret = NULL;
+
+ split = g_strsplit (str, ":", -1);
+
+ i = 0;
+ while (split[i])
+ ++i;
+
+ if (i == 0)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Could not parse \"%s\" as a floating point number"),
+ str);
+
+ g_strfreev (split);
+
+ return FALSE;
+ }
+
+ n_alphas = i;
+
+ /* FIXME allow specifying horizontal/vertical/diagonal in theme format,
+ * once we implement vertical/diagonal in gradient.c
+ */
+ spec = meta_alpha_gradient_spec_new (META_GRADIENT_HORIZONTAL,
+ n_alphas);
+
+ i = 0;
+ while (i < n_alphas)
+ {
+ double v;
+
+ if (!parse_double (split[i], &v, context, error))
+ {
+ /* clear up, but don't set error: it was set by parse_double */
+ g_strfreev (split);
+ meta_alpha_gradient_spec_free (spec);
+
+ return FALSE;
+ }
+
+ if (v < (0.0 - 1e-6) || v > (1.0 + 1e-6))
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Alpha must be between 0.0 (invisible) and 1.0 (fully opaque), was %g\n"),
+ v);
+
+ g_strfreev (split);
+ meta_alpha_gradient_spec_free (spec);
+
+ return FALSE;
+ }
+
+ spec->alphas[i] = (unsigned char) (v * 255);
+
+ ++i;
+ }
+
+ g_strfreev (split);
+
+ *spec_ret = spec;
+
+ return TRUE;
+}
+
+static MetaColorSpec*
+parse_color (MetaTheme *theme,
+ const char *str,
+ GError **err)
+{
+ char* referent;
+
+ if (META_THEME_ALLOWS (theme, META_THEME_COLOR_CONSTANTS) &&
+ meta_theme_lookup_color_constant (theme, str, &referent))
+ {
+ if (referent)
+ return meta_color_spec_new_from_string (referent, err);
+
+ /* no need to free referent: it's a pointer into the actual hash table */
+ }
+
+ return meta_color_spec_new_from_string (str, err);
+}
+
+static gboolean
+parse_title_scale (const char *str,
+ double *val,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ double factor;
+
+ if (strcmp (str, "xx-small") == 0)
+ factor = PANGO_SCALE_XX_SMALL;
+ else if (strcmp (str, "x-small") == 0)
+ factor = PANGO_SCALE_X_SMALL;
+ else if (strcmp (str, "small") == 0)
+ factor = PANGO_SCALE_SMALL;
+ else if (strcmp (str, "medium") == 0)
+ factor = PANGO_SCALE_MEDIUM;
+ else if (strcmp (str, "large") == 0)
+ factor = PANGO_SCALE_LARGE;
+ else if (strcmp (str, "x-large") == 0)
+ factor = PANGO_SCALE_X_LARGE;
+ else if (strcmp (str, "xx-large") == 0)
+ factor = PANGO_SCALE_XX_LARGE;
+ else
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Invalid title scale \"%s\" (must be one of xx-small,x-small,small,medium,large,x-large,xx-large)\n"),
+ str);
+ return FALSE;
+ }
+
+ *val = factor;
+
+ return TRUE;
+}
+
+static void
+parse_toplevel_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_THEME);
+
+ if (ELEMENT_IS ("info"))
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ push_state (info, STATE_INFO);
+ }
+ else if (ELEMENT_IS ("constant"))
+ {
+ const char *name;
+ const char *value;
+ int ival = 0;
+ double dval = 0.0;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!name", &name, "!value", &value,
+ NULL))
+ return;
+
+ /* We don't know how a a constant is going to be used, so we have guess its
+ * type from its contents:
+ *
+ * - Starts like a number and contains a '.': float constant
+ * - Starts like a number and doesn't contain a '.': int constant
+ * - Starts with anything else: a color constant.
+ * (colors always start with # or a letter)
+ */
+ if (value[0] == '.' || value[0] == '+' || value[0] == '-' || (value[0] >= '0' && value[0] <= '9'))
+ {
+ if (strchr (value, '.'))
+ {
+ if (!parse_double (value, &dval, context, error))
+ return;
+
+ if (!meta_theme_define_float_constant (info->theme,
+ name,
+ dval,
+ error))
+ {
+ add_context_to_error (error, context);
+ return;
+ }
+ }
+ else
+ {
+ if (!parse_positive_integer (value, &ival, context, info->theme, error))
+ return;
+
+ if (!meta_theme_define_int_constant (info->theme,
+ name,
+ ival,
+ error))
+ {
+ add_context_to_error (error, context);
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (!meta_theme_define_color_constant (info->theme,
+ name,
+ value,
+ error))
+ {
+ add_context_to_error (error, context);
+ return;
+ }
+ }
+
+ push_state (info, STATE_CONSTANT);
+ }
+ else if (ELEMENT_IS ("frame_geometry"))
+ {
+ const char *name = NULL;
+ const char *parent = NULL;
+ const char *has_title = NULL;
+ const char *title_scale = NULL;
+ const char *rounded_top_left = NULL;
+ const char *rounded_top_right = NULL;
+ const char *rounded_bottom_left = NULL;
+ const char *rounded_bottom_right = NULL;
+ const char *hide_buttons = NULL;
+ gboolean has_title_val;
+ guint rounded_top_left_val;
+ guint rounded_top_right_val;
+ guint rounded_bottom_left_val;
+ guint rounded_bottom_right_val;
+ gboolean hide_buttons_val;
+ double title_scale_val;
+ MetaFrameLayout *parent_layout;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!name", &name, "parent", &parent,
+ "has_title", &has_title, "title_scale", &title_scale,
+ "rounded_top_left", &rounded_top_left,
+ "rounded_top_right", &rounded_top_right,
+ "rounded_bottom_left", &rounded_bottom_left,
+ "rounded_bottom_right", &rounded_bottom_right,
+ "hide_buttons", &hide_buttons,
+ NULL))
+ return;
+
+ has_title_val = TRUE;
+ if (has_title && !parse_boolean (has_title, &has_title_val, context, error))
+ return;
+
+ hide_buttons_val = FALSE;
+ if (hide_buttons && !parse_boolean (hide_buttons, &hide_buttons_val, context, error))
+ return;
+
+ rounded_top_left_val = 0;
+ rounded_top_right_val = 0;
+ rounded_bottom_left_val = 0;
+ rounded_bottom_right_val = 0;
+
+ if (rounded_top_left && !parse_rounding (rounded_top_left, &rounded_top_left_val, context, info->theme, error))
+ return;
+ if (rounded_top_right && !parse_rounding (rounded_top_right, &rounded_top_right_val, context, info->theme, error))
+ return;
+ if (rounded_bottom_left && !parse_rounding (rounded_bottom_left, &rounded_bottom_left_val, context, info->theme, error))
+ return;
+ if (rounded_bottom_right && !parse_rounding (rounded_bottom_right, &rounded_bottom_right_val, context, info->theme, error))
+ return;
+
+ title_scale_val = 1.0;
+ if (title_scale && !parse_title_scale (title_scale, &title_scale_val, context, error))
+ return;
+
+ if (meta_theme_lookup_layout (info->theme, name))
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> name \"%s\" used a second time"),
+ element_name, name);
+ return;
+ }
+
+ parent_layout = NULL;
+ if (parent)
+ {
+ parent_layout = meta_theme_lookup_layout (info->theme, parent);
+ if (parent_layout == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> parent \"%s\" has not been defined"),
+ element_name, parent);
+ return;
+ }
+ }
+
+ g_assert (info->layout == NULL);
+
+ if (parent_layout)
+ info->layout = meta_frame_layout_copy (parent_layout);
+ else
+ info->layout = meta_frame_layout_new ();
+
+ if (has_title) /* only if explicit, otherwise inherit */
+ info->layout->has_title = has_title_val;
+
+ if (META_THEME_ALLOWS (info->theme, META_THEME_HIDDEN_BUTTONS) && hide_buttons_val)
+ info->layout->hide_buttons = hide_buttons_val;
+
+ if (title_scale)
+ info->layout->title_scale = title_scale_val;
+
+ if (rounded_top_left)
+ info->layout->top_left_corner_rounded_radius = rounded_top_left_val;
+
+ if (rounded_top_right)
+ info->layout->top_right_corner_rounded_radius = rounded_top_right_val;
+
+ if (rounded_bottom_left)
+ info->layout->bottom_left_corner_rounded_radius = rounded_bottom_left_val;
+
+ if (rounded_bottom_right)
+ info->layout->bottom_right_corner_rounded_radius = rounded_bottom_right_val;
+
+ meta_theme_insert_layout (info->theme, name, info->layout);
+
+ push_state (info, STATE_FRAME_GEOMETRY);
+ }
+ else if (ELEMENT_IS ("draw_ops"))
+ {
+ const char *name = NULL;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!name", &name,
+ NULL))
+ return;
+
+ if (meta_theme_lookup_draw_op_list (info->theme, name))
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> name \"%s\" used a second time"),
+ element_name, name);
+ return;
+ }
+
+ g_assert (info->op_list == NULL);
+ info->op_list = meta_draw_op_list_new (2);
+
+ meta_theme_insert_draw_op_list (info->theme, name, info->op_list);
+
+ push_state (info, STATE_DRAW_OPS);
+ }
+ else if (ELEMENT_IS ("frame_style"))
+ {
+ const char *name = NULL;
+ const char *parent = NULL;
+ const char *geometry = NULL;
+ const char *background = NULL;
+ const char *alpha = NULL;
+ MetaFrameStyle *parent_style;
+ MetaFrameLayout *layout;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!name", &name, "parent", &parent,
+ "geometry", &geometry,
+ "background", &background,
+ "alpha", &alpha,
+ NULL))
+ return;
+
+ if (meta_theme_lookup_style (info->theme, name))
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> name \"%s\" used a second time"),
+ element_name, name);
+ return;
+ }
+
+ parent_style = NULL;
+ if (parent)
+ {
+ parent_style = meta_theme_lookup_style (info->theme, parent);
+ if (parent_style == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> parent \"%s\" has not been defined"),
+ element_name, parent);
+ return;
+ }
+ }
+
+ layout = NULL;
+ if (geometry)
+ {
+ layout = meta_theme_lookup_layout (info->theme, geometry);
+ if (layout == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> geometry \"%s\" has not been defined"),
+ element_name, geometry);
+ return;
+ }
+ }
+ else if (parent_style)
+ {
+ layout = parent_style->layout;
+ }
+
+ if (layout == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> must specify either a geometry or a parent that has a geometry"),
+ element_name);
+ return;
+ }
+
+ g_assert (info->style == NULL);
+
+ info->style = meta_frame_style_new (parent_style);
+ g_assert (info->style->layout == NULL);
+ meta_frame_layout_ref (layout);
+ info->style->layout = layout;
+
+ if (background != NULL && META_THEME_ALLOWS (info->theme, META_THEME_FRAME_BACKGROUNDS))
+ {
+ info->style->window_background_color = meta_color_spec_new_from_string (background, error);
+ if (!info->style->window_background_color)
+ return;
+
+ if (alpha != NULL)
+ {
+
+ gboolean success;
+ MetaAlphaGradientSpec *alpha_vector;
+
+ g_clear_error (error);
+ /* fortunately, we already have a routine to parse alpha values,
+ * though it produces a vector of them, which is a superset of
+ * what we want.
+ */
+ success = parse_alpha (alpha, &alpha_vector, context, error);
+ if (!success)
+ return;
+
+ /* alpha_vector->alphas must contain at least one element */
+ info->style->window_background_alpha = alpha_vector->alphas[0];
+
+ meta_alpha_gradient_spec_free (alpha_vector);
+ }
+ }
+ else if (alpha != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("You must specify a background for an alpha value to be meaningful"));
+ return;
+ }
+
+ meta_theme_insert_style (info->theme, name, info->style);
+
+ push_state (info, STATE_FRAME_STYLE);
+ }
+ else if (ELEMENT_IS ("frame_style_set"))
+ {
+ const char *name = NULL;
+ const char *parent = NULL;
+ MetaFrameStyleSet *parent_set;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!name", &name, "parent", &parent,
+ NULL))
+ return;
+
+ if (meta_theme_lookup_style_set (info->theme, name))
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> name \"%s\" used a second time"),
+ element_name, name);
+ return;
+ }
+
+ parent_set = NULL;
+ if (parent)
+ {
+ parent_set = meta_theme_lookup_style_set (info->theme, parent);
+ if (parent_set == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> parent \"%s\" has not been defined"),
+ element_name, parent);
+ return;
+ }
+ }
+
+ g_assert (info->style_set == NULL);
+
+ info->style_set = meta_frame_style_set_new (parent_set);
+
+ meta_theme_insert_style_set (info->theme, name, info->style_set);
+
+ push_state (info, STATE_FRAME_STYLE_SET);
+ }
+ else if (ELEMENT_IS ("window"))
+ {
+ const char *type_name = NULL;
+ const char *style_set_name = NULL;
+ MetaFrameStyleSet *style_set;
+ MetaFrameType type;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!type", &type_name, "!style_set", &style_set_name,
+ NULL))
+ return;
+
+ type = meta_frame_type_from_string (type_name);
+
+ if (type == META_FRAME_TYPE_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Unknown type \"%s\" on <%s> element"),
+ type_name, element_name);
+ return;
+ }
+
+ style_set = meta_theme_lookup_style_set (info->theme,
+ style_set_name);
+
+ if (style_set == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Unknown style_set \"%s\" on <%s> element"),
+ style_set_name, element_name);
+ return;
+ }
+
+ if (info->theme->style_sets_by_type[type] != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Window type \"%s\" has already been assigned a style set"),
+ type_name);
+ return;
+ }
+
+ meta_frame_style_set_ref (style_set);
+ info->theme->style_sets_by_type[type] = style_set;
+
+ push_state (info, STATE_WINDOW);
+ }
+ else if (ELEMENT_IS ("menu_icon"))
+ {
+ /* Not supported any more, but we have to parse it if they include it,
+ * for backwards compatibility.
+ */
+ g_assert (info->op_list == NULL);
+
+ push_state (info, STATE_MENU_ICON);
+ }
+ else if (ELEMENT_IS ("fallback"))
+ {
+ /* Not supported any more, but we have to parse it if they include it,
+ * for backwards compatibility.
+ */
+ push_state (info, STATE_FALLBACK);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "metacity_theme");
+ }
+}
+
+static void
+parse_info_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_INFO);
+
+ if (ELEMENT_IS ("name"))
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ push_state (info, STATE_NAME);
+ }
+ else if (ELEMENT_IS ("author"))
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ push_state (info, STATE_AUTHOR);
+ }
+ else if (ELEMENT_IS ("copyright"))
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ push_state (info, STATE_COPYRIGHT);
+ }
+ else if (ELEMENT_IS ("description"))
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ push_state (info, STATE_DESCRIPTION);
+ }
+ else if (ELEMENT_IS ("date"))
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ push_state (info, STATE_DATE);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "info");
+ }
+}
+
+static void
+parse_distance (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ const char *name;
+ const char *value;
+ int val;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!name", &name, "!value", &value,
+ NULL))
+ return;
+
+ val = 0;
+ if (!parse_positive_integer (value, &val, context, info->theme, error))
+ return;
+
+ g_assert (val >= 0); /* yeah, "non-negative" not "positive" get over it */
+ g_assert (info->layout);
+
+ if (strcmp (name, "left_width") == 0)
+ info->layout->left_width = val;
+ else if (strcmp (name, "right_width") == 0)
+ info->layout->right_width = val;
+ else if (strcmp (name, "bottom_height") == 0)
+ info->layout->bottom_height = val;
+ else if (strcmp (name, "title_vertical_pad") == 0)
+ info->layout->title_vertical_pad = val;
+ else if (strcmp (name, "right_titlebar_edge") == 0)
+ info->layout->right_titlebar_edge = val;
+ else if (strcmp (name, "left_titlebar_edge") == 0)
+ info->layout->left_titlebar_edge = val;
+ else if (strcmp (name, "button_width") == 0)
+ {
+ info->layout->button_width = val;
+
+ if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST ||
+ info->layout->button_sizing == META_BUTTON_SIZING_FIXED))
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons"));
+ return;
+ }
+
+ info->layout->button_sizing = META_BUTTON_SIZING_FIXED;
+ }
+ else if (strcmp (name, "button_height") == 0)
+ {
+ info->layout->button_height = val;
+
+ if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST ||
+ info->layout->button_sizing == META_BUTTON_SIZING_FIXED))
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons"));
+ return;
+ }
+
+ info->layout->button_sizing = META_BUTTON_SIZING_FIXED;
+ }
+ else
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Distance \"%s\" is unknown"), name);
+ return;
+ }
+}
+
+static void
+parse_aspect_ratio (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ const char *name;
+ const char *value;
+ double val;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!name", &name, "!value", &value,
+ NULL))
+ return;
+
+ val = 0;
+ if (!parse_double (value, &val, context, error))
+ return;
+
+ g_assert (info->layout);
+
+ if (strcmp (name, "button") == 0)
+ {
+ info->layout->button_aspect = val;
+
+ if (info->layout->button_sizing != META_BUTTON_SIZING_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons"));
+ return;
+ }
+
+ info->layout->button_sizing = META_BUTTON_SIZING_ASPECT;
+ }
+ else
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Aspect ratio \"%s\" is unknown"), name);
+ return;
+ }
+}
+
+static void
+parse_border (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ const char *name;
+ const char *top;
+ const char *bottom;
+ const char *left;
+ const char *right;
+ int top_val;
+ int bottom_val;
+ int left_val;
+ int right_val;
+ GtkBorder *border;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!name", &name,
+ "!top", &top,
+ "!bottom", &bottom,
+ "!left", &left,
+ "!right", &right,
+ NULL))
+ return;
+
+ top_val = 0;
+ if (!parse_positive_integer (top, &top_val, context, info->theme, error))
+ return;
+
+ bottom_val = 0;
+ if (!parse_positive_integer (bottom, &bottom_val, context, info->theme, error))
+ return;
+
+ left_val = 0;
+ if (!parse_positive_integer (left, &left_val, context, info->theme, error))
+ return;
+
+ right_val = 0;
+ if (!parse_positive_integer (right, &right_val, context, info->theme, error))
+ return;
+
+ g_assert (info->layout);
+
+ border = NULL;
+
+ if (strcmp (name, "title_border") == 0)
+ border = &info->layout->title_border;
+ else if (strcmp (name, "button_border") == 0)
+ border = &info->layout->button_border;
+
+ if (border == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Border \"%s\" is unknown"), name);
+ return;
+ }
+
+ border->top = top_val;
+ border->bottom = bottom_val;
+ border->left = left_val;
+ border->right = right_val;
+}
+
+static void
+parse_geometry_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_FRAME_GEOMETRY);
+
+ if (ELEMENT_IS ("distance"))
+ {
+ parse_distance (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ push_state (info, STATE_DISTANCE);
+ }
+ else if (ELEMENT_IS ("border"))
+ {
+ parse_border (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ push_state (info, STATE_BORDER);
+ }
+ else if (ELEMENT_IS ("aspect_ratio"))
+ {
+ parse_aspect_ratio (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+
+ push_state (info, STATE_ASPECT_RATIO);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "frame_geometry");
+ }
+}
+
+#if 0
+static gboolean
+check_expression (PosToken *tokens,
+ int n_tokens,
+ gboolean has_object,
+ MetaTheme *theme,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ MetaPositionExprEnv env;
+ int x, y;
+
+ /* We set it all to 0 to try and catch divide-by-zero screwups.
+ * it's possible we should instead guarantee that widths and heights
+ * are at least 1.
+ */
+
+ env.rect = meta_rect (0, 0, 0, 0);
+ if (has_object)
+ {
+ env.object_width = 0;
+ env.object_height = 0;
+ }
+ else
+ {
+ env.object_width = -1;
+ env.object_height = -1;
+ }
+
+ env.left_width = 0;
+ env.right_width = 0;
+ env.top_height = 0;
+ env.bottom_height = 0;
+ env.title_width = 0;
+ env.title_height = 0;
+
+ env.icon_width = 0;
+ env.icon_height = 0;
+ env.mini_icon_width = 0;
+ env.mini_icon_height = 0;
+ env.theme = theme;
+
+ if (!meta_parse_position_expression (tokens, n_tokens,
+ &env,
+ &x, &y,
+ error))
+ {
+ add_context_to_error (error, context);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#endif
+
+static void
+parse_draw_op_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_DRAW_OPS);
+
+ if (ELEMENT_IS ("line"))
+ {
+ MetaDrawOp *op;
+ const char *color;
+ const char *x1;
+ const char *y1;
+ const char *x2;
+ const char *y2;
+ const char *dash_on_length;
+ const char *dash_off_length;
+ const char *width;
+ MetaColorSpec *color_spec;
+ int dash_on_val;
+ int dash_off_val;
+ int width_val;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!color", &color,
+ "!x1", &x1, "!y1", &y1,
+ "!x2", &x2, "!y2", &y2,
+ "dash_on_length", &dash_on_length,
+ "dash_off_length", &dash_off_length,
+ "width", &width,
+ NULL))
+ return;
+
+#if 0
+ if (!check_expression (x1, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y1, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (x2, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y2, FALSE, info->theme, context, error))
+ return;
+#endif
+
+ dash_on_val = 0;
+ if (dash_on_length &&
+ !parse_positive_integer (dash_on_length, &dash_on_val, context, info->theme, error))
+ return;
+
+ dash_off_val = 0;
+ if (dash_off_length &&
+ !parse_positive_integer (dash_off_length, &dash_off_val, context, info->theme, error))
+ return;
+
+ width_val = 0;
+ if (width &&
+ !parse_positive_integer (width, &width_val, context, info->theme, error))
+ return;
+
+ /* Check last so we don't have to free it when other
+ * stuff fails
+ */
+ color_spec = parse_color (info->theme, color, error);
+ if (color_spec == NULL)
+ {
+ add_context_to_error (error, context);
+ return;
+ }
+
+ op = meta_draw_op_new (META_DRAW_LINE);
+
+ op->data.line.color_spec = color_spec;
+
+ op->data.line.x1 = meta_draw_spec_new (info->theme, x1, NULL);
+ op->data.line.y1 = meta_draw_spec_new (info->theme, y1, NULL);
+
+ if (strcmp(x1, x2)==0)
+ op->data.line.x2 = NULL;
+ else
+ op->data.line.x2 = meta_draw_spec_new (info->theme, x2, NULL);
+
+ if (strcmp(y1, y2)==0)
+ op->data.line.y2 = NULL;
+ else
+ op->data.line.y2 = meta_draw_spec_new (info->theme, y2, NULL);
+
+ op->data.line.width = width_val;
+ op->data.line.dash_on_length = dash_on_val;
+ op->data.line.dash_off_length = dash_off_val;
+
+ g_assert (info->op_list);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_LINE);
+ }
+ else if (ELEMENT_IS ("rectangle"))
+ {
+ MetaDrawOp *op;
+ const char *color;
+ const char *x;
+ const char *y;
+ const char *width;
+ const char *height;
+ const char *filled;
+ gboolean filled_val;
+ MetaColorSpec *color_spec;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!color", &color,
+ "!x", &x, "!y", &y,
+ "!width", &width, "!height", &height,
+ "filled", &filled,
+ NULL))
+ return;
+
+#if 0
+ if (!check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (width, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (height, FALSE, info->theme, context, error))
+ return;
+#endif
+
+ filled_val = FALSE;
+ if (filled && !parse_boolean (filled, &filled_val, context, error))
+ return;
+
+ /* Check last so we don't have to free it when other
+ * stuff fails
+ */
+ color_spec = parse_color (info->theme, color, error);
+ if (color_spec == NULL)
+ {
+ add_context_to_error (error, context);
+ return;
+ }
+
+ op = meta_draw_op_new (META_DRAW_RECTANGLE);
+
+ op->data.rectangle.color_spec = color_spec;
+ op->data.rectangle.x = meta_draw_spec_new (info->theme, x, NULL);
+ op->data.rectangle.y = meta_draw_spec_new (info->theme, y, NULL);
+ op->data.rectangle.width = meta_draw_spec_new (info->theme, width, NULL);
+ op->data.rectangle.height = meta_draw_spec_new (info->theme,
+ height, NULL);
+
+ op->data.rectangle.filled = filled_val;
+
+ g_assert (info->op_list);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_RECTANGLE);
+ }
+ else if (ELEMENT_IS ("arc"))
+ {
+ MetaDrawOp *op;
+ const char *color;
+ const char *x;
+ const char *y;
+ const char *width;
+ const char *height;
+ const char *filled;
+ const char *start_angle;
+ const char *extent_angle;
+ const char *from;
+ const char *to;
+ gboolean filled_val;
+ double start_angle_val;
+ double extent_angle_val;
+ MetaColorSpec *color_spec;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!color", &color,
+ "!x", &x, "!y", &y,
+ "!width", &width, "!height", &height,
+ "filled", &filled,
+ "start_angle", &start_angle,
+ "extent_angle", &extent_angle,
+ "from", &from,
+ "to", &to,
+ NULL))
+ return;
+
+ if (META_THEME_ALLOWS (info->theme, META_THEME_DEGREES_IN_ARCS) )
+ {
+ if (start_angle == NULL && from == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"start_angle\" or \"from\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (extent_angle == NULL && to == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"extent_angle\" or \"to\" attribute on element <%s>"), element_name);
+ return;
+ }
+ }
+ else
+ {
+ if (start_angle == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ ATTRIBUTE_NOT_FOUND, "start_angle", element_name);
+ return;
+ }
+
+ if (extent_angle == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ ATTRIBUTE_NOT_FOUND, "extent_angle", element_name);
+ return;
+ }
+ }
+
+#if 0
+ if (!check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (width, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (height, FALSE, info->theme, context, error))
+ return;
+#endif
+
+ if (start_angle == NULL)
+ {
+ if (!parse_angle (from, &start_angle_val, context, error))
+ return;
+
+ start_angle_val = (180-start_angle_val)/360.0;
+ }
+ else
+ {
+ if (!parse_angle (start_angle, &start_angle_val, context, error))
+ return;
+ }
+
+ if (extent_angle == NULL)
+ {
+ if (!parse_angle (to, &extent_angle_val, context, error))
+ return;
+
+ extent_angle_val = ((180-extent_angle_val)/360.0) - start_angle_val;
+ }
+ else
+ {
+ if (!parse_angle (extent_angle, &extent_angle_val, context, error))
+ return;
+ }
+
+ filled_val = FALSE;
+ if (filled && !parse_boolean (filled, &filled_val, context, error))
+ return;
+
+ /* Check last so we don't have to free it when other
+ * stuff fails
+ */
+ color_spec = parse_color (info->theme, color, error);
+ if (color_spec == NULL)
+ {
+ add_context_to_error (error, context);
+ return;
+ }
+
+ op = meta_draw_op_new (META_DRAW_ARC);
+
+ op->data.arc.color_spec = color_spec;
+
+ op->data.arc.x = meta_draw_spec_new (info->theme, x, NULL);
+ op->data.arc.y = meta_draw_spec_new (info->theme, y, NULL);
+ op->data.arc.width = meta_draw_spec_new (info->theme, width, NULL);
+ op->data.arc.height = meta_draw_spec_new (info->theme, height, NULL);
+
+ op->data.arc.filled = filled_val;
+ op->data.arc.start_angle = start_angle_val;
+ op->data.arc.extent_angle = extent_angle_val;
+
+ g_assert (info->op_list);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_ARC);
+ }
+ else if (ELEMENT_IS ("clip"))
+ {
+ MetaDrawOp *op;
+ const char *x;
+ const char *y;
+ const char *width;
+ const char *height;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!x", &x, "!y", &y,
+ "!width", &width, "!height", &height,
+ NULL))
+ return;
+
+#if 0
+ if (!check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (width, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (height, FALSE, info->theme, context, error))
+ return;
+#endif
+ op = meta_draw_op_new (META_DRAW_CLIP);
+
+ op->data.clip.x = meta_draw_spec_new (info->theme, x, NULL);
+ op->data.clip.y = meta_draw_spec_new (info->theme, y, NULL);
+ op->data.clip.width = meta_draw_spec_new (info->theme, width, NULL);
+ op->data.clip.height = meta_draw_spec_new (info->theme, height, NULL);
+
+ g_assert (info->op_list);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_CLIP);
+ }
+ else if (ELEMENT_IS ("tint"))
+ {
+ MetaDrawOp *op;
+ const char *color;
+ const char *x;
+ const char *y;
+ const char *width;
+ const char *height;
+ const char *alpha;
+ MetaAlphaGradientSpec *alpha_spec;
+ MetaColorSpec *color_spec;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!color", &color,
+ "!x", &x, "!y", &y,
+ "!width", &width, "!height", &height,
+ "!alpha", &alpha,
+ NULL))
+ return;
+
+#if 0
+ if (!check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (width, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (height, FALSE, info->theme, context, error))
+ return;
+#endif
+ alpha_spec = NULL;
+ if (!parse_alpha (alpha, &alpha_spec, context, error))
+ return;
+
+ /* Check last so we don't have to free it when other
+ * stuff fails
+ */
+ color_spec = parse_color (info->theme, color, error);
+ if (color_spec == NULL)
+ {
+ if (alpha_spec)
+ meta_alpha_gradient_spec_free (alpha_spec);
+
+ add_context_to_error (error, context);
+ return;
+ }
+
+ op = meta_draw_op_new (META_DRAW_TINT);
+
+ op->data.tint.color_spec = color_spec;
+ op->data.tint.alpha_spec = alpha_spec;
+
+ op->data.tint.x = meta_draw_spec_new (info->theme, x, NULL);
+ op->data.tint.y = meta_draw_spec_new (info->theme, y, NULL);
+ op->data.tint.width = meta_draw_spec_new (info->theme, width, NULL);
+ op->data.tint.height = meta_draw_spec_new (info->theme, height, NULL);
+
+ g_assert (info->op_list);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_TINT);
+ }
+ else if (ELEMENT_IS ("gradient"))
+ {
+ const char *x;
+ const char *y;
+ const char *width;
+ const char *height;
+ const char *type;
+ const char *alpha;
+ MetaAlphaGradientSpec *alpha_spec;
+ MetaGradientType type_val;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!type", &type,
+ "!x", &x, "!y", &y,
+ "!width", &width, "!height", &height,
+ "alpha", &alpha,
+ NULL))
+ return;
+
+#if 0
+ if (!check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (width, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (height, FALSE, info->theme, context, error))
+ return;
+#endif
+
+ type_val = meta_gradient_type_from_string (type);
+ if (type_val == META_GRADIENT_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Did not understand value \"%s\" for type of gradient"),
+ type);
+ return;
+ }
+
+ alpha_spec = NULL;
+ if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
+ return;
+
+ g_assert (info->op == NULL);
+ info->op = meta_draw_op_new (META_DRAW_GRADIENT);
+
+ info->op->data.gradient.x = meta_draw_spec_new (info->theme, x, NULL);
+ info->op->data.gradient.y = meta_draw_spec_new (info->theme, y, NULL);
+ info->op->data.gradient.width = meta_draw_spec_new (info->theme,
+ width, NULL);
+ info->op->data.gradient.height = meta_draw_spec_new (info->theme,
+ height, NULL);
+
+ info->op->data.gradient.gradient_spec = meta_gradient_spec_new (type_val);
+
+ info->op->data.gradient.alpha_spec = alpha_spec;
+
+ push_state (info, STATE_GRADIENT);
+
+ /* op gets appended on close tag */
+ }
+ else if (ELEMENT_IS ("image"))
+ {
+ MetaDrawOp *op;
+ const char *filename;
+ const char *x;
+ const char *y;
+ const char *width;
+ const char *height;
+ const char *alpha;
+ const char *colorize;
+ const char *fill_type;
+ MetaAlphaGradientSpec *alpha_spec;
+ GdkPixbuf *pixbuf;
+ MetaColorSpec *colorize_spec = NULL;
+ MetaImageFillType fill_type_val;
+ int h, w, c;
+ int pixbuf_width, pixbuf_height, pixbuf_n_channels, pixbuf_rowstride;
+ guchar *pixbuf_pixels;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!x", &x, "!y", &y,
+ "!width", &width, "!height", &height,
+ "alpha", &alpha, "!filename", &filename,
+ "colorize", &colorize,
+ "fill_type", &fill_type,
+ NULL))
+ return;
+
+#if 0
+ if (!check_expression (x, TRUE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y, TRUE, info->theme, context, error))
+ return;
+
+ if (!check_expression (width, TRUE, info->theme, context, error))
+ return;
+
+ if (!check_expression (height, TRUE, info->theme, context, error))
+ return;
+#endif
+ fill_type_val = META_IMAGE_FILL_SCALE;
+ if (fill_type)
+ {
+ fill_type_val = meta_image_fill_type_from_string (fill_type);
+
+ if (((int) fill_type_val) == -1)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Did not understand fill type \"%s\" for <%s> element"),
+ fill_type, element_name);
+ }
+ }
+
+ /* Check last so we don't have to free it when other
+ * stuff fails.
+ *
+ * If it's a theme image, ask for it at 64px, which is
+ * the largest possible. We scale it anyway.
+ */
+ pixbuf = meta_theme_load_image (info->theme, filename, 64, error);
+
+ if (pixbuf == NULL)
+ {
+ add_context_to_error (error, context);
+ return;
+ }
+
+ if (colorize)
+ {
+ colorize_spec = parse_color (info->theme, colorize, error);
+
+ if (colorize_spec == NULL)
+ {
+ add_context_to_error (error, context);
+ g_object_unref (G_OBJECT (pixbuf));
+ return;
+ }
+ }
+
+ alpha_spec = NULL;
+ if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
+ {
+ g_object_unref (G_OBJECT (pixbuf));
+ return;
+ }
+
+ op = meta_draw_op_new (META_DRAW_IMAGE);
+
+ op->data.image.pixbuf = pixbuf;
+ op->data.image.colorize_spec = colorize_spec;
+
+ op->data.image.x = meta_draw_spec_new (info->theme, x, NULL);
+ op->data.image.y = meta_draw_spec_new (info->theme, y, NULL);
+ op->data.image.width = meta_draw_spec_new (info->theme, width, NULL);
+ op->data.image.height = meta_draw_spec_new (info->theme, height, NULL);
+
+ op->data.image.alpha_spec = alpha_spec;
+ op->data.image.fill_type = fill_type_val;
+
+ /* Check for vertical & horizontal stripes */
+ pixbuf_n_channels = gdk_pixbuf_get_n_channels(pixbuf);
+ pixbuf_width = gdk_pixbuf_get_width(pixbuf);
+ pixbuf_height = gdk_pixbuf_get_height(pixbuf);
+ pixbuf_rowstride = gdk_pixbuf_get_rowstride(pixbuf);
+ pixbuf_pixels = gdk_pixbuf_get_pixels(pixbuf);
+
+ /* Check for horizontal stripes */
+ for (h = 0; h < pixbuf_height; h++)
+ {
+ for (w = 1; w < pixbuf_width; w++)
+ {
+ for (c = 0; c < pixbuf_n_channels; c++)
+ {
+ if (pixbuf_pixels[(h * pixbuf_rowstride) + c] !=
+ pixbuf_pixels[(h * pixbuf_rowstride) + w + c])
+ break;
+ }
+ if (c < pixbuf_n_channels)
+ break;
+ }
+ if (w < pixbuf_width)
+ break;
+ }
+
+ if (h >= pixbuf_height)
+ {
+ op->data.image.horizontal_stripes = TRUE;
+ }
+ else
+ {
+ op->data.image.horizontal_stripes = FALSE;
+ }
+
+ /* Check for vertical stripes */
+ for (w = 0; w < pixbuf_width; w++)
+ {
+ for (h = 1; h < pixbuf_height; h++)
+ {
+ for (c = 0; c < pixbuf_n_channels; c++)
+ {
+ if (pixbuf_pixels[w + c] !=
+ pixbuf_pixels[(h * pixbuf_rowstride) + w + c])
+ break;
+ }
+ if (c < pixbuf_n_channels)
+ break;
+ }
+ if (h < pixbuf_height)
+ break;
+ }
+
+ if (w >= pixbuf_width)
+ {
+ op->data.image.vertical_stripes = TRUE;
+ }
+ else
+ {
+ op->data.image.vertical_stripes = FALSE;
+ }
+
+ g_assert (info->op_list);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_IMAGE);
+ }
+ else if (ELEMENT_IS ("gtk_arrow"))
+ {
+ MetaDrawOp *op;
+ const char *state;
+ const char *shadow;
+ const char *arrow;
+ const char *x;
+ const char *y;
+ const char *width;
+ const char *height;
+ const char *filled;
+ gboolean filled_val;
+ GtkStateType state_val;
+ GtkShadowType shadow_val;
+ GtkArrowType arrow_val;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!state", &state,
+ "!shadow", &shadow,
+ "!arrow", &arrow,
+ "!x", &x, "!y", &y,
+ "!width", &width, "!height", &height,
+ "filled", &filled,
+ NULL))
+ return;
+
+#if 0
+ if (!check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (width, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (height, FALSE, info->theme, context, error))
+ return;
+#endif
+ filled_val = TRUE;
+ if (filled && !parse_boolean (filled, &filled_val, context, error))
+ return;
+
+ state_val = meta_gtk_state_from_string (state);
+ if (((int) state_val) == -1)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Did not understand state \"%s\" for <%s> element"),
+ state, element_name);
+ return;
+ }
+
+ shadow_val = meta_gtk_shadow_from_string (shadow);
+ if (((int) shadow_val) == -1)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Did not understand shadow \"%s\" for <%s> element"),
+ shadow, element_name);
+ return;
+ }
+
+ arrow_val = meta_gtk_arrow_from_string (arrow);
+ if (((int) arrow_val) == -1)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Did not understand arrow \"%s\" for <%s> element"),
+ arrow, element_name);
+ return;
+ }
+
+ op = meta_draw_op_new (META_DRAW_GTK_ARROW);
+
+ op->data.gtk_arrow.x = meta_draw_spec_new (info->theme, x, NULL);
+ op->data.gtk_arrow.y = meta_draw_spec_new (info->theme, y, NULL);
+ op->data.gtk_arrow.width = meta_draw_spec_new (info->theme, width, NULL);
+ op->data.gtk_arrow.height = meta_draw_spec_new (info->theme,
+ height, NULL);
+
+ op->data.gtk_arrow.filled = filled_val;
+ op->data.gtk_arrow.state = state_val;
+ op->data.gtk_arrow.shadow = shadow_val;
+ op->data.gtk_arrow.arrow = arrow_val;
+
+ g_assert (info->op_list);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_GTK_ARROW);
+ }
+ else if (ELEMENT_IS ("gtk_box"))
+ {
+ MetaDrawOp *op;
+ const char *state;
+ const char *shadow;
+ const char *x;
+ const char *y;
+ const char *width;
+ const char *height;
+ GtkStateType state_val;
+ GtkShadowType shadow_val;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!state", &state,
+ "!shadow", &shadow,
+ "!x", &x, "!y", &y,
+ "!width", &width, "!height", &height,
+ NULL))
+ return;
+
+#if 0
+ if (!check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (width, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (height, FALSE, info->theme, context, error))
+ return;
+#endif
+ state_val = meta_gtk_state_from_string (state);
+ if (((int) state_val) == -1)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Did not understand state \"%s\" for <%s> element"),
+ state, element_name);
+ return;
+ }
+
+ shadow_val = meta_gtk_shadow_from_string (shadow);
+ if (((int) shadow_val) == -1)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Did not understand shadow \"%s\" for <%s> element"),
+ shadow, element_name);
+ return;
+ }
+
+ op = meta_draw_op_new (META_DRAW_GTK_BOX);
+
+ op->data.gtk_box.x = meta_draw_spec_new (info->theme, x, NULL);
+ op->data.gtk_box.y = meta_draw_spec_new (info->theme, y, NULL);
+ op->data.gtk_box.width = meta_draw_spec_new (info->theme, width, NULL);
+ op->data.gtk_box.height = meta_draw_spec_new (info->theme, height, NULL);
+
+ op->data.gtk_box.state = state_val;
+ op->data.gtk_box.shadow = shadow_val;
+
+ g_assert (info->op_list);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_GTK_BOX);
+ }
+ else if (ELEMENT_IS ("gtk_vline"))
+ {
+ MetaDrawOp *op;
+ const char *state;
+ const char *x;
+ const char *y1;
+ const char *y2;
+ GtkStateType state_val;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!state", &state,
+ "!x", &x, "!y1", &y1, "!y2", &y2,
+ NULL))
+ return;
+
+#if 0
+ if (!check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y1, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y2, FALSE, info->theme, context, error))
+ return;
+#endif
+
+ state_val = meta_gtk_state_from_string (state);
+ if (((int) state_val) == -1)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Did not understand state \"%s\" for <%s> element"),
+ state, element_name);
+ return;
+ }
+
+ op = meta_draw_op_new (META_DRAW_GTK_VLINE);
+
+ op->data.gtk_vline.x = meta_draw_spec_new (info->theme, x, NULL);
+ op->data.gtk_vline.y1 = meta_draw_spec_new (info->theme, y1, NULL);
+ op->data.gtk_vline.y2 = meta_draw_spec_new (info->theme, y2, NULL);
+
+ op->data.gtk_vline.state = state_val;
+
+ g_assert (info->op_list);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_GTK_VLINE);
+ }
+ else if (ELEMENT_IS ("icon"))
+ {
+ MetaDrawOp *op;
+ const char *x;
+ const char *y;
+ const char *width;
+ const char *height;
+ const char *alpha;
+ const char *fill_type;
+ MetaAlphaGradientSpec *alpha_spec;
+ MetaImageFillType fill_type_val;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!x", &x, "!y", &y,
+ "!width", &width, "!height", &height,
+ "alpha", &alpha,
+ "fill_type", &fill_type,
+ NULL))
+ return;
+
+#if 0
+ if (!check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (width, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (height, FALSE, info->theme, context, error))
+ return;
+#endif
+ fill_type_val = META_IMAGE_FILL_SCALE;
+ if (fill_type)
+ {
+ fill_type_val = meta_image_fill_type_from_string (fill_type);
+
+ if (((int) fill_type_val) == -1)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Did not understand fill type \"%s\" for <%s> element"),
+ fill_type, element_name);
+ }
+ }
+
+ alpha_spec = NULL;
+ if (alpha && !parse_alpha (alpha, &alpha_spec, context, error))
+ return;
+
+ op = meta_draw_op_new (META_DRAW_ICON);
+
+ op->data.icon.x = meta_draw_spec_new (info->theme, x, NULL);
+ op->data.icon.y = meta_draw_spec_new (info->theme, y, NULL);
+ op->data.icon.width = meta_draw_spec_new (info->theme, width, NULL);
+ op->data.icon.height = meta_draw_spec_new (info->theme, height, NULL);
+
+ op->data.icon.alpha_spec = alpha_spec;
+ op->data.icon.fill_type = fill_type_val;
+
+ g_assert (info->op_list);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_ICON);
+ }
+ else if (ELEMENT_IS ("title"))
+ {
+ MetaDrawOp *op;
+ const char *color;
+ const char *x;
+ const char *y;
+ MetaColorSpec *color_spec;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!color", &color,
+ "!x", &x, "!y", &y,
+ NULL))
+ return;
+
+#if 0
+ if (!check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (y, FALSE, info->theme, context, error))
+ return;
+#endif
+
+ /* Check last so we don't have to free it when other
+ * stuff fails
+ */
+ color_spec = parse_color (info->theme, color, error);
+ if (color_spec == NULL)
+ {
+ add_context_to_error (error, context);
+ return;
+ }
+
+ op = meta_draw_op_new (META_DRAW_TITLE);
+
+ op->data.title.color_spec = color_spec;
+
+ op->data.title.x = meta_draw_spec_new (info->theme, x, NULL);
+ op->data.title.y = meta_draw_spec_new (info->theme, y, NULL);
+
+ g_assert (info->op_list);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_TITLE);
+ }
+ else if (ELEMENT_IS ("include"))
+ {
+ MetaDrawOp *op;
+ const char *name;
+ const char *x;
+ const char *y;
+ const char *width;
+ const char *height;
+ MetaDrawOpList *op_list;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "x", &x, "y", &y,
+ "width", &width, "height", &height,
+ "!name", &name,
+ NULL))
+ return;
+
+ /* x/y/width/height default to 0,0,width,height - should
+ * probably do this for all the draw ops
+ */
+#if 0
+ if (x && !check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (y && !check_expression (y, FALSE, info->theme, context, error))
+ return;
+
+ if (width && !check_expression (width, FALSE, info->theme, context, error))
+ return;
+
+ if (height && !check_expression (height, FALSE, info->theme, context, error))
+ return;
+#endif
+
+ op_list = meta_theme_lookup_draw_op_list (info->theme,
+ name);
+ if (op_list == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("No <draw_ops> called \"%s\" has been defined"),
+ name);
+ return;
+ }
+
+ g_assert (info->op_list);
+
+ if (op_list == info->op_list ||
+ meta_draw_op_list_contains (op_list, info->op_list))
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Including draw_ops \"%s\" here would create a circular reference"),
+ name);
+ return;
+ }
+
+ op = meta_draw_op_new (META_DRAW_OP_LIST);
+
+ meta_draw_op_list_ref (op_list);
+ op->data.op_list.op_list = op_list;
+
+ op->data.op_list.x = meta_draw_spec_new (info->theme, x ? x : "0", NULL);
+ op->data.op_list.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL);
+ op->data.op_list.width = meta_draw_spec_new (info->theme,
+ width ? width : "width",
+ NULL);
+ op->data.op_list.height = meta_draw_spec_new (info->theme,
+ height ? height : "height",
+ NULL);
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_INCLUDE);
+ }
+ else if (ELEMENT_IS ("tile"))
+ {
+ MetaDrawOp *op;
+ const char *name;
+ const char *x;
+ const char *y;
+ const char *width;
+ const char *height;
+ const char *tile_xoffset;
+ const char *tile_yoffset;
+ const char *tile_width;
+ const char *tile_height;
+ MetaDrawOpList *op_list;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "x", &x, "y", &y,
+ "width", &width, "height", &height,
+ "!name", &name,
+ "tile_xoffset", &tile_xoffset,
+ "tile_yoffset", &tile_yoffset,
+ "!tile_width", &tile_width,
+ "!tile_height", &tile_height,
+ NULL))
+ return;
+
+ /* These default to 0 */
+#if 0
+ if (tile_xoffset && !check_expression (tile_xoffset, FALSE, info->theme, context, error))
+ return;
+
+ if (tile_yoffset && !check_expression (tile_yoffset, FALSE, info->theme, context, error))
+ return;
+
+ /* x/y/width/height default to 0,0,width,height - should
+ * probably do this for all the draw ops
+ */
+ if (x && !check_expression (x, FALSE, info->theme, context, error))
+ return;
+
+ if (y && !check_expression (y, FALSE, info->theme, context, error))
+ return;
+
+ if (width && !check_expression (width, FALSE, info->theme, context, error))
+ return;
+
+ if (height && !check_expression (height, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (tile_width, FALSE, info->theme, context, error))
+ return;
+
+ if (!check_expression (tile_height, FALSE, info->theme, context, error))
+ return;
+#endif
+ op_list = meta_theme_lookup_draw_op_list (info->theme,
+ name);
+ if (op_list == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("No <draw_ops> called \"%s\" has been defined"),
+ name);
+ return;
+ }
+
+ g_assert (info->op_list);
+
+ if (op_list == info->op_list ||
+ meta_draw_op_list_contains (op_list, info->op_list))
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Including draw_ops \"%s\" here would create a circular reference"),
+ name);
+ return;
+ }
+
+ op = meta_draw_op_new (META_DRAW_TILE);
+
+ meta_draw_op_list_ref (op_list);
+
+ op->data.tile.x = meta_draw_spec_new (info->theme, x ? x : "0", NULL);
+ op->data.tile.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL);
+ op->data.tile.width = meta_draw_spec_new (info->theme,
+ width ? width : "width",
+ NULL);
+ op->data.tile.height = meta_draw_spec_new (info->theme,
+ height ? height : "height",
+ NULL);
+ op->data.tile.tile_xoffset = meta_draw_spec_new (info->theme,
+ tile_xoffset ? tile_xoffset : "0",
+ NULL);
+ op->data.tile.tile_yoffset = meta_draw_spec_new (info->theme,
+ tile_yoffset ? tile_yoffset : "0",
+ NULL);
+ op->data.tile.tile_width = meta_draw_spec_new (info->theme, tile_width, NULL);
+ op->data.tile.tile_height = meta_draw_spec_new (info->theme, tile_height, NULL);
+
+ op->data.tile.op_list = op_list;
+
+ meta_draw_op_list_append (info->op_list, op);
+
+ push_state (info, STATE_TILE);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "draw_ops");
+ }
+}
+
+static void
+parse_gradient_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_GRADIENT);
+
+ if (ELEMENT_IS ("color"))
+ {
+ const char *value = NULL;
+ MetaColorSpec *color_spec;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!value", &value,
+ NULL))
+ return;
+
+ color_spec = parse_color (info->theme, value, error);
+ if (color_spec == NULL)
+ {
+ add_context_to_error (error, context);
+ return;
+ }
+
+ g_assert (info->op);
+ g_assert (info->op->type == META_DRAW_GRADIENT);
+ g_assert (info->op->data.gradient.gradient_spec != NULL);
+ info->op->data.gradient.gradient_spec->color_specs =
+ g_slist_append (info->op->data.gradient.gradient_spec->color_specs,
+ color_spec);
+
+ push_state (info, STATE_COLOR);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "gradient");
+ }
+}
+
+static void
+parse_style_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE);
+
+ g_assert (info->style);
+
+ if (ELEMENT_IS ("piece"))
+ {
+ const char *position = NULL;
+ const char *draw_ops = NULL;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!position", &position,
+ "draw_ops", &draw_ops,
+ NULL))
+ return;
+
+ info->piece = meta_frame_piece_from_string (position);
+ if (info->piece == META_FRAME_PIECE_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Unknown position \"%s\" for frame piece"),
+ position);
+ return;
+ }
+
+ if (info->style->pieces[info->piece] != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Frame style already has a piece at position %s"),
+ position);
+ return;
+ }
+
+ g_assert (info->op_list == NULL);
+
+ if (draw_ops)
+ {
+ MetaDrawOpList *op_list;
+
+ op_list = meta_theme_lookup_draw_op_list (info->theme,
+ draw_ops);
+
+ if (op_list == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No <draw_ops> with the name \"%s\" has been defined"),
+ draw_ops);
+ return;
+ }
+
+ meta_draw_op_list_ref (op_list);
+ info->op_list = op_list;
+ }
+
+ push_state (info, STATE_PIECE);
+ }
+ else if (ELEMENT_IS ("button"))
+ {
+ const char *function = NULL;
+ const char *state = NULL;
+ const char *draw_ops = NULL;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!function", &function,
+ "!state", &state,
+ "draw_ops", &draw_ops,
+ NULL))
+ return;
+
+ info->button_type = meta_button_type_from_string (function, info->theme);
+ if (info->button_type == META_BUTTON_TYPE_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Unknown function \"%s\" for button"),
+ function);
+ return;
+ }
+
+ if (meta_theme_earliest_version_with_button (info->button_type) >
+ info->theme->format_version)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Button function \"%s\" does not exist in this version (%d, need %d)"),
+ function,
+ info->theme->format_version,
+ meta_theme_earliest_version_with_button (info->button_type)
+ );
+ return;
+ }
+
+ info->button_state = meta_button_state_from_string (state);
+ if (info->button_state == META_BUTTON_STATE_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Unknown state \"%s\" for button"),
+ state);
+ return;
+ }
+
+ if (info->style->buttons[info->button_type][info->button_state] != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Frame style already has a button for function %s state %s"),
+ function, state);
+ return;
+ }
+
+ g_assert (info->op_list == NULL);
+
+ if (draw_ops)
+ {
+ MetaDrawOpList *op_list;
+
+ op_list = meta_theme_lookup_draw_op_list (info->theme,
+ draw_ops);
+
+ if (op_list == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No <draw_ops> with the name \"%s\" has been defined"),
+ draw_ops);
+ return;
+ }
+
+ meta_draw_op_list_ref (op_list);
+ info->op_list = op_list;
+ }
+
+ push_state (info, STATE_BUTTON);
+ }
+#ifdef USE_UBUNTU_CODE
+ else if (ELEMENT_IS ("shadow"))
+ {
+ push_state (info, STATE_SHADOW);
+ }
+ else if (ELEMENT_IS ("padding"))
+ {
+ push_state (info, STATE_PADDING);
+ }
+#endif
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "frame_style");
+ }
+}
+
+static void
+parse_style_set_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE_SET);
+
+ if (ELEMENT_IS ("frame"))
+ {
+ const char *focus = NULL;
+ const char *state = NULL;
+ const char *resize = NULL;
+ const char *style = NULL;
+ MetaFrameFocus frame_focus;
+ MetaFrameState frame_state;
+ MetaFrameResize frame_resize;
+ MetaFrameStyle *frame_style;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "!focus", &focus,
+ "!state", &state,
+ "resize", &resize,
+ "!style", &style,
+ NULL))
+ return;
+
+ frame_focus = meta_frame_focus_from_string (focus);
+ if (frame_focus == META_FRAME_FOCUS_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid value for focus attribute"),
+ focus);
+ return;
+ }
+
+ frame_state = meta_frame_state_from_string (state);
+ if (frame_state == META_FRAME_STATE_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid value for state attribute"),
+ focus);
+ return;
+ }
+
+ frame_style = meta_theme_lookup_style (info->theme, style);
+
+ if (frame_style == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("A style called \"%s\" has not been defined"),
+ style);
+ return;
+ }
+
+ switch (frame_state)
+ {
+ case META_FRAME_STATE_NORMAL:
+ if (resize == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ ATTRIBUTE_NOT_FOUND,
+ "resize", element_name);
+ return;
+ }
+
+
+ frame_resize = meta_frame_resize_from_string (resize);
+ if (frame_resize == META_FRAME_RESIZE_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid value for resize attribute"),
+ focus);
+ return;
+ }
+
+ break;
+
+ case META_FRAME_STATE_SHADED:
+ if (META_THEME_ALLOWS (info->theme, META_THEME_UNRESIZABLE_SHADED_STYLES))
+ {
+ if (resize == NULL)
+ /* In state="normal" we would complain here. But instead we accept
+ * not having a resize attribute and default to resize="both", since
+ * that most closely mimics what we did in v1, and thus people can
+ * upgrade a theme to v2 without as much hassle.
+ */
+ frame_resize = META_FRAME_RESIZE_BOTH;
+ else
+ {
+ frame_resize = meta_frame_resize_from_string (resize);
+ if (frame_resize == META_FRAME_RESIZE_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid value for resize attribute"),
+ focus);
+ return;
+ }
+ }
+ }
+ else /* v1 theme */
+ {
+ if (resize != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Should not have \"resize\" attribute on <%s> element for maximized/shaded states"),
+ element_name);
+ return;
+ }
+
+ /* resize="both" is equivalent to the old behaviour */
+ frame_resize = META_FRAME_RESIZE_BOTH;
+ }
+ break;
+
+ default:
+ if (resize != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Should not have \"resize\" attribute on <%s> element for maximized states"),
+ element_name);
+ return;
+ }
+
+ frame_resize = META_FRAME_RESIZE_LAST;
+ }
+
+ switch (frame_state)
+ {
+ case META_FRAME_STATE_NORMAL:
+ if (info->style_set->normal_styles[frame_resize][frame_focus])
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Style has already been specified for state %s resize %s focus %s"),
+ state, resize, focus);
+ return;
+ }
+ meta_frame_style_ref (frame_style);
+ info->style_set->normal_styles[frame_resize][frame_focus] = frame_style;
+ break;
+ case META_FRAME_STATE_MAXIMIZED:
+ if (info->style_set->maximized_styles[frame_focus])
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Style has already been specified for state %s focus %s"),
+ state, focus);
+ return;
+ }
+ meta_frame_style_ref (frame_style);
+ info->style_set->maximized_styles[frame_focus] = frame_style;
+ break;
+ case META_FRAME_STATE_SHADED:
+ if (info->style_set->shaded_styles[frame_resize][frame_focus])
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Style has already been specified for state %s resize %s focus %s"),
+ state, resize, focus);
+ return;
+ }
+ meta_frame_style_ref (frame_style);
+ info->style_set->shaded_styles[frame_resize][frame_focus] = frame_style;
+ break;
+ case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
+ if (info->style_set->maximized_and_shaded_styles[frame_focus])
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Style has already been specified for state %s focus %s"),
+ state, focus);
+ return;
+ }
+ meta_frame_style_ref (frame_style);
+ info->style_set->maximized_and_shaded_styles[frame_focus] = frame_style;
+ break;
+ case META_FRAME_STATE_LAST:
+ g_assert_not_reached ();
+ break;
+ }
+
+ push_state (info, STATE_FRAME);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "frame_style_set");
+ }
+}
+
+static void
+parse_piece_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_PIECE);
+
+ if (ELEMENT_IS ("draw_ops"))
+ {
+ if (info->op_list)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Can't have a two draw_ops for a <piece> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)"));
+ return;
+ }
+
+ if (!check_no_attributes (context, element_name, attribute_names, attribute_values,
+ error))
+ return;
+
+ g_assert (info->op_list == NULL);
+ info->op_list = meta_draw_op_list_new (2);
+
+ push_state (info, STATE_DRAW_OPS);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "piece");
+ }
+}
+
+static void
+parse_button_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_BUTTON);
+
+ if (ELEMENT_IS ("draw_ops"))
+ {
+ if (info->op_list)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Can't have a two draw_ops for a <button> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)"));
+ return;
+ }
+
+ if (!check_no_attributes (context, element_name, attribute_names, attribute_values,
+ error))
+ return;
+
+ g_assert (info->op_list == NULL);
+ info->op_list = meta_draw_op_list_new (2);
+
+ push_state (info, STATE_DRAW_OPS);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "button");
+ }
+}
+
+#ifdef USE_UBUNTU_CODE
+static void
+parse_shadow_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_SHADOW);
+
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "shadow");
+}
+
+static void
+parse_padding_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_PADDING);
+
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "padding");
+}
+#endif
+
+static void
+parse_menu_icon_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ g_return_if_fail (peek_state (info) == STATE_MENU_ICON);
+
+ if (ELEMENT_IS ("draw_ops"))
+ {
+ if (info->op_list)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Can't have a two draw_ops for a <menu_icon> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)"));
+ return;
+ }
+
+ if (!check_no_attributes (context, element_name, attribute_names, attribute_values,
+ error))
+ return;
+
+ g_assert (info->op_list == NULL);
+ info->op_list = meta_draw_op_list_new (2);
+
+ push_state (info, STATE_DRAW_OPS);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "menu_icon");
+ }
+}
+
+
+static void
+start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+
+ switch (peek_state (info))
+ {
+ case STATE_START:
+ if (strcmp (element_name, "metacity_theme") == 0)
+ {
+ info->theme = meta_theme_new ();
+ info->theme->name = g_strdup (info->theme_name);
+ info->theme->filename = g_strdup (info->theme_file);
+ info->theme->dirname = g_strdup (info->theme_dir);
+ info->theme->format_version = info->format_version;
+
+ push_state (info, STATE_THEME);
+ }
+ else
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Outermost element in theme must be <metacity_theme> not <%s>"),
+ element_name);
+ break;
+
+ case STATE_THEME:
+ parse_toplevel_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_INFO:
+ parse_info_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_NAME:
+ case STATE_AUTHOR:
+ case STATE_COPYRIGHT:
+ case STATE_DATE:
+ case STATE_DESCRIPTION:
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed inside a name/author/date/description element"),
+ element_name);
+ break;
+ case STATE_CONSTANT:
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed inside a <constant> element"),
+ element_name);
+ break;
+ case STATE_FRAME_GEOMETRY:
+ parse_geometry_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_DISTANCE:
+ case STATE_BORDER:
+ case STATE_ASPECT_RATIO:
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed inside a distance/border/aspect_ratio element"),
+ element_name);
+ break;
+ case STATE_DRAW_OPS:
+ parse_draw_op_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_LINE:
+ case STATE_RECTANGLE:
+ case STATE_ARC:
+ case STATE_CLIP:
+ case STATE_TINT:
+ case STATE_IMAGE:
+ case STATE_GTK_ARROW:
+ case STATE_GTK_BOX:
+ case STATE_GTK_VLINE:
+ case STATE_ICON:
+ case STATE_TITLE:
+ case STATE_INCLUDE:
+ case STATE_TILE:
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed inside a draw operation element"),
+ element_name);
+ break;
+ case STATE_GRADIENT:
+ parse_gradient_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_COLOR:
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed inside a <%s> element"),
+ element_name, "color");
+ break;
+ case STATE_FRAME_STYLE:
+ parse_style_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_PIECE:
+ parse_piece_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_BUTTON:
+ parse_button_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+#ifdef USE_UBUNTU_CODE
+ case STATE_SHADOW:
+ parse_shadow_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_PADDING:
+ parse_padding_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+#endif
+ case STATE_MENU_ICON:
+ parse_menu_icon_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_FRAME_STYLE_SET:
+ parse_style_set_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_FRAME:
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed inside a <%s> element"),
+ element_name, "frame");
+ break;
+ case STATE_WINDOW:
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed inside a <%s> element"),
+ element_name, "window");
+ break;
+ case STATE_FALLBACK:
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed inside a <%s> element"),
+ element_name, "fallback");
+ break;
+ }
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+
+ switch (peek_state (info))
+ {
+ case STATE_START:
+ break;
+ case STATE_THEME:
+ g_assert (info->theme);
+
+ if (!meta_theme_validate (info->theme, error))
+ {
+ add_context_to_error (error, context);
+ meta_theme_free (info->theme);
+ info->theme = NULL;
+ }
+
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_START);
+ break;
+ case STATE_INFO:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_THEME);
+ break;
+ case STATE_NAME:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_INFO);
+ break;
+ case STATE_AUTHOR:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_INFO);
+ break;
+ case STATE_COPYRIGHT:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_INFO);
+ break;
+ case STATE_DATE:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_INFO);
+ break;
+ case STATE_DESCRIPTION:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_INFO);
+ break;
+ case STATE_CONSTANT:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_THEME);
+ break;
+ case STATE_FRAME_GEOMETRY:
+ g_assert (info->layout);
+
+ if (!meta_frame_layout_validate (info->layout,
+ error))
+ {
+ add_context_to_error (error, context);
+ }
+
+ /* layout will already be stored in the theme under
+ * its name
+ */
+ meta_frame_layout_unref (info->layout);
+ info->layout = NULL;
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_THEME);
+ break;
+ case STATE_DISTANCE:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
+ break;
+ case STATE_BORDER:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
+ break;
+ case STATE_ASPECT_RATIO:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_FRAME_GEOMETRY);
+ break;
+ case STATE_DRAW_OPS:
+ {
+ g_assert (info->op_list);
+
+ if (!meta_draw_op_list_validate (info->op_list,
+ error))
+ {
+ add_context_to_error (error, context);
+ meta_draw_op_list_unref (info->op_list);
+ info->op_list = NULL;
+ }
+
+ pop_state (info);
+
+ switch (peek_state (info))
+ {
+ case STATE_BUTTON:
+ case STATE_PIECE:
+ case STATE_MENU_ICON:
+ /* Leave info->op_list to be picked up
+ * when these elements are closed
+ */
+ g_assert (info->op_list);
+ break;
+ case STATE_THEME:
+ g_assert (info->op_list);
+ meta_draw_op_list_unref (info->op_list);
+ info->op_list = NULL;
+ break;
+ default:
+ /* Op list can't occur in other contexts */
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ break;
+ case STATE_LINE:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_RECTANGLE:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_ARC:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_CLIP:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_TINT:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_GRADIENT:
+ g_assert (info->op);
+ g_assert (info->op->type == META_DRAW_GRADIENT);
+ if (!meta_gradient_spec_validate (info->op->data.gradient.gradient_spec,
+ error))
+ {
+ add_context_to_error (error, context);
+ meta_draw_op_free (info->op);
+ info->op = NULL;
+ }
+ else
+ {
+ g_assert (info->op_list);
+ meta_draw_op_list_append (info->op_list, info->op);
+ info->op = NULL;
+ }
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_IMAGE:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_GTK_ARROW:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_GTK_BOX:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_GTK_VLINE:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_ICON:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_TITLE:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_INCLUDE:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_TILE:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_DRAW_OPS);
+ break;
+ case STATE_COLOR:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_GRADIENT);
+ break;
+ case STATE_FRAME_STYLE:
+ g_assert (info->style);
+
+ if (!meta_frame_style_validate (info->style,
+ info->theme->format_version,
+ error))
+ {
+ add_context_to_error (error, context);
+ }
+
+ /* Frame style is in the theme hash table and a ref
+ * is held there
+ */
+ meta_frame_style_unref (info->style);
+ info->style = NULL;
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_THEME);
+ break;
+ case STATE_PIECE:
+ g_assert (info->style);
+ if (info->op_list == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No draw_ops provided for frame piece"));
+ }
+ else
+ {
+ info->style->pieces[info->piece] = info->op_list;
+ info->op_list = NULL;
+ }
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_FRAME_STYLE);
+ break;
+ case STATE_BUTTON:
+ g_assert (info->style);
+ if (info->op_list == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No draw_ops provided for button"));
+ }
+ else
+ {
+ info->style->buttons[info->button_type][info->button_state] =
+ info->op_list;
+ info->op_list = NULL;
+ }
+ pop_state (info);
+ break;
+#ifdef USE_UBUNTU_CODE
+ case STATE_SHADOW:
+ g_assert (info->style);
+ pop_state (info);
+ break;
+ case STATE_PADDING:
+ g_assert (info->style);
+ pop_state (info);
+ break;
+#endif
+ case STATE_MENU_ICON:
+ g_assert (info->theme);
+ if (info->op_list != NULL)
+ {
+ meta_draw_op_list_unref (info->op_list);
+ info->op_list = NULL;
+ }
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_THEME);
+ break;
+ case STATE_FRAME_STYLE_SET:
+ g_assert (info->style_set);
+
+ if (!meta_frame_style_set_validate (info->style_set,
+ error))
+ {
+ add_context_to_error (error, context);
+ }
+
+ /* Style set is in the theme hash table and a reference
+ * is held there.
+ */
+ meta_frame_style_set_unref (info->style_set);
+ info->style_set = NULL;
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_THEME);
+ break;
+ case STATE_FRAME:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_FRAME_STYLE_SET);
+ break;
+ case STATE_WINDOW:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_THEME);
+ break;
+ case STATE_FALLBACK:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_THEME);
+ break;
+ }
+}
+
+#define NO_TEXT(element_name) set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, _("No text is allowed inside element <%s>"), element_name)
+
+static gboolean
+all_whitespace (const char *text,
+ int text_len)
+{
+ const char *p;
+ const char *end;
+
+ p = text;
+ end = text + text_len;
+
+ while (p != end)
+ {
+ if (!g_ascii_isspace (*p))
+ return FALSE;
+
+ p = g_utf8_next_char (p);
+ }
+
+ return TRUE;
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+
+ if (all_whitespace (text, text_len))
+ return;
+
+ /* FIXME http://bugzilla.gnome.org/show_bug.cgi?id=70448 would
+ * allow a nice cleanup here.
+ */
+
+ switch (peek_state (info))
+ {
+ case STATE_START:
+ g_assert_not_reached (); /* gmarkup shouldn't do this */
+ break;
+ case STATE_THEME:
+ NO_TEXT ("metacity_theme");
+ break;
+ case STATE_INFO:
+ NO_TEXT ("info");
+ break;
+ case STATE_NAME:
+ if (info->theme->readable_name != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("<%s> specified twice for this theme"),
+ "name");
+ return;
+ }
+
+ info->theme->readable_name = g_strndup (text, text_len);
+ break;
+ case STATE_AUTHOR:
+ if (info->theme->author != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("<%s> specified twice for this theme"),
+ "author");
+ return;
+ }
+
+ info->theme->author = g_strndup (text, text_len);
+ break;
+ case STATE_COPYRIGHT:
+ if (info->theme->copyright != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("<%s> specified twice for this theme"),
+ "copyright");
+ return;
+ }
+
+ info->theme->copyright = g_strndup (text, text_len);
+ break;
+ case STATE_DATE:
+ if (info->theme->date != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("<%s> specified twice for this theme"),
+ "date");
+ return;
+ }
+
+ info->theme->date = g_strndup (text, text_len);
+ break;
+ case STATE_DESCRIPTION:
+ if (info->theme->description != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("<%s> specified twice for this theme"),
+ "description");
+ return;
+ }
+
+ info->theme->description = g_strndup (text, text_len);
+ break;
+ case STATE_CONSTANT:
+ NO_TEXT ("constant");
+ break;
+ case STATE_FRAME_GEOMETRY:
+ NO_TEXT ("frame_geometry");
+ break;
+ case STATE_DISTANCE:
+ NO_TEXT ("distance");
+ break;
+ case STATE_BORDER:
+ NO_TEXT ("border");
+ break;
+ case STATE_ASPECT_RATIO:
+ NO_TEXT ("aspect_ratio");
+ break;
+ case STATE_DRAW_OPS:
+ NO_TEXT ("draw_ops");
+ break;
+ case STATE_LINE:
+ NO_TEXT ("line");
+ break;
+ case STATE_RECTANGLE:
+ NO_TEXT ("rectangle");
+ break;
+ case STATE_ARC:
+ NO_TEXT ("arc");
+ break;
+ case STATE_CLIP:
+ NO_TEXT ("clip");
+ break;
+ case STATE_TINT:
+ NO_TEXT ("tint");
+ break;
+ case STATE_GRADIENT:
+ NO_TEXT ("gradient");
+ break;
+ case STATE_IMAGE:
+ NO_TEXT ("image");
+ break;
+ case STATE_GTK_ARROW:
+ NO_TEXT ("gtk_arrow");
+ break;
+ case STATE_GTK_BOX:
+ NO_TEXT ("gtk_box");
+ break;
+ case STATE_GTK_VLINE:
+ NO_TEXT ("gtk_vline");
+ break;
+ case STATE_ICON:
+ NO_TEXT ("icon");
+ break;
+ case STATE_TITLE:
+ NO_TEXT ("title");
+ break;
+ case STATE_INCLUDE:
+ NO_TEXT ("include");
+ break;
+ case STATE_TILE:
+ NO_TEXT ("tile");
+ break;
+ case STATE_COLOR:
+ NO_TEXT ("color");
+ break;
+ case STATE_FRAME_STYLE:
+ NO_TEXT ("frame_style");
+ break;
+ case STATE_PIECE:
+ NO_TEXT ("piece");
+ break;
+ case STATE_BUTTON:
+ NO_TEXT ("button");
+ break;
+#ifdef USE_UBUNTU_CODE
+ case STATE_SHADOW:
+ NO_TEXT ("shadow");
+ break;
+ case STATE_PADDING:
+ NO_TEXT ("padding");
+ break;
+#endif
+ case STATE_MENU_ICON:
+ NO_TEXT ("menu_icon");
+ break;
+ case STATE_FRAME_STYLE_SET:
+ NO_TEXT ("frame_style_set");
+ break;
+ case STATE_FRAME:
+ NO_TEXT ("frame");
+ break;
+ case STATE_WINDOW:
+ NO_TEXT ("window");
+ break;
+ case STATE_FALLBACK:
+ NO_TEXT ("fallback");
+ break;
+ }
+}
+
+/* We were intending to put the version number
+ * in the subdirectory name, but we ended up
+ * using the filename instead. The "-1" survives
+ * as a fossil.
+ */
+#define THEME_SUBDIR "metacity-1"
+
+/* Highest version of the theme format to
+ * look out for.
+ */
+#define THEME_VERSION 2
+
+#define MARCO_THEME_FILENAME_FORMAT "metacity-theme-%d.xml"
+
+MetaTheme*
+meta_theme_load (const char *theme_name,
+ GError **err)
+{
+ GMarkupParseContext *context;
+ GError *error;
+ ParseInfo info;
+ char *text;
+ gsize length;
+ char *theme_file;
+ char *theme_dir;
+ MetaTheme *retval;
+ guint version;
+ const gchar* const* xdg_data_dirs;
+ int i;
+
+ text = NULL;
+ length = 0;
+ retval = NULL;
+ context = NULL;
+
+ theme_dir = NULL;
+ theme_file = NULL;
+
+ if (meta_is_debugging ())
+ {
+ gchar *theme_filename = g_strdup_printf (MARCO_THEME_FILENAME_FORMAT,
+ THEME_VERSION);
+
+ /* Try in themes in our source tree */
+ theme_dir = g_build_filename ("./themes", theme_name, NULL);
+
+ theme_file = g_build_filename (theme_dir,
+ theme_filename,
+ NULL);
+
+ error = NULL;
+ if (!g_file_get_contents (theme_file,
+ &text,
+ &length,
+ &error))
+ {
+ meta_topic (META_DEBUG_THEMES, "Failed to read theme from file %s: %s\n",
+ theme_file, error->message);
+ g_error_free (error);
+ g_free (theme_dir);
+ g_free (theme_file);
+ theme_file = NULL;
+ }
+ version = THEME_VERSION;
+
+ g_free (theme_filename);
+ }
+
+ /* We try all supported versions from current to oldest */
+ for (version = THEME_VERSION; (version > 0) && (text == NULL); version--)
+ {
+ gchar *theme_filename = g_strdup_printf (MARCO_THEME_FILENAME_FORMAT,
+ version);
+
+ /* We try first in home dir, XDG_DATA_DIRS, then system dir for themes */
+
+ /* Try home dir for themes */
+ theme_dir = g_build_filename (g_get_home_dir (),
+ ".themes",
+ theme_name,
+ THEME_SUBDIR,
+ NULL);
+
+ theme_file = g_build_filename (theme_dir,
+ theme_filename,
+ NULL);
+
+ error = NULL;
+ if (!g_file_get_contents (theme_file,
+ &text,
+ &length,
+ &error))
+ {
+ meta_topic (META_DEBUG_THEMES, "Failed to read theme from file %s: %s\n",
+ theme_file, error->message);
+ g_error_free (error);
+ g_free (theme_dir);
+ g_free (theme_file);
+ theme_file = NULL;
+ }
+
+ /* Try each XDG_DATA_DIRS for theme */
+ xdg_data_dirs = g_get_system_data_dirs();
+ for(i = 0; xdg_data_dirs[i] != NULL; i++)
+ {
+ if (text == NULL)
+ {
+ theme_dir = g_build_filename (xdg_data_dirs[i],
+ "themes",
+ theme_name,
+ THEME_SUBDIR,
+ NULL);
+
+ theme_file = g_build_filename (theme_dir,
+ theme_filename,
+ NULL);
+
+ error = NULL;
+ if (!g_file_get_contents (theme_file,
+ &text,
+ &length,
+ &error))
+ {
+ meta_topic (META_DEBUG_THEMES, "Failed to read theme from file %s: %s\n",
+ theme_file, error->message);
+ g_error_free (error);
+ g_free (theme_dir);
+ g_free (theme_file);
+ theme_file = NULL;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ /* Look for themes in MARCO_DATADIR */
+ if (text == NULL)
+ {
+ theme_dir = g_build_filename (MARCO_DATADIR,
+ "themes",
+ theme_name,
+ THEME_SUBDIR,
+ NULL);
+
+ theme_file = g_build_filename (theme_dir,
+ theme_filename,
+ NULL);
+
+ error = NULL;
+ if (!g_file_get_contents (theme_file,
+ &text,
+ &length,
+ &error))
+ {
+ meta_topic (META_DEBUG_THEMES, "Failed to read theme from file %s: %s\n",
+ theme_file, error->message);
+ g_error_free (error);
+ g_free (theme_dir);
+ g_free (theme_file);
+ theme_file = NULL;
+ }
+ }
+
+ g_free (theme_filename);
+ }
+
+ if (text == NULL)
+ {
+ g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("Failed to find a valid file for theme %s\n"),
+ theme_name);
+
+ return NULL; /* all fallbacks failed */
+ }
+
+ meta_topic (META_DEBUG_THEMES, "Parsing theme file %s\n", theme_file);
+
+
+ parse_info_init (&info);
+ info.theme_name = theme_name;
+
+ /* pass ownership to info so we free it with the info */
+ info.theme_file = theme_file;
+ info.theme_dir = theme_dir;
+
+ info.format_version = version + 1;
+
+ context = g_markup_parse_context_new (&marco_theme_parser,
+ 0, &info, NULL);
+
+ error = NULL;
+ if (!g_markup_parse_context_parse (context,
+ text,
+ length,
+ &error))
+ goto out;
+
+ error = NULL;
+ if (!g_markup_parse_context_end_parse (context, &error))
+ goto out;
+
+ goto out;
+
+ out:
+
+ if (context)
+ g_markup_parse_context_free (context);
+ g_free (text);
+
+ if (info.theme)
+ info.theme->format_version = info.format_version;
+
+ if (error)
+ {
+ g_propagate_error (err, error);
+ }
+ else if (info.theme)
+ {
+ /* Steal theme from info */
+ retval = info.theme;
+ info.theme = NULL;
+ }
+ else
+ {
+ g_set_error (err, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Theme file %s did not contain a root <metacity_theme> element"),
+ info.theme_file);
+ }
+
+ parse_info_free (&info);
+
+ return retval;
+}
diff --git a/src/ui/theme-parser.h b/src/ui/theme-parser.h
new file mode 100644
index 00000000..ff090ee9
--- /dev/null
+++ b/src/ui/theme-parser.h
@@ -0,0 +1,32 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco theme parsing */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "theme.h"
+
+#ifndef META_THEME_PARSER_H
+#define META_THEME_PARSER_H
+
+MetaTheme* meta_theme_load (const char *theme_name,
+ GError **err);
+
+#endif
diff --git a/src/ui/theme-viewer.c b/src/ui/theme-viewer.c
new file mode 100644
index 00000000..c4a1fcd6
--- /dev/null
+++ b/src/ui/theme-viewer.c
@@ -0,0 +1,1338 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco theme viewer and test app main() */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "util.h"
+#include "theme.h"
+#include "theme-parser.h"
+#include "preview-widget.h"
+#include <gtk/gtk.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libintl.h>
+#define _(x) dgettext (GETTEXT_PACKAGE, x)
+#define N_(x) x
+
+/* We need to compute all different button arrangements
+ * in terms of button location. We don't care about
+ * different arrangements in terms of button function.
+ *
+ * So if dups are allowed, from 0-4 buttons on the left, from 0-4 on
+ * the right, 5x5=25 combinations.
+ *
+ * If no dups, 0-4 on left determines the number on the right plus
+ * we have a special case for the "no buttons on either side" case.
+ */
+#ifndef ALLOW_DUPLICATE_BUTTONS
+#define BUTTON_LAYOUT_COMBINATIONS (MAX_BUTTONS_PER_CORNER + 1 + 1)
+#else
+#define BUTTON_LAYOUT_COMBINATIONS ((MAX_BUTTONS_PER_CORNER+1)*(MAX_BUTTONS_PER_CORNER+1))
+#endif
+
+enum
+{
+ FONT_SIZE_SMALL,
+ FONT_SIZE_NORMAL,
+ FONT_SIZE_LARGE,
+ FONT_SIZE_LAST
+};
+
+static MetaTheme *global_theme = NULL;
+static GtkWidget *previews[META_FRAME_TYPE_LAST*FONT_SIZE_LAST + BUTTON_LAYOUT_COMBINATIONS] = { NULL, };
+static double milliseconds_to_draw_frame = 0.0;
+
+static void run_position_expression_tests (void);
+#if 0
+static void run_position_expression_timings (void);
+#endif
+static void run_theme_benchmark (void);
+
+
+static GtkItemFactoryEntry menu_items[] =
+{
+ { N_("/_Windows"), NULL, NULL, 0, "<Branch>" },
+ { N_("/Windows/tearoff"), NULL, NULL, 0, "<Tearoff>" },
+ { N_("/Windows/_Dialog"), "<control>d", NULL, 0, NULL },
+ { N_("/Windows/_Modal dialog"), NULL, NULL, 0, NULL },
+ { N_("/Windows/_Utility"), "<control>u", NULL, 0, NULL },
+ { N_("/Windows/_Splashscreen"), "<control>s", NULL, 0, NULL },
+ { N_("/Windows/_Top dock"), NULL, NULL, 0, NULL },
+ { N_("/Windows/_Bottom dock"), NULL, NULL, 0, NULL },
+ { N_("/Windows/_Left dock"), NULL, NULL, 0, NULL },
+ { N_("/Windows/_Right dock"), NULL, NULL, 0, NULL },
+ { N_("/Windows/_All docks"), NULL, NULL, 0, NULL },
+ { N_("/Windows/Des_ktop"), NULL, NULL, 0, NULL }
+};
+
+static GtkWidget *
+normal_contents (void)
+{
+ GtkWidget *table;
+ GtkWidget *toolbar;
+ GtkWidget *handlebox;
+ GtkWidget *statusbar;
+ GtkWidget *contents;
+ GtkWidget *sw;
+ GtkItemFactory *item_factory;
+
+ table = gtk_table_new (1, 4, FALSE);
+
+ /* Create the menubar
+ */
+
+ item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", NULL);
+
+ gtk_item_factory_set_translate_func(item_factory,
+ (GtkTranslateFunc)gettext, NULL, NULL);
+
+ /* Set up item factory to go away */
+ g_object_ref (item_factory);
+ g_object_ref_sink (item_factory);
+ g_object_unref (item_factory);
+ g_object_set_data_full (G_OBJECT (table),
+ "<main>",
+ item_factory,
+ (GDestroyNotify) g_object_unref);
+
+ /* create menu items */
+ gtk_item_factory_create_items (item_factory, G_N_ELEMENTS (menu_items),
+ menu_items, NULL);
+
+ gtk_table_attach (GTK_TABLE (table),
+ gtk_item_factory_get_widget (item_factory, "<main>"),
+ /* X direction */ /* Y direction */
+ 0, 1, 0, 1,
+ GTK_EXPAND | GTK_FILL, 0,
+ 0, 0);
+
+ /* Create the toolbar
+ */
+ toolbar = gtk_toolbar_new ();
+
+ GtkToolItem *newButton = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
+ gtk_tool_item_set_tooltip_text(newButton,
+ "Open another one of these windows");
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar),
+ newButton,
+ -1); /*-1 means append to end of toolbar*/
+
+ GtkToolItem *openButton = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN);
+ gtk_tool_item_set_tooltip_text(openButton,
+ "This is a demo button with an \'open\' icon");
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar),
+ openButton,
+ -1); /*-1 means append to end of toolbar*/
+
+ GtkToolItem *quitButton = gtk_tool_button_new_from_stock(GTK_STOCK_QUIT);
+ gtk_tool_item_set_tooltip_text(quitButton,
+ "This is a demo button with a \'quit\' icon");
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar),
+ quitButton,
+ -1); /*-1 means append to end of toolbar*/
+
+
+ handlebox = gtk_handle_box_new ();
+
+ gtk_container_add (GTK_CONTAINER (handlebox), toolbar);
+
+ gtk_table_attach (GTK_TABLE (table),
+ handlebox,
+ /* X direction */ /* Y direction */
+ 0, 1, 1, 2,
+ GTK_EXPAND | GTK_FILL, 0,
+ 0, 0);
+
+ /* Create document
+ */
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_IN);
+
+ gtk_table_attach (GTK_TABLE (table),
+ sw,
+ /* X direction */ /* Y direction */
+ 0, 1, 2, 3,
+ GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
+ 0, 0);
+
+ contents = gtk_text_view_new ();
+ gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (contents),
+ PANGO_WRAP_WORD);
+
+ gtk_container_add (GTK_CONTAINER (sw),
+ contents);
+
+ /* Create statusbar */
+
+ statusbar = gtk_statusbar_new ();
+ gtk_table_attach (GTK_TABLE (table),
+ statusbar,
+ /* X direction */ /* Y direction */
+ 0, 1, 3, 4,
+ GTK_EXPAND | GTK_FILL, 0,
+ 0, 0);
+
+ gtk_widget_show_all (table);
+
+ return table;
+}
+
+static void
+update_spacings (GtkWidget *vbox,
+ GtkWidget *action_area)
+{
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 2);
+ gtk_box_set_spacing (GTK_BOX (action_area), 10);
+ gtk_container_set_border_width (GTK_CONTAINER (action_area), 5);
+}
+
+static GtkWidget*
+dialog_contents (void)
+{
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *action_area;
+ GtkWidget *label;
+ GtkWidget *image;
+ GtkWidget *button;
+
+ vbox = gtk_vbox_new (FALSE, 0);
+
+ action_area = gtk_hbutton_box_new ();
+
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (action_area),
+ GTK_BUTTONBOX_END);
+
+ button = gtk_button_new_from_stock (GTK_STOCK_OK);
+ gtk_box_pack_end (GTK_BOX (action_area),
+ button,
+ FALSE, TRUE, 0);
+
+ gtk_box_pack_end (GTK_BOX (vbox), action_area,
+ FALSE, TRUE, 0);
+
+ update_spacings (vbox, action_area);
+
+ label = gtk_label_new (_("This is a sample message in a sample dialog"));
+ image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_INFO,
+ GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+
+ gtk_box_pack_start (GTK_BOX (hbox), image,
+ FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox), label,
+ TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (vbox),
+ hbox,
+ FALSE, FALSE, 0);
+
+ gtk_widget_show_all (vbox);
+
+ return vbox;
+}
+
+static GtkWidget*
+utility_contents (void)
+{
+ GtkWidget *table;
+ GtkWidget *button;
+ int i, j;
+
+ table = gtk_table_new (3, 4, FALSE);
+
+ i = 0;
+ while (i < 3)
+ {
+ j = 0;
+ while (j < 4)
+ {
+ char *str;
+
+ str = g_strdup_printf ("_%c", (char) ('A' + 4*i + j));
+
+ button = gtk_button_new_with_mnemonic (str);
+
+ g_free (str);
+
+ gtk_table_attach (GTK_TABLE (table),
+ button,
+ /* X direction */ /* Y direction */
+ i, i+1, j, j+1,
+ GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,
+ 0, 0);
+
+ ++j;
+ }
+
+ ++i;
+ }
+
+ gtk_widget_show_all (table);
+
+ return table;
+}
+
+static GtkWidget*
+menu_contents (void)
+{
+ GtkWidget *vbox;
+ GtkWidget *mi;
+ int i;
+ GtkWidget *frame;
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame),
+ GTK_SHADOW_OUT);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+
+ i = 0;
+ while (i < 10)
+ {
+ char *str = g_strdup_printf (_("Fake menu item %d\n"), i + 1);
+ mi = gtk_label_new (str);
+ gtk_misc_set_alignment (GTK_MISC (mi), 0.0, 0.5);
+ g_free (str);
+ gtk_box_pack_start (GTK_BOX (vbox), mi, FALSE, FALSE, 0);
+
+ ++i;
+ }
+
+ gtk_container_add (GTK_CONTAINER (frame), vbox);
+
+ gtk_widget_show_all (frame);
+
+ return frame;
+}
+
+static GtkWidget*
+border_only_contents (void)
+{
+ GtkWidget *event_box;
+ GtkWidget *vbox;
+ GtkWidget *w;
+ GdkColor color;
+
+ event_box = gtk_event_box_new ();
+
+ color.red = 40000;
+ color.green = 0;
+ color.blue = 40000;
+ gtk_widget_modify_bg (event_box, GTK_STATE_NORMAL, &color);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 3);
+
+ w = gtk_label_new (_("Border-only window"));
+ gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 0);
+ w = gtk_button_new_with_label (_("Bar"));
+ gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (event_box), vbox);
+
+ gtk_widget_show_all (event_box);
+
+ return event_box;
+}
+
+static GtkWidget*
+get_window_contents (MetaFrameType type,
+ const char **title)
+{
+ switch (type)
+ {
+ case META_FRAME_TYPE_NORMAL:
+ *title = _("Normal Application Window");
+ return normal_contents ();
+
+ case META_FRAME_TYPE_DIALOG:
+ *title = _("Dialog Box");
+ return dialog_contents ();
+
+ case META_FRAME_TYPE_MODAL_DIALOG:
+ *title = _("Modal Dialog Box");
+ return dialog_contents ();
+
+ case META_FRAME_TYPE_UTILITY:
+ *title = _("Utility Palette");
+ return utility_contents ();
+
+ case META_FRAME_TYPE_MENU:
+ *title = _("Torn-off Menu");
+ return menu_contents ();
+
+ case META_FRAME_TYPE_BORDER:
+ *title = _("Border");
+ return border_only_contents ();
+
+ case META_FRAME_TYPE_LAST:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return NULL;
+}
+
+static MetaFrameFlags
+get_window_flags (MetaFrameType type)
+{
+ MetaFrameFlags flags;
+
+ flags = META_FRAME_ALLOWS_DELETE |
+ META_FRAME_ALLOWS_MENU |
+ META_FRAME_ALLOWS_MINIMIZE |
+ META_FRAME_ALLOWS_MAXIMIZE |
+ META_FRAME_ALLOWS_VERTICAL_RESIZE |
+ META_FRAME_ALLOWS_HORIZONTAL_RESIZE |
+ META_FRAME_HAS_FOCUS |
+ META_FRAME_ALLOWS_SHADE |
+ META_FRAME_ALLOWS_MOVE;
+
+ switch (type)
+ {
+ case META_FRAME_TYPE_NORMAL:
+ break;
+
+ case META_FRAME_TYPE_DIALOG:
+ case META_FRAME_TYPE_MODAL_DIALOG:
+ flags &= ~(META_FRAME_ALLOWS_MINIMIZE |
+ META_FRAME_ALLOWS_MAXIMIZE);
+ break;
+
+ case META_FRAME_TYPE_UTILITY:
+ flags &= ~(META_FRAME_ALLOWS_MINIMIZE |
+ META_FRAME_ALLOWS_MAXIMIZE);
+ break;
+
+ case META_FRAME_TYPE_MENU:
+ flags &= ~(META_FRAME_ALLOWS_MINIMIZE |
+ META_FRAME_ALLOWS_MAXIMIZE);
+ break;
+
+ case META_FRAME_TYPE_BORDER:
+ break;
+
+ case META_FRAME_TYPE_LAST:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return flags;
+}
+
+static GtkWidget*
+preview_collection (int font_size,
+ PangoFontDescription *base_desc)
+{
+ GtkWidget *box;
+ GtkWidget *sw;
+ GdkColor desktop_color;
+ int i;
+ GtkWidget *eventbox;
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ box = gtk_vbox_new (FALSE, 0);
+ gtk_box_set_spacing (GTK_BOX (box), 20);
+ gtk_container_set_border_width (GTK_CONTAINER (box), 20);
+
+ eventbox = gtk_event_box_new ();
+ gtk_container_add (GTK_CONTAINER (eventbox), box);
+
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), eventbox);
+
+ desktop_color.red = 0x5144;
+ desktop_color.green = 0x75D6;
+ desktop_color.blue = 0xA699;
+
+ gtk_widget_modify_bg (eventbox, GTK_STATE_NORMAL, &desktop_color);
+
+ i = 0;
+ while (i < META_FRAME_TYPE_LAST)
+ {
+ const char *title = NULL;
+ GtkWidget *contents;
+ GtkWidget *align;
+ double xalign, yalign;
+ GtkWidget *eventbox2;
+ GtkWidget *preview;
+ PangoFontDescription *font_desc;
+ double scale;
+
+ eventbox2 = gtk_event_box_new ();
+
+ preview = meta_preview_new ();
+
+ gtk_container_add (GTK_CONTAINER (eventbox2), preview);
+
+ meta_preview_set_frame_type (META_PREVIEW (preview), i);
+ meta_preview_set_frame_flags (META_PREVIEW (preview),
+ get_window_flags (i));
+
+ meta_preview_set_theme (META_PREVIEW (preview), global_theme);
+
+ contents = get_window_contents (i, &title);
+
+ meta_preview_set_title (META_PREVIEW (preview), title);
+
+ gtk_container_add (GTK_CONTAINER (preview), contents);
+
+ if (i == META_FRAME_TYPE_MENU)
+ {
+ xalign = 0.0;
+ yalign = 0.0;
+ }
+ else
+ {
+ xalign = 0.5;
+ yalign = 0.5;
+ }
+
+ align = gtk_alignment_new (0.0, 0.0, xalign, yalign);
+ gtk_container_add (GTK_CONTAINER (align), eventbox2);
+
+ gtk_box_pack_start (GTK_BOX (box), align, TRUE, TRUE, 0);
+
+ switch (font_size)
+ {
+ case FONT_SIZE_SMALL:
+ scale = PANGO_SCALE_XX_SMALL;
+ break;
+ case FONT_SIZE_LARGE:
+ scale = PANGO_SCALE_XX_LARGE;
+ break;
+ default:
+ scale = 1.0;
+ break;
+ }
+
+ if (scale != 1.0)
+ {
+ font_desc = pango_font_description_new ();
+
+ pango_font_description_set_size (font_desc,
+ MAX (pango_font_description_get_size (base_desc) * scale, 1));
+
+ gtk_widget_modify_font (preview, font_desc);
+
+ pango_font_description_free (font_desc);
+ }
+
+ previews[font_size*META_FRAME_TYPE_LAST + i] = preview;
+
+ ++i;
+ }
+
+ return sw;
+}
+
+static MetaButtonLayout different_layouts[BUTTON_LAYOUT_COMBINATIONS];
+
+static void
+init_layouts (void)
+{
+ int i;
+
+ /* Blank out all the layouts */
+ i = 0;
+ while (i < (int) G_N_ELEMENTS (different_layouts))
+ {
+ int j;
+
+ j = 0;
+ while (j < MAX_BUTTONS_PER_CORNER)
+ {
+ different_layouts[i].left_buttons[j] = META_BUTTON_FUNCTION_LAST;
+ different_layouts[i].right_buttons[j] = META_BUTTON_FUNCTION_LAST;
+ ++j;
+ }
+ ++i;
+ }
+
+#ifndef ALLOW_DUPLICATE_BUTTONS
+ i = 0;
+ while (i <= MAX_BUTTONS_PER_CORNER)
+ {
+ int j;
+
+ j = 0;
+ while (j < i)
+ {
+ different_layouts[i].right_buttons[j] = (MetaButtonFunction) j;
+ ++j;
+ }
+ while (j < MAX_BUTTONS_PER_CORNER)
+ {
+ different_layouts[i].left_buttons[j-i] = (MetaButtonFunction) j;
+ ++j;
+ }
+
+ ++i;
+ }
+
+ /* Special extra case for no buttons on either side */
+ different_layouts[i].left_buttons[0] = META_BUTTON_FUNCTION_LAST;
+ different_layouts[i].right_buttons[0] = META_BUTTON_FUNCTION_LAST;
+
+#else
+ /* FIXME this code is if we allow duplicate buttons,
+ * which we currently do not
+ */
+ int left;
+ int i;
+
+ left = 0;
+ i = 0;
+
+ while (left < MAX_BUTTONS_PER_CORNER)
+ {
+ int right;
+
+ right = 0;
+
+ while (right < MAX_BUTTONS_PER_CORNER)
+ {
+ int j;
+
+ static MetaButtonFunction left_functions[MAX_BUTTONS_PER_CORNER] = {
+ META_BUTTON_FUNCTION_MENU,
+ META_BUTTON_FUNCTION_MINIMIZE,
+ META_BUTTON_FUNCTION_MAXIMIZE,
+ META_BUTTON_FUNCTION_CLOSE
+ };
+ static MetaButtonFunction right_functions[MAX_BUTTONS_PER_CORNER] = {
+ META_BUTTON_FUNCTION_MINIMIZE,
+ META_BUTTON_FUNCTION_MAXIMIZE,
+ META_BUTTON_FUNCTION_CLOSE,
+ META_BUTTON_FUNCTION_MENU
+ };
+
+ g_assert (i < BUTTON_LAYOUT_COMBINATIONS);
+
+ j = 0;
+ while (j <= left)
+ {
+ different_layouts[i].left_buttons[j] = left_functions[j];
+ ++j;
+ }
+
+ j = 0;
+ while (j <= right)
+ {
+ different_layouts[i].right_buttons[j] = right_functions[j];
+ ++j;
+ }
+
+ ++i;
+
+ ++right;
+ }
+
+ ++left;
+ }
+#endif
+}
+
+
+static GtkWidget*
+previews_of_button_layouts (void)
+{
+ static gboolean initted = FALSE;
+ GtkWidget *box;
+ GtkWidget *sw;
+ GdkColor desktop_color;
+ int i;
+ GtkWidget *eventbox;
+
+ if (!initted)
+ {
+ init_layouts ();
+ initted = TRUE;
+ }
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ box = gtk_vbox_new (FALSE, 0);
+ gtk_box_set_spacing (GTK_BOX (box), 20);
+ gtk_container_set_border_width (GTK_CONTAINER (box), 20);
+
+ eventbox = gtk_event_box_new ();
+ gtk_container_add (GTK_CONTAINER (eventbox), box);
+
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), eventbox);
+
+ desktop_color.red = 0x5144;
+ desktop_color.green = 0x75D6;
+ desktop_color.blue = 0xA699;
+
+ gtk_widget_modify_bg (eventbox, GTK_STATE_NORMAL, &desktop_color);
+
+ i = 0;
+ while (i < BUTTON_LAYOUT_COMBINATIONS)
+ {
+ GtkWidget *align;
+ double xalign, yalign;
+ GtkWidget *eventbox2;
+ GtkWidget *preview;
+ char *title;
+
+ eventbox2 = gtk_event_box_new ();
+
+ preview = meta_preview_new ();
+
+ gtk_container_add (GTK_CONTAINER (eventbox2), preview);
+
+ meta_preview_set_theme (META_PREVIEW (preview), global_theme);
+
+ title = g_strdup_printf (_("Button layout test %d"), i+1);
+ meta_preview_set_title (META_PREVIEW (preview), title);
+ g_free (title);
+
+ meta_preview_set_button_layout (META_PREVIEW (preview),
+ &different_layouts[i]);
+
+ xalign = 0.5;
+ yalign = 0.5;
+
+ align = gtk_alignment_new (0.0, 0.0, xalign, yalign);
+ gtk_container_add (GTK_CONTAINER (align), eventbox2);
+
+ gtk_box_pack_start (GTK_BOX (box), align, TRUE, TRUE, 0);
+
+ previews[META_FRAME_TYPE_LAST*FONT_SIZE_LAST + i] = preview;
+
+ ++i;
+ }
+
+ return sw;
+}
+
+static GtkWidget*
+benchmark_summary (void)
+{
+ char *msg;
+ GtkWidget *label;
+
+ msg = g_strdup_printf (_("%g milliseconds to draw one window frame"),
+ milliseconds_to_draw_frame);
+ label = gtk_label_new (msg);
+ g_free (msg);
+
+ return label;
+}
+
+int
+main (int argc, char **argv)
+{
+ GtkWidget *window;
+ GtkWidget *collection;
+ GError *err;
+ clock_t start, end;
+ GtkWidget *notebook;
+ int i;
+
+ bindtextdomain (GETTEXT_PACKAGE, MARCO_LOCALEDIR);
+ textdomain(GETTEXT_PACKAGE);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ run_position_expression_tests ();
+#if 0
+ run_position_expression_timings ();
+#endif
+
+ gtk_init (&argc, &argv);
+
+ if (g_getenv ("MARCO_DEBUG") != NULL)
+ {
+ meta_set_debugging (TRUE);
+ meta_set_verbose (TRUE);
+ }
+
+ start = clock ();
+ err = NULL;
+ if (argc == 1)
+ global_theme = meta_theme_load ("ClearlooksRe", &err);
+ else if (argc == 2)
+ global_theme = meta_theme_load (argv[1], &err);
+ else
+ {
+ g_printerr (_("Usage: marco-theme-viewer [THEMENAME]\n"));
+ exit (1);
+ }
+ end = clock ();
+
+ if (global_theme == NULL)
+ {
+ g_printerr (_("Error loading theme: %s\n"),
+ err->message);
+ g_error_free (err);
+ exit (1);
+ }
+
+ g_print (_("Loaded theme \"%s\" in %g seconds\n"),
+ global_theme->name,
+ (end - start) / (double) CLOCKS_PER_SEC);
+
+ run_theme_benchmark ();
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size (GTK_WINDOW (window), 350, 350);
+
+ if (strcmp (global_theme->name, global_theme->readable_name)==0)
+ gtk_window_set_title (GTK_WINDOW (window),
+ global_theme->readable_name);
+ else
+ {
+ /* The theme directory name is different from the name the theme
+ * gives itself within its file. Display both, directory name first.
+ */
+ gchar *title = g_strconcat (global_theme->name, " - ",
+ global_theme->readable_name,
+ NULL);
+
+ gtk_window_set_title (GTK_WINDOW (window),
+ title);
+
+ g_free (title);
+ }
+
+ g_signal_connect (G_OBJECT (window), "destroy",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ gtk_widget_realize (window);
+ g_assert (window->style);
+ g_assert (window->style->font_desc);
+
+ notebook = gtk_notebook_new ();
+ gtk_container_add (GTK_CONTAINER (window), notebook);
+
+ collection = preview_collection (FONT_SIZE_NORMAL,
+ window->style->font_desc);
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
+ collection,
+ gtk_label_new (_("Normal Title Font")));
+
+ collection = preview_collection (FONT_SIZE_SMALL,
+ window->style->font_desc);
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
+ collection,
+ gtk_label_new (_("Small Title Font")));
+
+ collection = preview_collection (FONT_SIZE_LARGE,
+ window->style->font_desc);
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
+ collection,
+ gtk_label_new (_("Large Title Font")));
+
+ collection = previews_of_button_layouts ();
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
+ collection,
+ gtk_label_new (_("Button Layouts")));
+
+ collection = benchmark_summary ();
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
+ collection,
+ gtk_label_new (_("Benchmark")));
+
+ i = 0;
+ while (i < (int) G_N_ELEMENTS (previews))
+ {
+ /* preview widget likes to be realized before its size request.
+ * it's lame that way.
+ */
+ gtk_widget_realize (previews[i]);
+
+ ++i;
+ }
+
+ gtk_widget_show_all (window);
+
+ gtk_main ();
+
+ return 0;
+}
+
+
+static MetaFrameFlags
+get_flags (GtkWidget *widget)
+{
+ return META_FRAME_ALLOWS_DELETE |
+ META_FRAME_ALLOWS_MENU |
+ META_FRAME_ALLOWS_MINIMIZE |
+ META_FRAME_ALLOWS_MAXIMIZE |
+ META_FRAME_ALLOWS_VERTICAL_RESIZE |
+ META_FRAME_ALLOWS_HORIZONTAL_RESIZE |
+ META_FRAME_HAS_FOCUS |
+ META_FRAME_ALLOWS_SHADE |
+ META_FRAME_ALLOWS_MOVE;
+}
+
+static int
+get_text_height (GtkWidget *widget)
+{
+ return meta_pango_font_desc_get_text_height (widget->style->font_desc,
+ gtk_widget_get_pango_context (widget));
+}
+
+static PangoLayout*
+create_title_layout (GtkWidget *widget)
+{
+ PangoLayout *layout;
+
+ layout = gtk_widget_create_pango_layout (widget, _("Window Title Goes Here"));
+
+ return layout;
+}
+
+static void
+run_theme_benchmark (void)
+{
+ GtkWidget* widget;
+ GdkPixmap *pixmap;
+ int top_height, bottom_height, left_width, right_width;
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST] =
+ {
+ META_BUTTON_STATE_NORMAL,
+ META_BUTTON_STATE_NORMAL,
+ META_BUTTON_STATE_NORMAL,
+ META_BUTTON_STATE_NORMAL
+ };
+ PangoLayout *layout;
+ clock_t start;
+ clock_t end;
+ GTimer *timer;
+ int i;
+ MetaButtonLayout button_layout;
+#define ITERATIONS 100
+ int client_width;
+ int client_height;
+ int inc;
+
+ widget = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_realize (widget);
+
+ meta_theme_get_frame_borders (global_theme,
+ META_FRAME_TYPE_NORMAL,
+ get_text_height (widget),
+ get_flags (widget),
+ &top_height,
+ &bottom_height,
+ &left_width,
+ &right_width);
+
+ layout = create_title_layout (widget);
+
+ i = 0;
+ while (i < MAX_BUTTONS_PER_CORNER)
+ {
+ button_layout.left_buttons[i] = META_BUTTON_FUNCTION_LAST;
+ button_layout.right_buttons[i] = META_BUTTON_FUNCTION_LAST;
+ ++i;
+ }
+
+ button_layout.left_buttons[0] = META_BUTTON_FUNCTION_MENU;
+
+ button_layout.right_buttons[0] = META_BUTTON_FUNCTION_MINIMIZE;
+ button_layout.right_buttons[1] = META_BUTTON_FUNCTION_MAXIMIZE;
+ button_layout.right_buttons[2] = META_BUTTON_FUNCTION_CLOSE;
+
+ timer = g_timer_new ();
+ start = clock ();
+
+ client_width = 50;
+ client_height = 50;
+ inc = 1000 / ITERATIONS; /* Increment to grow width/height,
+ * eliminates caching effects.
+ */
+
+ i = 0;
+ while (i < ITERATIONS)
+ {
+ /* Creating the pixmap in the loop is right, since
+ * GDK does the same with its double buffering.
+ */
+ pixmap = gdk_pixmap_new (widget->window,
+ client_width + left_width + right_width,
+ client_height + top_height + bottom_height,
+ -1);
+
+ meta_theme_draw_frame (global_theme,
+ widget,
+ pixmap,
+ NULL,
+ 0, 0,
+ META_FRAME_TYPE_NORMAL,
+ get_flags (widget),
+ client_width, client_height,
+ layout,
+ get_text_height (widget),
+ &button_layout,
+ button_states,
+ meta_preview_get_mini_icon (),
+ meta_preview_get_icon ());
+
+ g_object_unref (G_OBJECT (pixmap));
+
+ ++i;
+ client_width += inc;
+ client_height += inc;
+ }
+
+ end = clock ();
+ g_timer_stop (timer);
+
+ milliseconds_to_draw_frame = (g_timer_elapsed (timer, NULL) / (double) ITERATIONS) * 1000;
+
+ g_print (_("Drew %d frames in %g client-side seconds (%g milliseconds per frame) and %g seconds wall clock time including X server resources (%g milliseconds per frame)\n"),
+ ITERATIONS,
+ ((double)end - (double)start) / CLOCKS_PER_SEC,
+ (((double)end - (double)start) / CLOCKS_PER_SEC / (double) ITERATIONS) * 1000,
+ g_timer_elapsed (timer, NULL),
+ milliseconds_to_draw_frame);
+
+ g_timer_destroy (timer);
+ g_object_unref (G_OBJECT (layout));
+ gtk_widget_destroy (widget);
+
+#undef ITERATIONS
+}
+
+typedef struct
+{
+ GdkRectangle rect;
+ const char *expr;
+ int expected_x;
+ int expected_y;
+ MetaThemeError expected_error;
+} PositionExpressionTest;
+
+#define NO_ERROR -1
+
+static const PositionExpressionTest position_expression_tests[] = {
+ /* Just numbers */
+ { { 10, 20, 40, 50 },
+ "10", 20, 30, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "14.37", 24, 34, NO_ERROR },
+ /* Binary expressions with 2 ints */
+ { { 10, 20, 40, 50 },
+ "14 * 10", 150, 160, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "14 + 10", 34, 44, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "14 - 10", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "8 / 2", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "8 % 3", 12, 22, NO_ERROR },
+ /* Binary expressions with floats and mixed float/ints */
+ { { 10, 20, 40, 50 },
+ "7.0 / 3.5", 12, 22, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "12.1 / 3", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "12 / 2.95", 14, 24, NO_ERROR },
+ /* Binary expressions without whitespace after first number */
+ { { 10, 20, 40, 50 },
+ "14* 10", 150, 160, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "14+ 10", 34, 44, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "14- 10", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "8/ 2", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "7.0/ 3.5", 12, 22, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "12.1/ 3", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "12/ 2.95", 14, 24, NO_ERROR },
+ /* Binary expressions without whitespace before second number */
+ { { 10, 20, 40, 50 },
+ "14 *10", 150, 160, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "14 +10", 34, 44, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "14 -10", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "8 /2", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "7.0 /3.5", 12, 22, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "12.1 /3", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "12 /2.95", 14, 24, NO_ERROR },
+ /* Binary expressions without any whitespace */
+ { { 10, 20, 40, 50 },
+ "14*10", 150, 160, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "14+10", 34, 44, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "14-10", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "8/2", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "7.0/3.5", 12, 22, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "12.1/3", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "12/2.95", 14, 24, NO_ERROR },
+ /* Binary expressions with parentheses */
+ { { 10, 20, 40, 50 },
+ "(14) * (10)", 150, 160, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "(14) + (10)", 34, 44, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "(14) - (10)", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "(8) / (2)", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "(7.0) / (3.5)", 12, 22, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "(12.1) / (3)", 14, 24, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "(12) / (2.95)", 14, 24, NO_ERROR },
+ /* Lots of extra parentheses */
+ { { 10, 20, 40, 50 },
+ "(((14)) * ((10)))", 150, 160, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "((((14)))) + ((((((((10))))))))", 34, 44, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "((((((((((14 - 10))))))))))", 14, 24, NO_ERROR },
+ /* Binary expressions with variables */
+ { { 10, 20, 40, 50 },
+ "2 * width", 90, 100, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "2 * height", 110, 120, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "width - 10", 40, 50, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "height / 2", 35, 45, NO_ERROR },
+ /* More than two operands */
+ { { 10, 20, 40, 50 },
+ "8 / 2 + 5", 19, 29, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "8 * 2 + 5", 31, 41, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "8 + 2 * 5", 28, 38, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "8 + 8 / 2", 22, 32, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "14 / (2 + 5)", 12, 22, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "8 * (2 + 5)", 66, 76, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "(8 + 2) * 5", 60, 70, NO_ERROR },
+ { { 10, 20, 40, 50 },
+ "(8 + 8) / 2", 18, 28, NO_ERROR },
+ /* Errors */
+ { { 10, 20, 40, 50 },
+ "2 * foo", 0, 0, META_THEME_ERROR_UNKNOWN_VARIABLE },
+ { { 10, 20, 40, 50 },
+ "2 *", 0, 0, META_THEME_ERROR_FAILED },
+ { { 10, 20, 40, 50 },
+ "- width", 0, 0, META_THEME_ERROR_FAILED },
+ { { 10, 20, 40, 50 },
+ "5 % 1.0", 0, 0, META_THEME_ERROR_MOD_ON_FLOAT },
+ { { 10, 20, 40, 50 },
+ "1.0 % 5", 0, 0, META_THEME_ERROR_MOD_ON_FLOAT },
+ { { 10, 20, 40, 50 },
+ "! * 2", 0, 0, META_THEME_ERROR_BAD_CHARACTER },
+ { { 10, 20, 40, 50 },
+ " ", 0, 0, META_THEME_ERROR_FAILED },
+ { { 10, 20, 40, 50 },
+ "() () (( ) ()) ((()))", 0, 0, META_THEME_ERROR_FAILED },
+ { { 10, 20, 40, 50 },
+ "(*) () ((/) ()) ((()))", 0, 0, META_THEME_ERROR_FAILED },
+ { { 10, 20, 40, 50 },
+ "2 * 5 /", 0, 0, META_THEME_ERROR_FAILED },
+ { { 10, 20, 40, 50 },
+ "+ 2 * 5", 0, 0, META_THEME_ERROR_FAILED },
+ { { 10, 20, 40, 50 },
+ "+ 2 * 5", 0, 0, META_THEME_ERROR_FAILED }
+};
+
+static void
+run_position_expression_tests (void)
+{
+#if 0
+ int i;
+ MetaPositionExprEnv env;
+
+ i = 0;
+ while (i < (int) G_N_ELEMENTS (position_expression_tests))
+ {
+ GError *err;
+ gboolean retval;
+ const PositionExpressionTest *test;
+ PosToken *tokens;
+ int n_tokens;
+ int x, y;
+
+ test = &position_expression_tests[i];
+
+ if (g_getenv ("META_PRINT_TESTS") != NULL)
+ g_print ("Test expression: \"%s\" expecting x = %d y = %d",
+ test->expr, test->expected_x, test->expected_y);
+
+ err = NULL;
+
+ env.rect = meta_rect (test->rect.x, test->rect.y,
+ test->rect.width, test->rect.height);
+ env.object_width = -1;
+ env.object_height = -1;
+ env.left_width = 0;
+ env.right_width = 0;
+ env.top_height = 0;
+ env.bottom_height = 0;
+ env.title_width = 5;
+ env.title_height = 5;
+ env.icon_width = 32;
+ env.icon_height = 32;
+ env.mini_icon_width = 16;
+ env.mini_icon_height = 16;
+ env.theme = NULL;
+
+ if (err == NULL)
+ {
+ retval = meta_parse_position_expression (tokens, n_tokens,
+ &env,
+ &x, &y,
+ &err);
+ }
+
+ if (retval && err)
+ g_error (_("position expression test returned TRUE but set error"));
+ if (!retval && err == NULL)
+ g_error (_("position expression test returned FALSE but didn't set error"));
+ if (((int) test->expected_error) != NO_ERROR)
+ {
+ if (err == NULL)
+ g_error (_("Error was expected but none given"));
+ if (err->code != (int) test->expected_error)
+ g_error (_("Error %d was expected but %d given"),
+ test->expected_error, err->code);
+ }
+ else
+ {
+ if (err)
+ g_error (_("Error not expected but one was returned: %s"),
+ err->message);
+
+ if (x != test->expected_x)
+ g_error (_("x value was %d, %d was expected"), x, test->expected_x);
+
+ if (y != test->expected_y)
+ g_error (_("y value was %d, %d was expected"), y, test->expected_y);
+ }
+
+ if (err)
+ g_error_free (err);
+
+ meta_pos_tokens_free (tokens, n_tokens);
+ ++i;
+ }
+#endif
+}
+
+#if 0
+static void
+run_position_expression_timings (void)
+{
+ int i;
+ int iters;
+ clock_t start;
+ clock_t end;
+ MetaPositionExprEnv env;
+
+#define ITERATIONS 100000
+
+ start = clock ();
+
+ iters = 0;
+ i = 0;
+ while (iters < ITERATIONS)
+ {
+ const PositionExpressionTest *test;
+ int x, y;
+
+ test = &position_expression_tests[i];
+
+ env.x = test->rect.x;
+ env.y = test->rect.y;
+ env.width = test->rect.width;
+ env.height = test->rect.height;
+ env.object_width = -1;
+ env.object_height = -1;
+ env.left_width = 0;
+ env.right_width = 0;
+ env.top_height = 0;
+ env.bottom_height = 0;
+ env.title_width = 5;
+ env.title_height = 5;
+ env.icon_width = 32;
+ env.icon_height = 32;
+ env.mini_icon_width = 16;
+ env.mini_icon_height = 16;
+ env.theme = NULL;
+
+ meta_parse_position_expression (test->expr,
+ &env,
+ &x, &y, NULL);
+
+ ++iters;
+ ++i;
+ if (i == G_N_ELEMENTS (position_expression_tests))
+ i = 0;
+ }
+
+ end = clock ();
+
+ g_print (_("%d coordinate expressions parsed in %g seconds (%g seconds average)\n"),
+ ITERATIONS,
+ ((double)end - (double)start) / CLOCKS_PER_SEC,
+ ((double)end - (double)start) / CLOCKS_PER_SEC / (double) ITERATIONS);
+
+}
+#endif
diff --git a/src/ui/theme.c b/src/ui/theme.c
new file mode 100644
index 00000000..63103960
--- /dev/null
+++ b/src/ui/theme.c
@@ -0,0 +1,6652 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco Theme Rendering */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+/**
+ * \file theme.c Making Marco look pretty
+ *
+ * The window decorations drawn by Marco are described by files on disk
+ * known internally as "themes" (externally as "window border themes" on
+ * http://art.gnome.org/themes/marco/ or "Marco themes"). This file
+ * contains most of the code necessary to support themes; it does not
+ * contain the XML parser, which is in theme-parser.c.
+ *
+ * \bug This is a big file with lots of different subsystems, which might
+ * be better split out into separate files.
+ */
+
+/**
+ * \defgroup tokenizer The theme expression tokenizer
+ *
+ * Themes can use a simple expression language to represent the values of
+ * things. This is the tokeniser used for that language.
+ *
+ * \bug We could remove almost all this code by using GScanner instead,
+ * but we would also have to find every expression in every existing theme
+ * we could and make sure the parse trees were the same.
+ */
+
+/**
+ * \defgroup parser The theme expression parser
+ *
+ * Themes can use a simple expression language to represent the values of
+ * things. This is the parser used for that language.
+ */
+
+#include <config.h>
+#include "theme.h"
+#include "theme-parser.h"
+#include "util.h"
+#include "gradient.h"
+#include <gtk/gtk.h>
+#include <string.h>
+#include <stdlib.h>
+#define __USE_XOPEN
+#include <math.h>
+
+#define GDK_COLOR_RGBA(color) \
+ ((guint32) (0xff | \
+ (((color).red / 256) << 24) | \
+ (((color).green / 256) << 16) | \
+ (((color).blue / 256) << 8)))
+
+#define GDK_COLOR_RGB(color) \
+ ((guint32) ((((color).red / 256) << 16) | \
+ (((color).green / 256) << 8) | \
+ (((color).blue / 256))))
+
+#define ALPHA_TO_UCHAR(d) ((unsigned char) ((d) * 255))
+
+#define DEBUG_FILL_STRUCT(s) memset ((s), 0xef, sizeof (*(s)))
+#define CLAMP_UCHAR(v) ((guchar) (CLAMP (((int)v), (int)0, (int)255)))
+#define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
+
+static void gtk_style_shade (GdkColor *a,
+ GdkColor *b,
+ gdouble k);
+static void rgb_to_hls (gdouble *r,
+ gdouble *g,
+ gdouble *b);
+static void hls_to_rgb (gdouble *h,
+ gdouble *l,
+ gdouble *s);
+
+/**
+ * The current theme. (Themes are singleton.)
+ */
+static MetaTheme *meta_current_theme = NULL;
+
+static GdkPixbuf *
+colorize_pixbuf (GdkPixbuf *orig,
+ GdkColor *new_color)
+{
+ GdkPixbuf *pixbuf;
+ double intensity;
+ int x, y;
+ const guchar *src;
+ guchar *dest;
+ int orig_rowstride;
+ int dest_rowstride;
+ int width, height;
+ gboolean has_alpha;
+ const guchar *src_pixels;
+ guchar *dest_pixels;
+
+ pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (orig), gdk_pixbuf_get_has_alpha (orig),
+ gdk_pixbuf_get_bits_per_sample (orig),
+ gdk_pixbuf_get_width (orig), gdk_pixbuf_get_height (orig));
+
+ if (pixbuf == NULL)
+ return NULL;
+
+ orig_rowstride = gdk_pixbuf_get_rowstride (orig);
+ dest_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ has_alpha = gdk_pixbuf_get_has_alpha (orig);
+ src_pixels = gdk_pixbuf_get_pixels (orig);
+ dest_pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ for (y = 0; y < height; y++)
+ {
+ src = src_pixels + y * orig_rowstride;
+ dest = dest_pixels + y * dest_rowstride;
+
+ for (x = 0; x < width; x++)
+ {
+ double dr, dg, db;
+
+ intensity = INTENSITY (src[0], src[1], src[2]) / 255.0;
+
+ if (intensity <= 0.5)
+ {
+ /* Go from black at intensity = 0.0 to new_color at intensity = 0.5 */
+ dr = (new_color->red * intensity * 2.0) / 65535.0;
+ dg = (new_color->green * intensity * 2.0) / 65535.0;
+ db = (new_color->blue * intensity * 2.0) / 65535.0;
+ }
+ else
+ {
+ /* Go from new_color at intensity = 0.5 to white at intensity = 1.0 */
+ dr = (new_color->red + (65535 - new_color->red) * (intensity - 0.5) * 2.0) / 65535.0;
+ dg = (new_color->green + (65535 - new_color->green) * (intensity - 0.5) * 2.0) / 65535.0;
+ db = (new_color->blue + (65535 - new_color->blue) * (intensity - 0.5) * 2.0) / 65535.0;
+ }
+
+ dest[0] = CLAMP_UCHAR (255 * dr);
+ dest[1] = CLAMP_UCHAR (255 * dg);
+ dest[2] = CLAMP_UCHAR (255 * db);
+
+ if (has_alpha)
+ {
+ dest[3] = src[3];
+ src += 4;
+ dest += 4;
+ }
+ else
+ {
+ src += 3;
+ dest += 3;
+ }
+ }
+ }
+
+ return pixbuf;
+}
+
+static void
+color_composite (const GdkColor *bg,
+ const GdkColor *fg,
+ double alpha_d,
+ GdkColor *color)
+{
+ guint16 alpha;
+
+ *color = *bg;
+ alpha = alpha_d * 0xffff;
+ color->red = color->red + (((fg->red - color->red) * alpha + 0x8000) >> 16);
+ color->green = color->green + (((fg->green - color->green) * alpha + 0x8000) >> 16);
+ color->blue = color->blue + (((fg->blue - color->blue) * alpha + 0x8000) >> 16);
+}
+
+/**
+ * Sets all the fields of a border to dummy values.
+ *
+ * \param border The border whose fields should be reset.
+ */
+static void
+init_border (GtkBorder *border)
+{
+ border->top = -1;
+ border->bottom = -1;
+ border->left = -1;
+ border->right = -1;
+}
+
+/**
+ * Creates a new, empty MetaFrameLayout. The fields will be set to dummy
+ * values.
+ *
+ * \return The newly created MetaFrameLayout.
+ */
+MetaFrameLayout*
+meta_frame_layout_new (void)
+{
+ MetaFrameLayout *layout;
+
+ layout = g_new0 (MetaFrameLayout, 1);
+
+ layout->refcount = 1;
+
+ /* Fill with -1 values to detect invalid themes */
+ layout->left_width = -1;
+ layout->right_width = -1;
+ layout->bottom_height = -1;
+
+ init_border (&layout->title_border);
+
+ layout->title_vertical_pad = -1;
+
+ layout->right_titlebar_edge = -1;
+ layout->left_titlebar_edge = -1;
+
+ layout->button_sizing = META_BUTTON_SIZING_LAST;
+ layout->button_aspect = 1.0;
+ layout->button_width = -1;
+ layout->button_height = -1;
+
+ layout->has_title = TRUE;
+ layout->title_scale = 1.0;
+
+ init_border (&layout->button_border);
+
+ return layout;
+}
+
+/**
+ *
+ */
+static gboolean
+validate_border (const GtkBorder *border,
+ const char **bad)
+{
+ *bad = NULL;
+
+ if (border->top < 0)
+ *bad = _("top");
+ else if (border->bottom < 0)
+ *bad = _("bottom");
+ else if (border->left < 0)
+ *bad = _("left");
+ else if (border->right < 0)
+ *bad = _("right");
+
+ return *bad == NULL;
+}
+
+/**
+ * Ensures that the theme supplied a particular dimension. When a
+ * MetaFrameLayout is created, all its integer fields are set to -1
+ * by meta_frame_layout_new(). After an instance of this type
+ * should have been initialised, this function checks that
+ * a given field is not still at -1. It is never called directly, but
+ * rather via the CHECK_GEOMETRY_VALUE and CHECK_GEOMETRY_BORDER
+ * macros.
+ *
+ * \param val The value to check
+ * \param name The name to use in the error message
+ * \param[out] error Set to an error if val was not initialised
+ */
+static gboolean
+validate_geometry_value (int val,
+ const char *name,
+ GError **error)
+{
+ if (val < 0)
+ {
+ g_set_error (error, META_THEME_ERROR,
+ META_THEME_ERROR_FRAME_GEOMETRY,
+ _("frame geometry does not specify \"%s\" dimension"),
+ name);
+ return FALSE;
+ }
+ else
+ return TRUE;
+}
+
+static gboolean
+validate_geometry_border (const GtkBorder *border,
+ const char *name,
+ GError **error)
+{
+ const char *bad;
+
+ if (!validate_border (border, &bad))
+ {
+ g_set_error (error, META_THEME_ERROR,
+ META_THEME_ERROR_FRAME_GEOMETRY,
+ _("frame geometry does not specify dimension \"%s\" for border \"%s\""),
+ bad, name);
+ return FALSE;
+ }
+ else
+ return TRUE;
+}
+
+gboolean
+meta_frame_layout_validate (const MetaFrameLayout *layout,
+ GError **error)
+{
+ g_return_val_if_fail (layout != NULL, FALSE);
+
+#define CHECK_GEOMETRY_VALUE(vname) if (!validate_geometry_value (layout->vname, #vname, error)) return FALSE
+
+#define CHECK_GEOMETRY_BORDER(bname) if (!validate_geometry_border (&layout->bname, #bname, error)) return FALSE
+
+ CHECK_GEOMETRY_VALUE (left_width);
+ CHECK_GEOMETRY_VALUE (right_width);
+ CHECK_GEOMETRY_VALUE (bottom_height);
+
+ CHECK_GEOMETRY_BORDER (title_border);
+
+ CHECK_GEOMETRY_VALUE (title_vertical_pad);
+
+ CHECK_GEOMETRY_VALUE (right_titlebar_edge);
+ CHECK_GEOMETRY_VALUE (left_titlebar_edge);
+
+ switch (layout->button_sizing)
+ {
+ case META_BUTTON_SIZING_ASPECT:
+ if (layout->button_aspect < (0.1) ||
+ layout->button_aspect > (15.0))
+ {
+ g_set_error (error, META_THEME_ERROR,
+ META_THEME_ERROR_FRAME_GEOMETRY,
+ _("Button aspect ratio %g is not reasonable"),
+ layout->button_aspect);
+ return FALSE;
+ }
+ break;
+ case META_BUTTON_SIZING_FIXED:
+ CHECK_GEOMETRY_VALUE (button_width);
+ CHECK_GEOMETRY_VALUE (button_height);
+ break;
+ case META_BUTTON_SIZING_LAST:
+ g_set_error (error, META_THEME_ERROR,
+ META_THEME_ERROR_FRAME_GEOMETRY,
+ _("Frame geometry does not specify size of buttons"));
+ return FALSE;
+ }
+
+ CHECK_GEOMETRY_BORDER (button_border);
+
+ return TRUE;
+}
+
+MetaFrameLayout*
+meta_frame_layout_copy (const MetaFrameLayout *src)
+{
+ MetaFrameLayout *layout;
+
+ layout = g_new0 (MetaFrameLayout, 1);
+
+ *layout = *src;
+
+ layout->refcount = 1;
+
+ return layout;
+}
+
+void
+meta_frame_layout_ref (MetaFrameLayout *layout)
+{
+ g_return_if_fail (layout != NULL);
+
+ layout->refcount += 1;
+}
+
+void
+meta_frame_layout_unref (MetaFrameLayout *layout)
+{
+ g_return_if_fail (layout != NULL);
+ g_return_if_fail (layout->refcount > 0);
+
+ layout->refcount -= 1;
+
+ if (layout->refcount == 0)
+ {
+ DEBUG_FILL_STRUCT (layout);
+ g_free (layout);
+ }
+}
+
+void
+meta_frame_layout_get_borders (const MetaFrameLayout *layout,
+ int text_height,
+ MetaFrameFlags flags,
+ int *top_height,
+ int *bottom_height,
+ int *left_width,
+ int *right_width)
+{
+ int buttons_height, title_height;
+
+ g_return_if_fail (top_height != NULL);
+ g_return_if_fail (bottom_height != NULL);
+ g_return_if_fail (left_width != NULL);
+ g_return_if_fail (right_width != NULL);
+
+ if (!layout->has_title)
+ text_height = 0;
+
+ buttons_height = layout->button_height +
+ layout->button_border.top + layout->button_border.bottom;
+ title_height = text_height +
+ layout->title_vertical_pad +
+ layout->title_border.top + layout->title_border.bottom;
+
+ if (top_height)
+ {
+ *top_height = MAX (buttons_height, title_height);
+ }
+
+ if (left_width)
+ *left_width = layout->left_width;
+ if (right_width)
+ *right_width = layout->right_width;
+
+ if (bottom_height)
+ {
+ if (flags & META_FRAME_SHADED)
+ *bottom_height = 0;
+ else
+ *bottom_height = layout->bottom_height;
+ }
+
+ if (flags & META_FRAME_FULLSCREEN)
+ {
+ if (top_height)
+ *top_height = 0;
+ if (bottom_height)
+ *bottom_height = 0;
+ if (left_width)
+ *left_width = 0;
+ if (right_width)
+ *right_width = 0;
+ }
+}
+
+static MetaButtonSpace*
+rect_for_function (MetaFrameGeometry *fgeom,
+ MetaFrameFlags flags,
+ MetaButtonFunction function,
+ MetaTheme *theme)
+{
+
+ /* Firstly, check version-specific things. */
+
+ if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS))
+ {
+ switch (function)
+ {
+ case META_BUTTON_FUNCTION_SHADE:
+ if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED))
+ return &fgeom->shade_rect;
+ else
+ return NULL;
+ case META_BUTTON_FUNCTION_ABOVE:
+ if (!(flags & META_FRAME_ABOVE))
+ return &fgeom->above_rect;
+ else
+ return NULL;
+ case META_BUTTON_FUNCTION_STICK:
+ if (!(flags & META_FRAME_STUCK))
+ return &fgeom->stick_rect;
+ else
+ return NULL;
+ case META_BUTTON_FUNCTION_UNSHADE:
+ if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED))
+ return &fgeom->unshade_rect;
+ else
+ return NULL;
+ case META_BUTTON_FUNCTION_UNABOVE:
+ if (flags & META_FRAME_ABOVE)
+ return &fgeom->unabove_rect;
+ else
+ return NULL;
+ case META_BUTTON_FUNCTION_UNSTICK:
+ if (flags & META_FRAME_STUCK)
+ return &fgeom->unstick_rect;
+ default:
+ /* just go on to the next switch block */;
+ }
+ }
+
+ /* now consider the buttons which exist in all versions */
+
+ switch (function)
+ {
+ case META_BUTTON_FUNCTION_MENU:
+ if (flags & META_FRAME_ALLOWS_MENU)
+ return &fgeom->menu_rect;
+ else
+ return NULL;
+ case META_BUTTON_FUNCTION_MINIMIZE:
+ if (flags & META_FRAME_ALLOWS_MINIMIZE)
+ return &fgeom->min_rect;
+ else
+ return NULL;
+ case META_BUTTON_FUNCTION_MAXIMIZE:
+ if (flags & META_FRAME_ALLOWS_MAXIMIZE)
+ return &fgeom->max_rect;
+ else
+ return NULL;
+ case META_BUTTON_FUNCTION_CLOSE:
+ if (flags & META_FRAME_ALLOWS_DELETE)
+ return &fgeom->close_rect;
+ else
+ return NULL;
+ case META_BUTTON_FUNCTION_STICK:
+ case META_BUTTON_FUNCTION_SHADE:
+ case META_BUTTON_FUNCTION_ABOVE:
+ case META_BUTTON_FUNCTION_UNSTICK:
+ case META_BUTTON_FUNCTION_UNSHADE:
+ case META_BUTTON_FUNCTION_UNABOVE:
+ /* we are being asked for a >v1 button which hasn't been handled yet,
+ * so obviously we're not in a theme which supports that version.
+ * therefore, we don't show the button. return NULL and all will
+ * be well.
+ */
+ return NULL;
+
+ case META_BUTTON_FUNCTION_LAST:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+static gboolean
+strip_button (MetaButtonSpace *func_rects[MAX_BUTTONS_PER_CORNER],
+ GdkRectangle *bg_rects[MAX_BUTTONS_PER_CORNER],
+ int *n_rects,
+ MetaButtonSpace *to_strip)
+{
+ int i;
+
+ i = 0;
+ while (i < *n_rects)
+ {
+ if (func_rects[i] == to_strip)
+ {
+ *n_rects -= 1;
+
+ /* shift the other rects back in the array */
+ while (i < *n_rects)
+ {
+ func_rects[i] = func_rects[i+1];
+ bg_rects[i] = bg_rects[i+1];
+
+ ++i;
+ }
+
+ func_rects[i] = NULL;
+ bg_rects[i] = NULL;
+
+ return TRUE;
+ }
+
+ ++i;
+ }
+
+ return FALSE; /* did not strip anything */
+}
+
+void
+meta_frame_layout_calc_geometry (const MetaFrameLayout *layout,
+ int text_height,
+ MetaFrameFlags flags,
+ int client_width,
+ int client_height,
+ const MetaButtonLayout *button_layout,
+ MetaFrameGeometry *fgeom,
+ MetaTheme *theme)
+{
+ int i, n_left, n_right, n_left_spacers, n_right_spacers;
+ int x;
+ int button_y;
+ int title_right_edge;
+ int width, height;
+ int button_width, button_height;
+ int min_size_for_rounding;
+
+ /* the left/right rects in order; the max # of rects
+ * is the number of button functions
+ */
+ MetaButtonSpace *left_func_rects[MAX_BUTTONS_PER_CORNER];
+ MetaButtonSpace *right_func_rects[MAX_BUTTONS_PER_CORNER];
+ GdkRectangle *left_bg_rects[MAX_BUTTONS_PER_CORNER];
+ gboolean left_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
+ GdkRectangle *right_bg_rects[MAX_BUTTONS_PER_CORNER];
+ gboolean right_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
+
+ meta_frame_layout_get_borders (layout, text_height,
+ flags,
+ &fgeom->top_height,
+ &fgeom->bottom_height,
+ &fgeom->left_width,
+ &fgeom->right_width);
+
+ width = client_width + fgeom->left_width + fgeom->right_width;
+
+ height = ((flags & META_FRAME_SHADED) ? 0: client_height) +
+ fgeom->top_height + fgeom->bottom_height;
+
+ fgeom->width = width;
+ fgeom->height = height;
+
+ fgeom->top_titlebar_edge = layout->title_border.top;
+ fgeom->bottom_titlebar_edge = layout->title_border.bottom;
+ fgeom->left_titlebar_edge = layout->left_titlebar_edge;
+ fgeom->right_titlebar_edge = layout->right_titlebar_edge;
+
+ /* gcc warnings */
+ button_width = -1;
+ button_height = -1;
+
+ switch (layout->button_sizing)
+ {
+ case META_BUTTON_SIZING_ASPECT:
+ button_height = fgeom->top_height - layout->button_border.top - layout->button_border.bottom;
+ button_width = button_height / layout->button_aspect;
+ break;
+ case META_BUTTON_SIZING_FIXED:
+ button_width = layout->button_width;
+ button_height = layout->button_height;
+ break;
+ case META_BUTTON_SIZING_LAST:
+ g_assert_not_reached ();
+ break;
+ }
+
+ /* FIXME all this code sort of pretends that duplicate buttons
+ * with the same function are allowed, but that breaks the
+ * code in frames.c, so isn't really allowed right now.
+ * Would need left_close_rect, right_close_rect, etc.
+ */
+
+ /* Init all button rects to 0, lame hack */
+ memset (ADDRESS_OF_BUTTON_RECTS (fgeom), '\0',
+ LENGTH_OF_BUTTON_RECTS);
+
+ n_left = 0;
+ n_right = 0;
+ n_left_spacers = 0;
+ n_right_spacers = 0;
+
+ if (!layout->hide_buttons)
+ {
+ /* Try to fill in rects */
+ for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->left_buttons[i] != META_BUTTON_FUNCTION_LAST; i++)
+ {
+ left_func_rects[n_left] = rect_for_function (fgeom, flags,
+ button_layout->left_buttons[i],
+ theme);
+ if (left_func_rects[n_left] != NULL)
+ {
+ left_buttons_has_spacer[n_left] = button_layout->left_buttons_has_spacer[i];
+ if (button_layout->left_buttons_has_spacer[i])
+ ++n_left_spacers;
+
+ ++n_left;
+ }
+ }
+
+ for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->right_buttons[i] != META_BUTTON_FUNCTION_LAST; i++)
+ {
+ right_func_rects[n_right] = rect_for_function (fgeom, flags,
+ button_layout->right_buttons[i],
+ theme);
+ if (right_func_rects[n_right] != NULL)
+ {
+ right_buttons_has_spacer[n_right] = button_layout->right_buttons_has_spacer[i];
+ if (button_layout->right_buttons_has_spacer[i])
+ ++n_right_spacers;
+
+ ++n_right;
+ }
+ }
+ }
+
+ for (i = 0; i < MAX_BUTTONS_PER_CORNER; i++)
+ {
+ left_bg_rects[i] = NULL;
+ right_bg_rects[i] = NULL;
+ }
+
+ for (i = 0; i < n_left; i++)
+ {
+ if (i == 0) /* prefer left background if only one button */
+ left_bg_rects[i] = &fgeom->left_left_background;
+ else if (i == (n_left - 1))
+ left_bg_rects[i] = &fgeom->left_right_background;
+ else
+ left_bg_rects[i] = &fgeom->left_middle_backgrounds[i - 1];
+ }
+
+ for (i = 0; i < n_right; i++)
+ {
+ /* prefer right background if only one button */
+ if (i == (n_right - 1))
+ right_bg_rects[i] = &fgeom->right_right_background;
+ else if (i == 0)
+ right_bg_rects[i] = &fgeom->right_left_background;
+ else
+ right_bg_rects[i] = &fgeom->right_middle_backgrounds[i - 1];
+ }
+
+ /* Be sure buttons fit */
+ while (n_left > 0 || n_right > 0)
+ {
+ int space_used_by_buttons;
+ int space_available;
+
+ space_available = fgeom->width - layout->left_titlebar_edge - layout->right_titlebar_edge;
+
+ space_used_by_buttons = 0;
+
+ space_used_by_buttons += button_width * n_left;
+ space_used_by_buttons += (button_width * 0.75) * n_left_spacers;
+ space_used_by_buttons += layout->button_border.left * n_left;
+ space_used_by_buttons += layout->button_border.right * n_left;
+
+ space_used_by_buttons += button_width * n_right;
+ space_used_by_buttons += (button_width * 0.75) * n_right_spacers;
+ space_used_by_buttons += layout->button_border.left * n_right;
+ space_used_by_buttons += layout->button_border.right * n_right;
+
+ if (space_used_by_buttons <= space_available)
+ break; /* Everything fits, bail out */
+
+ /* First try to remove separators */
+ if (n_left_spacers > 0)
+ {
+ left_buttons_has_spacer[--n_left_spacers] = FALSE;
+ continue;
+ }
+ else if (n_right_spacers > 0)
+ {
+ right_buttons_has_spacer[--n_right_spacers] = FALSE;
+ continue;
+ }
+
+ /* Otherwise we need to shave out a button. Shave
+ * above, stick, shade, min, max, close, then menu (menu is most useful);
+ * prefer the default button locations.
+ */
+ if (strip_button (left_func_rects, left_bg_rects,
+ &n_left, &fgeom->above_rect))
+ continue;
+ else if (strip_button (right_func_rects, right_bg_rects,
+ &n_right, &fgeom->above_rect))
+ continue;
+ else if (strip_button (left_func_rects, left_bg_rects,
+ &n_left, &fgeom->stick_rect))
+ continue;
+ else if (strip_button (right_func_rects, right_bg_rects,
+ &n_right, &fgeom->stick_rect))
+ continue;
+ else if (strip_button (left_func_rects, left_bg_rects,
+ &n_left, &fgeom->shade_rect))
+ continue;
+ else if (strip_button (right_func_rects, right_bg_rects,
+ &n_right, &fgeom->shade_rect))
+ continue;
+ else if (strip_button (left_func_rects, left_bg_rects,
+ &n_left, &fgeom->min_rect))
+ continue;
+ else if (strip_button (right_func_rects, right_bg_rects,
+ &n_right, &fgeom->min_rect))
+ continue;
+ else if (strip_button (left_func_rects, left_bg_rects,
+ &n_left, &fgeom->max_rect))
+ continue;
+ else if (strip_button (right_func_rects, right_bg_rects,
+ &n_right, &fgeom->max_rect))
+ continue;
+ else if (strip_button (left_func_rects, left_bg_rects,
+ &n_left, &fgeom->close_rect))
+ continue;
+ else if (strip_button (right_func_rects, right_bg_rects,
+ &n_right, &fgeom->close_rect))
+ continue;
+ else if (strip_button (right_func_rects, right_bg_rects,
+ &n_right, &fgeom->menu_rect))
+ continue;
+ else if (strip_button (left_func_rects, left_bg_rects,
+ &n_left, &fgeom->menu_rect))
+ continue;
+ else
+ {
+ meta_bug ("Could not find a button to strip. n_left = %d n_right = %d\n",
+ n_left, n_right);
+ }
+ }
+
+ /* center buttons vertically */
+ button_y = (fgeom->top_height -
+ (button_height + layout->button_border.top + layout->button_border.bottom)) / 2 + layout->button_border.top;
+
+ /* right edge of farthest-right button */
+ x = width - layout->right_titlebar_edge;
+
+ i = n_right - 1;
+ while (i >= 0)
+ {
+ MetaButtonSpace *rect;
+
+ if (x < 0) /* if we go negative, leave the buttons we don't get to as 0-width */
+ break;
+
+ rect = right_func_rects[i];
+ rect->visible.x = x - layout->button_border.right - button_width;
+ if (right_buttons_has_spacer[i])
+ rect->visible.x -= (button_width * 0.75);
+
+ rect->visible.y = button_y;
+ rect->visible.width = button_width;
+ rect->visible.height = button_height;
+
+ if (flags & META_FRAME_MAXIMIZED)
+ {
+ rect->clickable.x = rect->visible.x;
+ rect->clickable.y = 0;
+ rect->clickable.width = rect->visible.width;
+ rect->clickable.height = button_height + button_y;
+
+ if (i == n_right - 1)
+ rect->clickable.width += layout->right_titlebar_edge + layout->right_width + layout->button_border.right;
+
+ }
+ else
+ g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable));
+
+ *(right_bg_rects[i]) = rect->visible;
+
+ x = rect->visible.x - layout->button_border.left;
+
+ --i;
+ }
+
+ /* save right edge of titlebar for later use */
+ title_right_edge = x - layout->title_border.right;
+
+ /* Now x changes to be position from the left and we go through
+ * the left-side buttons
+ */
+ x = layout->left_titlebar_edge;
+ for (i = 0; i < n_left; i++)
+ {
+ MetaButtonSpace *rect;
+
+ rect = left_func_rects[i];
+
+ rect->visible.x = x + layout->button_border.left;
+ rect->visible.y = button_y;
+ rect->visible.width = button_width;
+ rect->visible.height = button_height;
+
+ if (flags & META_FRAME_MAXIMIZED)
+ {
+ if (i==0)
+ {
+ rect->clickable.x = 0;
+ rect->clickable.width = button_width + x;
+ }
+ else
+ {
+ rect->clickable.x = rect->visible.x;
+ rect->clickable.width = button_width;
+ }
+
+ rect->clickable.y = 0;
+ rect->clickable.height = button_height + button_y;
+ }
+ else
+ g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable));
+
+
+ x = rect->visible.x + rect->visible.width + layout->button_border.right;
+ if (left_buttons_has_spacer[i])
+ x += (button_width * 0.75);
+
+ *(left_bg_rects[i]) = rect->visible;
+ }
+
+ /* We always fill as much vertical space as possible with title rect,
+ * rather than centering it like the buttons
+ */
+ fgeom->title_rect.x = x + layout->title_border.left;
+ fgeom->title_rect.y = layout->title_border.top;
+ fgeom->title_rect.width = title_right_edge - fgeom->title_rect.x;
+ fgeom->title_rect.height = fgeom->top_height - layout->title_border.top - layout->title_border.bottom;
+
+ /* Nuke title if it won't fit */
+ if (fgeom->title_rect.width < 0 ||
+ fgeom->title_rect.height < 0)
+ {
+ fgeom->title_rect.width = 0;
+ fgeom->title_rect.height = 0;
+ }
+
+ if (flags & META_FRAME_SHADED)
+ min_size_for_rounding = 0;
+ else
+ min_size_for_rounding = 5;
+
+ fgeom->top_left_corner_rounded_radius = 0;
+ fgeom->top_right_corner_rounded_radius = 0;
+ fgeom->bottom_left_corner_rounded_radius = 0;
+ fgeom->bottom_right_corner_rounded_radius = 0;
+
+ if (fgeom->top_height + fgeom->left_width >= min_size_for_rounding)
+ fgeom->top_left_corner_rounded_radius = layout->top_left_corner_rounded_radius;
+ if (fgeom->top_height + fgeom->right_width >= min_size_for_rounding)
+ fgeom->top_right_corner_rounded_radius = layout->top_right_corner_rounded_radius;
+
+ if (fgeom->bottom_height + fgeom->left_width >= min_size_for_rounding)
+ fgeom->bottom_left_corner_rounded_radius = layout->bottom_left_corner_rounded_radius;
+ if (fgeom->bottom_height + fgeom->right_width >= min_size_for_rounding)
+ fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius;
+}
+
+MetaGradientSpec*
+meta_gradient_spec_new (MetaGradientType type)
+{
+ MetaGradientSpec *spec;
+
+ spec = g_new (MetaGradientSpec, 1);
+
+ spec->type = type;
+ spec->color_specs = NULL;
+
+ return spec;
+}
+
+static void
+free_color_spec (gpointer spec, gpointer user_data)
+{
+ meta_color_spec_free (spec);
+}
+
+void
+meta_gradient_spec_free (MetaGradientSpec *spec)
+{
+ g_return_if_fail (spec != NULL);
+
+ g_slist_foreach (spec->color_specs, free_color_spec, NULL);
+ g_slist_free (spec->color_specs);
+
+ DEBUG_FILL_STRUCT (spec);
+ g_free (spec);
+}
+
+GdkPixbuf*
+meta_gradient_spec_render (const MetaGradientSpec *spec,
+ GtkWidget *widget,
+ int width,
+ int height)
+{
+ int n_colors;
+ GdkColor *colors;
+ GSList *tmp;
+ int i;
+ GdkPixbuf *pixbuf;
+
+ n_colors = g_slist_length (spec->color_specs);
+
+ if (n_colors == 0)
+ return NULL;
+
+ colors = g_new (GdkColor, n_colors);
+
+ i = 0;
+ tmp = spec->color_specs;
+ while (tmp != NULL)
+ {
+ meta_color_spec_render (tmp->data, widget, &colors[i]);
+
+ tmp = tmp->next;
+ ++i;
+ }
+
+ pixbuf = meta_gradient_create_multi (width, height,
+ colors, n_colors,
+ spec->type);
+
+ g_free (colors);
+
+ return pixbuf;
+}
+
+gboolean
+meta_gradient_spec_validate (MetaGradientSpec *spec,
+ GError **error)
+{
+ g_return_val_if_fail (spec != NULL, FALSE);
+
+ if (g_slist_length (spec->color_specs) < 2)
+ {
+ g_set_error (error, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Gradients should have at least two colors"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+MetaAlphaGradientSpec*
+meta_alpha_gradient_spec_new (MetaGradientType type,
+ int n_alphas)
+{
+ MetaAlphaGradientSpec *spec;
+
+ g_return_val_if_fail (n_alphas > 0, NULL);
+
+ spec = g_new0 (MetaAlphaGradientSpec, 1);
+
+ spec->type = type;
+ spec->alphas = g_new0 (unsigned char, n_alphas);
+ spec->n_alphas = n_alphas;
+
+ return spec;
+}
+
+void
+meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec)
+{
+ g_return_if_fail (spec != NULL);
+
+ g_free (spec->alphas);
+ g_free (spec);
+}
+
+MetaColorSpec*
+meta_color_spec_new (MetaColorSpecType type)
+{
+ MetaColorSpec *spec;
+ MetaColorSpec dummy;
+ int size;
+
+ size = G_STRUCT_OFFSET (MetaColorSpec, data);
+
+ switch (type)
+ {
+ case META_COLOR_SPEC_BASIC:
+ size += sizeof (dummy.data.basic);
+ break;
+
+ case META_COLOR_SPEC_GTK:
+ size += sizeof (dummy.data.gtk);
+ break;
+
+ case META_COLOR_SPEC_BLEND:
+ size += sizeof (dummy.data.blend);
+ break;
+
+ case META_COLOR_SPEC_SHADE:
+ size += sizeof (dummy.data.shade);
+ break;
+ }
+
+ spec = g_malloc0 (size);
+
+ spec->type = type;
+
+ return spec;
+}
+
+void
+meta_color_spec_free (MetaColorSpec *spec)
+{
+ g_return_if_fail (spec != NULL);
+
+ switch (spec->type)
+ {
+ case META_COLOR_SPEC_BASIC:
+ DEBUG_FILL_STRUCT (&spec->data.basic);
+ break;
+
+ case META_COLOR_SPEC_GTK:
+ DEBUG_FILL_STRUCT (&spec->data.gtk);
+ break;
+
+ case META_COLOR_SPEC_BLEND:
+ if (spec->data.blend.foreground)
+ meta_color_spec_free (spec->data.blend.foreground);
+ if (spec->data.blend.background)
+ meta_color_spec_free (spec->data.blend.background);
+ DEBUG_FILL_STRUCT (&spec->data.blend);
+ break;
+
+ case META_COLOR_SPEC_SHADE:
+ if (spec->data.shade.base)
+ meta_color_spec_free (spec->data.shade.base);
+ DEBUG_FILL_STRUCT (&spec->data.shade);
+ break;
+ }
+
+ g_free (spec);
+}
+
+MetaColorSpec*
+meta_color_spec_new_from_string (const char *str,
+ GError **err)
+{
+ MetaColorSpec *spec;
+
+ spec = NULL;
+
+ if (str[0] == 'g' && str[1] == 't' && str[2] == 'k' && str[3] == ':')
+ {
+ /* GTK color */
+ const char *bracket;
+ const char *end_bracket;
+ char *tmp;
+ GtkStateType state;
+ MetaGtkColorComponent component;
+
+ bracket = str;
+ while (*bracket && *bracket != '[')
+ ++bracket;
+
+ if (*bracket == '\0')
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("GTK color specification must have the state in brackets, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""),
+ str);
+ return NULL;
+ }
+
+ end_bracket = bracket;
+ ++end_bracket;
+ while (*end_bracket && *end_bracket != ']')
+ ++end_bracket;
+
+ if (*end_bracket == '\0')
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("GTK color specification must have a close bracket after the state, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""),
+ str);
+ return NULL;
+ }
+
+ tmp = g_strndup (bracket + 1, end_bracket - bracket - 1);
+ state = meta_gtk_state_from_string (tmp);
+ if (((int) state) == -1)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Did not understand state \"%s\" in color specification"),
+ tmp);
+ g_free (tmp);
+ return NULL;
+ }
+ g_free (tmp);
+
+ tmp = g_strndup (str + 4, bracket - str - 4);
+ component = meta_color_component_from_string (tmp);
+ if (component == META_GTK_COLOR_LAST)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Did not understand color component \"%s\" in color specification"),
+ tmp);
+ g_free (tmp);
+ return NULL;
+ }
+ g_free (tmp);
+
+ spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
+ spec->data.gtk.state = state;
+ spec->data.gtk.component = component;
+ g_assert (spec->data.gtk.state < N_GTK_STATES);
+ g_assert (spec->data.gtk.component < META_GTK_COLOR_LAST);
+ }
+ else if (str[0] == 'b' && str[1] == 'l' && str[2] == 'e' && str[3] == 'n' &&
+ str[4] == 'd' && str[5] == '/')
+ {
+ /* blend */
+ char **split;
+ double alpha;
+ char *end;
+ MetaColorSpec *fg;
+ MetaColorSpec *bg;
+
+ split = g_strsplit (str, "/", 4);
+
+ if (split[0] == NULL || split[1] == NULL ||
+ split[2] == NULL || split[3] == NULL)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Blend format is \"blend/bg_color/fg_color/alpha\", \"%s\" does not fit the format"),
+ str);
+ g_strfreev (split);
+ return NULL;
+ }
+
+ alpha = g_ascii_strtod (split[3], &end);
+ if (end == split[3])
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Could not parse alpha value \"%s\" in blended color"),
+ split[3]);
+ g_strfreev (split);
+ return NULL;
+ }
+
+ if (alpha < (0.0 - 1e6) || alpha > (1.0 + 1e6))
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Alpha value \"%s\" in blended color is not between 0.0 and 1.0"),
+ split[3]);
+ g_strfreev (split);
+ return NULL;
+ }
+
+ fg = NULL;
+ bg = NULL;
+
+ bg = meta_color_spec_new_from_string (split[1], err);
+ if (bg == NULL)
+ {
+ g_strfreev (split);
+ return NULL;
+ }
+
+ fg = meta_color_spec_new_from_string (split[2], err);
+ if (fg == NULL)
+ {
+ meta_color_spec_free (bg);
+ g_strfreev (split);
+ return NULL;
+ }
+
+ g_strfreev (split);
+
+ spec = meta_color_spec_new (META_COLOR_SPEC_BLEND);
+ spec->data.blend.alpha = alpha;
+ spec->data.blend.background = bg;
+ spec->data.blend.foreground = fg;
+ }
+ else if (str[0] == 's' && str[1] == 'h' && str[2] == 'a' && str[3] == 'd' &&
+ str[4] == 'e' && str[5] == '/')
+ {
+ /* shade */
+ char **split;
+ double factor;
+ char *end;
+ MetaColorSpec *base;
+
+ split = g_strsplit (str, "/", 3);
+
+ if (split[0] == NULL || split[1] == NULL ||
+ split[2] == NULL)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Shade format is \"shade/base_color/factor\", \"%s\" does not fit the format"),
+ str);
+ g_strfreev (split);
+ return NULL;
+ }
+
+ factor = g_ascii_strtod (split[2], &end);
+ if (end == split[2])
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Could not parse shade factor \"%s\" in shaded color"),
+ split[2]);
+ g_strfreev (split);
+ return NULL;
+ }
+
+ if (factor < (0.0 - 1e6))
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Shade factor \"%s\" in shaded color is negative"),
+ split[2]);
+ g_strfreev (split);
+ return NULL;
+ }
+
+ base = NULL;
+
+ base = meta_color_spec_new_from_string (split[1], err);
+ if (base == NULL)
+ {
+ g_strfreev (split);
+ return NULL;
+ }
+
+ g_strfreev (split);
+
+ spec = meta_color_spec_new (META_COLOR_SPEC_SHADE);
+ spec->data.shade.factor = factor;
+ spec->data.shade.base = base;
+ }
+ else
+ {
+ spec = meta_color_spec_new (META_COLOR_SPEC_BASIC);
+
+ if (!gdk_color_parse (str, &spec->data.basic.color))
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Could not parse color \"%s\""),
+ str);
+ meta_color_spec_free (spec);
+ return NULL;
+ }
+ }
+
+ g_assert (spec);
+
+ return spec;
+}
+
+MetaColorSpec*
+meta_color_spec_new_gtk (MetaGtkColorComponent component,
+ GtkStateType state)
+{
+ MetaColorSpec *spec;
+
+ spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
+
+ spec->data.gtk.component = component;
+ spec->data.gtk.state = state;
+
+ return spec;
+}
+
+void
+meta_color_spec_render (MetaColorSpec *spec,
+ GtkWidget *widget,
+ GdkColor *color)
+{
+ GtkStyle *widget_style;
+
+ g_return_if_fail (spec != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ widget_style = gtk_widget_get_style (widget);
+ g_return_if_fail (widget_style != NULL);
+
+ switch (spec->type)
+ {
+ case META_COLOR_SPEC_BASIC:
+ *color = spec->data.basic.color;
+ break;
+
+ case META_COLOR_SPEC_GTK:
+ switch (spec->data.gtk.component)
+ {
+ case META_GTK_COLOR_BG:
+ *color = widget_style->bg[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_FG:
+ *color = widget_style->fg[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_BASE:
+ *color = widget_style->base[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_TEXT:
+ *color = widget_style->text[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_LIGHT:
+ *color = widget_style->light[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_DARK:
+ *color = widget_style->dark[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_MID:
+ *color = widget_style->mid[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_TEXT_AA:
+ *color = widget_style->text_aa[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_LAST:
+ g_assert_not_reached ();
+ break;
+ }
+ break;
+
+ case META_COLOR_SPEC_BLEND:
+ {
+ GdkColor bg, fg;
+
+ meta_color_spec_render (spec->data.blend.background, widget, &bg);
+ meta_color_spec_render (spec->data.blend.foreground, widget, &fg);
+
+ color_composite (&bg, &fg, spec->data.blend.alpha,
+ &spec->data.blend.color);
+
+ *color = spec->data.blend.color;
+ }
+ break;
+
+ case META_COLOR_SPEC_SHADE:
+ {
+ meta_color_spec_render (spec->data.shade.base, widget,
+ &spec->data.shade.color);
+
+ gtk_style_shade (&spec->data.shade.color,
+ &spec->data.shade.color, spec->data.shade.factor);
+
+ *color = spec->data.shade.color;
+ }
+ break;
+ }
+}
+
+/**
+ * Represents an operation as a string.
+ *
+ * \param type an operation, such as addition
+ * \return a string, such as "+"
+ */
+static const char*
+op_name (PosOperatorType type)
+{
+ switch (type)
+ {
+ case POS_OP_ADD:
+ return "+";
+ case POS_OP_SUBTRACT:
+ return "-";
+ case POS_OP_MULTIPLY:
+ return "*";
+ case POS_OP_DIVIDE:
+ return "/";
+ case POS_OP_MOD:
+ return "%";
+ case POS_OP_MAX:
+ return "`max`";
+ case POS_OP_MIN:
+ return "`min`";
+ case POS_OP_NONE:
+ break;
+ }
+
+ return "<unknown>";
+}
+
+/**
+ * Parses a string and returns an operation.
+ *
+ * \param p a pointer into a string representing an operation; part of an
+ * expression somewhere, so not null-terminated
+ * \param len set to the length of the string found. Set to 0 if none is.
+ * \return the operation found. If none was, returns POS_OP_NONE.
+ */
+static PosOperatorType
+op_from_string (const char *p,
+ int *len)
+{
+ *len = 0;
+
+ switch (*p)
+ {
+ case '+':
+ *len = 1;
+ return POS_OP_ADD;
+ case '-':
+ *len = 1;
+ return POS_OP_SUBTRACT;
+ case '*':
+ *len = 1;
+ return POS_OP_MULTIPLY;
+ case '/':
+ *len = 1;
+ return POS_OP_DIVIDE;
+ case '%':
+ *len = 1;
+ return POS_OP_MOD;
+
+ case '`':
+ if (p[0] == '`' &&
+ p[1] == 'm' &&
+ p[2] == 'a' &&
+ p[3] == 'x' &&
+ p[4] == '`')
+ {
+ *len = 5;
+ return POS_OP_MAX;
+ }
+ else if (p[0] == '`' &&
+ p[1] == 'm' &&
+ p[2] == 'i' &&
+ p[3] == 'n' &&
+ p[4] == '`')
+ {
+ *len = 5;
+ return POS_OP_MIN;
+ }
+ }
+
+ return POS_OP_NONE;
+}
+
+/**
+ * Frees an array of tokens. All the tokens and their associated memory
+ * will be freed.
+ *
+ * \param tokens an array of tokens to be freed
+ * \param n_tokens how many tokens are in the array.
+ */
+static void
+free_tokens (PosToken *tokens,
+ int n_tokens)
+{
+ int i;
+
+ /* n_tokens can be 0 since tokens may have been allocated more than
+ * it was initialized
+ */
+
+ for (i = 0; i < n_tokens; i++)
+ if (tokens[i].type == POS_TOKEN_VARIABLE)
+ g_free (tokens[i].d.v.name);
+
+ g_free (tokens);
+}
+
+/**
+ * Tokenises a number in an expression.
+ *
+ * \param p a pointer into a string representing an operation; part of an
+ * expression somewhere, so not null-terminated
+ * \param end_return set to a pointer to the end of the number found; but
+ * not updated if no number was found at all
+ * \param next set to either an integer or a float token
+ * \param[out] err set to the problem if there was a problem
+ * \return TRUE if a valid number was found, FALSE otherwise (and "err" will
+ * have been set)
+ *
+ * \bug The "while (*start)..." part: what's wrong with strchr-ish things?
+ * \bug The name is wrong: it doesn't parse anything.
+ * \ingroup tokenizer
+ */
+static gboolean
+parse_number (const char *p,
+ const char **end_return,
+ PosToken *next,
+ GError **err)
+{
+ const char *start = p;
+ char *end;
+ gboolean is_float;
+ char *num_str;
+
+ while (*p && (*p == '.' || g_ascii_isdigit (*p)))
+ ++p;
+
+ if (p == start)
+ {
+ char buf[7] = { '\0' };
+ buf[g_unichar_to_utf8 (g_utf8_get_char (p), buf)] = '\0';
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_BAD_CHARACTER,
+ _("Coordinate expression contains character '%s' which is not allowed"),
+ buf);
+ return FALSE;
+ }
+
+ *end_return = p;
+
+ /* we need this to exclude floats like "1e6" */
+ num_str = g_strndup (start, p - start);
+ start = num_str;
+ is_float = FALSE;
+ while (*start)
+ {
+ if (*start == '.')
+ is_float = TRUE;
+ ++start;
+ }
+
+ if (is_float)
+ {
+ next->type = POS_TOKEN_DOUBLE;
+ next->d.d.val = g_ascii_strtod (num_str, &end);
+
+ if (end == num_str)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Coordinate expression contains floating point number '%s' which could not be parsed"),
+ num_str);
+ g_free (num_str);
+ return FALSE;
+ }
+ }
+ else
+ {
+ next->type = POS_TOKEN_INT;
+ next->d.i.val = strtol (num_str, &end, 10);
+ if (end == num_str)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Coordinate expression contains integer '%s' which could not be parsed"),
+ num_str);
+ g_free (num_str);
+ return FALSE;
+ }
+ }
+
+ g_free (num_str);
+
+ g_assert (next->type == POS_TOKEN_INT || next->type == POS_TOKEN_DOUBLE);
+
+ return TRUE;
+}
+
+/**
+ * Whether a variable can validly appear as part of the name of a variable.
+ */
+#define IS_VARIABLE_CHAR(c) (g_ascii_isalpha ((c)) || (c) == '_')
+
+#if 0
+static void
+debug_print_tokens (PosToken *tokens,
+ int n_tokens)
+{
+ int i;
+
+ for (i = 0; i < n_tokens; i++)
+ {
+ PosToken *t = &tokens[i];
+
+ g_print (" ");
+
+ switch (t->type)
+ {
+ case POS_TOKEN_INT:
+ g_print ("\"%d\"", t->d.i.val);
+ break;
+ case POS_TOKEN_DOUBLE:
+ g_print ("\"%g\"", t->d.d.val);
+ break;
+ case POS_TOKEN_OPEN_PAREN:
+ g_print ("\"(\"");
+ break;
+ case POS_TOKEN_CLOSE_PAREN:
+ g_print ("\")\"");
+ break;
+ case POS_TOKEN_VARIABLE:
+ g_print ("\"%s\"", t->d.v.name);
+ break;
+ case POS_TOKEN_OPERATOR:
+ g_print ("\"%s\"", op_name (t->d.o.op));
+ break;
+ }
+ }
+
+ g_print ("\n");
+}
+#endif
+
+/**
+ * Tokenises an expression.
+ *
+ * \param expr The expression
+ * \param[out] tokens_p The resulting tokens
+ * \param[out] n_tokens_p The number of resulting tokens
+ * \param[out] err set to the problem if there was a problem
+ *
+ * \return True if the expression was successfully tokenised; false otherwise.
+ *
+ * \ingroup tokenizer
+ */
+static gboolean
+pos_tokenize (const char *expr,
+ PosToken **tokens_p,
+ int *n_tokens_p,
+ GError **err)
+{
+ PosToken *tokens;
+ int n_tokens;
+ int allocated;
+ const char *p;
+
+ *tokens_p = NULL;
+ *n_tokens_p = 0;
+
+ allocated = 3;
+ n_tokens = 0;
+ tokens = g_new (PosToken, allocated);
+
+ p = expr;
+ while (*p)
+ {
+ PosToken *next;
+ int len;
+
+ if (n_tokens == allocated)
+ {
+ allocated *= 2;
+ tokens = g_renew (PosToken, tokens, allocated);
+ }
+
+ next = &tokens[n_tokens];
+
+ switch (*p)
+ {
+ case '*':
+ case '/':
+ case '+':
+ case '-': /* negative numbers aren't allowed so this is easy */
+ case '%':
+ case '`':
+ next->type = POS_TOKEN_OPERATOR;
+ next->d.o.op = op_from_string (p, &len);
+ if (next->d.o.op != POS_OP_NONE)
+ {
+ ++n_tokens;
+ p = p + (len - 1); /* -1 since we ++p later */
+ }
+ else
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Coordinate expression contained unknown operator at the start of this text: \"%s\""),
+ p);
+
+ goto error;
+ }
+ break;
+
+ case '(':
+ next->type = POS_TOKEN_OPEN_PAREN;
+ ++n_tokens;
+ break;
+
+ case ')':
+ next->type = POS_TOKEN_CLOSE_PAREN;
+ ++n_tokens;
+ break;
+
+ case ' ':
+ case '\t':
+ case '\n':
+ break;
+
+ default:
+ if (IS_VARIABLE_CHAR (*p))
+ {
+ /* Assume variable */
+ const char *start = p;
+ while (*p && IS_VARIABLE_CHAR (*p))
+ ++p;
+ g_assert (p != start);
+ next->type = POS_TOKEN_VARIABLE;
+ next->d.v.name = g_strndup (start, p - start);
+ ++n_tokens;
+ --p; /* since we ++p again at the end of while loop */
+ }
+ else
+ {
+ /* Assume number */
+ const char *end;
+
+ if (!parse_number (p, &end, next, err))
+ goto error;
+
+ ++n_tokens;
+ p = end - 1; /* -1 since we ++p again at the end of while loop */
+ }
+
+ break;
+ }
+
+ ++p;
+ }
+
+ if (n_tokens == 0)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Coordinate expression was empty or not understood"));
+
+ goto error;
+ }
+
+ *tokens_p = tokens;
+ *n_tokens_p = n_tokens;
+
+ return TRUE;
+
+ error:
+ g_assert (err == NULL || *err != NULL);
+
+ free_tokens (tokens, n_tokens);
+ return FALSE;
+}
+
+/**
+ * The type of a PosExpr: either integer, double, or an operation.
+ * \ingroup parser
+ */
+typedef enum
+{
+ POS_EXPR_INT,
+ POS_EXPR_DOUBLE,
+ POS_EXPR_OPERATOR
+} PosExprType;
+
+/**
+ * Type and value of an expression in a parsed sequence. We don't
+ * keep expressions in a tree; if this is of type POS_EXPR_OPERATOR,
+ * the arguments of the operator will be in the array positions
+ * immediately preceding and following this operator; they cannot
+ * themselves be operators.
+ *
+ * \bug operator is char; it should really be of PosOperatorType.
+ * \ingroup parser
+ */
+typedef struct
+{
+ PosExprType type;
+ union
+ {
+ double double_val;
+ int int_val;
+ char operator;
+ } d;
+} PosExpr;
+
+#if 0
+static void
+debug_print_exprs (PosExpr *exprs,
+ int n_exprs)
+{
+ int i;
+
+ for (i = 0; i < n_exprs; i++)
+ {
+ switch (exprs[i].type)
+ {
+ case POS_EXPR_INT:
+ g_print (" %d", exprs[i].d.int_val);
+ break;
+ case POS_EXPR_DOUBLE:
+ g_print (" %g", exprs[i].d.double_val);
+ break;
+ case POS_EXPR_OPERATOR:
+ g_print (" %s", op_name (exprs[i].d.operator));
+ break;
+ }
+ }
+ g_print ("\n");
+}
+#endif
+
+static gboolean
+do_operation (PosExpr *a,
+ PosExpr *b,
+ PosOperatorType op,
+ GError **err)
+{
+ /* Promote types to double if required */
+ if (a->type == POS_EXPR_DOUBLE ||
+ b->type == POS_EXPR_DOUBLE)
+ {
+ if (a->type != POS_EXPR_DOUBLE)
+ {
+ a->type = POS_EXPR_DOUBLE;
+ a->d.double_val = a->d.int_val;
+ }
+ if (b->type != POS_EXPR_DOUBLE)
+ {
+ b->type = POS_EXPR_DOUBLE;
+ b->d.double_val = b->d.int_val;
+ }
+ }
+
+ g_assert (a->type == b->type);
+
+ if (a->type == POS_EXPR_INT)
+ {
+ switch (op)
+ {
+ case POS_OP_MULTIPLY:
+ a->d.int_val = a->d.int_val * b->d.int_val;
+ break;
+ case POS_OP_DIVIDE:
+ if (b->d.int_val == 0)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_DIVIDE_BY_ZERO,
+ _("Coordinate expression results in division by zero"));
+ return FALSE;
+ }
+ a->d.int_val = a->d.int_val / b->d.int_val;
+ break;
+ case POS_OP_MOD:
+ if (b->d.int_val == 0)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_DIVIDE_BY_ZERO,
+ _("Coordinate expression results in division by zero"));
+ return FALSE;
+ }
+ a->d.int_val = a->d.int_val % b->d.int_val;
+ break;
+ case POS_OP_ADD:
+ a->d.int_val = a->d.int_val + b->d.int_val;
+ break;
+ case POS_OP_SUBTRACT:
+ a->d.int_val = a->d.int_val - b->d.int_val;
+ break;
+ case POS_OP_MAX:
+ a->d.int_val = MAX (a->d.int_val, b->d.int_val);
+ break;
+ case POS_OP_MIN:
+ a->d.int_val = MIN (a->d.int_val, b->d.int_val);
+ break;
+ case POS_OP_NONE:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ else if (a->type == POS_EXPR_DOUBLE)
+ {
+ switch (op)
+ {
+ case POS_OP_MULTIPLY:
+ a->d.double_val = a->d.double_val * b->d.double_val;
+ break;
+ case POS_OP_DIVIDE:
+ if (b->d.double_val == 0.0)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_DIVIDE_BY_ZERO,
+ _("Coordinate expression results in division by zero"));
+ return FALSE;
+ }
+ a->d.double_val = a->d.double_val / b->d.double_val;
+ break;
+ case POS_OP_MOD:
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_MOD_ON_FLOAT,
+ _("Coordinate expression tries to use mod operator on a floating-point number"));
+ return FALSE;
+ case POS_OP_ADD:
+ a->d.double_val = a->d.double_val + b->d.double_val;
+ break;
+ case POS_OP_SUBTRACT:
+ a->d.double_val = a->d.double_val - b->d.double_val;
+ break;
+ case POS_OP_MAX:
+ a->d.double_val = MAX (a->d.double_val, b->d.double_val);
+ break;
+ case POS_OP_MIN:
+ a->d.double_val = MIN (a->d.double_val, b->d.double_val);
+ break;
+ case POS_OP_NONE:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+ else
+ g_assert_not_reached ();
+
+ return TRUE;
+}
+
+static gboolean
+do_operations (PosExpr *exprs,
+ int *n_exprs,
+ int precedence,
+ GError **err)
+{
+ int i;
+
+#if 0
+ g_print ("Doing prec %d ops on %d exprs\n", precedence, *n_exprs);
+ debug_print_exprs (exprs, *n_exprs);
+#endif
+
+ i = 1;
+ while (i < *n_exprs)
+ {
+ gboolean compress;
+
+ /* exprs[i-1] first operand
+ * exprs[i] operator
+ * exprs[i+1] second operand
+ *
+ * we replace first operand with result of mul/div/mod,
+ * or skip over operator and second operand if we have
+ * an add/subtract
+ */
+
+ if (exprs[i-1].type == POS_EXPR_OPERATOR)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Coordinate expression has an operator \"%s\" where an operand was expected"),
+ op_name (exprs[i-1].d.operator));
+ return FALSE;
+ }
+
+ if (exprs[i].type != POS_EXPR_OPERATOR)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Coordinate expression had an operand where an operator was expected"));
+ return FALSE;
+ }
+
+ if (i == (*n_exprs - 1))
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Coordinate expression ended with an operator instead of an operand"));
+ return FALSE;
+ }
+
+ g_assert ((i+1) < *n_exprs);
+
+ if (exprs[i+1].type == POS_EXPR_OPERATOR)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Coordinate expression has operator \"%c\" following operator \"%c\" with no operand in between"),
+ exprs[i+1].d.operator,
+ exprs[i].d.operator);
+ return FALSE;
+ }
+
+ compress = FALSE;
+
+ switch (precedence)
+ {
+ case 2:
+ switch (exprs[i].d.operator)
+ {
+ case POS_OP_DIVIDE:
+ case POS_OP_MOD:
+ case POS_OP_MULTIPLY:
+ compress = TRUE;
+ if (!do_operation (&exprs[i-1], &exprs[i+1],
+ exprs[i].d.operator,
+ err))
+ return FALSE;
+ break;
+ }
+ break;
+ case 1:
+ switch (exprs[i].d.operator)
+ {
+ case POS_OP_ADD:
+ case POS_OP_SUBTRACT:
+ compress = TRUE;
+ if (!do_operation (&exprs[i-1], &exprs[i+1],
+ exprs[i].d.operator,
+ err))
+ return FALSE;
+ break;
+ }
+ break;
+ /* I have no rationale at all for making these low-precedence */
+ case 0:
+ switch (exprs[i].d.operator)
+ {
+ case POS_OP_MAX:
+ case POS_OP_MIN:
+ compress = TRUE;
+ if (!do_operation (&exprs[i-1], &exprs[i+1],
+ exprs[i].d.operator,
+ err))
+ return FALSE;
+ break;
+ }
+ break;
+ }
+
+ if (compress)
+ {
+ /* exprs[i-1] first operand (now result)
+ * exprs[i] operator
+ * exprs[i+1] second operand
+ * exprs[i+2] new operator
+ *
+ * we move new operator just after first operand
+ */
+ if ((i+2) < *n_exprs)
+ {
+ g_memmove (&exprs[i], &exprs[i+2],
+ sizeof (PosExpr) * (*n_exprs - i - 2));
+ }
+
+ *n_exprs -= 2;
+ }
+ else
+ {
+ /* Skip operator and next operand */
+ i += 2;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * There is a predefined set of variables which can appear in an expression.
+ * Here we take a token representing a variable, and return the current value
+ * of that variable in a particular environment.
+ * (The value is always an integer.)
+ *
+ * There are supposedly some circumstances in which this function can be
+ * called from outside Marco, in which case env->theme will be NULL, and
+ * therefore we can't use it to find out quark values, so we do the comparison
+ * using strcmp, which is slower.
+ *
+ * \param t The token representing a variable
+ * \param[out] result The value of that variable; not set if the token did
+ * not represent a known variable
+ * \param env The environment within which t should be evaluated
+ * \param[out] err set to the problem if there was a problem
+ *
+ * \return true if we found the variable asked for, false if we didn't
+ *
+ * \bug shouldn't t be const?
+ * \bug we should perhaps consider some sort of lookup arrangement into an
+ * array; also, the duplication of code is unlovely; perhaps using glib
+ * string hashes instead of quarks would fix both problems?
+ * \ingroup parser
+ */
+static gboolean
+pos_eval_get_variable (PosToken *t,
+ int *result,
+ const MetaPositionExprEnv *env,
+ GError **err)
+{
+ if (env->theme)
+ {
+ if (t->d.v.name_quark == env->theme->quark_width)
+ *result = env->rect.width;
+ else if (t->d.v.name_quark == env->theme->quark_height)
+ *result = env->rect.height;
+ else if (env->object_width >= 0 &&
+ t->d.v.name_quark == env->theme->quark_object_width)
+ *result = env->object_width;
+ else if (env->object_height >= 0 &&
+ t->d.v.name_quark == env->theme->quark_object_height)
+ *result = env->object_height;
+ else if (t->d.v.name_quark == env->theme->quark_left_width)
+ *result = env->left_width;
+ else if (t->d.v.name_quark == env->theme->quark_right_width)
+ *result = env->right_width;
+ else if (t->d.v.name_quark == env->theme->quark_top_height)
+ *result = env->top_height;
+ else if (t->d.v.name_quark == env->theme->quark_bottom_height)
+ *result = env->bottom_height;
+ else if (t->d.v.name_quark == env->theme->quark_mini_icon_width)
+ *result = env->mini_icon_width;
+ else if (t->d.v.name_quark == env->theme->quark_mini_icon_height)
+ *result = env->mini_icon_height;
+ else if (t->d.v.name_quark == env->theme->quark_icon_width)
+ *result = env->icon_width;
+ else if (t->d.v.name_quark == env->theme->quark_icon_height)
+ *result = env->icon_height;
+ else if (t->d.v.name_quark == env->theme->quark_title_width)
+ *result = env->title_width;
+ else if (t->d.v.name_quark == env->theme->quark_title_height)
+ *result = env->title_height;
+ else
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_UNKNOWN_VARIABLE,
+ _("Coordinate expression had unknown variable or constant \"%s\""),
+ t->d.v.name);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (strcmp (t->d.v.name, "width") == 0)
+ *result = env->rect.width;
+ else if (strcmp (t->d.v.name, "height") == 0)
+ *result = env->rect.height;
+ else if (env->object_width >= 0 &&
+ strcmp (t->d.v.name, "object_width") == 0)
+ *result = env->object_width;
+ else if (env->object_height >= 0 &&
+ strcmp (t->d.v.name, "object_height") == 0)
+ *result = env->object_height;
+ else if (strcmp (t->d.v.name, "left_width") == 0)
+ *result = env->left_width;
+ else if (strcmp (t->d.v.name, "right_width") == 0)
+ *result = env->right_width;
+ else if (strcmp (t->d.v.name, "top_height") == 0)
+ *result = env->top_height;
+ else if (strcmp (t->d.v.name, "bottom_height") == 0)
+ *result = env->bottom_height;
+ else if (strcmp (t->d.v.name, "mini_icon_width") == 0)
+ *result = env->mini_icon_width;
+ else if (strcmp (t->d.v.name, "mini_icon_height") == 0)
+ *result = env->mini_icon_height;
+ else if (strcmp (t->d.v.name, "icon_width") == 0)
+ *result = env->icon_width;
+ else if (strcmp (t->d.v.name, "icon_height") == 0)
+ *result = env->icon_height;
+ else if (strcmp (t->d.v.name, "title_width") == 0)
+ *result = env->title_width;
+ else if (strcmp (t->d.v.name, "title_height") == 0)
+ *result = env->title_height;
+ else
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_UNKNOWN_VARIABLE,
+ _("Coordinate expression had unknown variable or constant \"%s\""),
+ t->d.v.name);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Evaluates a sequence of tokens within a particular environment context,
+ * and returns the current value. May recur if parantheses are found.
+ *
+ * \param tokens A list of tokens to evaluate.
+ * \param n_tokens How many tokens are in the list.
+ * \param env The environment context in which to evaluate the expression.
+ * \param[out] result The current value of the expression
+ *
+ * \bug Yes, we really do reparse the expression every time it's evaluated.
+ * We should keep the parse tree around all the time and just
+ * run the new values through it.
+ * \ingroup parser
+ */
+static gboolean
+pos_eval_helper (PosToken *tokens,
+ int n_tokens,
+ const MetaPositionExprEnv *env,
+ PosExpr *result,
+ GError **err)
+{
+ /* Lazy-ass hardcoded limit on number of terms in expression */
+#define MAX_EXPRS 32
+ int paren_level;
+ int first_paren;
+ int i;
+ PosExpr exprs[MAX_EXPRS];
+ int n_exprs;
+ int precedence;
+
+ /* Our first goal is to get a list of PosExpr, essentially
+ * substituting variables and handling parentheses.
+ */
+
+ first_paren = 0;
+ paren_level = 0;
+ n_exprs = 0;
+ for (i = 0; i < n_tokens; i++)
+ {
+ PosToken *t = &tokens[i];
+
+ if (n_exprs >= MAX_EXPRS)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Coordinate expression parser overflowed its buffer."));
+ return FALSE;
+ }
+
+ if (paren_level == 0)
+ {
+ switch (t->type)
+ {
+ case POS_TOKEN_INT:
+ exprs[n_exprs].type = POS_EXPR_INT;
+ exprs[n_exprs].d.int_val = t->d.i.val;
+ ++n_exprs;
+ break;
+
+ case POS_TOKEN_DOUBLE:
+ exprs[n_exprs].type = POS_EXPR_DOUBLE;
+ exprs[n_exprs].d.double_val = t->d.d.val;
+ ++n_exprs;
+ break;
+
+ case POS_TOKEN_OPEN_PAREN:
+ ++paren_level;
+ if (paren_level == 1)
+ first_paren = i;
+ break;
+
+ case POS_TOKEN_CLOSE_PAREN:
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_BAD_PARENS,
+ _("Coordinate expression had a close parenthesis with no open parenthesis"));
+ return FALSE;
+
+ case POS_TOKEN_VARIABLE:
+ exprs[n_exprs].type = POS_EXPR_INT;
+
+ /* FIXME we should just dump all this crap
+ * in a hash, maybe keep width/height out
+ * for optimization purposes
+ */
+ if (!pos_eval_get_variable (t, &exprs[n_exprs].d.int_val, env, err))
+ return FALSE;
+
+ ++n_exprs;
+ break;
+
+ case POS_TOKEN_OPERATOR:
+ exprs[n_exprs].type = POS_EXPR_OPERATOR;
+ exprs[n_exprs].d.operator = t->d.o.op;
+ ++n_exprs;
+ break;
+ }
+ }
+ else
+ {
+ g_assert (paren_level > 0);
+
+ switch (t->type)
+ {
+ case POS_TOKEN_INT:
+ case POS_TOKEN_DOUBLE:
+ case POS_TOKEN_VARIABLE:
+ case POS_TOKEN_OPERATOR:
+ break;
+
+ case POS_TOKEN_OPEN_PAREN:
+ ++paren_level;
+ break;
+
+ case POS_TOKEN_CLOSE_PAREN:
+ if (paren_level == 1)
+ {
+ /* We closed a toplevel paren group, so recurse */
+ if (!pos_eval_helper (&tokens[first_paren+1],
+ i - first_paren - 1,
+ env,
+ &exprs[n_exprs],
+ err))
+ return FALSE;
+
+ ++n_exprs;
+ }
+
+ --paren_level;
+ break;
+
+ }
+ }
+ }
+
+ if (paren_level > 0)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_BAD_PARENS,
+ _("Coordinate expression had an open parenthesis with no close parenthesis"));
+ return FALSE;
+ }
+
+ /* Now we have no parens and no vars; so we just do all the multiplies
+ * and divides, then all the add and subtract.
+ */
+ if (n_exprs == 0)
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Coordinate expression doesn't seem to have any operators or operands"));
+ return FALSE;
+ }
+
+ /* precedence 1 ops */
+ precedence = 2;
+ while (precedence >= 0)
+ {
+ if (!do_operations (exprs, &n_exprs, precedence, err))
+ return FALSE;
+ --precedence;
+ }
+
+ g_assert (n_exprs == 1);
+
+ *result = *exprs;
+
+ return TRUE;
+}
+
+/*
+ * expr = int | double | expr * expr | expr / expr |
+ * expr + expr | expr - expr | (expr)
+ *
+ * so very not worth fooling with bison, yet so very painful by hand.
+ */
+/**
+ * Evaluates an expression.
+ *
+ * \param spec The expression to evaluate.
+ * \param env The environment context to evaluate the expression in.
+ * \param[out] val_p The integer value of the expression; if the expression
+ * is of type float, this will be rounded. If we return
+ * FALSE because the expression is invalid, this will be
+ * zero.
+ * \param[out] err The error, if anything went wrong.
+ *
+ * \return True if we evaluated the expression successfully; false otherwise.
+ *
+ * \bug Shouldn't spec be const?
+ * \ingroup parser
+ */
+static gboolean
+pos_eval (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env,
+ int *val_p,
+ GError **err)
+{
+ PosExpr expr;
+
+ *val_p = 0;
+
+ if (pos_eval_helper (spec->tokens, spec->n_tokens, env, &expr, err))
+ {
+ switch (expr.type)
+ {
+ case POS_EXPR_INT:
+ *val_p = expr.d.int_val;
+ break;
+ case POS_EXPR_DOUBLE:
+ *val_p = expr.d.double_val;
+ break;
+ case POS_EXPR_OPERATOR:
+ g_assert_not_reached ();
+ break;
+ }
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+/* We always return both X and Y, but only one will be meaningful in
+ * most contexts.
+ */
+
+gboolean
+meta_parse_position_expression (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env,
+ int *x_return,
+ int *y_return,
+ GError **err)
+{
+ /* All positions are in a coordinate system with x, y at the origin.
+ * The expression can have -, +, *, / as operators, floating point
+ * or integer constants, and the variables "width" and "height" and
+ * optionally "object_width" and object_height". Negative numbers
+ * aren't allowed.
+ */
+ int val;
+
+ if (spec->constant)
+ val = spec->value;
+ else
+ {
+ if (pos_eval (spec, env, &spec->value, err) == FALSE)
+ {
+ g_assert (err == NULL || *err != NULL);
+ return FALSE;
+ }
+
+ val = spec->value;
+ }
+
+ if (x_return)
+ *x_return = env->rect.x + val;
+ if (y_return)
+ *y_return = env->rect.y + val;
+
+ return TRUE;
+}
+
+
+gboolean
+meta_parse_size_expression (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env,
+ int *val_return,
+ GError **err)
+{
+ int val;
+
+ if (spec->constant)
+ val = spec->value;
+ else
+ {
+ if (pos_eval (spec, env, &spec->value, err) == FALSE)
+ {
+ g_assert (err == NULL || *err != NULL);
+ return FALSE;
+ }
+
+ val = spec->value;
+ }
+
+ if (val_return)
+ *val_return = MAX (val, 1); /* require that sizes be at least 1x1 */
+
+ return TRUE;
+}
+
+/* To do this we tokenize, replace variable tokens
+ * that are constants, then reassemble. The purpose
+ * here is to optimize expressions so we don't do hash
+ * lookups to eval them. Obviously it's a tradeoff that
+ * slows down theme load times.
+ */
+gboolean
+meta_theme_replace_constants (MetaTheme *theme,
+ PosToken *tokens,
+ int n_tokens,
+ GError **err)
+{
+ int i;
+ double dval;
+ int ival;
+ gboolean is_constant = TRUE;
+
+ /* Loop through tokenized string looking for variables to replace */
+ for (i = 0; i < n_tokens; i++)
+ {
+ PosToken *t = &tokens[i];
+
+ if (t->type == POS_TOKEN_VARIABLE)
+ {
+ if (meta_theme_lookup_int_constant (theme, t->d.v.name, &ival))
+ {
+ g_free (t->d.v.name);
+ t->type = POS_TOKEN_INT;
+ t->d.i.val = ival;
+ }
+ else if (meta_theme_lookup_float_constant (theme, t->d.v.name, &dval))
+ {
+ g_free (t->d.v.name);
+ t->type = POS_TOKEN_DOUBLE;
+ t->d.d.val = dval;
+ }
+ else
+ {
+ /* If we've found a variable that cannot be replaced then the
+ expression is not a constant expression and we want to
+ replace it with a GQuark */
+
+ t->d.v.name_quark = g_quark_from_string (t->d.v.name);
+ is_constant = FALSE;
+ }
+ }
+ }
+
+ return is_constant;
+}
+
+static int
+parse_x_position_unchecked (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env)
+{
+ int retval;
+ GError *error;
+
+ retval = 0;
+ error = NULL;
+ if (!meta_parse_position_expression (spec, env, &retval, NULL, &error))
+ {
+ meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
+ error->message);
+
+ g_error_free (error);
+ }
+
+ return retval;
+}
+
+static int
+parse_y_position_unchecked (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env)
+{
+ int retval;
+ GError *error;
+
+ retval = 0;
+ error = NULL;
+ if (!meta_parse_position_expression (spec, env, NULL, &retval, &error))
+ {
+ meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
+ error->message);
+
+ g_error_free (error);
+ }
+
+ return retval;
+}
+
+static int
+parse_size_unchecked (MetaDrawSpec *spec,
+ MetaPositionExprEnv *env)
+{
+ int retval;
+ GError *error;
+
+ retval = 0;
+ error = NULL;
+ if (!meta_parse_size_expression (spec, env, &retval, &error))
+ {
+ meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
+ error->message);
+
+ g_error_free (error);
+ }
+
+ return retval;
+}
+
+void
+meta_draw_spec_free (MetaDrawSpec *spec)
+{
+ if (!spec) return;
+ free_tokens (spec->tokens, spec->n_tokens);
+ g_slice_free (MetaDrawSpec, spec);
+}
+
+MetaDrawSpec *
+meta_draw_spec_new (MetaTheme *theme,
+ const char *expr,
+ GError **error)
+{
+ MetaDrawSpec *spec;
+
+ spec = g_slice_new0 (MetaDrawSpec);
+
+ pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL);
+
+ spec->constant = meta_theme_replace_constants (theme, spec->tokens,
+ spec->n_tokens, NULL);
+ if (spec->constant)
+ {
+ gboolean result;
+
+ result = pos_eval (spec, NULL, &spec->value, error);
+ if (result == FALSE)
+ {
+ meta_draw_spec_free (spec);
+ return NULL;
+ }
+ }
+
+ return spec;
+}
+
+MetaDrawOp*
+meta_draw_op_new (MetaDrawType type)
+{
+ MetaDrawOp *op;
+ MetaDrawOp dummy;
+ int size;
+
+ size = G_STRUCT_OFFSET (MetaDrawOp, data);
+
+ switch (type)
+ {
+ case META_DRAW_LINE:
+ size += sizeof (dummy.data.line);
+ break;
+
+ case META_DRAW_RECTANGLE:
+ size += sizeof (dummy.data.rectangle);
+ break;
+
+ case META_DRAW_ARC:
+ size += sizeof (dummy.data.arc);
+ break;
+
+ case META_DRAW_CLIP:
+ size += sizeof (dummy.data.clip);
+ break;
+
+ case META_DRAW_TINT:
+ size += sizeof (dummy.data.tint);
+ break;
+
+ case META_DRAW_GRADIENT:
+ size += sizeof (dummy.data.gradient);
+ break;
+
+ case META_DRAW_IMAGE:
+ size += sizeof (dummy.data.image);
+ break;
+
+ case META_DRAW_GTK_ARROW:
+ size += sizeof (dummy.data.gtk_arrow);
+ break;
+
+ case META_DRAW_GTK_BOX:
+ size += sizeof (dummy.data.gtk_box);
+ break;
+
+ case META_DRAW_GTK_VLINE:
+ size += sizeof (dummy.data.gtk_vline);
+ break;
+
+ case META_DRAW_ICON:
+ size += sizeof (dummy.data.icon);
+ break;
+
+ case META_DRAW_TITLE:
+ size += sizeof (dummy.data.title);
+ break;
+ case META_DRAW_OP_LIST:
+ size += sizeof (dummy.data.op_list);
+ break;
+ case META_DRAW_TILE:
+ size += sizeof (dummy.data.tile);
+ break;
+ }
+
+ op = g_malloc0 (size);
+
+ op->type = type;
+
+ return op;
+}
+
+void
+meta_draw_op_free (MetaDrawOp *op)
+{
+ g_return_if_fail (op != NULL);
+
+ switch (op->type)
+ {
+ case META_DRAW_LINE:
+ if (op->data.line.color_spec)
+ meta_color_spec_free (op->data.line.color_spec);
+
+ meta_draw_spec_free (op->data.line.x1);
+ meta_draw_spec_free (op->data.line.y1);
+ meta_draw_spec_free (op->data.line.x2);
+ meta_draw_spec_free (op->data.line.y2);
+ break;
+
+ case META_DRAW_RECTANGLE:
+ if (op->data.rectangle.color_spec)
+ g_free (op->data.rectangle.color_spec);
+
+ meta_draw_spec_free (op->data.rectangle.x);
+ meta_draw_spec_free (op->data.rectangle.y);
+ meta_draw_spec_free (op->data.rectangle.width);
+ meta_draw_spec_free (op->data.rectangle.height);
+ break;
+
+ case META_DRAW_ARC:
+ if (op->data.arc.color_spec)
+ g_free (op->data.arc.color_spec);
+
+ meta_draw_spec_free (op->data.arc.x);
+ meta_draw_spec_free (op->data.arc.y);
+ meta_draw_spec_free (op->data.arc.width);
+ meta_draw_spec_free (op->data.arc.height);
+ break;
+
+ case META_DRAW_CLIP:
+ meta_draw_spec_free (op->data.clip.x);
+ meta_draw_spec_free (op->data.clip.y);
+ meta_draw_spec_free (op->data.clip.width);
+ meta_draw_spec_free (op->data.clip.height);
+ break;
+
+ case META_DRAW_TINT:
+ if (op->data.tint.color_spec)
+ meta_color_spec_free (op->data.tint.color_spec);
+
+ if (op->data.tint.alpha_spec)
+ meta_alpha_gradient_spec_free (op->data.tint.alpha_spec);
+
+ meta_draw_spec_free (op->data.tint.x);
+ meta_draw_spec_free (op->data.tint.y);
+ meta_draw_spec_free (op->data.tint.width);
+ meta_draw_spec_free (op->data.tint.height);
+ break;
+
+ case META_DRAW_GRADIENT:
+ if (op->data.gradient.gradient_spec)
+ meta_gradient_spec_free (op->data.gradient.gradient_spec);
+
+ if (op->data.gradient.alpha_spec)
+ meta_alpha_gradient_spec_free (op->data.gradient.alpha_spec);
+
+ meta_draw_spec_free (op->data.gradient.x);
+ meta_draw_spec_free (op->data.gradient.y);
+ meta_draw_spec_free (op->data.gradient.width);
+ meta_draw_spec_free (op->data.gradient.height);
+ break;
+
+ case META_DRAW_IMAGE:
+ if (op->data.image.alpha_spec)
+ meta_alpha_gradient_spec_free (op->data.image.alpha_spec);
+
+ if (op->data.image.pixbuf)
+ g_object_unref (G_OBJECT (op->data.image.pixbuf));
+
+ if (op->data.image.colorize_spec)
+ meta_color_spec_free (op->data.image.colorize_spec);
+
+ if (op->data.image.colorize_cache_pixbuf)
+ g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
+
+ meta_draw_spec_free (op->data.image.x);
+ meta_draw_spec_free (op->data.image.y);
+ meta_draw_spec_free (op->data.image.width);
+ meta_draw_spec_free (op->data.image.height);
+ break;
+
+ case META_DRAW_GTK_ARROW:
+ meta_draw_spec_free (op->data.gtk_arrow.x);
+ meta_draw_spec_free (op->data.gtk_arrow.y);
+ meta_draw_spec_free (op->data.gtk_arrow.width);
+ meta_draw_spec_free (op->data.gtk_arrow.height);
+ break;
+
+ case META_DRAW_GTK_BOX:
+ meta_draw_spec_free (op->data.gtk_box.x);
+ meta_draw_spec_free (op->data.gtk_box.y);
+ meta_draw_spec_free (op->data.gtk_box.width);
+ meta_draw_spec_free (op->data.gtk_box.height);
+ break;
+
+ case META_DRAW_GTK_VLINE:
+ meta_draw_spec_free (op->data.gtk_vline.x);
+ meta_draw_spec_free (op->data.gtk_vline.y1);
+ meta_draw_spec_free (op->data.gtk_vline.y2);
+ break;
+
+ case META_DRAW_ICON:
+ if (op->data.icon.alpha_spec)
+ meta_alpha_gradient_spec_free (op->data.icon.alpha_spec);
+
+ meta_draw_spec_free (op->data.icon.x);
+ meta_draw_spec_free (op->data.icon.y);
+ meta_draw_spec_free (op->data.icon.width);
+ meta_draw_spec_free (op->data.icon.height);
+ break;
+
+ case META_DRAW_TITLE:
+ if (op->data.title.color_spec)
+ meta_color_spec_free (op->data.title.color_spec);
+
+ meta_draw_spec_free (op->data.title.x);
+ meta_draw_spec_free (op->data.title.y);
+ break;
+
+ case META_DRAW_OP_LIST:
+ if (op->data.op_list.op_list)
+ meta_draw_op_list_unref (op->data.op_list.op_list);
+
+ meta_draw_spec_free (op->data.op_list.x);
+ meta_draw_spec_free (op->data.op_list.y);
+ meta_draw_spec_free (op->data.op_list.width);
+ meta_draw_spec_free (op->data.op_list.height);
+ break;
+
+ case META_DRAW_TILE:
+ if (op->data.tile.op_list)
+ meta_draw_op_list_unref (op->data.tile.op_list);
+
+ meta_draw_spec_free (op->data.tile.x);
+ meta_draw_spec_free (op->data.tile.y);
+ meta_draw_spec_free (op->data.tile.width);
+ meta_draw_spec_free (op->data.tile.height);
+ meta_draw_spec_free (op->data.tile.tile_xoffset);
+ meta_draw_spec_free (op->data.tile.tile_yoffset);
+ meta_draw_spec_free (op->data.tile.tile_width);
+ meta_draw_spec_free (op->data.tile.tile_height);
+ break;
+ }
+
+ g_free (op);
+}
+
+static GdkPixbuf*
+apply_alpha (GdkPixbuf *pixbuf,
+ MetaAlphaGradientSpec *spec,
+ gboolean force_copy)
+{
+ GdkPixbuf *new_pixbuf;
+ gboolean needs_alpha;
+
+ g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
+
+ needs_alpha = spec && (spec->n_alphas > 1 ||
+ spec->alphas[0] != 0xff);
+
+ if (!needs_alpha)
+ return pixbuf;
+
+ if (!gdk_pixbuf_get_has_alpha (pixbuf))
+ {
+ new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
+ g_object_unref (G_OBJECT (pixbuf));
+ pixbuf = new_pixbuf;
+ }
+ else if (force_copy)
+ {
+ new_pixbuf = gdk_pixbuf_copy (pixbuf);
+ g_object_unref (G_OBJECT (pixbuf));
+ pixbuf = new_pixbuf;
+ }
+
+ g_assert (gdk_pixbuf_get_has_alpha (pixbuf));
+
+ meta_gradient_add_alpha (pixbuf, spec->alphas, spec->n_alphas, spec->type);
+
+ return pixbuf;
+}
+
+static GdkPixbuf*
+pixbuf_tile (GdkPixbuf *tile,
+ int width,
+ int height)
+{
+ GdkPixbuf *pixbuf;
+ int tile_width;
+ int tile_height;
+ int i, j;
+
+ tile_width = gdk_pixbuf_get_width (tile);
+ tile_height = gdk_pixbuf_get_height (tile);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ gdk_pixbuf_get_has_alpha (tile),
+ 8, width, height);
+
+ i = 0;
+ while (i < width)
+ {
+ j = 0;
+ while (j < height)
+ {
+ int w, h;
+
+ w = MIN (tile_width, width - i);
+ h = MIN (tile_height, height - j);
+
+ gdk_pixbuf_copy_area (tile,
+ 0, 0,
+ w, h,
+ pixbuf,
+ i, j);
+
+ j += tile_height;
+ }
+
+ i += tile_width;
+ }
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+replicate_rows (GdkPixbuf *src,
+ int src_x,
+ int src_y,
+ int width,
+ int height)
+{
+ unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
+ unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
+ unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
+ * n_channels);
+ unsigned char *dest_pixels;
+ GdkPixbuf *result;
+ unsigned int dest_rowstride;
+ int i;
+
+ result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
+ width, height);
+ dest_rowstride = gdk_pixbuf_get_rowstride (result);
+ dest_pixels = gdk_pixbuf_get_pixels (result);
+
+ for (i = 0; i < height; i++)
+ memcpy (dest_pixels + dest_rowstride * i, pixels, n_channels * width);
+
+ return result;
+}
+
+static GdkPixbuf *
+replicate_cols (GdkPixbuf *src,
+ int src_x,
+ int src_y,
+ int width,
+ int height)
+{
+ unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
+ unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
+ unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
+ * n_channels);
+ unsigned char *dest_pixels;
+ GdkPixbuf *result;
+ unsigned int dest_rowstride;
+ int i, j;
+
+ result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
+ width, height);
+ dest_rowstride = gdk_pixbuf_get_rowstride (result);
+ dest_pixels = gdk_pixbuf_get_pixels (result);
+
+ for (i = 0; i < height; i++)
+ {
+ unsigned char *p = dest_pixels + dest_rowstride * i;
+ unsigned char *q = pixels + src_rowstride * i;
+
+ unsigned char r = *(q++);
+ unsigned char g = *(q++);
+ unsigned char b = *(q++);
+
+ if (n_channels == 4)
+ {
+ unsigned char a;
+
+ a = *(q++);
+
+ for (j = 0; j < width; j++)
+ {
+ *(p++) = r;
+ *(p++) = g;
+ *(p++) = b;
+ *(p++) = a;
+ }
+ }
+ else
+ {
+ for (j = 0; j < width; j++)
+ {
+ *(p++) = r;
+ *(p++) = g;
+ *(p++) = b;
+ }
+ }
+ }
+
+ return result;
+}
+
+static GdkPixbuf*
+scale_and_alpha_pixbuf (GdkPixbuf *src,
+ MetaAlphaGradientSpec *alpha_spec,
+ MetaImageFillType fill_type,
+ int width,
+ int height,
+ gboolean vertical_stripes,
+ gboolean horizontal_stripes)
+{
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *temp_pixbuf;
+
+ pixbuf = NULL;
+
+ pixbuf = src;
+
+ if (gdk_pixbuf_get_width (pixbuf) == width &&
+ gdk_pixbuf_get_height (pixbuf) == height)
+ {
+ g_object_ref (G_OBJECT (pixbuf));
+ }
+ else
+ {
+ if (fill_type == META_IMAGE_FILL_TILE)
+ {
+ pixbuf = pixbuf_tile (pixbuf, width, height);
+ }
+ else
+ {
+ int src_h, src_w, dest_h, dest_w;
+ src_h = gdk_pixbuf_get_height (src);
+ src_w = gdk_pixbuf_get_width (src);
+
+ /* prefer to replicate_cols if possible, as that
+ * is faster (no memory reads)
+ */
+ if (horizontal_stripes)
+ {
+ dest_w = gdk_pixbuf_get_width (src);
+ dest_h = height;
+ }
+ else if (vertical_stripes)
+ {
+ dest_w = width;
+ dest_h = gdk_pixbuf_get_height (src);
+ }
+
+ else
+ {
+ dest_w = width;
+ dest_h = height;
+ }
+
+ if (dest_w == src_w && dest_h == src_h)
+ {
+ temp_pixbuf = src;
+ g_object_ref (G_OBJECT (temp_pixbuf));
+ }
+ else
+ {
+ temp_pixbuf = gdk_pixbuf_scale_simple (src,
+ dest_w, dest_h,
+ GDK_INTERP_BILINEAR);
+ }
+
+ /* prefer to replicate_cols if possible, as that
+ * is faster (no memory reads)
+ */
+ if (horizontal_stripes)
+ {
+ pixbuf = replicate_cols (temp_pixbuf, 0, 0, width, height);
+ g_object_unref (G_OBJECT (temp_pixbuf));
+ }
+ else if (vertical_stripes)
+ {
+ pixbuf = replicate_rows (temp_pixbuf, 0, 0, width, height);
+ g_object_unref (G_OBJECT (temp_pixbuf));
+ }
+ else
+ {
+ pixbuf = temp_pixbuf;
+ }
+ }
+ }
+
+ if (pixbuf)
+ pixbuf = apply_alpha (pixbuf, alpha_spec, pixbuf == src);
+
+ return pixbuf;
+}
+
+static GdkPixbuf*
+draw_op_as_pixbuf (const MetaDrawOp *op,
+ GtkWidget *widget,
+ const MetaDrawInfo *info,
+ int width,
+ int height)
+{
+ /* Try to get the op as a pixbuf, assuming w/h in the op
+ * matches the width/height passed in. return NULL
+ * if the op can't be converted to an equivalent pixbuf.
+ */
+ GdkPixbuf *pixbuf;
+
+ pixbuf = NULL;
+
+ switch (op->type)
+ {
+ case META_DRAW_LINE:
+ break;
+
+ case META_DRAW_RECTANGLE:
+ if (op->data.rectangle.filled)
+ {
+ GdkColor color;
+
+ meta_color_spec_render (op->data.rectangle.color_spec,
+ widget,
+ &color);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ FALSE,
+ 8, width, height);
+
+ gdk_pixbuf_fill (pixbuf, GDK_COLOR_RGBA (color));
+ }
+ break;
+
+ case META_DRAW_ARC:
+ break;
+
+ case META_DRAW_CLIP:
+ break;
+
+ case META_DRAW_TINT:
+ {
+ GdkColor color;
+ guint32 rgba;
+ gboolean has_alpha;
+
+ meta_color_spec_render (op->data.rectangle.color_spec,
+ widget,
+ &color);
+
+ has_alpha =
+ op->data.tint.alpha_spec &&
+ (op->data.tint.alpha_spec->n_alphas > 1 ||
+ op->data.tint.alpha_spec->alphas[0] != 0xff);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ has_alpha,
+ 8, width, height);
+
+ if (!has_alpha)
+ {
+ rgba = GDK_COLOR_RGBA (color);
+
+ gdk_pixbuf_fill (pixbuf, rgba);
+ }
+ else if (op->data.tint.alpha_spec->n_alphas == 1)
+ {
+ rgba = GDK_COLOR_RGBA (color);
+ rgba &= ~0xff;
+ rgba |= op->data.tint.alpha_spec->alphas[0];
+
+ gdk_pixbuf_fill (pixbuf, rgba);
+ }
+ else
+ {
+ rgba = GDK_COLOR_RGBA (color);
+
+ gdk_pixbuf_fill (pixbuf, rgba);
+
+ meta_gradient_add_alpha (pixbuf,
+ op->data.tint.alpha_spec->alphas,
+ op->data.tint.alpha_spec->n_alphas,
+ op->data.tint.alpha_spec->type);
+ }
+ }
+ break;
+
+ case META_DRAW_GRADIENT:
+ {
+ pixbuf = meta_gradient_spec_render (op->data.gradient.gradient_spec,
+ widget, width, height);
+
+ pixbuf = apply_alpha (pixbuf,
+ op->data.gradient.alpha_spec,
+ FALSE);
+ }
+ break;
+
+
+ case META_DRAW_IMAGE:
+ {
+ if (op->data.image.colorize_spec)
+ {
+ GdkColor color;
+
+ meta_color_spec_render (op->data.image.colorize_spec,
+ widget, &color);
+
+ if (op->data.image.colorize_cache_pixbuf == NULL ||
+ op->data.image.colorize_cache_pixel != GDK_COLOR_RGB (color))
+ {
+ if (op->data.image.colorize_cache_pixbuf)
+ g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
+
+ /* const cast here */
+ ((MetaDrawOp*)op)->data.image.colorize_cache_pixbuf =
+ colorize_pixbuf (op->data.image.pixbuf,
+ &color);
+ ((MetaDrawOp*)op)->data.image.colorize_cache_pixel =
+ GDK_COLOR_RGB (color);
+ }
+
+ if (op->data.image.colorize_cache_pixbuf)
+ {
+ pixbuf = scale_and_alpha_pixbuf (op->data.image.colorize_cache_pixbuf,
+ op->data.image.alpha_spec,
+ op->data.image.fill_type,
+ width, height,
+ op->data.image.vertical_stripes,
+ op->data.image.horizontal_stripes);
+ }
+ }
+ else
+ {
+ pixbuf = scale_and_alpha_pixbuf (op->data.image.pixbuf,
+ op->data.image.alpha_spec,
+ op->data.image.fill_type,
+ width, height,
+ op->data.image.vertical_stripes,
+ op->data.image.horizontal_stripes);
+ }
+ break;
+ }
+
+ case META_DRAW_GTK_ARROW:
+ case META_DRAW_GTK_BOX:
+ case META_DRAW_GTK_VLINE:
+ break;
+
+ case META_DRAW_ICON:
+ if (info->mini_icon &&
+ width <= gdk_pixbuf_get_width (info->mini_icon) &&
+ height <= gdk_pixbuf_get_height (info->mini_icon))
+ pixbuf = scale_and_alpha_pixbuf (info->mini_icon,
+ op->data.icon.alpha_spec,
+ op->data.icon.fill_type,
+ width, height,
+ FALSE, FALSE);
+ else if (info->icon)
+ pixbuf = scale_and_alpha_pixbuf (info->icon,
+ op->data.icon.alpha_spec,
+ op->data.icon.fill_type,
+ width, height,
+ FALSE, FALSE);
+ break;
+
+ case META_DRAW_TITLE:
+ break;
+
+ case META_DRAW_OP_LIST:
+ break;
+
+ case META_DRAW_TILE:
+ break;
+ }
+
+ return pixbuf;
+}
+
+static void
+fill_env (MetaPositionExprEnv *env,
+ const MetaDrawInfo *info,
+ MetaRectangle logical_region)
+{
+ /* FIXME this stuff could be raised into draw_op_list_draw() probably
+ */
+ env->rect = logical_region;
+ env->object_width = -1;
+ env->object_height = -1;
+ if (info->fgeom)
+ {
+ env->left_width = info->fgeom->left_width;
+ env->right_width = info->fgeom->right_width;
+ env->top_height = info->fgeom->top_height;
+ env->bottom_height = info->fgeom->bottom_height;
+ }
+ else
+ {
+ env->left_width = 0;
+ env->right_width = 0;
+ env->top_height = 0;
+ env->bottom_height = 0;
+ }
+
+ env->mini_icon_width = info->mini_icon ? gdk_pixbuf_get_width (info->mini_icon) : 0;
+ env->mini_icon_height = info->mini_icon ? gdk_pixbuf_get_height (info->mini_icon) : 0;
+ env->icon_width = info->icon ? gdk_pixbuf_get_width (info->icon) : 0;
+ env->icon_height = info->icon ? gdk_pixbuf_get_height (info->icon) : 0;
+
+ env->title_width = info->title_layout_width;
+ env->title_height = info->title_layout_height;
+ env->theme = meta_current_theme;
+}
+
+/* This code was originally rendering anti-aliased using X primitives, and
+ * now has been switched to draw anti-aliased using cairo. In general, the
+ * closest correspondence between X rendering and cairo rendering is given
+ * by offsetting the geometry by 0.5 pixels in both directions before rendering
+ * with cairo. This is because X samples at the upper left corner of the
+ * pixel while cairo averages over the entire pixel. However, in the cases
+ * where the X rendering was an exact rectangle with no "jaggies"
+ * we need to be a bit careful about applying the offset. We want to produce
+ * the exact same pixel-aligned rectangle, rather than a rectangle with
+ * fuzz around the edges.
+ */
+static void
+meta_draw_op_draw_with_env (const MetaDrawOp *op,
+ GtkStyle *style_gtk,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ MetaRectangle rect,
+ MetaPositionExprEnv *env)
+{
+ GdkColor color;
+ cairo_t *cr;
+
+ cr = gdk_cairo_create (drawable);
+
+ cairo_set_line_width (cr, 1.0);
+
+ if (clip)
+ {
+ gdk_cairo_rectangle (cr, clip);
+ cairo_clip (cr);
+ }
+
+ switch (op->type)
+ {
+ case META_DRAW_LINE:
+ {
+ int x1, x2, y1, y2;
+
+ meta_color_spec_render (op->data.line.color_spec, widget, &color);
+ gdk_cairo_set_source_color (cr, &color);
+
+ if (op->data.line.width > 0)
+ cairo_set_line_width (cr, op->data.line.width);
+
+ if (op->data.line.dash_on_length > 0 &&
+ op->data.line.dash_off_length > 0)
+ {
+ double dash_list[2];
+ dash_list[0] = op->data.line.dash_on_length;
+ dash_list[1] = op->data.line.dash_off_length;
+ cairo_set_dash (cr, dash_list, 2, 0);
+ }
+
+ x1 = parse_x_position_unchecked (op->data.line.x1, env);
+ y1 = parse_y_position_unchecked (op->data.line.y1, env);
+
+ if (!op->data.line.x2 &&
+ !op->data.line.y2 &&
+ op->data.line.width==0)
+ {
+ cairo_rectangle (cr, x1, y1, 1, 1);
+ cairo_fill (cr);
+ }
+ else
+ {
+ if (op->data.line.x2)
+ x2 = parse_x_position_unchecked (op->data.line.x2, env);
+ else
+ x2 = x1;
+
+ if (op->data.line.y2)
+ y2 = parse_y_position_unchecked (op->data.line.y2, env);
+ else
+ y2 = y1;
+
+ /* This is one of the cases where we are matching the exact
+ * pixel aligned rectangle produced by X.
+ */
+ if (y1 == y2 || x1 == x2)
+ {
+ double offset = (op->data.line.width == 0 ||
+ op->data.line.width % 2) ? .5 : 0;
+ /* X includes end points for lines of width 0 */
+ double line_extend = op->data.line.width == 0 ? 1. : 0.;
+
+ if (y1 == y2)
+ {
+ if (x2 < x1)
+ {
+ x1 ^= x2;
+ x2 ^= x1;
+ x1 ^= x2;
+ }
+ cairo_move_to (cr, x1, y1 + offset);
+ cairo_line_to (cr, x2 + line_extend, y2 + offset);
+ }
+ else
+ {
+ if (y2 < y1)
+ {
+ y1 ^= y2;
+ y2 ^= y1;
+ y1 ^= y2;
+ }
+ cairo_move_to (cr, x1 + offset, y1);
+ cairo_line_to (cr, x2 + offset, y2 + line_extend);
+ }
+ }
+ else
+ {
+ if (op->data.line.width <= 0)
+ {
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
+ }
+ cairo_move_to (cr, x1 + .5, y1 + .5);
+ cairo_line_to (cr, x2 + .5, y2 + .5);
+ }
+ cairo_stroke (cr);
+ }
+ }
+ break;
+
+ case META_DRAW_RECTANGLE:
+ {
+ int rx, ry, rwidth, rheight;
+
+ meta_color_spec_render (op->data.rectangle.color_spec, widget, &color);
+ gdk_cairo_set_source_color (cr, &color);
+
+ rx = parse_x_position_unchecked (op->data.rectangle.x, env);
+ ry = parse_y_position_unchecked (op->data.rectangle.y, env);
+ rwidth = parse_size_unchecked (op->data.rectangle.width, env);
+ rheight = parse_size_unchecked (op->data.rectangle.height, env);
+
+ /* Filled and stroked rectangles are the other cases
+ * we pixel-align to X rasterization
+ */
+ if (op->data.rectangle.filled)
+ {
+ cairo_rectangle (cr, rx, ry, rwidth, rheight);
+ cairo_fill (cr);
+ }
+ else
+ {
+ cairo_rectangle (cr, rx + .5, ry + .5, rwidth, rheight);
+ cairo_stroke (cr);
+ }
+ }
+ break;
+
+ case META_DRAW_ARC:
+ {
+ int rx, ry, rwidth, rheight;
+ double start_angle, end_angle;
+ double center_x, center_y;
+
+ meta_color_spec_render (op->data.arc.color_spec, widget, &color);
+ gdk_cairo_set_source_color (cr, &color);
+
+ rx = parse_x_position_unchecked (op->data.arc.x, env);
+ ry = parse_y_position_unchecked (op->data.arc.y, env);
+ rwidth = parse_size_unchecked (op->data.arc.width, env);
+ rheight = parse_size_unchecked (op->data.arc.height, env);
+
+ start_angle = op->data.arc.start_angle * (M_PI / 180.)
+ - (.25 * M_PI); /* start at 12 instead of 3 oclock */
+ end_angle = start_angle + op->data.arc.extent_angle * (M_PI / 180.);
+ center_x = rx + (double)rwidth / 2. + .5;
+ center_y = ry + (double)rheight / 2. + .5;
+
+ cairo_save (cr);
+
+ cairo_translate (cr, center_x, center_y);
+ cairo_scale (cr, (double)rwidth / 2., (double)rheight / 2.);
+
+ if (op->data.arc.extent_angle >= 0)
+ cairo_arc (cr, 0, 0, 1, start_angle, end_angle);
+ else
+ cairo_arc_negative (cr, 0, 0, 1, start_angle, end_angle);
+
+ cairo_restore (cr);
+
+ if (op->data.arc.filled)
+ {
+ cairo_line_to (cr, center_x, center_y);
+ cairo_fill (cr);
+ }
+ else
+ cairo_stroke (cr);
+ }
+ break;
+
+ case META_DRAW_CLIP:
+ break;
+
+ case META_DRAW_TINT:
+ {
+ int rx, ry, rwidth, rheight;
+ gboolean needs_alpha;
+
+ needs_alpha = op->data.tint.alpha_spec &&
+ (op->data.tint.alpha_spec->n_alphas > 1 ||
+ op->data.tint.alpha_spec->alphas[0] != 0xff);
+
+ rx = parse_x_position_unchecked (op->data.tint.x, env);
+ ry = parse_y_position_unchecked (op->data.tint.y, env);
+ rwidth = parse_size_unchecked (op->data.tint.width, env);
+ rheight = parse_size_unchecked (op->data.tint.height, env);
+
+ if (!needs_alpha)
+ {
+ meta_color_spec_render (op->data.tint.color_spec, widget, &color);
+ gdk_cairo_set_source_color (cr, &color);
+
+ cairo_rectangle (cr, rx, ry, rwidth, rheight);
+ cairo_fill (cr);
+ }
+ else
+ {
+ GdkPixbuf *pixbuf;
+
+ pixbuf = draw_op_as_pixbuf (op, widget, info,
+ rwidth, rheight);
+
+ if (pixbuf)
+ {
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry);
+ cairo_paint (cr);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ }
+ }
+ }
+ break;
+
+ case META_DRAW_GRADIENT:
+ {
+ int rx, ry, rwidth, rheight;
+ GdkPixbuf *pixbuf;
+
+ rx = parse_x_position_unchecked (op->data.gradient.x, env);
+ ry = parse_y_position_unchecked (op->data.gradient.y, env);
+ rwidth = parse_size_unchecked (op->data.gradient.width, env);
+ rheight = parse_size_unchecked (op->data.gradient.height, env);
+
+ pixbuf = draw_op_as_pixbuf (op, widget, info,
+ rwidth, rheight);
+
+ if (pixbuf)
+ {
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry);
+ cairo_paint (cr);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ }
+ }
+ break;
+
+ case META_DRAW_IMAGE:
+ {
+ int rx, ry, rwidth, rheight;
+ GdkPixbuf *pixbuf;
+
+ if (op->data.image.pixbuf)
+ {
+ env->object_width = gdk_pixbuf_get_width (op->data.image.pixbuf);
+ env->object_height = gdk_pixbuf_get_height (op->data.image.pixbuf);
+ }
+
+ rwidth = parse_size_unchecked (op->data.image.width, env);
+ rheight = parse_size_unchecked (op->data.image.height, env);
+
+ pixbuf = draw_op_as_pixbuf (op, widget, info,
+ rwidth, rheight);
+
+ if (pixbuf)
+ {
+ rx = parse_x_position_unchecked (op->data.image.x, env);
+ ry = parse_y_position_unchecked (op->data.image.y, env);
+
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry);
+ cairo_paint (cr);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ }
+ }
+ break;
+
+ case META_DRAW_GTK_ARROW:
+ {
+ int rx, ry, rwidth, rheight;
+
+ rx = parse_x_position_unchecked (op->data.gtk_arrow.x, env);
+ ry = parse_y_position_unchecked (op->data.gtk_arrow.y, env);
+ rwidth = parse_size_unchecked (op->data.gtk_arrow.width, env);
+ rheight = parse_size_unchecked (op->data.gtk_arrow.height, env);
+
+ gtk_paint_arrow (style_gtk,
+ drawable,
+ op->data.gtk_arrow.state,
+ op->data.gtk_arrow.shadow,
+ (GdkRectangle*) clip,
+ widget,
+ "marco",
+ op->data.gtk_arrow.arrow,
+ op->data.gtk_arrow.filled,
+ rx, ry, rwidth, rheight);
+ }
+ break;
+
+ case META_DRAW_GTK_BOX:
+ {
+ int rx, ry, rwidth, rheight;
+
+ rx = parse_x_position_unchecked (op->data.gtk_box.x, env);
+ ry = parse_y_position_unchecked (op->data.gtk_box.y, env);
+ rwidth = parse_size_unchecked (op->data.gtk_box.width, env);
+ rheight = parse_size_unchecked (op->data.gtk_box.height, env);
+
+ gtk_paint_box (style_gtk,
+ drawable,
+ op->data.gtk_box.state,
+ op->data.gtk_box.shadow,
+ (GdkRectangle*) clip,
+ widget,
+ "marco",
+ rx, ry, rwidth, rheight);
+ }
+ break;
+
+ case META_DRAW_GTK_VLINE:
+ {
+ int rx, ry1, ry2;
+
+ rx = parse_x_position_unchecked (op->data.gtk_vline.x, env);
+ ry1 = parse_y_position_unchecked (op->data.gtk_vline.y1, env);
+ ry2 = parse_y_position_unchecked (op->data.gtk_vline.y2, env);
+
+ gtk_paint_vline (style_gtk,
+ drawable,
+ op->data.gtk_vline.state,
+ (GdkRectangle*) clip,
+ widget,
+ "marco",
+ ry1, ry2, rx);
+ }
+ break;
+
+ case META_DRAW_ICON:
+ {
+ int rx, ry, rwidth, rheight;
+ GdkPixbuf *pixbuf;
+
+ rwidth = parse_size_unchecked (op->data.icon.width, env);
+ rheight = parse_size_unchecked (op->data.icon.height, env);
+
+ pixbuf = draw_op_as_pixbuf (op, widget, info,
+ rwidth, rheight);
+
+ if (pixbuf)
+ {
+ rx = parse_x_position_unchecked (op->data.icon.x, env);
+ ry = parse_y_position_unchecked (op->data.icon.y, env);
+
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry);
+ cairo_paint (cr);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ }
+ }
+ break;
+
+ case META_DRAW_TITLE:
+ if (info->title_layout)
+ {
+ int rx, ry;
+
+ meta_color_spec_render (op->data.title.color_spec, widget, &color);
+ gdk_cairo_set_source_color (cr, &color);
+
+ rx = parse_x_position_unchecked (op->data.title.x, env);
+ ry = parse_y_position_unchecked (op->data.title.y, env);
+
+ cairo_move_to (cr, rx, ry);
+ pango_cairo_show_layout (cr, info->title_layout);
+ }
+ break;
+
+ case META_DRAW_OP_LIST:
+ {
+ MetaRectangle d_rect;
+
+ d_rect.x = parse_x_position_unchecked (op->data.op_list.x, env);
+ d_rect.y = parse_y_position_unchecked (op->data.op_list.y, env);
+ d_rect.width = parse_size_unchecked (op->data.op_list.width, env);
+ d_rect.height = parse_size_unchecked (op->data.op_list.height, env);
+
+ meta_draw_op_list_draw_with_style (op->data.op_list.op_list,
+ style_gtk, widget, drawable, clip, info,
+ d_rect);
+ }
+ break;
+
+ case META_DRAW_TILE:
+ {
+ int rx, ry, rwidth, rheight;
+ int tile_xoffset, tile_yoffset;
+ GdkRectangle new_clip;
+ MetaRectangle tile;
+
+ rx = parse_x_position_unchecked (op->data.tile.x, env);
+ ry = parse_y_position_unchecked (op->data.tile.y, env);
+ rwidth = parse_size_unchecked (op->data.tile.width, env);
+ rheight = parse_size_unchecked (op->data.tile.height, env);
+
+ new_clip.x = rx;
+ new_clip.y = ry;
+ new_clip.width = rwidth;
+ new_clip.height = rheight;
+
+ if (clip == NULL || gdk_rectangle_intersect ((GdkRectangle*)clip, &new_clip,
+ &new_clip))
+ {
+ tile_xoffset = parse_x_position_unchecked (op->data.tile.tile_xoffset, env);
+ tile_yoffset = parse_y_position_unchecked (op->data.tile.tile_yoffset, env);
+ /* tile offset should not include x/y */
+ tile_xoffset -= rect.x;
+ tile_yoffset -= rect.y;
+
+ tile.width = parse_size_unchecked (op->data.tile.tile_width, env);
+ tile.height = parse_size_unchecked (op->data.tile.tile_height, env);
+
+ tile.x = rx - tile_xoffset;
+
+ while (tile.x < (rx + rwidth))
+ {
+ tile.y = ry - tile_yoffset;
+ while (tile.y < (ry + rheight))
+ {
+ meta_draw_op_list_draw_with_style (op->data.tile.op_list,
+ style_gtk, widget, drawable, &new_clip, info,
+ tile);
+
+ tile.y += tile.height;
+ }
+
+ tile.x += tile.width;
+ }
+ }
+ }
+ break;
+ }
+
+ cairo_destroy (cr);
+}
+
+void
+meta_draw_op_draw_with_style (const MetaDrawOp *op,
+ GtkStyle *style_gtk,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ MetaRectangle logical_region)
+{
+ MetaPositionExprEnv env;
+
+ g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable));
+
+ fill_env (&env, info, logical_region);
+
+ meta_draw_op_draw_with_env (op, style_gtk, widget, drawable, clip,
+ info, logical_region,
+ &env);
+
+}
+
+void
+meta_draw_op_draw (const MetaDrawOp *op,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ MetaRectangle logical_region)
+{
+ meta_draw_op_draw_with_style (op, gtk_widget_get_style (widget), widget,
+ drawable, clip, info, logical_region);
+}
+
+MetaDrawOpList*
+meta_draw_op_list_new (int n_preallocs)
+{
+ MetaDrawOpList *op_list;
+
+ g_return_val_if_fail (n_preallocs >= 0, NULL);
+
+ op_list = g_new (MetaDrawOpList, 1);
+
+ op_list->refcount = 1;
+ op_list->n_allocated = n_preallocs;
+ op_list->ops = g_new (MetaDrawOp*, op_list->n_allocated);
+ op_list->n_ops = 0;
+
+ return op_list;
+}
+
+void
+meta_draw_op_list_ref (MetaDrawOpList *op_list)
+{
+ g_return_if_fail (op_list != NULL);
+
+ op_list->refcount += 1;
+}
+
+void
+meta_draw_op_list_unref (MetaDrawOpList *op_list)
+{
+ g_return_if_fail (op_list != NULL);
+ g_return_if_fail (op_list->refcount > 0);
+
+ op_list->refcount -= 1;
+
+ if (op_list->refcount == 0)
+ {
+ int i;
+
+ for (i = 0; i < op_list->n_ops; i++)
+ meta_draw_op_free (op_list->ops[i]);
+
+ g_free (op_list->ops);
+
+ DEBUG_FILL_STRUCT (op_list);
+ g_free (op_list);
+ }
+}
+
+void
+meta_draw_op_list_draw_with_style (const MetaDrawOpList *op_list,
+ GtkStyle *style_gtk,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ MetaRectangle rect)
+{
+ int i;
+ GdkRectangle active_clip;
+ GdkRectangle orig_clip;
+ MetaPositionExprEnv env;
+
+ g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable));
+
+ if (op_list->n_ops == 0)
+ return;
+
+ fill_env (&env, info, rect);
+
+ /* FIXME this can be optimized, potentially a lot, by
+ * compressing multiple ops when possible. For example,
+ * anything convertible to a pixbuf can be composited
+ * client-side, and putting a color tint over a pixbuf
+ * can be done without creating the solid-color pixbuf.
+ *
+ * To implement this my plan is to have the idea of a
+ * compiled draw op (with the string expressions already
+ * evaluated), we make an array of those, and then fold
+ * adjacent items when possible.
+ */
+ if (clip)
+ {
+ orig_clip = *clip;
+ }
+ else
+ {
+ orig_clip.x = rect.x;
+ orig_clip.y = rect.y;
+ orig_clip.width = rect.width;
+ orig_clip.height = rect.height;
+ }
+
+ active_clip = orig_clip;
+
+ for (i = 0; i < op_list->n_ops; i++)
+ {
+ MetaDrawOp *op = op_list->ops[i];
+
+ if (op->type == META_DRAW_CLIP)
+ {
+ active_clip.x = parse_x_position_unchecked (op->data.clip.x, &env);
+ active_clip.y = parse_y_position_unchecked (op->data.clip.y, &env);
+ active_clip.width = parse_size_unchecked (op->data.clip.width, &env);
+ active_clip.height = parse_size_unchecked (op->data.clip.height, &env);
+
+ gdk_rectangle_intersect (&orig_clip, &active_clip, &active_clip);
+ }
+ else if (active_clip.width > 0 &&
+ active_clip.height > 0)
+ {
+ meta_draw_op_draw_with_env (op,
+ style_gtk, widget, drawable, &active_clip, info,
+ rect,
+ &env);
+ }
+ }
+}
+
+void
+meta_draw_op_list_draw (const MetaDrawOpList *op_list,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ MetaRectangle rect)
+
+{
+ meta_draw_op_list_draw_with_style (op_list, gtk_widget_get_style (widget), widget,
+ drawable, clip, info, rect);
+}
+
+void
+meta_draw_op_list_append (MetaDrawOpList *op_list,
+ MetaDrawOp *op)
+{
+ if (op_list->n_ops == op_list->n_allocated)
+ {
+ op_list->n_allocated *= 2;
+ op_list->ops = g_renew (MetaDrawOp*, op_list->ops, op_list->n_allocated);
+ }
+
+ op_list->ops[op_list->n_ops] = op;
+ op_list->n_ops += 1;
+}
+
+gboolean
+meta_draw_op_list_validate (MetaDrawOpList *op_list,
+ GError **error)
+{
+ g_return_val_if_fail (op_list != NULL, FALSE);
+
+ /* empty lists are OK, nothing else to check really */
+
+ return TRUE;
+}
+
+/* This is not done in validate, since we wouldn't know the name
+ * of the list to report the error. It might be nice to
+ * store names inside the list sometime.
+ */
+gboolean
+meta_draw_op_list_contains (MetaDrawOpList *op_list,
+ MetaDrawOpList *child)
+{
+ int i;
+
+ /* mmm, huge tree recursion */
+
+ for (i = 0; i < op_list->n_ops; i++)
+ {
+ if (op_list->ops[i]->type == META_DRAW_OP_LIST)
+ {
+ if (op_list->ops[i]->data.op_list.op_list == child)
+ return TRUE;
+
+ if (meta_draw_op_list_contains (op_list->ops[i]->data.op_list.op_list,
+ child))
+ return TRUE;
+ }
+ else if (op_list->ops[i]->type == META_DRAW_TILE)
+ {
+ if (op_list->ops[i]->data.tile.op_list == child)
+ return TRUE;
+
+ if (meta_draw_op_list_contains (op_list->ops[i]->data.tile.op_list,
+ child))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Constructor for a MetaFrameStyle.
+ *
+ * \param parent The parent style. Data not filled in here will be
+ * looked for in the parent style, and in its parent
+ * style, and so on.
+ *
+ * \return The newly-constructed style.
+ */
+MetaFrameStyle*
+meta_frame_style_new (MetaFrameStyle *parent)
+{
+ MetaFrameStyle *style;
+
+ style = g_new0 (MetaFrameStyle, 1);
+
+ style->refcount = 1;
+
+ /* Default alpha is fully opaque */
+ style->window_background_alpha = 255;
+
+ style->parent = parent;
+ if (parent)
+ meta_frame_style_ref (parent);
+
+ return style;
+}
+
+/**
+ * Increases the reference count of a frame style.
+ * If the style is NULL, this is a no-op.
+ *
+ * \param style The style.
+ */
+void
+meta_frame_style_ref (MetaFrameStyle *style)
+{
+ g_return_if_fail (style != NULL);
+
+ style->refcount += 1;
+}
+
+static void
+free_button_ops (MetaDrawOpList *op_lists[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST])
+{
+ int i, j;
+
+ for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
+ for (j = 0; j < META_BUTTON_STATE_LAST; j++)
+ if (op_lists[i][j])
+ meta_draw_op_list_unref (op_lists[i][j]);
+}
+
+void
+meta_frame_style_unref (MetaFrameStyle *style)
+{
+ g_return_if_fail (style != NULL);
+ g_return_if_fail (style->refcount > 0);
+
+ style->refcount -= 1;
+
+ if (style->refcount == 0)
+ {
+ int i;
+
+ free_button_ops (style->buttons);
+
+ for (i = 0; i < META_FRAME_PIECE_LAST; i++)
+ if (style->pieces[i])
+ meta_draw_op_list_unref (style->pieces[i]);
+
+ if (style->layout)
+ meta_frame_layout_unref (style->layout);
+
+ if (style->window_background_color)
+ meta_color_spec_free (style->window_background_color);
+
+ /* we hold a reference to any parent style */
+ if (style->parent)
+ meta_frame_style_unref (style->parent);
+
+ DEBUG_FILL_STRUCT (style);
+ g_free (style);
+ }
+}
+
+static MetaDrawOpList*
+get_button (MetaFrameStyle *style,
+ MetaButtonType type,
+ MetaButtonState state)
+{
+ MetaDrawOpList *op_list;
+ MetaFrameStyle *parent;
+
+ parent = style;
+ op_list = NULL;
+ while (parent && op_list == NULL)
+ {
+ op_list = parent->buttons[type][state];
+ parent = parent->parent;
+ }
+
+ /* We fall back to middle button backgrounds if we don't
+ * have the ones on the sides
+ */
+
+ if (op_list == NULL &&
+ (type == META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND ||
+ type == META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND))
+ return get_button (style, META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND,
+ state);
+
+ if (op_list == NULL &&
+ (type == META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND ||
+ type == META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND))
+ return get_button (style, META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND,
+ state);
+
+ /* We fall back to normal if no prelight */
+ if (op_list == NULL &&
+ state == META_BUTTON_STATE_PRELIGHT)
+ return get_button (style, type, META_BUTTON_STATE_NORMAL);
+
+ return op_list;
+}
+
+gboolean
+meta_frame_style_validate (MetaFrameStyle *style,
+ guint current_theme_version,
+ GError **error)
+{
+ int i, j;
+
+ g_return_val_if_fail (style != NULL, FALSE);
+ g_return_val_if_fail (style->layout != NULL, FALSE);
+
+ for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
+ {
+ /* for now the "positional" buttons are optional */
+ if (i >= META_BUTTON_TYPE_CLOSE)
+ {
+ for (j = 0; j < META_BUTTON_STATE_LAST; j++)
+ {
+ if (get_button (style, i, j) == NULL &&
+ meta_theme_earliest_version_with_button (i) <= current_theme_version
+ )
+ {
+ g_set_error (error, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("<button function=\"%s\" state=\"%s\" draw_ops=\"whatever\"/> must be specified for this frame style"),
+ meta_button_type_to_string (i),
+ meta_button_state_to_string (j));
+ return FALSE;
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+button_rect (MetaButtonType type,
+ const MetaFrameGeometry *fgeom,
+ int middle_background_offset,
+ GdkRectangle *rect)
+{
+ switch (type)
+ {
+ case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
+ *rect = fgeom->left_left_background;
+ break;
+
+ case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
+ *rect = fgeom->left_middle_backgrounds[middle_background_offset];
+ break;
+
+ case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
+ *rect = fgeom->left_right_background;
+ break;
+
+ case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
+ *rect = fgeom->right_left_background;
+ break;
+
+ case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
+ *rect = fgeom->right_middle_backgrounds[middle_background_offset];
+ break;
+
+ case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
+ *rect = fgeom->right_right_background;
+ break;
+
+ case META_BUTTON_TYPE_CLOSE:
+ *rect = fgeom->close_rect.visible;
+ break;
+
+ case META_BUTTON_TYPE_SHADE:
+ *rect = fgeom->shade_rect.visible;
+ break;
+
+ case META_BUTTON_TYPE_UNSHADE:
+ *rect = fgeom->unshade_rect.visible;
+ break;
+
+ case META_BUTTON_TYPE_ABOVE:
+ *rect = fgeom->above_rect.visible;
+ break;
+
+ case META_BUTTON_TYPE_UNABOVE:
+ *rect = fgeom->unabove_rect.visible;
+ break;
+
+ case META_BUTTON_TYPE_STICK:
+ *rect = fgeom->stick_rect.visible;
+ break;
+
+ case META_BUTTON_TYPE_UNSTICK:
+ *rect = fgeom->unstick_rect.visible;
+ break;
+
+ case META_BUTTON_TYPE_MAXIMIZE:
+ *rect = fgeom->max_rect.visible;
+ break;
+
+ case META_BUTTON_TYPE_MINIMIZE:
+ *rect = fgeom->min_rect.visible;
+ break;
+
+ case META_BUTTON_TYPE_MENU:
+ *rect = fgeom->menu_rect.visible;
+ break;
+
+ case META_BUTTON_TYPE_LAST:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+void
+meta_frame_style_draw_with_style (MetaFrameStyle *style,
+ GtkStyle *style_gtk,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ int x_offset,
+ int y_offset,
+ const GdkRectangle *clip,
+ const MetaFrameGeometry *fgeom,
+ int client_width,
+ int client_height,
+ PangoLayout *title_layout,
+ int text_height,
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST],
+ GdkPixbuf *mini_icon,
+ GdkPixbuf *icon)
+{
+ int i, j;
+ GdkRectangle titlebar_rect;
+ GdkRectangle left_titlebar_edge;
+ GdkRectangle right_titlebar_edge;
+ GdkRectangle bottom_titlebar_edge;
+ GdkRectangle top_titlebar_edge;
+ GdkRectangle left_edge, right_edge, bottom_edge;
+ PangoRectangle extents;
+ MetaDrawInfo draw_info;
+
+ g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable));
+
+ titlebar_rect.x = 0;
+ titlebar_rect.y = 0;
+ titlebar_rect.width = fgeom->width;
+ titlebar_rect.height = fgeom->top_height;
+
+ left_titlebar_edge.x = titlebar_rect.x;
+ left_titlebar_edge.y = titlebar_rect.y + fgeom->top_titlebar_edge;
+ left_titlebar_edge.width = fgeom->left_titlebar_edge;
+ left_titlebar_edge.height = titlebar_rect.height - fgeom->top_titlebar_edge - fgeom->bottom_titlebar_edge;
+
+ right_titlebar_edge.y = left_titlebar_edge.y;
+ right_titlebar_edge.height = left_titlebar_edge.height;
+ right_titlebar_edge.width = fgeom->right_titlebar_edge;
+ right_titlebar_edge.x = titlebar_rect.x + titlebar_rect.width - right_titlebar_edge.width;
+
+ top_titlebar_edge.x = titlebar_rect.x;
+ top_titlebar_edge.y = titlebar_rect.y;
+ top_titlebar_edge.width = titlebar_rect.width;
+ top_titlebar_edge.height = fgeom->top_titlebar_edge;
+
+ bottom_titlebar_edge.x = titlebar_rect.x;
+ bottom_titlebar_edge.width = titlebar_rect.width;
+ bottom_titlebar_edge.height = fgeom->bottom_titlebar_edge;
+ bottom_titlebar_edge.y = titlebar_rect.y + titlebar_rect.height - bottom_titlebar_edge.height;
+
+ left_edge.x = 0;
+ left_edge.y = fgeom->top_height;
+ left_edge.width = fgeom->left_width;
+ left_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height;
+
+ right_edge.x = fgeom->width - fgeom->right_width;
+ right_edge.y = fgeom->top_height;
+ right_edge.width = fgeom->right_width;
+ right_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height;
+
+ bottom_edge.x = 0;
+ bottom_edge.y = fgeom->height - fgeom->bottom_height;
+ bottom_edge.width = fgeom->width;
+ bottom_edge.height = fgeom->bottom_height;
+
+ if (title_layout)
+ pango_layout_get_pixel_extents (title_layout,
+ NULL, &extents);
+
+ draw_info.mini_icon = mini_icon;
+ draw_info.icon = icon;
+ draw_info.title_layout = title_layout;
+ draw_info.title_layout_width = title_layout ? extents.width : 0;
+ draw_info.title_layout_height = title_layout ? extents.height : 0;
+ draw_info.fgeom = fgeom;
+
+ /* The enum is in the order the pieces should be rendered. */
+ i = 0;
+ while (i < META_FRAME_PIECE_LAST)
+ {
+ GdkRectangle rect;
+ GdkRectangle combined_clip;
+
+ switch ((MetaFramePiece) i)
+ {
+ case META_FRAME_PIECE_ENTIRE_BACKGROUND:
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = fgeom->width;
+ rect.height = fgeom->height;
+ break;
+
+ case META_FRAME_PIECE_TITLEBAR:
+ rect = titlebar_rect;
+ break;
+
+ case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE:
+ rect = left_titlebar_edge;
+ break;
+
+ case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE:
+ rect = right_titlebar_edge;
+ break;
+
+ case META_FRAME_PIECE_TOP_TITLEBAR_EDGE:
+ rect = top_titlebar_edge;
+ break;
+
+ case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE:
+ rect = bottom_titlebar_edge;
+ break;
+
+ case META_FRAME_PIECE_TITLEBAR_MIDDLE:
+ rect.x = left_titlebar_edge.x + left_titlebar_edge.width;
+ rect.y = top_titlebar_edge.y + top_titlebar_edge.height;
+ rect.width = titlebar_rect.width - left_titlebar_edge.width -
+ right_titlebar_edge.width;
+ rect.height = titlebar_rect.height - top_titlebar_edge.height - bottom_titlebar_edge.height;
+ break;
+
+ case META_FRAME_PIECE_TITLE:
+ rect = fgeom->title_rect;
+ break;
+
+ case META_FRAME_PIECE_LEFT_EDGE:
+ rect = left_edge;
+ break;
+
+ case META_FRAME_PIECE_RIGHT_EDGE:
+ rect = right_edge;
+ break;
+
+ case META_FRAME_PIECE_BOTTOM_EDGE:
+ rect = bottom_edge;
+ break;
+
+ case META_FRAME_PIECE_OVERLAY:
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = fgeom->width;
+ rect.height = fgeom->height;
+ break;
+
+ case META_FRAME_PIECE_LAST:
+ g_assert_not_reached ();
+ break;
+ }
+
+ rect.x += x_offset;
+ rect.y += y_offset;
+
+ if (clip == NULL)
+ combined_clip = rect;
+ else
+ gdk_rectangle_intersect ((GdkRectangle*) clip, /* const cast */
+ &rect,
+ &combined_clip);
+
+ if (combined_clip.width > 0 && combined_clip.height > 0)
+ {
+ MetaDrawOpList *op_list;
+ MetaFrameStyle *parent;
+
+ parent = style;
+ op_list = NULL;
+ while (parent && op_list == NULL)
+ {
+ op_list = parent->pieces[i];
+ parent = parent->parent;
+ }
+
+ if (op_list)
+ {
+ MetaRectangle m_rect;
+ m_rect = meta_rect (rect.x, rect.y, rect.width, rect.height);
+ meta_draw_op_list_draw_with_style (op_list,
+ style_gtk,
+ widget,
+ drawable,
+ &combined_clip,
+ &draw_info,
+ m_rect);
+ }
+ }
+
+
+ /* Draw buttons just before overlay */
+ if ((i + 1) == META_FRAME_PIECE_OVERLAY)
+ {
+ int middle_bg_offset;
+
+ middle_bg_offset = 0;
+ j = 0;
+ while (j < META_BUTTON_TYPE_LAST)
+ {
+ button_rect (j, fgeom, middle_bg_offset, &rect);
+
+ rect.x += x_offset;
+ rect.y += y_offset;
+
+ if (clip == NULL)
+ combined_clip = rect;
+ else
+ gdk_rectangle_intersect ((GdkRectangle*) clip, /* const cast */
+ &rect,
+ &combined_clip);
+
+ if (combined_clip.width > 0 && combined_clip.height > 0)
+ {
+ MetaDrawOpList *op_list;
+
+ op_list = get_button (style, j, button_states[j]);
+
+ if (op_list)
+ {
+ MetaRectangle m_rect;
+ m_rect = meta_rect (rect.x, rect.y,
+ rect.width, rect.height);
+ meta_draw_op_list_draw_with_style (op_list,
+ style_gtk,
+ widget,
+ drawable,
+ &combined_clip,
+ &draw_info,
+ m_rect);
+ }
+ }
+
+ /* MIDDLE_BACKGROUND type may get drawn more than once */
+ if ((j == META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND ||
+ j == META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND) &&
+ middle_bg_offset < MAX_MIDDLE_BACKGROUNDS)
+ {
+ ++middle_bg_offset;
+ }
+ else
+ {
+ middle_bg_offset = 0;
+ ++j;
+ }
+ }
+ }
+
+ ++i;
+ }
+}
+
+void
+meta_frame_style_draw (MetaFrameStyle *style,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ int x_offset,
+ int y_offset,
+ const GdkRectangle *clip,
+ const MetaFrameGeometry *fgeom,
+ int client_width,
+ int client_height,
+ PangoLayout *title_layout,
+ int text_height,
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST],
+ GdkPixbuf *mini_icon,
+ GdkPixbuf *icon)
+{
+ meta_frame_style_draw_with_style (style, gtk_widget_get_style (widget), widget,
+ drawable, x_offset, y_offset,
+ clip, fgeom, client_width, client_height,
+ title_layout, text_height,
+ button_states, mini_icon, icon);
+}
+
+MetaFrameStyleSet*
+meta_frame_style_set_new (MetaFrameStyleSet *parent)
+{
+ MetaFrameStyleSet *style_set;
+
+ style_set = g_new0 (MetaFrameStyleSet, 1);
+
+ style_set->parent = parent;
+ if (parent)
+ meta_frame_style_set_ref (parent);
+
+ style_set->refcount = 1;
+
+ return style_set;
+}
+
+static void
+free_focus_styles (MetaFrameStyle *focus_styles[META_FRAME_FOCUS_LAST])
+{
+ int i;
+
+ for (i = 0; i < META_FRAME_FOCUS_LAST; i++)
+ if (focus_styles[i])
+ meta_frame_style_unref (focus_styles[i]);
+}
+
+void
+meta_frame_style_set_ref (MetaFrameStyleSet *style_set)
+{
+ g_return_if_fail (style_set != NULL);
+
+ style_set->refcount += 1;
+}
+
+void
+meta_frame_style_set_unref (MetaFrameStyleSet *style_set)
+{
+ g_return_if_fail (style_set != NULL);
+ g_return_if_fail (style_set->refcount > 0);
+
+ style_set->refcount -= 1;
+
+ if (style_set->refcount == 0)
+ {
+ int i;
+
+ for (i = 0; i < META_FRAME_RESIZE_LAST; i++)
+ {
+ free_focus_styles (style_set->normal_styles[i]);
+ free_focus_styles (style_set->shaded_styles[i]);
+ }
+
+ free_focus_styles (style_set->maximized_styles);
+ free_focus_styles (style_set->maximized_and_shaded_styles);
+
+ if (style_set->parent)
+ meta_frame_style_set_unref (style_set->parent);
+
+ DEBUG_FILL_STRUCT (style_set);
+ g_free (style_set);
+ }
+}
+
+
+static MetaFrameStyle*
+get_style (MetaFrameStyleSet *style_set,
+ MetaFrameState state,
+ MetaFrameResize resize,
+ MetaFrameFocus focus)
+{
+ MetaFrameStyle *style;
+
+ style = NULL;
+
+ switch (state)
+ {
+ case META_FRAME_STATE_NORMAL:
+ case META_FRAME_STATE_SHADED:
+ {
+ if (state == META_FRAME_STATE_SHADED)
+ style = style_set->shaded_styles[resize][focus];
+ else
+ style = style_set->normal_styles[resize][focus];
+
+ /* Try parent if we failed here */
+ if (style == NULL && style_set->parent)
+ style = get_style (style_set->parent, state, resize, focus);
+
+ /* Allow people to omit the vert/horz/none resize modes */
+ if (style == NULL &&
+ resize != META_FRAME_RESIZE_BOTH)
+ style = get_style (style_set, state, META_FRAME_RESIZE_BOTH, focus);
+ }
+ break;
+ default:
+ {
+ MetaFrameStyle **styles;
+
+ styles = NULL;
+
+ switch (state)
+ {
+ case META_FRAME_STATE_MAXIMIZED:
+ styles = style_set->maximized_styles;
+ break;
+ case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
+ styles = style_set->maximized_and_shaded_styles;
+ break;
+ case META_FRAME_STATE_NORMAL:
+ case META_FRAME_STATE_SHADED:
+ case META_FRAME_STATE_LAST:
+ g_assert_not_reached ();
+ break;
+ }
+
+ style = styles[focus];
+
+ /* Try parent if we failed here */
+ if (style == NULL && style_set->parent)
+ style = get_style (style_set->parent, state, resize, focus);
+ }
+ }
+
+ return style;
+}
+
+static gboolean
+check_state (MetaFrameStyleSet *style_set,
+ MetaFrameState state,
+ GError **error)
+{
+ int i;
+
+ for (i = 0; i < META_FRAME_FOCUS_LAST; i++)
+ {
+ if (get_style (style_set, state,
+ META_FRAME_RESIZE_NONE, i) == NULL)
+ {
+ /* Translators: This error occurs when a <frame> tag is missing
+ * in theme XML. The "<frame ...>" is intended as a noun phrase,
+ * and the "missing" qualifies it. You should translate "whatever".
+ */
+ g_set_error (error, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"),
+ meta_frame_state_to_string (state),
+ meta_frame_resize_to_string (META_FRAME_RESIZE_NONE),
+ meta_frame_focus_to_string (i));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+meta_frame_style_set_validate (MetaFrameStyleSet *style_set,
+ GError **error)
+{
+ int i, j;
+
+ g_return_val_if_fail (style_set != NULL, FALSE);
+
+ for (i = 0; i < META_FRAME_RESIZE_LAST; i++)
+ for (j = 0; j < META_FRAME_FOCUS_LAST; j++)
+ if (get_style (style_set, META_FRAME_STATE_NORMAL, i, j) == NULL)
+ {
+ g_set_error (error, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"),
+ meta_frame_state_to_string (META_FRAME_STATE_NORMAL),
+ meta_frame_resize_to_string (i),
+ meta_frame_focus_to_string (j));
+ return FALSE;
+ }
+
+ if (!check_state (style_set, META_FRAME_STATE_SHADED, error))
+ return FALSE;
+
+ if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED, error))
+ return FALSE;
+
+ if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED_AND_SHADED, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+MetaTheme*
+meta_theme_get_current (void)
+{
+ return meta_current_theme;
+}
+
+void
+meta_theme_set_current (const char *name,
+ gboolean force_reload)
+{
+ MetaTheme *new_theme;
+ GError *err;
+
+ meta_topic (META_DEBUG_THEMES, "Setting current theme to \"%s\"\n", name);
+
+ if (!force_reload &&
+ meta_current_theme &&
+ strcmp (name, meta_current_theme->name) == 0)
+ return;
+
+ err = NULL;
+ new_theme = meta_theme_load (name, &err);
+
+ if (new_theme == NULL)
+ {
+ meta_warning (_("Failed to load theme \"%s\": %s\n"),
+ name, err->message);
+ g_error_free (err);
+ }
+ else
+ {
+ if (meta_current_theme)
+ meta_theme_free (meta_current_theme);
+
+ meta_current_theme = new_theme;
+
+ meta_topic (META_DEBUG_THEMES, "New theme is \"%s\"\n", meta_current_theme->name);
+ }
+}
+
+MetaTheme*
+meta_theme_new (void)
+{
+ MetaTheme *theme;
+
+ theme = g_new0 (MetaTheme, 1);
+
+ theme->images_by_filename =
+ g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) g_object_unref);
+
+ theme->layouts_by_name =
+ g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) meta_frame_layout_unref);
+
+ theme->draw_op_lists_by_name =
+ g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) meta_draw_op_list_unref);
+
+ theme->styles_by_name =
+ g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) meta_frame_style_unref);
+
+ theme->style_sets_by_name =
+ g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) meta_frame_style_set_unref);
+
+ /* Create our variable quarks so we can look up variables without
+ having to strcmp for the names */
+ theme->quark_width = g_quark_from_static_string ("width");
+ theme->quark_height = g_quark_from_static_string ("height");
+ theme->quark_object_width = g_quark_from_static_string ("object_width");
+ theme->quark_object_height = g_quark_from_static_string ("object_height");
+ theme->quark_left_width = g_quark_from_static_string ("left_width");
+ theme->quark_right_width = g_quark_from_static_string ("right_width");
+ theme->quark_top_height = g_quark_from_static_string ("top_height");
+ theme->quark_bottom_height = g_quark_from_static_string ("bottom_height");
+ theme->quark_mini_icon_width = g_quark_from_static_string ("mini_icon_width");
+ theme->quark_mini_icon_height = g_quark_from_static_string ("mini_icon_height");
+ theme->quark_icon_width = g_quark_from_static_string ("icon_width");
+ theme->quark_icon_height = g_quark_from_static_string ("icon_height");
+ theme->quark_title_width = g_quark_from_static_string ("title_width");
+ theme->quark_title_height = g_quark_from_static_string ("title_height");
+ return theme;
+}
+
+
+void
+meta_theme_free (MetaTheme *theme)
+{
+ int i;
+
+ g_return_if_fail (theme != NULL);
+
+ g_free (theme->name);
+ g_free (theme->dirname);
+ g_free (theme->filename);
+ g_free (theme->readable_name);
+ g_free (theme->date);
+ g_free (theme->description);
+ g_free (theme->author);
+ g_free (theme->copyright);
+
+ /* be more careful when destroying the theme hash tables,
+ since they are only constructed as needed, and may be NULL. */
+ if (theme->integer_constants)
+ g_hash_table_destroy (theme->integer_constants);
+ if (theme->images_by_filename)
+ g_hash_table_destroy (theme->images_by_filename);
+ if (theme->layouts_by_name)
+ g_hash_table_destroy (theme->layouts_by_name);
+ if (theme->draw_op_lists_by_name)
+ g_hash_table_destroy (theme->draw_op_lists_by_name);
+ if (theme->styles_by_name)
+ g_hash_table_destroy (theme->styles_by_name);
+ if (theme->style_sets_by_name)
+ g_hash_table_destroy (theme->style_sets_by_name);
+
+ for (i = 0; i < META_FRAME_TYPE_LAST; i++)
+ if (theme->style_sets_by_type[i])
+ meta_frame_style_set_unref (theme->style_sets_by_type[i]);
+
+ DEBUG_FILL_STRUCT (theme);
+ g_free (theme);
+}
+
+gboolean
+meta_theme_validate (MetaTheme *theme,
+ GError **error)
+{
+ int i;
+
+ g_return_val_if_fail (theme != NULL, FALSE);
+
+ /* FIXME what else should be checked? */
+
+ g_assert (theme->name);
+
+ if (theme->readable_name == NULL)
+ {
+ /* Translators: This error means that a necessary XML tag (whose name
+ * is given in angle brackets) was not found in a given theme (whose
+ * name is given second, in quotation marks).
+ */
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("No <%s> set for theme \"%s\""), "name", theme->name);
+ return FALSE;
+ }
+
+ if (theme->author == NULL)
+ {
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("No <%s> set for theme \"%s\""), "author", theme->name);
+ return FALSE;
+ }
+
+ if (theme->date == NULL)
+ {
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("No <%s> set for theme \"%s\""), "date", theme->name);
+ return FALSE;
+ }
+
+ if (theme->description == NULL)
+ {
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("No <%s> set for theme \"%s\""), "description", theme->name);
+ return FALSE;
+ }
+
+ if (theme->copyright == NULL)
+ {
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("No <%s> set for theme \"%s\""), "copyright", theme->name);
+ return FALSE;
+ }
+
+ for (i = 0; i < (int)META_FRAME_TYPE_LAST; i++)
+ if (theme->style_sets_by_type[i] == NULL)
+ {
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("No frame style set for window type \"%s\" in theme \"%s\", add a <window type=\"%s\" style_set=\"whatever\"/> element"),
+ meta_frame_type_to_string (i),
+ theme->name,
+ meta_frame_type_to_string (i));
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+GdkPixbuf*
+meta_theme_load_image (MetaTheme *theme,
+ const char *filename,
+ guint size_of_theme_icons,
+ GError **error)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = g_hash_table_lookup (theme->images_by_filename,
+ filename);
+
+ if (pixbuf == NULL)
+ {
+
+ if (g_str_has_prefix (filename, "theme:") &&
+ META_THEME_ALLOWS (theme, META_THEME_IMAGES_FROM_ICON_THEMES))
+ {
+ pixbuf = gtk_icon_theme_load_icon (
+ gtk_icon_theme_get_default (),
+ filename+6,
+ size_of_theme_icons,
+ 0,
+ error);
+ if (pixbuf == NULL) return NULL;
+ }
+ else
+ {
+ char *full_path;
+ full_path = g_build_filename (theme->dirname, filename, NULL);
+
+ pixbuf = gdk_pixbuf_new_from_file (full_path, error);
+ if (pixbuf == NULL)
+ {
+ g_free (full_path);
+ return NULL;
+ }
+
+ g_free (full_path);
+ }
+ g_hash_table_replace (theme->images_by_filename,
+ g_strdup (filename),
+ pixbuf);
+ }
+
+ g_assert (pixbuf);
+
+ g_object_ref (G_OBJECT (pixbuf));
+
+ return pixbuf;
+}
+
+static MetaFrameStyle*
+theme_get_style (MetaTheme *theme,
+ MetaFrameType type,
+ MetaFrameFlags flags)
+{
+ MetaFrameState state;
+ MetaFrameResize resize;
+ MetaFrameFocus focus;
+ MetaFrameStyle *style;
+ MetaFrameStyleSet *style_set;
+
+ style_set = theme->style_sets_by_type[type];
+
+ /* Right now the parser forces a style set for all types,
+ * but this fallback code is here in case I take that out.
+ */
+ if (style_set == NULL)
+ style_set = theme->style_sets_by_type[META_FRAME_TYPE_NORMAL];
+ if (style_set == NULL)
+ return NULL;
+
+ switch (flags & (META_FRAME_MAXIMIZED | META_FRAME_SHADED))
+ {
+ case 0:
+ state = META_FRAME_STATE_NORMAL;
+ break;
+ case META_FRAME_MAXIMIZED:
+ state = META_FRAME_STATE_MAXIMIZED;
+ break;
+ case META_FRAME_SHADED:
+ state = META_FRAME_STATE_SHADED;
+ break;
+ case (META_FRAME_MAXIMIZED | META_FRAME_SHADED):
+ state = META_FRAME_STATE_MAXIMIZED_AND_SHADED;
+ break;
+ default:
+ g_assert_not_reached ();
+ state = META_FRAME_STATE_LAST; /* compiler */
+ break;
+ }
+
+ switch (flags & (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE))
+ {
+ case 0:
+ resize = META_FRAME_RESIZE_NONE;
+ break;
+ case META_FRAME_ALLOWS_VERTICAL_RESIZE:
+ resize = META_FRAME_RESIZE_VERTICAL;
+ break;
+ case META_FRAME_ALLOWS_HORIZONTAL_RESIZE:
+ resize = META_FRAME_RESIZE_HORIZONTAL;
+ break;
+ case (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE):
+ resize = META_FRAME_RESIZE_BOTH;
+ break;
+ default:
+ g_assert_not_reached ();
+ resize = META_FRAME_RESIZE_LAST; /* compiler */
+ break;
+ }
+
+ /* re invert the styles used for focus/unfocussed while flashing a frame */
+ if (((flags & META_FRAME_HAS_FOCUS) && !(flags & META_FRAME_IS_FLASHING))
+ || (!(flags & META_FRAME_HAS_FOCUS) && (flags & META_FRAME_IS_FLASHING)))
+ focus = META_FRAME_FOCUS_YES;
+ else
+ focus = META_FRAME_FOCUS_NO;
+
+ style = get_style (style_set, state, resize, focus);
+
+ return style;
+}
+
+MetaFrameStyle*
+meta_theme_get_frame_style (MetaTheme *theme,
+ MetaFrameType type,
+ MetaFrameFlags flags)
+{
+ MetaFrameStyle *style;
+
+ g_return_val_if_fail (type < META_FRAME_TYPE_LAST, NULL);
+
+ style = theme_get_style (theme, type, flags);
+
+ return style;
+}
+
+double
+meta_theme_get_title_scale (MetaTheme *theme,
+ MetaFrameType type,
+ MetaFrameFlags flags)
+{
+ MetaFrameStyle *style;
+
+ g_return_val_if_fail (type < META_FRAME_TYPE_LAST, 1.0);
+
+ style = theme_get_style (theme, type, flags);
+
+ /* Parser is not supposed to allow this currently */
+ if (style == NULL)
+ return 1.0;
+
+ return style->layout->title_scale;
+}
+
+void
+meta_theme_draw_frame_with_style (MetaTheme *theme,
+ GtkStyle *style_gtk,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ int x_offset,
+ int y_offset,
+ MetaFrameType type,
+ MetaFrameFlags flags,
+ int client_width,
+ int client_height,
+ PangoLayout *title_layout,
+ int text_height,
+ const MetaButtonLayout *button_layout,
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST],
+ GdkPixbuf *mini_icon,
+ GdkPixbuf *icon)
+{
+ MetaFrameGeometry fgeom;
+ MetaFrameStyle *style;
+
+ g_return_if_fail (type < META_FRAME_TYPE_LAST);
+
+ style = theme_get_style (theme, type, flags);
+
+ /* Parser is not supposed to allow this currently */
+ if (style == NULL)
+ return;
+
+ meta_frame_layout_calc_geometry (style->layout,
+ text_height,
+ flags,
+ client_width, client_height,
+ button_layout,
+ &fgeom,
+ theme);
+
+ meta_frame_style_draw_with_style (style,
+ style_gtk,
+ widget,
+ drawable,
+ x_offset, y_offset,
+ clip,
+ &fgeom,
+ client_width, client_height,
+ title_layout,
+ text_height,
+ button_states,
+ mini_icon, icon);
+}
+
+void
+meta_theme_draw_frame (MetaTheme *theme,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ int x_offset,
+ int y_offset,
+ MetaFrameType type,
+ MetaFrameFlags flags,
+ int client_width,
+ int client_height,
+ PangoLayout *title_layout,
+ int text_height,
+ const MetaButtonLayout *button_layout,
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST],
+ GdkPixbuf *mini_icon,
+ GdkPixbuf *icon)
+{
+ meta_theme_draw_frame_with_style (theme, gtk_widget_get_style (widget), widget,
+ drawable, clip, x_offset, y_offset, type,flags,
+ client_width, client_height,
+ title_layout, text_height,
+ button_layout, button_states,
+ mini_icon, icon);
+}
+
+void
+meta_theme_draw_frame_by_name (MetaTheme *theme,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ int x_offset,
+ int y_offset,
+ const gchar *style_name,
+ MetaFrameFlags flags,
+ int client_width,
+ int client_height,
+ PangoLayout *title_layout,
+ int text_height,
+ const MetaButtonLayout *button_layout,
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST],
+ GdkPixbuf *mini_icon,
+ GdkPixbuf *icon)
+{
+ MetaFrameGeometry fgeom;
+ MetaFrameStyle *style;
+
+ style = meta_theme_lookup_style (theme, style_name);
+
+ /* Parser is not supposed to allow this currently */
+ if (style == NULL)
+ return;
+
+ meta_frame_layout_calc_geometry (style->layout,
+ text_height,
+ flags,
+ client_width, client_height,
+ button_layout,
+ &fgeom,
+ theme);
+
+ meta_frame_style_draw (style,
+ widget,
+ drawable,
+ x_offset, y_offset,
+ clip,
+ &fgeom,
+ client_width, client_height,
+ title_layout,
+ text_height,
+ button_states,
+ mini_icon, icon);
+}
+
+void
+meta_theme_get_frame_borders (MetaTheme *theme,
+ MetaFrameType type,
+ int text_height,
+ MetaFrameFlags flags,
+ int *top_height,
+ int *bottom_height,
+ int *left_width,
+ int *right_width)
+{
+ MetaFrameStyle *style;
+
+ g_return_if_fail (type < META_FRAME_TYPE_LAST);
+
+ if (top_height)
+ *top_height = 0;
+ if (bottom_height)
+ *bottom_height = 0;
+ if (left_width)
+ *left_width = 0;
+ if (right_width)
+ *right_width = 0;
+
+ style = theme_get_style (theme, type, flags);
+
+ /* Parser is not supposed to allow this currently */
+ if (style == NULL)
+ return;
+
+ meta_frame_layout_get_borders (style->layout,
+ text_height,
+ flags,
+ top_height, bottom_height,
+ left_width, right_width);
+}
+
+void
+meta_theme_calc_geometry (MetaTheme *theme,
+ MetaFrameType type,
+ int text_height,
+ MetaFrameFlags flags,
+ int client_width,
+ int client_height,
+ const MetaButtonLayout *button_layout,
+ MetaFrameGeometry *fgeom)
+{
+ MetaFrameStyle *style;
+
+ g_return_if_fail (type < META_FRAME_TYPE_LAST);
+
+ style = theme_get_style (theme, type, flags);
+
+ /* Parser is not supposed to allow this currently */
+ if (style == NULL)
+ return;
+
+ meta_frame_layout_calc_geometry (style->layout,
+ text_height,
+ flags,
+ client_width, client_height,
+ button_layout,
+ fgeom,
+ theme);
+}
+
+MetaFrameLayout*
+meta_theme_lookup_layout (MetaTheme *theme,
+ const char *name)
+{
+ return g_hash_table_lookup (theme->layouts_by_name, name);
+}
+
+void
+meta_theme_insert_layout (MetaTheme *theme,
+ const char *name,
+ MetaFrameLayout *layout)
+{
+ meta_frame_layout_ref (layout);
+ g_hash_table_replace (theme->layouts_by_name, g_strdup (name), layout);
+}
+
+MetaDrawOpList*
+meta_theme_lookup_draw_op_list (MetaTheme *theme,
+ const char *name)
+{
+ return g_hash_table_lookup (theme->draw_op_lists_by_name, name);
+}
+
+void
+meta_theme_insert_draw_op_list (MetaTheme *theme,
+ const char *name,
+ MetaDrawOpList *op_list)
+{
+ meta_draw_op_list_ref (op_list);
+ g_hash_table_replace (theme->draw_op_lists_by_name, g_strdup (name), op_list);
+}
+
+MetaFrameStyle*
+meta_theme_lookup_style (MetaTheme *theme,
+ const char *name)
+{
+ return g_hash_table_lookup (theme->styles_by_name, name);
+}
+
+void
+meta_theme_insert_style (MetaTheme *theme,
+ const char *name,
+ MetaFrameStyle *style)
+{
+ meta_frame_style_ref (style);
+ g_hash_table_replace (theme->styles_by_name, g_strdup (name), style);
+}
+
+MetaFrameStyleSet*
+meta_theme_lookup_style_set (MetaTheme *theme,
+ const char *name)
+{
+ return g_hash_table_lookup (theme->style_sets_by_name, name);
+}
+
+void
+meta_theme_insert_style_set (MetaTheme *theme,
+ const char *name,
+ MetaFrameStyleSet *style_set)
+{
+ meta_frame_style_set_ref (style_set);
+ g_hash_table_replace (theme->style_sets_by_name, g_strdup (name), style_set);
+}
+
+static gboolean
+first_uppercase (const char *str)
+{
+ return g_ascii_isupper (*str);
+}
+
+gboolean
+meta_theme_define_int_constant (MetaTheme *theme,
+ const char *name,
+ int value,
+ GError **error)
+{
+ if (theme->integer_constants == NULL)
+ theme->integer_constants = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+
+ if (!first_uppercase (name))
+ {
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("User-defined constants must begin with a capital letter; \"%s\" does not"),
+ name);
+ return FALSE;
+ }
+
+ if (g_hash_table_lookup_extended (theme->integer_constants, name, NULL, NULL))
+ {
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("Constant \"%s\" has already been defined"),
+ name);
+
+ return FALSE;
+ }
+
+ g_hash_table_insert (theme->integer_constants,
+ g_strdup (name),
+ GINT_TO_POINTER (value));
+
+ return TRUE;
+}
+
+gboolean
+meta_theme_lookup_int_constant (MetaTheme *theme,
+ const char *name,
+ int *value)
+{
+ gpointer old_value;
+
+ *value = 0;
+
+ if (theme->integer_constants == NULL)
+ return FALSE;
+
+ if (g_hash_table_lookup_extended (theme->integer_constants,
+ name, NULL, &old_value))
+ {
+ *value = GPOINTER_TO_INT (old_value);
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+gboolean
+meta_theme_define_float_constant (MetaTheme *theme,
+ const char *name,
+ double value,
+ GError **error)
+{
+ double *d;
+
+ if (theme->float_constants == NULL)
+ theme->float_constants = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_free);
+
+ if (!first_uppercase (name))
+ {
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("User-defined constants must begin with a capital letter; \"%s\" does not"),
+ name);
+ return FALSE;
+ }
+
+ if (g_hash_table_lookup_extended (theme->float_constants, name, NULL, NULL))
+ {
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("Constant \"%s\" has already been defined"),
+ name);
+
+ return FALSE;
+ }
+
+ d = g_new (double, 1);
+ *d = value;
+
+ g_hash_table_insert (theme->float_constants,
+ g_strdup (name), d);
+
+ return TRUE;
+}
+
+gboolean
+meta_theme_lookup_float_constant (MetaTheme *theme,
+ const char *name,
+ double *value)
+{
+ double *d;
+
+ *value = 0.0;
+
+ if (theme->float_constants == NULL)
+ return FALSE;
+
+ d = g_hash_table_lookup (theme->float_constants, name);
+
+ if (d)
+ {
+ *value = *d;
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+gboolean
+meta_theme_define_color_constant (MetaTheme *theme,
+ const char *name,
+ const char *value,
+ GError **error)
+{
+ if (theme->color_constants == NULL)
+ theme->color_constants = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+
+ if (!first_uppercase (name))
+ {
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("User-defined constants must begin with a capital letter; \"%s\" does not"),
+ name);
+ return FALSE;
+ }
+
+ if (g_hash_table_lookup_extended (theme->color_constants, name, NULL, NULL))
+ {
+ g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
+ _("Constant \"%s\" has already been defined"),
+ name);
+
+ return FALSE;
+ }
+
+ g_hash_table_insert (theme->color_constants,
+ g_strdup (name),
+ g_strdup (value));
+
+ return TRUE;
+}
+
+/**
+ * Looks up a colour constant.
+ *
+ * \param theme the theme containing the constant
+ * \param name the name of the constant
+ * \param value [out] the string representation of the colour, or NULL if it
+ * doesn't exist
+ * \return TRUE if it exists, FALSE otherwise
+ */
+gboolean
+meta_theme_lookup_color_constant (MetaTheme *theme,
+ const char *name,
+ char **value)
+{
+ char *result;
+
+ *value = NULL;
+
+ if (theme->color_constants == NULL)
+ return FALSE;
+
+ result = g_hash_table_lookup (theme->color_constants, name);
+
+ if (result)
+ {
+ *value = result;
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+
+PangoFontDescription*
+meta_gtk_widget_get_font_desc (GtkWidget *widget,
+ double scale,
+ const PangoFontDescription *override)
+{
+ PangoFontDescription *font_desc;
+
+ g_return_val_if_fail (gtk_widget_get_realized (widget), NULL);
+
+ font_desc = pango_font_description_copy (gtk_widget_get_style (widget)->font_desc);
+
+ if (override)
+ pango_font_description_merge (font_desc, override, TRUE);
+
+ pango_font_description_set_size (font_desc,
+ MAX (pango_font_description_get_size (font_desc) * scale, 1));
+
+ return font_desc;
+}
+
+/**
+ * Returns the height of the letters in a particular font.
+ *
+ * \param font_desc the font
+ * \param context the context of the font
+ * \return the height of the letters
+ */
+int
+meta_pango_font_desc_get_text_height (const PangoFontDescription *font_desc,
+ PangoContext *context)
+{
+ PangoFontMetrics *metrics;
+ PangoLanguage *lang;
+ int retval;
+
+ lang = pango_context_get_language (context);
+ metrics = pango_context_get_metrics (context, font_desc, lang);
+
+ retval = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) +
+ pango_font_metrics_get_descent (metrics));
+
+ pango_font_metrics_unref (metrics);
+
+ return retval;
+}
+
+MetaGtkColorComponent
+meta_color_component_from_string (const char *str)
+{
+ if (strcmp ("fg", str) == 0)
+ return META_GTK_COLOR_FG;
+ else if (strcmp ("bg", str) == 0)
+ return META_GTK_COLOR_BG;
+ else if (strcmp ("light", str) == 0)
+ return META_GTK_COLOR_LIGHT;
+ else if (strcmp ("dark", str) == 0)
+ return META_GTK_COLOR_DARK;
+ else if (strcmp ("mid", str) == 0)
+ return META_GTK_COLOR_MID;
+ else if (strcmp ("text", str) == 0)
+ return META_GTK_COLOR_TEXT;
+ else if (strcmp ("base", str) == 0)
+ return META_GTK_COLOR_BASE;
+ else if (strcmp ("text_aa", str) == 0)
+ return META_GTK_COLOR_TEXT_AA;
+ else
+ return META_GTK_COLOR_LAST;
+}
+
+const char*
+meta_color_component_to_string (MetaGtkColorComponent component)
+{
+ switch (component)
+ {
+ case META_GTK_COLOR_FG:
+ return "fg";
+ case META_GTK_COLOR_BG:
+ return "bg";
+ case META_GTK_COLOR_LIGHT:
+ return "light";
+ case META_GTK_COLOR_DARK:
+ return "dark";
+ case META_GTK_COLOR_MID:
+ return "mid";
+ case META_GTK_COLOR_TEXT:
+ return "text";
+ case META_GTK_COLOR_BASE:
+ return "base";
+ case META_GTK_COLOR_TEXT_AA:
+ return "text_aa";
+ case META_GTK_COLOR_LAST:
+ break;
+ }
+
+ return "<unknown>";
+}
+
+MetaButtonState
+meta_button_state_from_string (const char *str)
+{
+ if (strcmp ("normal", str) == 0)
+ return META_BUTTON_STATE_NORMAL;
+ else if (strcmp ("pressed", str) == 0)
+ return META_BUTTON_STATE_PRESSED;
+ else if (strcmp ("prelight", str) == 0)
+ return META_BUTTON_STATE_PRELIGHT;
+ else
+ return META_BUTTON_STATE_LAST;
+}
+
+const char*
+meta_button_state_to_string (MetaButtonState state)
+{
+ switch (state)
+ {
+ case META_BUTTON_STATE_NORMAL:
+ return "normal";
+ case META_BUTTON_STATE_PRESSED:
+ return "pressed";
+ case META_BUTTON_STATE_PRELIGHT:
+ return "prelight";
+ case META_BUTTON_STATE_LAST:
+ break;
+ }
+
+ return "<unknown>";
+}
+
+MetaButtonType
+meta_button_type_from_string (const char *str, MetaTheme *theme)
+{
+ if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS))
+ {
+ if (strcmp ("shade", str) == 0)
+ return META_BUTTON_TYPE_SHADE;
+ else if (strcmp ("above", str) == 0)
+ return META_BUTTON_TYPE_ABOVE;
+ else if (strcmp ("stick", str) == 0)
+ return META_BUTTON_TYPE_STICK;
+ else if (strcmp ("unshade", str) == 0)
+ return META_BUTTON_TYPE_UNSHADE;
+ else if (strcmp ("unabove", str) == 0)
+ return META_BUTTON_TYPE_UNABOVE;
+ else if (strcmp ("unstick", str) == 0)
+ return META_BUTTON_TYPE_UNSTICK;
+ }
+
+ if (strcmp ("close", str) == 0)
+ return META_BUTTON_TYPE_CLOSE;
+ else if (strcmp ("maximize", str) == 0)
+ return META_BUTTON_TYPE_MAXIMIZE;
+ else if (strcmp ("minimize", str) == 0)
+ return META_BUTTON_TYPE_MINIMIZE;
+ else if (strcmp ("menu", str) == 0)
+ return META_BUTTON_TYPE_MENU;
+ else if (strcmp ("left_left_background", str) == 0)
+ return META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND;
+ else if (strcmp ("left_middle_background", str) == 0)
+ return META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND;
+ else if (strcmp ("left_right_background", str) == 0)
+ return META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND;
+ else if (strcmp ("right_left_background", str) == 0)
+ return META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND;
+ else if (strcmp ("right_middle_background", str) == 0)
+ return META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND;
+ else if (strcmp ("right_right_background", str) == 0)
+ return META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND;
+ else
+ return META_BUTTON_TYPE_LAST;
+}
+
+const char*
+meta_button_type_to_string (MetaButtonType type)
+{
+ switch (type)
+ {
+ case META_BUTTON_TYPE_CLOSE:
+ return "close";
+ case META_BUTTON_TYPE_MAXIMIZE:
+ return "maximize";
+ case META_BUTTON_TYPE_MINIMIZE:
+ return "minimize";
+ case META_BUTTON_TYPE_SHADE:
+ return "shade";
+ case META_BUTTON_TYPE_ABOVE:
+ return "above";
+ case META_BUTTON_TYPE_STICK:
+ return "stick";
+ case META_BUTTON_TYPE_UNSHADE:
+ return "unshade";
+ case META_BUTTON_TYPE_UNABOVE:
+ return "unabove";
+ case META_BUTTON_TYPE_UNSTICK:
+ return "unstick";
+ case META_BUTTON_TYPE_MENU:
+ return "menu";
+ case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
+ return "left_left_background";
+ case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
+ return "left_middle_background";
+ case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
+ return "left_right_background";
+ case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
+ return "right_left_background";
+ case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
+ return "right_middle_background";
+ case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
+ return "right_right_background";
+ case META_BUTTON_TYPE_LAST:
+ break;
+ }
+
+ return "<unknown>";
+}
+
+MetaFramePiece
+meta_frame_piece_from_string (const char *str)
+{
+ if (strcmp ("entire_background", str) == 0)
+ return META_FRAME_PIECE_ENTIRE_BACKGROUND;
+ else if (strcmp ("titlebar", str) == 0)
+ return META_FRAME_PIECE_TITLEBAR;
+ else if (strcmp ("titlebar_middle", str) == 0)
+ return META_FRAME_PIECE_TITLEBAR_MIDDLE;
+ else if (strcmp ("left_titlebar_edge", str) == 0)
+ return META_FRAME_PIECE_LEFT_TITLEBAR_EDGE;
+ else if (strcmp ("right_titlebar_edge", str) == 0)
+ return META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE;
+ else if (strcmp ("top_titlebar_edge", str) == 0)
+ return META_FRAME_PIECE_TOP_TITLEBAR_EDGE;
+ else if (strcmp ("bottom_titlebar_edge", str) == 0)
+ return META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE;
+ else if (strcmp ("title", str) == 0)
+ return META_FRAME_PIECE_TITLE;
+ else if (strcmp ("left_edge", str) == 0)
+ return META_FRAME_PIECE_LEFT_EDGE;
+ else if (strcmp ("right_edge", str) == 0)
+ return META_FRAME_PIECE_RIGHT_EDGE;
+ else if (strcmp ("bottom_edge", str) == 0)
+ return META_FRAME_PIECE_BOTTOM_EDGE;
+ else if (strcmp ("overlay", str) == 0)
+ return META_FRAME_PIECE_OVERLAY;
+ else
+ return META_FRAME_PIECE_LAST;
+}
+
+const char*
+meta_frame_piece_to_string (MetaFramePiece piece)
+{
+ switch (piece)
+ {
+ case META_FRAME_PIECE_ENTIRE_BACKGROUND:
+ return "entire_background";
+ case META_FRAME_PIECE_TITLEBAR:
+ return "titlebar";
+ case META_FRAME_PIECE_TITLEBAR_MIDDLE:
+ return "titlebar_middle";
+ case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE:
+ return "left_titlebar_edge";
+ case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE:
+ return "right_titlebar_edge";
+ case META_FRAME_PIECE_TOP_TITLEBAR_EDGE:
+ return "top_titlebar_edge";
+ case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE:
+ return "bottom_titlebar_edge";
+ case META_FRAME_PIECE_TITLE:
+ return "title";
+ case META_FRAME_PIECE_LEFT_EDGE:
+ return "left_edge";
+ case META_FRAME_PIECE_RIGHT_EDGE:
+ return "right_edge";
+ case META_FRAME_PIECE_BOTTOM_EDGE:
+ return "bottom_edge";
+ case META_FRAME_PIECE_OVERLAY:
+ return "overlay";
+ case META_FRAME_PIECE_LAST:
+ break;
+ }
+
+ return "<unknown>";
+}
+
+MetaFrameState
+meta_frame_state_from_string (const char *str)
+{
+ if (strcmp ("normal", str) == 0)
+ return META_FRAME_STATE_NORMAL;
+ else if (strcmp ("maximized", str) == 0)
+ return META_FRAME_STATE_MAXIMIZED;
+ else if (strcmp ("shaded", str) == 0)
+ return META_FRAME_STATE_SHADED;
+ else if (strcmp ("maximized_and_shaded", str) == 0)
+ return META_FRAME_STATE_MAXIMIZED_AND_SHADED;
+ else
+ return META_FRAME_STATE_LAST;
+}
+
+const char*
+meta_frame_state_to_string (MetaFrameState state)
+{
+ switch (state)
+ {
+ case META_FRAME_STATE_NORMAL:
+ return "normal";
+ case META_FRAME_STATE_MAXIMIZED:
+ return "maximized";
+ case META_FRAME_STATE_SHADED:
+ return "shaded";
+ case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
+ return "maximized_and_shaded";
+ case META_FRAME_STATE_LAST:
+ break;
+ }
+
+ return "<unknown>";
+}
+
+MetaFrameResize
+meta_frame_resize_from_string (const char *str)
+{
+ if (strcmp ("none", str) == 0)
+ return META_FRAME_RESIZE_NONE;
+ else if (strcmp ("vertical", str) == 0)
+ return META_FRAME_RESIZE_VERTICAL;
+ else if (strcmp ("horizontal", str) == 0)
+ return META_FRAME_RESIZE_HORIZONTAL;
+ else if (strcmp ("both", str) == 0)
+ return META_FRAME_RESIZE_BOTH;
+ else
+ return META_FRAME_RESIZE_LAST;
+}
+
+const char*
+meta_frame_resize_to_string (MetaFrameResize resize)
+{
+ switch (resize)
+ {
+ case META_FRAME_RESIZE_NONE:
+ return "none";
+ case META_FRAME_RESIZE_VERTICAL:
+ return "vertical";
+ case META_FRAME_RESIZE_HORIZONTAL:
+ return "horizontal";
+ case META_FRAME_RESIZE_BOTH:
+ return "both";
+ case META_FRAME_RESIZE_LAST:
+ break;
+ }
+
+ return "<unknown>";
+}
+
+MetaFrameFocus
+meta_frame_focus_from_string (const char *str)
+{
+ if (strcmp ("no", str) == 0)
+ return META_FRAME_FOCUS_NO;
+ else if (strcmp ("yes", str) == 0)
+ return META_FRAME_FOCUS_YES;
+ else
+ return META_FRAME_FOCUS_LAST;
+}
+
+const char*
+meta_frame_focus_to_string (MetaFrameFocus focus)
+{
+ switch (focus)
+ {
+ case META_FRAME_FOCUS_NO:
+ return "no";
+ case META_FRAME_FOCUS_YES:
+ return "yes";
+ case META_FRAME_FOCUS_LAST:
+ break;
+ }
+
+ return "<unknown>";
+}
+
+MetaFrameType
+meta_frame_type_from_string (const char *str)
+{
+ if (strcmp ("normal", str) == 0)
+ return META_FRAME_TYPE_NORMAL;
+ else if (strcmp ("dialog", str) == 0)
+ return META_FRAME_TYPE_DIALOG;
+ else if (strcmp ("modal_dialog", str) == 0)
+ return META_FRAME_TYPE_MODAL_DIALOG;
+ else if (strcmp ("utility", str) == 0)
+ return META_FRAME_TYPE_UTILITY;
+ else if (strcmp ("menu", str) == 0)
+ return META_FRAME_TYPE_MENU;
+ else if (strcmp ("border", str) == 0)
+ return META_FRAME_TYPE_BORDER;
+#if 0
+ else if (strcmp ("toolbar", str) == 0)
+ return META_FRAME_TYPE_TOOLBAR;
+#endif
+ else
+ return META_FRAME_TYPE_LAST;
+}
+
+const char*
+meta_frame_type_to_string (MetaFrameType type)
+{
+ switch (type)
+ {
+ case META_FRAME_TYPE_NORMAL:
+ return "normal";
+ case META_FRAME_TYPE_DIALOG:
+ return "dialog";
+ case META_FRAME_TYPE_MODAL_DIALOG:
+ return "modal_dialog";
+ case META_FRAME_TYPE_UTILITY:
+ return "utility";
+ case META_FRAME_TYPE_MENU:
+ return "menu";
+ case META_FRAME_TYPE_BORDER:
+ return "border";
+#if 0
+ case META_FRAME_TYPE_TOOLBAR:
+ return "toolbar";
+#endif
+ case META_FRAME_TYPE_LAST:
+ break;
+ }
+
+ return "<unknown>";
+}
+
+MetaGradientType
+meta_gradient_type_from_string (const char *str)
+{
+ if (strcmp ("vertical", str) == 0)
+ return META_GRADIENT_VERTICAL;
+ else if (strcmp ("horizontal", str) == 0)
+ return META_GRADIENT_HORIZONTAL;
+ else if (strcmp ("diagonal", str) == 0)
+ return META_GRADIENT_DIAGONAL;
+ else
+ return META_GRADIENT_LAST;
+}
+
+const char*
+meta_gradient_type_to_string (MetaGradientType type)
+{
+ switch (type)
+ {
+ case META_GRADIENT_VERTICAL:
+ return "vertical";
+ case META_GRADIENT_HORIZONTAL:
+ return "horizontal";
+ case META_GRADIENT_DIAGONAL:
+ return "diagonal";
+ case META_GRADIENT_LAST:
+ break;
+ }
+
+ return "<unknown>";
+}
+
+GtkStateType
+meta_gtk_state_from_string (const char *str)
+{
+ if (strcmp ("normal", str) == 0 || strcmp ("NORMAL", str) == 0)
+ return GTK_STATE_NORMAL;
+ else if (strcmp ("prelight", str) == 0 || strcmp ("PRELIGHT", str) == 0)
+ return GTK_STATE_PRELIGHT;
+ else if (strcmp ("active", str) == 0 || strcmp ("ACTIVE", str) == 0)
+ return GTK_STATE_ACTIVE;
+ else if (strcmp ("selected", str) == 0 || strcmp ("SELECTED", str) == 0)
+ return GTK_STATE_SELECTED;
+ else if (strcmp ("insensitive", str) == 0 || strcmp ("INSENSITIVE", str) == 0)
+ return GTK_STATE_INSENSITIVE;
+ else
+ return -1; /* hack */
+}
+
+const char*
+meta_gtk_state_to_string (GtkStateType state)
+{
+ switch (state)
+ {
+ case GTK_STATE_NORMAL:
+ return "NORMAL";
+ case GTK_STATE_PRELIGHT:
+ return "PRELIGHT";
+ case GTK_STATE_ACTIVE:
+ return "ACTIVE";
+ case GTK_STATE_SELECTED:
+ return "SELECTED";
+ case GTK_STATE_INSENSITIVE:
+ return "INSENSITIVE";
+ }
+
+ return "<unknown>";
+}
+
+GtkShadowType
+meta_gtk_shadow_from_string (const char *str)
+{
+ if (strcmp ("none", str) == 0)
+ return GTK_SHADOW_NONE;
+ else if (strcmp ("in", str) == 0)
+ return GTK_SHADOW_IN;
+ else if (strcmp ("out", str) == 0)
+ return GTK_SHADOW_OUT;
+ else if (strcmp ("etched_in", str) == 0)
+ return GTK_SHADOW_ETCHED_IN;
+ else if (strcmp ("etched_out", str) == 0)
+ return GTK_SHADOW_ETCHED_OUT;
+ else
+ return -1;
+}
+
+const char*
+meta_gtk_shadow_to_string (GtkShadowType shadow)
+{
+ switch (shadow)
+ {
+ case GTK_SHADOW_NONE:
+ return "none";
+ case GTK_SHADOW_IN:
+ return "in";
+ case GTK_SHADOW_OUT:
+ return "out";
+ case GTK_SHADOW_ETCHED_IN:
+ return "etched_in";
+ case GTK_SHADOW_ETCHED_OUT:
+ return "etched_out";
+ }
+
+ return "<unknown>";
+}
+
+GtkArrowType
+meta_gtk_arrow_from_string (const char *str)
+{
+ if (strcmp ("up", str) == 0)
+ return GTK_ARROW_UP;
+ else if (strcmp ("down", str) == 0)
+ return GTK_ARROW_DOWN;
+ else if (strcmp ("left", str) == 0)
+ return GTK_ARROW_LEFT;
+ else if (strcmp ("right", str) == 0)
+ return GTK_ARROW_RIGHT;
+ else if (strcmp ("none", str) == 0)
+ return GTK_ARROW_NONE;
+ else
+ return -1;
+}
+
+const char*
+meta_gtk_arrow_to_string (GtkArrowType arrow)
+{
+ switch (arrow)
+ {
+ case GTK_ARROW_UP:
+ return "up";
+ case GTK_ARROW_DOWN:
+ return "down";
+ case GTK_ARROW_LEFT:
+ return "left";
+ case GTK_ARROW_RIGHT:
+ return "right";
+ case GTK_ARROW_NONE:
+ return "none";
+ }
+
+ return "<unknown>";
+}
+
+/**
+ * Returns a fill_type from a string. The inverse of
+ * meta_image_fill_type_to_string().
+ *
+ * \param str a string representing a fill_type
+ * \result the fill_type, or -1 if it represents no fill_type.
+ */
+MetaImageFillType
+meta_image_fill_type_from_string (const char *str)
+{
+ if (strcmp ("tile", str) == 0)
+ return META_IMAGE_FILL_TILE;
+ else if (strcmp ("scale", str) == 0)
+ return META_IMAGE_FILL_SCALE;
+ else
+ return -1;
+}
+
+/**
+ * Returns a string representation of a fill_type. The inverse of
+ * meta_image_fill_type_from_string().
+ *
+ * \param fill_type the fill type
+ * \result a string representing that type
+ */
+const char*
+meta_image_fill_type_to_string (MetaImageFillType fill_type)
+{
+ switch (fill_type)
+ {
+ case META_IMAGE_FILL_TILE:
+ return "tile";
+ case META_IMAGE_FILL_SCALE:
+ return "scale";
+ }
+
+ return "<unknown>";
+}
+
+/**
+ * Takes a colour "a", scales the lightness and saturation by a certain amount,
+ * and sets "b" to the resulting colour.
+ * gtkstyle.c cut-and-pastage.
+ *
+ * \param a the starting colour
+ * \param b [out] the resulting colour
+ * \param k amount to scale lightness and saturation by
+ */
+static void
+gtk_style_shade (GdkColor *a,
+ GdkColor *b,
+ gdouble k)
+{
+ gdouble red;
+ gdouble green;
+ gdouble blue;
+
+ red = (gdouble) a->red / 65535.0;
+ green = (gdouble) a->green / 65535.0;
+ blue = (gdouble) a->blue / 65535.0;
+
+ rgb_to_hls (&red, &green, &blue);
+
+ green *= k;
+ if (green > 1.0)
+ green = 1.0;
+ else if (green < 0.0)
+ green = 0.0;
+
+ blue *= k;
+ if (blue > 1.0)
+ blue = 1.0;
+ else if (blue < 0.0)
+ blue = 0.0;
+
+ hls_to_rgb (&red, &green, &blue);
+
+ b->red = red * 65535.0;
+ b->green = green * 65535.0;
+ b->blue = blue * 65535.0;
+}
+
+/**
+ * Converts a red/green/blue triplet to a hue/lightness/saturation triplet.
+ *
+ * \param r on input, red; on output, hue
+ * \param g on input, green; on output, lightness
+ * \param b on input, blue; on output, saturation
+ */
+static void
+rgb_to_hls (gdouble *r,
+ gdouble *g,
+ gdouble *b)
+{
+ gdouble min;
+ gdouble max;
+ gdouble red;
+ gdouble green;
+ gdouble blue;
+ gdouble h, l, s;
+ gdouble delta;
+
+ red = *r;
+ green = *g;
+ blue = *b;
+
+ if (red > green)
+ {
+ if (red > blue)
+ max = red;
+ else
+ max = blue;
+
+ if (green < blue)
+ min = green;
+ else
+ min = blue;
+ }
+ else
+ {
+ if (green > blue)
+ max = green;
+ else
+ max = blue;
+
+ if (red < blue)
+ min = red;
+ else
+ min = blue;
+ }
+
+ l = (max + min) / 2;
+ s = 0;
+ h = 0;
+
+ if (max != min)
+ {
+ if (l <= 0.5)
+ s = (max - min) / (max + min);
+ else
+ s = (max - min) / (2 - max - min);
+
+ delta = max -min;
+ if (red == max)
+ h = (green - blue) / delta;
+ else if (green == max)
+ h = 2 + (blue - red) / delta;
+ else if (blue == max)
+ h = 4 + (red - green) / delta;
+
+ h *= 60;
+ if (h < 0.0)
+ h += 360;
+ }
+
+ *r = h;
+ *g = l;
+ *b = s;
+}
+
+/**
+ * Converts a hue/lightness/saturation triplet to a red/green/blue triplet.
+ *
+ * \param h on input, hue; on output, red
+ * \param l on input, lightness; on output, green
+ * \param s on input, saturation; on output, blue
+ */
+static void
+hls_to_rgb (gdouble *h,
+ gdouble *l,
+ gdouble *s)
+{
+ gdouble hue;
+ gdouble lightness;
+ gdouble saturation;
+ gdouble m1, m2;
+ gdouble r, g, b;
+
+ lightness = *l;
+ saturation = *s;
+
+ if (lightness <= 0.5)
+ m2 = lightness * (1 + saturation);
+ else
+ m2 = lightness + saturation - lightness * saturation;
+ m1 = 2 * lightness - m2;
+
+ if (saturation == 0)
+ {
+ *h = lightness;
+ *l = lightness;
+ *s = lightness;
+ }
+ else
+ {
+ hue = *h + 120;
+ while (hue > 360)
+ hue -= 360;
+ while (hue < 0)
+ hue += 360;
+
+ if (hue < 60)
+ r = m1 + (m2 - m1) * hue / 60;
+ else if (hue < 180)
+ r = m2;
+ else if (hue < 240)
+ r = m1 + (m2 - m1) * (240 - hue) / 60;
+ else
+ r = m1;
+
+ hue = *h;
+ while (hue > 360)
+ hue -= 360;
+ while (hue < 0)
+ hue += 360;
+
+ if (hue < 60)
+ g = m1 + (m2 - m1) * hue / 60;
+ else if (hue < 180)
+ g = m2;
+ else if (hue < 240)
+ g = m1 + (m2 - m1) * (240 - hue) / 60;
+ else
+ g = m1;
+
+ hue = *h - 120;
+ while (hue > 360)
+ hue -= 360;
+ while (hue < 0)
+ hue += 360;
+
+ if (hue < 60)
+ b = m1 + (m2 - m1) * hue / 60;
+ else if (hue < 180)
+ b = m2;
+ else if (hue < 240)
+ b = m1 + (m2 - m1) * (240 - hue) / 60;
+ else
+ b = m1;
+
+ *h = r;
+ *l = g;
+ *s = b;
+ }
+}
+
+#if 0
+/* These are some functions I'm saving to use in optimizing
+ * MetaDrawOpList, namely to pre-composite pixbufs on client side
+ * prior to rendering to the server
+ */
+static void
+draw_bg_solid_composite (const MetaTextureSpec *bg,
+ const MetaTextureSpec *fg,
+ double alpha,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ MetaTextureDrawMode mode,
+ double xalign,
+ double yalign,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ GdkColor bg_color;
+
+ g_assert (bg->type == META_TEXTURE_SOLID);
+ g_assert (fg->type != META_TEXTURE_COMPOSITE);
+ g_assert (fg->type != META_TEXTURE_SHAPE_LIST);
+
+ meta_color_spec_render (bg->data.solid.color_spec,
+ widget,
+ &bg_color);
+
+ switch (fg->type)
+ {
+ case META_TEXTURE_SOLID:
+ {
+ GdkColor fg_color;
+
+ meta_color_spec_render (fg->data.solid.color_spec,
+ widget,
+ &fg_color);
+
+ color_composite (&bg_color, &fg_color,
+ alpha, &fg_color);
+
+ draw_color_rectangle (widget, drawable, &fg_color, clip,
+ x, y, width, height);
+ }
+ break;
+
+ case META_TEXTURE_GRADIENT:
+ /* FIXME I think we could just composite all the colors in
+ * the gradient prior to generating the gradient?
+ */
+ /* FALL THRU */
+ case META_TEXTURE_IMAGE:
+ {
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *composited;
+
+ pixbuf = meta_texture_spec_render (fg, widget, mode, 255,
+ width, height);
+
+ if (pixbuf == NULL)
+ return;
+
+ composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ gdk_pixbuf_get_has_alpha (pixbuf), 8,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+
+ if (composited == NULL)
+ {
+ g_object_unref (G_OBJECT (pixbuf));
+ return;
+ }
+
+ gdk_pixbuf_composite_color (pixbuf,
+ composited,
+ 0, 0,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ 0.0, 0.0, /* offsets */
+ 1.0, 1.0, /* scale */
+ GDK_INTERP_BILINEAR,
+ 255 * alpha,
+ 0, 0, /* check offsets */
+ 0, /* check size */
+ GDK_COLOR_RGB (bg_color),
+ GDK_COLOR_RGB (bg_color));
+
+ /* Need to draw background since pixbuf is not
+ * necessarily covering the whole thing
+ */
+ draw_color_rectangle (widget, drawable, &bg_color, clip,
+ x, y, width, height);
+
+ render_pixbuf_aligned (drawable, clip, composited,
+ xalign, yalign,
+ x, y, width, height);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ g_object_unref (G_OBJECT (composited));
+ }
+ break;
+
+ case META_TEXTURE_BLANK:
+ case META_TEXTURE_COMPOSITE:
+ case META_TEXTURE_SHAPE_LIST:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+draw_bg_gradient_composite (const MetaTextureSpec *bg,
+ const MetaTextureSpec *fg,
+ double alpha,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ MetaTextureDrawMode mode,
+ double xalign,
+ double yalign,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ g_assert (bg->type == META_TEXTURE_GRADIENT);
+ g_assert (fg->type != META_TEXTURE_COMPOSITE);
+ g_assert (fg->type != META_TEXTURE_SHAPE_LIST);
+
+ switch (fg->type)
+ {
+ case META_TEXTURE_SOLID:
+ case META_TEXTURE_GRADIENT:
+ case META_TEXTURE_IMAGE:
+ {
+ GdkPixbuf *bg_pixbuf;
+ GdkPixbuf *fg_pixbuf;
+ GdkPixbuf *composited;
+ int fg_width, fg_height;
+
+ bg_pixbuf = meta_texture_spec_render (bg, widget, mode, 255,
+ width, height);
+
+ if (bg_pixbuf == NULL)
+ return;
+
+ fg_pixbuf = meta_texture_spec_render (fg, widget, mode, 255,
+ width, height);
+
+ if (fg_pixbuf == NULL)
+ {
+ g_object_unref (G_OBJECT (bg_pixbuf));
+ return;
+ }
+
+ /* gradients always fill the entire target area */
+ g_assert (gdk_pixbuf_get_width (bg_pixbuf) == width);
+ g_assert (gdk_pixbuf_get_height (bg_pixbuf) == height);
+
+ composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ gdk_pixbuf_get_has_alpha (bg_pixbuf), 8,
+ gdk_pixbuf_get_width (bg_pixbuf),
+ gdk_pixbuf_get_height (bg_pixbuf));
+
+ if (composited == NULL)
+ {
+ g_object_unref (G_OBJECT (bg_pixbuf));
+ g_object_unref (G_OBJECT (fg_pixbuf));
+ return;
+ }
+
+ fg_width = gdk_pixbuf_get_width (fg_pixbuf);
+ fg_height = gdk_pixbuf_get_height (fg_pixbuf);
+
+ /* If we wanted to be all cool we could deal with the
+ * offsets and try to composite only in the clip rectangle,
+ * but I just don't care enough to figure it out.
+ */
+
+ gdk_pixbuf_composite (fg_pixbuf,
+ composited,
+ x + (width - fg_width) * xalign,
+ y + (height - fg_height) * yalign,
+ gdk_pixbuf_get_width (fg_pixbuf),
+ gdk_pixbuf_get_height (fg_pixbuf),
+ 0.0, 0.0, /* offsets */
+ 1.0, 1.0, /* scale */
+ GDK_INTERP_BILINEAR,
+ 255 * alpha);
+
+ gdk_cairo_set_source_pixbuf (cr, composited, x, y);
+ cairo_paint (cr);
+
+ g_object_unref (G_OBJECT (bg_pixbuf));
+ g_object_unref (G_OBJECT (fg_pixbuf));
+ g_object_unref (G_OBJECT (composited));
+ }
+ break;
+
+ case META_TEXTURE_BLANK:
+ case META_TEXTURE_SHAPE_LIST:
+ case META_TEXTURE_COMPOSITE:
+ g_assert_not_reached ();
+ break;
+ }
+}
+#endif
+
+/**
+ * Returns the earliest version of the theme format which required support
+ * for a particular button. (For example, "shade" first appeared in v2, and
+ * "close" in v1.)
+ *
+ * \param type the button type
+ * \return the number of the theme format
+ */
+guint
+meta_theme_earliest_version_with_button (MetaButtonType type)
+{
+ switch (type)
+ {
+ case META_BUTTON_TYPE_CLOSE:
+ case META_BUTTON_TYPE_MAXIMIZE:
+ case META_BUTTON_TYPE_MINIMIZE:
+ case META_BUTTON_TYPE_MENU:
+ case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
+ case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
+ case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
+ case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
+ case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
+ case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
+ return 1;
+
+ case META_BUTTON_TYPE_SHADE:
+ case META_BUTTON_TYPE_ABOVE:
+ case META_BUTTON_TYPE_STICK:
+ case META_BUTTON_TYPE_UNSHADE:
+ case META_BUTTON_TYPE_UNABOVE:
+ case META_BUTTON_TYPE_UNSTICK:
+ return 2;
+
+ default:
+ meta_warning("Unknown button %d\n", type);
+ return 1;
+ }
+}
diff --git a/src/ui/theme.h b/src/ui/theme.h
new file mode 100644
index 00000000..4df8b00e
--- /dev/null
+++ b/src/ui/theme.h
@@ -0,0 +1,1190 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco Theme Rendering */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_THEME_H
+#define META_THEME_H
+
+#include "boxes.h"
+#include "gradient.h"
+#include "common.h"
+#include <gtk/gtk.h>
+
+typedef struct _MetaFrameStyle MetaFrameStyle;
+typedef struct _MetaFrameStyleSet MetaFrameStyleSet;
+typedef struct _MetaDrawOp MetaDrawOp;
+typedef struct _MetaDrawOpList MetaDrawOpList;
+typedef struct _MetaGradientSpec MetaGradientSpec;
+typedef struct _MetaAlphaGradientSpec MetaAlphaGradientSpec;
+typedef struct _MetaColorSpec MetaColorSpec;
+typedef struct _MetaFrameLayout MetaFrameLayout;
+typedef struct _MetaButtonSpace MetaButtonSpace;
+typedef struct _MetaFrameGeometry MetaFrameGeometry;
+typedef struct _MetaTheme MetaTheme;
+typedef struct _MetaPositionExprEnv MetaPositionExprEnv;
+typedef struct _MetaDrawInfo MetaDrawInfo;
+
+#define META_THEME_ERROR (g_quark_from_static_string ("meta-theme-error"))
+
+typedef enum
+{
+ META_THEME_ERROR_FRAME_GEOMETRY,
+ META_THEME_ERROR_BAD_CHARACTER,
+ META_THEME_ERROR_BAD_PARENS,
+ META_THEME_ERROR_UNKNOWN_VARIABLE,
+ META_THEME_ERROR_DIVIDE_BY_ZERO,
+ META_THEME_ERROR_MOD_ON_FLOAT,
+ META_THEME_ERROR_FAILED
+} MetaThemeError;
+
+/**
+ * Whether a button's size is calculated from the area around it (aspect
+ * sizing) or is given as a fixed height and width in pixels (fixed sizing).
+ *
+ * \bug This could be done away with; see the comment at the top of
+ * MetaFrameLayout.
+ */
+typedef enum
+{
+ META_BUTTON_SIZING_ASPECT,
+ META_BUTTON_SIZING_FIXED,
+ META_BUTTON_SIZING_LAST
+} MetaButtonSizing;
+
+/**
+ * Various parameters used to calculate the geometry of a frame.
+ * They are used inside a MetaFrameStyle.
+ * This corresponds closely to the <frame_geometry> tag in a theme file.
+ *
+ * \bug button_sizing isn't really necessary, because we could easily say
+ * that if button_aspect is zero, the height and width are fixed values.
+ * This would also mean that MetaButtonSizing didn't need to exist, and
+ * save code.
+ **/
+struct _MetaFrameLayout
+{
+ /** Reference count. */
+ int refcount;
+
+ /** Size of left side */
+ int left_width;
+ /** Size of right side */
+ int right_width;
+ /** Size of bottom side */
+ int bottom_height;
+
+ /** Border of blue title region
+ * \bug (blue?!)
+ **/
+ GtkBorder title_border;
+
+ /** Extra height for inside of title region, above the font height */
+ int title_vertical_pad;
+
+ /** Right indent of buttons from edges of frame */
+ int right_titlebar_edge;
+ /** Left indent of buttons from edges of frame */
+ int left_titlebar_edge;
+
+ /**
+ * Sizing rule of buttons, either META_BUTTON_SIZING_ASPECT
+ * (in which case button_aspect will be honoured, and
+ * button_width and button_height set from it), or
+ * META_BUTTON_SIZING_FIXED (in which case we read the width
+ * and height directly).
+ */
+ MetaButtonSizing button_sizing;
+
+ /**
+ * Ratio of height/width. Honoured only if
+ * button_sizing==META_BUTTON_SIZING_ASPECT.
+ * Otherwise we figure out the height from the button_border.
+ */
+ double button_aspect;
+
+ /** Width of a button; set even when we are using aspect sizing */
+ int button_width;
+
+ /** Height of a button; set even when we are using aspect sizing */
+ int button_height;
+
+ /** Space around buttons */
+ GtkBorder button_border;
+
+ /** scale factor for title text */
+ double title_scale;
+
+ /** Whether title text will be displayed */
+ guint has_title : 1;
+
+ /** Whether we should hide the buttons */
+ guint hide_buttons : 1;
+
+ /** Radius of the top left-hand corner; 0 if not rounded */
+ guint top_left_corner_rounded_radius;
+ /** Radius of the top right-hand corner; 0 if not rounded */
+ guint top_right_corner_rounded_radius;
+ /** Radius of the bottom left-hand corner; 0 if not rounded */
+ guint bottom_left_corner_rounded_radius;
+ /** Radius of the bottom right-hand corner; 0 if not rounded */
+ guint bottom_right_corner_rounded_radius;
+};
+
+/**
+ * The computed size of a button (really just a way of tying its
+ * visible and clickable areas together).
+ * The reason for two different rectangles here is Fitts' law & maximized
+ * windows; see bug #97703 for more details.
+ */
+struct _MetaButtonSpace
+{
+ /** The screen area where the button's image is drawn */
+ GdkRectangle visible;
+ /** The screen area where the button can be activated by clicking */
+ GdkRectangle clickable;
+};
+
+/**
+ * Calculated actual geometry of the frame
+ */
+struct _MetaFrameGeometry
+{
+ int left_width;
+ int right_width;
+ int top_height;
+ int bottom_height;
+
+ int width;
+ int height;
+
+ GdkRectangle title_rect;
+
+ int left_titlebar_edge;
+ int right_titlebar_edge;
+ int top_titlebar_edge;
+ int bottom_titlebar_edge;
+
+ /* used for a memset hack */
+#define ADDRESS_OF_BUTTON_RECTS(fgeom) (((char*)(fgeom)) + G_STRUCT_OFFSET (MetaFrameGeometry, close_rect))
+#define LENGTH_OF_BUTTON_RECTS (G_STRUCT_OFFSET (MetaFrameGeometry, right_right_background) + sizeof (GdkRectangle) - G_STRUCT_OFFSET (MetaFrameGeometry, close_rect))
+
+ /* The button rects (if changed adjust memset hack) */
+ MetaButtonSpace close_rect;
+ MetaButtonSpace max_rect;
+ MetaButtonSpace min_rect;
+ MetaButtonSpace menu_rect;
+ MetaButtonSpace shade_rect;
+ MetaButtonSpace above_rect;
+ MetaButtonSpace stick_rect;
+ MetaButtonSpace unshade_rect;
+ MetaButtonSpace unabove_rect;
+ MetaButtonSpace unstick_rect;
+
+#define MAX_MIDDLE_BACKGROUNDS (MAX_BUTTONS_PER_CORNER - 2)
+ GdkRectangle left_left_background;
+ GdkRectangle left_middle_backgrounds[MAX_MIDDLE_BACKGROUNDS];
+ GdkRectangle left_right_background;
+ GdkRectangle right_left_background;
+ GdkRectangle right_middle_backgrounds[MAX_MIDDLE_BACKGROUNDS];
+ GdkRectangle right_right_background;
+ /* End of button rects (if changed adjust memset hack) */
+
+ /* Round corners */
+ guint top_left_corner_rounded_radius;
+ guint top_right_corner_rounded_radius;
+ guint bottom_left_corner_rounded_radius;
+ guint bottom_right_corner_rounded_radius;
+};
+
+typedef enum
+{
+ META_IMAGE_FILL_SCALE, /* default, needs to be all-bits-zero for g_new0 */
+ META_IMAGE_FILL_TILE
+} MetaImageFillType;
+
+typedef enum
+{
+ META_COLOR_SPEC_BASIC,
+ META_COLOR_SPEC_GTK,
+ META_COLOR_SPEC_BLEND,
+ META_COLOR_SPEC_SHADE
+} MetaColorSpecType;
+
+typedef enum
+{
+ META_GTK_COLOR_FG,
+ META_GTK_COLOR_BG,
+ META_GTK_COLOR_LIGHT,
+ META_GTK_COLOR_DARK,
+ META_GTK_COLOR_MID,
+ META_GTK_COLOR_TEXT,
+ META_GTK_COLOR_BASE,
+ META_GTK_COLOR_TEXT_AA,
+ META_GTK_COLOR_LAST
+} MetaGtkColorComponent;
+
+struct _MetaColorSpec
+{
+ MetaColorSpecType type;
+ union
+ {
+ struct {
+ GdkColor color;
+ } basic;
+ struct {
+ MetaGtkColorComponent component;
+ GtkStateType state;
+ } gtk;
+ struct {
+ MetaColorSpec *foreground;
+ MetaColorSpec *background;
+ double alpha;
+
+ GdkColor color;
+ } blend;
+ struct {
+ MetaColorSpec *base;
+ double factor;
+
+ GdkColor color;
+ } shade;
+ } data;
+};
+
+struct _MetaGradientSpec
+{
+ MetaGradientType type;
+ GSList *color_specs;
+};
+
+struct _MetaAlphaGradientSpec
+{
+ MetaGradientType type;
+ unsigned char *alphas;
+ int n_alphas;
+};
+
+struct _MetaDrawInfo
+{
+ GdkPixbuf *mini_icon;
+ GdkPixbuf *icon;
+ PangoLayout *title_layout;
+ int title_layout_width;
+ int title_layout_height;
+ const MetaFrameGeometry *fgeom;
+};
+
+/**
+ * A drawing operation in our simple vector drawing language.
+ */
+typedef enum
+{
+ /** Basic drawing-- line */
+ META_DRAW_LINE,
+ /** Basic drawing-- rectangle */
+ META_DRAW_RECTANGLE,
+ /** Basic drawing-- arc */
+ META_DRAW_ARC,
+
+ /** Clip to a rectangle */
+ META_DRAW_CLIP,
+
+ /* Texture thingies */
+
+ /** Just a filled rectangle with alpha */
+ META_DRAW_TINT,
+ META_DRAW_GRADIENT,
+ META_DRAW_IMAGE,
+
+ /** GTK theme engine stuff */
+ META_DRAW_GTK_ARROW,
+ META_DRAW_GTK_BOX,
+ META_DRAW_GTK_VLINE,
+
+ /** App's window icon */
+ META_DRAW_ICON,
+ /** App's window title */
+ META_DRAW_TITLE,
+ /** a draw op list */
+ META_DRAW_OP_LIST,
+ /** tiled draw op list */
+ META_DRAW_TILE
+} MetaDrawType;
+
+typedef enum
+{
+ POS_TOKEN_INT,
+ POS_TOKEN_DOUBLE,
+ POS_TOKEN_OPERATOR,
+ POS_TOKEN_VARIABLE,
+ POS_TOKEN_OPEN_PAREN,
+ POS_TOKEN_CLOSE_PAREN
+} PosTokenType;
+
+typedef enum
+{
+ POS_OP_NONE,
+ POS_OP_ADD,
+ POS_OP_SUBTRACT,
+ POS_OP_MULTIPLY,
+ POS_OP_DIVIDE,
+ POS_OP_MOD,
+ POS_OP_MAX,
+ POS_OP_MIN
+} PosOperatorType;
+
+/**
+ * A token, as output by the tokeniser.
+ *
+ * \ingroup tokenizer
+ */
+typedef struct
+{
+ PosTokenType type;
+
+ union
+ {
+ struct {
+ int val;
+ } i;
+
+ struct {
+ double val;
+ } d;
+
+ struct {
+ PosOperatorType op;
+ } o;
+
+ struct {
+ char *name;
+ GQuark name_quark;
+ } v;
+
+ } d;
+} PosToken;
+
+/**
+ * A computed expression in our simple vector drawing language.
+ * While it appears to take the form of a tree, this is actually
+ * merely a list; concerns such as precedence of operators are
+ * currently recomputed on every recalculation.
+ *
+ * Created by meta_draw_spec_new(), destroyed by meta_draw_spec_free().
+ * pos_eval() fills this with ...FIXME. Are tokens a tree or a list?
+ * \ingroup parser
+ */
+typedef struct _MetaDrawSpec
+{
+ /**
+ * If this spec is constant, this is the value of the constant;
+ * otherwise it is zero.
+ */
+ int value;
+
+ /** A list of tokens in the expression. */
+ PosToken *tokens;
+
+ /** How many tokens are in the tokens list. */
+ int n_tokens;
+
+ /** Does the expression contain any variables? */
+ gboolean constant : 1;
+} MetaDrawSpec;
+
+/**
+ * A single drawing operation in our simple vector drawing language.
+ */
+struct _MetaDrawOp
+{
+ MetaDrawType type;
+
+ /* Positions are strings because they can be expressions */
+ union
+ {
+ struct {
+ MetaColorSpec *color_spec;
+ int dash_on_length;
+ int dash_off_length;
+ int width;
+ MetaDrawSpec *x1;
+ MetaDrawSpec *y1;
+ MetaDrawSpec *x2;
+ MetaDrawSpec *y2;
+ } line;
+
+ struct {
+ MetaColorSpec *color_spec;
+ gboolean filled;
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ MetaDrawSpec *width;
+ MetaDrawSpec *height;
+ } rectangle;
+
+ struct {
+ MetaColorSpec *color_spec;
+ gboolean filled;
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ MetaDrawSpec *width;
+ MetaDrawSpec *height;
+ double start_angle;
+ double extent_angle;
+ } arc;
+
+ struct {
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ MetaDrawSpec *width;
+ MetaDrawSpec *height;
+ } clip;
+
+ struct {
+ MetaColorSpec *color_spec;
+ MetaAlphaGradientSpec *alpha_spec;
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ MetaDrawSpec *width;
+ MetaDrawSpec *height;
+ } tint;
+
+ struct {
+ MetaGradientSpec *gradient_spec;
+ MetaAlphaGradientSpec *alpha_spec;
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ MetaDrawSpec *width;
+ MetaDrawSpec *height;
+ } gradient;
+
+ struct {
+ MetaColorSpec *colorize_spec;
+ MetaAlphaGradientSpec *alpha_spec;
+ GdkPixbuf *pixbuf;
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ MetaDrawSpec *width;
+ MetaDrawSpec *height;
+
+ guint32 colorize_cache_pixel;
+ GdkPixbuf *colorize_cache_pixbuf;
+ MetaImageFillType fill_type;
+ unsigned int vertical_stripes : 1;
+ unsigned int horizontal_stripes : 1;
+ } image;
+
+ struct {
+ GtkStateType state;
+ GtkShadowType shadow;
+ GtkArrowType arrow;
+ gboolean filled;
+
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ MetaDrawSpec *width;
+ MetaDrawSpec *height;
+ } gtk_arrow;
+
+ struct {
+ GtkStateType state;
+ GtkShadowType shadow;
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ MetaDrawSpec *width;
+ MetaDrawSpec *height;
+ } gtk_box;
+
+ struct {
+ GtkStateType state;
+ MetaDrawSpec *x;
+ MetaDrawSpec *y1;
+ MetaDrawSpec *y2;
+ } gtk_vline;
+
+ struct {
+ MetaAlphaGradientSpec *alpha_spec;
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ MetaDrawSpec *width;
+ MetaDrawSpec *height;
+ MetaImageFillType fill_type;
+ } icon;
+
+ struct {
+ MetaColorSpec *color_spec;
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ } title;
+
+ struct {
+ MetaDrawOpList *op_list;
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ MetaDrawSpec *width;
+ MetaDrawSpec *height;
+ } op_list;
+
+ struct {
+ MetaDrawOpList *op_list;
+ MetaDrawSpec *x;
+ MetaDrawSpec *y;
+ MetaDrawSpec *width;
+ MetaDrawSpec *height;
+ MetaDrawSpec *tile_xoffset;
+ MetaDrawSpec *tile_yoffset;
+ MetaDrawSpec *tile_width;
+ MetaDrawSpec *tile_height;
+ } tile;
+
+ } data;
+};
+
+/**
+ * A list of MetaDrawOp objects. Maintains a reference count.
+ * Grows as necessary and allows the allocation of unused spaces
+ * to keep reallocations to a minimum.
+ *
+ * \bug Do we really win anything from not using the equivalent
+ * GLib structures?
+ */
+struct _MetaDrawOpList
+{
+ int refcount;
+ MetaDrawOp **ops;
+ int n_ops;
+ int n_allocated;
+};
+
+typedef enum
+{
+ META_BUTTON_STATE_NORMAL,
+ META_BUTTON_STATE_PRESSED,
+ META_BUTTON_STATE_PRELIGHT,
+ META_BUTTON_STATE_LAST
+} MetaButtonState;
+
+typedef enum
+{
+ /* Ordered so that background is drawn first */
+ META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND,
+ META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND,
+ META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND,
+ META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND,
+ META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND,
+ META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND,
+ META_BUTTON_TYPE_CLOSE,
+ META_BUTTON_TYPE_MAXIMIZE,
+ META_BUTTON_TYPE_MINIMIZE,
+ META_BUTTON_TYPE_MENU,
+ META_BUTTON_TYPE_SHADE,
+ META_BUTTON_TYPE_ABOVE,
+ META_BUTTON_TYPE_STICK,
+ META_BUTTON_TYPE_UNSHADE,
+ META_BUTTON_TYPE_UNABOVE,
+ META_BUTTON_TYPE_UNSTICK,
+ META_BUTTON_TYPE_LAST
+} MetaButtonType;
+
+typedef enum
+{
+ META_MENU_ICON_TYPE_CLOSE,
+ META_MENU_ICON_TYPE_MAXIMIZE,
+ META_MENU_ICON_TYPE_UNMAXIMIZE,
+ META_MENU_ICON_TYPE_MINIMIZE,
+ META_MENU_ICON_TYPE_LAST
+} MetaMenuIconType;
+
+typedef enum
+{
+ /* Listed in the order in which the textures are drawn.
+ * (though this only matters for overlaps of course.)
+ * Buttons are drawn after the frame textures.
+ *
+ * On the corners, horizontal pieces are arbitrarily given the
+ * corner area:
+ *
+ * ===== |====
+ * | |
+ * | rather than |
+ *
+ */
+
+ /* entire frame */
+ META_FRAME_PIECE_ENTIRE_BACKGROUND,
+ /* entire titlebar background */
+ META_FRAME_PIECE_TITLEBAR,
+ /* portion of the titlebar background inside the titlebar
+ * background edges
+ */
+ META_FRAME_PIECE_TITLEBAR_MIDDLE,
+ /* left end of titlebar */
+ META_FRAME_PIECE_LEFT_TITLEBAR_EDGE,
+ /* right end of titlebar */
+ META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE,
+ /* top edge of titlebar */
+ META_FRAME_PIECE_TOP_TITLEBAR_EDGE,
+ /* bottom edge of titlebar */
+ META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE,
+ /* render over title background (text area) */
+ META_FRAME_PIECE_TITLE,
+ /* left edge of the frame */
+ META_FRAME_PIECE_LEFT_EDGE,
+ /* right edge of the frame */
+ META_FRAME_PIECE_RIGHT_EDGE,
+ /* bottom edge of the frame */
+ META_FRAME_PIECE_BOTTOM_EDGE,
+ /* place over entire frame, after drawing everything else */
+ META_FRAME_PIECE_OVERLAY,
+ /* Used to get size of the enum */
+ META_FRAME_PIECE_LAST
+} MetaFramePiece;
+
+#define N_GTK_STATES 5
+
+/**
+ * How to draw a frame in a particular state (say, a focussed, non-maximised,
+ * resizable frame). This corresponds closely to the <frame_style> tag
+ * in a theme file.
+ */
+struct _MetaFrameStyle
+{
+ /** Reference count. */
+ int refcount;
+ /**
+ * Parent style.
+ * Settings which are unspecified here will be taken from there.
+ */
+ MetaFrameStyle *parent;
+ /** Operations for drawing each kind of button in each state. */
+ MetaDrawOpList *buttons[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST];
+ /** Operations for drawing each piece of the frame. */
+ MetaDrawOpList *pieces[META_FRAME_PIECE_LAST];
+ /**
+ * Details such as the height and width of each edge, the corner rounding,
+ * and the aspect ratio of the buttons.
+ */
+ MetaFrameLayout *layout;
+ /**
+ * Background colour of the window. Only present in theme formats
+ * 2 and above. Can be NULL to use the standard GTK theme engine.
+ */
+ MetaColorSpec *window_background_color;
+ /**
+ * Transparency of the window background. 0=transparent; 255=opaque.
+ */
+ guint8 window_background_alpha;
+};
+
+/* Kinds of frame...
+ *
+ * normal -> noresize / vert only / horz only / both
+ * focused / unfocused
+ * max -> focused / unfocused
+ * shaded -> focused / unfocused
+ * max/shaded -> focused / unfocused
+ *
+ * so 4 states with 8 sub-states in one, 2 sub-states in the other 3,
+ * meaning 14 total
+ *
+ * 14 window states times 7 or 8 window types. Except some
+ * window types never get a frame so that narrows it down a bit.
+ *
+ */
+typedef enum
+{
+ META_FRAME_STATE_NORMAL,
+ META_FRAME_STATE_MAXIMIZED,
+ META_FRAME_STATE_SHADED,
+ META_FRAME_STATE_MAXIMIZED_AND_SHADED,
+ META_FRAME_STATE_LAST
+} MetaFrameState;
+
+typedef enum
+{
+ META_FRAME_RESIZE_NONE,
+ META_FRAME_RESIZE_VERTICAL,
+ META_FRAME_RESIZE_HORIZONTAL,
+ META_FRAME_RESIZE_BOTH,
+ META_FRAME_RESIZE_LAST
+} MetaFrameResize;
+
+typedef enum
+{
+ META_FRAME_FOCUS_NO,
+ META_FRAME_FOCUS_YES,
+ META_FRAME_FOCUS_LAST
+} MetaFrameFocus;
+
+/**
+ * How to draw frames at different times: when it's maximised or not, shaded
+ * or not, when it's focussed or not, and (for non-maximised windows), when
+ * it can be horizontally or vertically resized, both, or neither.
+ * Not all window types actually get a frame.
+ *
+ * A theme contains one of these objects for each type of window (each
+ * MetaFrameType), that is, normal, dialogue (modal and non-modal), etc.
+ *
+ * This corresponds closely to the <frame_style_set> tag in a theme file.
+ */
+struct _MetaFrameStyleSet
+{
+ int refcount;
+ MetaFrameStyleSet *parent;
+ MetaFrameStyle *normal_styles[META_FRAME_RESIZE_LAST][META_FRAME_FOCUS_LAST];
+ MetaFrameStyle *maximized_styles[META_FRAME_FOCUS_LAST];
+ MetaFrameStyle *shaded_styles[META_FRAME_RESIZE_LAST][META_FRAME_FOCUS_LAST];
+ MetaFrameStyle *maximized_and_shaded_styles[META_FRAME_FOCUS_LAST];
+};
+
+/**
+ * A theme. This is a singleton class which groups all settings from a theme
+ * on disk together.
+ *
+ * \bug It is rather useless to keep the metadata fields in core, I think.
+ */
+struct _MetaTheme
+{
+ /** Name of the theme (on disk), e.g. "Crux" */
+ char *name;
+ /** Path to the files associated with the theme */
+ char *dirname;
+ /**
+ * Filename of the XML theme file.
+ * \bug Kept lying around for no discernable reason.
+ */
+ char *filename;
+ /** Metadata: Human-readable name of the theme. */
+ char *readable_name;
+ /** Metadata: Author of the theme. */
+ char *author;
+ /** Metadata: Copyright holder. */
+ char *copyright;
+ /** Metadata: Date of the theme. */
+ char *date;
+ /** Metadata: Description of the theme. */
+ char *description;
+ /** Version of the theme format. Older versions cannot use the features
+ * of newer versions even if they think they can (this is to allow forward
+ * and backward compatibility.
+ */
+ guint format_version;
+
+ /** Symbol table of integer constants. */
+ GHashTable *integer_constants;
+ /** Symbol table of float constants. */
+ GHashTable *float_constants;
+ /**
+ * Symbol table of colour constants (hex triples, and triples
+ * plus alpha).
+ * */
+ GHashTable *color_constants;
+ GHashTable *images_by_filename;
+ GHashTable *layouts_by_name;
+ GHashTable *draw_op_lists_by_name;
+ GHashTable *styles_by_name;
+ GHashTable *style_sets_by_name;
+ MetaFrameStyleSet *style_sets_by_type[META_FRAME_TYPE_LAST];
+
+ GQuark quark_width;
+ GQuark quark_height;
+ GQuark quark_object_width;
+ GQuark quark_object_height;
+ GQuark quark_left_width;
+ GQuark quark_right_width;
+ GQuark quark_top_height;
+ GQuark quark_bottom_height;
+ GQuark quark_mini_icon_width;
+ GQuark quark_mini_icon_height;
+ GQuark quark_icon_width;
+ GQuark quark_icon_height;
+ GQuark quark_title_width;
+ GQuark quark_title_height;
+};
+
+struct _MetaPositionExprEnv
+{
+ MetaRectangle rect;
+ /* size of an object being drawn, if it has a natural size */
+ int object_width;
+ int object_height;
+ /* global object sizes, always available */
+ int left_width;
+ int right_width;
+ int top_height;
+ int bottom_height;
+ int title_width;
+ int title_height;
+ int mini_icon_width;
+ int mini_icon_height;
+ int icon_width;
+ int icon_height;
+ /* Theme so we can look up constants */
+ MetaTheme *theme;
+};
+
+MetaFrameLayout* meta_frame_layout_new (void);
+MetaFrameLayout* meta_frame_layout_copy (const MetaFrameLayout *src);
+void meta_frame_layout_ref (MetaFrameLayout *layout);
+void meta_frame_layout_unref (MetaFrameLayout *layout);
+void meta_frame_layout_get_borders (const MetaFrameLayout *layout,
+ int text_height,
+ MetaFrameFlags flags,
+ int *top_height,
+ int *bottom_height,
+ int *left_width,
+ int *right_width);
+void meta_frame_layout_calc_geometry (const MetaFrameLayout *layout,
+ int text_height,
+ MetaFrameFlags flags,
+ int client_width,
+ int client_height,
+ const MetaButtonLayout *button_layout,
+ MetaFrameGeometry *fgeom,
+ MetaTheme *theme);
+
+gboolean meta_frame_layout_validate (const MetaFrameLayout *layout,
+ GError **error);
+
+gboolean meta_parse_position_expression (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env,
+ int *x_return,
+ int *y_return,
+ GError **err);
+gboolean meta_parse_size_expression (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env,
+ int *val_return,
+ GError **err);
+
+MetaDrawSpec* meta_draw_spec_new (MetaTheme *theme,
+ const char *expr,
+ GError **error);
+void meta_draw_spec_free (MetaDrawSpec *spec);
+
+MetaColorSpec* meta_color_spec_new (MetaColorSpecType type);
+MetaColorSpec* meta_color_spec_new_from_string (const char *str,
+ GError **err);
+MetaColorSpec* meta_color_spec_new_gtk (MetaGtkColorComponent component,
+ GtkStateType state);
+void meta_color_spec_free (MetaColorSpec *spec);
+void meta_color_spec_render (MetaColorSpec *spec,
+ GtkWidget *widget,
+ GdkColor *color);
+
+
+MetaDrawOp* meta_draw_op_new (MetaDrawType type);
+void meta_draw_op_free (MetaDrawOp *op);
+void meta_draw_op_draw (const MetaDrawOp *op,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ /* logical region being drawn */
+ MetaRectangle logical_region);
+
+void meta_draw_op_draw_with_style (const MetaDrawOp *op,
+ GtkStyle *style_gtk,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ /* logical region being drawn */
+ MetaRectangle logical_region);
+
+MetaDrawOpList* meta_draw_op_list_new (int n_preallocs);
+void meta_draw_op_list_ref (MetaDrawOpList *op_list);
+void meta_draw_op_list_unref (MetaDrawOpList *op_list);
+void meta_draw_op_list_draw (const MetaDrawOpList *op_list,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ MetaRectangle rect);
+void meta_draw_op_list_draw_with_style (const MetaDrawOpList *op_list,
+ GtkStyle *style_gtk,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ MetaRectangle rect);
+void meta_draw_op_list_append (MetaDrawOpList *op_list,
+ MetaDrawOp *op);
+gboolean meta_draw_op_list_validate (MetaDrawOpList *op_list,
+ GError **error);
+gboolean meta_draw_op_list_contains (MetaDrawOpList *op_list,
+ MetaDrawOpList *child);
+
+MetaGradientSpec* meta_gradient_spec_new (MetaGradientType type);
+void meta_gradient_spec_free (MetaGradientSpec *desc);
+GdkPixbuf* meta_gradient_spec_render (const MetaGradientSpec *desc,
+ GtkWidget *widget,
+ int width,
+ int height);
+gboolean meta_gradient_spec_validate (MetaGradientSpec *spec,
+ GError **error);
+
+MetaAlphaGradientSpec* meta_alpha_gradient_spec_new (MetaGradientType type,
+ int n_alphas);
+void meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec);
+
+
+MetaFrameStyle* meta_frame_style_new (MetaFrameStyle *parent);
+void meta_frame_style_ref (MetaFrameStyle *style);
+void meta_frame_style_unref (MetaFrameStyle *style);
+
+void meta_frame_style_draw (MetaFrameStyle *style,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ int x_offset,
+ int y_offset,
+ const GdkRectangle *clip,
+ const MetaFrameGeometry *fgeom,
+ int client_width,
+ int client_height,
+ PangoLayout *title_layout,
+ int text_height,
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST],
+ GdkPixbuf *mini_icon,
+ GdkPixbuf *icon);
+
+
+void meta_frame_style_draw_with_style (MetaFrameStyle *style,
+ GtkStyle *style_gtk,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ int x_offset,
+ int y_offset,
+ const GdkRectangle *clip,
+ const MetaFrameGeometry *fgeom,
+ int client_width,
+ int client_height,
+ PangoLayout *title_layout,
+ int text_height,
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST],
+ GdkPixbuf *mini_icon,
+ GdkPixbuf *icon);
+
+
+gboolean meta_frame_style_validate (MetaFrameStyle *style,
+ guint current_theme_version,
+ GError **error);
+
+MetaFrameStyleSet* meta_frame_style_set_new (MetaFrameStyleSet *parent);
+void meta_frame_style_set_ref (MetaFrameStyleSet *style_set);
+void meta_frame_style_set_unref (MetaFrameStyleSet *style_set);
+
+gboolean meta_frame_style_set_validate (MetaFrameStyleSet *style_set,
+ GError **error);
+
+MetaTheme* meta_theme_get_current (void);
+void meta_theme_set_current (const char *name,
+ gboolean force_reload);
+
+MetaTheme* meta_theme_new (void);
+void meta_theme_free (MetaTheme *theme);
+gboolean meta_theme_validate (MetaTheme *theme,
+ GError **error);
+GdkPixbuf* meta_theme_load_image (MetaTheme *theme,
+ const char *filename,
+ guint size_of_theme_icons,
+ GError **error);
+
+MetaFrameStyle* meta_theme_get_frame_style (MetaTheme *theme,
+ MetaFrameType type,
+ MetaFrameFlags flags);
+
+double meta_theme_get_title_scale (MetaTheme *theme,
+ MetaFrameType type,
+ MetaFrameFlags flags);
+
+void meta_theme_draw_frame (MetaTheme *theme,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ int x_offset,
+ int y_offset,
+ MetaFrameType type,
+ MetaFrameFlags flags,
+ int client_width,
+ int client_height,
+ PangoLayout *title_layout,
+ int text_height,
+ const MetaButtonLayout *button_layout,
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST],
+ GdkPixbuf *mini_icon,
+ GdkPixbuf *icon);
+
+void meta_theme_draw_frame_by_name (MetaTheme *theme,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ int x_offset,
+ int y_offset,
+ const gchar *style_name,
+ MetaFrameFlags flags,
+ int client_width,
+ int client_height,
+ PangoLayout *title_layout,
+ int text_height,
+ const MetaButtonLayout *button_layout,
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST],
+ GdkPixbuf *mini_icon,
+ GdkPixbuf *icon);
+
+void meta_theme_draw_frame_with_style (MetaTheme *theme,
+ GtkStyle *style_gtk,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ int x_offset,
+ int y_offset,
+ MetaFrameType type,
+ MetaFrameFlags flags,
+ int client_width,
+ int client_height,
+ PangoLayout *title_layout,
+ int text_height,
+ const MetaButtonLayout *button_layout,
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST],
+ GdkPixbuf *mini_icon,
+ GdkPixbuf *icon);
+
+void meta_theme_get_frame_borders (MetaTheme *theme,
+ MetaFrameType type,
+ int text_height,
+ MetaFrameFlags flags,
+ int *top_height,
+ int *bottom_height,
+ int *left_width,
+ int *right_width);
+void meta_theme_calc_geometry (MetaTheme *theme,
+ MetaFrameType type,
+ int text_height,
+ MetaFrameFlags flags,
+ int client_width,
+ int client_height,
+ const MetaButtonLayout *button_layout,
+ MetaFrameGeometry *fgeom);
+
+MetaFrameLayout* meta_theme_lookup_layout (MetaTheme *theme,
+ const char *name);
+void meta_theme_insert_layout (MetaTheme *theme,
+ const char *name,
+ MetaFrameLayout *layout);
+MetaDrawOpList* meta_theme_lookup_draw_op_list (MetaTheme *theme,
+ const char *name);
+void meta_theme_insert_draw_op_list (MetaTheme *theme,
+ const char *name,
+ MetaDrawOpList *op_list);
+MetaFrameStyle* meta_theme_lookup_style (MetaTheme *theme,
+ const char *name);
+void meta_theme_insert_style (MetaTheme *theme,
+ const char *name,
+ MetaFrameStyle *style);
+MetaFrameStyleSet* meta_theme_lookup_style_set (MetaTheme *theme,
+ const char *name);
+void meta_theme_insert_style_set (MetaTheme *theme,
+ const char *name,
+ MetaFrameStyleSet *style_set);
+gboolean meta_theme_define_int_constant (MetaTheme *theme,
+ const char *name,
+ int value,
+ GError **error);
+gboolean meta_theme_lookup_int_constant (MetaTheme *theme,
+ const char *name,
+ int *value);
+gboolean meta_theme_define_float_constant (MetaTheme *theme,
+ const char *name,
+ double value,
+ GError **error);
+gboolean meta_theme_lookup_float_constant (MetaTheme *theme,
+ const char *name,
+ double *value);
+
+gboolean meta_theme_define_color_constant (MetaTheme *theme,
+ const char *name,
+ const char *value,
+ GError **error);
+gboolean meta_theme_lookup_color_constant (MetaTheme *theme,
+ const char *name,
+ char **value);
+
+gboolean meta_theme_replace_constants (MetaTheme *theme,
+ PosToken *tokens,
+ int n_tokens,
+ GError **err);
+
+/* random stuff */
+
+PangoFontDescription* meta_gtk_widget_get_font_desc (GtkWidget *widget,
+ double scale,
+ const PangoFontDescription *override);
+int meta_pango_font_desc_get_text_height (const PangoFontDescription *font_desc,
+ PangoContext *context);
+
+
+/* Enum converters */
+MetaGtkColorComponent meta_color_component_from_string (const char *str);
+const char* meta_color_component_to_string (MetaGtkColorComponent component);
+MetaButtonState meta_button_state_from_string (const char *str);
+const char* meta_button_state_to_string (MetaButtonState state);
+MetaButtonType meta_button_type_from_string (const char *str,
+ MetaTheme *theme);
+const char* meta_button_type_to_string (MetaButtonType type);
+MetaFramePiece meta_frame_piece_from_string (const char *str);
+const char* meta_frame_piece_to_string (MetaFramePiece piece);
+MetaFrameState meta_frame_state_from_string (const char *str);
+const char* meta_frame_state_to_string (MetaFrameState state);
+MetaFrameResize meta_frame_resize_from_string (const char *str);
+const char* meta_frame_resize_to_string (MetaFrameResize resize);
+MetaFrameFocus meta_frame_focus_from_string (const char *str);
+const char* meta_frame_focus_to_string (MetaFrameFocus focus);
+MetaFrameType meta_frame_type_from_string (const char *str);
+const char* meta_frame_type_to_string (MetaFrameType type);
+MetaGradientType meta_gradient_type_from_string (const char *str);
+const char* meta_gradient_type_to_string (MetaGradientType type);
+GtkStateType meta_gtk_state_from_string (const char *str);
+const char* meta_gtk_state_to_string (GtkStateType state);
+GtkShadowType meta_gtk_shadow_from_string (const char *str);
+const char* meta_gtk_shadow_to_string (GtkShadowType shadow);
+GtkArrowType meta_gtk_arrow_from_string (const char *str);
+const char* meta_gtk_arrow_to_string (GtkArrowType arrow);
+MetaImageFillType meta_image_fill_type_from_string (const char *str);
+const char* meta_image_fill_type_to_string (MetaImageFillType fill_type);
+
+guint meta_theme_earliest_version_with_button (MetaButtonType type);
+
+
+#define META_THEME_ALLOWS(theme, feature) (theme->format_version >= feature)
+
+/* What version of the theme file format were various features introduced in? */
+#define META_THEME_SHADE_STICK_ABOVE_BUTTONS 2
+#define META_THEME_UBIQUITOUS_CONSTANTS 2
+#define META_THEME_VARIED_ROUND_CORNERS 2
+#define META_THEME_IMAGES_FROM_ICON_THEMES 2
+#define META_THEME_UNRESIZABLE_SHADED_STYLES 2
+#define META_THEME_DEGREES_IN_ARCS 2
+#define META_THEME_HIDDEN_BUTTONS 2
+#define META_THEME_COLOR_CONSTANTS 2
+#define META_THEME_FRAME_BACKGROUNDS 2
+
+#endif
diff --git a/src/ui/themewidget.c b/src/ui/themewidget.c
new file mode 100644
index 00000000..2d4e99fe
--- /dev/null
+++ b/src/ui/themewidget.c
@@ -0,0 +1,183 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco theme widget (displays themed draw operations) */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "themewidget.h"
+#include <math.h>
+
+static void meta_area_class_init (MetaAreaClass *klass);
+static void meta_area_init (MetaArea *area);
+static void meta_area_size_request (GtkWidget *widget,
+ GtkRequisition *req);
+static gint meta_area_expose (GtkWidget *widget,
+ GdkEventExpose *event);
+static void meta_area_finalize (GObject *object);
+
+
+static GtkMiscClass *parent_class;
+
+GType
+meta_area_get_type (void)
+{
+ static GType area_type = 0;
+
+ if (!area_type)
+ {
+ static const GtkTypeInfo area_info =
+ {
+ "MetaArea",
+ sizeof (MetaArea),
+ sizeof (MetaAreaClass),
+ (GtkClassInitFunc) meta_area_class_init,
+ (GtkObjectInitFunc) meta_area_init,
+ /* reserved_1 */ NULL,
+ /* reserved_2 */ NULL,
+ (GtkClassInitFunc) NULL,
+ };
+
+ area_type = gtk_type_unique (GTK_TYPE_MISC, &area_info);
+ }
+
+ return area_type;
+}
+
+static void
+meta_area_class_init (MetaAreaClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = (GtkObjectClass*) class;
+ widget_class = (GtkWidgetClass*) class;
+ parent_class = g_type_class_peek (gtk_misc_get_type ());
+
+ gobject_class->finalize = meta_area_finalize;
+
+ widget_class->expose_event = meta_area_expose;
+ widget_class->size_request = meta_area_size_request;
+}
+
+static void
+meta_area_init (MetaArea *area)
+{
+ GTK_WIDGET_SET_FLAGS (area, GTK_NO_WINDOW);
+}
+
+GtkWidget*
+meta_area_new (void)
+{
+ MetaArea *area;
+
+ area = gtk_type_new (META_TYPE_AREA);
+
+ return GTK_WIDGET (area);
+}
+
+static void
+meta_area_finalize (GObject *object)
+{
+ MetaArea *area;
+
+ area = META_AREA (object);
+
+ if (area->dnotify)
+ (* area->dnotify) (area->user_data);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint
+meta_area_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ MetaArea *area;
+ GtkMisc *misc;
+ gint x, y;
+ gfloat xalign;
+
+ g_return_val_if_fail (META_IS_AREA (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ if (GTK_WIDGET_DRAWABLE (widget))
+ {
+ area = META_AREA (widget);
+ misc = GTK_MISC (widget);
+
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
+ xalign = misc->xalign;
+ else
+ xalign = 1.0 - misc->xalign;
+
+ x = floor (widget->allocation.x + misc->xpad
+ + ((widget->allocation.width - widget->requisition.width) * xalign)
+ + 0.5);
+ y = floor (widget->allocation.y + misc->ypad
+ + ((widget->allocation.height - widget->requisition.height) * misc->yalign)
+ + 0.5);
+
+ if (area->expose_func)
+ {
+ (* area->expose_func) (area, event, x, y,
+ area->user_data);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+meta_area_size_request (GtkWidget *widget,
+ GtkRequisition *req)
+{
+ MetaArea *area;
+
+ area = META_AREA (widget);
+
+ req->width = 0;
+ req->height = 0;
+
+ if (area->size_func)
+ {
+ (* area->size_func) (area, &req->width, &req->height,
+ area->user_data);
+ }
+}
+
+void
+meta_area_setup (MetaArea *area,
+ MetaAreaSizeFunc size_func,
+ MetaAreaExposeFunc expose_func,
+ void *user_data,
+ GDestroyNotify dnotify)
+{
+ if (area->dnotify)
+ (* area->dnotify) (area->user_data);
+
+ area->size_func = size_func;
+ area->expose_func = expose_func;
+ area->user_data = user_data;
+ area->dnotify = dnotify;
+
+ gtk_widget_queue_resize (GTK_WIDGET (area));
+}
+
diff --git a/src/ui/themewidget.h b/src/ui/themewidget.h
new file mode 100644
index 00000000..5679d7ab
--- /dev/null
+++ b/src/ui/themewidget.h
@@ -0,0 +1,78 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco theme widget (displays themed draw operations) */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "theme.h"
+#include <gtk/gtk.h>
+
+#ifndef META_THEME_WIDGET_H
+#define META_THEME_WIDGET_H
+
+#define META_TYPE_AREA (meta_area_get_type ())
+#define META_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_AREA, MetaArea))
+#define META_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_AREA, MetaAreaClass))
+#define META_IS_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_AREA))
+#define META_IS_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_AREA))
+#define META_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_AREA, MetaAreaClass))
+
+typedef struct _MetaArea MetaArea;
+typedef struct _MetaAreaClass MetaAreaClass;
+
+
+typedef void (* MetaAreaSizeFunc) (MetaArea *area,
+ int *width,
+ int *height,
+ void *user_data);
+
+typedef void (* MetaAreaExposeFunc) (MetaArea *area,
+ GdkEventExpose *event,
+ int x_offset,
+ int y_offset,
+ void *user_data);
+
+struct _MetaArea
+{
+ GtkMisc misc;
+
+ MetaAreaSizeFunc size_func;
+ MetaAreaExposeFunc expose_func;
+ void *user_data;
+ GDestroyNotify dnotify;
+};
+
+struct _MetaAreaClass
+{
+ GtkMiscClass parent_class;
+};
+
+
+GType meta_area_get_type (void) G_GNUC_CONST;
+GtkWidget* meta_area_new (void);
+
+void meta_area_setup (MetaArea *area,
+ MetaAreaSizeFunc size_func,
+ MetaAreaExposeFunc expose_func,
+ void *user_data,
+ GDestroyNotify dnotify);
+
+
+#endif
diff --git a/src/ui/ui.c b/src/ui/ui.c
new file mode 100644
index 00000000..c41bc891
--- /dev/null
+++ b/src/ui/ui.c
@@ -0,0 +1,1117 @@
+/* Marco interface for talking to GTK+ UI module */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ * stock icon code Copyright (C) 2002 Jorn Baayen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "prefs.h"
+#include "ui.h"
+#include "frames.h"
+#include "util.h"
+#include "menu.h"
+#include "core.h"
+#include "theme.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+static void meta_ui_accelerator_parse(const char* accel, guint* keysym, guint* keycode, GdkModifierType* keymask);
+
+struct _MetaUI {
+ Display* xdisplay;
+ Screen* xscreen;
+ MetaFrames* frames;
+
+ /* For double-click tracking */
+ guint button_click_number;
+ Window button_click_window;
+ int button_click_x;
+ int button_click_y;
+ guint32 button_click_time;
+};
+
+void meta_ui_init(int* argc, char*** argv)
+{
+ if (!gtk_init_check (argc, argv))
+ {
+ meta_fatal ("Unable to open X display %s\n", XDisplayName (NULL));
+ }
+}
+
+Display* meta_ui_get_display(void)
+{
+ return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+}
+
+/* We do some of our event handling in frames.c, which expects
+ * GDK events delivered by GTK+. However, since the transition to
+ * client side windows, we can't let GDK see button events, since the
+ * client-side tracking of implicit and explicit grabs it does will
+ * get confused by our direct use of X grabs in the core code.
+ *
+ * So we do a very minimal GDK => GTK event conversion here and send on the
+ * events we care about, and then filter them out so they don't go
+ * through the normal GDK event handling.
+ *
+ * To reduce the amount of code, the only events fields filled out
+ * below are the ones that frames.c uses. If frames.c is modified to
+ * use more fields, more fields need to be filled out below.
+ */
+
+static gboolean
+maybe_redirect_mouse_event (XEvent *xevent)
+{
+ GdkDisplay *gdisplay;
+ MetaUI *ui;
+ GdkEvent gevent;
+ GdkWindow *gdk_window;
+ Window window;
+
+ switch (xevent->type)
+ {
+ case ButtonPress:
+ case ButtonRelease:
+ window = xevent->xbutton.window;
+ break;
+ case MotionNotify:
+ window = xevent->xmotion.window;
+ break;
+ case EnterNotify:
+ case LeaveNotify:
+ window = xevent->xcrossing.window;
+ break;
+ default:
+ return FALSE;
+ }
+
+ gdisplay = gdk_x11_lookup_xdisplay (xevent->xany.display);
+ ui = g_object_get_data (G_OBJECT (gdisplay), "meta-ui");
+ if (!ui)
+ return FALSE;
+
+ gdk_window = gdk_window_lookup_for_display (gdisplay, window);
+ if (gdk_window == NULL)
+ return FALSE;
+
+ /* If GDK already thinks it has a grab, we better let it see events; this
+ * is the menu-navigation case and events need to get sent to the appropriate
+ * (client-side) subwindow for individual menu items.
+ */
+ if (gdk_display_pointer_is_grabbed (gdisplay))
+ return FALSE;
+
+ memset (&gevent, 0, sizeof (gevent));
+
+ switch (xevent->type)
+ {
+ case ButtonPress:
+ case ButtonRelease:
+ if (xevent->type == ButtonPress)
+ {
+ GtkSettings *settings = gtk_settings_get_default ();
+ int double_click_time;
+ int double_click_distance;
+
+ g_object_get (settings,
+ "gtk-double-click-time", &double_click_time,
+ "gtk-double-click-distance", &double_click_distance,
+ NULL);
+
+ if (xevent->xbutton.button == ui->button_click_number &&
+ xevent->xbutton.window == ui->button_click_window &&
+ xevent->xbutton.time < ui->button_click_time + double_click_time &&
+ ABS (xevent->xbutton.x - ui->button_click_x) <= double_click_distance &&
+ ABS (xevent->xbutton.y - ui->button_click_y) <= double_click_distance)
+ {
+ gevent.button.type = GDK_2BUTTON_PRESS;
+
+ ui->button_click_number = 0;
+ }
+ else
+ {
+ gevent.button.type = GDK_BUTTON_PRESS;
+ ui->button_click_number = xevent->xbutton.button;
+ ui->button_click_window = xevent->xbutton.window;
+ ui->button_click_time = xevent->xbutton.time;
+ ui->button_click_x = xevent->xbutton.x;
+ ui->button_click_y = xevent->xbutton.y;
+ }
+ }
+ else
+ {
+ gevent.button.type = GDK_BUTTON_RELEASE;
+ }
+
+ gevent.button.window = gdk_window;
+ gevent.button.button = xevent->xbutton.button;
+ gevent.button.time = xevent->xbutton.time;
+ gevent.button.x = xevent->xbutton.x;
+ gevent.button.y = xevent->xbutton.y;
+ gevent.button.x_root = xevent->xbutton.x_root;
+ gevent.button.y_root = xevent->xbutton.y_root;
+
+ break;
+ case MotionNotify:
+ gevent.motion.type = GDK_MOTION_NOTIFY;
+ gevent.motion.window = gdk_window;
+ break;
+ case EnterNotify:
+ case LeaveNotify:
+ gevent.crossing.type = xevent->type == EnterNotify ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY;
+ gevent.crossing.window = gdk_window;
+ gevent.crossing.x = xevent->xcrossing.x;
+ gevent.crossing.y = xevent->xcrossing.y;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ /* If we've gotten here, we've filled in the gdk_event and should send it on */
+ gtk_main_do_event (&gevent);
+
+ return TRUE;
+}
+
+typedef struct _EventFunc EventFunc;
+
+struct _EventFunc
+{
+ MetaEventFunc func;
+ gpointer data;
+};
+
+static EventFunc *ef = NULL;
+
+static GdkFilterReturn
+filter_func (GdkXEvent *xevent,
+ GdkEvent *event,
+ gpointer data)
+{
+ g_return_val_if_fail (ef != NULL, GDK_FILTER_CONTINUE);
+
+ if ((* ef->func) (xevent, ef->data) ||
+ maybe_redirect_mouse_event (xevent))
+ return GDK_FILTER_REMOVE;
+ else
+ return GDK_FILTER_CONTINUE;
+}
+
+void
+meta_ui_add_event_func (Display *xdisplay,
+ MetaEventFunc func,
+ gpointer data)
+{
+ g_return_if_fail (ef == NULL);
+
+ ef = g_new (EventFunc, 1);
+ ef->func = func;
+ ef->data = data;
+
+ gdk_window_add_filter (NULL, filter_func, ef);
+}
+
+/* removal is by data due to proxy function */
+void
+meta_ui_remove_event_func (Display *xdisplay,
+ MetaEventFunc func,
+ gpointer data)
+{
+ g_return_if_fail (ef != NULL);
+
+ gdk_window_remove_filter (NULL, filter_func, ef);
+
+ g_free (ef);
+ ef = NULL;
+}
+
+MetaUI*
+meta_ui_new (Display *xdisplay,
+ Screen *screen)
+{
+ GdkDisplay *gdisplay;
+ MetaUI *ui;
+
+ ui = g_new0 (MetaUI, 1);
+ ui->xdisplay = xdisplay;
+ ui->xscreen = screen;
+
+ gdisplay = gdk_x11_lookup_xdisplay (xdisplay);
+ g_assert (gdisplay == gdk_display_get_default ());
+
+ g_assert (xdisplay == GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+ ui->frames = meta_frames_new (XScreenNumberOfScreen (screen));
+ gtk_widget_realize (GTK_WIDGET (ui->frames));
+
+ g_object_set_data (G_OBJECT (gdisplay), "meta-ui", ui);
+
+ return ui;
+}
+
+void
+meta_ui_free (MetaUI *ui)
+{
+ GdkDisplay *gdisplay;
+
+ gtk_widget_destroy (GTK_WIDGET (ui->frames));
+
+ gdisplay = gdk_x11_lookup_xdisplay (ui->xdisplay);
+ g_object_set_data (G_OBJECT (gdisplay), "meta-ui", NULL);
+
+ g_free (ui);
+}
+
+void
+meta_ui_get_frame_geometry (MetaUI *ui,
+ Window frame_xwindow,
+ int *top_height, int *bottom_height,
+ int *left_width, int *right_width)
+{
+ meta_frames_get_geometry (ui->frames, frame_xwindow,
+ top_height, bottom_height,
+ left_width, right_width);
+}
+
+Window
+meta_ui_create_frame_window (MetaUI *ui,
+ Display *xdisplay,
+ Visual *xvisual,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gint screen_no)
+{
+ GdkDisplay *display = gdk_x11_lookup_xdisplay (xdisplay);
+ GdkScreen *screen = gdk_display_get_screen (display, screen_no);
+ GdkWindowAttr attrs;
+ gint attributes_mask;
+ GdkWindow *window;
+ GdkVisual *visual;
+ GdkColormap *cmap = gdk_screen_get_default_colormap (screen);
+
+ /* Default depth/visual handles clients with weird visuals; they can
+ * always be children of the root depth/visual obviously, but
+ * e.g. DRI games can't be children of a parent that has the same
+ * visual as the client.
+ */
+ if (!xvisual)
+ visual = gdk_screen_get_system_visual (screen);
+ else
+ {
+ visual = gdk_x11_screen_lookup_visual (screen,
+ XVisualIDFromVisual (xvisual));
+ cmap = gdk_colormap_new (visual, FALSE);
+ }
+
+ attrs.title = NULL;
+
+ /* frame.c is going to replace the event mask immediately, but
+ * we still have to set it here to let GDK know what it is.
+ */
+ attrs.event_mask =
+ GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
+ GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_FOCUS_CHANGE_MASK;
+ attrs.x = x;
+ attrs.y = y;
+ attrs.wclass = GDK_INPUT_OUTPUT;
+ attrs.visual = visual;
+ attrs.colormap = cmap;
+ attrs.window_type = GDK_WINDOW_CHILD;
+ attrs.cursor = NULL;
+ attrs.wmclass_name = NULL;
+ attrs.wmclass_class = NULL;
+ attrs.override_redirect = FALSE;
+
+ attrs.width = width;
+ attrs.height = height;
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+
+ window =
+ gdk_window_new (gdk_screen_get_root_window(screen),
+ &attrs, attributes_mask);
+
+ gdk_window_resize (window, width, height);
+
+ meta_frames_manage_window (ui->frames, GDK_WINDOW_XID (window), window);
+
+ return GDK_WINDOW_XID (window);
+}
+
+void
+meta_ui_destroy_frame_window (MetaUI *ui,
+ Window xwindow)
+{
+ meta_frames_unmanage_window (ui->frames, xwindow);
+}
+
+void
+meta_ui_move_resize_frame (MetaUI *ui,
+ Window frame,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ meta_frames_move_resize_frame (ui->frames, frame, x, y, width, height);
+}
+
+void
+meta_ui_map_frame (MetaUI *ui,
+ Window xwindow)
+{
+ GdkWindow *window;
+
+ window = gdk_xid_table_lookup (xwindow);
+
+ if (window)
+ gdk_window_show_unraised (window);
+}
+
+void
+meta_ui_unmap_frame (MetaUI *ui,
+ Window xwindow)
+{
+ GdkWindow *window;
+
+ window = gdk_xid_table_lookup (xwindow);
+
+ if (window)
+ gdk_window_hide (window);
+}
+
+void
+meta_ui_unflicker_frame_bg (MetaUI *ui,
+ Window xwindow,
+ int target_width,
+ int target_height)
+{
+ meta_frames_unflicker_bg (ui->frames, xwindow,
+ target_width, target_height);
+}
+
+void
+meta_ui_repaint_frame (MetaUI *ui,
+ Window xwindow)
+{
+ meta_frames_repaint_frame (ui->frames, xwindow);
+}
+
+void
+meta_ui_reset_frame_bg (MetaUI *ui,
+ Window xwindow)
+{
+ meta_frames_reset_bg (ui->frames, xwindow);
+}
+
+void
+meta_ui_apply_frame_shape (MetaUI *ui,
+ Window xwindow,
+ int new_window_width,
+ int new_window_height,
+ gboolean window_has_shape)
+{
+ meta_frames_apply_shapes (ui->frames, xwindow,
+ new_window_width, new_window_height,
+ window_has_shape);
+}
+
+void
+meta_ui_queue_frame_draw (MetaUI *ui,
+ Window xwindow)
+{
+ meta_frames_queue_draw (ui->frames, xwindow);
+}
+
+
+void
+meta_ui_set_frame_title (MetaUI *ui,
+ Window xwindow,
+ const char *title)
+{
+ meta_frames_set_title (ui->frames, xwindow, title);
+}
+
+MetaWindowMenu*
+meta_ui_window_menu_new (MetaUI *ui,
+ Window client_xwindow,
+ MetaMenuOp ops,
+ MetaMenuOp insensitive,
+ unsigned long active_workspace,
+ int n_workspaces,
+ MetaWindowMenuFunc func,
+ gpointer data)
+{
+ return meta_window_menu_new (ui->frames,
+ ops, insensitive,
+ client_xwindow,
+ active_workspace,
+ n_workspaces,
+ func, data);
+}
+
+void
+meta_ui_window_menu_popup (MetaWindowMenu *menu,
+ int root_x,
+ int root_y,
+ int button,
+ guint32 timestamp)
+{
+ meta_window_menu_popup (menu, root_x, root_y, button, timestamp);
+}
+
+void
+meta_ui_window_menu_free (MetaWindowMenu *menu)
+{
+ meta_window_menu_free (menu);
+}
+
+struct _MetaImageWindow
+{
+ GtkWidget *window;
+ GdkPixmap *pixmap;
+};
+
+MetaImageWindow*
+meta_image_window_new (Display *xdisplay,
+ int screen_number,
+ int max_width,
+ int max_height)
+{
+ MetaImageWindow *iw;
+ GdkDisplay *gdisplay;
+ GdkScreen *gscreen;
+
+ iw = g_new (MetaImageWindow, 1);
+ iw->window = gtk_window_new (GTK_WINDOW_POPUP);
+
+ gdisplay = gdk_x11_lookup_xdisplay (xdisplay);
+ gscreen = gdk_display_get_screen (gdisplay, screen_number);
+
+ gtk_window_set_screen (GTK_WINDOW (iw->window), gscreen);
+
+ gtk_widget_realize (iw->window);
+ iw->pixmap = gdk_pixmap_new (iw->window->window,
+ max_width, max_height,
+ -1);
+
+ gtk_widget_set_size_request (iw->window, 1, 1);
+ gtk_widget_set_double_buffered (iw->window, FALSE);
+ gtk_widget_set_app_paintable (iw->window, TRUE);
+
+ return iw;
+}
+
+void
+meta_image_window_free (MetaImageWindow *iw)
+{
+ gtk_widget_destroy (iw->window);
+ g_object_unref (G_OBJECT (iw->pixmap));
+ g_free (iw);
+}
+
+void
+meta_image_window_set_showing (MetaImageWindow *iw,
+ gboolean showing)
+{
+ if (showing)
+ gtk_widget_show_all (iw->window);
+ else
+ {
+ gtk_widget_hide (iw->window);
+ meta_core_increment_event_serial (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));
+ }
+}
+
+void
+meta_image_window_set (MetaImageWindow *iw,
+ GdkPixbuf *pixbuf,
+ int x,
+ int y)
+{
+ cairo_t *cr;
+
+ /* We use a back pixmap to avoid having to handle exposes, because
+ * it's really too slow for large clients being minimized, etc.
+ * and this way flicker is genuinely zero.
+ */
+
+ gdk_draw_pixbuf (iw->pixmap,
+ iw->window->style->black_gc,
+ pixbuf,
+ 0, 0,
+ 0, 0,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ GDK_RGB_DITHER_NORMAL,
+ 0, 0);
+ cr = gdk_cairo_create (iw->pixmap);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ gdk_window_set_back_pixmap (iw->window->window,
+ iw->pixmap,
+ FALSE);
+
+ gdk_window_move_resize (iw->window->window,
+ x, y,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+
+ gdk_window_clear (iw->window->window);
+}
+
+static GdkColormap*
+get_cmap (GdkPixmap *pixmap)
+{
+ GdkColormap *cmap;
+
+ cmap = gdk_drawable_get_colormap (pixmap);
+ if (cmap)
+ g_object_ref (G_OBJECT (cmap));
+
+ if (cmap == NULL)
+ {
+ if (gdk_drawable_get_depth (pixmap) == 1)
+ {
+ meta_verbose ("Using NULL colormap for snapshotting bitmap\n");
+ cmap = NULL;
+ }
+ else
+ {
+ meta_verbose ("Using system cmap to snapshot pixmap\n");
+ cmap = gdk_screen_get_system_colormap (gdk_drawable_get_screen (pixmap));
+
+ g_object_ref (G_OBJECT (cmap));
+ }
+ }
+
+ /* Be sure we aren't going to blow up due to visual mismatch */
+ if (cmap &&
+ (gdk_colormap_get_visual (cmap)->depth !=
+ gdk_drawable_get_depth (pixmap)))
+ {
+ cmap = NULL;
+ meta_verbose ("Switching back to NULL cmap because of depth mismatch\n");
+ }
+
+ return cmap;
+}
+
+GdkPixbuf*
+meta_gdk_pixbuf_get_from_window (GdkPixbuf *dest,
+ Window xwindow,
+ int src_x,
+ int src_y,
+ int dest_x,
+ int dest_y,
+ int width,
+ int height)
+{
+ GdkDrawable *drawable;
+ GdkPixbuf *retval;
+ GdkColormap *cmap;
+
+ retval = NULL;
+
+ drawable = gdk_xid_table_lookup (xwindow);
+
+ if (drawable)
+ g_object_ref (G_OBJECT (drawable));
+ else
+ drawable = gdk_window_foreign_new (xwindow);
+
+ cmap = get_cmap (drawable);
+
+ retval = gdk_pixbuf_get_from_drawable (dest,
+ drawable,
+ cmap,
+ src_x, src_y,
+ dest_x, dest_y,
+ width, height);
+
+ if (cmap)
+ g_object_unref (G_OBJECT (cmap));
+ g_object_unref (G_OBJECT (drawable));
+
+ return retval;
+}
+
+GdkPixbuf*
+meta_gdk_pixbuf_get_from_pixmap (GdkPixbuf *dest,
+ Pixmap xpixmap,
+ int src_x,
+ int src_y,
+ int dest_x,
+ int dest_y,
+ int width,
+ int height)
+{
+ GdkDrawable *drawable;
+ GdkPixbuf *retval;
+ GdkColormap *cmap;
+
+ retval = NULL;
+ cmap = NULL;
+
+ drawable = gdk_xid_table_lookup (xpixmap);
+
+ if (drawable)
+ g_object_ref (G_OBJECT (drawable));
+ else
+ drawable = gdk_pixmap_foreign_new (xpixmap);
+
+ if (drawable)
+ {
+ cmap = get_cmap (drawable);
+
+ retval = gdk_pixbuf_get_from_drawable (dest,
+ drawable,
+ cmap,
+ src_x, src_y,
+ dest_x, dest_y,
+ width, height);
+ }
+ if (cmap)
+ g_object_unref (G_OBJECT (cmap));
+ if (drawable)
+ g_object_unref (G_OBJECT (drawable));
+
+ return retval;
+}
+
+void
+meta_ui_push_delay_exposes (MetaUI *ui)
+{
+ meta_frames_push_delay_exposes (ui->frames);
+}
+
+void
+meta_ui_pop_delay_exposes (MetaUI *ui)
+{
+ meta_frames_pop_delay_exposes (ui->frames);
+}
+
+GdkPixbuf*
+meta_ui_get_default_window_icon (MetaUI *ui)
+{
+ static GdkPixbuf *default_icon = NULL;
+
+ if (default_icon == NULL)
+ {
+ GtkIconTheme *theme;
+ gboolean icon_exists;
+
+ theme = gtk_icon_theme_get_default ();
+
+ icon_exists = gtk_icon_theme_has_icon (theme, META_DEFAULT_ICON_NAME);
+
+ if (icon_exists)
+ default_icon = gtk_icon_theme_load_icon (theme,
+ META_DEFAULT_ICON_NAME,
+ META_ICON_WIDTH,
+ 0,
+ NULL);
+ else
+ default_icon = gtk_icon_theme_load_icon (theme,
+ "gtk-missing-image",
+ META_ICON_WIDTH,
+ 0,
+ NULL);
+
+ g_assert (default_icon);
+ }
+
+ g_object_ref (G_OBJECT (default_icon));
+
+ return default_icon;
+}
+
+GdkPixbuf*
+meta_ui_get_default_mini_icon (MetaUI *ui)
+{
+ static GdkPixbuf *default_icon = NULL;
+
+ if (default_icon == NULL)
+ {
+ GtkIconTheme *theme;
+ gboolean icon_exists;
+
+ theme = gtk_icon_theme_get_default ();
+
+ icon_exists = gtk_icon_theme_has_icon (theme, META_DEFAULT_ICON_NAME);
+
+ if (icon_exists)
+ default_icon = gtk_icon_theme_load_icon (theme,
+ META_DEFAULT_ICON_NAME,
+ META_MINI_ICON_WIDTH,
+ 0,
+ NULL);
+ else
+ default_icon = gtk_icon_theme_load_icon (theme,
+ "gtk-missing-image",
+ META_MINI_ICON_WIDTH,
+ 0,
+ NULL);
+
+ g_assert (default_icon);
+ }
+
+ g_object_ref (G_OBJECT (default_icon));
+
+ return default_icon;
+}
+
+gboolean
+meta_ui_window_should_not_cause_focus (Display *xdisplay,
+ Window xwindow)
+{
+ GdkWindow *window;
+
+ window = gdk_xid_table_lookup (xwindow);
+
+ /* we shouldn't cause focus if we're an override redirect
+ * toplevel which is not foreign
+ */
+ if (window && gdk_window_get_window_type (window) == GDK_WINDOW_TEMP)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+char*
+meta_text_property_to_utf8 (Display *xdisplay,
+ const XTextProperty *prop)
+{
+ char **list;
+ int count;
+ char *retval;
+
+ list = NULL;
+
+ count = gdk_text_property_to_utf8_list (gdk_x11_xatom_to_atom (prop->encoding),
+ prop->format,
+ prop->value,
+ prop->nitems,
+ &list);
+
+ if (count == 0)
+ retval = NULL;
+ else
+ {
+ retval = list[0];
+ list[0] = g_strdup (""); /* something to free */
+ }
+
+ g_strfreev (list);
+
+ return retval;
+}
+
+void
+meta_ui_theme_get_frame_borders (MetaUI *ui,
+ MetaFrameType type,
+ MetaFrameFlags flags,
+ int *top_height,
+ int *bottom_height,
+ int *left_width,
+ int *right_width)
+{
+ int text_height;
+ PangoContext *context;
+ const PangoFontDescription *font_desc;
+ GtkStyle *default_style;
+
+ if (meta_ui_have_a_theme ())
+ {
+ context = gtk_widget_get_pango_context (GTK_WIDGET (ui->frames));
+ font_desc = meta_prefs_get_titlebar_font ();
+
+ if (!font_desc)
+ {
+ default_style = gtk_widget_get_default_style ();
+ font_desc = default_style->font_desc;
+ }
+
+ text_height = meta_pango_font_desc_get_text_height (font_desc, context);
+
+ meta_theme_get_frame_borders (meta_theme_get_current (),
+ type, text_height, flags,
+ top_height, bottom_height,
+ left_width, right_width);
+ }
+ else
+ {
+ *top_height = *bottom_height = *left_width = *right_width = 0;
+ }
+}
+
+void
+meta_ui_set_current_theme (const char *name,
+ gboolean force_reload)
+{
+ meta_theme_set_current (name, force_reload);
+ meta_invalidate_default_icons ();
+}
+
+gboolean
+meta_ui_have_a_theme (void)
+{
+ return meta_theme_get_current () != NULL;
+}
+
+static void
+meta_ui_accelerator_parse (const char *accel,
+ guint *keysym,
+ guint *keycode,
+ GdkModifierType *keymask)
+{
+ if (accel[0] == '0' && accel[1] == 'x')
+ {
+ *keysym = 0;
+ *keycode = (guint) strtoul (accel, NULL, 16);
+ *keymask = 0;
+ }
+ else
+ gtk_accelerator_parse (accel, keysym, keymask);
+}
+
+gboolean
+meta_ui_parse_accelerator (const char *accel,
+ unsigned int *keysym,
+ unsigned int *keycode,
+ MetaVirtualModifier *mask)
+{
+ GdkModifierType gdk_mask = 0;
+ guint gdk_sym = 0;
+ guint gdk_code = 0;
+
+ *keysym = 0;
+ *keycode = 0;
+ *mask = 0;
+
+ if (strcmp (accel, "disabled") == 0)
+ return TRUE;
+
+ meta_ui_accelerator_parse (accel, &gdk_sym, &gdk_code, &gdk_mask);
+ if (gdk_mask == 0 && gdk_sym == 0 && gdk_code == 0)
+ return FALSE;
+
+ if (gdk_sym == None && gdk_code == 0)
+ return FALSE;
+
+ if (gdk_mask & GDK_RELEASE_MASK) /* we don't allow this */
+ return FALSE;
+
+ *keysym = gdk_sym;
+ *keycode = gdk_code;
+
+ if (gdk_mask & GDK_SHIFT_MASK)
+ *mask |= META_VIRTUAL_SHIFT_MASK;
+ if (gdk_mask & GDK_CONTROL_MASK)
+ *mask |= META_VIRTUAL_CONTROL_MASK;
+ if (gdk_mask & GDK_MOD1_MASK)
+ *mask |= META_VIRTUAL_ALT_MASK;
+ if (gdk_mask & GDK_MOD2_MASK)
+ *mask |= META_VIRTUAL_MOD2_MASK;
+ if (gdk_mask & GDK_MOD3_MASK)
+ *mask |= META_VIRTUAL_MOD3_MASK;
+ if (gdk_mask & GDK_MOD4_MASK)
+ *mask |= META_VIRTUAL_MOD4_MASK;
+ if (gdk_mask & GDK_MOD5_MASK)
+ *mask |= META_VIRTUAL_MOD5_MASK;
+ if (gdk_mask & GDK_SUPER_MASK)
+ *mask |= META_VIRTUAL_SUPER_MASK;
+ if (gdk_mask & GDK_HYPER_MASK)
+ *mask |= META_VIRTUAL_HYPER_MASK;
+ if (gdk_mask & GDK_META_MASK)
+ *mask |= META_VIRTUAL_META_MASK;
+
+ return TRUE;
+}
+
+/* Caller responsible for freeing return string of meta_ui_accelerator_name! */
+gchar*
+meta_ui_accelerator_name (unsigned int keysym,
+ MetaVirtualModifier mask)
+{
+ GdkModifierType mods = 0;
+
+ if (keysym == 0 && mask == 0)
+ {
+ return g_strdup ("disabled");
+ }
+
+ if (mask & META_VIRTUAL_SHIFT_MASK)
+ mods |= GDK_SHIFT_MASK;
+ if (mask & META_VIRTUAL_CONTROL_MASK)
+ mods |= GDK_CONTROL_MASK;
+ if (mask & META_VIRTUAL_ALT_MASK)
+ mods |= GDK_MOD1_MASK;
+ if (mask & META_VIRTUAL_MOD2_MASK)
+ mods |= GDK_MOD2_MASK;
+ if (mask & META_VIRTUAL_MOD3_MASK)
+ mods |= GDK_MOD3_MASK;
+ if (mask & META_VIRTUAL_MOD4_MASK)
+ mods |= GDK_MOD4_MASK;
+ if (mask & META_VIRTUAL_MOD5_MASK)
+ mods |= GDK_MOD5_MASK;
+ if (mask & META_VIRTUAL_SUPER_MASK)
+ mods |= GDK_SUPER_MASK;
+ if (mask & META_VIRTUAL_HYPER_MASK)
+ mods |= GDK_HYPER_MASK;
+ if (mask & META_VIRTUAL_META_MASK)
+ mods |= GDK_META_MASK;
+
+ return gtk_accelerator_name (keysym, mods);
+
+}
+
+gboolean
+meta_ui_parse_modifier (const char *accel,
+ MetaVirtualModifier *mask)
+{
+ GdkModifierType gdk_mask = 0;
+ guint gdk_sym = 0;
+ guint gdk_code = 0;
+
+ *mask = 0;
+
+ if (accel == NULL || strcmp (accel, "disabled") == 0)
+ return TRUE;
+
+ meta_ui_accelerator_parse (accel, &gdk_sym, &gdk_code, &gdk_mask);
+ if (gdk_mask == 0 && gdk_sym == 0 && gdk_code == 0)
+ return FALSE;
+
+ if (gdk_sym != None || gdk_code != 0)
+ return FALSE;
+
+ if (gdk_mask & GDK_RELEASE_MASK) /* we don't allow this */
+ return FALSE;
+
+ if (gdk_mask & GDK_SHIFT_MASK)
+ *mask |= META_VIRTUAL_SHIFT_MASK;
+ if (gdk_mask & GDK_CONTROL_MASK)
+ *mask |= META_VIRTUAL_CONTROL_MASK;
+ if (gdk_mask & GDK_MOD1_MASK)
+ *mask |= META_VIRTUAL_ALT_MASK;
+ if (gdk_mask & GDK_MOD2_MASK)
+ *mask |= META_VIRTUAL_MOD2_MASK;
+ if (gdk_mask & GDK_MOD3_MASK)
+ *mask |= META_VIRTUAL_MOD3_MASK;
+ if (gdk_mask & GDK_MOD4_MASK)
+ *mask |= META_VIRTUAL_MOD4_MASK;
+ if (gdk_mask & GDK_MOD5_MASK)
+ *mask |= META_VIRTUAL_MOD5_MASK;
+ if (gdk_mask & GDK_SUPER_MASK)
+ *mask |= META_VIRTUAL_SUPER_MASK;
+ if (gdk_mask & GDK_HYPER_MASK)
+ *mask |= META_VIRTUAL_HYPER_MASK;
+ if (gdk_mask & GDK_META_MASK)
+ *mask |= META_VIRTUAL_META_MASK;
+
+ return TRUE;
+}
+
+gboolean
+meta_ui_window_is_widget (MetaUI *ui,
+ Window xwindow)
+{
+ GdkWindow *window;
+
+ window = gdk_xid_table_lookup (xwindow);
+
+ if (window)
+ {
+ void *user_data = NULL;
+ gdk_window_get_user_data (window, &user_data);
+ return user_data != NULL && user_data != ui->frames;
+ }
+ else
+ return FALSE;
+}
+
+/* stock icon code Copyright (C) 2002 Jorn Baayen <[email protected]> */
+
+typedef struct {
+ char* stock_id;
+ const guint8* icon_data;
+} MetaStockIcon;
+
+int meta_ui_get_drag_threshold(MetaUI* ui)
+{
+ int threshold = 8;
+ GtkSettings* settings = gtk_widget_get_settings(GTK_WIDGET(ui->frames));
+
+ g_object_get(G_OBJECT(settings), "gtk-dnd-drag-threshold", &threshold, NULL);
+
+ return threshold;
+}
+
+MetaUIDirection meta_ui_get_direction(void)
+{
+ if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL)
+ {
+ return META_UI_DIRECTION_RTL;
+ }
+
+ return META_UI_DIRECTION_LTR;
+}
+
+GdkPixbuf* meta_ui_get_pixbuf_from_pixmap(Pixmap pmap)
+{
+ GdkPixmap* gpmap;
+ GdkScreen* screen;
+ GdkPixbuf* pixbuf;
+ GdkColormap* cmap;
+ int width;
+ int height;
+ int depth;
+
+ gpmap = gdk_pixmap_foreign_new(pmap);
+ screen = gdk_drawable_get_screen(gpmap);
+
+ #if GTK_CHECK_VERSION(3, 0, 0)
+ width = gdk_window_get_width(GDK_WINDOW(gpmap));
+ height = gdk_window_get_height(GDK_WINDOW(gpmap));
+ #else
+ gdk_drawable_get_size(GDK_DRAWABLE(gpmap), &width, &height);
+ #endif
+
+ depth = gdk_drawable_get_depth(GDK_DRAWABLE(gpmap));
+
+ if (depth <= 24)
+ {
+ cmap = gdk_screen_get_system_colormap(screen);
+ }
+ else
+ {
+ cmap = gdk_screen_get_rgba_colormap(screen);
+ }
+
+ pixbuf = gdk_pixbuf_get_from_drawable(NULL, gpmap, cmap, 0, 0, 0, 0, width, height);
+
+ g_object_unref(gpmap);
+
+ return pixbuf;
+}
diff --git a/src/wm-tester/Makefile.am b/src/wm-tester/Makefile.am
new file mode 100644
index 00000000..3d35c741
--- /dev/null
+++ b/src/wm-tester/Makefile.am
@@ -0,0 +1,25 @@
+
+INCLUDES=@MARCO_CFLAGS@
+
+wm_tester_SOURCES= \
+ main.c
+
+test_gravity_SOURCES= \
+ test-gravity.c
+
+focus_window_SOURCES= \
+ focus-window.c
+
+test_resizing_SOURCES= \
+ test-resizing.c
+
+test_size_hints_SOURCES= \
+ test-size-hints.c
+
+noinst_PROGRAMS=wm-tester test-gravity test-resizing focus-window test-size-hints
+
+wm_tester_LDADD= @MARCO_LIBS@
+test_gravity_LDADD= @MARCO_LIBS@
+test_resizing_LDADD= @MARCO_LIBS@
+test_size_hints_LDADD= @MARCO_LIBS@
+focus_window_LDADD= @MARCO_LIBS@ \ No newline at end of file
diff --git a/src/wm-tester/Makefile.in b/src/wm-tester/Makefile.in
new file mode 100644
index 00000000..a23fe7f8
--- /dev/null
+++ b/src/wm-tester/Makefile.in
@@ -0,0 +1,574 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = wm-tester$(EXEEXT) test-gravity$(EXEEXT) \
+ test-resizing$(EXEEXT) focus-window$(EXEEXT) \
+ test-size-hints$(EXEEXT)
+subdir = src/wm-tester
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+PROGRAMS = $(noinst_PROGRAMS)
+am_focus_window_OBJECTS = focus-window.$(OBJEXT)
+focus_window_OBJECTS = $(am_focus_window_OBJECTS)
+focus_window_DEPENDENCIES =
+am_test_gravity_OBJECTS = test-gravity.$(OBJEXT)
+test_gravity_OBJECTS = $(am_test_gravity_OBJECTS)
+test_gravity_DEPENDENCIES =
+am_test_resizing_OBJECTS = test-resizing.$(OBJEXT)
+test_resizing_OBJECTS = $(am_test_resizing_OBJECTS)
+test_resizing_DEPENDENCIES =
+am_test_size_hints_OBJECTS = test-size-hints.$(OBJEXT)
+test_size_hints_OBJECTS = $(am_test_size_hints_OBJECTS)
+test_size_hints_DEPENDENCIES =
+am_wm_tester_OBJECTS = main.$(OBJEXT)
+wm_tester_OBJECTS = $(am_wm_tester_OBJECTS)
+wm_tester_DEPENDENCIES =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SOURCES = $(focus_window_SOURCES) $(test_gravity_SOURCES) \
+ $(test_resizing_SOURCES) $(test_size_hints_SOURCES) \
+ $(wm_tester_SOURCES)
+DIST_SOURCES = $(focus_window_SOURCES) $(test_gravity_SOURCES) \
+ $(test_resizing_SOURCES) $(test_size_hints_SOURCES) \
+ $(wm_tester_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+ALL_CFLAGS = @ALL_CFLAGS@
+ALL_LIBS = @ALL_LIBS@
+ALL_LINGUAS = @ALL_LINGUAS@
+AMTAR = @AMTAR@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@
+DLLTOOL = @DLLTOOL@
+DOC_USER_FORMATS = @DOC_USER_FORMATS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GDK_PIXBUF_CSOURCE = @GDK_PIXBUF_CSOURCE@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GMOFILES = @GMOFILES@
+GMSGFMT = @GMSGFMT@
+GREP = @GREP@
+GTK_API_VERSION = @GTK_API_VERSION@
+HELP_DIR = @HELP_DIR@
+HOST_ALIAS = @HOST_ALIAS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INSTOBJEXT = @INSTOBJEXT@
+INTLLIBS = @INTLLIBS@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTLTOOL_PERL = @INTLTOOL_PERL@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MARCO_CFLAGS = @MARCO_CFLAGS@
+MARCO_LIBS = @MARCO_LIBS@
+MARCO_MESSAGE_CFLAGS = @MARCO_MESSAGE_CFLAGS@
+MARCO_MESSAGE_LIBS = @MARCO_MESSAGE_LIBS@
+MARCO_WINDOW_DEMO_CFLAGS = @MARCO_WINDOW_DEMO_CFLAGS@
+MARCO_WINDOW_DEMO_LIBS = @MARCO_WINDOW_DEMO_LIBS@
+MATECONFTOOL = @MATECONFTOOL@
+MATECONF_SCHEMA_CONFIG_SOURCE = @MATECONF_SCHEMA_CONFIG_SOURCE@
+MATECONF_SCHEMA_FILE_DIR = @MATECONF_SCHEMA_FILE_DIR@
+MATEDIALOG = @MATEDIALOG@
+MATE_KEYBINDINGS_KEYSDIR = @MATE_KEYBINDINGS_KEYSDIR@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MSGFMT = @MSGFMT@
+MSGFMT_OPTS = @MSGFMT_OPTS@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OMF_DIR = @OMF_DIR@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POFILES = @POFILES@
+POSUB = @POSUB@
+PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@
+PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = @MARCO_CFLAGS@
+wm_tester_SOURCES = \
+ main.c
+
+test_gravity_SOURCES = \
+ test-gravity.c
+
+focus_window_SOURCES = \
+ focus-window.c
+
+test_resizing_SOURCES = \
+ test-resizing.c
+
+test_size_hints_SOURCES = \
+ test-size-hints.c
+
+wm_tester_LDADD = @MARCO_LIBS@
+test_gravity_LDADD = @MARCO_LIBS@
+test_resizing_LDADD = @MARCO_LIBS@
+test_size_hints_LDADD = @MARCO_LIBS@
+focus_window_LDADD = @MARCO_LIBS@
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/wm-tester/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/wm-tester/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+focus-window$(EXEEXT): $(focus_window_OBJECTS) $(focus_window_DEPENDENCIES)
+ @rm -f focus-window$(EXEEXT)
+ $(LINK) $(focus_window_OBJECTS) $(focus_window_LDADD) $(LIBS)
+test-gravity$(EXEEXT): $(test_gravity_OBJECTS) $(test_gravity_DEPENDENCIES)
+ @rm -f test-gravity$(EXEEXT)
+ $(LINK) $(test_gravity_OBJECTS) $(test_gravity_LDADD) $(LIBS)
+test-resizing$(EXEEXT): $(test_resizing_OBJECTS) $(test_resizing_DEPENDENCIES)
+ @rm -f test-resizing$(EXEEXT)
+ $(LINK) $(test_resizing_OBJECTS) $(test_resizing_LDADD) $(LIBS)
+test-size-hints$(EXEEXT): $(test_size_hints_OBJECTS) $(test_size_hints_DEPENDENCIES)
+ @rm -f test-size-hints$(EXEEXT)
+ $(LINK) $(test_size_hints_OBJECTS) $(test_size_hints_LDADD) $(LIBS)
+wm-tester$(EXEEXT): $(wm_tester_OBJECTS) $(wm_tester_DEPENDENCIES)
+ @rm -f wm-tester$(EXEEXT)
+ $(LINK) $(wm_tester_OBJECTS) $(wm_tester_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/focus-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-gravity.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-resizing.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-size-hints.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+ clean-libtool clean-noinstPROGRAMS ctags distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/wm-tester/focus-window.c b/src/wm-tester/focus-window.c
new file mode 100644
index 00000000..dc33bd25
--- /dev/null
+++ b/src/wm-tester/focus-window.c
@@ -0,0 +1,37 @@
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main (int argc, char **argv)
+{
+ Display *d;
+ Window w;
+ const char *w_str;
+ char *end;
+
+ if (argc != 2)
+ {
+ fprintf (stderr, "Usage: focus-window WINDOWID\n");
+ exit (1);
+ }
+
+ d = XOpenDisplay (NULL);
+
+ w_str = argv[1];
+ end = NULL;
+
+ w = strtoul (w_str, &end, 16);
+ if (end == w_str)
+ {
+ fprintf (stderr, "Usage: focus-window WINDOWID\n");
+ exit (1);
+ }
+
+ printf ("Setting input focus to 0x%lx\n", w);
+ XSetInputFocus (d, w, RevertToPointerRoot, CurrentTime);
+ XFlush (d);
+
+ return 0;
+}
+
diff --git a/src/wm-tester/main.c b/src/wm-tester/main.c
new file mode 100644
index 00000000..e56d6606
--- /dev/null
+++ b/src/wm-tester/main.c
@@ -0,0 +1,245 @@
+/* WM tester main() */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <gtk/gtk.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static void set_up_the_evil (void);
+static void set_up_icon_windows (void);
+
+static void
+usage (void)
+{
+ g_print ("wm-tester [--evil] [--icon-windows]\n");
+ exit (0);
+}
+
+int
+main (int argc, char **argv)
+{
+ int i;
+ gboolean do_evil;
+ gboolean do_icon_windows;
+
+ gtk_init (&argc, &argv);
+
+ do_evil = FALSE;
+ do_icon_windows = FALSE;
+
+ i = 1;
+ while (i < argc)
+ {
+ const char *arg = argv[i];
+
+ if (strcmp (arg, "--help") == 0 ||
+ strcmp (arg, "-h") == 0 ||
+ strcmp (arg, "-?") == 0)
+ usage ();
+ else if (strcmp (arg, "--evil") == 0)
+ do_evil = TRUE;
+ else if (strcmp (arg, "--icon-windows") == 0)
+ do_icon_windows = TRUE;
+ else
+ usage ();
+
+ ++i;
+ }
+
+ /* Be sure some option was provided */
+ if (! (do_evil || do_icon_windows))
+ return 1;
+
+ if (do_evil)
+ set_up_the_evil ();
+
+ if (do_icon_windows)
+ set_up_icon_windows ();
+
+ gtk_main ();
+
+ return 0;
+}
+
+static GSList *evil_windows = NULL;
+
+static gint
+evil_timeout (gpointer data)
+{
+ int i;
+ int n_windows;
+ int len;
+ int create_count;
+ int destroy_count;
+
+ len = g_slist_length (evil_windows);
+
+ if (len > 35)
+ {
+ create_count = 2;
+ destroy_count = 5;
+ }
+ else
+ {
+ create_count = 5;
+ destroy_count = 5;
+ }
+
+ /* Create some windows */
+ n_windows = g_random_int_range (0, create_count);
+
+ i = 0;
+ while (i < n_windows)
+ {
+ GtkWidget *w;
+ GtkWidget *c;
+ int t;
+ GtkWidget *parent;
+
+ w = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ gtk_widget_set_uposition (w,
+ g_random_int_range (0,
+ gdk_screen_width ()),
+ g_random_int_range (0,
+ gdk_screen_height ()));
+
+ parent = NULL;
+
+ /* set transient for random window (may create all kinds of weird cycles) */
+ if (len > 0)
+ {
+ t = g_random_int_range (- (len / 3), len);
+ if (t >= 0)
+ {
+ parent = g_slist_nth_data (evil_windows, t);
+
+ if (parent != NULL)
+ gtk_window_set_transient_for (GTK_WINDOW (w), GTK_WINDOW (parent));
+ }
+ }
+
+ if (parent != NULL)
+ c = gtk_button_new_with_label ("Evil Transient!");
+ else
+ c = gtk_button_new_with_label ("Evil Window!");
+ gtk_container_add (GTK_CONTAINER (w), c);
+
+ gtk_widget_show_all (w);
+
+ evil_windows = g_slist_prepend (evil_windows, w);
+
+ ++i;
+ }
+
+ /* Destroy some windows */
+ if (len > destroy_count)
+ {
+ n_windows = g_random_int_range (0, destroy_count);
+ i = 0;
+ while (i < n_windows)
+ {
+ GtkWidget *w;
+
+ w = g_slist_nth_data (evil_windows,
+ g_random_int_range (0, len));
+ if (w)
+ {
+ --len;
+ evil_windows = g_slist_remove (evil_windows, w);
+ gtk_widget_destroy (w);
+ }
+
+ ++i;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+set_up_the_evil (void)
+{
+ g_timeout_add (400, evil_timeout, NULL);
+}
+
+static void
+set_up_icon_windows (void)
+{
+ int i;
+ int n_windows;
+
+ /* Create some windows */
+ n_windows = 9;
+
+ i = 0;
+ while (i < n_windows)
+ {
+ GtkWidget *w;
+ GtkWidget *c;
+ GList *icons;
+ GdkPixbuf *pix;
+
+ w = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ c = gtk_button_new_with_label ("Icon window");
+ gtk_container_add (GTK_CONTAINER (w), c);
+
+ icons = NULL;
+
+ pix = gtk_widget_render_icon (w,
+ GTK_STOCK_SAVE,
+ GTK_ICON_SIZE_LARGE_TOOLBAR,
+ NULL);
+
+ icons = g_list_append (icons, pix);
+
+ if (i % 2)
+ {
+ pix = gtk_widget_render_icon (w,
+ GTK_STOCK_SAVE,
+ GTK_ICON_SIZE_DIALOG,
+ NULL);
+ icons = g_list_append (icons, pix);
+ }
+
+ if (i % 3)
+ {
+ pix = gtk_widget_render_icon (w,
+ GTK_STOCK_SAVE,
+ GTK_ICON_SIZE_MENU,
+ NULL);
+ icons = g_list_append (icons, pix);
+ }
+
+ gtk_window_set_icon_list (GTK_WINDOW (w), icons);
+
+ g_list_foreach (icons, (GFunc) g_object_unref, NULL);
+ g_list_free (icons);
+
+ gtk_widget_show_all (w);
+
+ ++i;
+ }
+}
diff --git a/src/wm-tester/test-gravity.c b/src/wm-tester/test-gravity.c
new file mode 100644
index 00000000..8e5b581c
--- /dev/null
+++ b/src/wm-tester/test-gravity.c
@@ -0,0 +1,308 @@
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <stdio.h>
+#include <string.h>
+
+static int gravities[10] = {
+ NorthWestGravity,
+ NorthGravity,
+ NorthEastGravity,
+ WestGravity,
+ CenterGravity,
+ EastGravity,
+ SouthWestGravity,
+ SouthGravity,
+ SouthEastGravity,
+ StaticGravity
+};
+
+typedef struct
+{
+ int x, y, width, height;
+} Rectangle;
+
+static Window windows[10];
+static int doubled[10] = { 0, };
+static Rectangle window_rects[10];
+
+#define WINDOW_WIDTH 100
+#define WINDOW_HEIGHT 100
+
+static int x_offset[3] = { 0, - WINDOW_WIDTH/2, -WINDOW_WIDTH };
+static int y_offset[3] = { 0, - WINDOW_HEIGHT/2, -WINDOW_HEIGHT };
+static double screen_x_fraction[3] = { 0, 0.5, 1.0 };
+static double screen_y_fraction[3] = { 0, 0.5, 1.0 };
+static int screen_width;
+static int screen_height;
+
+static const char*
+window_gravity_to_string (int gravity)
+{
+ switch (gravity)
+ {
+ case NorthWestGravity:
+ return "NorthWestGravity";
+ case NorthGravity:
+ return "NorthGravity";
+ case NorthEastGravity:
+ return "NorthEastGravity";
+ case WestGravity:
+ return "WestGravity";
+ case CenterGravity:
+ return "CenterGravity";
+ case EastGravity:
+ return "EastGravity";
+ case SouthWestGravity:
+ return "SouthWestGravity";
+ case SouthGravity:
+ return "SouthGravity";
+ case SouthEastGravity:
+ return "SouthEastGravity";
+ case StaticGravity:
+ return "StaticGravity";
+ default:
+ return "NorthWestGravity";
+ }
+}
+
+static void
+calculate_position (int i, int doubled, int *x, int *y)
+{
+ if (i == 9)
+ {
+ *x = 150;
+ *y = 150;
+ }
+ else
+ {
+ int xoff = x_offset[i % 3];
+ int yoff = y_offset[i / 3];
+ if (doubled)
+ {
+ xoff *= 2;
+ yoff *= 2;
+ }
+
+ *x = screen_x_fraction[i % 3] * screen_width + xoff;
+ *y = screen_y_fraction[i / 3] * screen_height + yoff;
+ }
+}
+
+static int
+find_window (Window window)
+{
+ int i;
+ for (i=0; i<10; i++)
+ {
+ if (windows[i] == window)
+ return i;
+ }
+
+ return -1;
+}
+
+typedef struct {
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long input_mode;
+ unsigned long status;
+} MotifWmHints, MwmHints;
+
+#define MWM_HINTS_FUNCTIONS (1L << 0)
+#define MWM_HINTS_DECORATIONS (1L << 1)
+#define MWM_HINTS_INPUT_MODE (1L << 2)
+#define MWM_HINTS_STATUS (1L << 3)
+
+int main (int argc, char **argv)
+{
+ Display *d;
+ Window w;
+ XSizeHints hints;
+ int i;
+ int screen;
+ XEvent ev;
+ int noframes;
+
+ if (argc > 1 && strcmp (argv[1], "--noframes") == 0)
+ noframes = 1;
+ else
+ noframes = 0;
+
+ d = XOpenDisplay (NULL);
+
+ screen = DefaultScreen (d);
+ screen_width = DisplayWidth (d, screen);
+ screen_height = DisplayHeight (d, screen);
+
+ for (i=0; i<10; i++)
+ {
+ int x, y;
+
+ calculate_position (i, doubled[i], &x, &y);
+
+ w = XCreateSimpleWindow (d, RootWindow (d, screen),
+ x, y, WINDOW_WIDTH, WINDOW_HEIGHT, 0,
+ WhitePixel (d, screen), WhitePixel (d, screen));
+
+ windows[i] = w;
+ window_rects[i].x = x;
+ window_rects[i].y = y;
+ window_rects[i].width = WINDOW_WIDTH;
+ window_rects[i].height = WINDOW_HEIGHT;
+
+ XSelectInput (d, w, ButtonPressMask | ExposureMask | StructureNotifyMask);
+
+ hints.flags = USPosition | PMinSize | PMaxSize | PWinGravity;
+
+ hints.min_width = WINDOW_WIDTH / 2;
+ hints.min_height = WINDOW_HEIGHT / 2;
+
+#if 1
+ /* we constrain max size below the "doubled" size so that
+ * the WM will have to deal with constraints
+ * at the same time it's dealing with configure request
+ */
+ hints.max_width = WINDOW_WIDTH * 2 - WINDOW_WIDTH / 2;
+ hints.max_height = WINDOW_HEIGHT * 2 - WINDOW_HEIGHT / 2;
+#else
+ hints.max_width = WINDOW_WIDTH * 2 + WINDOW_WIDTH / 2;
+ hints.max_height = WINDOW_HEIGHT * 2 + WINDOW_HEIGHT / 2;
+#endif
+ hints.win_gravity = gravities[i];
+
+ XSetWMNormalHints (d, w, &hints);
+
+ XStoreName (d, w, window_gravity_to_string (hints.win_gravity));
+
+ if (noframes)
+ {
+ MotifWmHints mwm;
+ Atom mwm_atom;
+
+ mwm.decorations = 0;
+ mwm.flags = MWM_HINTS_DECORATIONS;
+
+ mwm_atom = XInternAtom (d, "_MOTIF_WM_HINTS", False);
+
+ XChangeProperty (d, w, mwm_atom, mwm_atom,
+ 32, PropModeReplace,
+ (unsigned char *)&mwm,
+ sizeof (MotifWmHints)/sizeof (long));
+ }
+
+ XMapWindow (d, w);
+ }
+
+ while (1)
+ {
+ XNextEvent (d, &ev);
+
+ if (ev.xany.type == ConfigureNotify)
+ {
+ i = find_window (ev.xconfigure.window);
+
+ if (i >= 0)
+ {
+ Window ignored;
+
+ window_rects[i].width = ev.xconfigure.width;
+ window_rects[i].height = ev.xconfigure.height;
+
+ XClearArea (d, windows[i], 0, 0,
+ ev.xconfigure.width,
+ ev.xconfigure.height,
+ True);
+
+ if (!ev.xconfigure.send_event)
+ XTranslateCoordinates (d, windows[i], DefaultRootWindow (d),
+ 0, 0,
+ &window_rects[i].x, &window_rects[i].y,
+ &ignored);
+ else
+ {
+ window_rects[i].x = ev.xconfigure.x;
+ window_rects[i].y = ev.xconfigure.y;
+ }
+ }
+ }
+ else if (ev.xany.type == Expose)
+ {
+ i = find_window (ev.xexpose.window);
+
+ if (i >= 0)
+ {
+ GC gc;
+ XGCValues values;
+ char buf[256];
+
+ values.foreground = BlackPixel (d, screen);
+
+ gc = XCreateGC (d, windows[i],
+ GCForeground, &values);
+
+ sprintf (buf,
+ "%d,%d",
+ window_rects[i].x,
+ window_rects[i].y);
+
+ XDrawString (d, windows[i], gc, 10, 15,
+ buf, strlen (buf));
+
+ sprintf (buf,
+ "%dx%d",
+ window_rects[i].width,
+ window_rects[i].height);
+
+ XDrawString (d, windows[i], gc, 10, 35,
+ buf, strlen (buf));
+
+ XFreeGC (d, gc);
+ }
+ }
+ else if (ev.xany.type == ButtonPress)
+ {
+ i = find_window (ev.xbutton.window);
+
+ if (i >= 0)
+ {
+ /* Button 1 = move, 2 = resize, 3 = both at once */
+
+ if (ev.xbutton.button == Button1)
+ {
+ int x, y;
+
+ calculate_position (i, doubled[i], &x, &y);
+ XMoveWindow (d, windows[i], x, y);
+ }
+ else if (ev.xbutton.button == Button2)
+ {
+ if (doubled[i])
+ XResizeWindow (d, windows[i], WINDOW_WIDTH, WINDOW_HEIGHT);
+ else
+ XResizeWindow (d, windows[i], WINDOW_WIDTH*2, WINDOW_HEIGHT*2);
+
+ doubled[i] = !doubled[i];
+ }
+ else if (ev.xbutton.button == Button3)
+ {
+ int x, y;
+
+ calculate_position (i, !doubled[i], &x, &y);
+
+ if (doubled[i])
+ XMoveResizeWindow (d, windows[i], x, y, WINDOW_WIDTH, WINDOW_HEIGHT);
+ else
+ XMoveResizeWindow (d, windows[i], x, y, WINDOW_WIDTH*2, WINDOW_HEIGHT*2);
+
+ doubled[i] = !doubled[i];
+ }
+ }
+ }
+ }
+
+ /* This program has an infinite loop above so a return statement would
+ * just cause compiler warnings.
+ */
+}
+
diff --git a/src/wm-tester/test-resizing.c b/src/wm-tester/test-resizing.c
new file mode 100644
index 00000000..f9481004
--- /dev/null
+++ b/src/wm-tester/test-resizing.c
@@ -0,0 +1,257 @@
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <stdlib.h>
+#include <glib.h>
+
+static void
+calc_rects (XRectangle *rects, int width, int height)
+{
+ int w = (width - 21) / 3;
+ int h = (height - 21) / 3;
+ int i;
+
+ i = 0;
+ while (i < 9)
+ {
+ rects[i].width = w;
+ rects[i].height = h;
+ ++i;
+ }
+
+ /* NW */
+ rects[0].x = 0;
+ rects[0].y = 0;
+
+ /* N */
+ rects[1].x = width / 2 - w / 2;
+ rects[1].y = 0;
+
+ /* NE */
+ rects[2].x = width - w;
+ rects[2].y = 0;
+
+ /* E */
+ rects[3].x = width - w;
+ rects[3].y = height / 2 - h / 2;
+
+ /* SE */
+ rects[4].x = width - w;
+ rects[4].y = height - h;
+
+ /* S */
+ rects[5].x = width / 2 - w / 2;
+ rects[5].y = height - h;
+
+ /* SW */
+ rects[6].x = 0;
+ rects[6].y = height - h;
+
+ /* W */
+ rects[7].x = 0;
+ rects[7].y = height / 2 - h / 2;
+
+ /* Center */
+ rects[8].x = width / 2 - w / 2;
+ rects[8].y = height / 2 - h / 2;
+}
+
+static Bool
+all_events (Display *display,
+ XEvent *event,
+ XPointer arg)
+{
+ return True;
+}
+
+static void
+get_size (Display *d, Drawable draw,
+ int *xp, int *yp, int *widthp, int *heightp)
+{
+ int x, y;
+ unsigned int width=0, height=0, border=0, depth=0;
+ Window root;
+
+ XGetGeometry (d, draw, &root, &x, &y, &width, &height, &border, &depth);
+
+ if (xp)
+ *xp = x;
+ if (yp)
+ *yp = y;
+ if (widthp)
+ *widthp = width;
+ if (*heightp)
+ *heightp = height;
+}
+
+int
+main (int argc, char **argv)
+{
+ Display *d;
+ Window w, cw;
+ XSizeHints hints;
+ int screen;
+ XEvent ev;
+ int x, y, width, height;
+ Pixmap pix;
+ GC gc;
+ XGCValues gc_vals;
+ XSetWindowAttributes set_attrs;
+ XWindowChanges changes;
+ XRectangle rects[9];
+ gboolean redraw_pending;
+ unsigned int mask;
+
+ d = XOpenDisplay (NULL);
+
+ screen = DefaultScreen (d);
+
+ /* Print some debug spew to show how StaticGravity works */
+ w = XCreateSimpleWindow (d, RootWindow (d, screen),
+ 0, 0, 100, 100, 0,
+ WhitePixel (d, screen),
+ WhitePixel (d, screen));
+ cw = XCreateSimpleWindow (d, w,
+ 0, 0, 100, 100, 0,
+ WhitePixel (d, screen),
+ WhitePixel (d, screen));
+ set_attrs.win_gravity = StaticGravity;
+
+ XChangeWindowAttributes (d, cw,
+ CWWinGravity,
+ &set_attrs);
+
+ get_size (d, w, &x, &y, &width, &height);
+
+ g_print ("Parent is %d,%d %d x %d before configuring parent\n",
+ x, y, width, height);
+
+ get_size (d, cw, &x, &y, &width, &height);
+
+ g_print ("Child is %d,%d %d x %d before configuring parent\n",
+ x, y, width, height);
+
+ changes.x = 10;
+ changes.y = 10;
+ changes.width = 110;
+ changes.height = 110;
+ /* last mask wins */
+ mask = CWX | CWY;
+ mask = CWWidth | CWHeight;
+ mask = CWX | CWY | CWWidth | CWHeight;
+
+ XConfigureWindow (d, w, mask, &changes);
+ XSync (d, False);
+
+ get_size (d, w, &x, &y, &width, &height);
+
+ g_print ("Parent is %d,%d %d x %d after configuring parent\n",
+ x, y, width, height);
+
+ get_size (d, cw, &x, &y, &width, &height);
+
+ g_print ("Child is %d,%d %d x %d after configuring parent\n",
+ x, y, width, height);
+
+ XDestroyWindow (d, w);
+
+ /* The window that gets displayed */
+
+ x = 20;
+ y = 20;
+ width = 100;
+ height = 100;
+
+ calc_rects (rects, width, height);
+
+ w = XCreateSimpleWindow (d, RootWindow (d, screen),
+ x, y, width, height, 0,
+ WhitePixel (d, screen),
+ WhitePixel (d, screen));
+
+ set_attrs.bit_gravity = StaticGravity;
+
+ XChangeWindowAttributes (d, w,
+ CWBitGravity,
+ &set_attrs);
+
+ XSelectInput (d, w,
+ ButtonPressMask | ExposureMask | StructureNotifyMask);
+
+ hints.flags = PMinSize;
+
+ hints.min_width = 100;
+ hints.min_height = 100;
+
+ XSetWMNormalHints (d, w, &hints);
+ XMapWindow (d, w);
+
+ redraw_pending = FALSE;
+ while (1)
+ {
+ XNextEvent (d, &ev);
+
+ switch (ev.xany.type)
+ {
+ case ButtonPress:
+ if (ev.xbutton.button == 3)
+ {
+ g_print ("Exiting on button 3 press\n");
+ exit (0);
+ }
+ break;
+
+ case ConfigureNotify:
+ x = ev.xconfigure.x;
+ y = ev.xconfigure.y;
+ width = ev.xconfigure.width;
+ height = ev.xconfigure.height;
+
+ redraw_pending = TRUE;
+ break;
+
+ case Expose:
+ redraw_pending = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ /* Primitive event compression */
+ if (XCheckIfEvent (d, &ev, all_events, NULL))
+ {
+ XPutBackEvent (d, &ev);
+ }
+ else if (redraw_pending)
+ {
+ calc_rects (rects, width, height);
+
+ pix = XCreatePixmap (d, w, width, height,
+ DefaultDepth (d, screen));
+
+ gc_vals.foreground = WhitePixel (d, screen);
+
+ gc = XCreateGC (d, pix, GCForeground, &gc_vals);
+
+ XFillRectangle (d, pix, gc, 0, 0, width, height);
+
+ /* Draw rectangles at each gravity point */
+ gc_vals.foreground = BlackPixel (d, screen);
+ XChangeGC (d, gc, GCForeground, &gc_vals);
+
+ XFillRectangles (d, pix, gc, rects, G_N_ELEMENTS (rects));
+
+ XCopyArea (d, pix, w, gc, 0, 0, width, height, 0, 0);
+
+ XFreePixmap (d, pix);
+ XFreeGC (d, gc);
+
+ redraw_pending = FALSE;
+ }
+ }
+
+ /* This program has an infinite loop above so a return statement would
+ * just cause compiler warnings.
+ */
+}
+
diff --git a/src/wm-tester/test-size-hints.c b/src/wm-tester/test-size-hints.c
new file mode 100644
index 00000000..72f1b486
--- /dev/null
+++ b/src/wm-tester/test-size-hints.c
@@ -0,0 +1,136 @@
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <stdlib.h>
+#include <glib.h>
+
+static Bool
+all_events (Display *display,
+ XEvent *event,
+ XPointer arg)
+{
+ return True;
+}
+
+#if 0
+static void
+get_size (Display *d, Drawable draw,
+ int *xp, int *yp, int *widthp, int *heightp)
+{
+ int x, y;
+ unsigned int width, height, border, depth;
+ Window root;
+
+ XGetGeometry (d, draw, &root, &x, &y, &width, &height, &border, &depth);
+
+ if (xp)
+ *xp = x;
+ if (yp)
+ *yp = y;
+ if (widthp)
+ *widthp = width;
+ if (*heightp)
+ *heightp = height;
+}
+#endif
+
+int
+main (int argc, char **argv)
+{
+ Display *d;
+ Window zero_min_size;
+ XSizeHints hints;
+ int screen;
+ XEvent ev;
+ int x, y, width, height;
+ Pixmap pix;
+ GC gc;
+ XGCValues gc_vals;
+ gboolean redraw_pending;
+
+ d = XOpenDisplay (NULL);
+
+ screen = DefaultScreen (d);
+
+ x = 0;
+ y = 0;
+ width = 100;
+ height = 100;
+
+ zero_min_size = XCreateSimpleWindow (d, RootWindow (d, screen),
+ x, y, width, height, 0,
+ WhitePixel (d, screen),
+ WhitePixel (d, screen));
+
+ XSelectInput (d, zero_min_size,
+ ButtonPressMask | ExposureMask | StructureNotifyMask);
+
+ hints.flags = PMinSize;
+
+ hints.min_width = 0;
+ hints.min_height = 0;
+
+ XSetWMNormalHints (d, zero_min_size, &hints);
+ XMapWindow (d, zero_min_size);
+
+ redraw_pending = FALSE;
+ while (1)
+ {
+ XNextEvent (d, &ev);
+
+ switch (ev.xany.type)
+ {
+ case ButtonPress:
+ if (ev.xbutton.button == 1)
+ {
+ g_print ("Exiting on button 1 press\n");
+ exit (0);
+ }
+ break;
+
+ case ConfigureNotify:
+ x = ev.xconfigure.x;
+ y = ev.xconfigure.y;
+ width = ev.xconfigure.width;
+ height = ev.xconfigure.height;
+
+ redraw_pending = TRUE;
+ break;
+
+ case Expose:
+ redraw_pending = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ /* Primitive event compression */
+ if (XCheckIfEvent (d, &ev, all_events, NULL))
+ {
+ XPutBackEvent (d, &ev);
+ }
+ else if (redraw_pending)
+ {
+ pix = XCreatePixmap (d, zero_min_size, width, height,
+ DefaultDepth (d, screen));
+
+ gc_vals.foreground = WhitePixel (d, screen);
+
+ gc = XCreateGC (d, pix, GCForeground, &gc_vals);
+
+ XFillRectangle (d, pix, gc, 0, 0, width, height);
+
+ XCopyArea (d, pix, zero_min_size, gc, 0, 0, width, height, 0, 0);
+
+ XFreePixmap (d, pix);
+ XFreeGC (d, gc);
+
+ redraw_pending = FALSE;
+ }
+ }
+
+ /* This program has an infinite loop above so a return statement would
+ * just cause compiler warnings.
+ */
+}
+