From 28a029a4990d2a84f9d6a0b890eba812ea503998 Mon Sep 17 00:00:00 2001 From: Perberos Date: Thu, 1 Dec 2011 23:52:01 -0300 Subject: moving from https://github.com/perberos/mate-desktop-environment --- src/50-marco-desktop-key.xml.in | 20 + src/50-marco-key.xml.in | 269 + src/Makefile.am | 223 + src/compositor/compositor-private.h | 54 + src/compositor/compositor-xrender.c | 3078 ++++++++++ src/compositor/compositor-xrender.h | 31 + src/compositor/compositor.c | 159 + src/core/async-getprop.c | 680 ++ src/core/async-getprop.h | 67 + src/core/atomnames.h | 166 + src/core/bell.c | 397 ++ src/core/bell.h | 108 + src/core/boxes.c | 1926 ++++++ src/core/constraints.c | 1382 +++++ src/core/constraints.h | 48 + src/core/core.c | 779 +++ src/core/delete.c | 266 + src/core/display-private.h | 513 ++ src/core/display.c | 5355 ++++++++++++++++ src/core/edge-resistance.c | 1277 ++++ src/core/edge-resistance.h | 48 + src/core/effects.c | 735 +++ src/core/effects.h | 170 + src/core/errors.c | 288 + src/core/eventqueue.c | 184 + src/core/eventqueue.h | 40 + src/core/frame-private.h | 88 + src/core/frame.c | 421 ++ src/core/group-private.h | 43 + src/core/group-props.c | 234 + src/core/group-props.h | 37 + src/core/group.c | 274 + src/core/group.h | 53 + src/core/iconcache.c | 849 +++ src/core/iconcache.h | 79 + src/core/keybindings.c | 3352 ++++++++++ src/core/keybindings.h | 60 + src/core/main.c | 673 ++ src/core/marco-Xatomtype.h | 136 + src/core/place.c | 932 +++ src/core/place.h | 37 + src/core/prefs.c | 2794 +++++++++ src/core/schema-bindings.c | 195 + src/core/screen-private.h | 226 + src/core/screen.c | 2815 +++++++++ src/core/session.c | 1831 ++++++ src/core/session.h | 91 + src/core/stack.c | 1661 +++++ src/core/stack.h | 402 ++ src/core/testasyncgetprop.c | 497 ++ src/core/testboxes.c | 1416 +++++ src/core/util.c | 641 ++ src/core/window-private.h | 640 ++ src/core/window-props.c | 1553 +++++ src/core/window-props.h | 129 + src/core/window.c | 8178 +++++++++++++++++++++++++ src/core/workspace.c | 1038 ++++ src/core/workspace.h | 113 + src/core/xprops.c | 1238 ++++ src/include/all-keybindings.h | 386 ++ src/include/boxes.h | 290 + src/include/common.h | 300 + src/include/compositor.h | 76 + src/include/core.h | 208 + src/include/display.h | 48 + src/include/errors.h | 51 + src/include/frame.h | 31 + src/include/main.h | 43 + src/include/prefs.h | 233 + src/include/resizepopup.h | 47 + src/include/screen.h | 48 + src/include/tabpopup.h | 67 + src/include/types.h | 31 + src/include/ui.h | 209 + src/include/util.h | 136 + src/include/window.h | 39 + src/include/xprops.h | 227 + src/libmarco-private.pc.in | 12 + src/marco-wm.desktop.in | 20 + src/marco.desktop.in | 17 + src/marco.schemas.in.in | 573 ++ src/themes/ClearlooksRe/metacity-theme-1.xml | 1013 +++ src/themes/Dopple-Left/metacity-theme-1.xml | 1135 ++++ src/themes/Dopple/metacity-theme-1.xml | 1135 ++++ src/themes/DustBlue/button_close_normal.png | Bin 0 -> 3830 bytes src/themes/DustBlue/button_close_prelight.png | Bin 0 -> 1393 bytes src/themes/DustBlue/button_close_pressed.png | Bin 0 -> 4087 bytes src/themes/DustBlue/button_max_normal.png | Bin 0 -> 3185 bytes src/themes/DustBlue/button_max_prelight.png | Bin 0 -> 1326 bytes src/themes/DustBlue/button_max_pressed.png | Bin 0 -> 4029 bytes src/themes/DustBlue/button_menu_normal.png | Bin 0 -> 3450 bytes src/themes/DustBlue/button_menu_prelight.png | Bin 0 -> 4041 bytes src/themes/DustBlue/button_menu_pressed.png | Bin 0 -> 4065 bytes src/themes/DustBlue/button_min_normal.png | Bin 0 -> 3152 bytes src/themes/DustBlue/button_min_prelight.png | Bin 0 -> 1413 bytes src/themes/DustBlue/button_min_pressed.png | Bin 0 -> 4093 bytes src/themes/DustBlue/menu.png | Bin 0 -> 164 bytes src/themes/DustBlue/metacity-theme-1.xml | 409 ++ src/themes/Makefile.am | 53 + src/themes/Spidey-Left/metacity-theme-1.xml | 1086 ++++ src/themes/Spidey/metacity-theme-1.xml | 1086 ++++ src/themes/Splint-Left/metacity-theme-1.xml | 802 +++ src/themes/Splint/metacity-theme-1.xml | 802 +++ src/themes/WinMe/close_normal.png | Bin 0 -> 256 bytes src/themes/WinMe/close_normal_small.png | Bin 0 -> 223 bytes src/themes/WinMe/close_pressed.png | Bin 0 -> 256 bytes src/themes/WinMe/close_pressed_small.png | Bin 0 -> 219 bytes src/themes/WinMe/maximize_normal.png | Bin 0 -> 220 bytes src/themes/WinMe/maximize_pressed.png | Bin 0 -> 241 bytes src/themes/WinMe/metacity-theme-1.xml | 375 ++ src/themes/WinMe/minimize_normal.png | Bin 0 -> 213 bytes src/themes/WinMe/minimize_pressed.png | Bin 0 -> 216 bytes src/themes/WinMe/restore_normal.png | Bin 0 -> 235 bytes src/themes/WinMe/restore_pressed.png | Bin 0 -> 257 bytes src/themes/eOS/close.png | Bin 0 -> 1031 bytes src/themes/eOS/close_unfocused.png | Bin 0 -> 775 bytes src/themes/eOS/close_unfocused_over.png | Bin 0 -> 1031 bytes src/themes/eOS/maximize.png | Bin 0 -> 845 bytes src/themes/eOS/maximize_unfocused.png | Bin 0 -> 775 bytes src/themes/eOS/maximize_unfocused_over.png | Bin 0 -> 845 bytes src/themes/eOS/menu.png | Bin 0 -> 775 bytes src/themes/eOS/menu_prelight.png | Bin 0 -> 755 bytes src/themes/eOS/metacity-theme-1.xml | 537 ++ src/themes/eOS/minimize.png | Bin 0 -> 800 bytes src/themes/eOS/minimize_unfocused.png | Bin 0 -> 775 bytes src/themes/eOS/minimize_unfocused_over.png | Bin 0 -> 800 bytes src/themes/eOS/trough_left.png | Bin 0 -> 322 bytes src/themes/eOS/trough_left_unfocused.png | Bin 0 -> 324 bytes src/themes/eOS/trough_middle.png | Bin 0 -> 188 bytes src/themes/eOS/trough_middle_unfocused.png | Bin 0 -> 195 bytes src/themes/eOS/trough_right.png | Bin 0 -> 330 bytes src/themes/eOS/trough_right_unfocused.png | Bin 0 -> 335 bytes src/themes/eOS/unmaximize.png | Bin 0 -> 845 bytes src/themes/eOS/unmaximize_unfocused.png | Bin 0 -> 775 bytes src/themes/eOS/unmaximize_unfocused_over.png | Bin 0 -> 845 bytes src/tools/Makefile.am | 33 + src/tools/marco-grayscale.c | 109 + src/tools/marco-mag.c | 278 + src/tools/marco-message.c | 187 + src/tools/marco-window-demo.c | 1016 +++ src/tools/marco-window-demo.png | Bin 0 -> 3453 bytes src/ui/draw-workspace.c | 229 + src/ui/draw-workspace.h | 61 + src/ui/fixedtip.c | 133 + src/ui/fixedtip.h | 69 + src/ui/frames.c | 2940 +++++++++ src/ui/frames.h | 163 + src/ui/gradient.c | 842 +++ src/ui/gradient.h | 65 + src/ui/menu.c | 509 ++ src/ui/menu.h | 51 + src/ui/metaaccellabel.c | 456 ++ src/ui/metaaccellabel.h | 106 + src/ui/preview-widget.c | 601 ++ src/ui/preview-widget.h | 87 + src/ui/resizepopup.c | 217 + src/ui/tabpopup.c | 967 +++ src/ui/testgradient.c | 336 + src/ui/theme-parser.c | 4212 +++++++++++++ src/ui/theme-parser.h | 32 + src/ui/theme-viewer.c | 1338 ++++ src/ui/theme.c | 6652 ++++++++++++++++++++ src/ui/theme.h | 1190 ++++ src/ui/themewidget.c | 183 + src/ui/themewidget.h | 78 + src/ui/ui.c | 1117 ++++ src/wm-tester/Makefile.am | 25 + src/wm-tester/Makefile.in | 574 ++ src/wm-tester/focus-window.c | 37 + src/wm-tester/main.c | 245 + src/wm-tester/test-gravity.c | 308 + src/wm-tester/test-resizing.c | 257 + src/wm-tester/test-size-hints.c | 136 + 173 files changed, 88353 insertions(+) create mode 100644 src/50-marco-desktop-key.xml.in create mode 100644 src/50-marco-key.xml.in create mode 100644 src/Makefile.am create mode 100644 src/compositor/compositor-private.h create mode 100644 src/compositor/compositor-xrender.c create mode 100644 src/compositor/compositor-xrender.h create mode 100644 src/compositor/compositor.c create mode 100644 src/core/async-getprop.c create mode 100644 src/core/async-getprop.h create mode 100644 src/core/atomnames.h create mode 100644 src/core/bell.c create mode 100644 src/core/bell.h create mode 100644 src/core/boxes.c create mode 100644 src/core/constraints.c create mode 100644 src/core/constraints.h create mode 100644 src/core/core.c create mode 100644 src/core/delete.c create mode 100644 src/core/display-private.h create mode 100644 src/core/display.c create mode 100644 src/core/edge-resistance.c create mode 100644 src/core/edge-resistance.h create mode 100644 src/core/effects.c create mode 100644 src/core/effects.h create mode 100644 src/core/errors.c create mode 100644 src/core/eventqueue.c create mode 100644 src/core/eventqueue.h create mode 100644 src/core/frame-private.h create mode 100644 src/core/frame.c create mode 100644 src/core/group-private.h create mode 100644 src/core/group-props.c create mode 100644 src/core/group-props.h create mode 100644 src/core/group.c create mode 100644 src/core/group.h create mode 100644 src/core/iconcache.c create mode 100644 src/core/iconcache.h create mode 100644 src/core/keybindings.c create mode 100644 src/core/keybindings.h create mode 100644 src/core/main.c create mode 100644 src/core/marco-Xatomtype.h create mode 100644 src/core/place.c create mode 100644 src/core/place.h create mode 100644 src/core/prefs.c create mode 100644 src/core/schema-bindings.c create mode 100644 src/core/screen-private.h create mode 100644 src/core/screen.c create mode 100644 src/core/session.c create mode 100644 src/core/session.h create mode 100644 src/core/stack.c create mode 100644 src/core/stack.h create mode 100644 src/core/testasyncgetprop.c create mode 100644 src/core/testboxes.c create mode 100644 src/core/util.c create mode 100644 src/core/window-private.h create mode 100644 src/core/window-props.c create mode 100644 src/core/window-props.h create mode 100644 src/core/window.c create mode 100644 src/core/workspace.c create mode 100644 src/core/workspace.h create mode 100644 src/core/xprops.c create mode 100644 src/include/all-keybindings.h create mode 100644 src/include/boxes.h create mode 100644 src/include/common.h create mode 100644 src/include/compositor.h create mode 100644 src/include/core.h create mode 100644 src/include/display.h create mode 100644 src/include/errors.h create mode 100644 src/include/frame.h create mode 100644 src/include/main.h create mode 100644 src/include/prefs.h create mode 100644 src/include/resizepopup.h create mode 100644 src/include/screen.h create mode 100644 src/include/tabpopup.h create mode 100644 src/include/types.h create mode 100644 src/include/ui.h create mode 100644 src/include/util.h create mode 100644 src/include/window.h create mode 100644 src/include/xprops.h create mode 100644 src/libmarco-private.pc.in create mode 100644 src/marco-wm.desktop.in create mode 100644 src/marco.desktop.in create mode 100644 src/marco.schemas.in.in create mode 100644 src/themes/ClearlooksRe/metacity-theme-1.xml create mode 100644 src/themes/Dopple-Left/metacity-theme-1.xml create mode 100644 src/themes/Dopple/metacity-theme-1.xml create mode 100644 src/themes/DustBlue/button_close_normal.png create mode 100644 src/themes/DustBlue/button_close_prelight.png create mode 100644 src/themes/DustBlue/button_close_pressed.png create mode 100644 src/themes/DustBlue/button_max_normal.png create mode 100644 src/themes/DustBlue/button_max_prelight.png create mode 100644 src/themes/DustBlue/button_max_pressed.png create mode 100644 src/themes/DustBlue/button_menu_normal.png create mode 100644 src/themes/DustBlue/button_menu_prelight.png create mode 100644 src/themes/DustBlue/button_menu_pressed.png create mode 100644 src/themes/DustBlue/button_min_normal.png create mode 100644 src/themes/DustBlue/button_min_prelight.png create mode 100644 src/themes/DustBlue/button_min_pressed.png create mode 100644 src/themes/DustBlue/menu.png create mode 100644 src/themes/DustBlue/metacity-theme-1.xml create mode 100644 src/themes/Makefile.am create mode 100644 src/themes/Spidey-Left/metacity-theme-1.xml create mode 100644 src/themes/Spidey/metacity-theme-1.xml create mode 100644 src/themes/Splint-Left/metacity-theme-1.xml create mode 100644 src/themes/Splint/metacity-theme-1.xml create mode 100644 src/themes/WinMe/close_normal.png create mode 100644 src/themes/WinMe/close_normal_small.png create mode 100644 src/themes/WinMe/close_pressed.png create mode 100644 src/themes/WinMe/close_pressed_small.png create mode 100644 src/themes/WinMe/maximize_normal.png create mode 100644 src/themes/WinMe/maximize_pressed.png create mode 100644 src/themes/WinMe/metacity-theme-1.xml create mode 100644 src/themes/WinMe/minimize_normal.png create mode 100644 src/themes/WinMe/minimize_pressed.png create mode 100644 src/themes/WinMe/restore_normal.png create mode 100644 src/themes/WinMe/restore_pressed.png create mode 100644 src/themes/eOS/close.png create mode 100644 src/themes/eOS/close_unfocused.png create mode 100644 src/themes/eOS/close_unfocused_over.png create mode 100644 src/themes/eOS/maximize.png create mode 100644 src/themes/eOS/maximize_unfocused.png create mode 100644 src/themes/eOS/maximize_unfocused_over.png create mode 100644 src/themes/eOS/menu.png create mode 100644 src/themes/eOS/menu_prelight.png create mode 100644 src/themes/eOS/metacity-theme-1.xml create mode 100644 src/themes/eOS/minimize.png create mode 100644 src/themes/eOS/minimize_unfocused.png create mode 100644 src/themes/eOS/minimize_unfocused_over.png create mode 100644 src/themes/eOS/trough_left.png create mode 100644 src/themes/eOS/trough_left_unfocused.png create mode 100644 src/themes/eOS/trough_middle.png create mode 100644 src/themes/eOS/trough_middle_unfocused.png create mode 100644 src/themes/eOS/trough_right.png create mode 100644 src/themes/eOS/trough_right_unfocused.png create mode 100644 src/themes/eOS/unmaximize.png create mode 100644 src/themes/eOS/unmaximize_unfocused.png create mode 100644 src/themes/eOS/unmaximize_unfocused_over.png create mode 100644 src/tools/Makefile.am create mode 100644 src/tools/marco-grayscale.c create mode 100644 src/tools/marco-mag.c create mode 100644 src/tools/marco-message.c create mode 100644 src/tools/marco-window-demo.c create mode 100644 src/tools/marco-window-demo.png create mode 100644 src/ui/draw-workspace.c create mode 100644 src/ui/draw-workspace.h create mode 100644 src/ui/fixedtip.c create mode 100644 src/ui/fixedtip.h create mode 100644 src/ui/frames.c create mode 100644 src/ui/frames.h create mode 100644 src/ui/gradient.c create mode 100644 src/ui/gradient.h create mode 100644 src/ui/menu.c create mode 100644 src/ui/menu.h create mode 100644 src/ui/metaaccellabel.c create mode 100644 src/ui/metaaccellabel.h create mode 100644 src/ui/preview-widget.c create mode 100644 src/ui/preview-widget.h create mode 100644 src/ui/resizepopup.c create mode 100644 src/ui/tabpopup.c create mode 100644 src/ui/testgradient.c create mode 100644 src/ui/theme-parser.c create mode 100644 src/ui/theme-parser.h create mode 100644 src/ui/theme-viewer.c create mode 100644 src/ui/theme.c create mode 100644 src/ui/theme.h create mode 100644 src/ui/themewidget.c create mode 100644 src/ui/themewidget.h create mode 100644 src/ui/ui.c create mode 100644 src/wm-tester/Makefile.am create mode 100644 src/wm-tester/Makefile.in create mode 100644 src/wm-tester/focus-window.c create mode 100644 src/wm-tester/main.c create mode 100644 src/wm-tester/test-gravity.c create mode 100644 src/wm-tester/test-resizing.c create mode 100644 src/wm-tester/test-size-hints.c (limited to 'src') 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 @@ + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + +#ifdef HAVE_COMPOSITE_EXTENSIONS + +#include +#include +#include +#include + +#include + +#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 +#include +#include +#include +#include +#include + +#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 +#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 + +#undef DEBUG_SPEW +#ifdef DEBUG_SPEW +#include +#endif + +#include "async-getprop.h" + +#define NEED_REPLIES +#include + +#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 +#include + +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: + * . + * + * 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 +#include "bell.h" +#include "screen-private.h" +#include "prefs.h" +#include + +/** + * 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: + * . + */ + +/* + * 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 +#ifdef HAVE_XKB +#include +#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 + * () + * 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 /* 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 +#include "constraints.h" +#include "workspace.h" +#include "place.h" +#include "prefs.h" + +#include +#include + +#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 +#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 +#include "util.h" +#include "window-private.h" +#include "errors.h" +#include "workspace.h" + +#include +#include +#include +#include +#include +#include +#include + +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 (_("%s is not responding."), + window_title); + window_content = g_strdup_printf ( + "%s\n\n%s", + 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 +#include +#include "eventqueue.h" +#include "common.h" +#include "boxes.h" +#include "display.h" + +#ifdef HAVE_STARTUP_NOTIFICATION + #include +#endif + +#ifdef HAVE_XSYNC + #include +#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 +#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 +#include + +#ifdef HAVE_SOLARIS_XINERAMA + #include +#endif + +#ifdef HAVE_XFREE_XINERAMA + #include +#endif + +#ifdef HAVE_RANDR + #include +#endif + +#ifdef HAVE_SHAPE + #include +#endif + +#ifdef HAVE_RENDER + #include +#endif + +#ifdef HAVE_XKB + #include +#endif + +#ifdef HAVE_XCURSOR + #include +#endif + +#ifdef HAVE_COMPOSITE_EXTENSIONS + #include + #include + #include + #include +#endif + +#include + +#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, ×tamp)) + 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, ×tamp)) + 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 +#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 +#include "effects.h" +#include "display-private.h" +#include "ui.h" +#include "window-private.h" +#include "prefs.h" + +#ifdef HAVE_SHAPE +#include +#endif + +#define META_MINIMIZE_ANIMATION_LENGTH 0.25 +#define META_SHADE_ANIMATION_LENGTH 0.2 + +#include + +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 (¤t_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 +#include "errors.h" +#include "display-private.h" +#include +#include +#include + +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 + +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 +#include + +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 +#include "frame-private.h" +#include "bell.h" +#include "errors.h" +#include "keybindings.h" + +#ifdef HAVE_RENDER +#include +#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 +#include "group-props.h" +#include "group-private.h" +#include "xprops.h" +#include + +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 +#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 +#include "iconcache.h" +#include "ui.h" +#include "errors.h" + +#include + +/* 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 +#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 +#include +#include +#include + +#ifdef HAVE_XKB +#include +#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; iflags & 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 " + "%s:\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 +#include "main.h" +#include "util.h" +#include "display-private.h" +#include "errors.h" +#include "ui.h" +#include "session.h" +#include "prefs.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * 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; imessage); + 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 + +#include "place.h" +#include "workspace.h" +#include "prefs.h" +#include +#include +#include + +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 +#include "prefs.h" +#include "ui.h" +#include "util.h" +#ifdef HAVE_MATECONF +#include +#endif +#include +#include + +#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 "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 +#include +#include +#include + +#include +#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, " \n"); + fprintf (target_file, " /schemas/apps/marco/%s_keybindings/%s\n", + keybinding_type, name); + fprintf (target_file, " /apps/marco/%s_keybindings/%s\n", + keybinding_type, name); + fprintf (target_file, " marco\n"); + fprintf (target_file, " string\n"); + fprintf (target_file, " %s\n", escaped_default_value); + + fprintf (target_file, " \n"); + fprintf (target_file, " %s\n", description); + fprintf (target_file, " %s\n", + can_reverse? about_reversible_keybindings: + about_keybindings); + fprintf (target_file, " \n"); + fprintf (target_file, " \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, "")) + 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 \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 \"a\" or \"F1\".\n\n"\ + "The parser is fairly liberal and allows "\ + "lower or upper case, and also abbreviations such as \"\" and " \ + "\"\". 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 \"a\" or \"F1\".\n\n"\ + "The parser is fairly liberal and allows "\ + "lower or upper case, and also abbreviations such as \"\" and " \ + "\"\". 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 +#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 +#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 +#endif +#ifdef HAVE_XFREE_XINERAMA +#include +#endif + +#include +#include +#include +#include + +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, + ¤t_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(¤t->rect, &input->rect)) || + (direction == META_SCREEN_LEFT && + input->rect.x == current->rect.x + current->rect.width && + meta_rectangle_vert_overlap(¤t->rect, &input->rect)) || + (direction == META_SCREEN_UP && + input->rect.y == current->rect.y + current->rect.height && + meta_rectangle_horiz_overlap(¤t->rect, &input->rect)) || + (direction == META_SCREEN_DOWN && + current->rect.y == input->rect.y + input->rect.height && + meta_rectangle_horiz_overlap(¤t->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 + +#include "session.h" +#include + +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 /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: + * + * + * + * + * + * + * + * + * + * Note that attributes on 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, "\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, + " \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 (" \n", outfile); + + /* Minimized */ + if (window->minimized) + fputs (" \n", outfile); + + /* Maximized */ + if (META_WINDOW_MAXIMIZED (window)) + { + fprintf (outfile, + " \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, + " \n", n); + } + + /* Gravity */ + { + int x, y, w, h; + meta_window_get_geometry (window, &x, &y, &w, &h); + + fprintf (outfile, + " \n", + x, y, w, h, + meta_gravity_to_string (window->size_hints.win_gravity)); + } + + fputs (" \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 ("\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, + _(" 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 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 "save current setup" " + "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 +#include "stack.h" +#include "window-private.h" +#include "errors.h" +#include "frame-private.h" +#include "group.h" +#include "prefs.h" +#include "workspace.h" + +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef NULL +#define NULL ((void*) 0) +#endif + +#ifdef HAVE_BACKTRACE +#include +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 (¤t_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 (¤t_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 (¤t_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 (¤t_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 +#include +#include +#include /* Just for the definition of the various gravities */ +#include /* 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 +#include "util.h" +#include "main.h" + +#include +#include +#include +#include +#include +#include /* must explicitly be included for Solaris; #326746 */ +#include /* Just for the definition of the various gravities */ + +#ifdef HAVE_BACKTRACE +#include +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 +#include "window.h" +#include "screen-private.h" +#include "util.h" +#include "stack.h" +#include "iconcache.h" +#include +#include + +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 +#include "window-props.h" +#include "errors.h" +#include "xprops.h" +#include "frame-private.h" +#include "group.h" +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_GTOP +#include +#include +#include +#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; idisplay, + 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; idisplay, + 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 +#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 +#include + +#ifdef HAVE_SHAPE +#include +#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; queuenumis_in_queues & 1<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<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; queuenumunmanaging) + 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<desc, + meta_window_queue_names[queuenum]); + + /* So, mark it as being in this queue. */ + window->is_in_queues |= 1<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 (¤t_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 (¤t_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 (¤t_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 +#include "workspace.h" +#include "errors.h" +#include "prefs.h" +#include +#include +#include + +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 +#include "xprops.h" +#include "errors.h" +#include "util.h" +#include "async-getprop.h" +#include "ui.h" +#include "marco-Xatomtype.h" +#include +#include +#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: .) + * + * 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_() 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, "Left", + _("Switch to workspace on the left of the current workspace")) + +keybind (switch_to_workspace_right, handle_switch_to_workspace, + META_MOTION_RIGHT, 0, "Right", + _("Switch to workspace on the right of the current workspace")) + +keybind (switch_to_workspace_up, handle_switch_to_workspace, + META_MOTION_UP, 0, "Up", + _("Switch to workspace above the current workspace")) + +keybind (switch_to_workspace_down, handle_switch_to_workspace, + META_MOTION_DOWN, 0, "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, "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, "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, "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, "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, "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, "d", + _("Hide all normal windows and set focus to the desktop")) +keybind (panel_main_menu, handle_panel, + META_KEYBINDING_ACTION_PANEL_MAIN_MENU, 0, "F1", + _("Show the panel's main menu")) +keybind (panel_run_dialog, handle_panel, + META_KEYBINDING_ACTION_PANEL_RUN_DIALOG, 0, "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,"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, "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, "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, "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, "F9", + _("Minimize window")) +keybind (close, handle_close, 0, BINDING_PER_WINDOW, "F4", + _("Close window")) +keybind (begin_move, handle_begin_move, 0, BINDING_PER_WINDOW, "F7", + _("Move window")) +keybind (begin_resize, handle_begin_resize, 0, BINDING_PER_WINDOW, "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, "Left", + _("Move window one workspace to the left")) +keybind (move_to_workspace_right, handle_move_to_workspace, + META_MOTION_RIGHT, BINDING_PER_WINDOW, "Right", + _("Move window one workspace to the right")) +keybind (move_to_workspace_up, handle_move_to_workspace, + META_MOTION_UP, BINDING_PER_WINDOW, "Up", + _("Move window one workspace up")) +keybind (move_to_workspace_down, handle_move_to_workspace, + META_MOTION_DOWN, BINDING_PER_WINDOW, "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 +#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 +#include + +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 +#include + +#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 +#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 +#include + +#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 + +#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 + +#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 + +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 + +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 +#include +#include + +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 +#include +#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 +#include +#include + +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 +#include +#include +#include + +/* 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 +#include + +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 +#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 +#include + +#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 + +#include "display.h" +#include + +#ifdef HAVE_XSYNC +#include +#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 @@ + + + + + + + /schemas/apps/marco/general/mouse_button_modifier + /apps/marco/general/mouse_button_modifier + marco + string + <Alt> + + Modifier to use for modified window click actions + + 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 "<Alt>" or "<Super>" + for example. + + + + + + /schemas/apps/marco/general/resize_with_right_button + /apps/marco/general/resize_with_right_button + marco + bool + true + + Whether to resize with the right button + + 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. + + + + + + /schemas/apps/marco/general/button_layout + /apps/marco/general/button_layout + marco + string + menu:minimize,maximize,close + + Arrangement of buttons on the titlebar + + 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. + + + + + + /schemas/apps/marco/general/focus_mode + /apps/marco/general/focus_mode + marco + string + click + + Window focus mode + + 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. + + + + + + /schemas/apps/marco/general/focus_new_windows + /apps/marco/general/focus_new_windows + marco + string + smart + + Control how new windows get focus + + 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. + + + + + + /schemas/apps/marco/general/raise_on_click + /apps/marco/general/raise_on_click + marco + bool + true + + Whether raising should be a side-effect of other user + interactions + + 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. + + + + + + /schemas/apps/marco/general/action_double_click_titlebar + /apps/marco/general/action_double_click_titlebar + marco + string + toggle_maximize + + Action on title bar double-click + + 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. + + + + + + /schemas/apps/marco/general/action_middle_click_titlebar + /apps/marco/general/action_middle_click_titlebar + marco + string + lower + + Action on title bar middle-click + + 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. + + + + + + /schemas/apps/marco/general/action_right_click_titlebar + /apps/marco/general/action_right_click_titlebar + marco + string + menu + + Action on title bar right-click + + 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. + + + + + + /schemas/apps/marco/general/auto_raise + /apps/marco/general/auto_raise + marco + bool + false + + Automatically raises the focused window + + 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. + + + + + + /schemas/apps/marco/general/auto_raise_delay + /apps/marco/general/auto_raise_delay + marco + int + 500 + + Delay in milliseconds for the auto raise option + + The time delay before raising a window if auto_raise is set to + true. The delay is given in thousandths of a second. + + + + + + /schemas/apps/marco/general/theme + /apps/marco/general/theme + marco + string + Spidey-Left + + Current theme + + The theme determines the appearance of window borders, + titlebar, and so forth. + + + + + + /schemas/apps/marco/general/titlebar_uses_system_font + /apps/marco/general/titlebar_uses_system_font + marco + bool + false + + Use standard system font in window titles + + If true, ignore the titlebar_font + option, and use the standard application font for window + titles. + + + + + + /schemas/apps/marco/general/titlebar_font + /apps/marco/general/titlebar_font + marco + string + Sans Bold 10 + + Window title font + + 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. + + + + + + /schemas/apps/marco/general/num_workspaces + /apps/marco/general/num_workspaces + marco + int + 4 + + Number of workspaces + + 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. + + + + + + /schemas/apps/marco/general/visual_bell + /apps/marco/general/visual_bell + marco + bool + false + + Enable Visual Bell + + 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. + + + + + + /schemas/apps/marco/general/audible_bell + /apps/marco/general/audible_bell + marco + bool + true + + System Bell is Audible + + Determines whether applications or the system can generate + audible 'beeps'; may be used in conjunction with 'visual bell' to + allow silent 'beeps'. + + + + + + /schemas/apps/marco/general/visual_bell_type + /apps/marco/general/visual_bell_type + marco + string + fullscreen + + Visual Bell Type + + 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. + + + + + + /schemas/apps/marco/general/compositing_manager + /apps/marco/general/compositing_manager + marco + bool + false + + Compositing Manager + + Determines whether Marco is a compositing manager. + + + + + + /schemas/apps/marco/workspace_names/name + /apps/marco/workspace_names/name_1 + /apps/marco/workspace_names/name_2 + /apps/marco/workspace_names/name_3 + /apps/marco/workspace_names/name_4 + /apps/marco/workspace_names/name_5 + /apps/marco/workspace_names/name_6 + /apps/marco/workspace_names/name_7 + /apps/marco/workspace_names/name_8 + /apps/marco/workspace_names/name_9 + /apps/marco/workspace_names/name_10 + /apps/marco/workspace_names/name_11 + /apps/marco/workspace_names/name_12 + /apps/marco/workspace_names/name_13 + /apps/marco/workspace_names/name_14 + /apps/marco/workspace_names/name_15 + /apps/marco/workspace_names/name_16 + marco + string + + + Name of workspace + + The name of a workspace. + + + + + + /schemas/apps/marco/general/reduced_resources + /apps/marco/general/reduced_resources + marco + bool + false + + If true, trade off usability for less resource usage + + 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. + + + + + + + + /schemas/apps/marco/global_keybindings/run_command + /apps/marco/global_keybindings/run_command_1 + /apps/marco/global_keybindings/run_command_2 + /apps/marco/global_keybindings/run_command_3 + /apps/marco/global_keybindings/run_command_4 + /apps/marco/global_keybindings/run_command_5 + /apps/marco/global_keybindings/run_command_6 + /apps/marco/global_keybindings/run_command_7 + /apps/marco/global_keybindings/run_command_8 + /apps/marco/global_keybindings/run_command_9 + /apps/marco/global_keybindings/run_command_10 + /apps/marco/global_keybindings/run_command_11 + /apps/marco/global_keybindings/run_command_12 + marco + string + disabled + + Run a defined command + + The keybinding that runs the correspondingly-numbered + command in /apps/marco/keybinding_commands + + The format looks like "<Control>a" or + "<Shift><Alt>F1". + + 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. + + + + + + /schemas/apps/marco/keybinding_commands/command + /apps/marco/keybinding_commands/command_1 + /apps/marco/keybinding_commands/command_2 + /apps/marco/keybinding_commands/command_3 + /apps/marco/keybinding_commands/command_4 + /apps/marco/keybinding_commands/command_5 + /apps/marco/keybinding_commands/command_6 + /apps/marco/keybinding_commands/command_7 + /apps/marco/keybinding_commands/command_8 + /apps/marco/keybinding_commands/command_9 + /apps/marco/keybinding_commands/command_10 + /apps/marco/keybinding_commands/command_11 + /apps/marco/keybinding_commands/command_12 + marco + string + + + Commands to run in response to keybindings + + 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. + + + + + + /schemas/apps/marco/keybinding_commands/command_screenshot + /apps/marco/keybinding_commands/command_screenshot + marco + string + mate-screenshot + + The screenshot command + + The /apps/marco/global_keybindings/run_command_screenshot + key defines a keybinding which causes the command specified + by this setting to be invoked. + + + + + + /schemas/apps/marco/keybinding_commands/command_window_screenshot + /apps/marco/keybinding_commands/command_window_screenshot + marco + string + mate-screenshot --window + + The window screenshot command + + The /apps/marco/global_keybindings/run_command_window_screenshot + key defines a keybinding which causes the command specified + by this setting to be invoked. + + + + + + + + + + + /schemas/apps/marco/general/application_based + /apps/marco/general/application_based + marco + bool + false + + (Not implemented) Navigation works in terms of applications not windows + + 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. + + + + + + /schemas/apps/marco/general/disable_workarounds + /apps/marco/general/disable_workarounds + marco + bool + false + + Disable misfeatures that are required by old or broken + applications + + 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. + + + + + + + + + + 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 @@ + + + + + ClearlooksRe + Daniel Borgmann <daniel.borgmann@gmail.com>, Andrea Cimitan <andrea.cimitan@gmail.com>, Germán Augusto Perugorria <perberos@gmail.com> +  2005-2007 Daniel Borgmann, Andrea Cimitan. 2010 Perberos + December, 2010 + The Clearlooks "Gummy" Metacity Theme + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <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 Binary files /dev/null and b/src/themes/DustBlue/button_close_normal.png 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 Binary files /dev/null and b/src/themes/DustBlue/button_close_prelight.png 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 Binary files /dev/null and b/src/themes/DustBlue/button_close_pressed.png 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 Binary files /dev/null and b/src/themes/DustBlue/button_max_normal.png 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 Binary files /dev/null and b/src/themes/DustBlue/button_max_prelight.png 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 Binary files /dev/null and b/src/themes/DustBlue/button_max_pressed.png 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 Binary files /dev/null and b/src/themes/DustBlue/button_menu_normal.png 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 Binary files /dev/null and b/src/themes/DustBlue/button_menu_prelight.png 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 Binary files /dev/null and b/src/themes/DustBlue/button_menu_pressed.png 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 Binary files /dev/null and b/src/themes/DustBlue/button_min_normal.png 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 Binary files /dev/null and b/src/themes/DustBlue/button_min_prelight.png 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 Binary files /dev/null and b/src/themes/DustBlue/button_min_pressed.png differ diff --git a/src/themes/DustBlue/menu.png b/src/themes/DustBlue/menu.png new file mode 100644 index 00000000..4610e233 Binary files /dev/null and b/src/themes/DustBlue/menu.png 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 Binary files /dev/null and b/src/themes/WinMe/close_normal.png 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 Binary files /dev/null and b/src/themes/WinMe/close_normal_small.png differ diff --git a/src/themes/WinMe/close_pressed.png b/src/themes/WinMe/close_pressed.png new file mode 100644 index 00000000..42970c2d Binary files /dev/null and b/src/themes/WinMe/close_pressed.png 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 Binary files /dev/null and b/src/themes/WinMe/close_pressed_small.png differ diff --git a/src/themes/WinMe/maximize_normal.png b/src/themes/WinMe/maximize_normal.png new file mode 100644 index 00000000..41b7694a Binary files /dev/null and b/src/themes/WinMe/maximize_normal.png differ diff --git a/src/themes/WinMe/maximize_pressed.png b/src/themes/WinMe/maximize_pressed.png new file mode 100644 index 00000000..68137306 Binary files /dev/null and b/src/themes/WinMe/maximize_pressed.png 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> 2007 Srivatsa Kanchi <srivatsa_nk@dataone.in>, 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 Binary files /dev/null and b/src/themes/WinMe/minimize_normal.png differ diff --git a/src/themes/WinMe/minimize_pressed.png b/src/themes/WinMe/minimize_pressed.png new file mode 100644 index 00000000..4b873d15 Binary files /dev/null and b/src/themes/WinMe/minimize_pressed.png differ diff --git a/src/themes/WinMe/restore_normal.png b/src/themes/WinMe/restore_normal.png new file mode 100644 index 00000000..95ab85ec Binary files /dev/null and b/src/themes/WinMe/restore_normal.png differ diff --git a/src/themes/WinMe/restore_pressed.png b/src/themes/WinMe/restore_pressed.png new file mode 100644 index 00000000..2b117058 Binary files /dev/null and b/src/themes/WinMe/restore_pressed.png differ diff --git a/src/themes/eOS/close.png b/src/themes/eOS/close.png new file mode 100644 index 00000000..6ba9495e Binary files /dev/null and b/src/themes/eOS/close.png differ diff --git a/src/themes/eOS/close_unfocused.png b/src/themes/eOS/close_unfocused.png new file mode 100644 index 00000000..bdff535c Binary files /dev/null and b/src/themes/eOS/close_unfocused.png 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 Binary files /dev/null and b/src/themes/eOS/close_unfocused_over.png differ diff --git a/src/themes/eOS/maximize.png b/src/themes/eOS/maximize.png new file mode 100644 index 00000000..785aa133 Binary files /dev/null and b/src/themes/eOS/maximize.png differ diff --git a/src/themes/eOS/maximize_unfocused.png b/src/themes/eOS/maximize_unfocused.png new file mode 100644 index 00000000..4c76ed67 Binary files /dev/null and b/src/themes/eOS/maximize_unfocused.png 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 Binary files /dev/null and b/src/themes/eOS/maximize_unfocused_over.png differ diff --git a/src/themes/eOS/menu.png b/src/themes/eOS/menu.png new file mode 100644 index 00000000..1e02701c Binary files /dev/null and b/src/themes/eOS/menu.png differ diff --git a/src/themes/eOS/menu_prelight.png b/src/themes/eOS/menu_prelight.png new file mode 100644 index 00000000..c7dbd0a2 Binary files /dev/null and b/src/themes/eOS/menu_prelight.png 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 Binary files /dev/null and b/src/themes/eOS/minimize.png differ diff --git a/src/themes/eOS/minimize_unfocused.png b/src/themes/eOS/minimize_unfocused.png new file mode 100644 index 00000000..9bd4d273 Binary files /dev/null and b/src/themes/eOS/minimize_unfocused.png 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 Binary files /dev/null and b/src/themes/eOS/minimize_unfocused_over.png differ diff --git a/src/themes/eOS/trough_left.png b/src/themes/eOS/trough_left.png new file mode 100644 index 00000000..070d3da0 Binary files /dev/null and b/src/themes/eOS/trough_left.png 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 Binary files /dev/null and b/src/themes/eOS/trough_left_unfocused.png differ diff --git a/src/themes/eOS/trough_middle.png b/src/themes/eOS/trough_middle.png new file mode 100644 index 00000000..28238835 Binary files /dev/null and b/src/themes/eOS/trough_middle.png 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 Binary files /dev/null and b/src/themes/eOS/trough_middle_unfocused.png differ diff --git a/src/themes/eOS/trough_right.png b/src/themes/eOS/trough_right.png new file mode 100644 index 00000000..84bfad11 Binary files /dev/null and b/src/themes/eOS/trough_right.png 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 Binary files /dev/null and b/src/themes/eOS/trough_right_unfocused.png differ diff --git a/src/themes/eOS/unmaximize.png b/src/themes/eOS/unmaximize.png new file mode 100644 index 00000000..785aa133 Binary files /dev/null and b/src/themes/eOS/unmaximize.png differ diff --git a/src/themes/eOS/unmaximize_unfocused.png b/src/themes/eOS/unmaximize_unfocused.png new file mode 100644 index 00000000..d7c35fc1 Binary files /dev/null and b/src/themes/eOS/unmaximize_unfocused.png 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 Binary files /dev/null and b/src/themes/eOS/unmaximize_unfocused_over.png 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 Binary files /dev/null and b/src/tools/marco-window-demo.png 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, >k_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 <jorn@nl.linux.org> + * + * 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 <jorn@nl.linux.org> */ + +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. + */ +} + -- cgit v1.2.1