diff --git a/AUTHORS.pekwm b/AUTHORS.pekwm new file mode 100644 index 0000000..7dd29f9 --- /dev/null +++ b/AUTHORS.pekwm @@ -0,0 +1,52 @@ +-- AUTHORS for pekwm + +Author: + + * Claes Nasten + +Patchers: + + * Andreas + - Bug fixing. + - Code cleanup. + + * Jyri Jokinen + - Documentation since 0.1.3. + + * Rando Christensen + - Autoconf scripts. + - Documentation since 0.1.1. + - Ideas, bug reports and moral support. + + * Lurene Frenier + - Make file patches. + +Moral support: + + * Alexandra Walford + - Moral support. + - English and CSS support. + + * Christoph Strake + - Default Theme author. + - WWW design consultant. + +Testers and Requsters: + + * Ashwin + - Beta testing, many good bug reports. + + * Michael ? + - GCC-3.1.0 compile verification. + +-- AUTHORS for aewm++ 1.0.16 + +Author: + + * Frank Hale + +-- AUTHORS for aewm + +Author: + + * Decklin Foster diff --git a/Jamconfig.in b/Jamconfig.in index 259b451..6273e64 100644 --- a/Jamconfig.in +++ b/Jamconfig.in @@ -60,6 +60,11 @@ EDE_PANEL_APPLETS_DIR ?= "$(EDE_DATA_DIR)/$(EDE_PREFIX_SUBDIR)/panel-applets" ; DBUS_SERVICE_DIR ?= "$(datarootdir)/dbus-1/services" ; XSESSIONS_DIR ?= "$(datarootdir)/xsessions" ; +PEKWM_CONFIG_DIR ?= "$(sysconfdir)/pekwm" ; +PEKWM_DATA_DIR ?= "$(datadir)/pekwm" ; +PEKWM_CXXFLAGS ?= "@PEKWM_CXXFLAGS@" ; +PEKWM_LIBS ?= "@PEKWM_LIBS@" ; + OPTIMFLAGS ?= @EDE_OPTIM_FLAGS@ ; DEBUGFLAGS ?= @EDE_DEBUG_FLAGS@ ; LARGEFILEFLAGS ?= @LARGEFILE@ ; diff --git a/Jamfile b/Jamfile index f118f87..b96088b 100644 --- a/Jamfile +++ b/Jamfile @@ -44,8 +44,7 @@ if ! $(SUN_COMPILER) { SubInclude TOP ede-panel ; } -# they will not be compiled if eFLTK wasn't found -SubInclude TOP edewm ; +SubInclude TOP pekwm ; # efiler is not compileable at all if $(WITH_EFILER) = 1 { diff --git a/configure.in b/configure.in index 001cfca..4d43a36 100644 --- a/configure.in +++ b/configure.in @@ -108,10 +108,10 @@ else fi if test -n "$with_edelib_path"; then - PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$with_edelib_path" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$with_edelib_path" else dnl TODO: remove this in release - PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/opt/ede/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/opt/ede/lib/pkgconfig" fi PKG_CHECK_MODULES(EDELIB, [edelib],, [have_edelib=no]) @@ -230,8 +230,8 @@ AC_TRY_LINK([ ],[have_xkbrules=yes],[]) AC_LANG_RESTORE -CFLAGS="ac_save_CFLAGS" -LIBS="ac_save_LIBS" +CFLAGS="$ac_save_CFLAGS" +LIBS="$ac_save_LIBS" if test "$have_xkbrules" = "yes"; then AC_DEFINE(HAVE_XKBRULES, 1, [Define to 1 if you have XKB extension]) @@ -256,6 +256,9 @@ else AC_MSG_RESULT(no) fi +dnl pekwm specific macros +EDE_CHECK_PEKWM_DEPENDENCIES + dnl expand variables before EDE_INIT_JAM convert them to jam variables EDE_EXPAND(sysconfdir, "NONE", my_sysconfdir) @@ -276,6 +279,8 @@ AC_SUBST(CURL_LIBS) AC_SUBST(LARGEFILE) AC_SUBST(XKB_LIBS) AC_SUBST(my_sysconfdir) +AC_SUBST(PEKWM_CXXFLAGS) +AC_SUBST(PEKWM_LIBS) AC_OUTPUT([ Jamconfig diff --git a/data/Jamfile b/data/Jamfile index dd7ef33..0e47bdd 100644 --- a/data/Jamfile +++ b/data/Jamfile @@ -23,3 +23,4 @@ SubInclude TOP data mime-types ; SubInclude TOP data icon-themes ; SubInclude TOP data menu ; SubInclude TOP data desktop-links ; +SubInclude TOP data pekwm ; diff --git a/data/pekwm/Jamfile b/data/pekwm/Jamfile new file mode 100644 index 0000000..d81f1e3 --- /dev/null +++ b/data/pekwm/Jamfile @@ -0,0 +1,24 @@ +# +# $Id: Jamfile 2858 2009-10-03 07:24:06Z karijes $ +# +# Part of Equinox Desktop Environment (EDE). +# Copyright (c) 2009-2011 EDE Authors. +# +# This program is licensed under terms of the +# GNU General Public License version 2 or newer. +# See COPYING for details. + +SubDir TOP data pekwm ; + +InstallData $(PEKWM_CONFIG_DIR) : + autoproperties + autoproperties_typerules + config + keys + menu + mouse + start + vars ; + +SubInclude TOP data pekwm themes ; +SubInclude TOP data pekwm scripts ; diff --git a/data/pekwm/autoproperties b/data/pekwm/autoproperties new file mode 100644 index 0000000..a095366 --- /dev/null +++ b/data/pekwm/autoproperties @@ -0,0 +1,248 @@ +/* + Autoproperties. The default template and simple course of autopropping + to help you add your own autoproperties. See the documentation for + more keywords and the rest of what is possible through this file. + + First, it's good to note that you can't just make up the property + string, you need to use a program called 'xprop' to show it. Please + conduct the documentation. + + Another good tip is to make sure you have an ApplyOn entry. The + autoproperties you define won't do any good if you don't tell pekwm + when to apply them! + + Third tip. You can't match a window with more than one property. The + first one that matches will be used, the rest ignored (see the gimp + example). + + Note that the default entries are commented out, don't comment out your + own autoproperties. :) +*/ + +Require { + Templates = "True" +} + +/* + Group terminal applications + */ +# Property = "(term|rxvt),(erm|xvt)" { +# ApplyOn = "New" +# Group = "term" { +# Size = "5" +# FocusedFirst = "True" +# Raise = "True" +# } +# } + +/* + Remove decor of customize toolbar window of mozilla firefox. + */ +Property = "^(gecko|Gecko|firefox-bin),^Firefox-bin,,^Customize Toolbar\$" { + ApplyOn = "Start New TransientOnly" + Border = "False" + Titlebar = "False" +} + +/* + Auto-group up to 10 mozilla download windows to group you call "moz-dl", + using a WM_CLASS and specifying the the download window using the + begining of its title. Make the windows go to the top-left corner of + your workspace and place them under other windows. Do this when new + windows show up, also to so called transient windows. +*/ +# Property = "^mozilla-bin,^Mozilla-bin,,^Saving" { +# ApplyOn = "New Transient" +# Group = "moz-dl" { Size = "10" } +# FrameGeometry = "+0+0" +# Layer = "Below" +# } + + +/* + Group together up to two windows that have a WM_CLASS that matches the + property. Start these windows on workspace two. +*/ +# Property = "^Mozilla,^navigator:browser" { +# ApplyOn = "Start New Workspace" +# Workspace = "2" +# } + + +/* + Group together an infinite number of windows that match the property. + When new windows are opened to this group, never make them the active + window of the group, but open them in the background. Make these + autoproperties apply on every pekwm start or when a new window is opened. +*/ +# Property = "^dillo,^Dillo" { +# ApplyOn = "Start New" +# Group = "dillo" { Size = "0"; Behind = "True" } +# } + + +/* + Put property matching windows under other windows and make the window + appear on every workspace. Do not show matching windows on the pekwm + goto menus, do not include them in frame switching (mod1+tab) and do + not let other windows snap to them. Do this on pekwm start or when new + window is opened, also include transient windows (in the example, + xmms playlist and equalizer are transients). +*/ +# Property = ".*,^xmms" { +# ApplyOn = "Start New Transient" +# Layer = "Desktop" +# Sticky = "True" +# Skip = "Menus FocusToggle Snap" +# } + + +/* + Remove Gimp windows from the menus, only show the main toolbox window. + Use the WM_WINDOW_ROLE to tell the difference between gimp windows. + + First make sure the toolbox window doesn't get confused with the rest + of the windows. This just tells pekwm to ignore any matches for the + toolbox later on. Without this, the toolbox would match with "the rest + of the windows" and get ignored from the pekwm menus! We don't want that. +*/ +# Property = "^gimp,^Gimp,gimp-toolbox" { +# ApplyOn = "Start New" +# } +/* + The Crop dialog always gets in the way, put it in the corner but place + it above other windows anyways. Don't show the window in pekwm menus. +*/ +# Property = "^gimp,^Gimp,gimp-crop-tool" { +# ApplyOn = "Start New" +# Layer = "OnTop" +# FrameGeometry = "+0+0" +# Skip = "Menus" +# } +/* + The rest of the gimp windows should not show in pekwm menus eather. +*/ +# Property = ".gimp,^Gimp" { +# ApplyOn = "Start New" +# Skip = "Menus"; +# } + + +/* + This should start making sense to you by now. +*/ +# Property = "^gkrellm,^Gkrellm" { +# ApplyOn = "Start New" +# Sticky = "True" +# Skip = "Menus FocusToggle" +# Layer = "Desktop" +# } + + +/* + Some useful standard application xclock xload and xbiff. This should + be fairly clear to you. In addition to what you've allready learned, + we make the windows appear without titlebars and borders. We are also + using the geometry in all its glory, defining the windows size in + addition to its position. +*/ +# Property = "^xclock,^XClock" { +# ApplyOn = "Start New" +# ClientGeometry = "120x137+0-137" +# Border = "False"; Titlebar = "False" +# Sticky = "True" +# Layer = "Desktop" +# Skip = "Menus FocusToggle Snap" +# } +# +# Property = "^xload,^XLoad" { +# ApplyOn = "Start New" +# ClientGeometry = "560x137+120-137" +# Border = "False"; Titlebar = "False" +# Sticky = "True" +# Layer = "Desktop" +# Skip = "Menus FocusToggle Snap" +# } +# +# Property = "^xbiff,^XBiff" { +# ApplyOn = "Start New" +# ClientGeometry = "120x137-120-137" +# Border = "False"; Titlebar = "False" +# Sticky = "True" +# Layer = "Desktop" +# Skip = "Menus FocusToggle Snap" +# } + + +// End of autoproperties. ------------------------- + +TypeRules { + INCLUDE = "$_PEKWM_ETC_PATH/autoproperties_typerules" +} + +/* + Next, we do some siple window title rewriting. + To make it simple, you can automatically make some windows get their + title edited. Cut out an annoying piece, add text, replace text. + This all happens in it's own section "TitleRules {}". + + I don't like the way dillo uses its titlebar, it says "Dillo: webpage". + I want that "Dillo:" part to not show in the beginning, instead I want + to make it show as "webpage - dillo". + + Then again, the "webpage - Mozilla Firefox" is too long for my taste. + I shorten it in the second titlerule. And I'll place the shortened text + in the beginning of the title just as a show how. +*/ +# TitleRules { +# Property = "^dillo,^Dillo" { +# Rule = "/Dillo: (.*)/\\1 - dillo/" +# } +# Property = "^firefox-bin,^Firefox-bin" { +# Rule = "/(.*) - Mozilla Firefox/MF: \\1/" +# } +# } + + +// End of titlerules. ----------------------------- + + +/* + Then for some harbour ordering done in it's own "Harbour {}" section. + This is simple really, but you might want to check the documentation on + how the positions work. + + Obpager is allways the last dockapp, the cpuload application is the + first, and wmnd will get placed in the centre. +*/ +# Harbour { +# Property = "^obpager,^obpager" { +# Position = "-1" +# } +# Property = ".*,.*cpuload" { +# Position = "1" +# } +# Property = ".*,^wmnd" { +# Position = "0" +# } +# } + + +// End of harbour order rules. -------------------- + + +/* + Last, if you have a theme that supports it, or you have hacked one up + yourself, you can use the "DecorRules {}" section to make windows show + up with independent decorations. + + Here we tell our terminal windows to use the special TERM decoration + section found from your theme. Note that this _needs_ a theme that + supports it. +*/ +# DecorRules { +# Property = "^term,^xterm" { +# Decor = "TERM" +# } +# } + diff --git a/data/pekwm/autoproperties_typerules b/data/pekwm/autoproperties_typerules new file mode 100644 index 0000000..ed16038 --- /dev/null +++ b/data/pekwm/autoproperties_typerules @@ -0,0 +1,44 @@ +/* + Desktop windows such as nautilus window in gnome. These should + cover the root window and be below all other windows. Also they + should not be included in the menu and in snapping. + */ +Property = "DESKTOP" { + FrameGeometry = "0x0+0+0" + Titlebar = "False" + Border = "False" + Sticky = "True" + Skip = "FocusToggle Menus Snap" + Layer = "Desktop" + Focusable = "False" + DisallowedActions = "Move" +} +Property = "DOCK" { + Titlebar = "False" + Border = "False" + Sticky = "True" + Layer = "Dock" + Skip = "FocusToggle Menus" + Focusable = "False" + DisallowedActions = "Move" +} +Property = "TOOLBAR" { + Skip = "FocusToggle Menus Snap" +} +Property = "MENU" { + Titlebar = "False" + Border = "False" + Skip = "FocusToggle Menus Snap" +} +Property = "UTILITY" { +} +Property = "SPLASH" { + Titlebar = "False" + Border = "False" + Layer = "OnTop" +} +Property = "DIALOG" { + Layer = "OnTop" +} +Property = "NORMAL" { +} diff --git a/data/pekwm/config b/data/pekwm/config new file mode 100644 index 0000000..1ce72cf --- /dev/null +++ b/data/pekwm/config @@ -0,0 +1,108 @@ +Files { + Keys = "~/.pekwm/keys" + Mouse = "~/.pekwm/mouse" + Menu = "~/.pekwm/menu" + Start = "~/.pekwm/start" + AutoProps = "~/.pekwm/autoproperties" + Theme = "$_PEKWM_THEME_PATH/default" + Icons = "~/.pekwm/icons/" +} + +MoveResize { + EdgeAttract = "10" + EdgeResist = "10" + WindowAttract = "5" + WindowResist = "5" + OpaqueMove = "True" + OpaqueResize = "False" +} + +Screen { + Workspaces = "4" + WorkspacesPerRow = "4" + WorkspaceNames = "Main;Web;E-mail;Music" + ShowFrameList = "True" + ShowStatusWindow = "True" + ShowStatusWindowCenteredOnRoot = "False" + ShowClientID = "False" + ShowWorkspaceIndicator = "500" + PlaceNew = "True" + FocusNew = "True" + + ReportAllClients = "False" + + TrimTitle = "..." + FullscreenAbove = "True" + FullscreenDetect = "True" + HonourRandr = "True" + HonourAspectRatio = "True" + EdgeSize = "1 1 1 1" + EdgeIndent = "False" + PixmapCacheSize = "20" + DoubleClickTime = "250" + + Placement { + Model = "CenteredOnParent Smart MouseNotUnder" + Smart { + Row = "True" + TopToBottom = "True" + LeftToRight = "True" + OffsetX = "0" + OffsetY = "0" + } + } + + UniqueNames { + SetUnique = "False" + Pre = " #" + Post = "" + } +} + +Menu { + DisplayIcons = "True" + + Icons = "DEFAULT" { + Minimum = "16x16" + Maximum = "16x16" + } + + # To enable make separate window have other icon size restrictions, + # for example wallpaper menu found in pekwm_menu_tools, set the following + # for each menu you want to "free". + + # Icons = "Wallpaper" { + # Minimum = "64x64" + # Maximum = "64x64" + # } + + # Defines how menus act on mouse input. + # Possible values are: "ButtonPress ButtonRelease DoubleClick Motion" + # To make submenus open on mouse over, comment the default Enter, + # uncomment the alternative, and reload pekwm. + + Select = "Motion MotionPressed" + Enter = "MotionPressed ButtonPress" + # Enter = "Motion" + Exec = "ButtonRelease" +} + +CmdDialog { + HistoryUnique = "True" + HistorySize = "1024" + HistoryFile = "~/.pekwm/history" + HistorySaveInterval = "16" +} + +Harbour { + OnTop = "True" + MaximizeOver = "False" + Placement = "Right" + Orientation = "TopToBottom" + Head = "0" + + DockApp { + SideMin = "64" + SideMax = "0" + } +} diff --git a/data/pekwm/keys b/data/pekwm/keys new file mode 100644 index 0000000..6002088 --- /dev/null +++ b/data/pekwm/keys @@ -0,0 +1,315 @@ +INCLUDE = "vars" + +Global { +# - - ----------------------------------------------- - - +# Simple bindings to most frequently used actions. +# +# Adding your own frequently used actions is easy - +# just copy it over from CHAINS and edit the keypress! + # Moving in frames + KeyPress = "Mod1 Tab" { Actions = "NextFrame EndRaise" } + KeyPress = "Mod1 Shift Tab" { Actions = "PrevFrame EndRaise" } + KeyPress = "Mod1 Ctrl Tab" { Actions = "NextFrameMRU EndRaise" } + KeyPress = "Mod1 Ctrl Shift Tab" { Actions = "PrevFrameMRU EndRaise" } + KeyPress = "Mod4 Tab" { Actions = "ActivateClientRel 1" } + KeyPress = "Mod4 Shift Tab" { Actions = "ActivateClientRel -1" } + KeyPress = "Mod4 Ctrl Right" { Actions = "MoveClientRel 1" } + KeyPress = "Mod4 Ctrl Left" { Actions = "MoveClientRel -1" } + KeyPress = "Mod4 Left" { Actions = "FocusDirectional Left" } + KeyPress = "Mod4 Right" { Actions = "FocusDirectional Right" } + KeyPress = "Mod4 Up" { Actions = "FocusDirectional Up" } + KeyPress = "Mod4 Down" { Actions = "FocusDirectional Down" } + # Moving in workspaces + KeyPress = "Ctrl Mod1 Left" { Actions = "GotoWorkspace Left" } + KeyPress = "Ctrl Mod1 Right" { Actions = "GotoWorkspace Right" } + KeyPress = "Ctrl Mod1 Up" { Actions = "GotoWorkspace Up" } + KeyPress = "Ctrl Mod1 Down" { Actions = "GotoWorkspace Down" } + KeyPress = "Mod4 1" { Actions = "GotoWorkspace 1" } + KeyPress = "Mod4 2" { Actions = "GotoWorkspace 2" } + KeyPress = "Mod4 3" { Actions = "GotoWorkspace 3" } + KeyPress = "Mod4 4" { Actions = "GotoWorkspace 4" } + KeyPress = "Mod4 5" { Actions = "GotoWorkspace 5" } + KeyPress = "Mod4 6" { Actions = "GotoWorkspace 6" } + KeyPress = "Mod4 7" { Actions = "GotoWorkspace 7" } + KeyPress = "Mod4 8" { Actions = "GotoWorkspace 8" } + KeyPress = "Mod4 9" { Actions = "GotoWorkspace 9" } + KeyPress = "Ctrl Mod1 Shift Left" { Actions = "SendToWorkspace Next; GoToWorkspace Next" } + KeyPress = "Ctrl Mod1 Shift Right" { Actions = "SendToWorkspace Prev; GoToWorkspace Prev" } + KeyPress = "Ctrl Mod1 Shift Up" { Actions = "SendToWorkspace NextV; GoToWorkspace NextV" } + KeyPress = "Ctrl Mod1 Shift Down" { Actions = "SendToWorkspace PrevV; GoToWorkspace PrevV" } + KeyPress = "Mod4 F1" { Actions = "SendToWorkspace 1" } + KeyPress = "Mod4 F2" { Actions = "SendToWorkspace 2" } + KeyPress = "Mod4 F3" { Actions = "SendToWorkspace 3" } + KeyPress = "Mod4 F4" { Actions = "SendToWorkspace 4" } + KeyPress = "Mod4 F5" { Actions = "SendToWorkspace 5" } + KeyPress = "Mod4 F6" { Actions = "SendToWorkspace 6" } + KeyPress = "Mod4 F7" { Actions = "SendToWorkspace 7" } + KeyPress = "Mod4 F8" { Actions = "SendToWorkspace 8" } + KeyPress = "Mod4 F9" { Actions = "SendToWorkspace 9" } + # Simple window management + KeyPress = "Mod4 M" { Actions = "Toggle Maximized True True" } + KeyPress = "Mod4 G" { Actions = "Maxfill True True" } + KeyPress = "Mod4 F" { Actions = "Toggle FullScreen" } + KeyPress = "Mod4 Return" { Actions = "MoveResize" } + KeyPress = "Mod4 Q" { Actions = "Close" } + KeyPress = "Mod4 S" { Actions = "Toggle Shaded" } + KeyPress = "Mod4 I" { Actions = "Toggle Iconified" } + # Marking + KeyPress = "Mod4 Z" { Actions = "Toggle Marked" } + KeyPress = "Mod4 A" { Actions = "AttachMarked" } + # Tagging + KeyPress = "Mod4 T" { Actions = "Toggle Tagged False" } + # Menus + KeyPress = "Mod4 R" { Actions = "ShowMenu Root" } + KeyPress = "Mod4 W" { Actions = "ShowMenu Window" } + KeyPress = "Mod4 L" { Actions = "ShowMenu Goto" } + KeyPress = "Mod4 C" { Actions = "ShowMenu GotoClient" } + KeyPress = "Mod4 Shift I" { Actions = "ShowMenu Icon" } + KeyPress = "Mod4 X" { Actions = "HideAllMenus" } + # External Commands + KeyPress = "Mod4 E" { Actions = "Exec $TERM" } + # Pekwm control + KeyPress = "Ctrl Mod1 Delete" { Actions = "Reload" } + KeyPress = "Mod4 D" { Actions = "ShowCmdDialog" } + KeyPress = "Mod4 V" { Actions = "ShowSearchDialog" } + KeyPress = "Mod4 H" { Actions = "Toggle HarbourHidden" } + +# - - ----------------------------------------------- - - +# CHAINS. These give you access to just about everything. + # Move to Corner + Chain = "Ctrl Mod1 C" { + KeyPress = "Q" { Actions = "MoveToEdge TopLeft" } + KeyPress = "Y" { Actions = "MoveToEdge TopCenterEdge" } + KeyPress = "W" { Actions = "MoveToEdge TopCenterEdge" } + KeyPress = "Shift Y" { Actions = "MoveToEdge TopEdge" } + KeyPress = "Shift W" { Actions = "MoveToEdge TopEdge" } + KeyPress = "P" { Actions = "MoveToEdge TopRight" } + KeyPress = "E" { Actions = "MoveToEdge TopRight" } + KeyPress = "A" { Actions = "MoveToEdge LeftCenterEdge" } + KeyPress = "Shift A" { Actions = "MoveToEdge LeftEdge" } + KeyPress = "L" { Actions = "MoveToEdge RightCenterEdge" } + KeyPress = "D" { Actions = "MoveToEdge RightCenterEdge" } + KeyPress = "Shift L" { Actions = "MoveToEdge RightEdge" } + KeyPress = "Shift D" { Actions = "MoveToEdge RightEdge" } + KeyPress = "Z" { Actions = "MoveToEdge BottomLeft" } + KeyPress = "B" { Actions = "MoveToEdge BottomCenterEdge" } + KeyPress = "X" { Actions = "MoveToEdge BottomCenterEdge" } + KeyPress = "Shift B" { Actions = "MoveToEdge BottomEdge" } + KeyPress = "Shift X" { Actions = "MoveToEdge BottomEdge" } + KeyPress = "M" { Actions = "MoveToEdge BottomRight" } + KeyPress = "C" { Actions = "MoveToEdge BottomRight" } + KeyPress = "H" { Actions = "MoveToEdge Center" } + KeyPress = "S" { Actions = "MoveToEdge Center" } + } + # Menus + Chain = "Ctrl Mod1 M" { + KeyPress = "R" { Actions = "ShowMenu Root" } + KeyPress = "W" { Actions = "ShowMenu Window" } + KeyPress = "I" { Actions = "ShowMenu Icon" } + KeyPress = "G" { Actions = "ShowMenu Goto" } + KeyPress = "C" { Actions = "ShowMenu GotoClient" } + KeyPress = "D" { Actions = "ShowMenu Decor" } + KeyPress = "A" { Actions = "ShowMenu AttachClientInFrame" } + KeyPress = "F" { Actions = "ShowMenu AttachFrameInFrame" } + Keypress = "Shift A" { Actions = "ShowMenu AttachClient" } + Keypress = "Shift F" { Actions = "ShowMenu AttachFrame" } + KeyPress = "X" { Actions = "HideAllMenus" } + } + # Grouping + Chain = "Ctrl Mod1 T" { + KeyPress = "T" { Actions = "Toggle Tagged False" } + KeyPress = "B" { Actions = "Toggle Tagged True" } + KeyPress = "C" { Actions = "Unset Tagged" } + KeyPress = "G" { Actions = "Toggle GlobalGrouping" } + KeyPress = "M" { Actions = "Toggle Marked" } + KeyPress = "A" { Actions = "AttachMarked" } + KeyPress = "D" { Actions = "Detach" } + Keypress = "P" { Actions = "AttachClientInNextFrame" } + KeyPress = "O" { Actions = "AttachClientInPrevFrame" } + Keypress = "I" { Actions = "AttachFrameInNextFrame" } + KeyPress = "U" { Actions = "AttachFrameInPrevFrame" } + } + # Decor Toggles + Chain = "Ctrl Mod1 D" { + KeyPress = "B" { Actions = "Toggle DecorBorder" } + KeyPress = "T" { Actions = "Toggle DecorTitlebar" } + KeyPress = "D" { Actions = "Toggle DecorBorder; Toggle DecorTitlebar" } + } + # Window Actions + Chain = "Ctrl Mod1 A" { + Chain = "G" { + KeyPress = "G" { Actions = "MaxFill True True" } + KeyPress = "V" { Actions = "MaxFill False True" } + KeyPress = "H" { Actions = "MaxFill True False" } + } + Chain = "M" { + KeyPress = "M" { Actions = "Toggle Maximized True True" } + KeyPress = "V" { Actions = "Toggle Maximized False True" } + KeyPress = "H" { Actions = "Toggle Maximized True False" } + } + Chain = "Q" { + KeyPress = "Q" { Actions = "Close" } + KeyPress = "F" { Actions = "CloseFrame" } + KeyPress = "K" { Actions = "Kill" } + } + KeyPress = "S" { Actions = "Toggle Shaded" } + KeyPress = "A" { Actions = "Toggle Sticky" } + KeyPress = "O" { Actions = "Toggle AlwaysOnTop" } + KeyPress = "B" { Actions = "Toggle AlwaysBelow" } + KeyPress = "I" { Actions = "Set Iconified" } + KeyPress = "R" { Actions = "Raise" } + KeyPress = "Shift R" { Actions = "Raise True" } + KeyPress = "L" { Actions = "Lower" } + KeyPress = "Shift L" { Actions = "Lower True" } + KeyPress = "X" { Actions = "ActivateOrRaise" } + KeyPress = "Return" { Actions = "MoveResize" } + KeyPress = "F" { Actions = "Toggle Fullscreen" } + KeyPress = "Left" { Actions = "GrowDirection Left" } + KeyPress = "Right" { Actions = "GrowDirection Right" } + KeyPress = "Up" { Actions = "GrowDirection Up" } + KeyPress = "Down" { Actions = "GrowDirection Down" } + } + # Moving in Frames + Chain = "Ctrl Mod1 F" { + KeyPress = "P" { Actions = "NextFrame AlwaysRaise" } + KeyPress = "O" { Actions = "PrevFrame AlwaysRaise" } + KeyPress = "Shift P" { Actions = "NextFrameMRU EndRaise" } + KeyPress = "Shift O" { Actions = "PrevFrameMRU EndRaise" } + KeyPress = "I" { Actions = "ActivateClientRel 1" } + KeyPress = "U" { Actions = "ActivateClientRel -1" } + KeyPress = "Shift I" { Actions = "MoveClientRel 1" } + KeyPress = "Shift U" { Actions = "MoveClientRel -1" } + KeyPress = "Up" { Actions = "FocusDirectional Up" } + KeyPress = "Down" { Actions = "FocusDirectional Down" } + KeyPress = "Left" { Actions = "FocusDirectional Left" } + Keypress = "Right" { Actions = "FocusDirectional Right" } + KeyPress = "1" { Actions = "ActivateClientNum 1" } + KeyPress = "2" { Actions = "ActivateClientNum 2" } + KeyPress = "3" { Actions = "ActivateClientNum 3" } + KeyPress = "4" { Actions = "ActivateClientNum 4" } + KeyPress = "5" { Actions = "ActivateClientNum 5" } + KeyPress = "6" { Actions = "ActivateClientNum 6" } + KeyPress = "7" { Actions = "ActivateClientNum 7" } + KeyPress = "8" { Actions = "ActivateClientNum 8" } + KeyPress = "9" { Actions = "ActivateClientNum 9" } + KeyPress = "0" { Actions = "ActivateClientNum 10" } + KeyPress = "C" { Actions = "ShowCmdDialog GotoClientID " } + } + # Workspaces + Chain = "Ctrl Mod1 W" { + KeyPress = "Right" { Actions = "GoToWorkspace Right" } + KeyPress = "Left" { Actions = "GoToWorkspace Left" } + KeyPress = "N" { Actions = "GoToWorkspace Next" } + KeyPress = "P" { Actions = "GoToWorkspace Prev" } + KeyPress = "1" { Actions = "GoToWorkspace 1" } + KeyPress = "2" { Actions = "GoToWorkspace 2" } + KeyPress = "3" { Actions = "GoToWorkspace 3" } + KeyPress = "4" { Actions = "GoToWorkspace 4" } + KeyPress = "5" { Actions = "GoToWorkspace 5" } + KeyPress = "6" { Actions = "GoToWorkspace 6" } + KeyPress = "7" { Actions = "GoToWorkspace 7" } + KeyPress = "8" { Actions = "GoToWorkspace 8" } + KeyPress = "9" { Actions = "GoToWorkspace 9" } + KeyPress = "Up" { Actions = "SendToWorkspace Next; GoToWorkspace Next" } + KeyPress = "Down" { Actions = "SendToWorkspace Prev; GoToWorkspace Prev" } + KeyPress = "F1" { Actions = "SendToWorkspace 1" } + KeyPress = "F2" { Actions = "SendToWorkspace 2" } + KeyPress = "F3" { Actions = "SendToWorkspace 3" } + KeyPress = "F4" { Actions = "SendToWorkspace 4" } + KeyPress = "F5" { Actions = "SendToWorkspace 5" } + KeyPress = "F6" { Actions = "SendToWorkspace 6" } + KeyPress = "F7" { Actions = "SendToWorkspace 7" } + KeyPress = "F8" { Actions = "SendToWorkspace 8" } + KeyPress = "F9" { Actions = "SendToWorkspace 9" } + } + # External commands + Chain = "Ctrl Mod1 E" { + KeyPress = "E" { Actions = "Exec $TERM" } + KeyPress = "L" { Actions = "Exec xlock -mode blank &" } + KeyPress = "S" { Actions = "Exec scrot &" } + KeyPress = "C" { Actions = "ShowCmdDialog" } + } + # Wm actions + Chain = "Ctrl Mod1 P" { + KeyPress = "Delete" { Actions = "Reload" } + KeyPress = "Next" { Actions = "Restart" } + KeyPress = "End" { Actions = "Exit" } + KeyPress = "Prior" { Actions = "RestartOther twm" } + KeyPress = "D" { Actions = "ShowCmdDialog" } + KeyPress = "H" { Actions = "Toggle HarbourHidden" } + } + # Skipping + Chain = "Ctrl Mod1 S" { + Keypress = "M" { Actions = "Toggle Skip Menus" } + Keypress = "F" { Actions = "Toggle Skip FocusToggle" } + Keypress = "S" { Actions = "Toggle Skip Snap" } + } +} + +# Keys when MoveResize is active +MoveResize { + KeyPress = "Left" { Actions = "MoveHorizontal -10" } + KeyPress = "Right" { Actions = "MoveHorizontal 10" } + KeyPress = "Up" { Actions = "MoveVertical -10" } + KeyPress = "Down" { Actions = "MoveVertical 10" } + Keypress = "Shift Left" { Actions = "MoveHorizontal -1" } + Keypress = "Shift Right" { Actions = "MoveHorizontal 1" } + Keypress = "Shift Up" { Actions = "MoveVertical -1" } + Keypress = "Shift Down" { Actions = "MoveVertical 1" } + Keypress = "Mod4 Left" { Actions = "ResizeHorizontal -10" } + Keypress = "Mod4 Right" { Actions = "ResizeHorizontal 10" } + Keypress = "Mod4 Up" { Actions = "ResizeVertical -10" } + Keypress = "Mod4 Down" { Actions = "ResizeVertical 10" } + Keypress = "Mod1 Left" { Actions = "ResizeHorizontal -10" } + Keypress = "Mod1 Right" { Actions = "ResizeHorizontal 10" } + Keypress = "Mod1 Up" { Actions = "ResizeVertical -10" } + Keypress = "Mod1 Down" { Actions = "ResizeVertical 10" } + Keypress = "Shift Mod4 Left" { Actions = "ResizeHorizontal -1" } + Keypress = "Shift Mod4 Right" { Actions = "ResizeHorizontal 1" } + Keypress = "Shift Mod4 Up" { Actions = "ResizeVertical -1" } + Keypress = "Shift Mod4 Down" { Actions = "ResizeVertical 1" } + Keypress = "Shift Mod1 Left" { Actions = "ResizeHorizontal -1" } + Keypress = "Shift Mod1 Right" { Actions = "ResizeHorizontal 1" } + Keypress = "Shift Mod1 Up" { Actions = "ResizeVertical -1" } + Keypress = "Shift Mod1 Down" { Actions = "ResizeVertical 1" } + Keypress = "s" { Actions = "MoveSnap" } + Keypress = "Escape" { Actions = "Cancel" } + Keypress = "q" { Actions = "Cancel" } + Keypress = "Return" { Actions = "End" } +} + +# Keys for CmdDialog editing +InputDialog { + KeyPress = "Left" { Actions = "CursPrev" } + KeyPress = "Right" { Actions = "CursNext" } + KeyPress = "Ctrl A" { Actions = "CursBegin" } + KeyPress = "Ctrl E" { Actions = "CursEnd" } + KeyPress = "BackSpace" { Actions = "Erase;CompleteAbort" } + KeyPress = "Ctrl K" { Actions = "ClearFromCursor" } + KeyPress = "Ctrl C" { Actions = "Clear" } + KeyPress = "Return" { Actions = "Exec" } + KeyPress = "Escape" { Actions = "Close" } + KeyPress = "Up" { Actions = "HistPrev" } + KeyPress = "Down" { Actions = "HistNext" } + KeyPress = "Ctrl P" { Actions = "HistPrev" } + KeyPress = "Ctrl N" { Actions = "HistNext" } + KeyPress = "Ctrl B" { Actions = "CursPrev" } + KeyPress = "Ctrl F" { Actions = "CursNext" } + KeyPress = "Tab" { Actions = "Complete" } + KeyPress = "Any Any" { Actions = "Insert" } +} + +# Keys working in menus +Menu { + KeyPress = "Down" { Actions = "NextItem" } + KeyPress = "Up" { Actions = "PrevItem" } + KeyPress = "Ctrl N" { Actions = "NextItem" } + KeyPress = "Ctrl P" { Actions = "PrevItem" } + KeyPress = "Left" { Actions = "LeaveSubmenu" } + KeyPress = "Right" { Actions = "EnterSubmenu" } + KeyPress = "Return" { Actions = "Select" } + KeyPress = "space" { Actions = "Select" } + KeyPress = "Escape" { Actions = "Close" } + KeyPress = "Q" { Actions = "Close" } +} + diff --git a/data/pekwm/menu b/data/pekwm/menu new file mode 100644 index 0000000..3e22e98 --- /dev/null +++ b/data/pekwm/menu @@ -0,0 +1,147 @@ +# Menu config for pekwm + +# Variables +INCLUDE = "vars" + +RootMenu = "Pekwm" { + Entry = "Terminal" { Actions = "Exec $TERM &" } + Entry = "Run.." { Actions = "ShowCmdDialog" } + + Separator {} + + Submenu = "Editors" { + Entry = "vim" { Actions = "Exec $TERM -title vim -e vim &" } + Entry = "gvim" { Actions = "Exec gvim &" } + Entry = "Emacs" { Actions = "Exec emacs &" } + Entry = "Emacs Terminal" { Actions = "Exec $TERM -title emacs -e emacs -nw &" } + Entry = "Kate" { Actions = "Exec kate &" } + } + Submenu = "Graphics" { + Entry = "display" { Actions = "Exec display &" } + Entry = "Gimp" { Actions = "Exec gimp &" } + Entry = "Gv" { Actions = "Exec gv &" } + Entry = "Xpdf" { Actions = "Exec xpdf &" } + Entry = "gqview" { Actions = "Exec gqview &" } + } + Submenu = "Multimedia" { + Entry = "Amarok" { Actions = "Exec amarok &" } + Entry = "Quod Libet" { Actions = "Exec quodlibet &" } + Entry = "Xmms" { Actions = "Exec xmms &" } + Entry = "MPlayer" { Actions = "Exec gnome-mplayer &" } + Entry = "Xine" { Actions = "Exec xine &" } + Entry = "xawtv" { Actions = "Exec xawtv &" } + Entry = "Totem" { actions = "exec totem &" } + Entry = "alsamixer" { Actions = "Exec $TERM -title alsamixer -e alsamixer &" } + } + Submenu = "Utils" { + Entry = "Calculator" { Actions = "Exec gcalctool &" } + Entry = "Xpdf" { Actions = "Exec xpdf &" } + Entry = "Evince" { Actions = "Exec evince &" } + Entry = "gucharmap" { Actions = "Exec gucharmap &" } + Entry = "Gkrellm" { Actions = "Exec gkrellm &" } + } + Submenu = "WWW" { + Entry = "Dillo" { Actions = "Exec dillo &" } + Entry = "Konqueror" { Actions = "Exec konqueror &" } + Entry = "Firefox" { Actions = "Exec firefox &" } + } + Submenu = "FTP" { + Entry = "gftp" { Actions = "Exec gftp &" } + Entry = "lftp" { Actions = "Exec $TERM -title lftp -e lftp &" } + } + Submenu = "Communication" { + Entry = "Mutt" { Actions = "Exec $TERM -title mutt -e mutt &" } + Entry = "Alpine" { Actions = "Exec $TERM -title alpine -e alpine &" } + Entry = "Thunderbird" { Actions = "Exec thunderbird &" } + Entry = "Evolution" { Actions = "Exec evolution &" } + Entry = "KMail" { Actions = "Exec kmail &" } + Entry = "Pidgin" { Actions = "Exec pidgin &" } + Entry = "Irssi" { Actions = "Exec $TERM -title irssi -e irssi &" } + Entry = "Kopete" { Actions = "Exec kopete &" } + } + Submenu = "Office" { + Entry = "KOffice Workspace" { Actions = "Exec koshell &" } + Entry = "OpenOffice" { Actions = "Exec ooffice &" } + } + Submenu = "Development" { + Entry = "Anjuta" { Actions = "Exec anjuta &" } + Entry = "Eclipse" { Actions = "Exec eclipse &" } + Entry = "KDevelop" { Actions = "Exec kdevelop &" } + } + + Separator {} + + Submenu = "Go to" { + SubMenu = "Workspace" { + # Create goto menu once per pekwm config reload. The fast way that + # will work for most if not all users. + COMMAND = "$_PEKWM_SCRIPT_PATH/pekwm_ws_menu.sh goto" + # Create goto menu every time the menu is opened. The slow way. + # This is what you want if you are using external tools to make + # the amount of workspaces something else than what you define in + # ~/.pekwm/config. You will know if you want this. + # Entry = "" { Actions = "Dynamic $_PEKWM_SCRIPT_PATH/pekwm_ws_menu.sh goto dynamic" } + } + Entry = "Window.." { Actions = "ShowMenu GotoClient True" } + } + Submenu = "Pekwm" { + Submenu = "Themes" { + Entry { Actions = "Dynamic $_PEKWM_SCRIPT_PATH/pekwm_themeset.sh $_PEKWM_THEME_PATH" } + Entry { Actions = "Dynamic $_PEKWM_SCRIPT_PATH/pekwm_themeset.sh ~/.pekwm/themes" } + } + Entry = "Reload" { Actions = "Reload" } + Entry = "Restart" { Actions = "Restart" } + Entry = "Exit" { Actions = "Exit" } + Submenu = "Exit to" { + Entry = "Xterm" { Actions = "RestartOther xterm" } + Entry = "TWM" { Actions = "RestartOther twm" } + } + } +} + +WindowMenu = "Window Menu" { + Entry = "(Un)Stick" { Actions = "Toggle Sticky" } + Entry = "(Un)Shade" { Actions = "Toggle Shaded" } + Entry = "Iconify" { Actions = "Set Iconified" } + Entry = "Command.." { Actions = "ShowCmdDialog" } + + Submenu = "Maximize" { + Entry = "Toggle Full" { Actions = "Toggle Maximized True True" } + Entry = "Toggle Horizontal" { Actions = "Toggle Maximized True False" } + Entry = "Toggle Vertical" { Actions = "Toggle Maximized False True" } + } + Submenu = "Fill" { + Entry = "Full" { Actions = "MaxFill True True" } + Entry = "Horizontal" { Actions = "MaxFill True False" } + Entry = "Vertical" { Actions = "MaxFill False True" } + } + Submenu = "Stacking" { + Entry = "Raise" { Actions = "Raise" } + Entry = "Lower" { Actions = "Lower" } + Entry = "Toggle Always On Top" { Actions = "Toggle AlwaysOnTop" } + Entry = "Toggle Always Below" { Actions = "Toggle AlwaysBelow" } + } + Submenu = "Decorations" { + Entry = "Toggle Decorations" { Actions = "Toggle DecorBorder; Toggle DecorTitlebar" } + Entry = "Toggle Borders" { Actions = "Toggle DecorBorder" } + Entry = "Toggle Titlebar" { Actions = "Toggle DecorTitlebar" } + } + Submenu = "Skip" { + Entry = "Toggle showing this frame in menus" { Actions = "Toggle Skip Menus" } + Entry = "Toggle including this frame in focus toggle" { Actions = "Toggle Skip FocusToggle" } + Entry = "Toggle if this frame snaps to other windows" { Actions = "Toggle Skip Snap" } + } + SubMenu = "Send To" { + # Create sendto menu once per pekwm config reload. The fast way that + # will work for most if not all users. + COMMAND = "$_PEKWM_SCRIPT_PATH/pekwm_ws_menu.sh send" + # Create sendto menu every time the menu is opened. The slow way. + # This is what you want if you are using external tools to make + # the amount of workspaces something else than what you define in + # ~/.pekwm/config. You will know if you want this. + # Entry = "" { Actions = "Dynamic $_PEKWM_SCRIPT_PATH/pekwm_ws_menu.sh send dynamic" } + } + Separator {} + Entry = "Close" { Actions = "Close" } + Submenu = "Kill" { Entry = "Kill application" { Actions = "Kill" } } +} diff --git a/data/pekwm/mouse b/data/pekwm/mouse new file mode 100644 index 0000000..e1ff07d --- /dev/null +++ b/data/pekwm/mouse @@ -0,0 +1,182 @@ +FrameTitle { + ButtonRelease = "1" { Actions = "Raise; Focus; ActivateClient" } + ButtonRelease = "Mod1 1" { Actions = "Focus; Raise" } + ButtonRelease = "Mod4 1" { Actions = "Focus; Raise" } + ButtonRelease = "2" { Actions = "ActivateClient" } + ButtonRelease = "Mod4 3" { Actions = "Close" } + ButtonRelease = "3" { Actions = "ShowMenu Window" } + ButtonRelease = "4" { Actions = "ActivateClientRel 1" } + ButtonRelease = "5" { Actions = "ActivateClientRel -1" } + ButtonRelease = "Mod1 4" { Actions = "SendToWorkspace Next; GotoWorkspace Next" } + ButtonRelease = "Mod1 5" { Actions = "SendToWorkspace Prev; GotoWorkspace Prev" } + ButtonRelease = "Mod1 Shift 4" { Actions = "SendToWorkspace PrevV; GotoWorkspace PrevV" } + ButtonRelease = "Mod1 Shift 5" { Actions = "SendToWorkspace NextV; GotoWorkspace NextV" } + ButtonRelease = "Ctrl 4" { Actions = "MoveClientRel 1" } + ButtonRelease = "Ctrl 5" { Actions = "MoveClientRel -1" } + ButtonRelease = "Ctrl Mod1 1" { Actions = "Focus; Raise True" } + DoubleClick = "2" { Actions = "Toggle Shaded" } + DoubleClick = "Mod1 2" { Actions = "Toggle Shaded" } + DoubleClick = "1" { Actions = "MaxFill True True" } + DoubleClick = "Mod1 1" { Actions = "Toggle Maximized True True" } + Motion = "1" { Threshold = "4"; Actions = "Raise; Move" } + Motion = "Mod1 1" { Threshold = "4"; Actions = "Raise; Move" } + Motion = "Mod4 1" { Threshold = "4"; Actions = "Raise; Move" } + Motion = "2" { Threshold = "4"; Actions = "GroupingDrag True" } + Motion = "Mod1 3" { Actions = "Resize" } + # Remove the following line if you want to use click to focus. + #Enter = "Any Any" { Actions = "Focus" } +} + +OtherTitle { + ButtonRelease = "1" { Actions = "Raise; Focus" } + ButtonRelease = "2" { Actions = "Focus" } + ButtonRelease = "3" { Actions = "Close" } + ButtonRelease = "Mod4 3" { Actions = "ShowMenu Window" } + ButtonRelease = "Mod1 4" { Actions = "SendToWorkspace Next; GotoWorkspace Next" } + ButtonRelease = "Mod1 5" { Actions = "SendToWorkspace Prev; GotoWorkspace Prev" } + ButtonRelease = "Mod1 Shift 4" { Actions = "SendToWorkspace PrevV; GotoWorkspace PrevV" } + ButtonRelease = "Mod1 Shift 5" { Actions = "SendToWorkspace NextV; GotoWorkspace NextV" } + Motion = "1" { Threshold = "4"; Actions = "Raise; Move" } + Motion = "Mod1 1" { Threshold = "4"; Actions = "Raise; Move" } + Motion = "Mod4 1" { Threshold = "4"; Actions = "Raise; Move" } + # Remove the following line if you want to use click to focus. + #Enter = "Any Any" { Actions = "Focus" } +} + +Border { + TopLeft { + # Remove the following line if you want to use click to focus. + #Enter = "Any Any" { Actions = "Focus" } + ButtonPress = "1" { Actions = "Focus; Resize TopLeft" } } + Top { + # Remove the following line if you want to use click to focus. + #Enter = "Any Any" { Actions = "Focus" } + ButtonPress = "1" { Actions = "Focus; Resize Top" } } + TopRight { + # Remove the following line if you want to use click to focus. + #Enter = "Any Any" { Actions = "Focus" } + ButtonPress = "1" { Actions = "Focus; Resize TopRight" } } + Left { + # Remove the following line if you want to use click to focus. + #Enter = "Any Any" { Actions = "Focus" } + ButtonPress = "1" { Actions = "Focus; Resize Left" } } + Right { + # Remove the following line if you want to use click to focus. + #Enter = "Any Any" { Actions = "Focus" } + ButtonPress = "1" { Actions = "Focus; Resize Right" } } + BottomLeft { + # Remove the following line if you want to use click to focus. + #Enter = "Any Any" { Actions = "Focus" } + ButtonPress = "1" { Actions = "Focus; Resize BottomLeft" } } + Bottom { + # Remove the following line if you want to use click to focus. + #Enter = "Any Any" { Actions = "Focus" } + ButtonPress = "1" { Actions = "Focus; Resize Bottom" } } + BottomRight { + # Remove the following line if you want to use click to focus. + #Enter = "Any Any" { Actions = "Focus" } + ButtonPress = "1" { Actions = "Focus; Resize BottomRight" } } +} + +ScreenEdge { + Down { + Enter = "Mod1 Any" { Actions = "GoToWorkspace Down" } + ButtonRelease = "3" { Actions = "ShowMenu Root" } + ButtonRelease = "2" { Actions = "ShowMenu Goto" } + ButtonRelease = "1" { Actions = "GoToWorkspace Down" } + ButtonRelease = "Mod4 2" { Actions = "ShowMenu GotoClient" } + ButtonRelease = "4" { Actions = "GoToWorkspace Up" } + ButtonRelease = "5" { Actions = "GoToWorkspace Down" } + ButtonRelease = "Mod1 4" { Actions = "GoToWorkspace PrevV" } + ButtonRelease = "Mod1 5" { Actions = "GoToWorkspace NextV" } + EnterMoving = "Any Any" { Actions = "WarpToWorkspace Down" } + } + Up { + Enter = "Mod1 Any" { Actions = "GoToWorkspace Up" } + ButtonRelease = "3" { Actions = "ShowMenu Root" } + ButtonRelease = "2" { Actions = "ShowMenu Goto" } + ButtonRelease = "1" { Actions = "GoToWorkspace Up" } + ButtonRelease = "Mod4 2" { Actions = "ShowMenu GotoClient" } + ButtonRelease = "4" { Actions = "GoToWorkspace Up" } + ButtonRelease = "5" { Actions = "GoToWorkspace Down" } + ButtonRelease = "Mod1 4" { Actions = "GoToWorkspace PrevV" } + ButtonRelease = "Mod1 5" { Actions = "GoToWorkspace NextV" } + EnterMoving = "Any Any" { Actions = "WarpToWorkspace Up" } + } + Left { + Enter = "Mod1 Any" { Actions = "GoToWorkspace Left" } + ButtonRelease = "3" { Actions = "ShowMenu Root" } + ButtonRelease = "1" { Actions = "GoToWorkspace Left" } + DoubleClick = "1" { Actions = "GoToWorkspace Left" } + ButtonRelease = "2" { Actions = "ShowMenu Goto" } + ButtonRelease = "Mod4 2" { Actions = "ShowMenu GotoClient" } + ButtonRelease = "4" { Actions = "GoToWorkspace Right" } + ButtonRelease = "5" { Actions = "GoToWorkspace Left" } + ButtonRelease = "Mod1 4" { Actions = "GoToWorkspace Next" } + ButtonRelease = "Mod1 5" { Actions = "GoToWorkspace Prev" } + EnterMoving = "Any Any" { Actions = "WarpToWorkspace Left" } + } + Right { + Enter = "Mod1 Any" { Actions = "GoToWorkspace Right" } + ButtonRelease = "3" { Actions = "ShowMenu Root" } + ButtonRelease = "1" { Actions = "GoToWorkspace Right" } + DoubleClick = "1" { Actions = "GoToWorkspace Right" } + ButtonRelease = "2" { Actions = "ShowMenu Goto" } + ButtonRelease = "Mod4 2" { Actions = "ShowMenu GotoClient" } + ButtonRelease = "4" { Actions = "GoToWorkspace Right" } + ButtonRelease = "5" { Actions = "GoToWorkspace Left" } + ButtonRelease = "Mod1 4" { Actions = "GoToWorkspace Next" } + ButtonRelease = "Mod1 5" { Actions = "GoToWorkspace Prev" } + EnterMoving = "Any Any" { Actions = "WarpToWorkspace Right" } + } +} + +Client { + # Remove the following line and uncomment the alternative if windows should raise when clicked. + #ButtonPress = "1" { Actions = "Focus" } + # Uncomment the following line if windows should raise when clicked. + ButtonPress = "1" { Actions = "Focus; Raise" } + + ButtonRelease = "Mod1 1" { Actions = "Focus; Raise" } + ButtonRelease = "Mod4 1" { Actions = "Lower" } + ButtonRelease = "Mod1 4" { Actions = "SendToWorkspace Next; GotoWorkspace Next" } + ButtonRelease = "Mod1 5" { Actions = "SendToWorkspace Prev; GotoWorkspace Prev" } + ButtonRelease = "Mod1 Shift 4" { Actions = "SendToWorkspace PrevV; GotoWorkspace PrevV" } + ButtonRelease = "Mod1 Shift 5" { Actions = "SendToWorkspace NextV; GotoWorkspace NextV" } + ButtonRelease = "Ctrl Mod1 1" { Actions = "Focus; Raise True" } + Motion = "Mod1 1" { Threshold = "4"; Actions = "Focus; Raise; Move" } + Motion = "Mod4 1" { Threshold = "4"; Actions = "Focus; Raise; Move" } + Motion = "Mod1 2" { Threshold = "4"; Actions = "GroupingDrag True" } + Motion = "Mod1 3" { Actions = "Resize" } + # Remove the following line if you want to use click to focus. + #Enter = "Any Any" { Actions = "Focus" } +} + +Root { + ButtonRelease = "3" { Actions = "ShowMenu Root" } + ButtonRelease = "2" { Actions = "ShowMenu Goto" } + ButtonRelease = "Mod4 2" { Actions = "ShowMenu GotoClient" } + # Horizontal movement + ButtonRelease = "4" { Actions = "GoToWorkspace Right" } + ButtonRelease = "5" { Actions = "GoToWorkspace Left" } + ButtonRelease = "Mod1 4" { Actions = "GoToWorkspace Next" } + ButtonRelease = "Mod1 5" { Actions = "GoToWorkspace Prev" } + # Vertical movement + ButtonRelease = "Shift 4" { Actions = "GoToWorkspace Up" } + ButtonRelease = "Shift 5" { Actions = "GoToWorkspace Down" } + ButtonRelease = "Mod1 Shift 4" { Actions = "GoToWorkspace NextV" } + ButtonRelease = "Mod1 Shift 5" { Actions = "GoToWorkspace PrevV" } + ButtonRelease = "1" { Actions = "HideAllMenus" } +} + +Menu { + Enter = "Any Any" { Actions = "Focus" } + Motion = "Mod1 1" { Threshold = "4"; Actions = "Focus; Raise; Move" } +} + +Other { + Enter = "Any Any" { Actions = "Focus" } + ButtonRelease = "3" { Actions = "Close" } + Motion = "1" { Actions = "Focus; Raise; Move" } + Motion = "Mod1 1" { Threshold = "4"; Actions = "Focus; Raise; Move" } +} diff --git a/data/pekwm/scripts/Jamfile b/data/pekwm/scripts/Jamfile new file mode 100644 index 0000000..ba20ae7 --- /dev/null +++ b/data/pekwm/scripts/Jamfile @@ -0,0 +1,13 @@ +# +# $Id: Jamfile 2858 2009-10-03 07:24:06Z karijes $ +# +# Part of Equinox Desktop Environment (EDE). +# Copyright (c) 2009-2011 EDE Authors. +# +# This program is licensed under terms of the +# GNU General Public License version 2 or newer. +# See COPYING for details. + +SubDir TOP data pekwm scripts ; + +InstallProgram "$(PEKWM_DATA_DIR)/scripts" : pekwm_themeset.sh pekwm_ws_menu.sh ; diff --git a/data/pekwm/scripts/pekwm_themeset.sh b/data/pekwm/scripts/pekwm_themeset.sh new file mode 100644 index 0000000..6e262e2 --- /dev/null +++ b/data/pekwm/scripts/pekwm_themeset.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# +# Copyright © 2003-2009 the pekwm development team +# +# Add this to your menu to use this script: +# +# SubMenu = "Themes" { +# Entry { Actions = "Dynamic /path/to/this/file /path/to/themedir" } +# } +# + +# Check usage +if test -z "${1}"; then + echo "usage: $0 /path/to/themedir (theme)"; + exit 1 +fi + +if test -z "${2}"; then + theme_dir="${1}" + + echo "Dynamic {" + + # Check that theme directory exists, if it does not exist create a + # dummy entry that says the dir does not exist. + if test -d "${theme_dir}"; then + ( cd ${theme_dir}; + for theme_name in *; do + # Themes must be directories. This test also prevents * globbing + # problems if theme_dir is empty. + if test -d "${theme_name}"; then + theme_path="${theme_dir}/${theme_name}" + echo "Entry = \"${theme_name}\" { Actions = \"Exec ${0} ${1} ${theme_path}\" }" + fi + done ) + else + echo "Entry = \"No such directory ${theme_dir}\" { Actions = \"None\" }" + fi + + echo "}" + +else + # Check for configuration file, if the environment is not set the + # script is not being run from pekwm, then exit with failure. + if test -f "${PEKWM_CONFIG_FILE}"; then + theme="$(echo "${2}" | /bin/sed -e "s@^${HOME}@~@" | /bin/sed -e 's/\//\\\//g')" + + # Get temporary file, not all platforms have mktemp though + if test -x "/bin/mktemp"; then + tmp_file=$(mktemp -t pekwm_themeset.XXXXXX) || exit 1; + else + tmp_file="/tmp/pekwm_themeset.${USER}" + fi + + # Change theme + /bin/sed -e "s/^\([^#]*\)[Tt][Hh][Ee][Mm][Ee]\ =\ \"[^\"]*\"/\\1Theme\ =\ \"${theme}\"/" "${PEKWM_CONFIG_FILE}" > "${tmp_file}" + mv "${tmp_file}" "${PEKWM_CONFIG_FILE}" + + # Reload pekwm + kill -HUP $(xprop -root _NET_WM_PID | awk '/_NET_WM_PID/ { print $3 }') + else + exit 1 + fi +fi + +exit 0 diff --git a/data/pekwm/scripts/pekwm_ws_menu.sh b/data/pekwm/scripts/pekwm_ws_menu.sh new file mode 100644 index 0000000..f2662c4 --- /dev/null +++ b/data/pekwm/scripts/pekwm_ws_menu.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# +# Copyright © 2009 the pekwm development team +# + +if test "${1}" = "send"; then + action="SendToWorkspace" +elif test "${1}" = "goto"; then + action="GotoWorkspace" +else + echo "usage: $0 goto|send dynamic" + exit 1 +fi + +if test "${2}" = "dynamic"; then + echo "Dynamic {" +fi + +num_workspaces="$(xprop -root _NET_NUMBER_OF_DESKTOPS | awk '{ print $3 }')" + +i=1; +while test "${i}" -le "${num_workspaces}"; do + echo "Entry = \"Workspace $i\" { Actions = \"${action} ${i}\" }" + + i=$(($i + 1)) +done + + +if test "${2}" = "dynamic"; then + echo "}" +fi diff --git a/data/pekwm/start b/data/pekwm/start new file mode 100644 index 0000000..ba11dda --- /dev/null +++ b/data/pekwm/start @@ -0,0 +1,18 @@ +#!/bin/sh +# PekWM start file +# This file is a simple shell script; It gets run on pekwm startup, after +# the theme and all config has loaded if it is set executable +# (chmod +x start). +# +# This is different from ~/.xinitrc because a normal configuration of +# .xinitrc you'll run all commands, then launch the window manager last. +# +# It also gets re-run every time pekwm is restarted. +# +# As for it's usefulness, well, it's up to you. I actually set my background +# from my start file; since it runs after the theme gets loaded, this +# effectively overrides whatever's in the theme. +# +# There's probably a few other good uses for it, too. I mainly pushed for it +# because when I was doing fluxbox's docs, people used to complain that there +# wasn't one, and I wanted to avoid that for pekwm. ;) --eyez diff --git a/data/pekwm/themes/Jamfile b/data/pekwm/themes/Jamfile new file mode 100644 index 0000000..1842a55 --- /dev/null +++ b/data/pekwm/themes/Jamfile @@ -0,0 +1,13 @@ +# +# $Id: Jamfile 2858 2009-10-03 07:24:06Z karijes $ +# +# Part of Equinox Desktop Environment (EDE). +# Copyright (c) 2009-2011 EDE Authors. +# +# This program is licensed under terms of the +# GNU General Public License version 2 or newer. +# See COPYING for details. + +SubDir TOP data pekwm themes ; + +SubInclude TOP data pekwm themes default ; diff --git a/data/pekwm/themes/default-plain/theme b/data/pekwm/themes/default-plain/theme new file mode 100644 index 0000000..d3a2989 --- /dev/null +++ b/data/pekwm/themes/default-plain/theme @@ -0,0 +1,216 @@ +# default theme for pekwm +# +# ChangeLog: +# +# * Update for 0.1.8 with templates enabled. +# * Created for version 0.1.7 +# + +Require { + Templates = "True" +} + +Define = "BaseDecor" { + Height = "17" + HeightAdapt = "True" + + # increase first number to bring title text downwards + Pad = "2 0 2 0" + + Focused = "Empty" + Unfocused = "Empty" + + Tab { + Focused = "Solid #dddddd" + FocusedSelected = "Solid #ffffff" + Unfocused = "Solid #aaaaaa" + UnfocusedSelected = "Solid #aaaaaa" + } + Separator { + Focused = "Empty" + Unfocused = "Empty" + } + Font { + Focused = "Sans:size=12#CENTER#XFT" + } + FontColor { + Focused = "#000000" + FocusedSelected = "#000000" + Unfocused = "#333333" + UnfocusedSelected = "#333333" + } + Border { + Focused { + TopLeft = "Solid #000000 1x1" + Top = "Solid #000000 1x1" + TopRight = "Solid #000000 1x1" + Left = "Solid #000000 1x1" + Right = "Solid #000000 1x1" + BottomLeft = "Solid #000000 1x1" + Bottom = "Solid #000000 1x1" + BottomRight = "Solid #000000 1x1" + } + Unfocused { + TopLeft = "Solid #666666 1x1" + Top = "Solid #666666 1x1" + TopRight = "Solid #666666 1x1" + Left = "Solid #666666 1x1" + Right = "Solid #666666 1x1" + BottomLeft = "Solid #666666 1x1" + Bottom = "Solid #666666 1x1" + BottomRight = "Solid #666666 1x1" + } + } +} + +Define = "ButtonStates" { + Focused = "Solid #ff790c 0x0" + Unfocused = "Solid #999999 0x0" + Pressed = "Solid #000000 0x0" + Hover = "Solid #ffcea5 0x0" +} + +Define = "ButtonStatesRemote" { + @ButtonStates +} + +Define = "ButtonStatesWarning" { + @ButtonStates + Focused = "Solid #ff0000 0x0" +} + +Define = "BaseButtonDecor" { + @BaseDecor + + Buttons { + Left { + @ButtonStates + + Button = "1" { Actions = "Close" } + Button = "2" { Actions = "Kill" } + } + Right { + @ButtonStates + + Button = "1" { Actions = "Toggle Maximized 1 1" } + Button = "2" { Actions = "Toggle Maximized 0 1" } + Button = "3" { Actions = "Toggle Maximized 1 0" } + } + } +} + +PDecor { + Decor = "DEFAULT" { + Title { + @BaseButtonDecor + } + } + + Decor = "REMOTE" { + Title { + @BaseButtonDecor + + Tab { + Focused = "Solid #fffcec" + FocusedSelected = "Solid #fff9d6" + } + + Buttons { + Left { + @ButtonStatesRemote + } + Right { + @ButtonStatesRemote + } + } + } + } + + Decor = "WARNING" { + Title { + @BaseButtonDecor + + Tab { + Focused = "Solid #ee5454" + FocusedSelected = "Solid #ff7474" + } + + Buttons { + Left { + @ButtonStatesWarning + } + Right { + @ButtonStatesWarning + } + } + } + } + + Decor = "MENU" { + Title { + @BaseDecor + } + } + + Decor = "WORKSPACEINDICATOR" { + Title { + @BaseDecor + + Height = "0" + HeightAdapt = "False" + } + } +} + +Harbour { + Texture = "SolidRaised #ffffff #000000 #000000 1 0" +} + +Menu { + Pad = "2 2 2 2" + Focused { + Font = "Sans:size=12#XFT" + Background = "Empty" + Item = "Solid #ffffff 1 0" + Separator = "Solid #aaaaaa 0x1" + Arrow = "Solid #ff790c 4x4" + Text = "#000000" + } + Unfocused { + Font = "Sans:size=12#XFT" + Background = "Empty" + Item = "Solid #cccccc 1 0" + Separator = "Solid #999999 0x2" + Arrow = "Solid #999999 4x4" + Text = "#000000" + } + Selected { + Font = "Sans:size=12#XFT" + Background = "Empty" + Item = "Solid #eeeeee" + Arrow = "Solid #ff790c 4x4" + Text = "#000000" + } +} + +CmdDialog { + Font = "Sans:size=12#CENTER#XFT" + Texture = "Solid #ffffff" + Text = "#000000" +} + +Status { + Font = "Sans:size=12#CENTER#XFT" + Texture = "Solid #ffffff" + Text = "#000000" +} + +WorkspaceIndicator { + Font = "Sans:size=12#XFT" + Background = "Solid #ffffff" + Workspace = "Solid #cccccc" + WorkspaceActive = "Solid #aaaaaa" + Text = "#000000" + EdgePadding = "5" + WorkspacePadding = "2" +} diff --git a/data/pekwm/themes/default/Jamfile b/data/pekwm/themes/default/Jamfile new file mode 100644 index 0000000..09bdaf3 --- /dev/null +++ b/data/pekwm/themes/default/Jamfile @@ -0,0 +1,15 @@ +# +# $Id: Jamfile 2858 2009-10-03 07:24:06Z karijes $ +# +# Part of Equinox Desktop Environment (EDE). +# Copyright (c) 2009-2011 EDE Authors. +# +# This program is licensed under terms of the +# GNU General Public License version 2 or newer. +# See COPYING for details. + +SubDir TOP data pekwm themes default ; + +IMAGES = [ Wildcard *.png ] ; + +InstallData "$(PEKWM_DATA_DIR)/themes/default" : $(IMAGES) theme ; diff --git a/data/pekwm/themes/default/arrow.png b/data/pekwm/themes/default/arrow.png new file mode 100644 index 0000000..2c745ac Binary files /dev/null and b/data/pekwm/themes/default/arrow.png differ diff --git a/data/pekwm/themes/default/arrow_focus.png b/data/pekwm/themes/default/arrow_focus.png new file mode 100644 index 0000000..ef2f506 Binary files /dev/null and b/data/pekwm/themes/default/arrow_focus.png differ diff --git a/data/pekwm/themes/default/bottom-border.png b/data/pekwm/themes/default/bottom-border.png new file mode 100644 index 0000000..025dceb Binary files /dev/null and b/data/pekwm/themes/default/bottom-border.png differ diff --git a/data/pekwm/themes/default/bottom-border_unfocus.png b/data/pekwm/themes/default/bottom-border_unfocus.png new file mode 100644 index 0000000..0c330b3 Binary files /dev/null and b/data/pekwm/themes/default/bottom-border_unfocus.png differ diff --git a/data/pekwm/themes/default/bottom-left.png b/data/pekwm/themes/default/bottom-left.png new file mode 100644 index 0000000..55271d7 Binary files /dev/null and b/data/pekwm/themes/default/bottom-left.png differ diff --git a/data/pekwm/themes/default/bottom-left_unfocus.png b/data/pekwm/themes/default/bottom-left_unfocus.png new file mode 100644 index 0000000..47250a6 Binary files /dev/null and b/data/pekwm/themes/default/bottom-left_unfocus.png differ diff --git a/data/pekwm/themes/default/bottom-right.png b/data/pekwm/themes/default/bottom-right.png new file mode 100644 index 0000000..7f7150f Binary files /dev/null and b/data/pekwm/themes/default/bottom-right.png differ diff --git a/data/pekwm/themes/default/bottom-right_unfocus.png b/data/pekwm/themes/default/bottom-right_unfocus.png new file mode 100644 index 0000000..534ff3b Binary files /dev/null and b/data/pekwm/themes/default/bottom-right_unfocus.png differ diff --git a/data/pekwm/themes/default/button.png b/data/pekwm/themes/default/button.png new file mode 100644 index 0000000..8c1dd6f Binary files /dev/null and b/data/pekwm/themes/default/button.png differ diff --git a/data/pekwm/themes/default/button_hover.png b/data/pekwm/themes/default/button_hover.png new file mode 100644 index 0000000..8999572 Binary files /dev/null and b/data/pekwm/themes/default/button_hover.png differ diff --git a/data/pekwm/themes/default/button_press.png b/data/pekwm/themes/default/button_press.png new file mode 100644 index 0000000..6a63180 Binary files /dev/null and b/data/pekwm/themes/default/button_press.png differ diff --git a/data/pekwm/themes/default/button_unfocus.png b/data/pekwm/themes/default/button_unfocus.png new file mode 100644 index 0000000..5ca9051 Binary files /dev/null and b/data/pekwm/themes/default/button_unfocus.png differ diff --git a/data/pekwm/themes/default/item.png b/data/pekwm/themes/default/item.png new file mode 100644 index 0000000..07a827d Binary files /dev/null and b/data/pekwm/themes/default/item.png differ diff --git a/data/pekwm/themes/default/item_focus.png b/data/pekwm/themes/default/item_focus.png new file mode 100644 index 0000000..02c4e6d Binary files /dev/null and b/data/pekwm/themes/default/item_focus.png differ diff --git a/data/pekwm/themes/default/left-border.png b/data/pekwm/themes/default/left-border.png new file mode 100644 index 0000000..d0c4aa8 Binary files /dev/null and b/data/pekwm/themes/default/left-border.png differ diff --git a/data/pekwm/themes/default/left-border_unfocus.png b/data/pekwm/themes/default/left-border_unfocus.png new file mode 100644 index 0000000..a97793c Binary files /dev/null and b/data/pekwm/themes/default/left-border_unfocus.png differ diff --git a/data/pekwm/themes/default/menu-bottom.png b/data/pekwm/themes/default/menu-bottom.png new file mode 100644 index 0000000..84baec9 Binary files /dev/null and b/data/pekwm/themes/default/menu-bottom.png differ diff --git a/data/pekwm/themes/default/menu-bottom_unfocus.png b/data/pekwm/themes/default/menu-bottom_unfocus.png new file mode 100644 index 0000000..ca03d45 Binary files /dev/null and b/data/pekwm/themes/default/menu-bottom_unfocus.png differ diff --git a/data/pekwm/themes/default/menuline.png b/data/pekwm/themes/default/menuline.png new file mode 100644 index 0000000..d418dcc Binary files /dev/null and b/data/pekwm/themes/default/menuline.png differ diff --git a/data/pekwm/themes/default/right-border.png b/data/pekwm/themes/default/right-border.png new file mode 100644 index 0000000..5fd5e1e Binary files /dev/null and b/data/pekwm/themes/default/right-border.png differ diff --git a/data/pekwm/themes/default/right-border_unfocus.png b/data/pekwm/themes/default/right-border_unfocus.png new file mode 100644 index 0000000..3976507 Binary files /dev/null and b/data/pekwm/themes/default/right-border_unfocus.png differ diff --git a/data/pekwm/themes/default/tab-separator.png b/data/pekwm/themes/default/tab-separator.png new file mode 100644 index 0000000..9859c1c Binary files /dev/null and b/data/pekwm/themes/default/tab-separator.png differ diff --git a/data/pekwm/themes/default/tab-separator_unfocus.png b/data/pekwm/themes/default/tab-separator_unfocus.png new file mode 100644 index 0000000..bc99e05 Binary files /dev/null and b/data/pekwm/themes/default/tab-separator_unfocus.png differ diff --git a/data/pekwm/themes/default/theme b/data/pekwm/themes/default/theme new file mode 100644 index 0000000..03dbd57 --- /dev/null +++ b/data/pekwm/themes/default/theme @@ -0,0 +1,225 @@ +# default-nice Pekwm theme +# License: GPL +# Author: adriano.src +# Email: adriano.src@gmail.com +# Homepage: http://adrinux.wordpress.com + +$FONT = "XFT#Sans:size=9#Left" +$FONT_TITLE = "XFT#Sans:size=9#Center" + +Require { + Templates = "True" +} + +Define = "BaseDecor" { + Height = "20" + + # Increase first number to bring title text downwards + Pad = "2 5 5 0" + + Focused = "Image title.png" + Unfocused = "Image title_unfocus.png" + + Tab { + Focused = "Image title.png" + FocusedSelected = "Image title.png" + Unfocused = "Image title_unfocus.png" + UnfocusedSelected = "Image title_unfocus.png" + } + Separator { + Focused = "Image tab-separator.png" + Unfocused = "Image tab-separator_unfocus.png" + } + Font { + Focused = "$FONT_TITLE" + } + FontColor { + Focused = "#ffc571" + FocusedSelected = "white" + Unfocused = "#777777" + UnfocusedSelected = "#777777" + } + Border { + Focused { + TopLeft = "Image top-left.png" + Top = "Image top-border.png" + TopRight = "Image top-right.png" + Left = "Image left-border.png" + Right = "Image right-border.png" + BottomLeft = "Image bottom-left.png" + Bottom = "Image bottom-border.png" + BottomRight = "Image bottom-right.png" + } + Unfocused { + TopLeft = "Image top-left_unfocus.png" + Top = "Image top-border_unfocus.png" + TopRight = "Image top-right_unfocus.png" + Left = "Image left-border_unfocus.png" + Right = "Image right-border_unfocus.png" + BottomLeft = "Image bottom-left_unfocus.png" + Bottom = "Image bottom-border_unfocus.png" + BottomRight = "Image bottom-right_unfocus.png" + } + } +} + +Define = "BaseButtons" { + Buttons { + Right = "Close" { + Focused = "Image button.png" + Unfocused = "Image button_unfocus.png" + Hoover = "Image button_hover.png" + Pressed = "Image button_press.png" + Button = "1" { Actions = "Close" } + Button = "3" { Actions = "Kill" } + } + + Right = "Maximize" { + Focused = "Image button.png" + Unfocused = "Image button_unfocus.png" + Hoover = "Image button_hover.png" + Pressed = "Image button_press.png" + Button = "1" { Actions = "Toggle Maximized 1 1" } + } + + Right = "Iconify" { + Focused = "Image button.png" + Unfocused = "Image button_unfocus.png" + Hoover = "Image button_hover.png" + Pressed = "Image button_press.png" + Button = "1" { Actions = "Set Iconified" } + } + + Left = "Shade" { + Focused = "Image button.png" + Unfocused = "Image button_unfocus.png" + Hoover = "Image button_hover.png" + Pressed = "Image button_press.png" + Button = "1" { Actions = "Toggle Shaded" } + } + } +} + +Define = "EmptyDecor" { + Focused = "Empty" + Unfocused = "Empty" + + Tab { + Focused = "Empty" + FocusedSelected = "Empty" + Unfocused = "Empty" + UnfocusedSelected = "Empty" + } + + Separator { + Focused = "Empty" + Unfocused = "Empty" + } + + Font { + Focused = "Empty" + } + + FontColor { + Focused = "Empty" + FocusedSelected = "Empty" + Unfocused = "Empty" + UnfocusedSelected = "Empty" + } + + Border { + Focused { + TopLeft = "Empty" + Top = "Empty" + TopRight = "Empty" + Left = "Empty" + Right = "Empty" + BottomLeft = "Empty" + Bottom = "Empty" + BottomRight = "Empty" + } + Unfocused { + TopLeft = "Empty" + Top = "Empty" + TopRight = "Empty" + Left = "Empty" + Right = "Empty" + BottomLeft = "Empty" + Bottom = "Empty" + BottomRight = "Empty" + } + } +} + +PDecor { + Decor = "Default" { + Title { + @BaseDecor + @BaseButtons + } + } + + Decor = "Menu" { + Title { + @BaseDecor + } + } + + Decor = "Titlebarless" { + Title { + @EmptyDecor + } + } + + Decor = "Statuswindow" { + Title { + @EmptyDecor + } + } +} + +Harbour { + Texture = "Solid #f9f9f9" +} + +Menu { + Pad = "0 0 4 4" + + Focused { + Font = "$FONT" + Background = "Solid #f9f9f9" + Item = "Empty" + Text = "#8b8b89" + Separator = "Image menuline.png#Scaled" + Arrow = "Image arrow.png" + } + Unfocused { + Font = "$FONT" + Background = "Solid #f9f9f9" + Item = "Empty" + Text = "#777777" + Separator = "Image menuline.png#Scaled" + Arrow = "Image arrow.png" + } + Selected { + Font = "$FONT" + Background = "Solid #f88408" + Item = "Image item_focus.png" + Text = "#ffffff" + Arrow = "Image arrow_focus.png" + } +} + +CmdDialog { + Font = "$FONT" + Texture = "Solid #ffffff" + Text = "#000000" + Pad = "3 0 1 10" +} + +Status { + Font = "$FONT" + Texture = "Solid #ffffff" + Text = "#8b8b89" + Pad = "2 2 10 10" +} diff --git a/data/pekwm/themes/default/title.png b/data/pekwm/themes/default/title.png new file mode 100644 index 0000000..56e8a8e Binary files /dev/null and b/data/pekwm/themes/default/title.png differ diff --git a/data/pekwm/themes/default/title_unfocus.png b/data/pekwm/themes/default/title_unfocus.png new file mode 100644 index 0000000..cc5ddb6 Binary files /dev/null and b/data/pekwm/themes/default/title_unfocus.png differ diff --git a/data/pekwm/themes/default/top-border.png b/data/pekwm/themes/default/top-border.png new file mode 100644 index 0000000..e7ca31c Binary files /dev/null and b/data/pekwm/themes/default/top-border.png differ diff --git a/data/pekwm/themes/default/top-border_unfocus.png b/data/pekwm/themes/default/top-border_unfocus.png new file mode 100644 index 0000000..b8ef309 Binary files /dev/null and b/data/pekwm/themes/default/top-border_unfocus.png differ diff --git a/data/pekwm/themes/default/top-left.png b/data/pekwm/themes/default/top-left.png new file mode 100644 index 0000000..b606f2a Binary files /dev/null and b/data/pekwm/themes/default/top-left.png differ diff --git a/data/pekwm/themes/default/top-left_unfocus.png b/data/pekwm/themes/default/top-left_unfocus.png new file mode 100644 index 0000000..ca33c58 Binary files /dev/null and b/data/pekwm/themes/default/top-left_unfocus.png differ diff --git a/data/pekwm/themes/default/top-right.png b/data/pekwm/themes/default/top-right.png new file mode 100644 index 0000000..9591c93 Binary files /dev/null and b/data/pekwm/themes/default/top-right.png differ diff --git a/data/pekwm/themes/default/top-right_unfocus.png b/data/pekwm/themes/default/top-right_unfocus.png new file mode 100644 index 0000000..db234b8 Binary files /dev/null and b/data/pekwm/themes/default/top-right_unfocus.png differ diff --git a/data/pekwm/vars b/data/pekwm/vars new file mode 100644 index 0000000..cd1027e --- /dev/null +++ b/data/pekwm/vars @@ -0,0 +1 @@ +$TERM="xterm -fn fixed +sb -bg white -fg black" diff --git a/ede-launch/ede-launch.cpp b/ede-launch/ede-launch.cpp index 8a21282..65769ab 100644 --- a/ede-launch/ede-launch.cpp +++ b/ede-launch/ede-launch.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include "icons/run.xpm" @@ -58,6 +59,7 @@ EDELIB_NS_USING(run_sync) EDELIB_NS_USING(run_async) EDELIB_NS_USING(alert) EDELIB_NS_USING(file_path) +EDELIB_NS_USING(window_center_on_screen) static Fl_Pixmap image_run((const char**)run_xpm); static Fl_Input* dialog_input; @@ -363,6 +365,7 @@ static int start_dialog(int argc, char** argv) { cancel->callback(cancel_cb, win); win->end(); win->window_icon(run_xpm); + window_center_on_screen(win); win->show(argc, argv); return Fl::run(); diff --git a/edewm/locale/messages.pot b/edewm/locale/messages.pot index 9512350..af72dbf 100644 --- a/edewm/locale/messages.pot +++ b/edewm/locale/messages.pot @@ -8,81 +8,82 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2005-02-04 12:33+0100\n" +"POT-Creation-Date: 2011-10-22 12:11+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: Desktop.cpp:138 Desktop.cpp:180 +#: edewm/Desktop.cpp:138 edewm/Desktop.cpp:180 #, c-format msgid "Workspace %d" msgstr "" -#: Frame.cpp:106 +#: edewm/Frame.cpp:146 msgid "Untitled" msgstr "" -#: Frame.cpp:938 +#: edewm/Frame.cpp:977 #, c-format msgid "EDEWM: Internal bug, when restacking (%d != %d)! Exiting... " msgstr "" -#: Titlebar.cpp:215 +#: edewm/Titlebar.cpp:225 msgid "Sticky" msgstr "" -#: Titlebar.cpp:275 Titlebar.cpp:320 +#: edewm/Titlebar.cpp:285 edewm/Titlebar.cpp:330 msgid "Set size" msgstr "" -#: Titlebar.cpp:276 +#: edewm/Titlebar.cpp:286 msgid "Set size to window:" msgstr "" -#: Titlebar.cpp:285 +#: edewm/Titlebar.cpp:295 msgid "width:" msgstr "" -#: Titlebar.cpp:287 +#: edewm/Titlebar.cpp:297 msgid "height:" msgstr "" -#: Titlebar.cpp:291 +#: edewm/Titlebar.cpp:301 msgid "&OK" msgstr "" -#: Titlebar.cpp:294 +#: edewm/Titlebar.cpp:304 msgid "&Cancel" msgstr "" -#: Titlebar.cpp:318 Titlebar.cpp:331 +#: edewm/Titlebar.cpp:328 edewm/Titlebar.cpp:341 msgid "Maximize" msgstr "" -#: Titlebar.cpp:319 +#: edewm/Titlebar.cpp:329 msgid "Minimize" msgstr "" -#: Titlebar.cpp:321 +#: edewm/Titlebar.cpp:331 msgid "To Desktop" msgstr "" -#: Titlebar.cpp:322 +#: edewm/Titlebar.cpp:332 msgid "Kill" msgstr "" -#: Titlebar.cpp:323 +#: edewm/Titlebar.cpp:333 msgid "Close" msgstr "" -#: Titlebar.cpp:330 +#: edewm/Titlebar.cpp:340 msgid "Restore" msgstr "" -#: Windowmanager.cpp:90 +#: edewm/Windowmanager.cpp:90 #, c-format msgid "Another window manager is running. You must exit it before running %s." msgstr "" diff --git a/evoke/ede-startup.conf b/evoke/ede-startup.conf index 768d485..0598bc4 100644 --- a/evoke/ede-startup.conf +++ b/evoke/ede-startup.conf @@ -1,10 +1,10 @@ # default ede-startup configuration [Startup] - start_order = edewm,ede-desktop,ede-panel,emountd,xscreensaver,autostart + start_order = wm,ede-desktop,ede-panel,emountd,xscreensaver,autostart splash_theme = scape -[edewm] - exec = edewm +[wm] + exec = pekwm icon = edewm.png description = Starting window manager... diff --git a/m4/pekwm.m4 b/m4/pekwm.m4 new file mode 100644 index 0000000..992ff0d --- /dev/null +++ b/m4/pekwm.m4 @@ -0,0 +1,200 @@ +dnl +dnl $Id: xlib.m4 1856 2007-03-17 11:28:25Z karijes $ +dnl +dnl Part of Equinox Desktop Environment (EDE). +dnl Copyright (c) pekwm authors. +dnl +dnl This program is licenced under terms of the +dnl GNU General Public Licence version 2 or newer. +dnl See COPYING for details. + +dnl all dependencies needed for pekwm +AC_DEFUN([EDE_CHECK_PEKWM_DEPENDENCIES], [ +AC_LANG_SAVE +AC_LANG_CPLUSPLUS + +dnl Check for iconv +dnl AM_ICONV +dnl if test "x$am_cv_func_iconv" != "xyes"; then +dnl AC_MSG_ERROR([Could not find iconv.]) +dnl fi +dnl +dnl PEKWM_LIBS="$LIBS $LIBICONV" +dnl PEKWM_CXXFLAGS="$CXXFLAGS $INCICONV" +dnl PEKWM_FEATURES="" +dnl +dnl Check for iconvctl +AC_CHECK_FUNC(iconvctl, [AC_DEFINE(HAVE_ICONVCTL, [1], [Define to 1 if you the iconvctl call])], ) + +dnl Check for Xinerama support +AC_MSG_CHECKING([whether to build support for the Xinerama extension]) +AC_CHECK_LIB(Xinerama, XineramaQueryScreens, + AC_DEFINE(HAVE_XINERAMA, [1], [Define to 1 if you want Xinerama support to be]) + PEKWM_LIBS="$PEKWM_LIBS -lXinerama" + PEKWM_FEATURES="$PEKWM_FEATURES Xinerama") + +dnl Check for Xft support +AC_MSG_CHECKING([whether to support Xft fonts]) +PKG_CHECK_MODULES([xft], [xft >= 2.0.0], HAVE_XFT=yes, HAVE_XFT=no) +if test "x$HAVE_XFT" = "xyes"; then + AC_DEFINE(HAVE_XFT, [1], [Define to 1 if you want Xft2 font support]) + PEKWM_LIBS="$PEKWM_LIBS $xft_LIBS" + PEKWM_CXXFLAGS="$PEKWM_CXXFLAGS $xft_CFLAGS" + PEKWM_FEATURES="$PEKWM_FEATURES Xft"; +else + AC_MSG_WARN([Couldn't find Xft >= 2.0.0]) +fi + +if test "x$HAVE_LIBPNG" = "xyes"; then + THEME="default" +else + THEME="default-plain" +fi +AC_SUBST([THEME]) + +dnl Check for XRANDR support +AC_MSG_CHECKING([wheter to build support for the XRANDR extension]) +PKG_CHECK_MODULES([xrandr], [xrandr >= 1.2.0], HAVE_XRANDR=yes, HAVE_XRANDR=no) +if test "x$HAVE_XRANDR" = "xyes"; then + AC_DEFINE(HAVE_XRANDR, [1], [Define to 1 if you have an XRANDR capable server]) + PEKWM_LIBS="$PEKWM_LIBS $xrandr_LIBS" + PEKWM_CXXFLAGS="$PEKWM_CXXFLAGS $xrandr_CFLAGS" + PEKWM_FEATURES="$PEKWM_FEATURES Xrandr" +else + AC_MSG_WARN([Couldn't find Xrandr >= 1.2.0]) +fi + +dnl Check for header files +AC_STDC_HEADERS +AC_CHECK_HEADERS([limits slist ext/slist]) + +dnl Check that at least one of slist or ext/slist exists +if test "x$av_cv_header_slist" = "xno" \ + && test "x$av_cv_header_ext_slist" = "xno"; then + AC_MSG_ERROR([Could not find slist or ext/slist include.]) +fi + +dnl Detect namespace slist is available in +AC_TRY_COMPILE([ +#ifdef HAVE_SLIST +#include +#else // HAVE_EXT_SLIST +#include +#endif // HAVE_SLIST + ],[std::slist test;], + AC_DEFINE(SLIST_NAMESPACE, [std], [Name of namespace slist is in]) + slist_std_namespace=yes, []) + + if test "x$slist_std_namespace" != "xyes"; then + AC_TRY_COMPILE([ +#ifdef HAVE_SLIST +#include +#else // HAVE_EXT_SLIST +#include +#endif // HAVE_SLIST + ],[__gnu_cxx::slist test;], + AC_DEFINE(SLIST_NAMESPACE, [__gnu_cxx], [Name of namespace slist is in]), []) +fi + +AC_CHECK_FUNC(setenv, [AC_DEFINE(HAVE_SETENV, [1], [Define to 1 if you the setenv systam call])], ) +AC_CHECK_FUNC(unsetenv, [AC_DEFINE(HAVE_UNSETENV, [1], [Define to 1 if you the unsetenv systam call])], ) +AC_CHECK_FUNC(swprintf, [AC_DEFINE(HAVE_SWPRINTF, [1], [Define to 1 if you have swprintf])], ) + +dnl Check whether time.h has timersub +AC_MSG_CHECKING(for timersub in time.h) +AC_TRY_LINK([#include ], + [struct timeval *a; timersub(a, a, a);], + [have_timersub=yes],[]) + +if test "x$have_timersub" = "xyes"; then + AC_MSG_RESULT(yes) + AC_DEFINE([HAVE_TIMERSUB], 1, [Define to 1 if your system defines timersub.]) +else + AC_MSG_RESULT(no) +fi + +dnl Check for XPM support +AC_MSG_CHECKING([wheter to build support XPM images]) +AC_CHECK_LIB(Xpm, XpmReadFileToPixmap, + AC_MSG_CHECKING([for X11/xpm.h]) + AC_TRY_COMPILE( +#include + , int foo = XpmSuccess, + AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_IMAGE_XPM, [1], [Define to 1 if you libXpm]) + PEKWM_LIBS="$PEKWM_LIBS -lXpm" + PEKWM_FEATURES="$PEKWM_FEATURES image-xpm", + AC_MSG_RESULT([no]))) + +dnl Check for JPEG support + AC_CHECK_LIB(jpeg, jpeg_read_header, + AC_MSG_CHECKING([for jpeglib.h]) + AC_TRY_CPP([#include ], + jpeg_ok=yes, + jpeg_ok=no) + AC_MSG_RESULT($jpeg_ok) + if test "$jpeg_ok" = yes; then + AC_DEFINE(HAVE_IMAGE_JPEG, [1], [Define to 1 if you have jpeg6b]) + PEKWM_LIBS="$PEKWM_LIBS -ljpeg" + PEKWM_FEATURES="$PEKWM_FEATURES image-jpeg" + fi, + AC_MSG_RESULT([no])) + +dnl Check for PNG support + PKG_CHECK_MODULES([libpng12], [libpng12 >= 1.2.0], HAVE_LIBPNG=yes, HAVE_LIBPNG=no) + if test "x$HAVE_LIBPNG" = "xyes"; then + AC_DEFINE(HAVE_IMAGE_PNG, [1], [Define to 1 if you have libpng12]) + PEKWM_LIBS="$PEKWM_LIBS $libpng12_LIBS" + PEKWM_CXXFLAGS="$PEKWM_CXXFLAGS $libpng12_CFLAGS" + PEKWM_FEATURES="$PEKWM_FEATURES image-png" + else + PKG_CHECK_MODULES([libpng], [libpng >= 1.0.0], HAVE_LIBPNG=yes, HAVE_LIBPNG=no) + + if test "x$HAVE_LIBPNG" = "xyes"; then + AC_DEFINE(HAVE_IMAGE_PNG, [1], [Define to 1 if you have libpng12]) + PEKWM_LIBS="PEKWM_$LIBS $libpng_LIBS" + PEKWM_CXXFLAGS="$PEKWM_CXXFLAGS $libpng_CFLAGS" + PEKWM_FEATURES="$PEKWM_FEATURES image-png" + else + AC_MSG_RESULT([no]) + fi + fi + +dnl Check whether to include debugging code +dnl AC_MSG_CHECKING([whether to include verbose debugging code]) +dnl AC_ARG_ENABLE(debug, +dnl AC_HELP_STRING([--enable-debug], +dnl [include verbose debugging code [default=no]]), , +dnl [enable_debug=no]) +dnl if test "x$enable_debug" = "xyes"; then +dnl AC_MSG_RESULT([yes]) +dnl AC_DEFINE(DEBUG, [1], [Define to 1 to compile in debug information]) +dnl FEATURES="$FEATURES debug" +dnl else +dnl AC_MSG_RESULT([no]) +dnl fi +dnl +dnl dnl Check wheter to use strict warnings +dnl AC_MSG_CHECKING([whether to use strict compile-time warnings]) +dnl AC_ARG_ENABLE(pedantic, +dnl AC_HELP_STRING([--enable-pedantic], +dnl [turn on strict compile-time warnings [default=no]]), , +dnl [enable_pedantic=no]) +dnl if test "$enable_pedantic" = "yes"; then +dnl AC_MSG_RESULT([yes]) +dnl if test "x$GXX" = "xyes"; then +dnl CXXFLAGS="-Wall -Werror -pedantic $CXXFLAGS" +dnl fi +dnl FEATURES="$FEATURES pedantic" +dnl else +dnl AC_MSG_RESULT([no]) +dnl fi +dnl + +AC_DEFINE(OPACITY, [1], [Define to 1 to compile in support for opacity hinting]) + +EVO=`date` +AC_DEFINE_UNQUOTED(FEATURES, "$PEKWM_FEATURES", [Build info for pekwm, do not touch]) +AC_DEFINE_UNQUOTED(EXTRA_VERSION_INFO, " Built on $EVO", [Build info for pekwm, do not touch]) +AC_LANG_RESTORE +]) diff --git a/pekwm/Action.hh b/pekwm/Action.hh new file mode 100644 index 0000000..694212e --- /dev/null +++ b/pekwm/Action.hh @@ -0,0 +1,243 @@ +// +// Action.hh for pekwm +// Copyright (C) 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _ACTION_HH_ +#define _ACTION_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Types.hh" + +#include +#include +#include + +class PWinObj; + +//! @brief Masks used to set Action context validity. +enum ActionOk { + KEYGRABBER_OK = (1<<1), //!< Keygrabber ok. + FRAME_OK = (1<<2), //!< Frame title ok. + CLIENT_OK = (1<<3), //!< Client click ok. + ROOTCLICK_OK = (1<<4), //!< Root window click ok. + BUTTONCLICK_OK = (1<<5), //!< Button{Press,Release} ok. + WINDOWMENU_OK = (1<<6), //!< Ok from WindowMenu. + ROOTMENU_OK = (1<<7), //!< Ok from RootMenu. + FRAME_BORDER_OK = (1<<8), //!< Frame border ok. + SCREEN_EDGE_OK = (1<<9) //!< ScreenEdge ok. +}; + +/** + * Mask used in auto properties granting/disallowing actions. + */ +enum ActionAccessMask { + ACTION_ACCESS_NO = 0, + ACTION_ACCESS_MOVE = 1<<1, + ACTION_ACCESS_RESIZE = 1<<2, + ACTION_ACCESS_MINIMIZE = 1<<3, + ACTION_ACCESS_SHADE = 1<<4, + ACTION_ACCESS_STICK = 1<<5, + ACTION_ACCESS_MAXIMIZE_HORZ = 1<<6, + ACTION_ACCESS_MAXIMIZE_VERT = 1<<7, + ACTION_ACCESS_FULLSCREEN = 1<<8, + ACTION_ACCESS_CHANGE_DESKTOP = 1<<9, + ACTION_ACCESS_CLOSE = 1<<10 +}; + +enum ActionType { + ACTION_UNSET = 0, ACTION_SET = 1, // to conform with bool values + ACTION_TOGGLE, + + ACTION_FOCUS, ACTION_UNFOCUS, + + ACTION_GROW_DIRECTION, ACTION_MAXFILL, + ACTION_RESIZE, ACTION_MOVE_RESIZE, + ACTION_RAISE, ACTION_LOWER, + ACTION_ACTIVATE_OR_RAISE, + ACTION_CLOSE, ACTION_CLOSE_FRAME, ACTION_KILL, + ACTION_MOVE_TO_EDGE, + ACTION_NEXT_FRAME, ACTION_NEXT_FRAME_MRU, + ACTION_PREV_FRAME, ACTION_PREV_FRAME_MRU, + ACTION_FOCUS_DIRECTIONAL, + ACTION_GOTO_CLIENT, + ACTION_ACTIVATE_CLIENT_REL, ACTION_MOVE_CLIENT_REL, + ACTION_ACTIVATE_CLIENT, ACTION_ACTIVATE_CLIENT_NUM, + ACTION_SEND_TO_WORKSPACE, ACTION_GOTO_WORKSPACE, + ACTION_WARP_TO_WORKSPACE, + ACTION_SHOW_MENU, ACTION_HIDE_ALL_MENUS, + ACTION_DETACH, ACTION_ATTACH_MARKED, + ACTION_ATTACH_CLIENT_IN_NEXT_FRAME, ACTION_ATTACH_CLIENT_IN_PREV_FRAME, + ACTION_ATTACH_FRAME_IN_NEXT_FRAME, ACTION_ATTACH_FRAME_IN_PREV_FRAME, + + ACTION_GOTO_CLIENT_ID, ACTION_FIND_CLIENT, + + ACTION_EXEC, ACTION_RELOAD, ACTION_RESTART, + ACTION_RESTART_OTHER, ACTION_EXIT, + + ACTION_MENU_NEXT, ACTION_MENU_PREV, ACTION_MENU_SELECT, + ACTION_MENU_ENTER_SUBMENU, ACTION_MENU_LEAVE_SUBMENU, + + ACTION_MENU_SUB, + ACTION_MENU_DYN, + + ACTION_MOVE, ACTION_GROUPING_DRAG, + ACTION_SHOW_CMD_DIALOG, + ACTION_SHOW_SEARCH_DIALOG, + + ACTION_HIDE_WORKSPACE_INDICATOR, + ACTION_SEND_KEY, +#ifdef OPACITY + ACTION_SET_OPACITY, +#endif // OPACITY + + ACTION_NO +}; + +enum ActionStateType { + ACTION_STATE_MAXIMIZED, ACTION_STATE_FULLSCREEN, + ACTION_STATE_SHADED, ACTION_STATE_STICKY, + ACTION_STATE_ALWAYS_ONTOP, ACTION_STATE_ALWAYS_BELOW, + ACTION_STATE_DECOR_BORDER, ACTION_STATE_DECOR_TITLEBAR, + ACTION_STATE_DECOR, ACTION_STATE_TITLE, + ACTION_STATE_ICONIFIED, ACTION_STATE_TAGGED, + ACTION_STATE_MARKED, ACTION_STATE_SKIP, + ACTION_STATE_CFG_DENY, +#ifdef OPACITY + ACTION_STATE_OPAQUE, +#endif // OPACITY + ACTION_STATE_HARBOUR_HIDDEN, + ACTION_STATE_GLOBAL_GROUPING, + + ACTION_STATE_NO +}; + +enum StateAction { + STATE_SET = ACTION_SET, + STATE_UNSET = ACTION_UNSET, + STATE_TOGGLE = ACTION_TOGGLE +}; + +enum MoveResizeActionType { + MOVE_HORIZONTAL = 1, MOVE_VERTICAL, + RESIZE_HORIZONTAL, RESIZE_VERTICAL, + MOVE_SNAP, + MOVE_CANCEL, MOVE_END, + NO_MOVERESIZE_ACTION = 0 +}; + +enum InputDialogAction { + INPUT_INSERT, INPUT_REMOVE, + INPUT_CLEAR, INPUT_CLEARFROMCURSOR, INPUT_EXEC, INPUT_CLOSE, + INPUT_COMPLETE, INPUT_COMPLETE_ABORT, + INPUT_CURS_NEXT, INPUT_CURS_PREV, + INPUT_CURS_END, INPUT_CURS_BEGIN, + INPUT_HIST_NEXT, INPUT_HIST_PREV, + INPUT_NO_ACTION +}; + +// Action Utils +namespace ActionUtil { + //! @brief Determines if state needs toggling. + //! @return true if state needs toggling, else false. + inline bool needToggle(StateAction sa, bool state) { + if ((state && (sa == STATE_SET)) + || (! state && (sa == STATE_UNSET))) { + return false; + } + return true; + } +} + +// Structs and Classes + +class Action { +public: + Action(void) : _action(ACTION_UNSET) + { + _param_i[0] = _param_i[1] = _param_i[2] = 0; + } + + Action(uint action) : _action(action) + { + _param_i[0] = _param_i[1] = _param_i[2] = 0; + } + + Action(uint action, int param_i[3]) : _action(action) + { + std::memcpy(_param_i, param_i, sizeof(param_i)); + } + Action(uint action, const std::string ¶m_s) + : _action(action), _param_s(param_s) + { + _param_i[0] = _param_i[1] = _param_i[2] = 0; + } + ~Action(void) + { + } + + inline uint getAction(void) const { return _action; } + inline int getParamI(uint n) const { return _param_i[(n < 3) ? n : 0]; } + inline const std::string &getParamS(void) const { return _param_s; } + + inline void setAction(uint action) { _action = action; } + inline void setParamI(uint n, int param) { _param_i[(n < 3) ? n : 0] = param; } + inline void setParamS(const std::string param) { _param_s = param; } + + inline void clear() { _action = ACTION_UNSET; _param_s.clear(); _param_i[0] = _param_i[1] = _param_i[2] = 0; } +private: + uint _action; + + int _param_i[3]; + std::string _param_s; +}; + +class ActionEvent { +public: + ActionEvent(void) + { + } + ~ActionEvent(void) + { + } + + inline bool isOnlyAction(uint action) const { + if ((action_list.size() == 1) && + (action_list.front().getAction() == action)) { + return true; + } + return false; + } + +public: + uint mod, sym; // event matching + uint type, threshold; // more matching, press, release etc + + std::list action_list; +}; + +class ActionPerformed { +public: + ActionPerformed(PWinObj *w, const ActionEvent &a) : wo(w), ae(a), type(0) { } + ~ActionPerformed(void) { } + + PWinObj *wo; + const ActionEvent &ae; + + int type; + union _event { + XButtonEvent *button; + XKeyEvent *key; + XMotionEvent *motion; + XCrossingEvent *crossing; + XExposeEvent *expose; + } event; +}; + +#endif // _ACTION_HH_ diff --git a/pekwm/ActionHandler.cc b/pekwm/ActionHandler.cc new file mode 100644 index 0000000..9a2c799 --- /dev/null +++ b/pekwm/ActionHandler.cc @@ -0,0 +1,1038 @@ +// +// ActionHandler.cc for pekwm +// Copyright © 2002-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "ActionHandler.hh" + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "PMenu.hh" +#include "PScreen.hh" +#include "Frame.hh" +#include "Client.hh" +#include "Config.hh" +#include "CmdDialog.hh" +#include "SearchDialog.hh" +#include "Workspaces.hh" +#include "WindowManager.hh" +#include "Util.hh" +#include "RegexString.hh" +#include "WorkspaceIndicator.hh" +#include "Harbour.hh" +#include "MenuHandler.hh" +#include "PDecor.hh" +#include "PMenu.hh" +#include "WORefMenu.hh" +#include "ActionMenu.hh" +#include "FrameListMenu.hh" +#include "DecorMenu.hh" +#include "HarbourMenu.hh" + +#include + +extern "C" { +#include +} + +#ifdef DEBUG +#include +using std::cerr; +using std::endl; +#endif // DEBUG +using std::auto_ptr; +using std::string; +using std::list; +using std::find; +using std::map; + +ActionHandler *ActionHandler::_instance = 0; + +//! @brief ActionHandler constructor +ActionHandler::ActionHandler(void) +{ + _instance = this; + + // Initialize state_to_keycode map + for (uint i = 0; i < PScreen::MODIFIER_TO_MASK_NUM; ++i) { + uint modifier = PScreen::MODIFIER_TO_MASK[i]; + _state_to_keycode[modifier] = PScreen::instance()->getKeycodeFromMask(modifier); + } +} + +//! @brief ActionHandler destructor +ActionHandler::~ActionHandler(void) +{ + _instance = 0; +} + +//! @brief Executes an ActionPerformed event. +void +ActionHandler::handleAction(const ActionPerformed &ap) +{ + PWinObj *wo = ap.wo; + Client *client = 0; + Frame *frame = 0; + PMenu *menu = 0; + PDecor *decor = 0; + bool matched = false; + + // go through the list of actions and execute them + list::const_iterator it = ap.ae.action_list.begin(); + for (; it != ap.ae.action_list.end(); ++it, matched = false) { + // Determine what type if any of the window object that is focused + // and check if it is still alive. + lookupWindowObjects(&wo, &client, &frame, &menu, &decor); + + // actions valid for all PWinObjs + if (! matched && wo) { + matched = true; + switch (it->getAction()) { + case ACTION_FOCUS: + wo->giveInputFocus(); + break; + case ACTION_UNFOCUS: + PWinObj::getRootPWinObj()->giveInputFocus(); + break; + case ACTION_FOCUS_DIRECTIONAL: + actionFocusDirectional(wo, DirectionType(it->getParamI(0)), it->getParamI(1)); + break; + case ACTION_SEND_KEY: + actionSendKey(wo, it->getParamS()); + break; + default: + matched = false; + break; + }; + } + + // actions valid for Clients and Frames + if (! matched && frame) { + matched = true; + switch (it->getAction()) { + case ACTION_GROUPING_DRAG: + if (ap.type == MotionNotify) { + frame->doGroupingDrag(ap.event.motion, client, it->getParamI(0)); + } + break; + case ACTION_ACTIVATE_CLIENT: + if ((ap.type == ButtonPress) || (ap.type == ButtonRelease)) + frame->activateChild(frame->getChildFromPos(ap.event.button->x)); + break; + case ACTION_GOTO_CLIENT: + gotoClient(client); + break; + case ACTION_MAXFILL: + frame->setStateMaximized(STATE_SET, + it->getParamI(0), it->getParamI(1), true); + break; + case ACTION_GROW_DIRECTION: + frame->growDirection(it->getParamI(0)); + break; + case ACTION_RESIZE: + if (ap.type == MotionNotify && ! it->getParamI(0)) + frame->doResize(ap.event.motion); + else + frame->doResize( BorderPosition(it->getParamI(0)-1) ); + break; + case ACTION_MOVE_RESIZE: + frame->doKeyboardMoveResize(); + break; + case ACTION_CLOSE: + client->close(); + break; + case ACTION_CLOSE_FRAME: + frame->close(); + break; + case ACTION_KILL: + client->kill(); + break; + case ACTION_RAISE: + if (it->getParamI(0)) { + WindowManager::instance()->familyRaiseLower(client, true); + } else { + frame->raise(); + } + break; + case ACTION_LOWER: + if (it->getParamI(0)) { + WindowManager::instance()->familyRaiseLower(client, false); + } else { + frame->lower(); + } + break; + case ACTION_ACTIVATE_OR_RAISE: + if ((ap.type == ButtonPress) || (ap.type == ButtonRelease)) { + if (ap.event.button->window == frame->getTitleWindow()) { + frame->activateChild(frame->getChildFromPos(ap.event.button->x)); + } + } + + if (frame->isFocused()) { + frame->raise(); + } else { + wo->giveInputFocus(); + } + break; + case ACTION_MOVE_TO_EDGE: + frame->moveToEdge(OrientationType(it->getParamI(0))); + break; + case ACTION_ACTIVATE_CLIENT_REL: + frame->activateChildRel(it->getParamI(0)); + break; + case ACTION_MOVE_CLIENT_REL: + frame->moveChildRel(it->getParamI(0)); + break; + case ACTION_ACTIVATE_CLIENT_NUM: + frame->activateChildNum(it->getParamI(0)); + break; + case ACTION_SEND_TO_WORKSPACE: + actionSendToWorkspace(decor, it->getParamI(0)); + break; + case ACTION_DETACH: + frame->detachClient(client); + break; + case ACTION_ATTACH_MARKED: + WindowManager::instance()->attachMarked(frame); + break; + case ACTION_ATTACH_CLIENT_IN_NEXT_FRAME: + WindowManager::instance()->attachInNextPrevFrame(client, false, true); + break; + case ACTION_ATTACH_CLIENT_IN_PREV_FRAME: + WindowManager::instance()->attachInNextPrevFrame(client, false, false); + break; + case ACTION_ATTACH_FRAME_IN_NEXT_FRAME: + WindowManager::instance()->attachInNextPrevFrame(client, true, true); + break; + case ACTION_ATTACH_FRAME_IN_PREV_FRAME: + WindowManager::instance()->attachInNextPrevFrame(client, true, false); + break; +#ifdef OPACITY + case ACTION_SET_OPACITY: + actionSetOpacity(client, frame, it->getParamI(0), it->getParamI(1)); + break; +#endif // OPACITY + default: + matched = false; + break; + } + } + + // Actions valid for Menus + if (! matched && menu) { + matched = true; + switch (it->getAction()) { + // menu navigation + case ACTION_MENU_NEXT: + menu->selectNextItem(); + break; + case ACTION_MENU_PREV: + menu->selectPrevItem(); + break; + case ACTION_MENU_SELECT: + menu->exec(menu->getItemCurr()); + + // special case: execItem can cause an reload to be issued, if that's + // the case it causes the list (ae) to change and therefore + // it can't be used anymore + return; + case ACTION_MENU_ENTER_SUBMENU: + if (menu->getItemCurr() && + menu->getItemCurr()->getWORef() && + (menu->getItemCurr()->getWORef()->getType() == PWinObj::WO_MENU)) { + menu->mapSubmenu(static_cast(menu->getItemCurr()->getWORef()), true); + } + break; + case ACTION_MENU_LEAVE_SUBMENU: + menu->gotoParentMenu(); + break; + case ACTION_CLOSE: + menu->unmapAll(); + WindowManager::instance()->findWOAndFocus(0); + break; + default: + matched = false; + break; + } + } + // actions valid for pdecor + if (! matched && decor) { + int x_root, y_root; + + matched = true; + switch (it->getAction()) { + case ACTION_MOVE: + // Get root position, previously event positions was + // used however this seems to be error prone on + // Xinerama setups + PScreen::instance()->getMousePosition(x_root, y_root); + + decor->doMove(x_root, y_root); + break; + case ACTION_CLOSE: + decor->unmapWindow(); + if (decor->getType() == PWinObj::WO_CMD_DIALOG + || decor->getType() == PWinObj::WO_SEARCH_DIALOG) { + WindowManager::instance()->findWOAndFocus(0); + } + break; + case ACTION_WARP_TO_WORKSPACE: + actionWarpToWorkspace(decor, it->getParamI(0)); + break; + default: + matched = false; + break; + } + } + + // Actions valid from everywhere + if (! matched) { + matched = true; + switch (it->getAction()) { + case ACTION_SET: + case ACTION_UNSET: + case ACTION_TOGGLE: + handleStateAction(*it, wo, client, frame); + break; + case ACTION_NEXT_FRAME: + actionFocusToggle(ap.ae.sym, it->getParamI(0), 1 /* Direction */, + it->getParamI(1), false); + break; + case ACTION_NEXT_FRAME_MRU: + actionFocusToggle(ap.ae.sym, it->getParamI(0), 1 /* Direction */, + it->getParamI(1), true); + break; + case ACTION_PREV_FRAME: + actionFocusToggle(ap.ae.sym, it->getParamI(0), -1 /* Direction */, + it->getParamI(1), false); + break; + case ACTION_PREV_FRAME_MRU: + actionFocusToggle(ap.ae.sym, it->getParamI(0), -1 /* Direction */, + it->getParamI(1), true); + break; + case ACTION_GOTO_WORKSPACE: + // Events caused by a motion event ( dragging frame to + // the edge ) or enter event ( moving the pointer to + // the edge ) should warp the pointer. + actionGotoWorkspace(it->getParamI(0), (ap.type == MotionNotify) || (ap.type == EnterNotify)); + break; + case ACTION_FIND_CLIENT: + actionFindClient(Util::to_wide_str(it->getParamS())); + break; + case ACTION_GOTO_CLIENT_ID: + actionGotoClientID(it->getParamI(0)); + break; + case ACTION_EXEC: + actionExec(client, it->getParamS()); + break; + case ACTION_SHOW_MENU: + actionShowMenu(it->getParamS(), it->getParamI(0), + ap.type, client ? client : wo); + break; + case ACTION_HIDE_ALL_MENUS: + MenuHandler::instance()->hideAllMenus(); + break; + case ACTION_RELOAD: + WindowManager::instance()->reload(); + break; + case ACTION_RESTART: + WindowManager::instance()->restart(); + break; + case ACTION_RESTART_OTHER: + if (it->getParamS().size()) + WindowManager::instance()->restart(it->getParamS()); + break; + case ACTION_EXIT: + WindowManager::instance()->shutdown(); + break; + case ACTION_SHOW_CMD_DIALOG: + if (WindowManager::instance()->getCmdDialog()->isMapped()) { + WindowManager::instance()->getCmdDialog()->unmapWindow(); + } else { + WindowManager::instance()->getCmdDialog()->mapCentered(it->getParamS(), true, + frame ? frame : wo); + } + break; + case ACTION_SHOW_SEARCH_DIALOG: + if (WindowManager::instance()->getSearchDialog()->isMapped()) { + WindowManager::instance()->getSearchDialog()->unmapWindow(); + } else { + WindowManager::instance()->getSearchDialog()->mapCentered(it->getParamS(), true, frame ? frame : wo); + } + break; + case ACTION_HIDE_WORKSPACE_INDICATOR: + WindowManager::instance()->getWorkspaceIndicator()->unmapWindow(); + break; + default: + matched = false; + break; + } + } + } +} + +void +ActionHandler::lookupWindowObjects(PWinObj **wo, Client **client, Frame **frame, + PMenu **menu, PDecor **decor) +{ + *client = 0; + *frame = 0; + *menu = 0; + *decor = 0; + + if (PWinObj::windowObjectExists(*wo)) { + if ((*wo)->getType() == PWinObj::WO_CLIENT) { + *client = static_cast(*wo); + *frame = static_cast((*client)->getParent()); + *decor = static_cast(*frame); + } else if ((*wo)->getType() == PWinObj::WO_FRAME) { + *frame = static_cast(*wo); + *client = static_cast((*frame)->getActiveChild()); + *decor = static_cast(*wo); + } else if ((*wo)->getType() == PWinObj::WO_MENU) { + *menu = static_cast(*wo); + *decor = static_cast(*wo); + } else { + *decor = dynamic_cast(*wo); + } + } else { + *wo = 0; + } +} + +//! @brief Handles state actions +void +ActionHandler::handleStateAction(const Action &action, PWinObj *wo, + Client *client, Frame *frame) +{ + StateAction sa = static_cast(action.getAction()); // convenience + + bool matched = false; + + // check for frame actions + if (! matched && frame) { + matched = true; + switch (action.getParamI(0)) { + case ACTION_STATE_MAXIMIZED: + frame->setStateMaximized(sa, action.getParamI(1), + action.getParamI(2), false); + break; + case ACTION_STATE_FULLSCREEN: + frame->setStateFullscreen(sa); + break; + case ACTION_STATE_SHADED: + frame->setShaded(sa); + break; + case ACTION_STATE_STICKY: + frame->setStateSticky(sa); + break; + case ACTION_STATE_ALWAYS_ONTOP: + frame->setStateAlwaysOnTop(sa); + break; + case ACTION_STATE_ALWAYS_BELOW: + frame->setStateAlwaysBelow(sa); + break; + case ACTION_STATE_DECOR_BORDER: + frame->setStateDecorBorder(sa); + break; + case ACTION_STATE_DECOR_TITLEBAR: + frame->setStateDecorTitlebar(sa); + break; + case ACTION_STATE_ICONIFIED: + frame->setStateIconified(sa); + break; + case ACTION_STATE_TAGGED: + frame->setStateTagged(sa, action.getParamI(1)); + break; + case ACTION_STATE_MARKED: + frame->setStateMarked(sa, client); + break; + case ACTION_STATE_SKIP: + frame->setStateSkip(sa, action.getParamI(1)); + break; + case ACTION_STATE_CFG_DENY: + client->setStateCfgDeny(sa, action.getParamI(1)); + break; + case ACTION_STATE_DECOR: + frame->setDecorOverride(sa, action.getParamS()); + break; + case ACTION_STATE_TITLE: + frame->setStateTitle(sa, client, Util::to_wide_str(action.getParamS())); + break; +#ifdef OPACITY + case ACTION_STATE_OPAQUE: + frame->setStateOpaque(sa); + break; +#endif // OPACITY + default: + matched = false; + break; + } + } + + // check for menu actions + if (! matched && wo && + (wo->getType() == PWinObj::WO_MENU)) { + matched = true; + switch (action.getParamI(0)) { + case ACTION_STATE_STICKY: + wo->stick(); + break; + default: + matched = false; + break; + } + } + + if (! matched) { + matched = true; + switch (action.getParamI(0)) { + case ACTION_STATE_HARBOUR_HIDDEN: + WindowManager::instance()->getHarbour()->setStateHidden(sa); + break; + case ACTION_STATE_GLOBAL_GROUPING: + WindowManager::instance()->setStateGlobalGrouping(sa); + break; + default: + matched = false; + break; + } + } +} + +//! @brief Checks if motion threshold is within bounds. +bool +ActionHandler::checkAEThreshold(int x, int y, int x_t, int y_t, uint t) +{ + if (((x > x_t) ? (x > (x_t + signed(t))) : (x < (x_t - signed(t)))) || + ((y > y_t) ? (y > (y_t + signed(t))) : (y < (y_t - signed(t))))) { + return true; + } + return false; +} + +//! @brief Searches the actions list for an matching event +ActionEvent* +ActionHandler::findMouseAction(uint button, uint state, MouseEventType type, std::list *actions) +{ + if (! actions) { + return 0; + } + + PScreen::stripStateModifiers(&state); + PScreen::stripButtonModifiers(&state); + + list::iterator it(actions->begin()); + for (; it != actions->end(); ++it) { + if ((it->type == unsigned(type)) + && ((it->mod == MOD_ANY) || (it->mod == state)) + && ((it->sym == BUTTON_ANY) || (it->sym == button))) { + return &*it; + } + } + + return 0; +} + +/** + * Execute action, setting client environment before (if any). + */ +void +ActionHandler::actionExec(Client *client, const std::string &command) +{ + if (command.size()) { + Client::setClientEnvironment(client); + Util::forkExec(command); + } +} + +//! @brief Searches for a client matching titles and makes it visible +void +ActionHandler::actionFindClient(const std::wstring &title) +{ + Client *client = findClientFromTitle(title); + if (client) { + gotoClient(client); + } +} + +//! @brief Goto workspace +//! @param workspace Workspace to got to +//! @param warp If true, warp pointer as well +void +ActionHandler::actionGotoWorkspace(uint workspace, bool warp) +{ + Workspaces::instance()->gotoWorkspace(workspace, warp); +} + +//! @brief Focus client with id. +//! @param id Client id. +void +ActionHandler::actionGotoClientID(uint id) +{ + Client *client = Client::findClientFromID(id); + if (client) { + gotoClient(client); + } +} + +//! @brief Sends client to specified workspace +//! @param direction Workspace to send to, or special meaning relative space. +void +ActionHandler::actionSendToWorkspace(PDecor *decor, int direction) +{ + // Convenience + Workspaces *ws = Workspaces::instance(); + uint per_row = Config::instance()->getWorkspacesPerRow(); + uint cur_row = ws->getRow(), row_min = ws->getRowMin(), row_max = ws->getRowMax(); + + switch (direction) { + case WORKSPACE_LEFT: + case WORKSPACE_PREV: + if (ws->getActive() > row_min) { + decor->setWorkspace(ws->getActive() - 1); + } else if (static_cast(direction) == WORKSPACE_PREV) { + decor->setWorkspace(row_max); + } + break; + case WORKSPACE_NEXT: + case WORKSPACE_RIGHT: + if (ws->getActive() < row_max) { + decor->setWorkspace(ws->getActive() + 1); + } else if (static_cast(direction) == WORKSPACE_NEXT) { + decor->setWorkspace(row_min); + } + break; + case WORKSPACE_PREV_V: + case WORKSPACE_UP: + if (ws->getActive() >= per_row) { + decor->setWorkspace(ws->getActive() - per_row); + } else if (static_cast(direction) == WORKSPACE_PREV_V) { + // Bottom left + column + decor->setWorkspace(ws->size() - per_row + + ws->getActive() - cur_row * per_row); + } + break; + case WORKSPACE_NEXT_V: + case WORKSPACE_DOWN: + if ((ws->getActive() + per_row) < ws->size()) { + decor->setWorkspace(ws->getActive() + per_row); + } else if (static_cast(direction) == WORKSPACE_NEXT_V) { + decor->setWorkspace(ws->getActive() - cur_row * per_row); + } + break; + case WORKSPACE_LAST: + decor->setWorkspace(ws->getPrevious()); + break; + default: + decor->setWorkspace(direction); + break; + } +} + +//! @brief +void +ActionHandler::actionWarpToWorkspace(PDecor *decor, uint direction) +{ + // Removing the already accumulated motion events can help + // to avoid skipping workspaces (see task #77). + PScreen::removeMotionEvents(); + + // actually did move + if (Workspaces::instance()->gotoWorkspace(DirectionType(direction), true)) { + int x, y; + PScreen::instance()->getMousePosition(x, y); + + decor->move(decor->getClickX() + x - decor->getPointerX(), + decor->getClickY() + y - decor->getPointerY()); + decor->setWorkspace(Workspaces::instance()->getActive()); + } +} + +//! @brief Tries to find the next/prev frame relative to the focused client +void +ActionHandler::actionFocusToggle(uint button, uint raise, int off, + bool show_iconified, bool mru) +{ + PMenu *p_menu; + + if (mru) { + p_menu = createMRUMenu(show_iconified); + } else { + p_menu = createNextPrevMenu(show_iconified); + } + + auto_ptr menu(p_menu); + + // no clients in the list + if (menu->size() == 0) { + return; + } + + // unable to grab keyboard + if (! PScreen::instance()->grabKeyboard(PScreen::instance()->getRoot())) { + return; + } + + // find the focused window object + PWinObj *fo_wo = 0; + if (PWinObj::isFocusedPWinObj(PWinObj::WO_CLIENT)) { + fo_wo = PWinObj::getFocusedPWinObj()->getParent(); + + list::iterator it(menu->m_begin()); + for (; it != menu->m_end(); ++it) { + if ((*it)->getWORef() == fo_wo) { + menu->selectItem(it); + break; + } + } + fo_wo->setFocused(false); + } + + if (Config::instance()->getShowFrameList()) { + menu->buildMenu(); + + Geometry head; + PScreen::instance()->getHeadInfo(PScreen::instance()->getCurrHead(), head); + menu->move(head.x + ((head.width - menu->getWidth()) / 2), + head.y + ((head.height - menu->getHeight()) / 2)); + menu->setFocused(true); + menu->mapWindowRaised(); + } + + menu->selectItemRel(off); + fo_wo = menu->getItemCurr()->getWORef(); + + XEvent ev; + bool cycling = true; + bool was_iconified = false; + + while (cycling) { + if (fo_wo) { + fo_wo->setFocused(true); + if (Raise(raise) == ALWAYS_RAISE) { + // Make sure it's not iconified if raise is on. + if (fo_wo->isIconified()) { + was_iconified = true; + fo_wo->mapWindow(); + } + fo_wo->raise(); + } + } + + XMaskEvent(PScreen::instance()->getDpy(), KeyPressMask|KeyReleaseMask, &ev); + if (ev.type == KeyPress) { + if (ev.xkey.keycode == button) { + if (fo_wo) { + // Restore iconified state + if (was_iconified) { + was_iconified = false; + fo_wo->iconify(); + } + fo_wo->setFocused(false); + } + + menu->selectItemRel(off); + fo_wo = menu->getItemCurr()->getWORef(); + } else { + XPutBackEvent (PScreen::instance()->getDpy(), &ev); + cycling = false; + } + } else if (ev.type == KeyRelease) { + if (IsModifierKey(XKeycodeToKeysym(PScreen::instance()->getDpy(), + ev.xkey.keycode, 0))) { + cycling = false; + } + } else { + XPutBackEvent(PScreen::instance()->getDpy(), &ev); + } + } + + PScreen::instance()->ungrabKeyboard(); + + // Got something to focus + if (fo_wo) { + // De-iconify if iconified, user probably wants this + if (fo_wo->isIconified()) { + // If the window was iconfied, and sticky + fo_wo->setWorkspace(Workspaces::instance()->getActive()); + fo_wo->mapWindow(); + fo_wo->raise(); + } else if (Raise(raise) == END_RAISE) { + fo_wo->raise(); + } + + // Give focus + fo_wo->giveInputFocus(); + } +} + + +//! @brief Finds a window in direction and gives it focus +void +ActionHandler::actionFocusDirectional(PWinObj *wo, DirectionType dir, bool raise) +{ + PWinObj *wo_focus = + Workspaces::instance()->findDirectional(wo, dir, SKIP_FOCUS_TOGGLE); + + if (wo_focus) { + if (raise) { + wo_focus->raise(); + } + wo_focus->giveInputFocus(); + } +} + +/** + * Parse key information and send to wo. + * + * To handle this smoothly first all state modifiers are pressed one + * by one adding to the state, then the real key is pressed and + * released, then the state modifiers are released. + * + * @param win Window to send key to. + * @param key_str String definition of key to send. + * @return true if key was parsed ok, no validation of sending is done. + */ +bool +ActionHandler::actionSendKey(PWinObj *wo, const std::string &key_str) +{ + uint mod, key; + if (! Config::instance()->parseKey(key_str, mod, key)) { + return false; + } + + XEvent ev; + initSendKeyEvent(ev, wo); + + // Press state modifiers + map::iterator it; + for (it = _state_to_keycode.begin(); it != _state_to_keycode.end(); ++it) { + if (mod & it->first) { + ev.xkey.keycode = it->second; + XSendEvent(PScreen::instance()->getDpy(), wo->getWindow(), True, KeyPressMask, &ev); + ev.xkey.state |= it->first; + } + } + + // Send press and release of main key + ev.xkey.keycode = key; + XSendEvent(PScreen::instance()->getDpy(), wo->getWindow(), True, KeyPressMask, &ev); + ev.type = KeyRelease; + XSendEvent(PScreen::instance()->getDpy(), wo->getWindow(), True, KeyPressMask, &ev); + + // Release state modifiers + for (it = _state_to_keycode.begin(); it != _state_to_keycode.end(); ++it) { + if (mod & it->first) { + ev.xkey.keycode = it->second; + XSendEvent(PScreen::instance()->getDpy(), wo->getWindow(), True, KeyPressMask, &ev); + ev.xkey.state &= ~it->first; + } + } + + return true; +} + +#ifdef OPACITY +void +ActionHandler::actionSetOpacity(PWinObj *client, PWinObj *frame, uint focus, uint unfocus) +{ + if (! unfocus) { + unfocus = focus; + } + CONV_OPACITY(focus); + CONV_OPACITY(unfocus); + client->setOpacity(focus, unfocus); + frame->setOpacity(client); +} +#endif // OPACITY + +//! @brief Toggles visibility of menu. +//! @param name Name of menu to toggle visibilty of +//! @param stick Stick menu when showing +//! @param e_type Event type triggered this action +//! @param wo_ref Reference to active window object +void +ActionHandler::actionShowMenu(const std::string &name, bool stick, + uint e_type, PWinObj *wo_ref) +{ + PMenu *menu = MenuHandler::instance()->getMenu(name); + if (! menu) { + return; + } + + if (menu->isMapped()) { + menu->unmapAll(); + + } else { + // if it's a WORefMenu, set referencing client + WORefMenu *wo_ref_menu = dynamic_cast(menu); + if (wo_ref_menu + // Don't set reference on these, we don't want a funky title + && (wo_ref_menu->getMenuType() != ROOTMENU_TYPE) + && (wo_ref_menu->getMenuType() != ROOTMENU_STANDALONE_TYPE)) { + wo_ref_menu->setWORef(wo_ref); + } + + // mapping can fail because of empty menu, like iconmenu, so we check + menu->mapUnderMouse(); + if (menu->isMapped()) { + menu->giveInputFocus(); + if (stick) { + menu->setSticky(true); + } + + // if we opened the menu with the keyboard, select item 0 + if (e_type == KeyPress) { + menu->selectItemNum(0); + } + } + } +} + +//! @brief Creates a menu containing a list of Frames currently visible +//! @param show_iconified Flag to show/hide iconified windows +PMenu* +ActionHandler::createNextPrevMenu(bool show_iconified) +{ + ActionEvent ae; // empty ae, used when inserting + PMenu *menu = + new PMenu(WindowManager::instance()->getTheme(), L"Windows", + "" /* Empty name*/); + + Frame *fr; + list::iterator f_it(Frame::frame_begin()); + for (; f_it != Frame::frame_end(); ++f_it) { + fr = static_cast(*f_it); + if (createMenuInclude(fr, show_iconified)) { + menu->insert(static_cast(fr->getActiveChild())->getTitle()->getVisible(), + ae, fr, static_cast(fr->getActiveChild())->getIcon()); + } + } + + return menu; +} + +//! @brief Creates a menu containing a list of Frames currently visible ( MRU order ) +//! @param show_iconified Flag to show/hide iconified windows +PMenu* +ActionHandler::createMRUMenu(bool show_iconified) +{ + ActionEvent ae; // empty ae, used when inserting + PMenu *menu = new PMenu(WindowManager::instance()->getTheme(), + L"MRU Windows", "" /* Empty name */); + + Frame *fr; + list::reverse_iterator f_it = WindowManager::instance()->mru_rbegin(); + for (; f_it != WindowManager::instance()->mru_rend(); ++f_it) { + fr = static_cast(*f_it); + if (createMenuInclude(fr, show_iconified)) { + menu->insert(static_cast(fr->getActiveChild())->getTitle()->getVisible(), + ae, fr, static_cast(fr->getActiveChild())->getIcon()); + } + } + + return menu; +} + +//! @brief Helper to decide wheter or not to include Frame in menu +//! @param frame Frame to check +//! @param show_iconified Wheter or not to include iconified windows +//! @return true if it should be included, else false +bool +ActionHandler::createMenuInclude(Frame *frame, bool show_iconified) +{ + // Make sure the frame is mapped, or on the correct workspace if + // it's iconified. Also make sure it's possible to give it focus + // and should not be skipped. The condition is rather complex, so + // we split it up for readability. + + // focw == frame on current workspace + bool focw = frame->isSticky() || frame->getWorkspace() == Workspaces::instance()->getActive(); + + // ibs == iconified but should be shown + bool ibs = show_iconified && frame->isIconified() && focw; + + // mos == mapped or shown nonetheless + bool mos = frame->isMapped() || ibs; + + return ! frame->isSkip(SKIP_FOCUS_TOGGLE) && frame->isFocusable() && mos; +} + +//! @brief Searches the client list for a client with a title matching title +Client* +ActionHandler::findClientFromTitle(const std::wstring &or_title) +{ + RegexString o_rs; + + if (o_rs.parse_match(or_title, true)) { + list::iterator it (Client::client_begin()); + for (; it != Client::client_end (); ++it) { + if (o_rs == (*it)->getTitle()->getVisible()) { + return (*it); + } + } + } + + return 0; +} + +//! @brief Makes sure Client gets visible and focus it. +//! @param client Client to activate. +void +ActionHandler::gotoClient(Client *client) +{ + Frame *frame = dynamic_cast(client->getParent()); + if (! frame) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "ActionHandler(" << this << ")::gotoClient(" << client << ")" + << endl << " *** parent is not a Frame!" << endl; +#endif // DEBUG + return; + } + + // Make sure it's visible + if (! frame->isSticky() + && (frame->getWorkspace() != Workspaces::instance()->getActive())) { + Workspaces::instance()->setWorkspace(frame->getWorkspace(), false); + } + + if (! frame->isMapped()) { + frame->mapWindow(); + } + + // Activate it within the Frame. + frame->activateChild(client); + frame->raise(); + frame->giveInputFocus(); +} + +/** + * Initilalize XEvent for sending KeyPress/KeyRelease events. + * + * @param ev Reference to event. + * @param wo PWinObj key event is aimed for. + */ +void +ActionHandler::initSendKeyEvent(XEvent &ev, PWinObj *wo) +{ + ev.type = KeyPress; + ev.xkey.display = PScreen::instance()->getDpy(); + ev.xkey.root = PScreen::instance()->getRoot(); + ev.xkey.window = wo->getWindow(); + ev.xkey.time = CurrentTime; + ev.xkey.x = 1; + ev.xkey.y = 1; + PScreen::instance()->getMousePosition(ev.xkey.x_root, ev.xkey.y_root); + ev.xkey.same_screen = True; + ev.xkey.type = KeyPress; + ev.xkey.state = 0; +} diff --git a/pekwm/ActionHandler.hh b/pekwm/ActionHandler.hh new file mode 100644 index 0000000..19f4f7f --- /dev/null +++ b/pekwm/ActionHandler.hh @@ -0,0 +1,78 @@ +// +// ActionHandler.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _ACTIONHANDLER_HH_ +#define _ACTIONHANDLER_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" +#include "Action.hh" + +#include +#include +#include + +class Client; +class Frame; +class PWinObj; +class PDecor; +class PMenu; + +class ActionHandler +{ +public: + ActionHandler(void); + ~ActionHandler(void); + + static inline ActionHandler *instance(void) { return _instance; } + + void handleAction(const ActionPerformed &ap); + + static bool checkAEThreshold(int x, int y, int x_t, int y_t, uint t); + static ActionEvent *findMouseAction(uint button, uint mod, MouseEventType type, + std::list *actions); + +private: + void lookupWindowObjects(PWinObj **wo, Client **client, Frame **frame, + PMenu **menu, PDecor **decor); + void handleStateAction(const Action &action, PWinObj *wo, Client *client, Frame *frame); + + void actionExec(Client *client, const std::string &command); + void actionFindClient(const std::wstring &title); + void actionGotoClientID(uint id); + void actionGotoWorkspace(uint workspace, bool warp); + void actionSendToWorkspace(PDecor *decor, int direction); + void actionWarpToWorkspace(PDecor *decor, uint direction); + void actionFocusToggle(uint button, uint raise, int off, bool show_iconified, bool mru); + void actionFocusDirectional(PWinObj *wo, DirectionType dir, bool raise); + bool actionSendKey(PWinObj *wo, const std::string &key_str); +#ifdef OPACITY + static void actionSetOpacity(PWinObj *client, PWinObj *frame, uint focus, uint unfocus); +#endif // OPACITY + void actionShowMenu(const std::string &name, bool stick, uint e_type, PWinObj *wo_ref); + + // action helpers + Client *findClientFromTitle(const std::wstring &title); + void gotoClient(Client *client); + + PMenu *createNextPrevMenu(bool show_iconified); + PMenu *createMRUMenu(bool show_iconified); + bool createMenuInclude(Frame *frame, bool show_iconified); + + void initSendKeyEvent(XEvent &ev, PWinObj *wo); + +private: + std::map _state_to_keycode; /**< Map translating state modifiers to keycode. */ + + static ActionHandler *_instance; /**< Instance pointer for ActionHandler. */ +}; + +#endif // _ACTIONHANDLER_HH_ diff --git a/pekwm/ActionMenu.cc b/pekwm/ActionMenu.cc new file mode 100644 index 0000000..bd03f5a --- /dev/null +++ b/pekwm/ActionMenu.cc @@ -0,0 +1,370 @@ +// +// ActionMenu.cc for pekwm +// Copyright © 2002-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "PMenu.hh" +#include "WORefMenu.hh" +#include "ActionMenu.hh" +#include "TextureHandler.hh" +#include "ImageHandler.hh" + +#include "Config.hh" +#include "ActionHandler.hh" +#include "Client.hh" +#include "Frame.hh" +#include "WindowManager.hh" +#include "Util.hh" + +using std::cerr; +using std::endl; +using std::find; +using std::list; +using std::string; +using std::wstring; + +//! @brief ActionMenu constructor +//! @param type Type of menu +//! @param title Title of menu +//! @param name Name of the menu, empty for dynamic else should be unique +//! @param decor_name Name of decor to use, defaults to MENU. +ActionMenu::ActionMenu(MenuType type, + const std::wstring &title, const std::string &name, + const std::string &decor_name) : + WORefMenu(WindowManager::instance()->getScreen(), + WindowManager::instance()->getTheme(), title, name, decor_name), + _act(WindowManager::instance()->getActionHandler()), + _has_dynamic(false) +{ + // when creating dynamic submenus, this needs to be initialized as + // dynamic inserting will be done + _insert_at = _item_list.begin(); + _menu_type = type; + + if (_menu_type == WINDOWMENU_TYPE) { + _action_ok = WINDOWMENU_OK; + } else if ((_menu_type == ROOTMENU_TYPE) || (_menu_type == ROOTMENU_STANDALONE_TYPE)) { + _action_ok = ROOTMENU_OK; + } +} + +//! @brief ActionMenu destructor +ActionMenu::~ActionMenu(void) +{ + removeAll(); +} + +// START - PWinObj interface. + +//! @brief Rebuilds the vmenu and if it has any items after it shows it. +void +ActionMenu::mapWindow(void) +{ + // find and rebuild the dynamic entries + if (! isMapped() && _has_dynamic) { + uint size_before = _item_list.size(); + rebuildDynamic(); + if (size_before != _item_list.size()) { + buildMenu(); + } + } + + PMenu::mapWindow(); +} + +//! @brief Hides and removes all items created by dynamic entries. +void +ActionMenu::unmapWindow(void) +{ + if (! isMapped()) { + return; + } + + if (_has_dynamic) { + removeDynamic(); + } + + PMenu::unmapWindow(); +} + +// END - PWinObj interface. + +//! @brief Handle item exec. +void +ActionMenu::handleItemExec(PMenu::Item *item) +{ + if (! item) { + return; + } + + ActionPerformed ap(getWORef(), item->getAE()); + _act->handleAction(ap); +} + + +/** + * Re-reads the configuration clearing old entries from the menu. This + * adds the ICON_PATH to the ImageHandler before loading to support + * loading icons properly. + * + * @param section CfgParser::Entry with menu configuration + */ +void +ActionMenu::reload(CfgParser::Entry *section) +{ + // Clear menu + removeAll(); + + // Parse section (if any) + ImageHandler::instance()->path_push_back(Config::instance()->getSystemIconPath()); + ImageHandler::instance()->path_push_back(Config::instance()->getIconPath()); + _insert_at = _item_list.begin(); + parse(section); + ImageHandler::instance()->path_pop_back(); + ImageHandler::instance()->path_pop_back(); + + // Build menu from parsed content + buildMenu(); +} + +//! @brief Checks if we have a position set for where to insert. +void +ActionMenu::insert(PMenu::Item *item) +{ + if (! item) { + return; + } + + checkItemWORef(item); + + _insert_at = _item_list.insert(++_insert_at, item); +} + +//! @brief Non-shadowing PMenu::insert +void +ActionMenu::insert(const std::wstring &name, PWinObj *wo_ref, PTexture *icon) +{ + PMenu::insert(name, wo_ref, icon); +} + +//! @brief Non-shadowing PMenu::insert +void +ActionMenu::insert(const std::wstring &name, const ActionEvent &ae, PWinObj *wo_ref, PTexture *icon) +{ + PMenu::insert(name, ae, wo_ref); +} + +//! @brief Removes a BaseMenuItem from the menu +void +ActionMenu::remove(PMenu::Item *item) +{ + if (! item) { + return; + } + + if (item->getIcon()) { + TextureHandler::instance()->returnTexture(item->getIcon()); + } + + if (item->getWORef() && (item->getWORef()->getType() == WO_MENU)) { + delete item->getWORef(); + } + + PMenu::remove(item); +} + +//! @brief Removes all items from the menu +void +ActionMenu::removeAll(void) +{ + while (_item_list.size() > 0) { + remove(_item_list.back()); + } +} + +//! @brief Parse config and push items into menu +//! @param cs Section object to read config from +//! @param menu BaseMenu object to push object in +//! @param has_dynamic If true the menu being parsed is dynamic, defaults to false. +void +ActionMenu::parse(CfgParser::Entry *section, PMenu::Item *parent) +{ + if (! section) { + return; + } + + if (section->get_value().size()) { + wstring title(Util::to_wide_str(section->get_value())); + setTitle(title); + _title_base = title; + } + + CfgParser::Entry *value; + ActionEvent ae; + + ActionMenu *submenu = 0; + PMenu::Item *item = 0; + PTexture *icon = 0; + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + item = 0; + + if (*(*it) == "SUBMENU") { + CfgParser::Entry *sub_section = (*it)->get_section(); + if (sub_section) { + icon = getIcon(sub_section->find_entry("ICON")); + + submenu = new ActionMenu(_menu_type, Util::to_wide_str((*it)->get_value()), (*it)->get_value()); + submenu->_menu_parent = this; + submenu->parse(sub_section, parent); + submenu->buildMenu(); + + item = new PMenu::Item(Util::to_wide_str(sub_section->get_value()), submenu, icon); + item->setCreator(parent); + } else { + cerr << " *** WARNING: submenu entry does not contain any section." << endl; + } + } else if (*(*it) == "SEPARATOR") { + // No icon support on separators. + item = new PMenu::Item(L"", 0, 0); + item->setType(PMenu::Item::MENU_ITEM_SEPARATOR); + item->setCreator(parent); + + } else { + CfgParser::Entry *sub_section = (*it)->get_section(); + if (sub_section) { + // Inside of the Entry = "foo" { ... } section, here + // Actions and Icon are the valid options. + value = sub_section->find_entry("ACTIONS"); + if (value && Config::instance()->parseActions(value->get_value(), ae, _action_ok)) { + icon = getIcon(sub_section->find_entry("ICON")); + + item = new PMenu::Item(Util::to_wide_str(sub_section->get_value()), 0, icon); + item->setCreator(parent); + item->setAE(ae); + + if (ae.isOnlyAction(ACTION_MENU_DYN)) { + _has_dynamic = true; + item->setType(PMenu::Item::MENU_ITEM_HIDDEN); + } + } + } + } + + // If an item was successfully created, insert it to the menu. + if (item) { + ActionMenu::insert (item); + } + } +} + +/** + * Get icon texture from parser value. + * + * @param value Entry to get icon name from. + * @return PTexture if icon was loaded, else 0. + */ +PTexture* +ActionMenu::getIcon(CfgParser::Entry *value) +{ + // Skip blank icons + if (! value || ! value->get_value().size()) { + return 0; + } + + PTexture *icon = 0; + + // Try to load the icon as a complete texture specification, if + // that fails load is an scaled image. + icon = TextureHandler::instance()->getTexture(value->get_value()); + if (! icon) { + icon = TextureHandler::instance()->getTexture("IMAGE " + value->get_value() + "#SCALED"); + } + + return icon; +} + +/** + * Executes all Dynamic entries in the menu. + */ +void +ActionMenu::rebuildDynamic(void) +{ + PWinObj *wo_ref = getWORef(); + + // Export environment before to dynamic script. + Client *client = 0; + if (wo_ref && wo_ref->getType() == WO_CLIENT) { + client = static_cast(wo_ref); + } + Client::setClientEnvironment(client); + + // Setup icon path before parsing. + ImageHandler::instance()->path_push_back(Config::instance()->getSystemIconPath()); + ImageHandler::instance()->path_push_back(Config::instance()->getIconPath()); + + PMenu::Item* item = 0; + list::iterator it; + for (it = _item_list.begin(); it != _item_list.end(); ++it) { + if ((*it)->getAE().isOnlyAction(ACTION_MENU_DYN)) { + _insert_at = it; + + item = *it; + + CfgParser dynamic; + if (dynamic.parse((*it)->getAE().action_list.front().getParamS(), + CfgParserSource::SOURCE_COMMAND)) { + _has_dynamic = true; + parse(dynamic.get_entry_root()->find_section("DYNAMIC"), *it); + } + + it = find(_item_list.begin(), _item_list.end(), item); + _insert_at = _item_list.end(); + } + } + + // Cleanup icon path + ImageHandler::instance()->path_pop_back(); + ImageHandler::instance()->path_pop_back(); +} + +//! @brief Remove all entries from the menu created by dynamic entries. +void +ActionMenu::removeDynamic(void) +{ + std::set dynlist; + + list::iterator it(_item_list.begin()); + for (; it != _item_list.end(); ++it) { + if ((*it)->getType() == PMenu::Item::MENU_ITEM_HIDDEN) { + dynlist.insert(*it); + } + } + + it = _item_list.begin(); + for (; it != _item_list.end();) { + if (dynlist.find((*it)->getCreator()) != dynlist.end()) { + if ((*it)->getWORef() && ((*it)->getWORef()->getType() == WO_MENU)) { + delete (*it)->getWORef(); + } + delete (*it); + it = _item_list.erase(it); + } else { + ++it; + } + } +} diff --git a/pekwm/ActionMenu.hh b/pekwm/ActionMenu.hh new file mode 100644 index 0000000..6c6d961 --- /dev/null +++ b/pekwm/ActionMenu.hh @@ -0,0 +1,70 @@ +// +// ActionMenu.hh for pekwm +// Copyright © 2002-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _ACTIONMENU_HH_ +#define _ACTIONMENU_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" +#include "Action.hh" // For ActionOk +#include "CfgParser.hh" +#include "PMenu.hh" + +#include +#include + +class WORefMenu; +class PScreen; +class Theme; +class ActionHandler; + +class ActionMenu : public WORefMenu +{ +public: + ActionMenu(MenuType type, + const std::wstring &title, const std::string &name, + const std::string &decor_name = "MENU"); + virtual ~ActionMenu(void); + + // START - PWinObj interface. + virtual void mapWindow(void); + virtual void unmapWindow(void); + // END - PWinObj interface. + + virtual void handleItemExec(PMenu::Item *item); + + virtual void insert(PMenu::Item *item); + virtual void insert(const std::wstring &name, PWinObj *wo_ref = 0, PTexture *icon = 0); + virtual void insert(const std::wstring &name, const ActionEvent &ae, + PWinObj *wo_ref = 0, PTexture *icon = 0); + + virtual void reload(CfgParser::Entry *section); + + virtual void remove(PMenu::Item *item); + virtual void removeAll(void); + +private: + void parse(CfgParser::Entry *section, PMenu::Item *parent=0); + + PTexture *getIcon(CfgParser::Entry *value); + void rebuildDynamic(void); + void removeDynamic(void); + +private: + ActionHandler *_act; + + ActionOk _action_ok; + std::list::iterator _insert_at; + + bool _has_dynamic; /**< Set to true if any of the entries in the menu is dynamic. */ +}; + +#endif // _ACTIONMENU_HH_ diff --git a/pekwm/Atoms.cc b/pekwm/Atoms.cc new file mode 100644 index 0000000..310dc15 --- /dev/null +++ b/pekwm/Atoms.cc @@ -0,0 +1,373 @@ +// +// Atoms.cc for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Atoms.hh" +#include "PScreen.hh" +#include "Util.hh" + +extern "C" { +#include +} + +using std::string; +using std::map; + +static const char *atomnames[] = { + // EWMH atoms + "_NET_SUPPORTED", + "_NET_CLIENT_LIST", "_NET_CLIENT_LIST_STACKING", + "_NET_NUMBER_OF_DESKTOPS", + "_NET_DESKTOP_GEOMETRY", "_NET_DESKTOP_VIEWPORT", + "_NET_CURRENT_DESKTOP", "_NET_DESKTOP_NAMES", + "_NET_ACTIVE_WINDOW", "_NET_WORKAREA", + "_NET_DESKTOP_LAYOUT", "_NET_SUPPORTING_WM_CHECK", + "_NET_CLOSE_WINDOW", + "_NET_WM_NAME", "_NET_WM_VISIBLE_NAME", + "_NET_WM_ICON_NAME", "_NET_WM_VISIBLE_ICON_NAME", + "_NET_WM_ICON", "_NET_WM_DESKTOP", + "_NET_WM_STRUT", "_NET_WM_PID", + "_NET_WM_WINDOW_OPACITY", + + "_NET_WM_WINDOW_TYPE", + "_NET_WM_WINDOW_TYPE_DESKTOP", "_NET_WM_WINDOW_TYPE_DOCK", + "_NET_WM_WINDOW_TYPE_TOOLBAR", "_NET_WM_WINDOW_TYPE_MENU", + "_NET_WM_WINDOW_TYPE_UTILITY", "_NET_WM_WINDOW_TYPE_SPLASH", + "_NET_WM_WINDOW_TYPE_DIALOG", "_NET_WM_WINDOW_TYPE_NORMAL", + + "_NET_WM_STATE", + "_NET_WM_STATE_MODAL", "_NET_WM_STATE_STICKY", + "_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ", + "_NET_WM_STATE_SHADED", + "_NET_WM_STATE_SKIP_TASKBAR", "_NET_WM_STATE_SKIP_PAGER", + "_NET_WM_STATE_HIDDEN", "_NET_WM_STATE_FULLSCREEN", + "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_BELOW", + "_NET_WM_STATE_DEMANDS_ATTENTION", + + "_NET_WM_ALLOWED_ACTIONS", + "_NET_WM_ACTION_MOVE", "_NET_WM_ACTION_RESIZE", + "_NET_WM_ACTION_MINIMIZE", "_NET_WM_ACTION_SHADE", + "_NET_WM_ACTION_STICK", + "_NET_WM_ACTION_MAXIMIZE_VERT", "_NET_WM_ACTION_MAXIMIZE_HORZ", + "_NET_WM_ACTION_FULLSCREEN", "_NET_WM_ACTION_CHANGE_DESKTOP", + "_NET_WM_ACTION_CLOSE", + "UTF8_STRING", // When adding an ewmh atom after this, + // fix setEwmhAtomsSupport(Window) + "STRING", "MANAGER", + + // pekwm atoms + "_PEKWM_FRAME_ID", + "_PEKWM_FRAME_ORDER", + "_PEKWM_FRAME_ACTIVE", + "_PEKWM_FRAME_DECOR", + "_PEKWM_FRAME_SKIP", + "_PEKWM_TITLE", + + // ICCCM atoms + "WM_NAME", + "WM_ICON_NAME", + "WM_CLASS", + "WM_STATE", + "WM_CHANGE_STATE", + "WM_PROTOCOLS", + "WM_DELETE_WINDOW", + "WM_COLORMAP_WINDOWS", + "WM_TAKE_FOCUS", + "WM_WINDOW_ROLE", + "WM_CLIENT_MACHINE", + + // miscellaneous atoms + "_MOTIF_WM_HINTS" +}; + +//! @brief initialise the atoms mappings +void +Atoms::init(void) +{ + const uint num = sizeof(atomnames) / sizeof(char*); + Atom *atoms = new Atom[num]; + + XInternAtoms(PScreen::instance()->getDpy(), + const_cast(atomnames), num, 0, atoms); + + for (uint i = 0; i < num; ++i) { + _atoms[AtomName(i)] = atoms[i]; + } + + delete [] atoms; +} + +//! @brief Builds a array of all atoms in the map. +void +Atoms::setEwmhAtomsSupport(Window win) +{ + Atom *atoms = new Atom[UTF8_STRING+1]; + + for (uint i = 0; i <= UTF8_STRING; ++i) { + atoms[i] = _atoms[AtomName(i)]; + } + + AtomUtil::setAtoms(win, getAtom(NET_SUPPORTED), atoms, UTF8_STRING+1); + + delete [] atoms; +} + +std::map Atoms::_atoms; + +namespace AtomUtil { + +//! @brief Get unknown property +bool +getProperty(Window win, Atom atom, Atom type, + ulong expected, uchar** data, ulong *actual) +{ + Atom r_type; + int r_format, status; + ulong read = 0, left = 0; + + *data = 0; + do { + if (*data) { + XFree(*data); + *data = 0; + } + expected += left; + + status = + XGetWindowProperty(PScreen::instance()->getDpy(), win, atom, + 0L, expected, False, type, + &r_type, &r_format, &read, &left, data); + + if (status != Success || type != r_type || read == 0) { + if (*data) { + XFree(*data); + *data = 0; + } + left = 0; + } + } while (left); + + if (actual) { + *actual = read; + } + + return (*data != 0); +} + +//! @brief Set XA_ATOM, one value +void +setAtom(Window win, Atom atom, Atom value) +{ + XChangeProperty(PScreen::instance()->getDpy(), win, atom, XA_ATOM, 32, + PropModeReplace, (uchar *) &value, 1); +} + +//! @brief Set XA_ATOM, multiple values +void +setAtoms(Window win, Atom atom, Atom *values, int size) +{ + XChangeProperty(PScreen::instance()->getDpy(), win, atom, XA_ATOM, 32, + PropModeReplace, (uchar *) values, size); +} + +//! @brief Set XA_WINDOW, one value +void +setWindow(Window win, Atom atom, Window value) +{ + XChangeProperty(PScreen::instance()->getDpy(), win, atom, XA_WINDOW, 32, + PropModeReplace, (uchar *) &value, 1); +} + +//! @brief Set XA_WINDOW, multiple values +void +setWindows(Window win, Atom atom, Window *values, int size) +{ + XChangeProperty(PScreen::instance()->getDpy(), win, atom, XA_WINDOW, 32, + PropModeReplace, (uchar *) values, size); +} + +//! @brief Get XA_CARDINAL +bool +getLong(Window win, Atom atom, long &value) +{ + long *data = 0; + uchar *udata = 0; + + if (getProperty(win, atom, XA_CARDINAL, 1L, &udata, 0)) { + data = reinterpret_cast(udata); + value = *data; + XFree(udata); + + return true; + } + + return false; +} + +//! @brief Set XA_CARDINAL +void +setLong(Window win, Atom atom, long value) +{ + XChangeProperty(PScreen::instance()->getDpy(), win, atom, XA_CARDINAL, 32, + PropModeReplace, (uchar *) &value, 1); +} + +/** + * Set array of longs as Cardinal/32. + * + * @param win Window to set longs on. + * @param atom Atom to set longs as. + * @param values Array of longs to set. + * @param size Number of elements in array. + */ +void +setLongs(Window win, Atom atom, long *values, int size) +{ + XChangeProperty(PScreen::instance()->getDpy(), win, atom, XA_CARDINAL, 32, + PropModeReplace, reinterpret_cast(values), size); +} + + +//! @brief Get XA_STRING property +bool +getString(Window win, Atom atom, string &value) +{ + uchar *data = 0; + + if (getProperty(win, atom, XA_STRING, 64L, &data, 0)) { + value = string((const char*) data); + XFree(data); + + return true; + } + + return false; +} + +//! @brief Get UTF-8 string. +bool +getUtf8String(Window win, Atom atom, std::wstring &value) +{ + bool status = false; + unsigned char *data = 0; + + if (getProperty(win, atom, Atoms::getAtom(UTF8_STRING), + 32, &data, 0)) { + status = true; + + string utf8_str(reinterpret_cast(data)); + value = Util::from_utf8_str(utf8_str); + XFree(data); + } + + return status; +} + +//! @brief Set UTF-8 string. +void +setUtf8String(Window win, Atom atom, const std::wstring &value) +{ + string utf8_string(Util::to_utf8_str(value)); + + XChangeProperty(PScreen::instance()->getDpy(), win, atom, + Atoms::getAtom(UTF8_STRING), 8, + PropModeReplace, + reinterpret_cast(utf8_string.c_str()), + utf8_string.size()); +} + + /** + * Set array of UTF-8 strings. + * + * @param win Window to set string on. + * @param atom Atom to set array value. + * @param values Array of strings. + * @param length Number of elements in value. + */ + void + setUtf8StringArray(Window win, Atom atom, unsigned char *values, unsigned int length) + { + XChangeProperty(PScreen::instance()->getDpy(), win, atom, Atoms::getAtom(UTF8_STRING), + 8, PropModeReplace, values, length); + } + +//! @brief Set XA_STRING property +void +setString(Window win, Atom atom, const string &value) +{ + XChangeProperty(PScreen::instance()->getDpy(), win, atom, XA_STRING, 8, + PropModeReplace, (uchar*) value.c_str(), value.size()); +} + +/** + * Read text property. + * + * @param win Window to get property from. + * @param atom Atom holding the property. + * @param value Return string. + * @return true if property was successfully read. + */ +bool +getTextProperty(Window win, Atom atom, std::string &value) +{ + // Read text property, return if it fails. + XTextProperty text_property; + if (! XGetTextProperty(PScreen::instance()->getDpy(), win, &text_property, atom) + || ! text_property.value || ! text_property.nitems) { + return false; + } + + if (text_property.encoding == XA_STRING) { + value = reinterpret_cast(text_property.value); + } else { + char **mb_list; + int num; + + XmbTextPropertyToTextList(PScreen::instance()->getDpy(), &text_property, &mb_list, &num); + if (mb_list && num > 0) { + value = *mb_list; + XFreeStringList(mb_list); + } + } + + XFree(text_property.value); + + return true; +} + +//! @brief +void* +getEwmhPropData(Window win, Atom prop, Atom type, int &num) +{ + Atom type_ret; + int format_ret; + ulong items_ret, after_ret; + uchar *prop_data = 0; + + XGetWindowProperty(PScreen::instance()->getDpy(), win, prop, 0, 0x7fffffff, + False, type, &type_ret, &format_ret, &items_ret, + &after_ret, &prop_data); + + num = items_ret; + + return prop_data; +} + +/** + * Remove property from window. + */ +void +unsetProperty(Window win, Atom prop) +{ + XDeleteProperty(PScreen::instance()->getDpy(), win, prop); +} + +} // end namespace AtomUtil diff --git a/pekwm/Atoms.hh b/pekwm/Atoms.hh new file mode 100644 index 0000000..cad317a --- /dev/null +++ b/pekwm/Atoms.hh @@ -0,0 +1,142 @@ +// +// Atoms.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _ATOMS_HH_ +#define _ATOMS_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Types.hh" + +#include +#include + +extern "C" { +#include +#include +} + +enum AtomName { + // Ewmh Atom Names + NET_SUPPORTED, + NET_CLIENT_LIST, NET_CLIENT_LIST_STACKING, + NET_NUMBER_OF_DESKTOPS, + NET_DESKTOP_GEOMETRY, NET_DESKTOP_VIEWPORT, + NET_CURRENT_DESKTOP, NET_DESKTOP_NAMES, + NET_ACTIVE_WINDOW, NET_WORKAREA, + NET_DESKTOP_LAYOUT, NET_SUPPORTING_WM_CHECK, + NET_CLOSE_WINDOW, + NET_WM_NAME, NET_WM_VISIBLE_NAME, + NET_WM_ICON_NAME, NET_WM_VISIBLE_ICON_NAME, + NET_WM_ICON, NET_WM_DESKTOP, + NET_WM_STRUT, NET_WM_PID, + NET_WM_WINDOW_OPACITY, + + WINDOW_TYPE, + WINDOW_TYPE_DESKTOP, WINDOW_TYPE_DOCK, + WINDOW_TYPE_TOOLBAR, WINDOW_TYPE_MENU, + WINDOW_TYPE_UTILITY, WINDOW_TYPE_SPLASH, + WINDOW_TYPE_DIALOG, WINDOW_TYPE_NORMAL, + + STATE, + STATE_MODAL, STATE_STICKY, + STATE_MAXIMIZED_VERT, STATE_MAXIMIZED_HORZ, + STATE_SHADED, + STATE_SKIP_TASKBAR, STATE_SKIP_PAGER, + STATE_HIDDEN, STATE_FULLSCREEN, + STATE_ABOVE, STATE_BELOW, + STATE_DEMANDS_ATTENTION, + + EWMH_ALLOWED_ACTIONS, + EWMH_ACTION_MOVE, EWMH_ACTION_RESIZE, + EWMH_ACTION_MINIMIZE, EWMH_ACTION_SHADE, + EWMH_ACTION_STICK, + EWHM_ACTION_MAXIMIZE_VERT, EWMH_ACTION_MAXIMIZE_HORZ, + EWMH_ACTION_FULLSCREEN, ACTION_CHANGE_DESKTOP, + EWMH_ACTION_CLOSE, + + UTF8_STRING, // When adding an ewmh atom after this, + // fix setEwmhAtomsSupport(Window) + STRING, MANAGER, + + // pekwm atom names + PEKWM_FRAME_ID, + PEKWM_FRAME_ORDER, + PEKWM_FRAME_ACTIVE, + PEKWM_FRAME_DECOR, + PEKWM_FRAME_SKIP, + PEKWM_TITLE, + + // ICCCM Atom Names + WM_NAME, + WM_ICON_NAME, + WM_CLASS, + WM_STATE, + WM_CHANGE_STATE, + WM_PROTOCOLS, + WM_DELETE_WINDOW, + WM_COLORMAP_WINDOWS, + WM_TAKE_FOCUS, + WM_WINDOW_ROLE, + WM_CLIENT_MACHINE, + + // List of non PEKWM, ICCCM and EWMH atoms. + MOTIF_WM_HINTS +}; + + +// Atoms class +class Atoms { +public: + static void init(); + + static inline Atom getAtom(AtomName name) { + std::map::const_iterator it = _atoms.find(name); + if (it != _atoms.end()) { + return it->second; + } + return None; + } + + static void setEwmhAtomsSupport(Window win); + +private: + static std::map _atoms; +}; + +namespace AtomUtil { + bool getProperty(Window win, Atom atom, Atom type, + ulong expected, uchar **data, ulong *actual); + + void setAtom(Window win, Atom atom, Atom value); + void setAtoms(Window win, Atom atom, Atom *values, int size); + + void setWindow(Window win, Atom atom, Window value); + void setWindows(Window win, Atom atom, Window *values, int size); + + bool getLong(Window win, Atom atom, long &value); + void setLong(Window win, Atom atom, long value); + void setLongs(Window win, Atom atom, long *values, int size); + + bool getString(Window win, Atom atom, std::string &value); + void setString(Window win, Atom atom, const std::string &value); + + bool getUtf8String(Window win, Atom atom, std::wstring &value); + void setUtf8String(Window win, Atom atom, const std::wstring &value); + void setUtf8StringArray(Window win, Atom atom, unsigned char *values, unsigned int length); + + bool getTextProperty(Window win, Atom atom, std::string &value); + + void *getEwmhPropData(Window win, Atom prop, Atom type, int &num); + + void unsetProperty(Window win, Atom prop); +} + +#endif // _FONT_HH_ diff --git a/pekwm/AutoProperties.cc b/pekwm/AutoProperties.cc new file mode 100644 index 0000000..0cdfc7f --- /dev/null +++ b/pekwm/AutoProperties.cc @@ -0,0 +1,916 @@ +// +// AutoProperties.cc for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include + +#include "AutoProperties.hh" +#include "Config.hh" +#include "Util.hh" + +using std::cerr; +using std::endl; +using std::find; +using std::list; +using std::map; +using std::string; +using std::vector; +using std::strtol; + +AutoProperties *AutoProperties::_instance = 0; + +//! @brief Constructor for AutoProperties class +AutoProperties::AutoProperties(void) + : _extended(false), _harbour_sort(false), + _apply_on_start(true) +{ +#ifdef DEBUG + if (_instance) { + cerr << __FILE__ << "@" << __LINE__ << ": " + << "AutoProperties(" << this << ")::AutoProperties()" << endl + << " *** _instance already set: " << _instance << endl; + } +#endif // DEBUG + _instance = this; + + // fill parsing maps + _apply_on_map[""] = APPLY_ON_NONE; + _apply_on_map["START"] = APPLY_ON_START; + _apply_on_map["NEW"] = APPLY_ON_NEW; + _apply_on_map["RELOAD"] = APPLY_ON_RELOAD; + _apply_on_map["WORKSPACE"] = APPLY_ON_WORKSPACE; + _apply_on_map["TRANSIENT"] = APPLY_ON_TRANSIENT; + _apply_on_map["TRANSIENTONLY"] = APPLY_ON_TRANSIENT_ONLY; + + // global properties + _property_map[""] = AP_NO_PROPERTY; + _property_map["WORKSPACE"] = AP_WORKSPACE; + _property_map["PROPERTY"] = AP_PROPERTY; + _property_map["STICKY"] = AP_STICKY; + _property_map["SHADED"] = AP_SHADED; + _property_map["MAXIMIZEDVERTICAL"] = AP_MAXIMIZED_VERTICAL; + _property_map["MAXIMIZEDHORIZONTAL"] = AP_MAXIMIZED_HORIZONTAL; + _property_map["ICONIFIED"] = AP_ICONIFIED; + _property_map["BORDER"] = AP_BORDER; + _property_map["TITLEBAR"] = AP_TITLEBAR; + _property_map["FRAMEGEOMETRY"] = AP_FRAME_GEOMETRY; + _property_map["CLIENTGEOMETRY"] = AP_CLIENT_GEOMETRY; + _property_map["LAYER"] = AP_LAYER; + _property_map["SKIP"] = AP_SKIP; + _property_map["FULLSCREEN"] = AP_FULLSCREEN; + _property_map["PLACENEW"] = AP_PLACE_NEW; + _property_map["FOCUSNEW"] = AP_FOCUS_NEW; + _property_map["FOCUSABLE"] = AP_FOCUSABLE; + _property_map["CFGDENY"] = AP_CFG_DENY; + _property_map["ALLOWEDACTIONS"] = AP_ALLOWED_ACTIONS; + _property_map["DISALLOWEDACTIONS"] = AP_DISALLOWED_ACTIONS; +#ifdef OPACITY + _property_map["OPACITY"] = AP_OPACITY; +#endif // OPACITY + + // group properties + _group_property_map[""] = AP_NO_PROPERTY; + _group_property_map["SIZE"] = AP_GROUP_SIZE; + _group_property_map["BEHIND"] = AP_GROUP_BEHIND; + _group_property_map["FOCUSEDFIRST"] = AP_GROUP_FOCUSED_FIRST; + _group_property_map["GLOBAL"] = AP_GROUP_GLOBAL; + _group_property_map["RAISE"] = AP_GROUP_RAISE; + + // window type map + _window_type_map[""] = WINDOW_TYPE; + _window_type_map["DESKTOP"] = WINDOW_TYPE_DESKTOP; + _window_type_map["DOCK"] = WINDOW_TYPE_DOCK; + _window_type_map["TOOLBAR"] = WINDOW_TYPE_TOOLBAR; + _window_type_map["MENU"] = WINDOW_TYPE_MENU; + _window_type_map["UTILITY"] = WINDOW_TYPE_UTILITY; + _window_type_map["SPLASH"] = WINDOW_TYPE_SPLASH; + _window_type_map["DIALOG"] = WINDOW_TYPE_DIALOG; + _window_type_map["NORMAL"] = WINDOW_TYPE_NORMAL; +} + +//! @brief Destructor for AutoProperties class +AutoProperties::~AutoProperties(void) +{ + unload(); + _instance = 0; +} + +//! @brief Loads the autoprop config file. +bool +AutoProperties::load(void) +{ + string cfg_file(Config::instance()->getAutoPropsFile()); + if (! Util::requireReload(_cfg_state, cfg_file)) { + return false; + } + + // dealloc memory + unload(); + + CfgParser a_cfg; + if (! a_cfg.parse(cfg_file, CfgParserSource::SOURCE_FILE, false)) { + cfg_file = SYSCONFDIR "/autoproperties"; + if (! a_cfg.parse (cfg_file, CfgParserSource::SOURCE_FILE, false)) { + setDefaultTypeProperties(); + return false; + } + } + + // Setup template parsing if requested + loadRequire(a_cfg, cfg_file); + + if (a_cfg.is_dynamic_content()) { + _cfg_state.clear(); + } else { + _cfg_state = a_cfg.get_file_list(); + } + + // reset values + _apply_on_start = true; + + vector tokens; + vector::iterator token_it; + list workspaces; + + CfgParser::iterator it(a_cfg.get_entry_root()->begin()); + for (; it != a_cfg.get_entry_root()->end(); ++it) { + if (*(*it) == "PROPERTY") { + parseAutoProperty(*it, 0); + } else if (*(*it) == "TITLERULES") { + parseTitleProperty(*it); + } else if (*(*it) == "DECORRULES") { + parseDecorProperty(*it); + } else if (*(*it) == "TYPERULES") { + parseTypeProperty(*it); + } else if (*(*it) == "HARBOUR") { + parseDockAppProperty(*it); + } else if (*(*it) == "WORKSPACE") { // Workspace section + CfgParser::Entry *workspace = (*it)->get_section(); + CfgParser::Entry *value = workspace->find_entry("WORKSPACE"); + if (! value) { + continue; // Need workspace numbers. + } + + tokens.clear(); + if (Util::splitString(value->get_value(), tokens, " \t")) { + workspaces.clear(); + for (token_it = tokens.begin(); token_it != tokens.end(); ++token_it) + workspaces.push_back(strtol(token_it->c_str(), 0, 10) - 1); + + // Get all properties on for these workspaces. + CfgParser::iterator workspace_it(workspace->begin()); + for (; workspace_it != workspace->end(); ++workspace_it) { + parseAutoProperty(*workspace_it, &workspaces); + } + } + } + } + + // Validate date + setDefaultTypeProperties(); + + return true; +} + +/** + * Load autoproperties quirks. + */ +void +AutoProperties::loadRequire(CfgParser &a_cfg, std::string &file) +{ + CfgParser::Entry *section; + + // Look for requires section, + section = a_cfg.get_entry_root()->find_section("REQUIRE"); + if (section) { + list key_list; + + key_list.push_back(new CfgParserKeyBool("TEMPLATES", _extended, false)); + section->parse_key_values(key_list.begin(), key_list.end()); + for_each(key_list.begin(), key_list.end(), Util::Free()); + + // Re-load configuration with templates enabled. + if (_extended) { + a_cfg.clear(true); + a_cfg.parse(file, CfgParserSource::SOURCE_FILE, true); + } + } else { + _extended = false; + } +} + +//! @brief Frees allocated memory +void +AutoProperties::unload(void) +{ + list::iterator it; + + // remove auto properties + for (it = _prop_list.begin(); it != _prop_list.end(); ++it) { + delete *it; + } + _prop_list.clear(); + + // remove title properties + for (it = _title_prop_list.begin(); it != _title_prop_list.end(); ++it) { + delete *it; + } + _title_prop_list.clear(); + + // remove decor properties + for (it = _decor_prop_list.begin(); it != _decor_prop_list.end(); ++it) { + delete *it; + } + _decor_prop_list.clear(); + + // remove dock app properties + for (it = _dock_app_prop_list.begin(); it != _dock_app_prop_list.end(); ++it) { + delete *it; + } + _dock_app_prop_list.clear(); + + // remove type properties + map::iterator m_it(_window_type_prop_map.begin()); + for (; m_it != _window_type_prop_map.end(); ++m_it) { + delete m_it->second; + } + _window_type_prop_map.clear(); +} + +//! @brief Finds a property from the prop_list +Property* +AutoProperties::findProperty(const ClassHint* class_hint, + std::list* prop_list, int ws, uint type) +{ + // Allready remove apply on start + if (! _apply_on_start && (type == APPLY_ON_START)) + return 0; + + list::iterator it(prop_list->begin()); + list::iterator w_it; + + // start searching for a suitable property + for (bool ok = false; it != prop_list->end(); ++it, ok = false) { + // see if the type matches, if we have one + if ((type != 0) && ! (*it)->isApplyOn(type)) + continue; + + if (matchAutoClass(*class_hint, *it)) { + + // make sure it applies on the correct workspace + if ((*it)->getWsList().size()) { + w_it = find((*it)->getWsList().begin(), (*it)->getWsList().end(), + unsigned(ws)); + if (w_it != (*it)->getWsList().end()) { + return *it; + } + } else { + return *it; + } + } + } + + return 0; +} + +/** + * Parse regex_str and set on regex, outputting warning with name if + * it fails. + */ +bool +AutoProperties::parseRegexpOrWarning(RegexString ®ex, const std::string regex_str, const std::string &name) +{ + if (! regex_str.size() || regex.parse_match(Util::to_wide_str(regex_str))) { + return true; + } else { + cerr << " *** WARNING: invalid regexp " << regex_str << " for autoproperty " << name << endl; + return false; + } +} + +//! @brief Parses a property match rule +//! @param str String to parse. +//! @param prop Property to place result in. +//! @param extended Extended syntax including role and title in the name, defaults to true. +//! @return true on success, else false. +bool +AutoProperties::parsePropertyMatch(const std::string &str, Property *prop, bool extended) +{ + bool status = false; + + // Format of property matches are regexp,regexp . Split up in class + // and role regexps. + vector tokens; + Util::splitString(str, tokens, ",", extended ? 5 : 2, true); + + if (tokens.size() >= 2) { + // Make sure one of the two regexps compiles + status = parseRegexpOrWarning(prop->getHintName(), tokens[0], "name"); + status = status && parseRegexpOrWarning(prop->getHintClass(), tokens[1], "class"); + } + + // Parse extended part of regexp, role, title and apply on + if (status && extended) { + if (status && tokens.size() > 2) { + status = parseRegexpOrWarning(prop->getRole(), tokens[2], "role"); + } + if (status && tokens.size() > 3) { + status = parseRegexpOrWarning(prop->getTitle(), tokens[3], "title"); + } + if (status && tokens.size() > 4) { + parsePropertyApplyOn(tokens[4], prop); + } + } + + return status; +} + +//! @brief Parses a cs and sets up a basic property +bool +AutoProperties::parseProperty(CfgParser::Entry *section, Property *prop) +{ + CfgParser::Entry *value; + + // Get extra matching info. + value = section->find_entry ("TITLE"); + if (value) { + parseRegexpOrWarning(prop->getTitle(), value->get_value(), "title"); + } + value = section->find_entry ("ROLE"); + if (value) { + parseRegexpOrWarning(prop->getRole(), value->get_value(), "role"); + } + + // Parse apply on mask. + value = section->find_entry ("APPLYON"); + if (value) { + parsePropertyApplyOn(value->get_value(), prop); + } + + return true; +} + +/** + * Parse property apply on. + */ +void +AutoProperties::parsePropertyApplyOn(const std::string &apply_on, Property *prop) +{ + vector tokens; + if ((Util::splitString(apply_on, tokens, " \t", 5))) { + vector::iterator it(tokens.begin()); + for (; it != tokens.end(); ++it) { + prop->applyAdd(static_cast(ParseUtil::getValue(*it, _apply_on_map))); + } + } +} + +//! @brief Parses AutopProperty +void +AutoProperties::parseAutoProperty(CfgParser::Entry *section, std::list* ws) +{ + // Get sub section + section = section->get_section(); + if (! section) { + return; + } + + AutoProperty* property = new AutoProperty(); + parsePropertyMatch(section->get_value(), property, _extended); + + if (parseProperty(section, property)) { + parseAutoPropertyValue(section, property, ws); + _prop_list.push_back(property); + + } else { + delete property; + } +} + +//! @brief Parses a Group section of the AutoProps +void +AutoProperties::parseAutoGroup(CfgParser::Entry *section, AutoProperty* property) +{ + if (! section) { + return; + } + + if (section->get_value().size()) { + property->group_name = Util::to_wide_str(section->get_value()); + } + + PropertyType property_type; + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + property_type = ParseUtil::getValue((*it)->get_name(), _group_property_map); + + switch (property_type) { + case AP_GROUP_SIZE: + property->group_size = strtol((*it)->get_value().c_str(), 0, 10); + break; + case AP_GROUP_BEHIND: + property->group_behind = Util::isTrue((*it)->get_value()); + break; + case AP_GROUP_FOCUSED_FIRST: + property->group_focused_first = Util::isTrue((*it)->get_value()); + break; + case AP_GROUP_GLOBAL: + property->group_global = Util::isTrue((*it)->get_value()); + break; + case AP_GROUP_RAISE: + property->group_raise = Util::isTrue((*it)->get_value()); + break; + default: + // do nothing + break; + } + } +} + +/** + * Parses a title property section. + */ +void +AutoProperties::parseTitleProperty(CfgParser::Entry *section) +{ + section = section->get_section(); + + TitleProperty *title_property; + CfgParser::Entry *title_section; + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + title_section = (*it)->get_section(); + if (! title_section) { + continue; + } + + title_property = new TitleProperty(); + parsePropertyMatch(title_section->get_value(), title_property, _extended); + if (parseProperty(title_section, title_property)) { + CfgParser::Entry *value = title_section->find_entry("RULE"); + if (value && title_property->getTitleRule().parse_ed_s(Util::to_wide_str(value->get_value()))) { + _title_prop_list.push_back(title_property); + title_property = 0; + } + } + + if (title_property) { + delete title_property; + } + } +} + +/** + * Parse decor property sections. + */ +void +AutoProperties::parseDecorProperty(CfgParser::Entry *section) +{ + section = section->get_section(); + + DecorProperty *decor_property; + CfgParser::Entry *decor_section; + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + decor_section = (*it)->get_section (); + if (! decor_section) { + continue; + } + + decor_property = new DecorProperty(); + parsePropertyMatch(decor_section->get_value (), decor_property, _extended); + if (parseProperty(decor_section, decor_property)) { + CfgParser::Entry *value = decor_section->find_entry("DECOR"); + if (value) { + decor_property->applyAdd(APPLY_ON_START); + decor_property->setName(value->get_value()); + _decor_prop_list.push_back(decor_property); + decor_property = 0; + } + } + + if (decor_property) { + delete decor_property; + } + } +} + +/** + * Parse dock app properties. + */ +void +AutoProperties::parseDockAppProperty(CfgParser::Entry *section) +{ + section = section->get_section(); + + // Reset harbour sort, set to true if POSITION property found. + _harbour_sort = false; + + DockAppProperty *dock_property; + CfgParser::Entry *dock_section; + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + dock_section = (*it)->get_section(); + if (! dock_section) { + continue; + } + + dock_property = new DockAppProperty(); + parsePropertyMatch(dock_section->get_value(), dock_property, _extended); + if (parseProperty(dock_section, dock_property)) { + CfgParser::Entry *value = dock_section->find_entry("POSITION"); + if (value) { + _harbour_sort = true; + + int position = strtol(value->get_value().c_str(), 0, 10); + dock_property->setPosition(position); + _decor_prop_list.push_back(dock_property); + dock_property = 0; + } + } + + if (dock_property) { + delete dock_property; + } + } +} + +//! @brief Parse type auto properties. +//! @param section Section containing properties. +void +AutoProperties::parseTypeProperty(CfgParser::Entry *section) +{ + // Get sub section + section = section->get_section(); + + AtomName atom; + AutoProperty *type_property; + CfgParser::Entry *type_section; + map::iterator atom_it; + + // Look for all type properties + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + type_section = (*it)->get_section(); + if (! type_section) { + continue; + } + + // Create new property and try to parse + type_property = new AutoProperty(); + atom = ParseUtil::getValue(type_section->get_value(), _window_type_map); + if (atom == WINDOW_TYPE) { + cerr << " *** WARNING: unknown type " << type_section->get_value() << " for autoproperty." << endl; + } + + if (atom != WINDOW_TYPE && parseProperty(type_section, type_property)) { + // Parse of match ok, parse values + parseAutoPropertyValue(type_section, type_property, 0); + + // Add to list, make sure it does not exist already + atom_it = _window_type_prop_map.find(atom); + if (atom_it != _window_type_prop_map.end()) { + cerr << " *** WARNING: multiple type autoproperties for type " + << type_section->get_value() << endl; + delete atom_it->second; + } + _window_type_prop_map[atom] = type_property; + } else { + delete type_property; + } + } +} + +//! @brief Set default values for type auto properties not in configuration. +void +AutoProperties::setDefaultTypeProperties(void) +{ + // DESKTOP + if (! findWindowTypeProperty(WINDOW_TYPE_DESKTOP)) { + AutoProperty *prop = new AutoProperty(); + prop->maskAdd(AP_CLIENT_GEOMETRY); + prop->client_gm_mask = + XParseGeometry("0x0+0+0", + &prop->client_gm.x, &prop->client_gm.y, + &prop->client_gm.width, &prop->client_gm.height); + prop->maskAdd(AP_STICKY); + prop->sticky = true; + prop->maskAdd(AP_TITLEBAR); + prop->titlebar = false; + prop->maskAdd(AP_BORDER); + prop->border = false; + prop->maskAdd(AP_SKIP); + prop->skip = SKIP_MENUS|SKIP_FOCUS_TOGGLE|SKIP_SNAP|SKIP_PAGER|SKIP_TASKBAR; + prop->maskAdd(AP_LAYER); + prop->layer = LAYER_DESKTOP; + prop->maskAdd(AP_FOCUSABLE); + prop->focusable = false; + prop->maskAdd(AP_DISALLOWED_ACTIONS); + prop->disallowed_actions = ACTION_ACCESS_MOVE|ACTION_ACCESS_RESIZE; + + _window_type_prop_map[WINDOW_TYPE_DESKTOP] = prop; + } + + // DOCK + if (! findWindowTypeProperty(WINDOW_TYPE_DOCK)) { + AutoProperty *prop = new AutoProperty(); + prop->maskAdd(AP_STICKY); + prop->sticky = true; + prop->maskAdd(AP_TITLEBAR); + prop->titlebar = false; + prop->maskAdd(AP_BORDER); + prop->border = false; + prop->maskAdd(AP_SKIP); + prop->skip = SKIP_MENUS|SKIP_FOCUS_TOGGLE|SKIP_PAGER|SKIP_TASKBAR; + prop->maskAdd(AP_LAYER); + prop->layer = LAYER_DOCK; + prop->maskAdd(AP_FOCUSABLE); + prop->focusable = false; + prop->maskAdd(AP_DISALLOWED_ACTIONS); + prop->disallowed_actions = ACTION_ACCESS_MOVE|ACTION_ACCESS_RESIZE; + + _window_type_prop_map[WINDOW_TYPE_DOCK] = prop; + } + + // TOOLBAR + if (! findWindowTypeProperty(WINDOW_TYPE_TOOLBAR)) { + AutoProperty *prop = new AutoProperty(); + prop->maskAdd(AP_TITLEBAR); + prop->titlebar = true; + prop->maskAdd(AP_BORDER); + prop->border = true; + prop->maskAdd(AP_SKIP); + prop->skip = SKIP_MENUS|SKIP_FOCUS_TOGGLE|SKIP_PAGER|SKIP_TASKBAR; + + _window_type_prop_map[WINDOW_TYPE_TOOLBAR] = prop; + } + + // MENU + if (! findWindowTypeProperty(WINDOW_TYPE_MENU)) { + AutoProperty *prop = new AutoProperty(); + prop->maskAdd(AP_TITLEBAR); + prop->titlebar = false; + prop->maskAdd(AP_BORDER); + prop->border = false; + prop->maskAdd(AP_SKIP); + prop->skip = SKIP_MENUS|SKIP_FOCUS_TOGGLE|SKIP_SNAP|SKIP_PAGER|SKIP_TASKBAR; + + _window_type_prop_map[WINDOW_TYPE_MENU] = prop; + } + + // UTILITY + if (! findWindowTypeProperty(WINDOW_TYPE_UTILITY)) { + AutoProperty *prop = new AutoProperty(); + prop->maskAdd(AP_TITLEBAR); + prop->titlebar = true; + prop->maskAdd(AP_BORDER); + prop->border = true; + prop->maskAdd(AP_SKIP); + prop->skip = SKIP_MENUS|SKIP_FOCUS_TOGGLE|SKIP_SNAP; + + _window_type_prop_map[WINDOW_TYPE_UTILITY] = prop; + } + + // SPLASH + if (! findWindowTypeProperty(WINDOW_TYPE_SPLASH)) { + AutoProperty *prop = new AutoProperty(); + prop->maskAdd(AP_TITLEBAR); + prop->titlebar = false; + prop->maskAdd(AP_BORDER); + prop->border = false; + + _window_type_prop_map[WINDOW_TYPE_SPLASH] = prop; + } +} + +//! @brief Parse AutoProperty value attributes. +//! @param section Config section to parse. +//! @param prop Property to store result in. +//! @param ws List of workspaces to apply property on. +void +AutoProperties::parseAutoPropertyValue(CfgParser::Entry *section, AutoProperty *prop, std::list *ws) +{ + // Copy workspaces, if any + if (ws) { + prop->getWsList().assign(ws->begin(), ws->end()); + } + + // See if we have a group section + CfgParser::Entry *group_section(section->find_section ("GROUP")); + if (group_section) { + parseAutoGroup(group_section, prop); + } + + // start parsing of values + string name, value; + vector tokens; + vector::iterator token_it; + PropertyType property_type; + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + property_type = ParseUtil::getValue((*it)->get_name(), _property_map); + + switch (property_type) { + case AP_STICKY: + prop->maskAdd(AP_STICKY); + prop->sticky = Util::isTrue((*it)->get_value()); + break; + case AP_SHADED: + prop->maskAdd(AP_SHADED); + prop->shaded = Util::isTrue((*it)->get_value()); + break; + case AP_MAXIMIZED_VERTICAL: + prop->maskAdd(AP_MAXIMIZED_VERTICAL); + prop->maximized_vertical = Util::isTrue((*it)->get_value()); + break; + case AP_MAXIMIZED_HORIZONTAL: + prop->maskAdd(AP_MAXIMIZED_HORIZONTAL); + prop->maximized_horizontal = Util::isTrue((*it)->get_value()); + break; + case AP_ICONIFIED: + prop->maskAdd(AP_ICONIFIED); + prop->iconified = Util::isTrue((*it)->get_value()); + break; + case AP_BORDER: + prop->maskAdd(AP_BORDER); + prop->border = Util::isTrue((*it)->get_value()); + break; + case AP_TITLEBAR: + prop->maskAdd(AP_TITLEBAR); + prop->titlebar = Util::isTrue((*it)->get_value()); + break; + case AP_FRAME_GEOMETRY: + prop->maskAdd(AP_FRAME_GEOMETRY); + prop->frame_gm_mask = + XParseGeometry((char*) (*it)->get_value().c_str(), + &prop->frame_gm.x, &prop->frame_gm.y, + &prop->frame_gm.width, &prop->frame_gm.height); + break; + case AP_CLIENT_GEOMETRY: + prop->maskAdd(AP_CLIENT_GEOMETRY); + prop->client_gm_mask = + XParseGeometry((char*) (*it)->get_value().c_str(), + &prop->client_gm.x, &prop->client_gm.y, + &prop->client_gm.width, &prop->client_gm.height); + break; + case AP_LAYER: + prop->layer = Config::instance()->getLayer((*it)->get_value()); + if (prop->layer != LAYER_NONE) { + prop->maskAdd(AP_LAYER); + } + break; + case AP_WORKSPACE: + prop->maskAdd(AP_WORKSPACE); + prop->workspace = unsigned(strtol((*it)->get_value().c_str(), 0, 10) - 1); + break; + case AP_SKIP: + prop->maskAdd(AP_SKIP); + tokens.clear(); + if ((Util::splitString((*it)->get_value(), tokens, " \t"))) { + for (token_it = tokens.begin(); token_it != tokens.end(); ++token_it) { + prop->skip |= Config::instance()->getSkip(*token_it); + } + } + break; + case AP_FULLSCREEN: + prop->maskAdd(AP_FULLSCREEN); + prop->fullscreen = Util::isTrue((*it)->get_value()); + break; + case AP_PLACE_NEW: + prop->maskAdd(AP_PLACE_NEW); + prop->place_new = Util::isTrue((*it)->get_value()); + break; + case AP_FOCUS_NEW: + prop->maskAdd(AP_FOCUS_NEW); + prop->focus_new = Util::isTrue((*it)->get_value()); + break; + case AP_FOCUSABLE: + prop->maskAdd(AP_FOCUSABLE); + prop->focusable = Util::isTrue((*it)->get_value()); + break; + case AP_CFG_DENY: + prop->maskAdd(AP_CFG_DENY); + tokens.clear(); + if ((Util::splitString((*it)->get_value(), tokens, " \t"))) { + for (token_it = tokens.begin(); token_it != tokens.end(); ++token_it) { + prop->cfg_deny |= Config::instance()->getCfgDeny(*token_it); + } + } + break; + case AP_ALLOWED_ACTIONS: + prop->maskAdd(AP_ALLOWED_ACTIONS); + Config::instance()->parseActionAccessMask((*it)->get_value(), prop->allowed_actions); + break; + case AP_DISALLOWED_ACTIONS: + prop->maskAdd(AP_DISALLOWED_ACTIONS); + Config::instance()->parseActionAccessMask((*it)->get_value(), prop->disallowed_actions); + break; +#ifdef OPACITY + case AP_OPACITY: + prop->maskAdd(AP_OPACITY); + Config::parseOpacity((*it)->get_value(), prop->focus_opacity, prop->unfocus_opacity); + break; +#endif // OPACITY + default: + // do nothing + break; + } + } +} + +//! @brief Searches the _prop_list for a property +AutoProperty* +AutoProperties::findAutoProperty(const ClassHint* class_hint, int ws, uint type) +{ + return static_cast(findProperty(class_hint, &_prop_list, ws, type)); +} + +//! @brief Searches the _title_prop_list for a property +TitleProperty* +AutoProperties::findTitleProperty(const ClassHint* class_hint) +{ + return static_cast(findProperty(class_hint, &_title_prop_list, -1, 0)); +} + +//! @brief +DecorProperty* +AutoProperties::findDecorProperty(const ClassHint* class_hint) +{ + return static_cast(findProperty(class_hint, &_decor_prop_list, -1, 0)); +} + +DockAppProperty* +AutoProperties::findDockAppProperty(const ClassHint *class_hint) +{ + return static_cast(findProperty(class_hint, &_dock_app_prop_list, -1, 0)); +} + +//! @brief Get AutoProperty for window of type type +//! @param atom Atom to get property for. +//! @return AutoProperty on success, else 0. +AutoProperty* +AutoProperties::findWindowTypeProperty(AtomName atom) +{ + AutoProperty *prop = 0; + map::iterator it(_window_type_prop_map.find(atom)); + if (it != _window_type_prop_map.end()) { + prop = it->second; + } + + return prop; +} + +//! @brief Removes all ApplyOnStart actions as they consume memory +void +AutoProperties::removeApplyOnStart(void) +{ + list::iterator it(_prop_list.begin()); + for (; it != _prop_list.end(); ++it) { + if ((*it)->isApplyOn(APPLY_ON_START)) { + (*it)->applyRemove(APPLY_ON_START); + if (! (*it)->getApplyOn()) { + delete *it; + it = _prop_list.erase(it); + --it; // compensate for the ++ in the loop + } + } + } + + _apply_on_start = false; +} + +//! @brief Tries to match a class hint against an autoproperty data entry +bool +AutoProperties::matchAutoClass(const ClassHint &hint, Property *prop) +{ + bool ok = false; + + if ((prop->getHintName() == hint.h_name) + && (prop->getHintClass() == hint.h_class)) + { + ok = true; + if (prop->getTitle ().is_match_ok ()) { + ok = (prop->getTitle () == hint.title); + } + if (ok && prop->getRole ().is_match_ok ()) { + ok = (prop->getRole () == hint.h_role); + } + } + + return ok; +} diff --git a/pekwm/AutoProperties.hh b/pekwm/AutoProperties.hh new file mode 100644 index 0000000..34ba4d7 --- /dev/null +++ b/pekwm/AutoProperties.hh @@ -0,0 +1,278 @@ +// +// AutoProperties.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _AUTOPROPERTIES_HH_ +#define _AUTOPROPERTIES_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" +#include "Atoms.hh" +#include "CfgParser.hh" +#include "RegexString.hh" +#include "ParseUtil.hh" + +#include +#include + +/** + * Bitmask with different auto property types, used to identify what + * properties has been set in Property. + */ +enum PropertyType { + AP_STICKY = (1L << 1), + AP_SHADED = (1L << 2), + AP_MAXIMIZED_VERTICAL = (1L << 3), + AP_MAXIMIZED_HORIZONTAL = (1L << 4), + AP_ICONIFIED = (1L << 5), + AP_BORDER = (1L << 6), + AP_TITLEBAR = (1L << 7), + AP_FRAME_GEOMETRY = (1L << 8), + AP_CLIENT_GEOMETRY = (1L << 9), + AP_LAYER = (1L << 10), + AP_WORKSPACE = (1L << 11), + AP_SKIP = (1L << 12), + AP_FULLSCREEN = (1L << 13), + AP_PLACE_NEW = (1L << 14), + AP_FOCUS_NEW = (1L << 15), + AP_FOCUSABLE = (1L << 16), + AP_CFG_DENY = (1L << 17), + AP_ALLOWED_ACTIONS = (1L << 18), + AP_DISALLOWED_ACTIONS = (1L << 19), +#ifdef OPACITY + AP_OPACITY = (1L << 20), +#endif // OPACITY + + AP_GROUP_SIZE, + AP_GROUP_BEHIND, + AP_GROUP_FOCUSED_FIRST, + AP_GROUP_GLOBAL, + AP_GROUP_RAISE, + + AP_PROPERTY, + AP_NO_PROPERTY +}; + +/** + * ClassHint holds information from a window required to identify it. + */ +class ClassHint { +public: + ClassHint(void) { } + ClassHint(const std::wstring &n_h_name, const std::wstring &n_h_class, + const std::wstring &n_h_role, const std::wstring &n_title, const std::wstring &n_group) + : h_name(n_h_name), h_class(n_h_class), h_role(n_h_role), title(n_title), group(n_group) { } + ~ClassHint(void) { } + + inline ClassHint& operator = (const ClassHint& rhs) { + h_name = rhs.h_name; + h_class = rhs.h_class; + h_role = rhs.h_role; + title = rhs.title; + group = rhs.group; + + return *this; + } + inline bool operator == (const ClassHint& rhs) const { + if (group.size() > 0) { + if (group == rhs.group) { + return true; + } + } else if ((h_name == rhs.h_name) && (h_class == rhs.h_class) && + (h_role == rhs.h_role)) { + return true; + } + + return false; + } + +public: + std::wstring h_name; /**< name part of WM_CLASS hint. */ + std::wstring h_class; /**< class part of WM_CLASS hint. */ + std::wstring h_role; /**< WM_ROLE hint value. */ + std::wstring title; /**< Title of window. */ + std::wstring group; /**< Group window belongs to. */ +}; + +/** + * Base class for auto properties, includes base matching information. + */ +class Property { +public: + Property(void) : _apply_mask(0) { } + virtual ~Property(void) { } + + inline RegexString& getHintName(void) { return _hint_name; } + inline RegexString& getHintClass(void) { return _hint_class; } + inline RegexString& getRole(void) { return _role; } + inline RegexString& getTitle(void) { return _title; } + + inline uint getApplyOn(void) const { return _apply_mask; } + + inline bool isApplyOn(uint mask) const { return (_apply_mask&mask); } + inline void applyAdd(uint mask) { _apply_mask |= mask; } + inline void applyRemove(uint mask) { _apply_mask &= ~mask; } + + inline std::list getWsList(void) { return _ws_list; } + +private: + RegexString _hint_name, _hint_class; + RegexString _role, _title; + + uint _apply_mask; + std::list _ws_list; +}; + +// AutoProperty for everything except title rewriting +class AutoProperty : public Property +{ +public: + AutoProperty(void) : skip(SKIP_NONE), cfg_deny(0), + group_size(-1), group_behind(false), group_focused_first(false), + group_global(false), group_raise(false), _prop_mask(0) { } + virtual ~AutoProperty(void) { } + + inline bool isMask(uint mask) { return (_prop_mask&mask); } + inline void maskAdd(uint mask) { _prop_mask |= mask; } + inline void maskRemove(uint mask) { _prop_mask &= ~mask; } + +public: + Geometry frame_gm, client_gm; + int frame_gm_mask, client_gm_mask; + + bool sticky, shaded, iconified; + bool maximized_vertical, maximized_horizontal, fullscreen; + bool border, titlebar; + bool focusable, place_new, focus_new; + uint workspace, layer, skip, cfg_deny; +#ifdef OPACITY + uint focus_opacity, unfocus_opacity; +#endif //OPACITY + uint allowed_actions, disallowed_actions; + + std::string frame_decor; + + // grouping variables + int group_size; + std::wstring group_name; + bool group_behind, group_focused_first, group_global, group_raise; + +private: + uint _prop_mask; +}; + +// TitleProperty for title rewriting +class TitleProperty : public Property +{ +public: + TitleProperty(void) { } + virtual ~TitleProperty(void) { } + + RegexString& getTitleRule(void) { return _title_rule; } + +private: + RegexString _title_rule; +}; + +// DecorProperty for multiple decor types +class DecorProperty : public Property +{ +public: + DecorProperty(void) { } + virtual ~DecorProperty(void) { } + + inline const std::string &getName(void) const { return _decor_name; } + inline void setName(const std::string &name) { _decor_name = name; } + +private: + std::string _decor_name; +}; + +// DockApp for Harbour sorting +class DockAppProperty : public Property +{ +public: + DockAppProperty(void) : _position(0) { } + virtual ~DockAppProperty(void) { } + + inline int getPosition(void) const { return _position; } + inline void setPosition(int position) { _position = position; } + +private: + int _position; +}; + +class AutoProperties { +public: + AutoProperties(void); + ~AutoProperties(void); + + static inline AutoProperties *instance(void) { return _instance; } + + AutoProperty* findAutoProperty(const ClassHint* class_hintbb, + int ws = -1, uint type = 0); + TitleProperty* findTitleProperty(const ClassHint* class_hint); + DecorProperty* findDecorProperty(const ClassHint* class_hint); + DockAppProperty* findDockAppProperty(const ClassHint *class_hint); + inline bool isHarbourSort(void) const { return _harbour_sort; } + + AutoProperty *findWindowTypeProperty(AtomName atom); + + bool load(void); + void unload(void); + + void removeApplyOnStart(void); + + static bool matchAutoClass(const ClassHint &hint, Property *prop); + +private: + Property* findProperty(const ClassHint* class_hint, + std::list* prop_list, + int ws, uint type); + + void loadRequire(CfgParser &a_cfg, std::string &file); + + bool parsePropertyMatch(const std::string &str, Property *prop, bool extended = true); + void parsePropertyApplyOn(const std::string &apply_on, Property *prop); + bool parseRegexpOrWarning(RegexString ®ex, const std::string regex_str, const std::string &name); + bool parseProperty(CfgParser::Entry *section, Property *prop); + void parseAutoProperty(CfgParser::Entry *section, std::list* ws); + void parseAutoGroup(CfgParser::Entry *section, AutoProperty* prop); + void parseTitleProperty(CfgParser::Entry *section); + void parseDecorProperty(CfgParser::Entry *section); + + void parseAutoPropertyValue(CfgParser::Entry *section, AutoProperty *prop, std::list *ws); + + void parseDockAppProperty(CfgParser::Entry *section); + void parseTypeProperty(CfgParser::Entry *section); + + void setDefaultTypeProperties(void); + +private: + std::map _cfg_state; /**< Map of file mtime for all files touched by a configuration. */ + bool _extended; /**< Extended syntax enabled for autoproperties? */ + + std::map _window_type_prop_map; + std::list _prop_list; + std::list _title_prop_list; + std::list _decor_prop_list; + std::list _dock_app_prop_list; + bool _harbour_sort; + bool _apply_on_start; + + std::map _apply_on_map; + std::map _property_map; + std::map _group_property_map; + std::map _window_type_map; + + static AutoProperties *_instance; +}; + +#endif // _AUTOPROPS_HH_ diff --git a/pekwm/CfgParser.cc b/pekwm/CfgParser.cc new file mode 100644 index 0000000..123ae69 --- /dev/null +++ b/pekwm/CfgParser.cc @@ -0,0 +1,737 @@ +// +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "CfgParser.hh" +#include "Compat.hh" +#include "Util.hh" + +#include +#include +#include +#include +#include + +enum { + PARSE_BUF_SIZE = 1024 +}; + +using std::cerr; +using std::endl; +using std::list; +using std::map; +using std::set; +using std::string; +using std::auto_ptr; + +const string CfgParser::_root_source_name = string(""); +const char *CP_PARSE_BLANKS = " \t\n"; + +//! @brief CfgParser::Entry constructor. +CfgParser::Entry::Entry(const std::string &source_name, int line, + const std::string &name, const std::string &value, + CfgParser::Entry *section) + : _section(section), + _name(name), _value(value), + _line(line), _source_name(source_name) +{ +} + +/** + * Copy Entry together with the content. + */ +CfgParser::Entry::Entry(const CfgParser::Entry &entry) + : _section(0), + _name(entry._name), _value(entry._value), + _line(entry._line), _source_name(_source_name) +{ + list::const_iterator it(entry._entries.begin()); + for (; it != entry._entries.end(); ++it) { + _entries.push_back(new Entry(*(*it))); + } + if (entry._section) { + _section = new Entry(*entry._section); + } +} + +//! @brief CfgParser::Entry destructor. +CfgParser::Entry::~Entry(void) +{ + for_each(_entries.begin(), _entries.end(), Util::Free()); + + if (_section) { + delete _section; + _section = 0; + } +} + +/** + * Append Entry to the end of Entry list at current depth. + */ +CfgParser::Entry* +CfgParser::Entry::add_entry(CfgParser::Entry *entry, bool overwrite) +{ + CfgParser::Entry *entry_search = 0; + if (overwrite) { + if (entry->get_section()) { + entry_search = find_entry(entry->get_name(), true, entry->get_section()->get_value().c_str()); + } else { + entry_search = find_entry(entry->get_name(), false); + } + } + + // This is a bit awkward but to keep compatible with old + // configuration syntax overwriting of section is only allowed + // when the value is the same. + if (entry_search + && (! entry_search->get_section() + || strcasecmp(entry->get_value().c_str(), entry_search->get_value().c_str()) == 0)) { + entry_search->_value = entry->get_value(); + entry_search->set_section(entry->get_section(), overwrite); + + // Clear resources used by entry + entry->_section = 0; + delete entry; + entry = entry_search; + } else { + _entries.push_back(entry); + } + + return entry; +} + +//! @brief Adds Entry to the end of Entry list at current depth. +CfgParser::Entry* +CfgParser::Entry::add_entry(const std::string &source_name, int line, + const std::string &name, const std::string &value, + CfgParser::Entry *section, bool overwrite) +{ + return add_entry(new Entry(source_name, line, name, value, section), overwrite); +} + +/** + * Set section, copy section entires over if overwrite. + */ +CfgParser::Entry* +CfgParser::Entry::set_section(CfgParser::Entry *section, bool overwrite) +{ + if (_section) { + if (overwrite) { + _section->copy_tree_into(section); + delete section; + } else { + delete _section; + _section = section; + } + } else { + _section = section; + } + + return _section; +} + +//! @brief Gets next entry without subsection matching the name name. +//! @param name Name of Entry to look for. +CfgParser::Entry* +CfgParser::Entry::find_entry(const std::string &name, bool include_sections, const char *value) +{ + CfgParser::Entry *value_check; + list::iterator it(_entries.begin()); + for (; it != _entries.end(); ++it) { + value_check = include_sections ? (*it)->get_section() : (*it); + + if (*(*it) == name.c_str() + && (! (*it)->get_section() || include_sections) + && (! value || (value_check && value_check->get_value() == value))) { + return *it; + } + } + + return 0; +} + +//! @brief Gets the next entry with subsection matchin the name name. +//! @param name Name of Entry to look for. +CfgParser::Entry* +CfgParser::Entry::find_section(const std::string &name, const char *value) +{ + list::iterator it(_entries.begin()); + for (; it != _entries.end(); ++it) { + if ((*it)->get_section() && *(*it) == name.c_str() + && (! value || (*it)->get_section()->get_value() == value)) { + return (*it)->get_section(); + } + } + + return 0; +} + + +//! @brief Sets and validates data specified by key list. +void +CfgParser::Entry::parse_key_values(std::list::iterator begin, + std::list::iterator end) +{ + CfgParser::Entry *value; + list::iterator it; + + for (it = begin; it != end; ++it) { + value = find_entry((*it)->get_name()); + if (value) { + try { + (*it)->parse_value(value->get_value()); + + } catch (string &ex) { + cerr << " *** WARNING " << ex << endl << " " << *value << endl; + } + } + } +} + +/** + * Copy tree into current entry, overwrite entries if overwrite is + * true. + */ +void +CfgParser::Entry::copy_tree_into(CfgParser::Entry *from, bool overwrite) +{ + // Copy section + if (from->get_section()) { + if (_section) { + _section->copy_tree_into(from->get_section(), overwrite); + } else { + _section = new Entry(*(from->get_section())); + } + } + + // Copy elements + CfgParser::iterator it(from->begin()); + for (; it != from->end(); ++it) { + CfgParser::Entry *entry_section = 0; + if ((*it)->get_section()) { + entry_section = new Entry(*((*it)->get_section())); + } + + add_entry((*it)->get_source_name(), (*it)->get_line(), (*it)->get_name(), (*it)->get_value(), + entry_section, true); + } +} + +//! @brief Operator <<, return info on source, line, name and value. +std::ostream& +operator<<(std::ostream &stream, const CfgParser::Entry &entry) +{ + stream << entry.get_source_name() << "@" << entry.get_line() + << " " << entry.get_name() << " = " << entry.get_value(); + return stream; +} + +//! @brief CfgParser constructor. +CfgParser::CfgParser(void) + : _source(0), _root_entry(0), _is_dynamic_content(false), + _section(_root_entry), _overwrite(false) +{ + _root_entry = new CfgParser::Entry(_root_source_name, 0, "ROOT", ""); + _section = _root_entry; +} + +//! @brief CfgParser destructor. +CfgParser::~CfgParser(void) +{ + clear(false); +} + +/** + * Clear resources used by parser, end up in the same state as in + * after construction. + * + * @param realloc If realloc is false, root_entry will be cleared as well rendering the parser useless. Defaults to true. + */ +void +CfgParser::clear(bool realloc) +{ + _source = 0; + delete _root_entry; + + if (realloc) { + _root_entry = new CfgParser::Entry(_root_source_name, 0, "ROOT", ""); + } else { + _root_entry = 0; + } + + _section = _root_entry; + _overwrite = false; + + // Clear lists + _source_list.clear(); + _source_name_list.clear(); + _source_name_set.clear(); + _section_list.clear(); + _var_map.clear(); + + // Remove sections + map::iterator it(_section_map.begin()); + for (; it != _section_map.end(); ++it) { + delete it->second; + } + _section_map.clear(); +} + +/** + * Parses source and fills root section with data. + * + * @param src Source. + * @param type Type of source, defaults to file. + * @param overwrite Overwrite or append duplicate elements, defaults to false. + */ +bool +CfgParser::parse(const std::string &src, CfgParserSource::Type type, bool overwrite) +{ + // Set overwrite + _overwrite = overwrite; + + // Init parse buffer and reserve memory. + string buf, value; + buf.reserve(PARSE_BUF_SIZE); + + // Open initial source. + parse_source_new(src, type); + if (_source_list.size() == 0) { + return false; + } + + int c, next; + while (_source_list.size()) { + _source = _source_list.back(); + if (_source->is_dynamic()) { + _is_dynamic_content = true; + } + + while ((c = _source->getc()) != EOF) { + switch (c) { + case '\n': + // To be able to handle entry ends AND { after \n a check + // to see what comes after the newline is done. If { appears + // we continue as nothing happened else we finish the entry. + next = parse_skip_blank(_source); + if (next != '{') { + parse_entry_finish(buf, value); + } + break; + case ';': + parse_entry_finish(buf, value); + break; + case '{': + if (parse_name(buf)) { + parse_section_finish(buf, value); + } else { + cerr << "Ignoring section as name is empty." << endl; + } + buf.clear(); + value.clear(); + break; + case '}': + if (_section_list.size() > 0) { + if (buf.size() && parse_name(buf)) { + parse_entry_finish(buf, value); + buf.clear(); + value.clear(); + } + _section = _section_list.back(); + _section_list.pop_back(); + } else { + cerr << "Extra } character found, ignoring." << endl; + } + break; + case '=': + value.clear(); + parse_value(_source, value); + break; + case '#': + parse_comment_line(_source); + break; + case '/': + next = _source->getc(); + if (next == '/') { + parse_comment_line(_source); + } else if (next == '*') { + parse_comment_c(_source); + } else { + buf += c; + _source->ungetc(next); + } + break; + default: + buf += c; + break; + } + } + + try { + _source->close(); + + } catch (string &ex) { + cerr << ex << endl; + } + delete _source; + _source_list.pop_back(); + _source_name_list.pop_back(); + } + + if (buf.size()) { + parse_entry_finish(buf, value); + } + + return true; +} + +//! @brief Creates and opens new CfgParserSource. +void +CfgParser::parse_source_new(const std::string &name_orig, CfgParserSource::Type type) +{ + int done = 0; + string name(name_orig); + + do { + CfgParserSource *source = source_new(name, type); + assert(source); + + // Open and set as active, delete if fails. + try { + source->open(); + // Add source to file list if file + if (type == CfgParserSource::SOURCE_FILE) { + _file_list[name] = Util::getMtime(name); + } + + _source = source; + _source_list.push_back(_source); + done = 1; + + } catch (string &ex) { + delete source; + // Previously added in source_new + _source_name_list.pop_back(); + + + // Display error message on second try + if (done) { + cerr << ex << endl; + } + + // If the open fails and we are trying to open a file, try + // to open the file from the current files directory. + if (! done && (type == CfgParserSource::SOURCE_FILE)) { + if (_source_name_list.size() && (name[0] != '/')) { + name = Util::getDir(_source_name_list.back()); + name += "/" + name_orig; + } + } + } + } while (! done++ && (type == CfgParserSource::SOURCE_FILE)); +} + +//! @brief Parses from beginning to first blank. +bool +CfgParser::parse_name(std::string &buf) +{ + if (! buf.size()) { + cerr << "Unable to parse empty name." << endl; + return false; + } + + // Identify name. + string::size_type begin, end; + begin = buf.find_first_not_of(CP_PARSE_BLANKS); + if (begin == string::npos) { + return false; + } + end = buf.find_first_of(CP_PARSE_BLANKS, begin); + + // Check if there is any garbage after the value. + if (end != string::npos) { + if (buf.find_first_not_of(CP_PARSE_BLANKS, end) != string::npos) { + // Pass, do notihng + } + } + + // Set name. + buf = buf.substr(begin, end - begin); + + return true; +} + +//! @brief Parses from = to end of " pair. +void +CfgParser::parse_value(CfgParserSource *source, std::string &value) +{ + // We expect to get a " after the =, however we ignore anything else. + int c; + while ((c = source->getc()) != EOF && c != '"') + ; + + // Check if we got to a " or found EOF first. + if (c == EOF) { + cerr << "Reached EOF before opening \" in value." << endl; + return; + } + + // Parse until next ", and escape characters after \. + while ((c = source->getc()) != EOF && c != '"') { + // Escape character after \, if newline drop it. + if (c == '\\') { + c = source->getc(); + if (c == '\n' || c == EOF) { + continue; + } + } + value += c; + } + + if (c == EOF) { + cerr << "Reached EOF before closing \" in value." << endl; + } + + // If the value is empty, parse_entry_finish() might later just skip + // the complete entry. To allow empty config options we add a dummy space. + if (!value.size()) { + value = " "; + } +} + +//! @brief Parses entry (name + value) and executes command accordingly. +void +CfgParser::parse_entry_finish(std::string &buf, std::string &value) +{ + if (value.size()) { + parse_entry_finish_standard(buf, value); + } else { + // Template handling, expand or define template. + if (buf.size() && parse_name(buf) && buf[0] == '@') { + parse_entry_finish_template(buf); + } + buf.clear(); + } +} +/** + * Finish standard entry. + */ +void +CfgParser::parse_entry_finish_standard(std::string &buf, std::string &value) +{ + if (parse_name(buf)) { + if (buf[0] == '$') { + variable_define(buf, value); + } else { + variable_expand(value); + + if (buf == "INCLUDE") { + parse_source_new(value, CfgParserSource::SOURCE_FILE); + } else if (buf == "COMMAND") { + parse_source_new(value, CfgParserSource::SOURCE_COMMAND); + } else { + _section->add_entry(_source->get_name(), _source->get_line(), buf, value, 0, _overwrite); + } + } + } else { + cerr << "Dropping entry with empty name." << endl; + } + + value.clear(); + buf.clear(); +} + +/** + * Finish template entry, copy data into current section. + */ +void +CfgParser::parse_entry_finish_template(std::string &name) +{ + map::iterator it(_section_map.find(name.c_str() + 1)); + if (it == _section_map.end()) { + cerr << " *** WARNING: No such template " << name << endl; + return; + } + + _section->copy_tree_into(it->second); +} + +//! @brief Creates new Section on { +void +CfgParser::parse_section_finish(std::string &buf, std::string &value) +{ + // Create Entry representing Section + Entry *section = 0; + if (buf.size() == 6 && strcasecmp(buf.c_str(), "DEFINE") == 0) { + // Look for define section, started with Define = "Name" { + map::iterator it(_section_map.find(value)); + if (it != _section_map.end()) { + delete it->second; + _section_map.erase(it); + } + + section = new Entry(_source->get_name(), _source->get_line(), buf, value); + _section_map[value] = section; + } else { + // Create Entry for sub-section. + section = new Entry(_source->get_name(), _source->get_line(), buf, value); + + // Add parent section, get section from parent section as it + // can be different from the newly created if it is not + // overwritten. + CfgParser::Entry *parent = _section->add_entry(_source->get_name(), _source->get_line(), + buf, value, section, _overwrite); + section = parent->get_section(); + } + + // Set current Entry to newly created Section. + _section_list.push_back(_section); + _section = section; +} + +//! @brief Parses Source until end of line discarding input. +void +CfgParser::parse_comment_line(CfgParserSource *source) +{ + int c; + while (((c = source->getc()) != EOF) && (c != '\n')) + ; + + // Give back the newline, needed for flushing value before comment + if (c == '\n') { + source->ungetc(c); + } +} + +//! @brief Parses Source until */ is found. +void +CfgParser::parse_comment_c(CfgParserSource *source) +{ + int c; + while ((c = source->getc()) != EOF) { + if (c == '*') { + if ((c = source->getc()) == '/') { + break; + } else if (c != EOF) { + source->ungetc(c); + } + } + } + + if (c == EOF) { + cerr << "Reached EOF before closing */ in comment." << endl; + } +} + +//! @brief Parses Source until next non whitespace char is found. +char +CfgParser::parse_skip_blank(CfgParserSource *source) +{ + int c; + while (((c = source->getc()) != EOF) && isspace(c)) + ; + if (c != EOF) { + source->ungetc(c); + } + return c; +} + +//! @brief Creates a CfgParserSource of type type. +CfgParserSource* +CfgParser::source_new(const std::string &name, CfgParserSource::Type type) +{ + CfgParserSource *source = 0; + + // Create CfgParserSource. + _source_name_list.push_back(name); + _source_name_set.insert(name); + switch (type) { + case CfgParserSource::SOURCE_FILE: + source = new CfgParserSourceFile(*_source_name_set.find(name)); + break; + case CfgParserSource::SOURCE_COMMAND: + source = new CfgParserSourceCommand(*_source_name_set.find(name)); + break; + default: + break; + } + + return source; +} + +//! @brief Defines a variable in the _var_map/setenv. +void +CfgParser::variable_define(const std::string &name, const std::string &value) +{ + _var_map[name] = value; + + // If the variable begins with $_ it should update the environment aswell. + if ((name.size() > 2) && (name[1] == '_')) { + setenv(name.c_str() + 2, value.c_str(), 1); + } +} + +//! @brief Expands all $ variables in a string. +void +CfgParser::variable_expand(std::string &var) +{ + bool did_expand; + + do { + did_expand = false; + + string::size_type begin = 0, end = 0; + while ((begin = var.find_first_of('$', end)) != string::npos) { + end = begin + 1; + + // Skip escaped \$ + if ((begin > 0) && (var[begin - 1] == '\\')) { + continue; + } + + // Find end of variable + for (; end != var.size(); ++end) { + if ((isalnum(var[end]) == 0) && (var[end] != '_')) { + break; + } + } + + did_expand = variable_expand_name(var, begin, end) || did_expand; + } + } while (did_expand); +} + +bool +CfgParser::variable_expand_name(std::string &var, + string::size_type begin, string::size_type &end) +{ + bool did_expand = false; + string var_name(var.substr(begin, end - begin)); + + // If the variable starts with _ it is considered an environment + // variable, use getenv to see if it is available + if (var_name.size() > 2 && var_name[1] == '_') { + char *value = getenv(var_name.c_str() + 2); + if (value) { + var.replace(begin, end - begin, value); + end = begin + strlen(value); + did_expand = true; + } else { + cerr << "Trying to use undefined environment variable: " << var_name << endl;; + } + } else { + map::iterator it(_var_map.find(var_name)); + if (it != _var_map.end()) { + var.replace(begin, end - begin, it->second); + end = begin + it->second.size(); + did_expand = true; + } else { + cerr << "Trying to use undefined variable: " << var_name << endl; + } + } + + return did_expand; +} diff --git a/pekwm/CfgParser.hh b/pekwm/CfgParser.hh new file mode 100644 index 0000000..6e3b3dd --- /dev/null +++ b/pekwm/CfgParser.hh @@ -0,0 +1,154 @@ +// +// Copyright © 2005-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +// +// Configuration file parser with file inclusion support and command +// output parsing support. The format being parsed: +// +// $var = "value" +// INCLUDE = "file to include" +// +// section = "name" { +// key = "name" { +// value = "$var" +// } +// } +// + +#ifndef _CFG_PARSER_HH_ +#define _CFG_PARSER_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "CfgParserKey.hh" +#include "CfgParserSource.hh" + +#include +#include +#include +#include +#include +#include +#include + +//! @brief Configuration file parser. +class CfgParser { +public: + //! @brief Entry in parsed data structure. + class Entry { + public: + Entry(const std::string &source_name, int line, + const std::string &name, const std::string &value, + CfgParser::Entry *section=0); + Entry(const Entry &entry); + ~Entry(void); + + std::list::iterator begin(void) { return _entries.begin(); } + std::list::iterator end(void) { return _entries.end(); } + + //! @brief Returns the name. + const std::string &get_name(void) const { return _name; } + //! @brief Returns the value. + const std::string &get_value(void) const { return _value; } + //! @brief Returns the linenumber in the source this was parsed. + int get_line(void) const { return _line; } + //! @brief Returns the name of the source this was parsed. + const std::string &get_source_name(void) const { return _source_name; } + + Entry *add_entry(Entry *entry, bool overwrite=false); + Entry *add_entry(const std::string &source_name, int line, + const std::string &name, const std::string &value, + CfgParser::Entry *section=0, bool overwrite=false); + + //! @brief Returns the sub section. + Entry *get_section(void) { return _section; } + Entry *set_section(Entry *section, bool overwrite=false); + + Entry *find_entry(const std::string &name, bool include_sections=false, const char *value=0); + Entry *find_section(const std::string &name, const char *value=0); + void parse_key_values(std::list::iterator begin, + std::list::iterator end); + + void copy_tree_into(CfgParser::Entry *from, bool overwrite=false); + + //! @brief Matches Entry name agains op_rhs. + bool operator==(const char *rhs) { + return (strcasecmp(rhs, _name.c_str()) == 0); + } + friend std::ostream &operator<<(std::ostream &stream, const CfgParser::Entry &entry); + + private: + std::list _entries; /**< List of entries in section. */ + Entry *_section; /**< Sub-section of node. */ + + std::string _name; /**< Name of node. */ + std::string _value; /**< Value of node. */ + + int _line; + const std::string &_source_name; + }; + + + typedef std::list::iterator iterator; + + CfgParser(void); + ~CfgParser(void); + + /** Return map of file / mtime */ + const std::map &get_file_list(void) const { return _file_list; } + + //! @brief Returns the root Entry node. + Entry *get_entry_root(void) { return _root_entry; } + /** Return true if data parsed included dynamic content such as from COMMAND. */ + bool is_dynamic_content(void) { return _is_dynamic_content; } + + void clear(bool realloc = true); + bool parse(const std::string &src, CfgParserSource::Type type = CfgParserSource::SOURCE_FILE, + bool overwrite = false); + +private: + void parse_source_new(const std::string &name, CfgParserSource::Type type); + bool parse_name(std::string &buf); + void parse_value(CfgParserSource *source, std::string &value); + void parse_entry_finish(std::string &buf, std::string &value); + void parse_entry_finish_standard(std::string &buf, std::string &value); + void parse_entry_finish_template(std::string &name); + void parse_section_finish(std::string &buf, std::string &value); + void parse_comment_line(CfgParserSource *source); + void parse_comment_c(CfgParserSource *source); + char parse_skip_blank(CfgParserSource *source); + + CfgParserSource *source_new(const std::string &name, CfgParserSource::Type type); + + void variable_define(const std::string &name, const std::string &value); + void variable_expand(std::string &var); + bool variable_expand_name(std::string &var, + std::string::size_type begin, std::string::size_type &end); +private: + CfgParserSource *_source; + + std::map _file_list; //!< Map of source, mtime of loaded files. */ + + std::list _source_list; //!< List of sources, for recursive parsing. + std::list _source_name_list; //!< List of source names, to keep track of current source. + std::set _source_name_set; //!< Set of source names, source of memory usage on long-going CfgParser objects. + std::list _section_list; //!< List sections, for recursive parsing. + + std::map _var_map; //!< Map of $VARS + std::map _section_map; //!< Map of Define = ... sections + + Entry *_root_entry; /**< Root Entry. */ + bool _is_dynamic_content; /**< If true, parsed data included command or similar. */ + Entry *_section; /**< Current section. */ + bool _overwrite; /**< Overwrite elements when appending. */ + + static const std::string _root_source_name; //!< Root Entry Source Name. +}; + +#endif // _CFG_PARSER_HH_ diff --git a/pekwm/CfgParserKey.cc b/pekwm/CfgParserKey.cc new file mode 100644 index 0000000..f0a2bbf --- /dev/null +++ b/pekwm/CfgParserKey.cc @@ -0,0 +1,74 @@ +// +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "CfgParserKey.hh" +#include "Util.hh" + +#include +#include + +using std::string; +using std::cerr; +using std::endl; +using std::numeric_limits; + + +//! @brief Parses value and sets _br_set. +//! Boolean true is represented either by case insensitive true or 1. +//! Boolean false is represented either by case insensitive false or 0. +//! @param value Value to parse. +//! @return true on success, else false and _br_set set to _b_default. +void +CfgParserKeyBool::parse_value(const std::string &value) + throw (std::string&) +{ + if ((value == "1") || ! strcasecmp(value.c_str(), "TRUE")) { + _set = true; + } else if ((value == "0") || ! strcasecmp(value.c_str(), "FALSE")) { + _set = false; + } else { + _set = _default; + throw string("not bool value"); + } +} + +//! @brief Parses value and sets _set. +//! @param value Value to parse. +//! @return true on success, else false and _set set to _default. +void +CfgParserKeyString::parse_value(const std::string &value) + throw (std::string&) +{ + _set = value; + + if ((_length_min != numeric_limits::min()) + && (static_cast(_set.size()) < _length_min)) { + _set = _default; + throw string("string too short, min length " + Util::to_string(_length_min)); + } + if ((_length_max != numeric_limits::max()) + && (static_cast(_set.size()) > _length_max)) { + _set = _default; + throw string("string too long, max length " + Util::to_string(_length_max)); + } +} + +//! @brief Parses value and sets _set. +//! @param value Value to parse. +//! @return true on success, else false and _set set to _default. +void +CfgParserKeyPath::parse_value(const std::string &value) + throw (std::string&) +{ + if (value.size()) { + _set = value; + Util::expandFileName(_set); + } else { + _set = _default; + throw string("path too short"); + } +} diff --git a/pekwm/CfgParserKey.hh b/pekwm/CfgParserKey.hh new file mode 100644 index 0000000..71c7aad --- /dev/null +++ b/pekwm/CfgParserKey.hh @@ -0,0 +1,189 @@ +// +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _CFG_PARSER_KEY_HH_ +#define _CFG_PARSER_KEY_HH_ + +#include +#include +#include + +#include "Util.hh" + +//! @brief CfgParserKey value type. +enum CfgParserKeyType { + KEY_SECTION, //!< Subsection. + KEY_BOOL, //!< Boolean value. + KEY_NUMERIC, //!< Integer value. + KEY_STRING, //!< String value. + KEY_PATH //!< Path value. +}; + +//! @brief CfgParserKey base class. +class CfgParserKey { +public: + //! @brief CfgParserKey constructor. + CfgParserKey(const char *name) : _name(name) { } + //! @brief CfgParserKey destructor. + virtual ~CfgParserKey(void) { } + + //! @brief Returns Key name. + const char *get_name(void) const { return _name; } + //! @brief Returns Key type. + const CfgParserKeyType get_type(void) const { return _type; } + + //! @brief Parses value and sets Key value. + virtual void parse_value(const std::string &value) throw (std::string&) { } + +protected: + CfgParserKeyType _type; //!< Key type. + const char *_name; //!< Key name. +}; + +/** + * CfgParserKey numeric type with minimum, maximum and default + * value. The type must be available in numeric_limits. + */ +template +class CfgParserKeyNumeric : public CfgParserKey { +public: + /** + * CfgParserKeyNumeric constructor, sets default values + * + * @param name Name of the key. + * @param set Variable to store parsed value in. + * @param default_val Default value for key, defaults to 0. + * @param value_min Minimum value for key, defaults to limits::min(). + * @param value_min Maximum value for key, defaults to limits::max(). + */ + CfgParserKeyNumeric(const char *name, T &set, const T default_val = 0, + const T value_min = std::numeric_limits::min(), + const T value_max = std::numeric_limits::max()) + : CfgParserKey(name), + _set(set), _default(default_val), + _value_min(value_min), _value_max(value_max) + { + _type = KEY_NUMERIC; + } + + /** + * CfgParserKeyNumeric destructor. + */ + virtual ~CfgParserKeyNumeric(void) { } + + /** + * Parses and store integer value. + * + * @param value Reference to string representing integer value. + */ + virtual void + parse_value(const std::string &value_str) + throw (std::string&) + { + double value; + char *endptr; + + // Get long value. + value = strtod(value_str.c_str(), &endptr); + + // Check for validity, 0 is returned on failiure with endptr set to the + // beginning of the string, else we are (semi) ok. + if ((value == 0) && (endptr == value_str.c_str())) { + _set = _default; + + } else { + T value_for_type = static_cast(value); + + if (value_for_type < _value_min) { + _set = _value_min; + throw std::string("value to low, min value " + Util::to_string(_value_min)); + } if (value_for_type > _value_max) { + _set = _value_max; + throw std::string("value to high, max value " + Util::to_string(_value_max)); + } + + _set = value_for_type; + } + } + +private: + T &_set; /**< Reference to store parsed value in. */ + const T _default; /**< Default value. */ + const T _value_min; /**< Minimum value. */ + const T _value_max; /**< Maximum value. */ +}; + +//! @brief CfgParser Key boolean value parser. +class CfgParserKeyBool : public CfgParserKey { +public: + //! @brief CfgParserKeyBool constructor. + CfgParserKeyBool(const char *name, + bool &set, const bool default_val = false) + : CfgParserKey(name), + _set(set), _default(default_val) + { + _type = KEY_BOOL; + } + //! @brief CfgParserKeyBool destructor. + virtual ~CfgParserKeyBool(void) { } + + virtual void parse_value(const std::string &value) throw (std::string&); + +private: + bool &_set; //! Reference to stored parsed value in. + const bool _default; //! Default value. +}; + +//! @brief CfgParser Key string value parser. +class CfgParserKeyString : public CfgParserKey { +public: + //! @brief CfgParserKeyString constructor. + CfgParserKeyString(const char *name, + std::string &set, const std::string default_val = "", + const int length_min = std::numeric_limits::min(), + const int length_max = std::numeric_limits::max()) + : CfgParserKey(name), + _set(set), _default(default_val), + _length_min(length_min), _length_max(length_max) + { + _type = KEY_STRING; + } + //! @brief CfgParserKeyString destructor. + virtual ~CfgParserKeyString(void) { } + + virtual void parse_value(const std::string &value) throw (std::string&); + +private: + std::string &_set; //!< Reference to store parsed value in. + const std::string _default; //!< Default value. + const int _length_min; //!< Minimum lenght of string. + const int _length_max; //!< Maximum length of string. +}; + +//! @brief CfgParser Key path parser. +class CfgParserKeyPath : public CfgParserKey { +public: + //! @brief CfgParserKeyPath constructor. + CfgParserKeyPath(const char *name, + std::string &set, const std::string default_val = "") + : CfgParserKey(name), + _set(set), _default(default_val) + { + _type = KEY_PATH; + Util::expandFileName(_default); + } + //! @brief CfgParserKeyPath destructor. + virtual ~CfgParserKeyPath(void) { } + + virtual void parse_value(const std::string &value) throw (std::string&); + +private: + std::string &_set; //!< Reference to store parsed value in. + std::string _default; //!< Default value. +}; + +#endif // _CFG_PARSER_KEY_HH_ diff --git a/pekwm/CfgParserSource.cc b/pekwm/CfgParserSource.cc new file mode 100644 index 0000000..2b3659f --- /dev/null +++ b/pekwm/CfgParserSource.cc @@ -0,0 +1,148 @@ +// +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "CfgParserSource.hh" +#include "Util.hh" + +#include +#include +#include + +extern "C" { +#include +#include +} + +using std::cerr; +using std::endl; +using std::string; +using std::fopen; +using std::fclose; +using std::exit; + +unsigned int CfgParserSourceCommand::_sigaction_counter = 0; + +/** + * Open file based configuration source. + */ +bool +CfgParserSourceFile::open(void) + throw (std::string&) +{ + if (_file) { + throw string("TRYING TO OPEN ALLREADY OPEN SOURCE"); + } + + _file = fopen(_name.c_str(), "r"); + if (! _file) { + throw string("failed to open file " + _name); + } + + return true; +} + +void +CfgParserSourceFile::close(void) + throw (std::string&) +{ + if (! _file) { + throw string("trying to close already closed source"); + } + + fclose(_file); + _file = 0; +} + +/** + * Run command and treat output as configuration source. + */ +bool +CfgParserSourceCommand::open(void) + throw (std::string&) +{ + int fd[2]; + int status; + + status = pipe(fd); + if (status == -1) { + return false; + } + + // Remove signal handler while parsing as otherwise reading from the + // pipe will break sometimes. + if (_sigaction_counter++ == 0) { + struct sigaction action; + + action.sa_handler = SIG_DFL; + action.sa_mask = sigset_t(); + action.sa_flags = 0; + + sigaction(SIGCHLD, &action, &_sigaction); + } + + _pid = fork(); + if (_pid == -1) { // Error + return false; + + } else if (_pid == 0) { // Child + dup2(fd[1], STDOUT_FILENO); + + ::close(fd[0]); + ::close(fd[1]); + + execlp("/bin/sh", "sh", "-c", _name.c_str(), (char *) 0); + + // PRINT ERROR + + ::close (STDOUT_FILENO); + + exit (1); + + } else { // Parent + + ::close (fd[1]); + + _file = ::fdopen(fd[0], "r"); + } + return true; +} + +/** + * Close source, wait for child process to finish. + */ +void +CfgParserSourceCommand::close(void) + throw (std::string&) +{ + if (_sigaction_counter < 1) { + return; + } + _sigaction_counter--; + + // Close source. + fclose(_file); + + // Wait for process. + int status; + int pid_status; + + status = waitpid(_pid, &pid_status, 0); + + // If no other open CfgParserSourceCommand open, restore sigaction. + if (_sigaction_counter == 0) { + sigaction(SIGCHLD, &_sigaction, 0); + } + + // Wait failed, throw error + if (status == -1) { + throw string("failed to wait for pid " + Util::to_string(_pid)); + } +} diff --git a/pekwm/CfgParserSource.hh b/pekwm/CfgParserSource.hh new file mode 100644 index 0000000..bf71822 --- /dev/null +++ b/pekwm/CfgParserSource.hh @@ -0,0 +1,125 @@ +// +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _CFG_PARSER_SOURCE_HH_ +#define _CFG_PARSER_SOURCE_HH_ + +#include +#include + +extern "C" { +#include +#include +#include +} + +/** + * Base class for configuration sources defining the interface and + * common methods. + */ +class CfgParserSource +{ +public: + //! @brief Source type description. + enum Type { + SOURCE_FILE, //!< Source from filesystem accesible file. + SOURCE_COMMAND, //!< Source from command output. + SOURCE_VIRTUAL //!< Source base type. + }; + + /** + * CfgParserSource constructor, just set default values. + */ + CfgParserSource(const std::string &source) + : _file(0), _name(source), + _type(SOURCE_VIRTUAL), _line(0), _is_dynamic(false) { } + virtual ~CfgParserSource (void) { } + + /** + * Gets a character from _file, increments line count if \n. + */ + inline int getc(void) { + int c = std::fgetc(_file); + if (c == '\n') { + ++_line; + } + return c; + } + + /** + * Returns a character to _op_file, decrements line count if \n. + */ + inline void ungetc(int c) { + std::ungetc(c, _file); + if (c == '\n') { + --_line; + } + } + + /**< Return name of source. */ + const std::string &get_name(void) const { return _name; } + /**< return type of source. */ + CfgParserSource::Type get_type(void) const { return _type; } + /**< Get current line from source. */ + unsigned int get_line(void) const { return _line; } + /**< Return true if current source is dynamic. */ + bool is_dynamic(void) const { return _is_dynamic; } + + virtual bool open(void) throw (std::string&) { return false; } + virtual void close(void) throw (std::string&) { } + +protected: + std::FILE *_file; /**< FILE object source is reading from. */ + const std::string &_name; /**< Name of source. */ + CfgParserSource::Type _type; /**< Type of source. */ + unsigned int _line; /**< Line number. */ + bool _is_dynamic; /**< Set to true if source has dynamic content. */ +}; + +/** + * File based configuration source, reads data from a plain file on + * disk. + */ +class CfgParserSourceFile : public CfgParserSource +{ +public: + CfgParserSourceFile (const std::string &source) + : CfgParserSource(source) + { + _type = SOURCE_FILE; + } + virtual ~CfgParserSourceFile (void) { } + + virtual bool open(void) throw (std::string&); + virtual void close(void) throw (std::string&); +}; + +/** + * Command based configuration source, executes a commands and parses + * the output. + */ +class CfgParserSourceCommand : public CfgParserSource +{ +public: + CfgParserSourceCommand(const std::string &source) + : CfgParserSource (source) + { + _type = SOURCE_COMMAND; + _is_dynamic = true; + } + virtual ~CfgParserSourceCommand(void) { } + + virtual bool open(void) throw (std::string&); + virtual void close(void) throw (std::string&); + +private: + pid_t _pid; /**< Process id of command generating output. */ + struct sigaction _sigaction; /**< sigaction for restore. */ + static unsigned int _sigaction_counter; /**< Counts open. */ +}; + +#endif // _CFG_PARSER_SOURCE_HH_ diff --git a/pekwm/Client.cc b/pekwm/Client.cc new file mode 100644 index 0000000..04c96bb --- /dev/null +++ b/pekwm/Client.cc @@ -0,0 +1,1958 @@ +// +// Client.cc for pekwm +// Copyright © 2002-2009 Claes Nästén +// +// client.cc for aewm++ +// Copyright (C) 2000 Frank Hale +// http://sapphire.sourceforge.net/ +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include + +extern "C" { +#include +#include +#include +#ifdef HAVE_SHAPE +#include +#endif // HAVE_SHAPE +} + +#include "Compat.hh" // setenv, unsetenv +#include "PWinObj.hh" +#include "PDecor.hh" // PDecor::TitleItem +#include "Client.hh" +#include "PScreen.hh" +#include "Config.hh" +#include "KeyGrabber.hh" +#include "Theme.hh" +#include "AutoProperties.hh" +#include "Frame.hh" +#include "Workspaces.hh" +#include "WindowManager.hh" +#include "PTexturePlain.hh" +#include "PImageIcon.hh" +#include "TextureHandler.hh" + +#include "PMenu.hh" +#include "WORefMenu.hh" + +using std::cerr; +using std::endl; +using std::find; +using std::list; +using std::string; +using std::vector; +using std::wstring; + +list Client::_client_list = list(); +vector Client::_clientid_list = vector(); + +Client::Client(Window new_client, bool is_new) + : PWinObj(), + _id(0), _size(0), + _transient(None), _strut(0), _icon(0), + _pid(0), _is_remote(false), _class_hint(0), + _alive(false), _marked(false), + _send_focus_message(false), _send_close_message(false), + _wm_hints_input(true), _cfg_request_lock(false), + _shaped(false), _extended_net_name(false) +{ + // PWinObj attributes, required by validate etc. + _window = new_client; + _type = WO_CLIENT; + + // Construct the client + PScreen::instance()->grabServer(); + if (! validate() || ! getAndUpdateWindowAttributes()) { + PScreen::instance()->ungrabServer(true); + return; + } + + // Get unique Client id + _id = findClientID(); + _title.setId(_id); + _title.infoAdd(PDecor::TitleItem::INFO_ID); + + if (PScreen::instance()->hasExtensionShape()) { +#ifdef HAVE_SHAPE + XShapeSelectInput(_dpy, _window, ShapeNotifyMask); +#endif // HAVE_SHAPE + } + + XAddToSaveSet(_dpy, _window); + XSetWindowBorderWidth(_dpy, _window, 0); + + // Load the Class hint before loading the autoprops and + // getting client title as we search for TitleRule in the autoprops + _class_hint = new ClassHint(); + readClassRoleHints(); + + getWMNormalHints(); + readName(); + readIconName(); + + // cyclic dependency, getting the name requires quiering autoprops + _class_hint->title = _title.getReal(); + + // Get Autoproperties before EWMH as we need the cfg_deny + // property, however the _transient hint needs to be setup to + // avoid auto-grouping to be to greedy. + getTransientForHint(); + + AutoProperty *ap = readAutoprops(WindowManager::instance()->isStartup() + ? APPLY_ON_NEW : APPLY_ON_START); + + readHints(); + + // We need to set the state before acquiring a frame, + // so that Frame's state can match the state of the Client. + setInitialState(); + + findOrCreateFrame(ap); + + // Grab keybindings and mousebutton actions + KeyGrabber::instance()->grabKeys(_window); + grabButtons(); + + // Tell the world about our state + updateEwmhStates(); + + PScreen::instance()->ungrabServer(true); + + setMappedStateAndFocus(is_new, ap); + + _alive = true; + + findAndRaiseIfTransient(); + + // Finished creating the client, so now adding it to the client list. + woListAdd(this); + _wo_map[_window] = this; + _client_list.push_back(this); +} + +//! @brief Client destructor +Client::~Client(void) +{ + // Remove from lists + if (_transient) { + _transient->_transient_clients.remove(this); + _transient->removeObserver(this); + } + _wo_map.erase(_window); + woListRemove(this); + _client_list.remove(this); + + returnClientID(_id); + + PScreen::instance()->grabServer(); + + // removes gravity and moves it back to root if we are alive + bool focus = false; + if (_parent && (_parent->getType() == PWinObj::WO_FRAME)) { + focus = _parent->isFocused(); + static_cast(_parent)->removeChild(this); + } + + // Focus the parent if we had focus before + if (focus && _transient) { + Frame *trans_frame = static_cast(_transient->getParent()); + if (trans_frame->getActiveChild() == _transient) { + trans_frame->giveInputFocus(); + } + } + + // Clean up if the client still is alive, it'll be dead all times + // except when we exit pekwm + if (_alive) { + XUngrabButton(_dpy, AnyButton, AnyModifier, _window); + KeyGrabber::instance()->ungrabKeys(_window); + XRemoveFromSaveSet(_dpy, _window); + PWinObj::mapWindow(); + } + + // free names and size hint + if (_size) { + XFree(_size); + } + + removeStrutHint(); + + if (_class_hint) { + delete _class_hint; + } + + if (_icon) { + TextureHandler::instance()->returnTexture(_icon); + _icon = 0; + } + + PScreen::instance()->ungrabServer(true); +} + +/** + * Read basic window attributes including geometry and update the + * window attributes being listened to. Returns false if the client + * disappears during the check. + */ +bool +Client::getAndUpdateWindowAttributes(void) +{ + XWindowAttributes attr; + if (! XGetWindowAttributes(_dpy, _window, &attr)) { + return false; + } + _gm.x = attr.x; + _gm.y = attr.y; + _gm.width = attr.width; + _gm.height = attr.height; + + _cmap = attr.colormap; + _size = XAllocSizeHints(); + + XSetWindowAttributes sattr; + sattr.event_mask = + PropertyChangeMask|StructureNotifyMask|FocusChangeMask; + sattr.do_not_propagate_mask = + ButtonPressMask|ButtonReleaseMask|ButtonMotionMask; + + // We don't want these masks to be propagated down to the frame + XChangeWindowAttributes(_dpy, _window, CWEventMask|CWDontPropagate, &sattr); + + return true; +} + +/** + * Find frame for client based on tagging, hints and + * autoproperties. Create a new one if not found and add the client. + */ +void +Client::findOrCreateFrame(AutoProperty *autoproperty) +{ + if (! _parent) { + findTaggedFrame(); + } + if (! _parent) { + findPreviousFrame(); + } + + // Apply Autoproperties again to override EWMH state. It's done twice as + // we need the cfg_deny property when reading the EWMH state. + if (autoproperty != 0) { + applyAutoprops(autoproperty); + if (! _parent) { + findAutoGroupFrame(autoproperty); + } + } + + // if we don't have a frame already, create a new one + if (! _parent) { + _parent = new Frame(this, autoproperty); + } +} + +/** + * Find from client from for the currently tagged client. + */ +bool +Client::findTaggedFrame(void) +{ + if (! WindowManager::instance()->isStartup()) { + return false; + } + + // Check for tagged frame + Frame *frame = Frame::getTagFrame(); + if (frame && frame->isMapped()) { + _parent = frame; + frame->addChild(this); + + if (! Frame::getTagBehind()) { + frame->activateChild(this); + } + } + + return _parent != 0; +} + +/** + * Find frame for client on PEKWM_FRAME_ID hint. + */ +bool +Client::findPreviousFrame(void) +{ + if (WindowManager::instance()->isStartup()) { + return false; + } + + long id; + if (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_ID), id)) { + _parent = Frame::findFrameFromID(id); + if (_parent) { + Frame *frame = static_cast(_parent); + frame->addChildOrdered(this); + if (getPekwmFrameActive()) { + frame->activateChild(this); + } + } + } + + return _parent != 0; +} + +/** + * Find frame for client based on autoproperties. + */ +bool +Client::findAutoGroupFrame(AutoProperty *autoproperty) +{ + if (autoproperty->group_size < 0) { + return false; + } + + Frame* frame = WindowManager::instance()->findGroup(autoproperty); + if (frame) { + frame->addChild(this); + + if (! autoproperty->group_behind) { + frame->activateChild(this); + } + if (autoproperty->group_raise) { + frame->raise(); + } + } + + return _parent != 0; +} + +/** + * Get the client state and set iconified and mapped flags. + */ +void +Client::setInitialState(void) +{ + // Set state either specified in hint + ulong initial_state = readWmHints(); + if (getWmState() == IconicState) { + _iconified = true; + } + + if (_iconified || initial_state == IconicState) { + _iconified = true; + _mapped = true; + unmapWindow(); + } else { + setWmState(initial_state); + } +} + +/** + * Ensure the Client is (un) mapped and give input focus if requested. + */ +void +Client::setMappedStateAndFocus(bool is_new, AutoProperty *autoproperty) +{ + // Make sure the window is mapped, this is done after it has been + // added to the decor/frame as otherwise IsViewable state won't + // be correct and we don't know whether or not to place the window + if (! _iconified && _parent->isMapped()) { + PWinObj::mapWindow(); + } + + // Let us hear what autoproperties has to say about focusing + bool do_focus = is_new ? Config::instance()->isFocusNew() : false; + if (is_new && autoproperty && autoproperty->isMask(AP_FOCUS_NEW)) { + do_focus = autoproperty->focus_new; + } + + // Only can give input focus to mapped windows + if (_parent->isMapped()) { + // Ordinary focus + if (do_focus) { + _parent->giveInputFocus(); + + // Check if we are transient, and if we want to focus + } else if (_transient && _transient->isFocused() + && Config::instance()->isFocusNewChild()) { + _parent->giveInputFocus(); + } + } +} + +/** + * Re-lookup transient client if window is set but not client, raise over + * transient for window if found/set. + */ +void +Client::findAndRaiseIfTransient(void) +{ + if (_transient_window != None && ! _transient) { + getTransientForHint(); + } + + if (_transient) { + Frame *frame = static_cast(getParent()); + Frame *frame_transient = static_cast(_transient->getParent()); + if (frame->getActiveChild() == this) { + frame->setLayer(frame_transient->getLayer() + 1); + frame->raise(); + } + } +} + +// START - PWinObj interface. + +//! @brief Maps the window. +void +Client::mapWindow(void) +{ + if (_mapped) { + return; + } + + if (_iconified) { + _iconified = false; + setWmState(NormalState); + updateEwmhStates(); + } + + if(! _transient) { + // Unmap our transient windows if we have any + mapOrUnmapTransients(_window, false); + } + + XSelectInput(_dpy, _window, NoEventMask); + PWinObj::mapWindow(); + XSelectInput(_dpy, _window, + PropertyChangeMask|StructureNotifyMask|FocusChangeMask); +} + + +//! @brief Sets the client to WithdrawnState and unmaps it. +void +Client::unmapWindow(void) +{ + if (! _mapped) { + return; + } + + if (_iconified) { + // Set the state of the window + setWmState(IconicState); + updateEwmhStates(); + } + + XSelectInput(_dpy, _window, NoEventMask); + PWinObj::unmapWindow(); + XSelectInput(_dpy, _window, PropertyChangeMask|StructureNotifyMask|FocusChangeMask); +} + +//! @brief Iconifies the client and adds it to the iconmenu +void +Client::iconify(void) +{ + if (_iconified) { + return; + } + + _iconified = true; + if (! _transient) { + mapOrUnmapTransients(_window, true); + } + + unmapWindow(); +} + +//! @brief Toggle client sticky state +void +Client::stick(void) +{ + PWinObj::stick(); + + updateEwmhStates(); +} + +//! @brief Update the position variables. +void +Client::move(int x, int y) +{ + bool request = ((_gm.x != x) || (_gm.y != y)); + + _gm.x = x; + _gm.y = y; + + if (request) { + configureRequestSend(); + } +} + +//! @brief Resizes the client window to specified size. +void +Client::resize(uint width, uint height) +{ + bool request = ((_gm.width != width) || (_gm.height != height)); + + PWinObj::resize(width, height); + + if (request) { + configureRequestSend(); + } +} + +//! @brief Move and resizes the client window to specified size. +void +Client::moveResize(int x, int y, uint width, uint height) +{ + bool request = ((_gm.x != x) || (_gm.y != y) || (_gm.width != width) || (_gm.height != height)); + + _gm.x = x; + _gm.y = y; + + PWinObj::resize(width, height); + + if (request) { + configureRequestSend(); + } +} + +//! @brief Sets the workspace and updates the _NET_WM_DESKTOP hint. +void +Client::setWorkspace(uint workspace) +{ + if (workspace != NET_WM_STICKY_WINDOW) { + if (workspace >= Workspaces::instance()->size()) { + workspace = Workspaces::instance()->size() - 1; + } + _workspace = workspace; + + if (_sticky) { + AtomUtil::setLong(_window, Atoms::getAtom(NET_WM_DESKTOP), NET_WM_STICKY_WINDOW); + } else { + AtomUtil::setLong(_window, Atoms::getAtom(NET_WM_DESKTOP), _workspace); + } + } +} + +//! @brief Gives the Client input focus. +void +Client::giveInputFocus(void) +{ + if (_wm_hints_input) { + PWinObj::giveInputFocus(); + } + + sendTakeFocusMessage(); +} + +//! @brief Reparents and sets _parent member, filtering unmap events +void +Client::reparent(PWinObj *parent, int x, int y) +{ + XSelectInput(_dpy, _window, NoEventMask); + PWinObj::reparent(parent, x, y); + _gm.x = parent->getX() + x; + _gm.y = parent->getY() + y; + XSelectInput(_dpy, _window, + PropertyChangeMask|StructureNotifyMask|FocusChangeMask); +} + +ActionEvent* +Client::handleUnmapEvent(XUnmapEvent *ev) +{ + if ((ev->window != ev->event) && (ev->send_event != true)) { + return 0; + } + + // ICCCM 4.1.4 advices the window manager to trigger the transition to + // Withdrawn state on real and synthetic UnmapNotify events. + setWmState(WithdrawnState); + + // Extended Window Manager Hints 1.3 specifies that a window manager + // should remove the _NET_WM_STATE property when a window is withdrawn. + AtomUtil::unsetProperty(_window, Atoms::getAtom(STATE)); + + // Extended Window Manager Hints 1.3 specifies that a window manager + // should remove the _NET_WM_DESKTOP property when a window is withdrawn. + // (to allow legacy applications to reuse a withdrawn window) + AtomUtil::unsetProperty(_window, Atoms::getAtom(NET_WM_DESKTOP)); + +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "Client(" << this << ")::handleUnmapEvent(" << ev << ")" << endl + << " *** unmapping client, window: " << _window << endl; +#endif // DEBUG + + // FIXME: Listen mask should change as this doesn't work? + _alive = false; + delete this; + + return 0; +} + +ActionEvent* +Client::handleMapRequest(XMapRequestEvent *ev) +{ + if (_parent && dynamic_cast(_parent)) { + dynamic_cast(_parent)->deiconify(); + } + return 0; +} + +// END - PWinObj interface. + +// START - Observer interface + +void +Client::notify(Observable *observable, Observation *observation) +{ + if (observation) { + LayerObservation *layer_observation = dynamic_cast(observation); + if (layer_observation) { + setLayer(layer_observation->layer + 1); + + Frame *frame = static_cast(getParent()); + if (frame->getActiveChild() == this) { + frame->setLayer(layer_observation->layer + 1); + frame->raise(); + } + } + } else { + Client *client = static_cast(observable); + if (client == _transient) { + _transient = 0; + } else { + _transient_clients.remove(client); + } + } +} + +// END - Observer interface + +//! @brief Finds the Client which holds the Window w. +//! @param win Window to search for. +//! @return Pointer to the client if found, else 0 +Client* +Client::findClient(Window win) +{ + // Validate input window. + if ((win == None) || (win == PScreen::instance()->getRoot())) { + return 0; + } + + list::iterator it(_client_list.begin()); + for (; it != _client_list.end(); ++it) { + if (win == (*it)->getWindow() + || ((*it)->getParent() && (*((*it)->getParent()) == win))) { + return (*it); + } + } + + return 0; +} + +//! @brief Finds the Client of Window win. +//! @param win Window to search for. +Client* +Client::findClientFromWindow(Window win) +{ + // Validate input window. + if (! win || win == PScreen::instance()->getRoot()) { + return 0; + } + + list::iterator it(_client_list.begin()); + for(; it != _client_list.end(); ++it) { + if (win == (*it)->getWindow()) { + return (*it); + } + } + + return 0; +} + +//! @brief Finds Client with equal ClassHint. +//! @param class_hint ClassHint to search for. +//! @return Client if found, else 0. +Client* +Client::findClientFromHint(const ClassHint *class_hint) +{ + list::iterator it(_client_list.begin()); + for (; it != _client_list.end(); ++it) { + if (*class_hint == *(*it)->getClassHint()) { + return *it; + } + } + + return 0; +} + +//! @brief Finds Client with id. +//! @param id ID to search for. +//! @return Client if found, else 0. +Client* +Client::findClientFromID(uint id) +{ + list::iterator it(_client_list.begin()); + for (; it != _client_list.end(); ++it) { + if ((*it)->getClientID() == id) { + return *it; + } + } + + return 0; +} + +/** + * Insert all clients with the transient for set to win. + */ +void +Client::findFamilyFromWindow(std::list &client_list, Window win) +{ + list::iterator it(Client::client_begin()); + for (; it != Client::client_end(); ++it) { + if ((*it)->getTransientClientWindow() == win) { + client_list.push_back(*it); + } + } +} + + +/** + * (Un)Maps all windows having transient_for set to win + */ +void +Client::mapOrUnmapTransients(Window win, bool hide) +{ + list client_list; + findFamilyFromWindow(client_list, win); + + list::iterator it(client_list.begin()); + for (; it != client_list.end(); ++it) { + if (static_cast((*it)->getParent())->getActiveChild() == *it) { + if (hide) { + (*it)->getParent()->iconify(); + } else { + (*it)->getParent()->mapWindow(); + } + } + } +} + +//! @brief Checks if the window has any Destroy or Unmap notifys. +bool +Client::validate(void) +{ + XSync(_dpy, false); + + XEvent ev; + if (XCheckTypedWindowEvent(_dpy, _window, DestroyNotify, &ev) + || XCheckTypedWindowEvent(_dpy, _window, UnmapNotify, &ev)) { + XPutBackEvent(_dpy, &ev); + return false; + } + + return true; +} + +//! @brief Checks if the window has attribute IsViewable set +bool +Client::isViewable(void) +{ + XWindowAttributes attr; + XGetWindowAttributes(_dpy, _window, &attr); + + return (attr.map_state == IsViewable); +} + +//! @brief Grabs all the mouse button actions on the client. +void +Client::grabButtons(void) +{ + // Make sure we don't have any buttons grabbed. + XUngrabButton(_dpy, AnyButton, AnyModifier, _window); + + list *actions = Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_CHILD_FRAME); + list::iterator it(actions->begin()); + for (; it != actions->end(); ++it) { + if ((it->type == MOUSE_EVENT_PRESS) || (it->type == MOUSE_EVENT_RELEASE)) { + // No need to grab mod less events, replied with the frame + if ((it->mod == 0) || (it->mod == MOD_ANY)) { + continue; + } + + grabButton(it->sym, it->mod, + ButtonPressMask|ButtonReleaseMask, + _window, None); + } else if (it->type == MOUSE_EVENT_MOTION) { + // FIXME: Add support for MOD_ANY + grabButton(it->sym, it->mod, + ButtonPressMask|ButtonReleaseMask|ButtonMotionMask, + _window, None); + } + } +} + +//! @brief Sets CfgDeny state on Client +void +Client::setStateCfgDeny(StateAction sa, uint deny) +{ + if (! ActionUtil::needToggle(sa, _state.cfg_deny&deny)) { + return; + } + + if (_state.cfg_deny&deny) { + _state.cfg_deny &= ~deny; + } else { + _state.cfg_deny |= deny; + } +} + +/** + * Read "all" hints required during creation of Client. + */ +void +Client::readHints(void) +{ + readMwmHints(); + readEwmhHints(); + readPekwmHints(); + readIcon(); + readClientPid(); + readClientRemote(); + getWMProtocols(); +} + +/** + * Read WM Hints, return the initial state of the window. + */ +ulong +Client::readWmHints(void) +{ + ulong initial_state = NormalState; + XWMHints* hints = XGetWMHints(_dpy, _window); + if (hints) { + // get the input focus mode + if (hints->flags&InputHint) { // FIXME: More logic needed + _wm_hints_input = hints->input; + } + + // Get initial state of the window + if (hints->flags&StateHint) { + initial_state = hints->initial_state; + } + + XFree(hints); + } + return initial_state; +} + +/** + * Read the WM_CLASS hint and set information on the _class_hint used + * in matching auto properties. + */ +void +Client::readClassRoleHints(void) +{ + // class hint + XClassHint class_hint; + if (XGetClassHint(_dpy, _window, &class_hint)) { + _class_hint->h_name = Util::to_wide_str(class_hint.res_name); + _class_hint->h_class = Util::to_wide_str(class_hint.res_class); + XFree(class_hint.res_name); + XFree(class_hint.res_class); + } + + // wm window role + string role; + AtomUtil::getString(_window, Atoms::getAtom(WM_WINDOW_ROLE), role); + + _class_hint->h_role = Util::to_wide_str(role); +} + +//! @brief Loads the Clients state from EWMH atoms. +void +Client::readEwmhHints(void) +{ + // which workspace do we belong to? + long workspace = -1; + AtomUtil::getLong(_window, Atoms::getAtom(NET_WM_DESKTOP), workspace); + if (workspace < 0) { + _workspace = Workspaces::instance()->getActive(); + AtomUtil::setLong(_window, Atoms::getAtom(NET_WM_DESKTOP), _workspace); + } else { + _workspace = workspace; + } + + // try to figure out what kind of window we are and alter it accordingly + int items; + Atom *atoms = 0; + + AtomName window_type = WINDOW_TYPE; + atoms = (Atom*) AtomUtil::getEwmhPropData(_window, Atoms::getAtom(WINDOW_TYPE), XA_ATOM, items); + if (atoms) { + for (int i = 0; window_type == WINDOW_TYPE && i < items; ++i) { + if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_DESKTOP)) { + window_type = WINDOW_TYPE_DESKTOP; + } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_DOCK)) { + window_type = WINDOW_TYPE_DOCK; + } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_TOOLBAR)) { + window_type = WINDOW_TYPE_TOOLBAR; + } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_MENU)) { + window_type = WINDOW_TYPE_MENU; + } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_UTILITY)) { + window_type = WINDOW_TYPE_UTILITY; + } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_DIALOG)) { + window_type = WINDOW_TYPE_DIALOG; + } else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_SPLASH)) { + window_type = WINDOW_TYPE_SPLASH; + } + } + + XFree(atoms); + } + + // Set window type to WINDOW_TYPE_NORMAL if it did not match + if (window_type == WINDOW_TYPE) { + window_type = WINDOW_TYPE_NORMAL; + AtomUtil::setAtom(_window, Atoms::getAtom(WINDOW_TYPE), Atoms::getAtom(WINDOW_TYPE_NORMAL)); + } + + // Apply autoproperties for window type + AutoProperty *auto_property = AutoProperties::instance()->findWindowTypeProperty(window_type); + if (auto_property) { + applyAutoprops(auto_property); + } + + // The _NET_WM_STATE overrides the _NET_WM_TYPE + NetWMStates win_states; + if (getEwmhStates(win_states)) { + if (win_states.hidden) _iconified = true; + if (win_states.shaded) _state.shaded = true; + if (win_states.max_vert) _state.maximized_vert = true; + if (win_states.max_horz) _state.maximized_horz = true; + if (win_states.skip_taskbar) _state.skip |= SKIP_TASKBAR; + if (win_states.skip_pager) _state.skip |= SKIP_PAGER; + if (win_states.sticky) _sticky = true; + if (win_states.above) { + setLayer(LAYER_ABOVE_DOCK); + } + if (win_states.below) { + setLayer(LAYER_BELOW); + } + if (win_states.fullscreen) _state.fullscreen = true; + } + + // check if we have a strut + getStrutHint(); +} + +//! @brief Loads the Clients state from MWM atoms. +void +Client::readMwmHints(void) +{ + MwmHints *mwm_hints = getMwmHints(_window); + + if (mwm_hints) { + if (mwm_hints->flags&MWM_HINTS_FUNCTIONS) { + bool state = ! (mwm_hints->functions&MWM_FUNC_ALL); + + _actions.resize = (mwm_hints->functions&MWM_FUNC_RESIZE) ? state : ! state; + _actions.move = (mwm_hints->functions&MWM_FUNC_MOVE) ? state : ! state; + _actions.minimize = (mwm_hints->functions&MWM_FUNC_ICONIFY) ? state : ! state; + _actions.close = (mwm_hints->functions&MWM_FUNC_CLOSE) ? state : ! state; + _actions.maximize_vert = (mwm_hints->functions&MWM_FUNC_MAXIMIZE) ? state : ! state; + _actions.maximize_horz = (mwm_hints->functions&MWM_FUNC_MAXIMIZE) ? state : ! state; + } + + // Check decoration flags + if (mwm_hints->flags & MWM_HINTS_DECORATIONS + && ! (mwm_hints->decorations & MWM_DECOR_ALL)) { + if (! (mwm_hints->decorations & MWM_DECOR_TITLE)) { + setTitlebar(false); + } + if (! (mwm_hints->decorations & MWM_DECOR_BORDER)) { + setBorder(false); + } + + // Do not handle HANDLE, MENU, ICONFIY or MAXIMIZE. Maybe + // one should set the allowed actions for the client based + // on this but that might be annoying so ignoring these. + } + + XFree(mwm_hints); + } +} + +//! @brief Reads non-standard pekwm hints +void +Client::readPekwmHints(void) +{ + long value; + string str; + + // Get decor state + if (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_DECOR), value)) { + _state.decor = value; + } + // Get skip state + if (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_SKIP), value)) { + _state.skip = value; + } + + // Get custom title + if (AtomUtil::getString(_window, Atoms::getAtom(PEKWM_TITLE), str)) { + _title.setUser(Util::to_wide_str(str)); + } + + _state.initial_frame_order = getPekwmFrameOrder(); +} + +//! @brief Read _NET_WM_ICON from client window. +void +Client::readIcon(void) +{ + PImageIcon *image = new PImageIcon(_dpy); + if (image->loadFromWindow(_window)) { + if (! _icon) { + _icon = new PTextureImage(_dpy); + TextureHandler::instance()->referenceTexture(_icon); + } + + _icon->setImage(image); + } else { + if (image) { + delete image; + } + + if (_icon) { + TextureHandler::instance()->returnTexture(_icon); + _icon = 0; + } + } +} + +//! @brief Tries to find a AutoProp for the current client. +//! @param type Defaults to 0 +//! @return AutoProperty if any is found, else 0. +AutoProperty* +Client::readAutoprops(uint type) +{ + AutoProperty *data = + AutoProperties::instance()->findAutoProperty(_class_hint, _workspace, type); + + if (data) { + // Make sure transient state matches + if (isTransient() + ? data->isApplyOn(APPLY_ON_TRANSIENT|APPLY_ON_TRANSIENT_ONLY) + : ! data->isApplyOn(APPLY_ON_TRANSIENT_ONLY)) { + applyAutoprops(data); + } else { + data = 0; + } + } + + return data; +} + +//! @brief Applies AutoPropery to this Client. +void +Client::applyAutoprops(AutoProperty *ap) +{ + // Set the correct group of the window + _class_hint->group = ap->group_name; + + // We only apply grouping if it's a new client or if we are restarting + // and have APPLY_ON_START set + if (ap->isMask(AP_STICKY)) + _sticky = ap->sticky; + if (ap->isMask(AP_SHADED)) + _state.shaded = ap->shaded; + if (ap->isMask(AP_MAXIMIZED_VERTICAL)) + _state.maximized_vert = ap->maximized_vertical; + if (ap->isMask(AP_MAXIMIZED_HORIZONTAL)) + _state.maximized_horz = ap->maximized_horizontal; + if (ap->isMask(AP_FULLSCREEN)) + _state.fullscreen = ap->fullscreen; + if (ap->isMask(AP_ICONIFIED)) + _iconified = ap->iconified; + if (ap->isMask(AP_TITLEBAR)) + setTitlebar(ap->titlebar); + if (ap->isMask(AP_BORDER)) + setBorder(ap->border); + if (ap->isMask(AP_LAYER) && (ap->layer <= LAYER_MENU)) { + setLayer(ap->layer); + } + if (ap->isMask(AP_SKIP)) + _state.skip = ap->skip; + if (ap->isMask(AP_FOCUSABLE)) + _focusable = ap->focusable; + if (ap->isMask(AP_WORKSPACE)) { + _workspace = ap->workspace; + } + if (ap->isMask(AP_CFG_DENY)) { + _state.cfg_deny = ap->cfg_deny; + } + if (ap->isMask(AP_ALLOWED_ACTIONS)) { + applyActionAccessMask(ap->allowed_actions, true); + } + if (ap->isMask(AP_DISALLOWED_ACTIONS)) { + applyActionAccessMask(ap->disallowed_actions, false); + } +#ifdef OPACITY + if (ap->isMask(AP_OPACITY)) { + setOpacity(ap->focus_opacity, ap->unfocus_opacity); + } +#endif // OPACITY +} + +void +Client::applyActionAccessMask(uint mask, bool value) +{ + if (mask & ACTION_ACCESS_MOVE) { + _actions.move = value; + } + if (mask & ACTION_ACCESS_RESIZE) { + _actions.resize = value; + } + if (mask & ACTION_ACCESS_MINIMIZE) { + _actions.minimize = value; + } + if (mask & ACTION_ACCESS_SHADE) { + _actions.shade = value; + } + if (mask & ACTION_ACCESS_STICK) { + _actions.stick = value; + } + if (mask & ACTION_ACCESS_MAXIMIZE_HORZ) { + _actions.maximize_horz = value; + } + if (mask & ACTION_ACCESS_MAXIMIZE_VERT) { + _actions.maximize_vert = value; + } + if (mask & ACTION_ACCESS_FULLSCREEN) { + _actions.fullscreen = value; + } + if (mask & ACTION_ACCESS_CHANGE_DESKTOP) { + _actions.change_desktop = value; + } + if (mask & ACTION_ACCESS_CLOSE) { + _actions.close = value; + } +} + +/** + * Read _NET_WM_PID. + */ +void +Client::readClientPid(void) +{ + AtomUtil::getLong(_window, Atoms::getAtom(NET_WM_PID), _pid); +} + +/** + * Read WM_CLIENT_MACHINE and check against local hostname and set + * _is_remote if it does not match. + */ +void +Client::readClientRemote(void) +{ + string client_machine; + if (AtomUtil::getTextProperty(_window, XA_WM_CLIENT_MACHINE, client_machine)) { + _is_remote = Util::getHostname() != client_machine; + } +} + +//! @brief Finds free Client ID. +//! @return First free Client ID. +uint +Client::findClientID(void) +{ + uint id = 0; + + if (_clientid_list.size()) { + // Check for used Frame IDs + id = _clientid_list.back(); + _clientid_list.pop_back(); + } else { + // No free, get next number (Client is not in list when this is called.) + id = _client_list.size() + 1; + } + + return id; +} + +//! @brief Returns Client ID to used client id list. +//! @param id ID to return. +void +Client::returnClientID(uint id) +{ + vector::iterator it(_clientid_list.begin()); + for (; it != _clientid_list.end() && id < *it; ++it) + ; + _clientid_list.insert(it, id); +} + +/** + * Tries to get the NET_WM name, else fall back to WM_NAME + */ +void +Client::readName(void) +{ + // Read title, bail out if it fails. + wstring title; + if (! AtomUtil::getUtf8String(_window, Atoms::getAtom(NET_WM_NAME), title)) { + string mb_title; + if (! AtomUtil::getTextProperty(_window, XA_WM_NAME, mb_title)) { + return; + } + title = Util::to_wide_str(mb_title); + } + + // Mirror it on the visible + _title.setCustom(L""); + _title.setCount(titleFindID(title)); + _title.setReal(title); + + // Apply title rules and find unique name, doesn't apply on + // user-set titles + if (titleApplyRule(title)) { + _title.setCustom(title); + AtomUtil::setUtf8String(_window, Atoms::getAtom(NET_WM_VISIBLE_NAME), title); + } else { + AtomUtil::unsetProperty(_window, Atoms::getAtom(NET_WM_VISIBLE_NAME)); + } +} + +//! @brief Searches for an TitleRule and if found, applies it +//! @param title Title to apply rule on. +//! @return true if rule was applied, else false. +bool +Client::titleApplyRule(std::wstring &title) +{ + _class_hint->title = title; + TitleProperty *data = AutoProperties::instance()->findTitleProperty(_class_hint); + + if (data) { + return data->getTitleRule().ed_s(title); + } else { + return false; + } +} + +//! @brief Searches for a unique ID within Clients having the same title +//! @param title Title of client to find ID for. +//! @return Number of clients with that id. +uint +Client::titleFindID(std::wstring &title) +{ + // Do not search for unique IDs if it is not enabled. + if (! Config::instance()->getClientUniqueName()) { + return 0; + } + + uint id_found = 0; + list ids_used; + + list::iterator it = _client_list.begin(); + for (; it != _client_list.end(); ++it) { + if (*it != this) { + if ((*it)->getTitle()->getReal() == title) { + ids_used.push_back((*it)->getTitle()->getCount()); + } + } + } + + // more than one client having this name + if (ids_used.size() > 0) { + ids_used.sort(); + + list::iterator ui_it( ids_used.begin()); + for (uint i = 0; ui_it != ids_used.end(); ++i, ++ui_it) { + if (i < *ui_it) { + id_found = i; + break; + } + } + + if (ui_it == ids_used.end()) { + id_found = ids_used.size(); + } + } + + return id_found; +} + +/** + * Get the clients icon name to be displayed when the client is + * iconified. + */ +void +Client::readIconName(void) +{ + wstring icon_name; + + if (! AtomUtil::getUtf8String(_window, Atoms::getAtom(NET_WM_ICON_NAME), icon_name)) { + string mb_icon_name; + if (AtomUtil::getTextProperty(_window, XA_WM_ICON_NAME, mb_icon_name)) { + icon_name = Util::to_wide_str(mb_icon_name); + } + } + + // Set real name + _icon_name.setReal(icon_name); + _icon_name.setCustom(icon_name); + + if (_icon_name.getVisible() == _icon_name.getReal()) { + AtomUtil::unsetProperty(_window, Atoms::getAtom(NET_WM_VISIBLE_ICON_NAME)); + } else { + AtomUtil::setUtf8String(_window, Atoms::getAtom(NET_WM_VISIBLE_ICON_NAME), icon_name); + } +} + +//! @brief Sets the WM_STATE of the client to state +//! @param state State to set. +void +Client::setWmState(ulong state) +{ + ulong data[2]; + + data[0] = state; + data[1] = None; // No Icon + + XChangeProperty(_dpy, _window, + Atoms::getAtom(WM_STATE), + Atoms::getAtom(WM_STATE), + 32, PropModeReplace, (uchar*) data, 2); +} + +// If we can't find a wm_state we're going to have to assume +// Withdrawn. This is not exactly optimal, since we can't really +// distinguish between the case where no WM has run yet and when the +// state was explicitly removed (Clients are allowed to either set the +// atom to Withdrawn or just remove it... yuck.) +long +Client::getWmState(void) +{ + Atom real_type; + int real_format; + long *data, state = WithdrawnState; + ulong items_read, items_left; + uchar *udata; + + int status = + XGetWindowProperty(_dpy, _window, Atoms::getAtom(WM_STATE), + 0L, 2L, False, Atoms::getAtom(WM_STATE), + &real_type, &real_format, &items_read, &items_left, + &udata); + if ((status == Success) && items_read) { + data = reinterpret_cast(udata); + state = *data; + XFree(udata); + } + + return state; +} + +//! @brief Send XConfigureEvent, letting the client know about changes +void +Client::configureRequestSend(void) +{ + if (_cfg_request_lock) { + return; + } + + XConfigureEvent e; + + e.type = ConfigureNotify; + e.event = _window; + e.window = _window; + e.x = _gm.x; + e.y = _gm.y; + e.width = _gm.width; + e.height = _gm.height; + e.border_width = 0; + e.above = None; + e.override_redirect = False; + + XSendEvent(_dpy, _window, false, StructureNotifyMask, (XEvent *) &e); +} + +//! @brief Send a TAKE_FOCUS client message to the client (if requested by it). +void Client::sendTakeFocusMessage(void) +{ + if (_send_focus_message) { + { + XEvent ev; + XChangeProperty(_dpy, RootWindow(_dpy, DefaultScreen(_dpy)), XA_PRIMARY, XA_STRING, 8, PropModeAppend, 0, 0); + XWindowEvent(_dpy, RootWindow(_dpy, DefaultScreen(_dpy)), PropertyChangeMask, &ev); + PScreen::instance()->setLastEventTime(ev.xproperty.time); + } + sendXMessage(_window, + Atoms::getAtom(WM_PROTOCOLS), NoEventMask, + Atoms::getAtom(WM_TAKE_FOCUS), + PScreen::instance()->getLastEventTime()); + } +} + +//! @brief Grabs a button on the window win +//! Grabs the button button, with the mod mod and mask mask on the window win +//! and cursor curs with "all" possible extra modifiers +void +Client::grabButton(int button, int mod, int mask, Window win, Cursor curs) +{ + uint num_lock = PScreen::instance()->getNumLock(); + uint scroll_lock = PScreen::instance()->getScrollLock(); + + XGrabButton(_dpy, button, mod, + win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); + XGrabButton(_dpy, button, mod|LockMask, + win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); + + if (num_lock) { + XGrabButton(_dpy, button, mod|num_lock, + win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); + XGrabButton(_dpy, button, mod|num_lock|LockMask, + win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); + } + if (scroll_lock) { + XGrabButton(_dpy, button, mod|scroll_lock, + win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); + XGrabButton(_dpy, button, mod|scroll_lock|LockMask, + win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); + } + if (num_lock && scroll_lock) { + XGrabButton(_dpy, button, mod|num_lock|scroll_lock, + win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); + XGrabButton(_dpy, button, mod|num_lock|scroll_lock|LockMask, + win, true, mask, GrabModeAsync, GrabModeAsync, None, curs); + } +} + +/** + * Toggles the clients always on top state + */ +void +Client::alwaysOnTop(bool top) +{ + setLayer(top ? LAYER_ONTOP : LAYER_NORMAL); + updateEwmhStates(); +} + +/** + * Toggles the clients always below state. + */ +void +Client::alwaysBelow(bool below) +{ + setLayer(below ? LAYER_BELOW : LAYER_NORMAL); + updateEwmhStates(); +} + +//! @brief Sets the skip state, and updates the _PEKWM_FRAME_SKIP atom +void +Client::setSkip(uint skip) +{ + _state.skip = skip; + AtomUtil::setLong(_window, Atoms::getAtom(PEKWM_FRAME_SKIP), _state.skip); +} + +/** + * Set demands attention state, this should be unset when client + * recieves focus. This is ignored if client has focus. + */ +void +Client::setStateDemandsAttention(StateAction sa, bool attention) +{ + // FIXME: Demands attention state. +} + +//! @brief Sends an WM_DELETE message to the client, else kills it. +void +Client::close(void) +{ + if (_send_close_message) { + sendXMessage(_window, Atoms::getAtom(WM_PROTOCOLS), NoEventMask, + Atoms::getAtom(WM_DELETE_WINDOW), CurrentTime); + } else { + kill(); + } +} + +//! @brief Kills the client using XKillClient +void +Client::kill(void) +{ + XKillClient(_dpy, _window); +} + +//! @brief Sets the position based on P or U position. +//! @return Returns true on success, else false. +bool +Client::setPUPosition(void) +{ + if ((_size->flags&PPosition) || (_size->flags&USPosition)) { + if (_gm.x == 0) { + _gm.x = _size->x; + } + if (_gm.y == 0) { + _gm.y = _size->y; + } + return true; + } + return false; +} + + +//! @brief Get the closest size confirming to the aspect ratio and ResizeInc (if applicable) +//! @param r_w Pointer to put the new width in +//! @param r_h Pointer to put the new height in +//! @param w Width to calculate from +//! @param h Height to calculate from +//! @return true if width/height have changed +bool +Client::getAspectSize(uint *r_w, uint *r_h, uint w, uint h) +{ + // see ICCCM 4.1.2.3 for PAspect and {min,max}_aspect + if (_size->flags & PAspect && Config::instance()->isHonourAspectRatio()) { + // shorthand + const uint amin_x = _size->min_aspect.x; + const uint amin_y = _size->min_aspect.y; + const uint amax_x = _size->max_aspect.x; + const uint amax_y = _size->max_aspect.y; + + uint base_w = 0, base_h = 0; + + // If PBaseSize is specified, base_{width,height} should be subtracted + // before checking the aspect ratio (c.f. ICCCM). The additional checks avoid + // underflows in w and h. Keep in mind that _size->base_{width,height} are + // guaranteed to be non-negative by getWMNormalHints(). + if (_size->flags & PBaseSize) { + if (static_cast(_size->base_width) < w) { + base_w = _size->base_width; + w -= base_w; + } + if (static_cast(_size->base_height) < h) { + base_h = _size->base_height; + h -= base_h; + } + } + + double tmp; + + // We have to ensure: min_aspect.x/min_aspect.y <= w/h <= max_aspect.x/max_aspect.y + + // How do we calculate the new r_w, r_h? + // Consider the plane spanned by width and height. The points with one specific + // aspect ratio form a line and (w,h) is a point. So, we simply calculate the + // point on the line that is closest to (w, h) under the Euclidean metric. + + // Lesson learned doing this: It is good to look at a different implementation + // (thanks fluxbox!) and then let a friend go over your own calculation to + // tell you what your mistake is (thanks Robert!). ;-) + + // Check if w/h is less than amin_x/amin_y + if (w * amin_y < h * amin_x) { + tmp = ((double)(w * amin_x + h * amin_y)) / + ((double)(amin_x * amin_x + amin_y * amin_y)); + + w = static_cast(amin_x * tmp) + base_w; + h = static_cast(amin_y * tmp) + base_h; + + // Check if w/h is greater than amax_x/amax_y + } else if (w * amax_y > amax_x * h) { + tmp = ((double)(w * amax_x + h * amax_y)) / + ((double)(amax_x * amax_x + amax_y * amax_y)); + + w = static_cast(amax_x * tmp) + base_w; + h = static_cast(amax_y * tmp) + base_h; + } + + getIncSize(r_w, r_h, w, h, false); + return true; + } + return getIncSize(r_w, r_h, w, h); +} + +//! @brief Get the size closest to the ResizeInc incrementer +//! @param r_w Pointer to put the new width in +//! @param r_h Pointer to put the new height in +//! @param w Width to calculate from +//! @param h Height to calculate from +//! @param incr If true, increase w,h to fulfil PResizeInc (instead of decreasing them) +bool +Client::getIncSize(uint *r_w, uint *r_h, uint w, uint h, bool incr) +{ + uint basex, basey; + + if (_size->flags&PResizeInc) { + basex = (_size->flags&PBaseSize) + ? _size->base_width + : (_size->flags&PMinSize) ? _size->min_width : 0; + + basey = (_size->flags&PBaseSize) + ? _size->base_height + : (_size->flags&PMinSize) ? _size->min_height : 0; + + if (w-basex < 0 || h-basey<0) { + basex=basey=0; + } + + uint dw = (w - basex) % _size->width_inc; + uint dh = (h - basey) % _size->height_inc; + + *r_w = w - dw + ((incr && dw)?_size->width_inc:0); + *r_h = h - dh + ((incr && dh)?_size->height_inc:0); + return true; + } + + *r_w = w; + *r_h = h; + return false; +} + +//! @brief Gets a MwmHint structure from a window. Doesn't free memory. +Client::MwmHints* +Client::getMwmHints(Window win) +{ + Atom real_type; int real_format; + ulong items_read, items_left; + MwmHints *data = 0; + uchar *udata; + + Atom hints_atom = Atoms::getAtom(MOTIF_WM_HINTS); + + int status = XGetWindowProperty(_dpy, win, hints_atom, 0L, 20L, False, hints_atom, + &real_type, &real_format, &items_read, &items_left, &udata); + + if (status == Success) { + if (items_read < MWM_HINTS_NUM) { + XFree(udata); + udata = 0; + } + } else { + udata = 0; + } + + if (udata) { + data = reinterpret_cast(udata); + } + + return data; +} + +// This happens when a window is iconified and destroys itself. An +// Unmap event wouldn't happen in that case because the window is +// already unmapped. +void +Client::handleDestroyEvent(XDestroyWindowEvent *e) +{ + _alive = false; + delete this; +} + +void +Client::handleColormapChange(XColormapEvent *e) +{ + if (e->c_new) { + _cmap = e->colormap; + XInstallColormap(_dpy, _cmap); + } +} + +int +Client::sendXMessage(Window window, Atom atom, long mask, + long v1, long v2, long v3, long v4, long v5) +{ + XEvent e; + + e.type = e.xclient.type = ClientMessage; + e.xclient.display = _dpy; + e.xclient.window = window; + e.xclient.format = 32; + e.xclient.message_type = atom; + + e.xclient.data.l[0] = v1; + e.xclient.data.l[1] = v2; + e.xclient.data.l[2] = v3; + e.xclient.data.l[3] = v4; + e.xclient.data.l[4] = v5; + + return XSendEvent(_dpy, window, false, mask, &e); +} + +//! @brief +bool +Client::getEwmhStates(NetWMStates &win_states) +{ + int num = 0; + Atom *states; + states = (Atom*) + AtomUtil::getEwmhPropData(_window, Atoms::getAtom(STATE), + XA_ATOM, num); + + if (states) { + for (int i = 0; i < num; ++i) { + if (states[i] == Atoms::getAtom(STATE_MODAL)) { + win_states.modal = true; + } else if (states[i] == Atoms::getAtom(STATE_STICKY)) { + win_states.sticky = true; + } else if (states[i] == Atoms::getAtom(STATE_MAXIMIZED_VERT) + && ! isCfgDeny(CFG_DENY_STATE_MAXIMIZED_VERT)) { + win_states.max_vert = true; + } else if (states[i] == Atoms::getAtom(STATE_MAXIMIZED_HORZ) + && ! isCfgDeny(CFG_DENY_STATE_MAXIMIZED_HORZ)) { + win_states.max_horz = true; + } else if (states[i] == Atoms::getAtom(STATE_SHADED)) { + win_states.shaded = true; + } else if (states[i] == Atoms::getAtom(STATE_SKIP_TASKBAR)) { + win_states.skip_taskbar = true; + } else if (states[i] == Atoms::getAtom(STATE_SKIP_PAGER)) { + win_states.skip_pager = true; + } else if (states[i] == Atoms::getAtom(STATE_DEMANDS_ATTENTION)) { + win_states.demands_attention = true; + } else if (states[i] == Atoms::getAtom(STATE_HIDDEN) + && ! isCfgDeny(CFG_DENY_STATE_HIDDEN)) { + win_states.hidden = true; + } else if (states[i] == Atoms::getAtom(STATE_FULLSCREEN) + && ! isCfgDeny(CFG_DENY_STATE_FULLSCREEN)) { + win_states.fullscreen = true; + } else if (states[i] == Atoms::getAtom(STATE_ABOVE) + && ! isCfgDeny(CFG_DENY_STATE_ABOVE)) { + win_states.above = true; + } else if (states[i] == Atoms::getAtom(STATE_BELOW) + && ! isCfgDeny(CFG_DENY_STATE_BELOW)) { + win_states.below = true; + } + } + + XFree(states); + + return true; + } else { + return false; + } +} + +//! @brief Tells the world about our states, such as shaded etc. +void +Client::updateEwmhStates(void) +{ + list states; + + if (false) // we don't yet support modal state + states.push_back(Atoms::getAtom(STATE_MODAL)); + if (_sticky) + states.push_back(Atoms::getAtom(STATE_STICKY)); + if (_state.maximized_vert) + states.push_back(Atoms::getAtom(STATE_MAXIMIZED_VERT)); + if (_state.maximized_horz) + states.push_back(Atoms::getAtom(STATE_MAXIMIZED_HORZ)); + if (_state.shaded) + states.push_back(Atoms::getAtom(STATE_SHADED)); + if (isSkip(SKIP_TASKBAR)) + states.push_back(Atoms::getAtom(STATE_SKIP_TASKBAR)); + if (isSkip(SKIP_PAGER)) + states.push_back(Atoms::getAtom(STATE_SKIP_PAGER)); + if (_iconified) + states.push_back(Atoms::getAtom(STATE_HIDDEN)); + if (_state.fullscreen) + states.push_back(Atoms::getAtom(STATE_FULLSCREEN)); + if (getLayer() == LAYER_ABOVE_DOCK) { + states.push_back(Atoms::getAtom(STATE_ABOVE)); + } + if (getLayer() == LAYER_BELOW) { + states.push_back(Atoms::getAtom(STATE_BELOW)); + } + + Atom *atoms = new Atom[(states.size() > 0) ? states.size() : 1]; + if (states.size() > 0) { + copy(states.begin(), states.end(), atoms); + } + AtomUtil::setAtoms(_window, Atoms::getAtom(STATE), atoms, states.size()); + delete [] atoms; +} + +void +Client::getWMNormalHints(void) +{ + long dummy; + XGetWMNormalHints(_dpy, _window, _size, &dummy); + + // let's do some sanity checking + if (_size->flags & PBaseSize) { + if (_size->base_width<0 || _size->base_height<0) { + _size->base_width = _size->base_height = 0; + _size->flags &= ~PBaseSize; + } + } + if (_size->flags & PAspect) { + if (_size->min_aspect.x < 0 || _size->min_aspect.y < 0 || + _size->max_aspect.x < 0 || _size->max_aspect.y < 0) { + + _size->min_aspect.x = _size->min_aspect.y = 0; + _size->max_aspect.x = _size->max_aspect.y = 0; + _size->flags &= ~PAspect; + } + } + if (_size->flags & PResizeInc) { + if (_size->width_inc <= 0 || _size->height_inc <= 0) { + _size->width_inc = 0; + _size->height_inc = 0; + _size->flags &= ~PBaseSize; + } + } + + if (_size->flags & PMaxSize) { + if (_size->max_width <= 0 || _size->max_height <= 0 || + ((_size->flags & PMinSize) && + (_size->max_width < _size->min_width || + _size->max_height < _size->min_height) )) { + _size->max_width = 0; + _size->max_height = 0; + _size->flags &= ~PMaxSize; + } + } +} + +//! @brief +void +Client::getWMProtocols(void) +{ + int count; + Atom *protocols; + + if (XGetWMProtocols(_dpy, _window, &protocols, &count) != 0) { + for (int i = 0; i < count; ++i) { + if (protocols[i] == Atoms::getAtom(WM_TAKE_FOCUS)) { + _send_focus_message = true; + } else if (protocols[i] == Atoms::getAtom(WM_DELETE_WINDOW)) { + _send_close_message = true; + } + } + + XFree(protocols); + } +} + +/** + * Read WM_TRANSIENT_FOR hint. + */ +void +Client::getTransientForHint(void) +{ + if (! _transient) { + XGetTransientForHint(_dpy, _window, &_transient_window); + + if (_transient_window != None) { + _transient = findClientFromWindow(_transient_window); + if (_transient) { + _transient->_transient_clients.push_back(this); + _transient->addObserver(this); + } + } + } +} + +//! @brief +void +Client::getStrutHint(void) +{ + int num = 0; + long *strut = static_cast(AtomUtil::getEwmhPropData(_window, + Atoms::getAtom(NET_WM_STRUT), + XA_CARDINAL, num)); + if (strut) { + if (_strut) { + PScreen::instance()->removeStrut(_strut); + } else { + _strut = new Strut(); + } + + *_strut = strut; + PScreen::instance()->addStrut(_strut); + + XFree(strut); + + } else if (_strut) { + PScreen::instance()->removeStrut(_strut); + delete _strut; + _strut = 0; + } +} + +//! @brief +void +Client::removeStrutHint(void) +{ + if (! _strut) + return; + + PScreen::instance()->removeStrut(_strut); + delete _strut; + _strut = 0; +} + +/** + * Get _PEKWM_FRAME_ORDER hint from client, return < 0 on failure. + */ +long +Client::getPekwmFrameOrder(void) +{ + long num = -1; + AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_ORDER), num); + return num; +} + +/** + * Update _PEKWM_FRAME_ORDER hint on client window. + */ +void +Client::setPekwmFrameOrder(long num) +{ + AtomUtil::setLong(_window, Atoms::getAtom(PEKWM_FRAME_ORDER), num); +} + +/** + * Get _PEKWM_FRAME_ACTIVE hint from client window, return true if + * client is treated as active. + */ +bool +Client::getPekwmFrameActive(void) +{ + long act = 0; + return (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_ACTIVE), act) + && act == 1); +} + +/** + * Set _PEKWM_FRAME_ACTIVE hint on client window. + */ +void +Client::setPekwmFrameActive(bool act) +{ + AtomUtil::setLong(_window, Atoms::getAtom(PEKWM_FRAME_ACTIVE), act ? 1 : 0); +} + +/** + * Update the environment with CLIENT_PID and CLIENT_WINDOW. + */ +void +Client::setClientEnvironment(Client *client) +{ + if (client) { + setenv("CLIENT_PID", Util::to_string(client->isRemote() ? -1 : client->getPid()).c_str(), 1); + setenv("CLIENT_WINDOW", Util::to_string(client->getWindow()).c_str(), 1); + } else { + unsetenv("CLIENT_PID"); + unsetenv("CLIENT_WINDOW"); + } +} diff --git a/pekwm/Client.hh b/pekwm/Client.hh new file mode 100644 index 0000000..19976f1 --- /dev/null +++ b/pekwm/Client.hh @@ -0,0 +1,413 @@ +// +// Client.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// client.hh for aewm++ +// Copyright (C) 2002 Frank Hale +// http://sapphire.sourceforge.net/ +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _CLIENT_HH_ +#define _CLIENT_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" +#include "Observer.hh" +#include "PTexturePlain.hh" +#include "PDecor.hh" + +class PScreen; +class Strut; +class PWinObj; +class ClassHint; +class AutoProperty; +class Frame; + +#include + +extern "C" { +#include +} + +class LayerObservation : public Observation { +public: + LayerObservation(enum Layer _layer) : layer(_layer) { }; + virtual ~LayerObservation(void) { }; +public: + const enum Layer layer; /**< Layer client changed to. */ +}; + +class Client : public PWinObj, public Observer +{ + // FIXME: This relationship should end as soon as possible, but I need to + // figure out a good way of sharing. :) + friend class Frame; + +public: // Public Member Functions + struct MwmHints { + ulong flags; + ulong functions; + ulong decorations; + }; + enum { + MWM_HINTS_FUNCTIONS = (1L << 0), + MWM_HINTS_DECORATIONS = (1L << 1), + MWM_HINTS_NUM = 3 + }; + enum { + MWM_FUNC_ALL = (1L << 0), + MWM_FUNC_RESIZE = (1L << 1), + MWM_FUNC_MOVE = (1L << 2), + MWM_FUNC_ICONIFY = (1L << 3), + MWM_FUNC_MAXIMIZE = (1L << 4), + MWM_FUNC_CLOSE = (1L << 5) + }; + enum { + MWM_DECOR_ALL = (1L << 0), + MWM_DECOR_BORDER = (1L << 1), + MWM_DECOR_HANDLE = (1L << 2), + MWM_DECOR_TITLE = (1L << 3), + MWM_DECOR_MENU = (1L << 4), + MWM_DECOR_ICONIFY = (1L << 5), + MWM_DECOR_MAXIMIZE = (1L << 6) + }; + + Client(Window new_client, bool is_new = false); + virtual ~Client(void); + + // START - PWinObj interface. + virtual void mapWindow(void); + virtual void unmapWindow(void); + virtual void iconify(void); + virtual void stick(void); + + virtual void move(int x, int y); + virtual void resize(uint width, uint height); + virtual void moveResize(int x, int y, uint width, uint height); + + virtual void setWorkspace(uint workspace); + + virtual void giveInputFocus(void); + virtual void reparent(PWinObj *parent, int x, int y); + + virtual ActionEvent *handleButtonPress(XButtonEvent *ev) { + if (_parent) { return _parent->handleButtonPress(ev); } + return 0; + } + virtual ActionEvent *handleButtonRelease(XButtonEvent *ev) { + if (_parent) { return _parent->handleButtonRelease(ev); } + return 0; + } + + virtual ActionEvent *handleMapRequest(XMapRequestEvent *ev); + virtual ActionEvent *handleUnmapEvent(XUnmapEvent *ev); + // END - PWinObj interface. + + // START - Observer interface. + virtual void notify(Observable *observable, Observation *observation); + // END - Observer interface. + + static Client *findClient(Window win); + static Client *findClientFromWindow(Window win); + static Client *findClientFromHint(const ClassHint *class_hint); + static Client *findClientFromID(uint id); + static void findFamilyFromWindow(std::list &client_list, Window win); + + static void mapOrUnmapTransients(Window win, bool hide); + + // START - Iterators + static uint client_size(void) { return _client_list.size(); } + static std::list::iterator client_begin(void) { + return _client_list.begin(); + } + static std::list::iterator client_end(void) { + return _client_list.end(); + } + static std::list::reverse_iterator client_rbegin(void) { + return _client_list.rbegin(); + } + static std::list::reverse_iterator client_rend(void) { + return _client_list.rend(); + } + + unsigned int transient_size(void) { return _transient_clients.size(); } + std::list::iterator transient_begin(void) { + return _transient_clients.begin(); } + std::list::iterator transient_end(void) { + return _transient_clients.end(); } + // END - Iterators + + bool validate(void); + + inline uint getClientID(void) { return _id; } + /**< Return title item for client name. */ + inline PDecor::TitleItem *getTitle(void) { return &_title; } + /**< Return title item for client icon name. */ + inline PDecor::TitleItem *getIconName(void) { return &_icon_name; } + + inline const ClassHint* getClassHint(void) const { return _class_hint; } + + bool isTransient(void) const { return _transient_window != None; } + Client *getTransientClient(void) const { return _transient; } + Window getTransientClientWindow(void) const { return _transient_window; } + void findAndRaiseIfTransient(void); + + inline XSizeHints* getXSizeHints(void) const { return _size; } + + bool isViewable(void); + bool setPUPosition(void); + + inline bool hasTitlebar(void) const { return (_state.decor&DECOR_TITLEBAR); } + inline bool hasBorder(void) const { return (_state.decor&DECOR_BORDER); } + inline bool isShaped(void) const { return _shaped; } + inline bool hasStrut(void) const { return (_strut); } + Strut *getStrut(void) const { return _strut; } + + PTexture *getIcon(void) const { return _icon; } + + /** Return PID of client. */ + long getPid(void) const { return _pid; } + /** Return true if client is remote. */ + bool isRemote(void) const { return _is_remote; } + + // State accessors + inline bool isMaximizedVert(void) const { return _state.maximized_vert; } + inline bool isMaximizedHorz(void) const { return _state.maximized_horz; } + inline bool isShaded(void) const { return _state.shaded; } + inline bool isFullscreen(void) const { return _state.fullscreen; } + inline bool isPlaced(void) const { return _state.placed; } + inline uint getInitialFrameOrder(void) const { return _state.initial_frame_order; } + inline uint getSkip(void) const { return _state.skip; } + inline bool isSkip(Skip skip) const { return (_state.skip&skip); } + inline uint getDecorState(void) const { return _state.decor; } + inline bool isCfgDeny(uint deny) { return (_state.cfg_deny&deny); } + + inline bool allowMove(void) const { return _actions.move; } + inline bool allowResize(void) const { return _actions.resize; } + inline bool allowMinimize(void) const { return _actions.minimize; } + inline bool allowShade(void) const { return _actions.shade; } + inline bool allowStick(void) const { return _actions.stick; } + inline bool allowMaximizeHorz(void) const { return _actions.maximize_horz; } + inline bool allowMaximizeVert(void) const { return _actions.maximize_vert; } + inline bool allowFullscreen(void) const { return _actions.fullscreen; } + inline bool allowChangeDesktop(void) const { return _actions.change_desktop; } + inline bool allowClose(void) const { return _actions.close; } + + inline bool isAlive(void) const { return _alive; } + inline bool isMarked(void) const { return _marked; } + + // We have this public so that we can reload button actions. + void grabButtons(void); + + void setStateCfgDeny(StateAction sa, uint deny); + inline void setStateMarked(StateAction sa) { + if (ActionUtil::needToggle(sa, _marked)) { + _marked = !_marked; + if (_marked) { + _title.infoAdd(PDecor::TitleItem::INFO_MARKED); + } else { + _title.infoRemove(PDecor::TitleItem::INFO_MARKED); + } + _title.updateVisible(); + } + } + + // toggles + void alwaysOnTop(bool top); + void alwaysBelow(bool bottom); + + void setSkip(uint skip); + + inline void setStateSkip(StateAction sa, Skip skip) { + if ((isSkip(skip) && (sa == STATE_SET)) || (! isSkip(skip) && (sa == STATE_UNSET))) { + return; + } + _state.skip ^= skip; + } + + void setStateDemandsAttention(StateAction sa, bool attention); + + inline void setTitlebar(bool titlebar) { + if (titlebar) { + _state.decor |= DECOR_TITLEBAR; + } else { + _state.decor &= ~DECOR_TITLEBAR; + } + } + inline void setBorder(bool border) { + if (border) { + _state.decor |= DECOR_BORDER; + } else { + _state.decor &= ~DECOR_BORDER; + } + } + + /** Set shaped flag on Client. */ + inline void setShaped(bool shaped) { + _shaped = shaped; + } + + void close(void); + void kill(void); + + // Event handlers below - Used by WindowManager + void handleDestroyEvent(XDestroyWindowEvent *ev); + void handleColormapChange(XColormapEvent *ev); + + inline bool setConfigureRequestLock(bool lock) { + bool old_lock = _cfg_request_lock; + _cfg_request_lock = lock; + return old_lock; + } + + void configureRequestSend(void); + void sendTakeFocusMessage(void); + + bool getAspectSize(uint *r_w, uint *r_h, uint w, uint h); + bool getIncSize(uint *r_w, uint *r_h, uint w, uint h, bool incr=false); + + bool getEwmhStates(NetWMStates &win_states); + void updateEwmhStates(void); + + void getWMNormalHints(void); + void getWMProtocols(void); + void getTransientForHint(void); + void getStrutHint(void); + void readName(void); + void readIconName(void); + void removeStrutHint(void); + + long getPekwmFrameOrder(void); + void setPekwmFrameOrder(long num); + bool getPekwmFrameActive(void); + void setPekwmFrameActive(bool active); + + static void setClientEnvironment(Client *client); + AutoProperty* readAutoprops(uint type = 0); + +private: + bool getAndUpdateWindowAttributes(void); + + void findOrCreateFrame(AutoProperty *autoproperty); + bool findTaggedFrame(void); + bool findPreviousFrame(void); + bool findAutoGroupFrame(AutoProperty *autoproperty); + + void setInitialState(void); + void setMappedStateAndFocus(bool is_new, AutoProperty *autoproperty); + + bool titleApplyRule(std::wstring &wtitle); + uint titleFindID(std::wstring &wtitle); + + void setWmState(ulong state); + long getWmState(void); + + int sendXMessage(Window window, Atom atom, long mask, + long v1 = 0l, long v2 = 0l, long v3 = 0l, + long v4 = 0l, long v5 = 0l); + + MwmHints* getMwmHints(Window w); + + // these are used by frame + inline void setMaximizedVert(bool m) { _state.maximized_vert = m; } + inline void setMaximizedHorz(bool m) { _state.maximized_horz = m; } + inline void setShade(bool s) { _state.shaded = s; } + inline void setFullscreen(bool f) { _state.fullscreen = f; } + inline void setFocusable(bool f) { _focusable = f; } + + // Grabs button with Caps,Num and so on + void grabButton(int button, int mod, int mask, Window win, Cursor curs); + + void readHints(void); + ulong readWmHints(void); + void readClassRoleHints(void); + void readEwmhHints(void); + void readMwmHints(void); + void readPekwmHints(void); + void readIcon(void); + void applyAutoprops(AutoProperty *ap); + void applyActionAccessMask(uint mask, bool value); + void readClientPid(void); + void readClientRemote(void); + + static uint findClientID(void); + static void returnClientID(uint id); + +private: // Private Member Variables + uint _id; // _transient_clients; /**< List of transient clients. */ + + Strut *_strut; + + PDecor::TitleItem _title; /**< Name of the client. */ + PDecor::TitleItem _icon_name; /**< Name of the client when iconified. */ + PTextureImage *_icon; + + long _pid; /**< _NET_WM_PID of the client, only valid if is_remote is false. */ + bool _is_remote; /**< Boolean flag */ + + ClassHint *_class_hint; + + bool _alive, _marked; + bool _send_focus_message, _send_close_message, _wm_hints_input; + bool _cfg_request_lock; + bool _shaped; + bool _extended_net_name; + + class State { + public: + State(void) + : maximized_vert(false), maximized_horz(false), shaded(false), fullscreen(false), + placed(false), initial_frame_order(0), + skip(0), decor(DECOR_TITLEBAR|DECOR_BORDER), + cfg_deny(CFG_DENY_NO), demands_attention(true) { } + ~State(void) { } + + bool maximized_vert, maximized_horz; + bool shaded; + bool fullscreen; + + // pekwm states + bool placed; + uint initial_frame_order; /**< Initial frame position */ + uint skip, decor, cfg_deny; + bool demands_attention; /**< If true, the client requires attention from the user. */ + } _state; + + class Actions { + public: + Actions(void) : move(true), resize(true), minimize(true), + shade(true), stick(true), maximize_horz(true), + maximize_vert(true), fullscreen(true), + change_desktop(true), close(true) { } + ~Actions(void) { } + + bool move; + bool resize; + bool minimize; // iconify + bool shade; + bool stick; + bool maximize_horz; + bool maximize_vert; + bool fullscreen; + bool change_desktop; // workspace + bool close; + } _actions; + + static std::list _client_list; //!< List of all Clients. + static std::vector _clientid_list; //!< List of free Client IDs. +}; + +#endif // _CLIENT_HH_ diff --git a/pekwm/CmdDialog.cc b/pekwm/CmdDialog.cc new file mode 100644 index 0000000..e6b2c36 --- /dev/null +++ b/pekwm/CmdDialog.cc @@ -0,0 +1,145 @@ +// +// CmdDialog.cc for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include + +extern "C" { +#include +#include // XLookupString +#include +} + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "CmdDialog.hh" +#include "Config.hh" +#include "PScreen.hh" +#include "PixmapHandler.hh" +#include "KeyGrabber.hh" +#include "ScreenResources.hh" +#include "Workspaces.hh" + +using std::cerr; +using std::endl; +using std::list; +using std::string; +using std::wstring; + +/** + * CmdDialog constructor, init and load history file. + * + * @todo Make size configurable. + */ +CmdDialog::CmdDialog(Theme *theme) + : InputDialog(theme, L"Enter command"), + _completer(L";"), _exec_count(0) +{ + _type = PWinObj::WO_CMD_DIALOG; + + // Setup completer + _completer.add_method(new ActionCompleterMethod()); + _completer.add_method(new PathCompleterMethod()); + + if (Config::instance()->getCmdDialogHistoryFile().size() > 0) { + _hist_list.load(Config::instance()->getCmdDialogHistoryFile()); + } +} + +/** + * CmdDialog de-structor, clean up and save history file. + */ +CmdDialog::~CmdDialog(void) +{ + if (Config::instance()->getCmdDialogHistoryFile().size() > 0) { + _hist_list.save(Config::instance()->getCmdDialogHistoryFile()); + } +} + + +//! @brief Parses _buf and tries to generate an ActionEvent +//! @return Pointer to ActionEvent. +ActionEvent* +CmdDialog::exec(void) +{ + // Update history + if (Config::instance()->isCmdDialogHistoryUnique()) { + _hist_list.push_back_unique(_buf); + } else { + _hist_list.push_back(_buf); + } + if (_hist_list.size() > static_cast(Config::instance()->getCmdDialogHistorySize())) { + _hist_list.pop_front(); + } + + // Persist changes + if (Config::instance()->getCmdDialogHistorySaveInterval() > 0 + && Config::instance()->getCmdDialogHistoryFile().size() > 0 + && ++_exec_count > Config::instance()->getCmdDialogHistorySaveInterval()) { + _hist_list.save(Config::instance()->getCmdDialogHistoryFile()); + _exec_count = 0; + } + + + // Check if it's a valid Action, if not we assume it's a command and try + // to execute it. + string buf_mb(Util::to_mb_str(_buf)); + if (! Config::instance()->parseAction(buf_mb, _ae.action_list.back(), KEYGRABBER_OK)) { + _ae.action_list.back().setAction(ACTION_EXEC); + _ae.action_list.back().setParamS(buf_mb); + } + + return &_ae; +} + +//! @brief Unmaps window, overloaded to clear buffer. +void +CmdDialog::unmapWindow(void) +{ + if (_mapped) { + InputDialog::unmapWindow(); + setWORef(0); + bufClear(); + } +} + +/** + * Tab completion, complete word at cursor position. + */ +void +CmdDialog::complete(void) +{ + // Find completion if changed since last time. + if (_buf != _buf_on_complete_result) { + InputDialog::complete(); + _complete_list = _completer.find_completions(_buf, _pos); + _complete_it = _complete_list.begin(); + } + + if (_complete_list.size()) { + _buf = _completer.do_complete(_buf_on_complete, _pos, _complete_list, _complete_it); + _buf_on_complete_result = _buf; + } +} + +/** + * Map CmdDialog centered on wo_ref is specified, else _wo_ref. + */ +void +CmdDialog::mapCentered(const std::string &buf, bool focus, PWinObj *wo_ref) +{ + if (wo_ref != 0) { + setWORef(wo_ref); + } + InputDialog::mapCentered(buf, focus, wo_ref); +} diff --git a/pekwm/CmdDialog.hh b/pekwm/CmdDialog.hh new file mode 100644 index 0000000..280874c --- /dev/null +++ b/pekwm/CmdDialog.hh @@ -0,0 +1,49 @@ +// +// CmdDialog.hh for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _CMD_DIALOG_HH_ +#define _CMD_DIALOG_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Action.hh" +#include "Completer.hh" +#include "InputDialog.hh" +#include "Theme.hh" + +#include +#include + +//! @brief CmdDialog presenting the user with an pekwm action command line. +class CmdDialog : public InputDialog +{ +public: + CmdDialog(Theme *theme); + virtual ~CmdDialog(void); + + void unmapWindow(void); + + virtual void mapCentered(const std::string &buf, bool focus, PWinObj *wo_ref); + +private: + void render(void); + + virtual ActionEvent *exec(void); + virtual void complete(void); + +private: + Completer _completer; /**< Completer used completing actions. */ + complete_list _complete_list; /**< List of completions found by completer. */ + complete_it _complete_it; /**< Iterator used to step between completions. */ + + int _exec_count; /**< Number of CmdDialog has run exec since last history save. */ +}; + +#endif // _CMD_DIALOG_HH_ diff --git a/pekwm/ColorHandler.cc b/pekwm/ColorHandler.cc new file mode 100644 index 0000000..a3fe78a --- /dev/null +++ b/pekwm/ColorHandler.cc @@ -0,0 +1,141 @@ +// +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "ColorHandler.hh" +#include "PScreen.hh" +#include + +#ifdef DEBUG +#include +using std::cerr; +using std::endl; +#endif // DEBUG + +using std::list; +using std::string; + +ColorHandler* ColorHandler::_instance = 0; + +//! @brief ColorHandler constructor +ColorHandler::ColorHandler(Display *dpy) + : _dpy(dpy), + _free_on_return(false) +{ +#ifdef DEBUG + if (_instance) { + cerr << __FILE__ << "@" << __LINE__ << ": " << endl + << "ColorHandler(" << this << ")::ColorHandler(" << _dpy << ")" + << endl << " *** _instance already set" << endl; + } +#endif // DEBUG + _instance = this; + + // setup default color + _xc_default.pixel = PScreen::instance()->getBlackPixel(); + _xc_default.red = _xc_default.green = _xc_default.blue = 0; +} + +//! @brief ColorHandler destructor +ColorHandler::~ColorHandler(void) +{ + freeColors(true); + + _instance = 0; +} + +//! @brief Gets or allocs a color +XColor* +ColorHandler::getColor(const std::string &color) +{ + // check for already existing entry + list::iterator it(_color_list.begin()); + + if (strcasecmp(color.c_str(), "EMPTY") == 0) { + return &_xc_default; + } + + for (; it != _color_list.end(); ++it) { + if (*(*it) == color) { + (*it)->incRef(); + return (*it)->getColor(); + } + } + + // create new entry + ColorHandler::Entry *entry = new ColorHandler::Entry(color); + entry->incRef(); + + // X alloc + XColor dummy; + if (XAllocNamedColor(_dpy, PScreen::instance()->getColormap(), + color.c_str(), entry->getColor(), &dummy) == 0) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "ColorHandler(" << this << ")::getColor(" << color << ")" << endl + << " *** failed to alloc color: " << color << endl; +#endif // DEBUG + delete entry; + entry = 0; + } else { + _color_list.push_back(entry); + } + + return (entry) ? entry->getColor() : &_xc_default; +} + +//! @brief Returns a color +void +ColorHandler::returnColor(XColor *xc) +{ + if (&_xc_default == xc) { // no need to return default color + return; + } + + list::iterator it(_color_list.begin()); + + for (; it != _color_list.end(); ++it) { + if ((*it)->getColor() == xc) { + (*it)->decRef(); + if (_free_on_return && ((*it)->getRef() == 0)) { + ulong pixels[1] = { (*it)->getColor()->pixel }; + XFreeColors(_dpy, PScreen::instance()->getColormap(), pixels, 1, 0); + + delete *it; + _color_list.erase(it); + } + break; + } + } +} + +//! @brief Frees color allocated by ColorHandler +//! @param all If false free on only unref colors +void +ColorHandler::freeColors(bool all) +{ + list pixel_list; + list::iterator it(_color_list.begin()); + + for (; it != _color_list.end(); ++it) { + if (all || ((*it)->getRef() == 0)) { + pixel_list.push_back((*it)->getColor()->pixel); + delete *it; + } + } + + if (pixel_list.size() > 0) { + ulong *pixels = new ulong[pixel_list.size()]; + copy(pixel_list.begin(), pixel_list.end(), pixels); + XFreeColors(_dpy, PScreen::instance()->getColormap(), + pixels, pixel_list.size(), 0); + delete [] pixels; + } +} diff --git a/pekwm/ColorHandler.hh b/pekwm/ColorHandler.hh new file mode 100644 index 0000000..2637ac6 --- /dev/null +++ b/pekwm/ColorHandler.hh @@ -0,0 +1,74 @@ +// +// ColorHandler.hh for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _COLOR_HANDLER_HH_ +#define _COLOR_HANDLER_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" + +#include +#include +#include + +extern "C" { +#include +} + +class ColorHandler { +public: + class Entry { + public: + Entry(const std::string &name) : _name(name), _ref(0) { } + ~Entry(void) { } + + inline XColor *getColor(void) { return &_xc; } + + inline uint getRef(void) const { return _ref; } + inline void incRef(void) { _ref++; } + inline void decRef(void) { if (_ref > 0) {_ref--; } + } + + inline bool operator==(const std::string &name) { + return (::strcasecmp(_name.c_str(), name.c_str()) == 0); + } + + private: + std::string _name; + XColor _xc; + + uint _ref; + }; + + ColorHandler(Display *dpy); + ~ColorHandler(void); + + static ColorHandler *instance(void) { return _instance; } + + inline bool isFreeOnReturn(void) const { return _free_on_return; } + inline void setFreeOnReturn(bool free) { _free_on_return = free; } + + XColor *getColor(const std::string &color); + void returnColor(XColor *xc); + + void freeColors(bool all); + +private: + Display *_dpy; + + XColor _xc_default; // when allocating fails + std::list _color_list; + bool _free_on_return; // used when returning many colours + + static ColorHandler *_instance; +}; + +#endif // _COLOR_HANDLER_HH_ diff --git a/pekwm/Compat.cc b/pekwm/Compat.cc new file mode 100644 index 0000000..8a2f5a5 --- /dev/null +++ b/pekwm/Compat.cc @@ -0,0 +1,120 @@ +// +// Compat.cc for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Compat.hh" +#include "Util.hh" + +#include +#include +extern "C" { +#include +#include +} + +#ifndef HAVE_UNSETENV +#include +#endif // ! HAVE_UNSETENV + +using std::string; +using std::wstring; + +#ifndef HAVE_SWPRINTF +/**< Message displayed when %ls formatting is attempted. */ +static const wchar_t *SWPRINTF_LS_NOT_SUPPORTED = L"%ls format string not supported"; + +/** + * Compat swprintf, print formatted wide string. + * + * @param wcs Result string, maxlen long. + * @param maxlen Maximum number of characters to print. + * @param format Formatting string. + * @param ... Formatting arguments. + * @return Number of characters written or -1 on error. + */ +namespace std { + int + swprintf(wchar_t *wcs, size_t maxlen, const wchar_t *format, ...) + { + size_t len; + string mb_format(Util::to_mb_str(format)); + + // Look for wide string formatting, not yet implemented. + if (mb_format.find("%ls") != string::npos) { + len = std::min(wcslen(SWPRINTF_LS_NOT_SUPPORTED), maxlen - 1); + wmemcpy(wcs, SWPRINTF_LS_NOT_SUPPORTED, len); + } else { + char *res = new char[maxlen]; + + va_list ap; + va_start(ap, format); + vsnprintf(res, maxlen, mb_format.c_str(), ap); + va_end(ap); + + wstring w_res(Util::to_wide_str(res)); + len = std::min(maxlen - 1, w_res.size()); + wmemcpy(wcs, w_res.c_str(), len); + + delete [] res; + } + + // Null terminate and return result. + wcs[len] = L'\0'; + + return wcslen(wcs); + } +} +#endif // HAVE_SWPRINTF + +#ifndef HAVE_SETENV +/** + * Compat setenv, insert variable to environment. + */ +int +setenv(const char *name, const char *value, int overwrite) +{ + // Invalid parameters + if (! name || ! value) { + return -1; + } + // Do not overwrite + if (! overwrite && getenv(name)) { + return 0; + } + + size_t len = strlen(name) + strlen(value) + 2; + char *str = new char[len]; + if (! str) { + errno = ENOMEM; + return -1; + } + + snprintf(str, len, "%s=%s", name, value); + + return (putenv(str)); +} +#endif // ! HAVE_SETENV + +#ifndef HAVE_UNSETENV +/** + * Compat unsetenv, removes variable from the environment. + */ +int +unsetenv(const char *name) { + const char *value = getenv(name); + if (value && strlen(value)) { + return setenv(name, "", 1); + } else { + errno = EINVAL; + return -1; + } +} +#endif // ! HAVE_UNSETENV diff --git a/pekwm/Compat.hh b/pekwm/Compat.hh new file mode 100644 index 0000000..03e0298 --- /dev/null +++ b/pekwm/Compat.hh @@ -0,0 +1,53 @@ +// +// Compat.hh for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _COMPAT_HH_ +#define _COMPAT_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include + +#ifndef HAVE_SWPRINTF +namespace std { + int swprintf(wchar_t *wcs, size_t maxlen, const wchar_t *format, ...); +} +#endif // HAVE_SWPRINTF + +#ifdef HAVE_SETENV +extern "C" { +#include +} +#else // ! HAVE_SETENV +int setenv(const char *name, const char *value, int overwrite); +#endif // HAVE_SETENV + +#ifdef HAVE_UNSETENV +extern "C" { +#include +} +#else // ! HAVE_UNSETENV +int unsetenv(const char *name); +#endif // HAVE_UNSETENV + +#ifndef HAVE_TIMERSUB +#define timersub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) +#endif // HAVE_TIMERSUB + +#endif // _COMPAT_HH_ diff --git a/pekwm/Completer.cc b/pekwm/Completer.cc new file mode 100644 index 0000000..6b5b7c5 --- /dev/null +++ b/pekwm/Completer.cc @@ -0,0 +1,344 @@ +// +// Completer.cc for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +#include "Completer.hh" +#include "Config.hh" +#include "Util.hh" +#include "PWinObj.hh" +#include "MenuHandler.hh" + +using std::copy; +using std::cerr; +using std::wcerr; +using std::endl; +using std::list; +using std::vector; +using std::string; +using std::wstring; +using std::pair; + +ActionCompleterMethod::StateMatch ActionCompleterMethod::STATE_MATCHES[] = { + StateMatch(ActionCompleterMethod::STATE_STATE, L"set"), + StateMatch(ActionCompleterMethod::STATE_STATE, L"unset"), + StateMatch(ActionCompleterMethod::STATE_STATE, L"toggle"), + StateMatch(ActionCompleterMethod::STATE_MENU, L"showmenu") + }; + +/** + * Find matches for word in completions_list and add to completions. + */ +unsigned int +CompleterMethod::complete_word(completions_list &completions_list, + complete_list &completions, + const std::wstring &word) +{ + unsigned int completed = 0, equality = -1; + + completions_it it(completions_list.begin()); + for (; it != completions_list.end(); ++it) { + if (it->first.size() < word.size()) { + continue; + } + equality = it->first.compare(0, word.size(), word, 0, word.size()); + if (equality == 0) { + completions.push_back(it->second); + completed++; + } + } + + return completed; +} + +/** + * Complete str with available path elements. + */ +unsigned int +PathCompleterMethod::complete(CompletionState &completion_state) +{ + return complete_word(_path_list, + completion_state.completions, + completion_state.word); +} + +/** + * Refresh available completions. + */ +void +PathCompleterMethod::refresh(void) +{ + // Clear out previous data + _path_list.clear(); + + vector path_parts; + Util::splitString(getenv("PATH") ? getenv("PATH") : "", path_parts, ":"); + + vector::iterator it(path_parts.begin()); + for (; it != path_parts.end(); ++it) { + DIR *dh = opendir(it->c_str()); + if (dh) { + refresh_path(dh, Util::to_wide_str(*it)); + closedir(dh); + } + } + + _path_list.unique(); + _path_list.sort(); +} + +/** + * Refresh single directory. + */ +void +PathCompleterMethod::refresh_path(DIR *dh, const std::wstring path) +{ + struct dirent *entry; + while ((entry = readdir(dh)) != 0) { + if (entry->d_name[0] == '.') { + continue; + } + + wstring name(Util::to_wide_str(entry->d_name)); + _path_list.push_back(pair(name, name)); + _path_list.push_back(pair(path + L"/" + name, + path + L"/" + name)); + } +} + +/** + * Check if str matches state prefix. + */ +bool +ActionCompleterMethod::StateMatch::is_state(const wstring &str, size_t pos) +{ + if (str.size() - pos < _prefix_len) { + return false; + } else { + return str.compare(pos, _prefix_len, _prefix, _prefix_len) == 0; + } +} + +/** + * Complete action. + */ +unsigned int +ActionCompleterMethod::complete(CompletionState &state) +{ + State type_state = find_state(state); + switch (type_state) { + case STATE_STATE: + return complete_word(_state_list, state.completions, state.word_lower); + case STATE_MENU: + return complete_word(_menu_list, state.completions, state.word_lower); + case STATE_ACTION: + return complete_word(_action_list, state.completions, state.word_lower); + case STATE_NO: + default: + return 0; + } +} + +/** + * Build list of completions from available actions. + */ +void +ActionCompleterMethod::refresh(void) +{ + completions_list_from_name_list(Config::instance()->getActionNameList(), + _action_list); + completions_list_from_name_list(Config::instance()->getStateNameList(), + _state_list); + completions_list_from_name_list(MenuHandler::instance()->getMenuNames(), + _menu_list); +} + +/** + * Build completions_list from a list with strings. + */ +void +ActionCompleterMethod::completions_list_from_name_list(std::list name_list, + completions_list &completions_list) +{ + completions_list.clear(); + list::iterator it(name_list.begin()); + for (; it != name_list.end(); ++it) { + wstring name(Util::to_wide_str(*it)); + wstring name_lower(name); + Util::to_lower(name_lower); + completions_list.push_back(pair(name_lower, name)); + } + completions_list.unique(); + completions_list.sort(); +} + +/** + * Detect state being completed + */ +ActionCompleterMethod::State +ActionCompleterMethod::find_state(CompletionState &completion_state) +{ + State state = STATE_ACTION; + if (completion_state.word_begin != 0) { + state = find_state_match(completion_state.part_lower, + completion_state.part_begin); + } + return state; +} + +/** + * Find matching state. + */ +ActionCompleterMethod::State +ActionCompleterMethod::find_state_match(const std::wstring &str, size_t pos) +{ + for (int i = 0; i < STATE_NUM; ++i) { + if (STATE_MATCHES[i].is_state(str, pos)) { + return STATE_MATCHES[i].get_state(); + } + } + return STATE_NO; +} + +/** + * Completer destructor, free up resources used by methods. + */ +Completer::~Completer(void) +{ + list::iterator it(_methods.begin()); + for (; it != _methods.end(); ++it) { + delete *it; + } +} + +/** + * Find completions for string with the cursor at position. + */ +complete_list +Completer::find_completions(const wstring &str, unsigned int pos) +{ + // Get current part of str, if it is empty return no completions. + CompletionState state; + state.part = state.part_lower = get_part(str, pos, state.part_begin, state.part_end); + if (! state.part.size()) { + return state.completions; + } + + // Get word at position, the one that will be completed + state.word = state.word_lower = get_word_at_position(str, pos, state.word_begin, state.word_end); + + Util::to_lower(state.part_lower); + Util::to_lower(state.word_lower); + + // Go through completer methods and add completions. + list::iterator it(_methods.begin()); + for (; it != _methods.end(); ++it) { + if ((*it)->can_complete(state.part)) { + (*it)->complete(state); + } + } + + return state.completions; +} + +/** + * Perform actual completion, returns new string with completion + * inserted if any. + * + * @param str String to complete. + * @param pos Cursor position in string. + * @return Completed string. + */ +wstring +Completer::do_complete(const wstring &str, unsigned int &pos, + complete_list &completions, complete_it &it) +{ + // Do not perform completion if there is nothing to complete + if (! completions.size()) { + pos = str.size(); + return str; + } + // Wrap completions, return original string + if (it == completions.end()) { + it = completions.begin(); + pos = str.size(); + return str; + } + + // Get current word, this is the one being replaced + size_t word_begin, word_end; + wstring word(get_word_at_position(str, pos, word_begin, word_end)); + + // Replace the current word + wstring completed(str); + completed.replace(word_begin, word_end - word_begin, *it); + + // Update position + pos = word_begin + it->size(); + + // Increment completion + it++; + + return completed; +} + +/** + * Find current part being completed, string can be split up with + * separators and only one part should be treated at a time. + * + * @param str String to find part in. + * @param pos Position in string. + * @param part_begin + * @param part_end + * @return Current part of string. + */ +wstring +Completer::get_part(const wstring &str, unsigned int pos, size_t &part_begin, size_t &part_end) +{ + // If no separators are defined, do nothing. + if (! _separators.size()) { + return str; + } + + // Get beginning and end of string, add 1 for removal of separator + part_begin = String::safe_position(str.find_last_of(_separators, pos), 0, 1); + part_end = String::safe_position(str.find_first_of(_separators, pos), str.size()); + + // Strip spaces from the beginning of the string + part_begin = String::safe_position(str.find_first_not_of(L" \t", part_begin), part_end); + + return str.substr(part_begin, part_end - part_begin); +} + +/** + * Get word at position. + */ +wstring +Completer::get_word_at_position(const wstring &str, unsigned int pos, size_t &word_begin, size_t &word_end) +{ + // Get beginning and end of string, add 1 for removal of separator + word_begin = String::safe_position(str.find_last_of(L" \t", pos), 0, 1); + word_end = String::safe_position(str.find_first_of(L" \t", pos), str.size()); + + return str.substr(word_begin, word_end - word_begin); +} diff --git a/pekwm/Completer.hh b/pekwm/Completer.hh new file mode 100644 index 0000000..e64179b --- /dev/null +++ b/pekwm/Completer.hh @@ -0,0 +1,194 @@ +// +// Completer.cc for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _COMPLETER_HH_ +#define _COMPLETER_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include + +extern "C" { +#include +#include +} + +typedef std::list complete_list; +typedef complete_list::iterator complete_it; +typedef std::list > completions_list; +typedef completions_list::iterator completions_it; + +/** + * State data used during completion. + */ +class CompletionState { +public: + std::wstring part; + std::wstring part_lower; + size_t part_begin, part_end; + std::wstring word; + std::wstring word_lower; + size_t word_begin, word_end; + complete_list completions; +}; + +/** + * Base class for completer methods, provides method to see if it + * should be used and also actual completion. + */ +class CompleterMethod +{ +public: + /** Constructor for CompleterMethod, refresh completion list. */ + CompleterMethod(void) { } + /** Destructor for CompleterMethod */ + virtual ~CompleterMethod(void) { } + + /** Return true if method can complete. */ + virtual bool can_complete(const std::wstring &str) { return false; } + /** Find completions for string. */ + virtual unsigned int complete(CompletionState &completion_state) { return 0; } + /** Refresh completion list. */ + virtual void refresh(void) { } + +protected: + unsigned int complete_word(completions_list &completions_list, + complete_list &completions, + const std::wstring &word); +}; + +/** + * Null completer, provides no completion independent of input. + */ +class NullCompleterMethod : public CompleterMethod +{ +public: + /** Null completer can always complete. */ + virtual bool can_complete(const std::wstring &str) { return true; } + /** Find completions, does nothing. */ + virtual unsigned int complete(CompletionState &completion_state) { return 0; } +}; + +/** + * Path completer, provides completion of elements in the path. + */ +class PathCompleterMethod : public CompleterMethod +{ +public: + /** Constructor for PathCompleter method. */ + PathCompleterMethod(void) : CompleterMethod() { refresh(); } + /** Destructor for PathCompleterMethod */ + virtual ~PathCompleterMethod(void) { } + + /** Path completer can always complete. */ + virtual bool can_complete(const std::wstring &str) { return true; } + virtual unsigned int complete(CompletionState &completion_state); + virtual void refresh(void); + +private: + void refresh_path(DIR *dh, const std::wstring path); + +private: + completions_list _path_list; /**< List of all elements in path. */ +}; + +/** + * Action completer, provides completion of all available actions in + * pekwm. + */ +class ActionCompleterMethod : public CompleterMethod +{ +public: + /** + * States for context sensitive ActionCompleterMethod completions. + */ + enum State { + STATE_ACTION, + STATE_STATE, + STATE_MENU, + STATE_NO, + STATE_NUM = 5 + }; + + /** + * Context match information. + */ + class StateMatch { + public: + StateMatch(State state, const wchar_t *prefix) + : _prefix(prefix), _prefix_len(wcslen(prefix)), _state(state) { + } + + State get_state(void) { return _state; } + bool is_state(const std::wstring &str, size_t pos); + private: + const wchar_t *_prefix; /**< Matching prefix */ + const size_t _prefix_len; /**< */ + State _state; /**< State */ + }; + + /** Constructor for ActionCompleter method. */ + ActionCompleterMethod(void) : CompleterMethod() { refresh(); } + /** Destructor for ActionCompleterMethod */ + virtual ~ActionCompleterMethod(void) { } + + /** Path completer can always complete. */ + virtual bool can_complete(const std::wstring &str) { return true; } + virtual unsigned int complete(CompletionState &completion_state); + virtual void refresh(void); + +private: + void completions_list_from_name_list(std::list name_list, + completions_list &completions_list); + + State find_state(CompletionState &completion_state); + size_t find_state_word_start(const std::wstring &str); + State find_state_match(const std::wstring &str, size_t pos); + +private: + completions_list _action_list; /**< List of all available actions. */ + completions_list _state_list; /**< List of parameters to state actions. */ + completions_list _menu_list; /**< List of parameters to state actions. */ + static StateMatch STATE_MATCHES[]; /**< List of known states with matching data. */ +}; + +/** + * Completer class, has a set of completer methods which provides + * completions. Handles the string handling to detect the action, + * replacing completion results etc. + */ +class Completer +{ +public: + /** Completer constructor */ + Completer(const std::wstring separators = L"") : _separators(separators) { } + /** Completer destructor. */ + ~Completer(void); + + /** Add method to completer. */ + void add_method(CompleterMethod *method) { _methods.push_back(method); } + + complete_list find_completions(const std::wstring &str, unsigned int pos); + std::wstring do_complete(const std::wstring &str, unsigned int &pos, + complete_list &completions, complete_it &it); + +private: + std::wstring get_part(const std::wstring &str, unsigned int pos, + size_t &part_begin, size_t &part_end); + std::wstring get_word_at_position(const std::wstring &str, unsigned int pos, + size_t &word_begin, size_t &word_end); + +private: + std::list _methods; /**< List of CompleterMethods. */ + const std::wstring _separators; /**< String with separator characters. */ +}; + +#endif // _COMPLETER_HH_ diff --git a/pekwm/Config.cc b/pekwm/Config.cc new file mode 100644 index 0000000..15fbcd0 --- /dev/null +++ b/pekwm/Config.cc @@ -0,0 +1,1882 @@ +// +// Config.cc for pekwm +// Copyright © 2002-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Config.hh" + +#include "Compat.hh" +#include "Util.hh" +#include "PScreen.hh" // for DPY in keyconfig code + +#include +#include + +#include + +extern "C" { +#include +#include +#include +} + +using std::cerr; +using std::endl; +using std::string; +using std::wstring; +using std::list; +using std::map; +using std::vector; +using std::pair; +using std::ifstream; +using std::ofstream; +using std::strtol; +using std::getenv; + +Config* Config::_instance = 0; + +const int FRAME_MASK = + FRAME_OK|FRAME_BORDER_OK|CLIENT_OK|WINDOWMENU_OK| + KEYGRABBER_OK|BUTTONCLICK_OK; +const int ANY_MASK = + KEYGRABBER_OK|FRAME_OK|FRAME_BORDER_OK|CLIENT_OK|ROOTCLICK_OK| + BUTTONCLICK_OK|WINDOWMENU_OK|ROOTMENU_OK|SCREEN_EDGE_OK; + +/** + * Parse width and height limits. + */ +bool +SizeLimits::parse(const std::string &minimum, const std::string &maximum) +{ + return (parseLimit(minimum, _limits[WIDTH_MIN], _limits[HEIGHT_MIN]) + && parseLimit(maximum, _limits[WIDTH_MAX], _limits[HEIGHT_MAX])); +} + +/** + * Parse single limit. + */ +bool +SizeLimits::parseLimit(const std::string &limit, unsigned int &min, unsigned int &max) +{ + bool status = false; + vector tokens; + if ((Util::splitString(limit, tokens, "x", 2, true)) == 2) { + min = strtol(tokens[0].c_str(), 0, 10); + max = strtol(tokens[1].c_str(), 0, 10); + status = true; + } else { + min = 0; + max = 0; + } + + return status; +} + +//! @brief Constructor for Config class +Config::Config(void) : + _moveresize_edgeattract(0), _moveresize_edgeresist(0), + _moveresize_woattract(0), _moveresize_woresist(0), + _moveresize_opaquemove(0), _moveresize_opaqueresize(0), + _screen_workspaces(4), _screen_pixmap_cache_size(20), + _screen_workspaces_per_row(0), _screen_workspace_name_default(L"Workspace"), + _screen_edge_indent(false), + _screen_doubleclicktime(250), _screen_fullscreen_above(true), + _screen_fullscreen_detect(true), + _screen_showframelist(true), + _screen_show_status_window(true), _screen_show_status_window_on_root(false), + _screen_show_client_id(false), + _screen_show_workspace_indicator(500), _screen_workspace_indicator_scale(16), +#ifdef OPACITY + _screen_workspace_indicator_opacity(EWMH_OPAQUE_WINDOW), +#endif // OPACITY + _screen_place_new(true), _screen_focus_new(false), + _screen_focus_new_child(true), _screen_honour_randr(true), + _screen_honour_aspectratio(true), + _screen_placement_row(false), + _screen_placement_ltr(true), _screen_placement_ttb(true), + _screen_placement_offset_x(0), _screen_placement_offset_y(0), + _screen_client_unique_name(true), + _screen_client_unique_name_pre(" #"), _screen_client_unique_name_post(""), + _screen_report_all_clients(false), + _menu_select_mask(0), _menu_enter_mask(0), _menu_exec_mask(0), + _menu_display_icons(true), +#ifdef OPACITY + _menu_focus_opacity(EWMH_OPAQUE_WINDOW), + _menu_unfocus_opacity(EWMH_OPAQUE_WINDOW), +#endif // OPACITY + _cmd_dialog_history_unique(true), _cmd_dialog_history_size(1024), + _cmd_dialog_history_file("~/.pekwm/history"), _cmd_dialog_history_save_interval(16), + _harbour_da_min_s(0), _harbour_da_max_s(0), + _harbour_ontop(true), _harbour_maximize_over(false), + _harbour_placement(TOP), _harbour_orientation(TOP_TO_BOTTOM), _harbour_head_nr(0) +#ifdef OPACITY + ,_harbour_opacity(EWMH_OPAQUE_WINDOW) +#endif // OPACITY +{ + if (_instance) { + throw string("Config, trying to create multiple instances"); + } + _instance = this; + + for (uint i = 0; i <= SCREEN_EDGE_NO; ++i) { + _screen_edge_sizes.push_back(0); + } + + // fill parsing maps + _action_map[""] = pair(ACTION_NO, 0); + _action_map["Focus"] = pair(ACTION_FOCUS, ANY_MASK); + _action_map["UnFocus"] = pair(ACTION_UNFOCUS, ANY_MASK); + + _action_map["Set"] = pair(ACTION_SET, ANY_MASK); + _action_map["Unset"] = pair(ACTION_UNSET, ANY_MASK); + _action_map["Toggle"] = pair(ACTION_TOGGLE, ANY_MASK); + + _action_map["MaxFill"] = pair(ACTION_MAXFILL, FRAME_MASK); + _action_map["GrowDirection"] = pair(ACTION_GROW_DIRECTION, FRAME_MASK); + _action_map["Close"] = pair(ACTION_CLOSE, FRAME_MASK); + _action_map["CloseFrame"] = pair(ACTION_CLOSE_FRAME, FRAME_MASK); + _action_map["Kill"] = pair(ACTION_KILL, FRAME_MASK); + _action_map["Raise"] = pair(ACTION_RAISE, FRAME_MASK); + _action_map["Lower"] = pair(ACTION_LOWER, FRAME_MASK); + _action_map["ActivateOrRaise"] = pair(ACTION_ACTIVATE_OR_RAISE, FRAME_MASK); + _action_map["ActivateClientRel"] = pair(ACTION_ACTIVATE_CLIENT_REL, FRAME_MASK); + _action_map["MoveClientRel"] = pair(ACTION_MOVE_CLIENT_REL, FRAME_MASK); + _action_map["ActivateClient"] = pair(ACTION_ACTIVATE_CLIENT, FRAME_MASK); + _action_map["ActivateClientNum"] = pair(ACTION_ACTIVATE_CLIENT_NUM, KEYGRABBER_OK); + _action_map["Resize"] = pair(ACTION_RESIZE, BUTTONCLICK_OK|CLIENT_OK|FRAME_OK|FRAME_BORDER_OK); + _action_map["Move"] = pair(ACTION_MOVE, FRAME_OK|FRAME_BORDER_OK|CLIENT_OK); + _action_map["MoveResize"] = pair(ACTION_MOVE_RESIZE, KEYGRABBER_OK); + _action_map["GroupingDrag"] = pair(ACTION_GROUPING_DRAG, FRAME_OK|CLIENT_OK); + _action_map["WarpToWorkspace"] = pair(ACTION_WARP_TO_WORKSPACE, SCREEN_EDGE_OK); + _action_map["MoveToEdge"] = pair(ACTION_MOVE_TO_EDGE, KEYGRABBER_OK); + _action_map["NextFrame"] = pair(ACTION_NEXT_FRAME, KEYGRABBER_OK|ROOTCLICK_OK|SCREEN_EDGE_OK); + _action_map["PrevFrame"] = pair(ACTION_PREV_FRAME, KEYGRABBER_OK|ROOTCLICK_OK|SCREEN_EDGE_OK); + _action_map["NextFrameMRU"] = pair(ACTION_NEXT_FRAME_MRU, KEYGRABBER_OK|ROOTCLICK_OK|SCREEN_EDGE_OK); + _action_map["PrevFrameMRU"] = pair(ACTION_PREV_FRAME_MRU, KEYGRABBER_OK|ROOTCLICK_OK|SCREEN_EDGE_OK); + _action_map["FocusDirectional"] = pair(ACTION_FOCUS_DIRECTIONAL, FRAME_MASK); + _action_map["AttachMarked"] = pair(ACTION_ATTACH_MARKED, FRAME_MASK); + _action_map["AttachClientInNextFrame"] = pair(ACTION_ATTACH_CLIENT_IN_NEXT_FRAME, FRAME_MASK); + _action_map["AttachClientInPrevFrame"] = pair(ACTION_ATTACH_CLIENT_IN_PREV_FRAME, FRAME_MASK); + _action_map["FindClient"] = pair(ACTION_FIND_CLIENT, ANY_MASK); + _action_map["GotoClientID"] = pair(ACTION_GOTO_CLIENT_ID, ANY_MASK); + _action_map["Detach"] = pair(ACTION_DETACH, FRAME_MASK); + _action_map["SendToWorkspace"] = pair(ACTION_SEND_TO_WORKSPACE, ANY_MASK); + _action_map["GoToWorkspace"] = pair(ACTION_GOTO_WORKSPACE, ANY_MASK ); + _action_map["Exec"] = pair(ACTION_EXEC, FRAME_MASK|ROOTMENU_OK|ROOTCLICK_OK|SCREEN_EDGE_OK); + _action_map["Reload"] = pair(ACTION_RELOAD, KEYGRABBER_OK|ROOTMENU_OK); + _action_map["Restart"] = pair(ACTION_RESTART, KEYGRABBER_OK|ROOTMENU_OK); + _action_map["RestartOther"] = pair(ACTION_RESTART_OTHER, KEYGRABBER_OK|ROOTMENU_OK); + _action_map["Exit"] = pair(ACTION_EXIT, KEYGRABBER_OK|ROOTMENU_OK); + _action_map["ShowCmdDialog"] = pair(ACTION_SHOW_CMD_DIALOG, KEYGRABBER_OK|ROOTCLICK_OK|SCREEN_EDGE_OK|ROOTMENU_OK|WINDOWMENU_OK); + _action_map["ShowSearchDialog"] = pair(ACTION_SHOW_SEARCH_DIALOG, KEYGRABBER_OK|ROOTCLICK_OK|SCREEN_EDGE_OK|ROOTMENU_OK|WINDOWMENU_OK); + _action_map["ShowMenu"] = pair(ACTION_SHOW_MENU, FRAME_MASK|ROOTCLICK_OK|SCREEN_EDGE_OK|ROOTMENU_OK|WINDOWMENU_OK); + _action_map["HideAllMenus"] = pair(ACTION_HIDE_ALL_MENUS, FRAME_MASK|ROOTCLICK_OK|SCREEN_EDGE_OK); + _action_map["SubMenu"] = pair(ACTION_MENU_SUB, ROOTMENU_OK|WINDOWMENU_OK); + _action_map["Dynamic"] = pair(ACTION_MENU_DYN, ROOTMENU_OK|WINDOWMENU_OK); + _action_map["SendKey"] = pair(ACTION_SEND_KEY, ANY_MASK); +#ifdef OPACITY + _action_map["SetOpacity"] = pair(ACTION_SET_OPACITY, FRAME_MASK); +#endif // OPACITY + + _action_access_mask_map[""] = ACTION_ACCESS_NO; + _action_access_mask_map["MOVE"] = ACTION_ACCESS_MOVE; + _action_access_mask_map["RESIZE"] = ACTION_ACCESS_RESIZE; + _action_access_mask_map["ICONIFY"] = ACTION_ACCESS_MINIMIZE; + _action_access_mask_map["SHADE"] = ACTION_ACCESS_SHADE; + _action_access_mask_map["STICK"] = ACTION_ACCESS_STICK; + _action_access_mask_map["MAXIMIZEHORIZONTAL"] = ACTION_ACCESS_MAXIMIZE_HORZ; + _action_access_mask_map["MAXIMIZEVERTICAL"] = ACTION_ACCESS_MAXIMIZE_VERT; + _action_access_mask_map["FULLSCREEN"] = ACTION_ACCESS_FULLSCREEN; + _action_access_mask_map["SETWORKSPACE"] = ACTION_ACCESS_CHANGE_DESKTOP; + _action_access_mask_map["CLOSE"] = ACTION_ACCESS_CLOSE; + + _placement_map[""] = PLACE_NO; + _placement_map["SMART"] = PLACE_SMART; + _placement_map["MOUSENOTUNDER"] = PLACE_MOUSE_NOT_UNDER; + _placement_map["MOUSECENTERED"] = PLACE_MOUSE_CENTERED; + _placement_map["MOUSETOPLEFT"] = PLACE_MOUSE_TOP_LEFT; + _placement_map["CENTEREDONPARENT"] = PLACE_CENTERED_ON_PARENT; + + _edge_map[""] = NO_EDGE; + _edge_map["TOPLEFT"] = TOP_LEFT; + _edge_map["TOPEDGE"] = TOP_EDGE; + _edge_map["TOPCENTEREDGE"] = TOP_CENTER_EDGE; + _edge_map["TOPRIGHT"] = TOP_RIGHT; + _edge_map["BOTTOMRIGHT"] = BOTTOM_RIGHT; + _edge_map["BOTTOMEDGE"] = BOTTOM_EDGE; + _edge_map["BOTTOMCENTEREDGE"] = BOTTOM_CENTER_EDGE; + _edge_map["BOTTOMLEFT"] = BOTTOM_LEFT; + _edge_map["LEFTEDGE"] = LEFT_EDGE; + _edge_map["LEFTCENTEREDGE"] = LEFT_CENTER_EDGE; + _edge_map["RIGHTEDGE"] = RIGHT_EDGE; + _edge_map["RIGHTCENTEREDGE"] = RIGHT_CENTER_EDGE; + _edge_map["CENTER"] = CENTER; + + _raise_map[""] = NO_RAISE; + _raise_map["ALWAYSRAISE"] = ALWAYS_RAISE; + _raise_map["ENDRAISE"] = END_RAISE; + _raise_map["NEVERRAISE"] = NEVER_RAISE; + + _skip_map[""] = SKIP_NONE; + _skip_map["MENUS"] = SKIP_MENUS; + _skip_map["FOCUSTOGGLE"] = SKIP_FOCUS_TOGGLE; + _skip_map["SNAP"] = SKIP_SNAP; + _skip_map["PAGER"] = SKIP_PAGER; + _skip_map["TASKBAR"] = SKIP_TASKBAR; + + _layer_map[""] = LAYER_NONE; + _layer_map["DESKTOP"] = LAYER_DESKTOP; + _layer_map["BELOW"] = LAYER_BELOW; + _layer_map["NORMAL"] = LAYER_NORMAL; + _layer_map["ONTOP"] = LAYER_ONTOP; + _layer_map["HARBOUR"] = LAYER_DOCK; + _layer_map["ABOVEHARBOUR"] = LAYER_ABOVE_DOCK; + _layer_map["MENU"] = LAYER_MENU; + + _moveresize_map[""] = NO_MOVERESIZE_ACTION; + _moveresize_map["MOVEHORIZONTAL"] = MOVE_HORIZONTAL; + _moveresize_map["MOVEVERTICAL"] = MOVE_VERTICAL; + _moveresize_map["RESIZEHORIZONTAL"] = RESIZE_HORIZONTAL; + _moveresize_map["RESIZEVERTICAL"] = RESIZE_VERTICAL; + _moveresize_map["MOVESNAP"] = MOVE_SNAP; + _moveresize_map["CANCEL"] = MOVE_CANCEL; + _moveresize_map["END"] = MOVE_END; + + _inputdialog_map[""] = INPUT_NO_ACTION; + _inputdialog_map["INSERT"] = INPUT_INSERT; + _inputdialog_map["ERASE"] = INPUT_REMOVE; + _inputdialog_map["CLEAR"] = INPUT_CLEAR; + _inputdialog_map["CLEARFROMCURSOR"] = INPUT_CLEARFROMCURSOR; + _inputdialog_map["EXEC"] = INPUT_EXEC; + _inputdialog_map["CLOSE"] = INPUT_CLOSE; + _inputdialog_map["COMPLETE"] = INPUT_COMPLETE; + _inputdialog_map["COMPLETEABORT"] = INPUT_COMPLETE_ABORT; + _inputdialog_map["CURSNEXT"] = INPUT_CURS_NEXT; + _inputdialog_map["CURSPREV"] = INPUT_CURS_PREV; + _inputdialog_map["CURSEND"] = INPUT_CURS_END; + _inputdialog_map["CURSBEGIN"] = INPUT_CURS_BEGIN; + _inputdialog_map["HISTNEXT"] = INPUT_HIST_NEXT; + _inputdialog_map["HISTPREV"] = INPUT_HIST_PREV; + + _direction_map[""] = DIRECTION_NO; + _direction_map["UP"] = DIRECTION_UP; + _direction_map["DOWN"] = DIRECTION_DOWN; + _direction_map["LEFT"] = DIRECTION_LEFT; + _direction_map["RIGHT"] = DIRECTION_RIGHT; + + _workspace_change_map[""] = WORKSPACE_NO; + _workspace_change_map["LEFT"] = WORKSPACE_LEFT; + _workspace_change_map["PREV"] = WORKSPACE_PREV; + _workspace_change_map["RIGHT"] = WORKSPACE_RIGHT; + _workspace_change_map["NEXT"] = WORKSPACE_NEXT; + _workspace_change_map["PREVV"] = WORKSPACE_PREV_V; + _workspace_change_map["UP"] = WORKSPACE_UP; + _workspace_change_map["NEXTV"] = WORKSPACE_NEXT_V; + _workspace_change_map["DOWN"] = WORKSPACE_DOWN; + _workspace_change_map["LAST"] = WORKSPACE_LAST; + + _borderpos_map[""] = BORDER_NO_POS; + _borderpos_map["TOPLEFT"] = BORDER_TOP_LEFT; + _borderpos_map["TOP"] = BORDER_TOP; + _borderpos_map["TOPRIGHT"] = BORDER_TOP_RIGHT; + _borderpos_map["LEFT"] = BORDER_LEFT; + _borderpos_map["RIGHT"] = BORDER_RIGHT; + _borderpos_map["BOTTOMLEFT"] = BORDER_BOTTOM_LEFT; + _borderpos_map["BOTTOM"] = BORDER_BOTTOM; + _borderpos_map["BOTTOMRIGHT"] = BORDER_BOTTOM_RIGHT; + + _mouse_event_map[""] = MOUSE_EVENT_NO; + _mouse_event_map["BUTTONPRESS"] = MOUSE_EVENT_PRESS; + _mouse_event_map["BUTTONRELEASE"] = MOUSE_EVENT_RELEASE; + _mouse_event_map["DOUBLECLICK"] = MOUSE_EVENT_DOUBLE; + _mouse_event_map["MOTION"] = MOUSE_EVENT_MOTION; + _mouse_event_map["ENTER"] = MOUSE_EVENT_ENTER; + _mouse_event_map["LEAVE"] = MOUSE_EVENT_LEAVE; + _mouse_event_map["ENTERMOVING"] = MOUSE_EVENT_ENTER_MOVING; + _mouse_event_map["MOTIONPRESSED"] = MOUSE_EVENT_MOTION_PRESSED; + + _mod_map[""] = 0; + _mod_map["NONE"] = 0; + _mod_map["SHIFT"] = ShiftMask; + _mod_map["CTRL"] = ControlMask; + _mod_map["MOD1"] = Mod1Mask; + _mod_map["MOD2"] = Mod2Mask; + _mod_map["MOD3"] = Mod3Mask; + _mod_map["MOD4"] = Mod4Mask; + _mod_map["MOD5"] = Mod5Mask; + _mod_map["ANY"] = MOD_ANY; + + _action_state_map[""] = ACTION_STATE_NO; + _action_state_map["Maximized"] = ACTION_STATE_MAXIMIZED; + _action_state_map["Fullscreen"] = ACTION_STATE_FULLSCREEN; + _action_state_map["Shaded"] = ACTION_STATE_SHADED; + _action_state_map["Sticky"] = ACTION_STATE_STICKY; + _action_state_map["AlwaysOnTop"] = ACTION_STATE_ALWAYS_ONTOP; + _action_state_map["AlwaysBelow"] = ACTION_STATE_ALWAYS_BELOW; + _action_state_map["DecorBorder"] = ACTION_STATE_DECOR_BORDER; + _action_state_map["DecorTitlebar"] = ACTION_STATE_DECOR_TITLEBAR; + _action_state_map["Iconified"] = ACTION_STATE_ICONIFIED; + _action_state_map["Tagged"] = ACTION_STATE_TAGGED; + _action_state_map["Marked"] = ACTION_STATE_MARKED; + _action_state_map["Skip"] = ACTION_STATE_SKIP; + _action_state_map["CfgDeny"] = ACTION_STATE_CFG_DENY; +#ifdef OPACITY + _action_state_map["Opaque"] = ACTION_STATE_OPAQUE; +#endif // OPACITY + _action_state_map["Title"] = ACTION_STATE_TITLE; + _action_state_map["HarbourHidden"] = ACTION_STATE_HARBOUR_HIDDEN; + _action_state_map["GlobalGrouping"] = ACTION_STATE_GLOBAL_GROUPING; + + + _cfg_deny_map["POSITION"] = CFG_DENY_POSITION; + _cfg_deny_map["SIZE"] = CFG_DENY_SIZE; + _cfg_deny_map["STACKING"] = CFG_DENY_STACKING; + _cfg_deny_map["ACTIVEWINDOW"] = CFG_DENY_ACTIVE_WINDOW; + _cfg_deny_map["MAXIMIZEDVERT"] = CFG_DENY_STATE_MAXIMIZED_VERT; + _cfg_deny_map["MAXIMIZEDHORZ"] = CFG_DENY_STATE_MAXIMIZED_HORZ; + _cfg_deny_map["HIDDEN"] = CFG_DENY_STATE_HIDDEN; + _cfg_deny_map["FULLSCREEN"] = CFG_DENY_STATE_FULLSCREEN; + _cfg_deny_map["ABOVE"] = CFG_DENY_STATE_ABOVE; + _cfg_deny_map["BELOW"] = CFG_DENY_STATE_BELOW; + + _menu_action_map[""] = ACTION_MENU_NEXT; + _menu_action_map["NEXTITEM"] = ACTION_MENU_NEXT; + _menu_action_map["PREVITEM"] = ACTION_MENU_PREV; + _menu_action_map["SELECT"] = ACTION_MENU_SELECT; + _menu_action_map["ENTERSUBMENU"] = ACTION_MENU_ENTER_SUBMENU; + _menu_action_map["LEAVESUBMENU"] = ACTION_MENU_LEAVE_SUBMENU; + _menu_action_map["CLOSE"] = ACTION_CLOSE; + + _harbour_placement_map[""] = NO_HARBOUR_PLACEMENT; + _harbour_placement_map["TOP"] = TOP; + _harbour_placement_map["LEFT"] = LEFT; + _harbour_placement_map["RIGHT"] = RIGHT; + _harbour_placement_map["BOTTOM"] = BOTTOM; + + _harbour_orientation_map[""] = NO_ORIENTATION; + _harbour_orientation_map["TOPTOBOTTOM"] = TOP_TO_BOTTOM; + _harbour_orientation_map["LEFTTORIGHT"] = TOP_TO_BOTTOM; + _harbour_orientation_map["BOTTOMTOTOP"] = BOTTOM_TO_TOP; + _harbour_orientation_map["RIGHTTOLEFT"] = BOTTOM_TO_TOP; + + // fill the mouse action map + _mouse_action_map[MOUSE_ACTION_LIST_TITLE_FRAME] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_TITLE_OTHER] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_CHILD_FRAME] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_CHILD_OTHER] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_ROOT] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_MENU] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_OTHER] = new list; + + _mouse_action_map[MOUSE_ACTION_LIST_EDGE_T] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_EDGE_B] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_EDGE_L] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_EDGE_R] = new list; + + _mouse_action_map[MOUSE_ACTION_LIST_BORDER_TL] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_BORDER_T] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_BORDER_TR] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_BORDER_L] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_BORDER_R] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_BORDER_BL] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_BORDER_B] = new list; + _mouse_action_map[MOUSE_ACTION_LIST_BORDER_BR] = new list; +} + +//! @brief Destructor for Config class +Config::~Config(void) +{ + _instance = 0; + + map* >::iterator it; + for (it = _mouse_action_map.begin(); it != _mouse_action_map.end(); ++it) { + delete it->second; + } +} + +/** + * Returns an array of NULL-terminated desktop names in UTF-8. + * + * @param names *names will be set to an array of desktop names or 0. The caller has to delete [] *names + * @param length *length will be set to the complete length of array *names points to or 0. + */ +void +Config::getDesktopNamesUTF8(uchar **names, uint *length) const +{ + if (! _screen_workspace_names.size()) { + *names = 0; + *length = 0; + return; + } + + // Convert strings to UTF-8 and calculate total length + string utf8_names; + vector::const_iterator it(_screen_workspace_names.begin()); + for (; it != _screen_workspace_names.end(); ++it) { + string utf8_name(Util::to_utf8_str(*it)); + utf8_names.append(utf8_name.c_str(), utf8_name.size() + 1); + } + + *names = new uchar[utf8_names.size()]; + ::memcpy(*names, utf8_names.c_str(), utf8_names.size()); + *length = utf8_names.size(); +} + +/** + * Sets the desktop names. + * + * @param names names is expected to point to an array of NULL-terminated utf8-strings. + * @param length The length of the array "names". + */ +void +Config::setDesktopNamesUTF8(char *names, ulong length) +{ + _screen_workspace_names.clear(); + + if (! names || ! length) { + return; + } + + for (ulong i = 0; i < length;) { + _screen_workspace_names.push_back(Util::from_utf8_str(names)); + i += strlen(names) + 1; + names += strlen(names) + 1; + } +} + +//! @brief Tries to load config_file, ~/.pekwm/config, SYSCONFDIR/config +bool +Config::load(const std::string &config_file) +{ + if (! Util::requireReload(_cfg_state, config_file)) { + return false; + } + + CfgParser cfg; + + _config_file = config_file; + bool success = tryHardLoadConfig(cfg, _config_file); + + // Make sure config is reloaded next time as content is dynamically + // generated from the configuration file. + if (! success || cfg.is_dynamic_content()) { + _cfg_state.clear(); + } else { + _cfg_state = cfg.get_file_list(); + } + + if (! success) { + cerr << " *** WARNING: unable to load configuration files!" << endl; + return false; + } + + // Update PEKWM_CONFIG_FILE environment if needed (to reflect active file) + char *cfg_env = getenv("PEKWM_CONFIG_FILE"); + if (! cfg_env || (strcmp(cfg_env, _config_file.c_str()) != 0)) { + setenv("PEKWM_CONFIG_FILE", _config_file.c_str(), 1); + } + + string o_file_mouse; // temporary filepath for mouseconfig + + CfgParser::Entry *section; + + // Get other config files dests. + section = cfg.get_entry_root()->find_section("FILES"); + if (section) { + loadFiles(section); + } + + // Parse moving / resizing options. + section = cfg.get_entry_root()->find_section("MOVERESIZE"); + if (section) { + loadMoveResize(section); + } + + // Screen, important stuff such as number of workspaces + section = cfg.get_entry_root()->find_section("SCREEN"); + if (section) { + loadScreen(section); + } + + section = cfg.get_entry_root()->find_section("MENU"); + if (section) { + loadMenu(section); + } + + section = cfg.get_entry_root()->find_section("CMDDIALOG"); + if (section) { + loadCmdDialog(section); + } + + section = cfg.get_entry_root()->find_section("HARBOUR"); + if (section) { + loadHarbour(section); + } + + return true; +} + +//! @brief Loads file section of configuration +//! @param section Pointer to FILES section. +void +Config::loadFiles(CfgParser::Entry *section) +{ + if (! section) { + return; + } + + list key_list; + key_list.push_back(new CfgParserKeyPath("KEYS", _files_keys, SYSCONFDIR "/keys")); + key_list.push_back(new CfgParserKeyPath("MOUSE", _files_mouse, SYSCONFDIR "/mouse")); + key_list.push_back(new CfgParserKeyPath("MENU", _files_menu, SYSCONFDIR "/menu")); + key_list.push_back(new CfgParserKeyPath("START", _files_start, SYSCONFDIR "/start")); + key_list.push_back(new CfgParserKeyPath("AUTOPROPS", _files_autoprops, SYSCONFDIR "/autoproperties")); + key_list.push_back(new CfgParserKeyPath("THEME", _files_theme, DATADIR "/pekwm/themes/default/theme")); + key_list.push_back(new CfgParserKeyPath("ICONS", _files_icon_path, DATADIR "/pekwm/icons")); + + // Parse + section->parse_key_values(key_list.begin(), key_list.end()); + + // Free up resources + for_each(key_list.begin(), key_list.end(), Util::Free()); +} + +//! @brief Loads MOVERESIZE section of main configuration +//! @param section Pointer to MOVERESIZE section. +void +Config::loadMoveResize(CfgParser::Entry *section) +{ + if (! section) { + return; + } + + list key_list; + key_list.push_back(new CfgParserKeyNumeric("EDGEATTRACT", _moveresize_edgeattract, 0, 0)); + key_list.push_back(new CfgParserKeyNumeric("EDGERESIST", _moveresize_edgeresist, 0, 0)); + key_list.push_back(new CfgParserKeyNumeric("WINDOWATTRACT",_moveresize_woattract, 0, 0)); + key_list.push_back(new CfgParserKeyNumeric("WINDOWRESIST", _moveresize_woresist, 0, 0)); + key_list.push_back(new CfgParserKeyBool("OPAQUEMOVE", _moveresize_opaquemove)); + key_list.push_back(new CfgParserKeyBool("OPAQUERESIZE", _moveresize_opaqueresize)); + + // Parse data + section->parse_key_values(key_list.begin(), key_list.end()); + + // Free up resources + for_each(key_list.begin(), key_list.end(), Util::Free()); +} + +//! @brief Loads SCREEN section of main configuration +//! @param section Pointer to SCREEN section. +void +Config::loadScreen(CfgParser::Entry *section) +{ + if (! section) { + return; + } + + // Parse data + string edge_size, workspace_names, trim_title; + CfgParser::Entry *value; + + list key_list; + key_list.push_back(new CfgParserKeyNumeric("WORKSPACES", _screen_workspaces, 4, 1)); + key_list.push_back(new CfgParserKeyNumeric("PIXMAPCACHESIZE", _screen_pixmap_cache_size)); + key_list.push_back(new CfgParserKeyNumeric("WORKSPACESPERROW", _screen_workspaces_per_row, 0, 0)); + key_list.push_back(new CfgParserKeyString("WORKSPACENAMES", workspace_names)); + key_list.push_back(new CfgParserKeyString("EDGESIZE", edge_size)); + key_list.push_back(new CfgParserKeyBool("EDGEINDENT", _screen_edge_indent)); + key_list.push_back(new CfgParserKeyNumeric("DOUBLECLICKTIME", _screen_doubleclicktime, 250, 0)); + key_list.push_back(new CfgParserKeyString("TRIMTITLE", trim_title)); + key_list.push_back(new CfgParserKeyBool("FULLSCREENABOVE", _screen_fullscreen_above, true)); + key_list.push_back(new CfgParserKeyBool("FULLSCREENDETECT", _screen_fullscreen_detect, true)); + key_list.push_back(new CfgParserKeyBool("SHOWFRAMELIST", _screen_showframelist)); + key_list.push_back(new CfgParserKeyBool("SHOWSTATUSWINDOW", _screen_show_status_window)); + key_list.push_back(new CfgParserKeyBool("SHOWSTATUSWINDOWCENTEREDONROOT", _screen_show_status_window_on_root, false)); + key_list.push_back(new CfgParserKeyBool("SHOWCLIENTID", _screen_show_client_id)); + key_list.push_back(new CfgParserKeyNumeric("SHOWWORKSPACEINDICATOR", + _screen_show_workspace_indicator, 500, 0)); + key_list.push_back(new CfgParserKeyNumeric("WORKSPACEINDICATORSCALE", + _screen_workspace_indicator_scale, 16, 2)); +#ifdef OPACITY + key_list.push_back(new CfgParserKeyNumeric("WORKSPACEINDICATOROPACITY", + _screen_workspace_indicator_opacity, 100, 0, 100)); +#endif // OPACITY + key_list.push_back(new CfgParserKeyBool("PLACENEW", _screen_place_new)); + key_list.push_back(new CfgParserKeyBool("FOCUSNEW", _screen_focus_new)); + key_list.push_back(new CfgParserKeyBool("FOCUSNEWCHILD", _screen_focus_new_child, true)); + key_list.push_back(new CfgParserKeyBool("HONOURRANDR", _screen_honour_randr, true)); + key_list.push_back(new CfgParserKeyBool("HONOURASPECTRATIO", _screen_honour_aspectratio, true)); + key_list.push_back(new CfgParserKeyBool("REPORTALLCLIENTS", _screen_report_all_clients, false)); + + // Parse data + section->parse_key_values(key_list.begin(), key_list.end()); + + // Free up resources + for_each(key_list.begin(), key_list.end(), Util::Free()); + key_list.clear(); + + // Convert input data + _screen_trim_title = Util::to_wide_str(trim_title); + + // Convert opacity from percent to absolute value + CONV_OPACITY(_screen_workspace_indicator_opacity); + + int edge_size_all = 0; + _screen_edge_sizes.clear(); + if (edge_size.size()) { + vector sizes; + if (Util::splitString(edge_size, sizes, " \t", 4) == 4) { + for (vector::iterator it(sizes.begin()); it != sizes.end(); ++it) { + _screen_edge_sizes.push_back(strtol(it->c_str(), 0, 10)); + } + } else { + edge_size_all = strtol(edge_size.c_str(), 0, 10); + } + } + + for (uint i = 0; i < SCREEN_EDGE_NO; ++i) { + _screen_edge_sizes.push_back(edge_size_all); + } + // Add SCREEN_EDGE_NO to the list for safety + _screen_edge_sizes.push_back(0); + + // Workspace names + _screen_workspace_names.clear(); + + vector vs; + if (Util::splitString(workspace_names, vs, ";", 0, true)) { + vector::iterator vs_it(vs.begin()); + for (; vs_it != vs.end(); ++vs_it) { + _screen_workspace_names.push_back(Util::to_wide_str(*vs_it)); + } + } + + CfgParser::Entry *sub = section->find_section("PLACEMENT"); + if (sub) { + value = sub->find_entry("MODEL"); + if (value) { + _screen_placementmodels.clear(); + + vector models; + if (Util::splitString(value->get_value(), models, " \t")) { + vector::iterator it(models.begin()); + for (; it != models.end(); ++it) + _screen_placementmodels.push_back(ParseUtil::getValue(*it, _placement_map)); + } + } + + CfgParser::Entry *sub_2 = sub->find_section("SMART"); + if (sub_2) { + key_list.push_back(new CfgParserKeyBool("ROW", _screen_placement_row)); + key_list.push_back(new CfgParserKeyBool("LEFTTORIGHT", _screen_placement_ltr)); + key_list.push_back(new CfgParserKeyBool("TOPTOBOTTOM", _screen_placement_ttb)); + key_list.push_back(new CfgParserKeyNumeric("OFFSETX", _screen_placement_offset_x, 0, 0)); + key_list.push_back(new CfgParserKeyNumeric("OFFSETY", _screen_placement_offset_y, 0, 0)); + + // Do the parsing + sub_2->parse_key_values(key_list.begin(), key_list.end()); + + // Freeup resources + for_each(key_list.begin(), key_list.end(), Util::Free()); + key_list.clear(); + } + } + + // Fallback value + if (! _screen_placementmodels.size()) { + _screen_placementmodels.push_back(PLACE_MOUSE_CENTERED); + } + + sub = section->find_section("UNIQUENAMES"); + if (sub) { + key_list.push_back(new CfgParserKeyBool("SETUNIQUE", _screen_client_unique_name)); + key_list.push_back(new CfgParserKeyString("PRE", _screen_client_unique_name_pre)); + key_list.push_back(new CfgParserKeyString("POST", _screen_client_unique_name_post)); + + // Parse data + sub->parse_key_values(key_list.begin(), key_list.end()); + + // Free up resources + for_each(key_list.begin(), key_list.end(), Util::Free()); + key_list.clear(); + } +} + +//! @brief Loads the MENU section of the main configuration +//! @param section Pointer to MENU section +void +Config::loadMenu(CfgParser::Entry *section) +{ + if (! section) { + return; + } + + list key_list; + string value_select, value_enter, value_exec; + + key_list.push_back(new CfgParserKeyString("SELECT", value_select, "MOTION", 0)); + key_list.push_back(new CfgParserKeyString("ENTER", value_enter, "BUTTONPRESS", 0)); + key_list.push_back(new CfgParserKeyString("EXEC", value_exec, "BUTTONRELEASE", 0)); + key_list.push_back(new CfgParserKeyBool("DISPLAYICONS", _menu_display_icons, true)); +#ifdef OPACITY + key_list.push_back(new CfgParserKeyNumeric("FOCUSOPACITY", _menu_focus_opacity, 100, 0, 100)); + key_list.push_back(new CfgParserKeyNumeric("UNFOCUSOPACITY", _menu_unfocus_opacity, 100, 0, 100)); +#endif // OPACITY + + // Parse data + section->parse_key_values(key_list.begin(), key_list.end()); + + _menu_select_mask = getMenuMask(value_select); + _menu_enter_mask = getMenuMask(value_enter); + _menu_exec_mask = getMenuMask(value_exec); + + // Free up resources + for_each(key_list.begin(), key_list.end(), Util::Free()); + key_list.clear(); + + // Parse icon size limits + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + if (*(*it) == "ICONS") { + loadMenuIcons((*it)->get_section()); + } + } + + // Convert opacity from percent to absolute value + CONV_OPACITY(_menu_focus_opacity); + CONV_OPACITY(_menu_unfocus_opacity); +} + +/** + * Load Icon size limits for menu. + */ +void +Config::loadMenuIcons(CfgParser::Entry *section) +{ + if (! section || ! section->get_value().size()) { + return; + } + + list key_list; + string minimum, maximum; + + key_list.push_back(new CfgParserKeyString("MINIMUM", minimum, "16x16", 3)); + key_list.push_back(new CfgParserKeyString("MAXIMUM", maximum, "16x16", 3)); + + // Parse data + section->parse_key_values(key_list.begin(), key_list.end()); + + for_each(key_list.begin(), key_list.end(), Util::Free()); + + SizeLimits limits; + if (limits.parse(minimum, maximum)) { + _menu_icon_limits[section->get_value()] = limits; + } +} + +/** + * Load configuration from CmdDialog section. + */ +void +Config::loadCmdDialog(CfgParser::Entry *section) +{ + if (! section) { + return; + } + + list key_list; + + key_list.push_back(new CfgParserKeyBool("HISTORYUNIQUE", _cmd_dialog_history_unique)); + key_list.push_back(new CfgParserKeyNumeric("HISTORYSIZE", _cmd_dialog_history_size, 1024, 1)); + key_list.push_back(new CfgParserKeyPath("HISTORYFILE", _cmd_dialog_history_file, "~/.pekwm/history")); + key_list.push_back(new CfgParserKeyNumeric("HISTORYSAVEINTERVAL", _cmd_dialog_history_save_interval, 16, 0)); + + section->parse_key_values(key_list.begin(), key_list.end()); + + for_each(key_list.begin(), key_list.end(), Util::Free()); +} + +//! @brief Loads the HARBOUR section of the main configuration +void +Config::loadHarbour(CfgParser::Entry *section) +{ + if (! section) { + return; + } + + list key_list; + string value_placement, value_orientation; + + key_list.push_back(new CfgParserKeyBool("ONTOP", _harbour_ontop, true)); + key_list.push_back(new CfgParserKeyBool("MAXIMIZEOVER", _harbour_maximize_over, false)); + key_list.push_back(new CfgParserKeyNumeric("HEAD", _harbour_head_nr, 0, 0)); + key_list.push_back(new CfgParserKeyString("PLACEMENT", value_placement, "RIGHT", 0)); + key_list.push_back(new CfgParserKeyString("ORIENTATION", value_orientation, "TOPTOBOTTOM", 0)); +#ifdef OPACITY + key_list.push_back(new CfgParserKeyNumeric("OPACITY", _harbour_opacity, 100, 0, 100)); +#endif // OPACITY + + // Parse data + section->parse_key_values(key_list.begin(), key_list.end()); + + // Free up resources + for_each(key_list.begin(), key_list.end(), Util::Free()); + key_list.clear(); + + // Convert opacity from percent to absolute value + CONV_OPACITY(_harbour_opacity); + + _harbour_placement = ParseUtil::getValue(value_placement, _harbour_placement_map); + _harbour_orientation = ParseUtil::getValue(value_orientation, _harbour_orientation_map); + if (_harbour_placement == NO_HARBOUR_PLACEMENT) { + _harbour_placement = RIGHT; + } + if (_harbour_orientation == NO_ORIENTATION) { + _harbour_orientation = TOP_TO_BOTTOM; + } + + CfgParser::Entry *sub = section->find_section("DOCKAPP"); + if (sub) { + key_list.push_back(new CfgParserKeyNumeric("SIDEMIN", _harbour_da_min_s, 64, 0)); + key_list.push_back(new CfgParserKeyNumeric("SIDEMAX", _harbour_da_max_s, 64, 0)); + + // Parse data + sub->parse_key_values(key_list.begin(), key_list.end()); + + // Free up resources + for_each(key_list.begin(), key_list.end(), Util::Free()); + key_list.clear(); + } +} + +//! @brief +ActionType +Config::getAction(const std::string &name, uint mask) +{ + pair val(ParseUtil::getValue >(name, _action_map)); + + if ((val.first != ACTION_NO) && (val.second&mask)) { + return val.first; + } + return ACTION_NO; +} + +ActionAccessMask +Config::getActionAccessMask(const std::string &name) +{ + ActionAccessMask mask = ParseUtil::getValue(name, _action_access_mask_map); + return mask; +} + +//! @brief +bool +Config::parseKey(const std::string &key_string, uint &mod, uint &key) +{ + // used for parsing + vector tok; + vector::iterator it; + + uint num; + + // chop the string up separating mods and the end key/button + if (Util::splitString(key_string, tok, " \t")) { + num = tok.size() - 1; + if ((tok[num].size() > 1) && (tok[num][0] == '#')) { + key = strtol(tok[num].c_str() + 1, 0, 10); + } else if (strcasecmp(tok[num].c_str(), "ANY") == 0) { + // Do no matching, anything goes. + key = 0; + } else { + KeySym keysym = XStringToKeysym(tok[num].c_str()); + + // XStringToKeysym() may fail. Perhaps we have luck after some + // simple transformations. First we convert the string to lowercase + // and try again. Then we try with only the first character in + // uppercase and at last we try a complete uppercase string. If all + // fails, we print a warning and return false. + if (keysym == NoSymbol) { + string str = tok[num]; + Util::to_lower(str); + keysym = XStringToKeysym(str.c_str()); + if (keysym == NoSymbol) { + str[0] = ::toupper(str[0]); + keysym = XStringToKeysym(str.c_str()); + if (keysym == NoSymbol) { + Util::to_upper(str); + keysym = XStringToKeysym(str.c_str()); + if (keysym == NoSymbol) { + cerr << " *** WARNING: Couldn't find keysym for " << tok[num] << endl; + return false; + } + } + } + } + key = XKeysymToKeycode(PScreen::instance()->getDpy(), keysym); + } + + // if the last token isn't an key/button, the action isn't valid + if ((key != 0) || (strcasecmp(tok[num].c_str(), "ANY") == 0)) { + tok.pop_back(); // remove the key/button + + // add the modifier + mod = 0; + for (it = tok.begin(); it != tok.end(); ++it) { + mod |= getMod(*it); + } + + return true; + } + } + + return false; +} + +bool +Config::parseButton(const std::string &button_string, uint &mod, uint &button) +{ + // used for parsing + vector tok; + vector::iterator it; + + // chop the string up separating mods and the end key/button + if (Util::splitString(button_string, tok, " \t")) { + // if the last token isn't an key/button, the action isn't valid + button = getMouseButton(tok[tok.size() - 1]); + if (button != BUTTON_NO) { + tok.pop_back(); // remove the key/button + + // add the modifier + mod = 0; + uint tmp_mod; + + for (it = tok.begin(); it != tok.end(); ++it) { + tmp_mod = getMod(*it); + if (tmp_mod == MOD_ANY) { + mod = MOD_ANY; + break; + } else { + mod |= tmp_mod; + } + } + + return true; + } + } + + return false; +} + +//! @brief Parse a single action and fills action. +//! @param action_string String representation of action. +//! @param action Action structure to fill in. +//! @param mask Mask action is valid for. +//! @return true on success, else false +bool +Config::parseAction(const std::string &action_string, Action &action, uint mask) +{ + vector tok; + + // chop the string up separating the action and parameters + if (Util::splitString(action_string, tok, " \t", 2)) { + action.setAction(getAction(tok[0], mask)); + if (action.getAction() != ACTION_NO) { + if (tok.size() == 2) { // we got enough tok for a parameter + switch (action.getAction()) { + case ACTION_EXEC: + case ACTION_RESTART_OTHER: + case ACTION_FIND_CLIENT: + case ACTION_SHOW_CMD_DIALOG: + case ACTION_SHOW_SEARCH_DIALOG: + case ACTION_SEND_KEY: + case ACTION_MENU_DYN: + action.setParamS(tok[1]); + break; + case ACTION_ACTIVATE_CLIENT_REL: + case ACTION_MOVE_CLIENT_REL: + case ACTION_GOTO_CLIENT_ID: + action.setParamI(0, strtol(tok[1].c_str(), 0, 10)); + break; + case ACTION_SET: + case ACTION_UNSET: + case ACTION_TOGGLE: + parseActionState(action, tok[1]); + break; + case ACTION_MAXFILL: + if ((Util::splitString(tok[1], tok, " \t", 2)) == 2) { + action.setParamI(0, Util::isTrue(tok[tok.size() - 2])); + action.setParamI(1, Util::isTrue(tok[tok.size() - 1])); + } else { + cerr << "*** WARNING: Missing argument to MaxFill." << endl; + } + break; + case ACTION_GROW_DIRECTION: + action.setParamI(0, ParseUtil::getValue(tok[1], _direction_map)); + break; + case ACTION_ACTIVATE_CLIENT_NUM: + action.setParamI(0, strtol(tok[1].c_str(), 0, 10) - 1); + if (action.getParamI(0) < 0) { + cerr << "*** WARNING: Negative number to ActivateClientNum." << endl; + action.setParamI(0, 0); + } + break; + case ACTION_WARP_TO_WORKSPACE: + case ACTION_SEND_TO_WORKSPACE: + case ACTION_GOTO_WORKSPACE: + action.setParamI(0, parseWorkspaceNumber(tok[1])); + break; + case ACTION_GROUPING_DRAG: + action.setParamI(0, Util::isTrue(tok[1])); + break; + case ACTION_MOVE_TO_EDGE: + action.setParamI(0, ParseUtil::getValue(tok[1], _edge_map)); + break; + case ACTION_NEXT_FRAME: + case ACTION_NEXT_FRAME_MRU: + case ACTION_PREV_FRAME: + case ACTION_PREV_FRAME_MRU: + if ((Util::splitString(tok[1], tok, " \t", 2)) == 2) { + action.setParamI(0, ParseUtil::getValue(tok[tok.size() - 2], + _raise_map)); + action.setParamI(1, Util::isTrue(tok[tok.size() - 1])); + } else { + action.setParamI(0, ParseUtil::getValue(tok[1], + _raise_map)); + action.setParamI(1, false); + } + break; + case ACTION_FOCUS_DIRECTIONAL: + if ((Util::splitString(tok[1], tok, " \t", 2)) == 2) { + action.setParamI(0, ParseUtil::getValue(tok[tok.size() - 2], _direction_map)); + action.setParamI(1, Util::isTrue(tok[tok.size() - 1])); // raise? + } else { + action.setParamI(0, ParseUtil::getValue(tok[1], _direction_map)); + action.setParamI(1, true); // default to raise + } + break; + case ACTION_RESIZE: + action.setParamI(0, 1 + ParseUtil::getValue(tok[1], _borderpos_map)); + break; + case ACTION_RAISE: + case ACTION_LOWER: + if ((Util::splitString(tok[1], tok, " \t", 1)) == 1) { + action.setParamI(0, Util::isTrue(tok[tok.size() - 1])); + } else { + action.setParamI(0, false); + } + break; + case ACTION_SHOW_MENU: + if ((Util::splitString(tok[1], tok, " \t", 2)) == 2) { + Util::to_upper(tok[tok.size() - 2]); + action.setParamS(tok[tok.size() - 2]); + action.setParamI(0, Util::isTrue(tok[tok.size() - 1])); + } else { + Util::to_upper(tok[1]); + action.setParamS(tok[1]); + action.setParamI(0, false); // Default to non-sticky + } + break; +#ifdef OPACITY + case ACTION_SET_OPACITY: + if ((Util::splitString(tok[1], tok, " \t", 2)) == 2) { + action.setParamI(0, std::atoi(tok[tok.size() - 2].c_str())); + action.setParamI(1, std::atoi(tok[tok.size() - 1].c_str())); + } else { + action.setParamI(0, std::atoi(tok[1].c_str())); + action.setParamI(1, std::atoi(tok[1].c_str())); + } + break; +#endif // OPACITY + default: + // do nothing + break; + } + } else { + switch (action.getAction()) { + case ACTION_MAXFILL: + action.setParamI(0, 1); + action.setParamI(1, 1); + break; + default: + // do nothing + break; + } + } + + return true; + } + } + + return false; +} + +bool +Config::parseActionAccessMask(const std::string &action_mask, uint &mask) +{ + mask = ACTION_ACCESS_NO; + + vector tok; + if (Util::splitString(action_mask, tok, " \t")) { + vector::iterator it(tok.begin()); + for (; it != tok.end(); ++it) { + mask |= getActionAccessMask(*it); + } + } + + return true; +} + +//! @brief +bool +Config::parseActionState(Action &action, const std::string &as_action) +{ + vector tok; + + // chop the string up separating the action and parameters + if (Util::splitString(as_action, tok, " \t", 2)) { + action.setParamI(0, ParseUtil::getValue(tok[0], _action_state_map)); + if (action.getParamI(0) != ACTION_STATE_NO) { + if (tok.size() == 2) { // we got enough tok for a parameter + string directions; + + switch (action.getParamI(0)) { + case ACTION_STATE_MAXIMIZED: + // Using copy of token here to silence valgrind checks. + directions = tok[1]; + + Util::splitString(directions, tok, " \t", 2); + if (tok.size() == 4) { + action.setParamI(1, Util::isTrue(tok[2])); + action.setParamI(2, Util::isTrue(tok[3])); + } else { + cerr << "*** WARNING: Missing argument to Maximized." << endl; + } + break; + case ACTION_STATE_TAGGED: + action.setParamI(1, Util::isTrue(tok[1])); + break; + case ACTION_STATE_SKIP: + action.setParamI(1, getSkip(tok[1])); + break; + case ACTION_STATE_CFG_DENY: + action.setParamI(1, getCfgDeny(tok[1])); + break; + case ACTION_STATE_DECOR: + case ACTION_STATE_TITLE: + action.setParamS(tok[1]); + break; + }; + } else { + switch (action.getParamI(0)) { + case ACTION_STATE_MAXIMIZED: + action.setParamI(1, 1); + action.setParamI(2, 1); + break; + default: + // do nothing + break; + } + } + + return true; + } + } + + return false; +} + +//! @brief +bool +Config::parseActions(const std::string &action_string, ActionEvent &ae, uint mask) +{ + vector tok; + vector::iterator it; + Action action; + + // reset the action event + ae.action_list.clear(); + + // chop the string up separating the actions + if (Util::splitString(action_string, tok, ";")) { + for (it = tok.begin(); it != tok.end(); ++it) { + if (parseAction(*it, action, mask)) { + ae.action_list.push_back(action); + action.clear(); + } + } + + return true; + } + + return false; +} + +//! @brief +bool +Config::parseActionEvent(CfgParser::Entry *section, ActionEvent &ae, uint mask, bool button) +{ + CfgParser::Entry *value = section->find_entry("ACTIONS"); + if (! value && section->get_section()) { + value = section->get_section()->find_entry("ACTIONS"); + } + + if (! value) { + return false; + } + + string str_button = section->get_value(); + if (! str_button.size()) { + if ((ae.type == MOUSE_EVENT_ENTER) || (ae.type == MOUSE_EVENT_LEAVE)) { + str_button = "1"; + } else { + return false; + } + } + + bool ok; + if (button) { + ok = parseButton(str_button, ae.mod, ae.sym); + } else { + ok = parseKey(str_button, ae.mod, ae.sym); + } + + if (ok) { + return parseActions(value->get_value(), ae, mask); + } + return false; +} + +//! @brief +bool +Config::parseMoveResizeAction(const std::string &action_string, Action &action) +{ + vector tok; + + // Chop the string up separating the actions. + if (Util::splitString(action_string, tok, " \t", 2)) { + action.setAction(ParseUtil::getValue(tok[0], + _moveresize_map)); + if (action.getAction() != NO_MOVERESIZE_ACTION) { + if (tok.size() == 2) { // we got enough tok for a paremeter + switch (action.getAction()) { + case MOVE_HORIZONTAL: + case MOVE_VERTICAL: + case RESIZE_HORIZONTAL: + case RESIZE_VERTICAL: + case MOVE_SNAP: + action.setParamI(0, strtol(tok[1].c_str(), 0, 10)); + break; + default: + // Do nothing. + break; + } + } + + return true; + } + } + + return false; +} + +//! @brief +bool +Config::parseMoveResizeActions(const std::string &action_string, ActionEvent& ae) +{ + vector tok; + vector::iterator it; + Action action; + + // reset the action event + ae.action_list.clear(); + + // chop the string up separating the actions + if (Util::splitString(action_string, tok, ";")) { + for (it = tok.begin(); it != tok.end(); ++it) { + if (parseMoveResizeAction(*it, action)) { + ae.action_list.push_back(action); + action.clear(); + } + } + + return true; + } + + return false; +} + +//! @brief Parses MoveResize Event. +bool +Config::parseMoveResizeEvent(CfgParser::Entry *section, ActionEvent& ae) +{ + CfgParser::Entry *value; + + if (! section->get_value().size ()) { + return false; + } + + if (parseKey(section->get_value(), ae.mod, ae.sym)) { + value = section->get_section()->find_entry("ACTIONS"); + if (value) { + return parseMoveResizeActions(value->get_value(), ae); + } + } + + return false; +} + +//! @brief +bool +Config::parseInputDialogAction(const std::string &val, Action &action) +{ + action.setAction(ParseUtil::getValue(val, _inputdialog_map)); + return (action.getAction() != INPUT_NO_ACTION); +} + +//! @brief +bool +Config::parseInputDialogActions(const std::string &actions, ActionEvent &ae) +{ + vector tok; + vector::iterator it; + Action action; + + // reset the action event + ae.action_list.clear(); + + // chop the string up separating the actions + if (Util::splitString(actions, tok, ";")) { + for (it = tok.begin(); it != tok.end(); ++it) { + if (parseInputDialogAction(*it, action)) { + ae.action_list.push_back(action); + action.clear(); + } + } + + return true; + } + + return false; + +} + +//! @brief Parses InputDialog Event. +bool +Config::parseInputDialogEvent(CfgParser::Entry *section, ActionEvent &ae) +{ + CfgParser::Entry *value; + + if (! section->get_value().size()) { + return false; + } + + if (parseKey(section->get_value(), ae.mod, ae.sym)) { + value = section->get_section()->find_entry("ACTIONS"); + if (value) { + return parseInputDialogActions(value->get_value(), ae); + } + } + + return false; +} + +/** + * Get mask for handling menu events. + */ +uint +Config::getMenuMask(const std::string &mask) +{ + uint mask_return = 0, val; + + vector tok; + Util::splitString(mask, tok, " \t"); + + vector::iterator it(tok.begin()); + for (; it != tok.end(); ++it) { + val = ParseUtil::getValue(*it, _mouse_event_map); + if (val != MOUSE_EVENT_NO) { + mask_return |= val; + } + } + return mask_return; +} + +//! @brief +bool +Config::parseMenuAction(const std::string &action_string, Action &action) +{ + vector tok; + + // chop the string up separating the actions + if (Util::splitString(action_string, tok, " \t", 2)) { + action.setAction(ParseUtil::getValue(tok[0], _menu_action_map)); + if (action.getAction() != ACTION_NO) { + return true; + } + } + + return false; +} + +//! @brief +bool +Config::parseMenuActions(const std::string &actions, ActionEvent &ae) +{ + vector tok; + vector::iterator it; + Action action; + + // reset the action event + ae.action_list.clear(); + + // chop the string up separating the actions + if (Util::splitString(actions, tok, ";")) { + for (it = tok.begin(); it != tok.end(); ++it) { + if (parseMenuAction(*it, action)) { + ae.action_list.push_back(action); + action.clear(); + } + } + + return true; + } + + return false; +} + +//! @brief Parses MenuEvent. +bool +Config::parseMenuEvent(CfgParser::Entry *section, ActionEvent& ae) +{ + CfgParser::Entry *value; + + if (! section->get_value().size()) { + return false; + } + + if (parseKey(section->get_value(), ae.mod, ae.sym)) { + value = section->get_section()->find_entry("ACTIONS"); + if (value) { + return parseMenuActions(value->get_value(), ae); + } + } + + return false; +} + +//! @brief +uint +Config::getMouseButton(const std::string &button) +{ + uint btn; + + if (button.size() == 1) { // it's a button + btn = unsigned(strtol(button.c_str(), 0, 10)); + } else if (strcasecmp(button.c_str(), "ANY") == 0) { // any button + btn = BUTTON_ANY; + } else { + btn = BUTTON_NO; + } + + if (btn > BUTTON_NO) { + btn = BUTTON_NO; + } + + return btn; +} + +/** + * Load main configuration file, priority as follows: + * + * 1. Load command line specified file. + * 2. Load ~/.pekwm/config + * 3. Copy configuration and load ~/.pekwm/config + * 4. Load system configuration + */ +bool +Config::tryHardLoadConfig(CfgParser &cfg, std::string &file) +{ + bool success = false; + + // Try loading command line specified file. + if (file.size()) { + success = cfg.parse(file, CfgParserSource::SOURCE_FILE, true); + } + + // Try loading ~/.pekwm/config + if (! success) { + file = string(getenv("HOME")) + string("/.pekwm/config"); + success = cfg.parse(file, CfgParserSource::SOURCE_FILE, true); + + // Copy cfg files to ~/.pekwm and try loading ~/.pekwm/config again. + if (! success) { + copyConfigFiles(); + success = cfg.parse(file, CfgParserSource::SOURCE_FILE, true); + } + } + + // Try loading system configuration files. + if (! success) { + file = string(SYSCONFDIR "/config"); + success = cfg.parse(file, CfgParserSource::SOURCE_FILE, true); + } + + return success; +} + +//! @brief Populates the ~/.pekwm/ dir with config files +void +Config::copyConfigFiles(void) +{ + string cfg_dir = getenv("HOME") + string("/.pekwm"); + + string cfg_file = cfg_dir + string("/config"); + string keys_file = cfg_dir + string("/keys"); + string mouse_file = cfg_dir + string("/mouse"); + string menu_file = cfg_dir + string("/menu"); + string autoprops_file = cfg_dir + string("/autoproperties"); + string start_file = cfg_dir + string("/start"); + string vars_file = cfg_dir + string("/vars"); + string themes_dir = cfg_dir + string("/themes"); + + bool cp_config, cp_keys, cp_mouse, cp_menu; + bool cp_autoprops, cp_start, cp_vars; + bool make_themes = false; + cp_config = cp_keys = cp_mouse = cp_menu = false; + cp_autoprops = cp_start = cp_vars = false; + + struct stat stat_buf; + // check and see if we already have a ~/.pekwm/ directory + if (stat(cfg_dir.c_str(), &stat_buf) == 0) { + // is it a dir or file? + if (! S_ISDIR(stat_buf.st_mode)) { + cerr << cfg_dir << " already exists and isn't a directory" << endl + << "Can't copy config files !" << endl; + return; + } + + // we already have a directory, see if it's writeable and executable + bool cfg_dir_ok = false; + + if (getuid() == stat_buf.st_uid) { + if ((stat_buf.st_mode&S_IWUSR) && (stat_buf.st_mode&(S_IXUSR))) { + cfg_dir_ok = true; + } + } + if (! cfg_dir_ok) { + if (getgid() == stat_buf.st_gid) { + if ((stat_buf.st_mode&S_IWGRP) && (stat_buf.st_mode&(S_IXGRP))) { + cfg_dir_ok = true; + } + } + } + + if (! cfg_dir_ok) { + if (! (stat_buf.st_mode&S_IWOTH) || ! (stat_buf.st_mode&(S_IXOTH))) { + cerr << "You don't have the rights to add files to the: " << cfg_dir + << " directory! Therefor I can't copy the config files!" << endl; + return; + } + } + + // we apparently could write and exec that dir, now see if we have any + // files in it + if (stat(cfg_file.c_str(), &stat_buf)) + cp_config = true; + if (stat(keys_file.c_str(), &stat_buf)) + cp_keys = true; + if (stat(mouse_file.c_str(), &stat_buf)) + cp_mouse = true; + if (stat(menu_file.c_str(), &stat_buf)) + cp_menu = true; + if (stat(autoprops_file.c_str(), &stat_buf)) + cp_autoprops = true; + if (stat(start_file.c_str(), &stat_buf)) + cp_start = true; + if (stat(vars_file.c_str(), &stat_buf)) + cp_vars = true; + if (stat(themes_dir.c_str(), &stat_buf)) { + make_themes = true; + } + } else { // we didn't have a ~/.pekwm directory already, lets create one + if (mkdir(cfg_dir.c_str(), 0700)) { + cerr << "Can't create " << cfg_dir << " directory!" << endl; + cerr << "Can't copy config files !" << endl; + return; + } + + cp_config = cp_keys = cp_mouse = cp_menu = true; + cp_autoprops = cp_start = cp_vars = true; + make_themes = true; + } + + if (cp_config) { + Util::copyTextFile(SYSCONFDIR "/config", cfg_file); + } + if (cp_keys) { + Util::copyTextFile(SYSCONFDIR "/keys", keys_file); + } + if (cp_mouse) { + Util::copyTextFile(SYSCONFDIR "/mouse", mouse_file); + } + if (cp_menu) { + Util::copyTextFile(SYSCONFDIR "/menu", menu_file); + } + if (cp_autoprops) { + Util::copyTextFile(SYSCONFDIR "/autoproperties", autoprops_file); + } + if (cp_start) { + Util::copyTextFile(SYSCONFDIR "/start", start_file); + } + if (cp_vars) { + Util::copyTextFile(SYSCONFDIR "/vars", vars_file); + } + if (make_themes) { + mkdir(themes_dir.c_str(), 0700); + } +} + +/** + * Parses mouse configuration file. + */ +bool +Config::loadMouseConfig(const std::string &mouse_file) +{ + if (! Util::requireReload(_mouse_state, mouse_file)) { + return false; + } + + CfgParser mouse_cfg; + if (! mouse_cfg.parse(mouse_file, CfgParserSource::SOURCE_FILE, true) + && ! mouse_cfg.parse(SYSCONFDIR "/mouse", CfgParserSource::SOURCE_FILE, true)) { + _mouse_state.clear(); + return false; + } + + // Clear state if load failed or dynamic content. + if (mouse_cfg.is_dynamic_content()) { + _mouse_state.clear(); + } else { + _mouse_state = mouse_cfg.get_file_list(); + } + + // Make sure old actions get unloaded. + map* >::iterator it; + for (it = _mouse_action_map.begin(); it != _mouse_action_map.end(); ++it) { + it->second->clear(); + } + + CfgParser::Entry *section; + + section = mouse_cfg.get_entry_root()->find_section("FRAMETITLE"); + if (section) { + parseButtons(section, _mouse_action_map[MOUSE_ACTION_LIST_TITLE_FRAME], FRAME_OK); + } + + section = mouse_cfg.get_entry_root()->find_section("OTHERTITLE"); + if (section) { + parseButtons(section, _mouse_action_map[MOUSE_ACTION_LIST_TITLE_OTHER], FRAME_OK); + } + + section = mouse_cfg.get_entry_root()->find_section("CLIENT"); + if (section) { + parseButtons(section, _mouse_action_map[MOUSE_ACTION_LIST_CHILD_FRAME], CLIENT_OK); + } + + section = mouse_cfg.get_entry_root()->find_section("ROOT"); + if (section) { + parseButtons(section, _mouse_action_map[MOUSE_ACTION_LIST_ROOT], ROOTCLICK_OK); + } + + section = mouse_cfg.get_entry_root()->find_section("MENU"); + if (section) { + parseButtons(section, _mouse_action_map[MOUSE_ACTION_LIST_MENU], FRAME_OK); + } + + section = mouse_cfg.get_entry_root()->find_section("OTHER"); + if (section) { + parseButtons(section, _mouse_action_map[MOUSE_ACTION_LIST_OTHER], FRAME_OK); + } + + section = mouse_cfg.get_entry_root()->find_section("SCREENEDGE"); + if (section) { + CfgParser::iterator edge_it(section->begin()); + for (; edge_it != section->end(); ++edge_it) { + uint pos = ParseUtil::getValue((*edge_it)->get_name(), _direction_map); + + if (pos != SCREEN_EDGE_NO) { + parseButtons((*edge_it)->get_section(), getEdgeListFromPosition(pos), SCREEN_EDGE_OK); + } + } + } + + section = mouse_cfg.get_entry_root()->find_section("BORDER"); + if (section) { + CfgParser::iterator border_it(section->begin()); + for (; border_it != section->end(); ++border_it) { + uint pos = ParseUtil::getValue((*border_it)->get_name(), _borderpos_map); + if (pos != BORDER_NO_POS) { + parseButtons((*border_it)->get_section(), getBorderListFromPosition(pos), FRAME_BORDER_OK); + } + } + } + + return true; +} + +//! @brief Parses mouse config section, like FRAME +void +Config::parseButtons(CfgParser::Entry *section, std::list* mouse_list, ActionOk action_ok) +{ + if (! section || ! mouse_list) { + return; + } + + ActionEvent ae; + CfgParser::Entry *value; + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + if (! (*it)->get_section()) { + continue; + } + + ae.type = ParseUtil::getValue((*it)->get_name(), _mouse_event_map); + + if (ae.type == MOUSE_EVENT_NO) { + continue; + } + + if (ae.type == MOUSE_EVENT_MOTION) { + value = (*it)->get_section()->find_entry("THRESHOLD"); + if (value) { + ae.threshold = strtol(value->get_value().c_str(), 0, 10); + } else { + ae.threshold = 0; + } + } + + if (parseActionEvent((*it), ae, action_ok, true)) { + mouse_list->push_back(ae); + } + } +} + +// frame border configuration + +list* +Config::getBorderListFromPosition(uint pos) +{ + list *ret = 0; + + switch (pos) { + case BORDER_TOP_LEFT: + ret = _mouse_action_map[MOUSE_ACTION_LIST_BORDER_TL]; + break; + case BORDER_TOP: + ret = _mouse_action_map[MOUSE_ACTION_LIST_BORDER_T]; + break; + case BORDER_TOP_RIGHT: + ret = _mouse_action_map[MOUSE_ACTION_LIST_BORDER_TR]; + break; + case BORDER_LEFT: + ret = _mouse_action_map[MOUSE_ACTION_LIST_BORDER_L]; + break; + case BORDER_RIGHT: + ret = _mouse_action_map[MOUSE_ACTION_LIST_BORDER_R]; + break; + case BORDER_BOTTOM_LEFT: + ret = _mouse_action_map[MOUSE_ACTION_LIST_BORDER_BL]; + break; + case BORDER_BOTTOM: + ret = _mouse_action_map[MOUSE_ACTION_LIST_BORDER_B]; + break; + case BORDER_BOTTOM_RIGHT: + ret = _mouse_action_map[MOUSE_ACTION_LIST_BORDER_BR]; + break; + } + + return ret; +} + +list* +Config::getEdgeListFromPosition(uint pos) +{ + list *ret = 0; + + switch (pos) { + case SCREEN_EDGE_TOP: + ret = _mouse_action_map[MOUSE_ACTION_LIST_EDGE_T]; + break; + case SCREEN_EDGE_BOTTOM: + ret = _mouse_action_map[MOUSE_ACTION_LIST_EDGE_B]; + break; + case SCREEN_EDGE_LEFT: + ret = _mouse_action_map[MOUSE_ACTION_LIST_EDGE_L]; + break; + case SCREEN_EDGE_RIGHT: + ret = _mouse_action_map[MOUSE_ACTION_LIST_EDGE_R]; + break; + }; + + return ret; +} + + +//! @brief Parses workspace number +int +Config::parseWorkspaceNumber(const std::string &workspace) +{ + // Get workspace looking for relative numbers + uint num = ParseUtil::getValue(workspace, _workspace_change_map); + + if (num == WORKSPACE_NO) { + // Workspace isn't relative, check for 2x2 and ordinary specification + vector tok; + if (Util::splitString(workspace, tok, "x", 2, true) == 2) { + uint row = strtol(tok[0].c_str(), 0, 10) - 1; + uint col = strtol(tok[1].c_str(), 0, 10) - 1; + + num = _screen_workspaces_per_row * row + col; + + } else { + num = strtol(workspace.c_str(), 0, 10) - 1; + } + } + + // Fallback to 0 if something went wrong + if (num < 0) { + num = 0; + } + + return num; +} + +#ifdef OPACITY +//! @brief Parses a string which contains two opacity values +bool +Config::parseOpacity(const std::string value, uint &focused, uint &unfocused) +{ + std::vector tokens; + switch ((Util::splitString(value, tokens, " ,", 2))) { + case 2: + focused = std::atoi(tokens.at(0).c_str()); + unfocused = std::atoi(tokens.at(1).c_str()); + break; + case 1: + focused = unfocused = std::atoi(tokens.at(0).c_str()); + break; + default: + return false; + } + CONV_OPACITY(focused); + CONV_OPACITY(unfocused); + return true; +} +#endif // OPACITY diff --git a/pekwm/Config.hh b/pekwm/Config.hh new file mode 100644 index 0000000..11d1c97 --- /dev/null +++ b/pekwm/Config.hh @@ -0,0 +1,373 @@ +// +// Config.hh for pekwm +// Copyright © 2002-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _CONFIG_HH_ +#define _CONFIG_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" +#include "Action.hh" +#include "CfgParser.hh" +#include "ParseUtil.hh" + +#include +#include +#include +#include +#include + +/** + * Enum describing the different limits of a size limit. + */ +enum SizeLimitType { + WIDTH_MIN = 0, + WIDTH_MAX, + HEIGHT_MIN, + HEIGHT_MAX +}; + +/** + * Simple class describing size limitations, width and height min and + * max sizes. + */ +class SizeLimits +{ +public: + /** SizeLimits constructor setting limits to 0. */ + SizeLimits(void) { + for (unsigned int i = 0; i < HEIGHT_MAX; ++i) { + _limits[i] = 0; + } + } + + /** Get limit for limit type. */ + unsigned int get(SizeLimitType limit) const { return _limits[limit]; } + bool parse(const std::string &minimum, const std::string &maximum); + +private: + bool parseLimit(const std::string &limit, unsigned int &min, unsigned int &max); + +private: + unsigned int _limits[HEIGHT_MAX + 1]; /**< Limits. */ +}; + +// CONV_OPACITY converts percentage to absolute opacity values. +// The variable X containing the percent value is changed directly. +#ifdef OPACITY +#define CONV_OPACITY(X)\ + X = (X == 100)?EWMH_OPAQUE_WINDOW:X*(EWMH_OPAQUE_WINDOW/100) +#else +#define CONV_OPACITY(X) +#endif + +/** + * Large set of configuration options stored and parsed by the + * singleton Config class. + */ +class Config +{ +public: + Config(void); + ~Config(void); + + static Config* instance(void) { return _instance; } + + bool load(const std::string &config_file); + bool loadMouseConfig(const std::string &mouse_file); + + inline const std::string &getConfigFile(void) const { return _config_file; } + + /** Return list with available keyboard actions names. */ + std::list getActionNameList(void) { + std::list action_names; + std::map >::iterator it; + for (it = _action_map.begin(); it != _action_map.end(); ++it) { + if (it->second.second&KEYGRABBER_OK) { + action_names.push_back(it->first.get_text()); + } + } + return action_names; + } + + /** Return list with available state action names. */ + std::list getStateNameList(void) { + std::list state_names; + std::map::iterator it; + for (it = _action_state_map.begin(); it != _action_state_map.end(); ++it) { + state_names.push_back(it->first.get_text()); + } + return state_names; + } + + // Files + const std::string &getKeyFile(void) const { return _files_keys; } + const std::string &getMenuFile(void) const { return _files_menu; } + const std::string &getStartFile(void) const { return _files_start; } + const std::string &getAutoPropsFile(void) const { return _files_autoprops; } + const std::string &getThemeFile(void) const { return _files_theme; } + const std::string &getMouseConfigFile(void) const { return _files_mouse; } + const std::string &getIconPath(void) const { return _files_icon_path; } + const char *getSystemIconPath(void) const { return DATADIR "/pekwm/icons/"; } + + // Moveresize + inline int getEdgeAttract(void) const { return _moveresize_edgeattract; } + inline int getEdgeResist(void) const { return _moveresize_edgeresist; } + inline int getWOAttract(void) const { return _moveresize_woattract; } + inline int getWOResist(void) const { return _moveresize_woresist; } + inline bool getOpaqueMove(void) const { return _moveresize_opaquemove; } + inline bool getOpaqueResize(void) const { return _moveresize_opaqueresize; } + + // Screen + inline int getWorkspaces(void) const { return _screen_workspaces; } + inline int getScreenPixmapCacheSize(void) const { return _screen_pixmap_cache_size; } + inline int getWorkspacesPerRow(void) const { return _screen_workspaces_per_row; } + void getDesktopNamesUTF8(uchar **names, uint *length) const; + const std::wstring &getWorkspaceName(uint num) const { + return (num >= _screen_workspace_names.size())?_screen_workspace_name_default: + _screen_workspace_names[num]; + } + void setDesktopNamesUTF8(char *names, ulong length); + + inline int getScreenEdgeSize(EdgeType edge) const { return _screen_edge_sizes[edge]; } + inline bool getScreenEdgeIndent(void) const { return _screen_edge_indent; } + inline int getDoubleClickTime(void) const { return _screen_doubleclicktime; } + inline const std::wstring &getTrimTitle(void) const { return _screen_trim_title; } + + inline bool isFullscreenAbove(void) const { return _screen_fullscreen_above; } + inline bool isFullscreenDetect(void) const { return _screen_fullscreen_detect; } + + inline bool getShowFrameList(void) const { return _screen_showframelist; } + inline bool isShowStatusWindow(void) const { return _screen_show_status_window; } + bool isShowStatusWindowOnRoot(void) const { return _screen_show_status_window_on_root; } + inline bool isShowClientID(void) const { return _screen_show_client_id; } + int getShowWorkspaceIndicator(void) const { return _screen_show_workspace_indicator; } + int getWorkspaceIndicatorScale(void) const { return _screen_workspace_indicator_scale; } +#ifdef OPACITY + uint getWorkspaceIndicatorOpacity(void) const { return _screen_workspace_indicator_opacity; } +#endif // OPACITY + inline bool isPlaceNew(void) const { return _screen_place_new; } + inline bool isFocusNew(void) const { return _screen_focus_new; } + inline bool isFocusNewChild(void) const { return _screen_focus_new_child; } + inline bool isHonourRandr(void) const { return _screen_honour_randr; } + inline bool isHonourAspectRatio(void) const { return _screen_honour_aspectratio; } + + inline std::list::iterator getPlacementModelBegin(void) { return _screen_placementmodels.begin(); } + inline std::list::iterator getPlacementModelEnd(void) { return _screen_placementmodels.end(); } + + inline bool getPlacementRow(void) const { return _screen_placement_row; } + inline bool getPlacementLtR(void) const { return _screen_placement_ltr; } + inline bool getPlacementTtB(void) const { return _screen_placement_ttb; } + inline int getPlacementOffsetX(void) const { return _screen_placement_offset_x; } + inline int getPlacementOffsetY(void) const { return _screen_placement_offset_y; } + + inline bool getClientUniqueName(void) const { return _screen_client_unique_name; } + inline const std::string &getClientUniqueNamePre(void) const { + return _screen_client_unique_name_pre; + } + inline const std::string &getClientUniqueNamePost(void) const { + return _screen_client_unique_name_post; + } + inline bool isReportAllClients(void) const { return _screen_report_all_clients; } + + inline bool isMenuSelectOn(uint val) const { return (_menu_select_mask&val); } + inline bool isMenuEnterOn(uint val) const { return (_menu_enter_mask&val); } + inline bool isMenuExecOn(uint val) const { return (_menu_exec_mask&val); } + bool isDisplayMenuIcons(void) const { return _menu_display_icons; } +#ifdef OPACITY + inline uint getMenuFocusOpacity(void) const { return _menu_focus_opacity; } + inline uint getMenuUnfocusOpacity(void) const { return _menu_unfocus_opacity; } +#endif // OPACITY + + bool isCmdDialogHistoryUnique(void) const { return _cmd_dialog_history_unique; } + int getCmdDialogHistorySize(void) const { return _cmd_dialog_history_size; } + const std::string &getCmdDialogHistoryFile(void) const { return _cmd_dialog_history_file; } + int getCmdDialogHistorySaveInterval(void) const { return _cmd_dialog_history_save_interval; } + + inline int getHarbourDAMinSide(void) const { return _harbour_da_min_s; } + inline int getHarbourDAMaxSide(void) const { return _harbour_da_max_s; } + inline int getHarbourHead(void) const { return _harbour_head_nr; } + inline bool isHarbourOntop(void) const { return _harbour_ontop; } + inline bool isHarbourMaximizeOver(void) const { return _harbour_maximize_over; } + inline uint getHarbourPlacement(void) const { return _harbour_placement; } + inline uint getHarbourOrientation(void) const { return _harbour_orientation; } +#ifdef OPACITY + inline uint getHarbourOpacity(void) const { return _harbour_opacity; } +#endif // OPACITY + + inline std::list *getMouseActionList(MouseActionListName name) + { return _mouse_action_map[name]; } + + std::list *getBorderListFromPosition(uint pos); + std::list *getEdgeListFromPosition(uint pos); + + // map parsing + ActionType getAction(const std::string &name, uint mask); + ActionAccessMask getActionAccessMask(const std::string &name); + inline Layer getLayer(const std::string &layer) { return ParseUtil::getValue(layer, _layer_map); } + inline Skip getSkip(const std::string &skip) { return ParseUtil::getValue(skip, _skip_map); } + inline CfgDeny getCfgDeny(const std::string &deny) { return ParseUtil::getValue(deny, _cfg_deny_map); } + + bool parseKey(const std::string &key_string, uint& mod, uint &key); + bool parseButton(const std::string &button_string, uint &mod, uint &button); + bool parseAction(const std::string &action_string, Action &action, uint mask); + bool parseActionAccessMask(const std::string &action_mask_string, uint &mask); + bool parseActionState(Action &action, const std::string &st_action); + bool parseActions(const std::string &actions, ActionEvent &ae, uint mask); + bool parseActionEvent(CfgParser::Entry *section, ActionEvent &ae, uint mask, bool button); + + bool parseMoveResizeAction(const std::string &action_string, Action &action); + bool parseMoveResizeActions(const std::string &actions, ActionEvent &ae); + bool parseMoveResizeEvent(CfgParser::Entry *section, ActionEvent &ae); + + bool parseInputDialogAction(const std::string &val, Action &action); + bool parseInputDialogActions(const std::string &actions, ActionEvent &ae); + bool parseInputDialogEvent(CfgParser::Entry *section, ActionEvent &ae); + + uint getMenuMask(const std::string &mask); + /** Return maximum allowed icon width. */ + unsigned int getMenuIconLimit(unsigned int value, SizeLimitType limit, const std::string &name) const { + unsigned int limit_val = 0; + std::map::const_iterator it(_menu_icon_limits.find(name)); + if (it == _menu_icon_limits.end()) { + if (name == "DEFAULT") { + limit_val = 16; + } else { + limit_val = getMenuIconLimit(value, limit, "DEFAULT"); + } + } else { + limit_val = it->second.get(limit); + } + + return limit_val ? limit_val : value; + } + + bool parseMenuAction(const std::string& action_string, Action& action); + bool parseMenuActions(const std::string& actions, ActionEvent& ae); + bool parseMenuEvent(CfgParser::Entry *section, ActionEvent& ae); + + inline uint getMod(const std::string &mod) { return ParseUtil::getValue(mod, _mod_map); } + uint getMouseButton(const std::string& button); + +#ifdef OPACITY + static bool parseOpacity(const std::string value, uint &focused, uint &unfocused); +#endif // OPACITY + +private: + bool tryHardLoadConfig(CfgParser &cfg, std::string &file); + void copyConfigFiles(void); + + void loadFiles(CfgParser::Entry *section); + void loadMoveResize(CfgParser::Entry *section); + void loadScreen(CfgParser::Entry *section); + void loadMenu(CfgParser::Entry *section); + void loadMenuIcons(CfgParser::Entry *section); + void loadCmdDialog(CfgParser::Entry *section); + void loadHarbour(CfgParser::Entry *section); + + void parseButtons(CfgParser::Entry *section, std::list* mouse_list, ActionOk action_ok); + + int parseWorkspaceNumber(const std::string &workspace); + +private: + std::string _config_file; /**< Path to config file last loaded. */ + std::map _cfg_state; /**< Map of file mtime for all files touched by a configuration. */ + std::map _mouse_state; /**< Map of file mtime for all files touched by a configuration. */ + + // files + std::string _files_keys, _files_menu; + std::string _files_start, _files_autoprops; + std::string _files_theme, _files_mouse; + std::string _files_icon_path; /**< Path to user icon directory. */ + + // moveresize + int _moveresize_edgeattract, _moveresize_edgeresist; + int _moveresize_woattract, _moveresize_woresist; + bool _moveresize_opaquemove, _moveresize_opaqueresize; + + // screen + int _screen_workspaces, _screen_pixmap_cache_size; + int _screen_workspaces_per_row; + std::vector _screen_workspace_names; + std::wstring _screen_workspace_name_default; + std::vector _screen_edge_sizes; + bool _screen_edge_indent; + int _screen_doubleclicktime; + std::wstring _screen_trim_title; + bool _screen_fullscreen_above; //!< Flag to make fullscreen go above all windows. */ + bool _screen_fullscreen_detect; /**< Flag to make configure request fullscreen detection. */ + bool _screen_showframelist; + bool _screen_show_status_window; + bool _screen_show_status_window_on_root; /**< If true, center status window relative to current head. */ + bool _screen_show_client_id; //!< Flag to display client ID in title. + int _screen_show_workspace_indicator; //!< Display workspace indicator for N seconds. + int _screen_workspace_indicator_scale; //!< Scale of the workspace indicator head +#ifdef OPACITY + uint _screen_workspace_indicator_opacity; +#endif // OPACITY + bool _screen_place_new, _screen_focus_new, _screen_focus_new_child; + bool _screen_honour_randr; /**< Boolean flag if randr information should be honoured. */ + bool _screen_honour_aspectratio; /**< if true, pekwm keeps aspect ratio (XSizeHint) */ + bool _screen_placement_row, _screen_placement_ltr, _screen_placement_ttb; + int _screen_placement_offset_x, _screen_placement_offset_y; + std::list _screen_placementmodels; + bool _screen_client_unique_name; + std::string _screen_client_unique_name_pre, _screen_client_unique_name_post; + bool _screen_report_all_clients; + + uint _menu_select_mask, _menu_enter_mask, _menu_exec_mask; + bool _menu_display_icons; /**< Boolean flag, when true display icons in menus. */ +#ifdef OPACITY + uint _menu_focus_opacity, _menu_unfocus_opacity; +#endif // OPACITY + + std::map _menu_icon_limits; /**< Map of name -> limit for icons in menus */ + + bool _cmd_dialog_history_unique; /**< Boolean flag, when true entries in the CmdDialog history are unique. */ + int _cmd_dialog_history_size; /**< Number of entries in the history before the last entries are dropped. */ + std::string _cmd_dialog_history_file; /**< Path to cmd dialog history file. */ + int _cmd_dialog_history_save_interval; /**< Save history file each Nth CmdDialog exec. */ + + int _harbour_da_min_s, _harbour_da_max_s; + bool _harbour_ontop; + bool _harbour_maximize_over; + uint _harbour_placement; + uint _harbour_orientation; + int _harbour_head_nr; +#ifdef OPACITY + uint _harbour_opacity; +#endif // OPACITY + + std::map* > _mouse_action_map; + + std::map > _action_map; + std::map _action_access_mask_map; + std::map _placement_map; + std::map _edge_map; + std::map _raise_map; + std::map _skip_map; + std::map _layer_map; + std::map _moveresize_map; + std::map _inputdialog_map; + std::map _direction_map; + std::map _workspace_change_map; + std::map _borderpos_map; + std::map _mouse_event_map; + std::map _mod_map; + std::map _action_state_map; + std::map _cfg_deny_map; + std::map _menu_action_map; + std::map _harbour_placement_map; + std::map _harbour_orientation_map; + + static Config *_instance; /**< Singleton Config pointer. */ +}; + +#endif // _CONFIG_HH_ diff --git a/pekwm/DecorMenu.cc b/pekwm/DecorMenu.cc new file mode 100644 index 0000000..c6cbedd --- /dev/null +++ b/pekwm/DecorMenu.cc @@ -0,0 +1,79 @@ +// +// FrameListMenu.cc for pekwm +// Copyright (C) 2002-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "../config.h" + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "PMenu.hh" +#include "WORefMenu.hh" +#include "DecorMenu.hh" + +#include "Workspaces.hh" +#include "ActionHandler.hh" +#include "Theme.hh" + +#include + +#ifdef DEBUG +#include +using std::cerr; +using std::endl; +#endif // DEBUG +using std::string; +using std::map; + +//! @brief Constructor for DecorMenu. +DecorMenu::DecorMenu(PScreen *scr, Theme *theme, ActionHandler *act, + const std::string &name) : + WORefMenu(scr, theme, L"Decor Menu", name), + _act(act) +{ + _menu_type = DECORMENU_TYPE; +} + +//! @brief Destructor for DecorMenu +DecorMenu::~DecorMenu(void) +{ +} + +//! @brief Handles button1 release +void +DecorMenu::handleItemExec(PMenu::Item *item) +{ + if (! item) { + return; + } + + ActionPerformed ap(getWORef(), item->getAE()); + _act->handleAction(ap); +} + +//! @brief Rebuilds the menu. +void +DecorMenu::reload(CfgParser::Entry *section) +{ + // clear the menu before loading + removeAll(); + + // setup dummy action + Action action; + ActionEvent ae; + + action.setAction(ACTION_SET); + action.setParamI(0, ACTION_STATE_DECOR); + ae.action_list.push_back(action); + + map::const_iterator it(_theme->decor_begin()); + for (; it != _theme->decor_end(); ++it) { + ae.action_list.back().setParamS(it->first); + insert(Util::to_wide_str(it->first), ae, 0); + } + + buildMenu(); // rebuild the menu +} diff --git a/pekwm/DecorMenu.hh b/pekwm/DecorMenu.hh new file mode 100644 index 0000000..9cf4da7 --- /dev/null +++ b/pekwm/DecorMenu.hh @@ -0,0 +1,40 @@ +// +// DecorMenu.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _DECORMENU_HH_ +#define _DECORMENU_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" +#include "PMenu.hh" + +#include + +class WORefMenu; +class PScreen; +class Theme; +class ActionHandler; + +class DecorMenu : public WORefMenu +{ +public: + DecorMenu(PScreen *scr, Theme *theme, ActionHandler *act, + const std::string &name); + virtual ~DecorMenu(void); + + virtual void handleItemExec(PMenu::Item *item); + virtual void reload(CfgParser::Entry *section); + +private: + ActionHandler *_act; +}; + +#endif // _DECORMENU_HH_ diff --git a/pekwm/DockApp.cc b/pekwm/DockApp.cc new file mode 100644 index 0000000..b3c3f77 --- /dev/null +++ b/pekwm/DockApp.cc @@ -0,0 +1,297 @@ +// +// Dockapp.cc for pekwm +// Copyright (C) 2003-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "../config.h" + +#include "PWinObj.hh" +#include "DockApp.hh" + +#include "Config.hh" +#include "PScreen.hh" +#include "PTexture.hh" +#include "PDecor.hh" +#include "ScreenResources.hh" +#include "Theme.hh" +#include "PixmapHandler.hh" +#include "AutoProperties.hh" + +extern "C" { +#include +} + +#ifdef DEBUG +#include +using std::cerr; +using std::endl; +#endif // DEBUG + +const uint DOCKAPP_DEFAULT_SIDE = 64; +const uint DOCKAPP_BORDER_WIDTH = 2; + +//! @brief DockApp constructor +DockApp::DockApp(PScreen *s, Theme *t, Window win) : + PWinObj(), + _scr(s), _theme(t), + _dockapp_window(win), + _client_window(win), _icon_window(None), + _position(0), _background(None), + _is_alive(true) +{ + Config *cfg = Config::instance(); + + // PWinObj attributes. + _type = WO_DOCKAPP; + _iconified = true; // We set ourself iconified for workspace switching. + _sticky = true; + + // First, we need to figure out which window that actually belongs to the + // dockapp. This we do by checking if it has the IconWindowHint set in it's + // WM Hint. + XWMHints *wm_hints = XGetWMHints(_dpy, _dockapp_window); + if (wm_hints) { + if ((wm_hints->flags&IconWindowHint) && + (wm_hints->icon_window != None)) { + // let us hide the _client_window window, as we won't use it. + XUnmapWindow(_dpy, _client_window); + + _icon_window = wm_hints->icon_window; + _dockapp_window = wm_hints->icon_window; + } + XFree(wm_hints); + } + + // Now, when we now what window id we should use, set the size up. + XWindowAttributes attr; + if (XGetWindowAttributes(_dpy, _dockapp_window, &attr)) { + _c_gm.width = attr.width; + _c_gm.height = attr.height; + + _gm.width = attr.width; + _gm.height = attr.height; + + } else { + // Didn't get any size from the dockapp, use default + if (cfg->getHarbourDAMinSide() > 0) { + _c_gm.width = cfg->getHarbourDAMinSide(); + _c_gm.height = cfg->getHarbourDAMinSide(); + } else { + _c_gm.width = DOCKAPP_DEFAULT_SIDE - DOCKAPP_BORDER_WIDTH * 2; + _c_gm.height = DOCKAPP_DEFAULT_SIDE - DOCKAPP_BORDER_WIDTH * 2; + } + + _gm.width = _c_gm.width; + _gm.height = _c_gm.height; + } + + // make sure size is valid and position the dockapp + updateSize(); + + // Okie, now lets create it's parent window which is going to hold the border + XSetWindowAttributes sattr; + sattr.override_redirect = True; + sattr.event_mask = SubstructureRedirectMask|ButtonPressMask|ButtonMotionMask; + + _window = + XCreateWindow(_dpy, _scr->getRoot(), + _gm.x, _gm.y, _gm.width, _gm.height, 0, + CopyFromParent, InputOutput, CopyFromParent, + CWOverrideRedirect|CWEventMask, &sattr); + + // initial makeup + repaint(); + XSetWindowBorderWidth(_dpy, _dockapp_window, 0); + + // move the dockapp to it's new parent, making sure we don't + // get any UnmapEvents + XSelectInput(_dpy, _dockapp_window, NoEventMask); + XReparentWindow(_dpy, _dockapp_window, _window, _c_gm.x, _c_gm.y); + XSelectInput(_dpy, _dockapp_window, SubstructureNotifyMask); + + readClassHint(); + readAutoProperties(); +} + +//! @brief DockApp destructor +DockApp::~DockApp(void) +{ + // if the client still is alive, we should reparent it to the root + // window, else we don't have to care about that. + if (_is_alive) { + _scr->grabServer(); + + if (_icon_window != None) { + XUnmapWindow(_dpy, _icon_window); + } + + // move the dockapp back to the root window, making sure we don't + // get any UnmapEvents + XSelectInput(_dpy, _dockapp_window, NoEventMask); + XReparentWindow(_dpy, _dockapp_window, _scr->getRoot(), _gm.x, _gm.y); + XMapWindow(_dpy, _client_window); + + _scr->ungrabServer(false); + } + + // clean up + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_background); + XDestroyWindow(_dpy, _window); +} + +// START - PWinObj interface. + +//! @brief Maps the DockApp +void +DockApp::mapWindow(void) +{ + if (_mapped) { + return; + } + _mapped = true; + + XSelectInput(_dpy, _dockapp_window, NoEventMask); + XMapWindow(_dpy, _window); + XMapWindow(_dpy, _dockapp_window); + XSelectInput(_dpy, _dockapp_window, + StructureNotifyMask|SubstructureNotifyMask); +} + +//! @brief Unmaps the DockApp +void +DockApp::unmapWindow(void) +{ + if (! _mapped) { + return; + } + _mapped = false; + + XSelectInput(_dpy, _dockapp_window, NoEventMask); + XUnmapWindow(_dpy, _dockapp_window); + XUnmapWindow(_dpy, _window); + XSelectInput(_dpy, _dockapp_window, + StructureNotifyMask|SubstructureNotifyMask); +} + +// END - PWinObj interface. + +//! @brief Kills the DockApp +void +DockApp::kill(void) +{ + XKillClient(_dpy, _dockapp_window); +} + +//! @brief Resizes the DockApp, size excludes the border. +//! @todo Make sure it's inside the screen! +void +DockApp::resize(uint width, uint height) +{ + if ((_c_gm.width == width) && (_c_gm.height == height)) { + return; + } + + _c_gm.width = width; + _c_gm.height = height; + + updateSize(); + + XMoveResizeWindow(_dpy, _window, + _gm.x, _gm.y, _gm.width, _gm.height); + XMoveResizeWindow(_dpy, _dockapp_window, + _c_gm.x, _c_gm.y, _c_gm.width, _c_gm.height); + + repaint(); +} + +//! @brief Loads the current theme and repaints the DockApp. +void +DockApp::loadTheme(void) +{ + // Note, here we are going to check if the DockApp borderwidth have + // changed etc in the future but for now we'll only have to repaint it + repaint(); +} + +//! @brief Repaints the DockApp's background. +void +DockApp::repaint(void) +{ + PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler(); + + pm->returnPixmap(_background); + _background = pm->getPixmap(_gm.width, _gm.height, _scr->getDepth()); + + _theme->getHarbourData()->getTexture()->render(_background, 0, 0, _gm.width, _gm.height); + + XSetWindowBackgroundPixmap(_dpy, _window, _background); + XClearWindow(_dpy, _window); +} + +//! @brief Validates geometry and centers the window +void +DockApp::updateSize(void) +{ + // resize the window holding the dockapp + _gm.width = _c_gm.width + DOCKAPP_BORDER_WIDTH * 2; + _gm.height = _c_gm.height + DOCKAPP_BORDER_WIDTH * 2; + + // resize + validateSize(); + + // position the dockapp + _c_gm.x = (_gm.width - _c_gm.width) / 2; + _c_gm.y = (_gm.height - _c_gm.height) / 2; +} + +//! @brief Makes sure DockApp conforms to SideMin and SideMax +void +DockApp::validateSize(void) +{ + Config *cfg = Config::instance(); // convenience + + if (cfg->getHarbourDAMinSide() > 0) { + if (_gm.width < static_cast(cfg->getHarbourDAMinSide())) { + _gm.width = cfg->getHarbourDAMinSide(); + } + if (_gm.height < static_cast(cfg->getHarbourDAMinSide())) { + _gm.height = cfg->getHarbourDAMinSide(); + } + } + + if (cfg->getHarbourDAMaxSide() > 0) { + if (_gm.width > static_cast(cfg->getHarbourDAMaxSide())) { + _gm.width = cfg->getHarbourDAMaxSide(); + } + if (_gm.height > static_cast(cfg->getHarbourDAMaxSide())) { + _gm.height = cfg->getHarbourDAMaxSide(); + } + } +} + +//! @brief Reads XClassHint of client. +void +DockApp::readClassHint(void) +{ + XClassHint x_class_hint; + if (XGetClassHint(_dpy, _client_window, &x_class_hint)) { + _class_hint.h_name = Util::to_wide_str(x_class_hint.res_name); + _class_hint.h_class = Util::to_wide_str(x_class_hint.res_class); + XFree(x_class_hint.res_name); + XFree(x_class_hint.res_class); + } +} + +//! @brief Reads DockApp AutoProperties. +void +DockApp::readAutoProperties(void) +{ + DockAppProperty *prop = + AutoProperties::instance()->findDockAppProperty(&_class_hint); + if (prop) { + _position = prop->getPosition(); + } +} diff --git a/pekwm/DockApp.hh b/pekwm/DockApp.hh new file mode 100644 index 0000000..54caeae --- /dev/null +++ b/pekwm/DockApp.hh @@ -0,0 +1,89 @@ +// +// DockApp.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _DOCKAPP_HH_ +#define _DOCKAPP_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" +#include "AutoProperties.hh" + +class PScreen; +class Theme; +class PWinObj; + +//! @brief DockApp handling class. +class DockApp : public PWinObj +{ +public: + DockApp(PScreen *scr, Theme *theme, Window win); + ~DockApp(void); + + // START - PWinObj interface. + virtual void mapWindow(void); + virtual void unmapWindow(void); + // END - PWinObj interface. + + //! @brief Returns DockApp client width. + inline uint getClientWidth(void) const { return _c_gm.width; } + //! @brief Returns DockApp client height. + inline uint getClientHeight(void) const { return _c_gm.height; } + //! @brief Returns DockApp position. + inline int getPosition(void) const { return _position; } + //! @brief Sets alive state of DockApp. + inline void setAlive(bool alive) { _is_alive = alive; } + + //! @brief Matches win against DockApp client window(s). + inline bool findDockApp(Window win) { + if ((win != None) && ((win == _client_window) || (win == _icon_window))) { + return true; + } + return false; + } + //! @brief Matches win against DockApp window. + inline bool findDockAppFromFrame(Window win) { + if ((win != None) && (win == _window)) + return true; + return false; + } + + void kill(void); + void resize(uint width, uint height); + + void loadTheme(void); + +private: + void repaint(void); + + void updateSize(void); + void validateSize(void); + + void readClassHint(void); + void readAutoProperties(void); + +private: + PScreen *_scr; + Theme *_theme; + + Window _dockapp_window; + Window _client_window, _icon_window; + + ClassHint _class_hint; + + Geometry _c_gm; + int _position; // used in sorted mode + + Pixmap _background; + + bool _is_alive; +}; + +#endif // _DOCKAPP_HH_ diff --git a/pekwm/Exception.hh b/pekwm/Exception.hh new file mode 100644 index 0000000..08a5402 --- /dev/null +++ b/pekwm/Exception.hh @@ -0,0 +1,32 @@ +// +// PDecor.hh for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _EXCEPTION_H_ +#define _EXCEPTION_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +/** + * Exception thrown when loading of a file/data fails. + */ +class LoadException +{ +public: + LoadException(const char *resource) : _resource(resource) { } + virtual ~LoadException(void) { } + + /** Get resource string. */ + const char *getResource(void) const { return _resource; } + +private: + const char *_resource; /**< Resource that failed to load. */ +}; + +#endif // _EXCEPTION_H_ diff --git a/pekwm/FontHandler.cc b/pekwm/FontHandler.cc new file mode 100644 index 0000000..ee0af17 --- /dev/null +++ b/pekwm/FontHandler.cc @@ -0,0 +1,278 @@ +// +// FontHandler.cc for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include + +#include "PScreen.hh" +#include "ColorHandler.hh" +#include "FontHandler.hh" +#include "Util.hh" + +using std::cerr; +using std::endl; +using std::map; +using std::list; +using std::vector; +using std::string; + +FontHandler* FontHandler::_instance = 0; + +//! @brief FontHandler constructor +FontHandler::FontHandler(void) +{ +#ifdef DEBUG + if (_instance) { + cerr << __FILE__ << "@" << __LINE__ << ": " + << "FontHandler(" << this << ")::FontHandler()" + << endl << " *** _instance already set" << endl; + } +#endif // DEBUG + + if (_map_justify.size() == 0) { + _map_justify[""] = FONT_JUSTIFY_NO; + _map_justify["LEFT"] = FONT_JUSTIFY_LEFT; + _map_justify["CENTER"] = FONT_JUSTIFY_CENTER; + _map_justify["RIGHT"] = FONT_JUSTIFY_RIGHT; + } + + if (_map_type.size() == 0) { + _map_type[""] = PFont::FONT_TYPE_NO; + _map_type["X11"] = PFont::FONT_TYPE_X11; + _map_type["XFT"] = PFont::FONT_TYPE_XFT; + _map_type["XMB"] = PFont::FONT_TYPE_XMB; + } + + _instance = this; +} + +//! @brief FontHandler destructor +FontHandler::~FontHandler(void) +{ + list >::iterator it_f(_font_list.begin()); + for (; it_f != _font_list.end(); ++it_f) { + delete it_f->getData(); + } + + list >::iterator it_c(_color_list.begin()); + for (; it_c != _color_list.end(); ++it_c) { + delete it_c->getData(); + } +} + +//! @brief Gets or allocs a font +//! +//! Syntax of font specification goes as follows: +//! "Font Name#Justify#Offset#Type" ex "Vera#Center#1 1#XFT" +//! where only the first field is obligatory and type needs to be the last +//! +PFont* +FontHandler::getFont(const std::string &font) +{ + // Check cache + list >::iterator it(_font_list.begin()); + for (; it != _font_list.end(); ++it) { + if (*it == font) { + it->incRef(); + return it->getData(); + } + } + + // create new + PFont *pfont = 0; + + vector tok; + vector::iterator tok_it; // old gcc doesn't like --tok.end() + if ((Util::splitString(font, tok, "#", 0, true)) > 1) { + // Try getting the font type from the first paramter, if that + // doesn't work fall back to the last. This is to backwards + // compatible. + tok_it = tok.begin(); + uint type = ParseUtil::getValue(*tok_it, _map_type); + if (type == PFont::FONT_TYPE_NO) { + tok_it = tok.end() - 1; + type = ParseUtil::getValue(*tok_it, _map_type); + } + + switch (type) { + case PFont::FONT_TYPE_XMB: + pfont = new PFontXmb(PScreen::instance()); + tok.erase(tok_it); + break; +#ifdef HAVE_XFT + case PFont::FONT_TYPE_XFT: + pfont = new PFontXft(PScreen::instance()); + tok.erase(tok_it); + break; +#endif // HAVE_XFT + case PFont::FONT_TYPE_X11: + pfont = new PFontX11(PScreen::instance()); + tok.erase(tok_it); + break; + default: + pfont = new PFontXmb(PScreen::instance()); + break; + }; + pfont->load(tok.front()); + + // Remove used fields, type and + tok.erase(tok.begin()); + + // fields left for justify and offset + vector::iterator s_it(tok.begin()); + for (; s_it != tok.end(); ++s_it) { + if (isdigit((*s_it)[0])) { // number + vector tok_2; + if (Util::splitString(*s_it, tok_2, " \t", 2) == 2) { + pfont->setOffset(strtol(tok_2[0].c_str(), 0, 10), + strtol(tok_2[1].c_str(), 0, 10)); + } + } else { // justify + uint justify = ParseUtil::getValue(*s_it, _map_justify); + if (justify == FONT_JUSTIFY_NO) { + justify = FONT_JUSTIFY_LEFT; + } + pfont->setJustify(justify); + } + } + } else { + pfont = new PFontXmb(PScreen::instance()); + pfont->load(font); + } + + // create new entry + HandlerEntry entry(font); + entry.incRef(); + entry.setData(pfont); + + _font_list.push_back(entry); + + return pfont; +} + +//! @brief Returns a font +void +FontHandler::returnFont(PFont *font) +{ + list >::iterator it(_font_list.begin()); + for (; it != _font_list.begin(); ++it) { + if (it->getData() == font) { + it->decRef(); + if (! it->getRef()) { + delete it->getData(); + _font_list.erase(it); + } + break; + } + } +} + +//! @brief Gets or allocs a color +PFont::Color* +FontHandler::getColor(const std::string &color) +{ + // check cache + list >::iterator it(_color_list.begin()); + for (; it != _color_list.end(); ++it) { + if (*it == color) { + it->incRef(); + return it->getData(); + } + } + + // create new + PFont::Color *font_color = new PFont::Color(); + font_color->setHasFg(true); + + vector tok; + if (Util::splitString(color, tok, " \t", 2) == 2) { + loadColor(tok[0], font_color, true); + loadColor(tok[1], font_color, false); + font_color->setHasBg(true); + } else { + loadColor(color, font_color, true); + } + + // create new entry + HandlerEntry entry(color); + entry.incRef(); + entry.setData(font_color); + + _color_list.push_back(entry); + + return font_color; +} + +//! @brief Returns a color +void +FontHandler::returnColor(PFont::Color *color) +{ + list >::iterator it(_color_list.begin()); + for (; it != _color_list.begin(); ++it) { + if (it->getData() == color) { + it->decRef(); + if (! it->getRef()) { + delete it->getData(); + _color_list.erase(it); + } + break; + } + } +} + +//! @brief Helper loader of font colors ( main and offset color ) +void +FontHandler::loadColor(const std::string &color, PFont::Color *font_color, bool fg) +{ + XColor *xc; + + vector tok; + if (Util::splitString(color, tok, ",", 2, true) == 2) { + uint alpha = static_cast(strtol(tok[1].c_str(), 0, 10)); + if (alpha > 100) { + cerr << " *** WARNING: Alpha for font color greater than 100%" << endl; + alpha = 100; + } + + alpha = static_cast(65535 * (static_cast(alpha) / 100)); + + if (fg) { + font_color->setFgAlpha(alpha); + } else { + font_color->setBgAlpha(alpha); + } + xc = ColorHandler::instance()->getColor(tok[0]); + } else { + xc = ColorHandler::instance()->getColor(color); + } + + if (fg) { + font_color->setFg(xc); + } else { + font_color->setBg(xc); + } +} + +//! @brief Helper unloader of font colors +void +FontHandler::freeColor(PFont::Color *font_color) +{ + if (font_color->hasFg()) { + ColorHandler::instance()->returnColor(font_color->getFg()); + } + + if (font_color->hasBg()) { + ColorHandler::instance()->returnColor(font_color->getBg()); + } + + delete font_color; +} diff --git a/pekwm/FontHandler.hh b/pekwm/FontHandler.hh new file mode 100644 index 0000000..e60b699 --- /dev/null +++ b/pekwm/FontHandler.hh @@ -0,0 +1,52 @@ +// +// FontHandler.hh for pekwm +// Copyright (C) 2004-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "../config.h" + +#ifndef _FONT_HANDLER_HH_ +#define _FONT_HANDLER_HH_ + +#include "PFont.hh" +#include "Handler.hh" +#include "ParseUtil.hh" + +#include +#include +#include + +//! @brief FontHandler, a caching and font type transparent font handler. +class FontHandler { +public: + FontHandler(void); + ~FontHandler(void); + + //! @brief Returns the FontHandler instance pointer. + static inline FontHandler *instance(void) { return _instance; } + + PFont *getFont(const std::string &font); + void returnFont(PFont *font); + + PFont::Color *getColor(const std::string &color); + void returnColor(PFont::Color *color); + +private: + void loadColor(const std::string &color, PFont::Color *font_color, bool fg); + void freeColor(PFont::Color *font_color); + +private: + std::list > _font_list; + std::list > _color_list; + + std::map _map_type; + std::map _map_justify; + + //! @brief Pointer to FontHandler instance, should only be one. + static FontHandler *_instance; +}; + +#endif // _FONT_HANDLER_HH_ diff --git a/pekwm/Frame.cc b/pekwm/Frame.cc new file mode 100644 index 0000000..067e123 --- /dev/null +++ b/pekwm/Frame.cc @@ -0,0 +1,2325 @@ +// +// Frame.cc for pekwm +// Copyright © 2002-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "Frame.hh" + +#include "Compat.hh" +#include "PScreen.hh" +#include "Config.hh" +#include "ActionHandler.hh" +#include "AutoProperties.hh" +#include "Client.hh" +#include "ScreenResources.hh" +#include "StatusWindow.hh" +#include "Workspaces.hh" +#include "WindowManager.hh" +#include "KeyGrabber.hh" +#include "PMenu.hh" +#include "Theme.hh" + +using std::cerr; +using std::endl; +using std::find; +using std::list; +using std::mem_fun; +using std::string; +using std::vector; +using std::wstring; +using std::swprintf; + +list Frame::_frame_list = list(); +vector Frame::_frameid_list = vector(); + +Frame* Frame::_tag_frame = 0; +bool Frame::_tag_behind = false; + +//! @brief Frame constructor +Frame::Frame(Client *client, AutoProperty *ap) + : PDecor(WindowManager::instance()->getTheme(), + Frame::getClientDecorName(client), client->getWindow()), + _id(0), _client(0), _class_hint(0), + _non_fullscreen_decor_state(0), _non_fullscreen_layer(LAYER_NORMAL) +{ + // setup basic pointers + _scr = WindowManager::instance()->getScreen(); + + // PWinObj attributes + _type = WO_FRAME; + + // PDecor attributes + _decor_cfg_child_move_overloaded = true; + _decor_cfg_bpr_replay_pointer = true; + _decor_cfg_bpr_al_title = MOUSE_ACTION_LIST_TITLE_FRAME; + _decor_cfg_bpr_al_child = MOUSE_ACTION_LIST_CHILD_FRAME; + + // grab buttons so that we can reply them + for (uint i = 0; i < BUTTON_NO; ++i) { + XGrabButton(_dpy, i, AnyModifier, _window, + True, ButtonPressMask|ButtonReleaseMask, + GrabModeSync, GrabModeAsync, None, None); + } + + // get unique id of the frame, if the client didn't have an id + if (! WindowManager::instance()->isStartup()) { + long id; + if (AtomUtil::getLong(client->getWindow(), Atoms::getAtom(PEKWM_FRAME_ID), id)) { + _id = id; + } + + } else { + _id = findFrameID(); + } + + // get the clients class_hint + _class_hint = new ClassHint(); + *_class_hint = *client->getClassHint(); + + _scr->grabServer(); + + // We don't send any ConfigurRequests during setup, we send one when we + // are finished to minimize traffic and confusion + client->setConfigureRequestLock(true); + + // Before setting position and size up we make sure the decor state + // of the client match the decore state of the framewidget + if (! client->hasBorder()) { + setBorder(STATE_UNSET); + } + if (! client->hasTitlebar()) { + setTitlebar(STATE_UNSET); + } + + // We first get the size of the window as it will be needed when placing + // the window with the help of WinGravity. + resizeChild(client->getWidth(), client->getHeight()); + + // setup position + bool place = false; + if (client->isViewable() || client->isPlaced()) { + moveChild(client->getX(), client->getY()); + } else if (client->setPUPosition()) { + int x, y; + calcGravityPosition(client->getXSizeHints()->win_gravity, client->getX(), client->getY(), x, y); + move(x, y); + } else { + place = Config::instance()->isPlaceNew(); + } + + // override both position and size with autoproperties + bool ap_geometry = false; + if (ap) { + if (ap->isMask(AP_FRAME_GEOMETRY|AP_CLIENT_GEOMETRY)) { + ap_geometry = true; + + setupAPGeometry(client, ap); + + if (ap->frame_gm_mask&(XValue|YValue) || ap->client_gm_mask&(XValue|YValue)) { + place = false; + } + } + + if (ap->isMask(AP_PLACE_NEW)) { + place = ap->place_new; + } + } + + // still need a position? + if (place) { + Workspaces::instance()->placeWo(this, client->getTransientClientWindow()); + } + + _old_gm = _gm; + _non_fullscreen_decor_state = client->getDecorState(); + _non_fullscreen_layer = client->getLayer(); + + _scr->ungrabServer(true); // ungrab and sync + + // now insert the client in the frame we created. + addChild(client); + + // needs to be done before the workspace insert and after the client + // has been inserted, in order for layer settings to be propagated + setLayer(client->getLayer()); + + // I add these to the list before I insert the client into the frame to + // be able to skip an extra updateClientList + _frame_list.push_back(this); + WindowManager::instance()->addToFrameList(this); + Workspaces::instance()->insert(this); + + activateChild(client); + + // set the window states, shaded, maximized... + getState(client); + + if (! _client->hasStrut() && ! ap_geometry) { + if (fixGeometry()) { + moveResize(_gm.x, _gm.y, _gm.width, _gm.height); + } + } + + client->setConfigureRequestLock(false); + client->configureRequestSend(); + + // Figure out if we should be hidden or not, do not read autoprops + PDecor::setWorkspace(_client->getWorkspace()); + + woListAdd(this); + _wo_map[_window] = this; +} + +//! @brief Frame destructor +Frame::~Frame(void) +{ + // remove from lists + _wo_map.erase(_window); + woListRemove(this); + _frame_list.remove(this); + Workspaces::instance()->remove(this); + WindowManager::instance()->removeFromFrameList(this); + if (_tag_frame == this) { + _tag_frame = 0; + } + + returnFrameID(_id); + + if (_class_hint) { + delete _class_hint; + } + + Workspaces::instance()->updateClientList(); + Workspaces::instance()->updateClientStackingList(); +} + +// START - PWinObj interface. + +//! @brief Iconifies the Frame. +void +Frame::iconify(void) +{ + if (_iconified) { + return; + } + _iconified = true; + + unmapWindow(); +} + +//! @brief Toggles the Frame's sticky state +void +Frame::stick(void) +{ + _client->setSticky(_sticky); // FIXME: FRAME + _client->stick(); + + _sticky = ! _sticky; + + // make sure it's visible/hidden + PDecor::setWorkspace(Workspaces::instance()->getActive()); +} + +void +Frame::raise(void) +{ + PDecor::raise(); + + if (_client->transient_size()) { + Frame *frame; + list::iterator it(_client->transient_begin()); + for (; it != _client->transient_end(); ++it) { + frame = static_cast((*it)->getParent()); + if (frame != this && frame->getActiveClient() == *it) { + frame->raise(); + } + } + } +} + +//! @brief Sets workspace on frame, wrapper to allow autoproperty loading +void +Frame::setWorkspace(unsigned int workspace) +{ + // Duplicate the behavior done in PDecor::setWorkspace to have a sane + // value on _workspace and not NET_WM_STICKY_WINDOW. + if (workspace != NET_WM_STICKY_WINDOW) { + // First we set the workspace, then load autoproperties for possible + // overrun of workspace and then set the workspace. + _workspace = workspace; + readAutoprops(APPLY_ON_WORKSPACE); + workspace = _workspace; + } + + PDecor::setWorkspace(workspace); +} + +void +Frame::setLayer(unsigned int layer) +{ + PDecor::setLayer(layer); + + if (_client) { + LayerObservation *observation = new LayerObservation(Layer(layer)); + _client->notifyObservers(observation); + delete observation; + } +} + +// event handlers + +ActionEvent* +Frame::handleMotionEvent(XMotionEvent *ev) +{ + // This is true when we have a title button pressed and then we don't want + // to be able to drag windows around, therefore we ignore the event + if (_button) { + return 0; + } + + ActionEvent *ae = 0; + uint button = _scr->getButtonFromState(ev->state); + + if (ev->window == getTitleWindow()) { + ae = ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_TITLE_FRAME)); + } else if (ev->window == _client->getWindow()) { + ae = ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_CHILD_FRAME)); + } else { + uint pos = getBorderPosition(ev->subwindow); + + // If ev->subwindow wasn't one of the border windows, perhaps ev->window is. + if (pos == BORDER_NO_POS) { + pos = getBorderPosition(ev->window); + } + + if (pos != BORDER_NO_POS) { + list *bl = Config::instance()->getBorderListFromPosition(pos); + ae = ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION, bl); + } + } + + // check motion threshold + if (ae && (ae->threshold > 0)) { + if (! ActionHandler::checkAEThreshold(ev->x_root, ev->y_root, + _pointer_x, _pointer_y, ae->threshold)) { + ae = 0; + } + } + + return ae; +} + +//! @brief +ActionEvent* +Frame::handleEnterEvent(XCrossingEvent *ev) +{ + // Run event handler to get hoovering to work but ignore action + // returned. + PDecor::handleEnterEvent(ev); + + ActionEvent *ae = 0; + list *al = 0; + + if (ev->window == getTitleWindow()) { + al = Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_TITLE_FRAME); + + } else if (ev->subwindow == _client->getWindow()) { + al = Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_CHILD_FRAME); + + } else { + uint pos = getBorderPosition(ev->window); + if (pos != BORDER_NO_POS) { + al = Config::instance()->getBorderListFromPosition(pos); + } + } + + if (al) { + ae = ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_ENTER, al); + } + + return ae; +} + +//! @brief +ActionEvent* +Frame::handleLeaveEvent(XCrossingEvent *ev) +{ + // Run event handler to get hoovering to work but ignore action + // returned. + PDecor::handleLeaveEvent(ev); + + ActionEvent *ae; + + MouseActionListName ln = MOUSE_ACTION_LIST_TITLE_FRAME; + if (ev->window == _client->getWindow()) { + ln = MOUSE_ACTION_LIST_CHILD_FRAME; + } + + ae = ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_LEAVE, + Config::instance()->getMouseActionList(ln)); + + return ae; +} + +ActionEvent* +Frame::handleMapRequest(XMapRequestEvent *ev) +{ + if (! _client || (ev->window != _client->getWindow())) { + return 0; + } + + if (! _sticky && (_workspace != Workspaces::instance()->getActive())) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "Ignoring MapRequest, not on current workspace!" << endl; +#endif // DEBUG + return 0; + } + + mapWindow(); + + return 0; +} + +ActionEvent* +Frame::handleUnmapEvent(XUnmapEvent *ev) +{ + list::iterator it(_child_list.begin()); + for (; it != _child_list.end(); ++it) { + if (*(*it) == ev->window) { + (*it)->handleUnmapEvent(ev); + break; + } + } + + return 0; +} + +// END - PWinObj interface. + +void +Frame::handleShapeEvent(XAnyEvent *ev) +{ +#ifdef HAVE_SHAPE + if (! _client || (ev->window != _client->getWindow())) { + return; + } + _client->setShaped(setShape()); +#endif // HAVE_SHAPE +} + +// START - PDecor interface. + +bool +Frame::allowMove(void) const +{ + return _client ? _client->allowMove() : true; +} + +/** + * Return active client, or 0 if no clients or active child is not a Client. + */ +Client* +Frame::getActiveClient(void) +{ + if (getActiveChild() && getActiveChild()->getType() == WO_CLIENT) { + return static_cast(getActiveChild()); + } else { + return 0; + } +} + +//! @brief Adds child to the frame. +void +Frame::addChild (PWinObj *child, std::list::iterator *it) +{ + PDecor::addChild(child, it); + AtomUtil::setLong(child->getWindow(), Atoms::getAtom(PEKWM_FRAME_ID), _id); + child->lower(); +} + +/** + * Add child preserving order from the previous pekwm run. + */ +void +Frame::addChildOrdered (Client *child) +{ + Client *client; + + list::iterator it(_child_list.begin()); + for (; it != _child_list.end(); ++it) { + client = static_cast(*it); + if (child->getInitialFrameOrder() < client->getInitialFrameOrder()) { + break; + } + } + + addChild(child, &it); +} + +//! @brief Removes child from the frame. +void +Frame::removeChild (PWinObj *child, bool do_delete) +{ + PDecor::removeChild(child, do_delete); +} + +/** + * Activates child in Frame, updating it's state and re-loading decor + * rules to match updated title. + */ +void +Frame::activateChild (PWinObj *child) +{ + // FIXME: Update default decoration for this child, can change + // decoration from DEFAULT to REMOTE or WARNING + + // Sync the frame state with the client only if we already had a client + if (_client && _client != child) { + applyState(static_cast(child)); + } + + _client = static_cast(child); + PDecor::activateChild(child); + + // setShape uses current active child, so we need to activate the + // child before setting shape + if (PScreen::instance()->hasExtensionShape()) { + _client->setShaped(setShape()); + } + +#ifdef OPACITY + setOpacity(_client); +#endif // OPACITY + + if (_focused) { + child->giveInputFocus(); + } + + // Reload decor rules if needed. + handleTitleChange(_client); + + Workspaces::instance()->updateClientList(); + Workspaces::instance()->updateClientStackingList(); +} + +/** + * Called when child order is updated, re-sets the titles and updates + * the _PEKWM_FRAME hints. + */ +void +Frame::updatedChildOrder(void) +{ + titleClear(); + + Client *client; + list::iterator it(_child_list.begin()); + for (long num = 0; it != _child_list.end(); ++num, ++it) { + client = static_cast(*it); + client->setPekwmFrameOrder(num); + titleAdd(client->getTitle()); + } + + updatedActiveChild(); +} + +/** + * Set the active title and update the _PEKWM_FRAME hints. + */ +void +Frame::updatedActiveChild(void) +{ + titleSetActive(0); + + list::iterator it(_child_list.begin()); + for (uint i = 0; it != _child_list.end(); ++i, ++it) { + static_cast(*it)->setPekwmFrameActive(_child == *it); + if (_child == *it) { + titleSetActive(i); + } + } + + renderTitle(); +} + +//! @brief +void +Frame::getDecorInfo(wchar_t *buf, uint size) +{ + uint width, height; + if (_client) { + calcSizeInCells(width, height); + } else { + width = _gm.width; + height = _gm.height; + } + swprintf(buf, size, L"%d+%d+%d+%d", width, height, _gm.x, _gm.y); +} + +//! @brief +void +Frame::setShaded(StateAction sa) +{ + bool shaded = isShaded(); + PDecor::setShaded(sa); + if (shaded != isShaded()) { + _client->setShade(isShaded()); + _client->updateEwmhStates(); + } +} + +//! @brief +int +Frame::resizeHorzStep(int diff) const +{ + if (! _client) { + return diff; + } + + int diff_ret = 0; + uint min = _gm.width - getChildWidth(); + if (min == 0) { // borderless windows, we don't want X errors + min = 1; + } + const XSizeHints *hints = _client->getXSizeHints(); // convenience + + // if we have ResizeInc hint set we use it instead of pixel diff + if (hints->flags&PResizeInc) { + if (diff > 0) { + diff_ret = hints->width_inc; + } else if ((_gm.width - hints->width_inc) >= min) { + diff_ret = -hints->width_inc; + } + } else if ((_gm.width + diff) >= min) { + diff_ret = diff; + } + + // check max/min size hints + if (diff > 0) { + if ((hints->flags&PMaxSize) && + ((getChildWidth() + diff) > unsigned(hints->max_width))) { + diff_ret = _gm.width - hints->max_width + min; + } + } else if ((hints->flags&PMinSize) && + ((getChildWidth() + diff) < unsigned(hints->min_width))) { + diff_ret = _gm.width - hints->min_width + min; + } + + return diff_ret; +} + +//! @brief +int +Frame::resizeVertStep(int diff) const +{ + if (! _client) { + return diff; + } + + int diff_ret = 0; + uint min = _gm.height - getChildHeight(); + if (min == 0) { // borderless windows, we don't want X errors + min = 1; + } + const XSizeHints *hints = _client->getXSizeHints(); // convenience + + // if we have ResizeInc hint set we use it instead of pixel diff + if (hints->flags&PResizeInc) { + if (diff > 0) { + diff_ret = hints->height_inc; + } else if ((_gm.height - hints->height_inc) >= min) { + diff_ret = -hints->height_inc; + } + } else { + diff_ret = diff; + } + + // check max/min size hints + if (diff > 0) { + if ((hints->flags&PMaxSize) && + ((getChildHeight() + diff) > unsigned(hints->max_height))) { + diff_ret = _gm.height - hints->max_height + min; + } + } else if ((hints->flags&PMinSize) && + ((getChildHeight() + diff) < unsigned(hints->min_height))) { + diff_ret = _gm.height - hints->min_width + min; + } + + return diff_ret; +} + +// END - PDecor interface. + +//! @brief Sets _PEKWM_FRAME_ID on all children in the frame +void +Frame::setId(uint id) +{ + _id = id; + + list::iterator it(_child_list.begin()); + long atom = Atoms::getAtom(PEKWM_FRAME_ID); + for (; it != _child_list.end(); ++it) { + AtomUtil::setLong((*it)->getWindow(), atom, id); + } +} + +//! @brief Gets the state from the Client +void +Frame::getState(Client *cl) +{ + if (! cl) + return; + + bool b_client_iconified = cl->isIconified (); + + if (_sticky != cl->isSticky()) + _sticky = ! _sticky; + if (_maximized_horz != cl->isMaximizedHorz()) + setStateMaximized(STATE_TOGGLE, true, false, false); + if (_maximized_vert != cl->isMaximizedVert()) + setStateMaximized(STATE_TOGGLE, false, true, false); + if (isShaded() != cl->isShaded()) + setShaded(STATE_TOGGLE); + if (getLayer() != cl->getLayer()) { + setLayer(cl->getLayer()); + } + if (_workspace != cl->getWorkspace()) + PDecor::setWorkspace(cl->getWorkspace()); + + // We need to set border and titlebar before setting fullscreen, as + // fullscreen will unset border and titlebar if needed. + if (hasBorder() != _client->hasBorder()) + setBorder(STATE_TOGGLE); + if (hasTitlebar() != _client->hasTitlebar()) + setTitlebar(STATE_TOGGLE); + if (_fullscreen != cl->isFullscreen()) + setStateFullscreen(STATE_TOGGLE); + + if (_iconified != b_client_iconified) { + if (_iconified) { + mapWindow(); + } else { + iconify(); + } + } + + if (_skip != cl->getSkip()) { + setSkip(cl->getSkip()); + } +} + +//! @brief Applies the frame's state on the Client +void +Frame::applyState(Client *cl) +{ + if (! cl) { + return; + } + + cl->setSticky(_sticky); + cl->setMaximizedHorz(_maximized_horz); + cl->setMaximizedVert(_maximized_vert); + cl->setShade(isShaded()); + cl->setWorkspace(_workspace); + cl->setLayer(getLayer()); + + // fix border / titlebar state + cl->setBorder(hasBorder()); + cl->setTitlebar(hasTitlebar()); + // make sure the window has the correct mapped state + if (_mapped != cl->isMapped()) { + if (! _mapped) { + cl->unmapWindow(); + } else { + cl->mapWindow(); + } + } + + cl->updateEwmhStates(); +} + +//! @brief Sets skip state. +void +Frame::setSkip(uint skip) +{ + PDecor::setSkip(skip); + + // Propagate changes on client as well + if (_client) { + _client->setSkip(skip); + } +} + +//! @brief Find Frame with Window +//! @param win Window to search for. +//! @return Frame if found, else 0. +Frame* +Frame::findFrameFromWindow(Window win) +{ + // Validate input window. + if ((win == None) || (win == PScreen::instance()->getRoot())) { + return 0; + } + + list::iterator it(_frame_list.begin()); + for(; it !=_frame_list.end(); ++it) { + if (win == (*it)->getWindow()) { // operator == does more than that + return (*it); + } + } + + return 0; +} + +//! @brief Find Frame with id. +//! @param id ID to search for. +//! @return Frame if found, else 0. +Frame* +Frame::findFrameFromID(uint id) +{ + list::iterator it(Frame::frame_begin()); + for (; it != Frame::frame_end(); ++it) { + if ((*it)->getId() == id) { + return (*it); + } + } + + return 0; +} + +//! @brief +void +Frame::setupAPGeometry(Client *client, AutoProperty *ap) +{ + // frame geomtry overides client geometry + + // get client geometry + if (ap->isMask(AP_CLIENT_GEOMETRY)) { + Geometry gm(client->_gm); + applyAPGeometry(gm, ap->client_gm, ap->client_gm_mask); + + if (ap->client_gm_mask&(XValue|YValue)) { + moveChild(gm.x, gm.y); + } + if(ap->client_gm_mask&(WidthValue|HeightValue)) { + resizeChild(gm.width, gm.height); + } + } + + // get frame geometry + if (ap->isMask(AP_FRAME_GEOMETRY)) { + applyAPGeometry(_gm, ap->frame_gm, ap->frame_gm_mask); + if (ap->frame_gm_mask&(XValue|YValue)) { + move(_gm.x, _gm.y); + } + if (ap->frame_gm_mask&(WidthValue|HeightValue)) { + resize(_gm.width, _gm.height); + } + } +} + +//! @brief Apply autoproperties geometry. +//! @param gm Geometry to modify. +//! @param ap_gm Geometry to get values from. +//! @param mask Geometry mask. +void +Frame::applyAPGeometry(Geometry &gm, const Geometry &ap_gm, int mask) +{ + // Read size before position so negative position works, if size is + // < 1 consider it to be full screen size. + if (mask&WidthValue) { + if (ap_gm.width < 1) { + gm.width = _scr->getWidth(); + } else { + gm.width = ap_gm.width; + } + } + + if (mask&HeightValue) { + if (ap_gm.height < 1) { + } else { + gm.height = ap_gm.height; + } + } + + // Read position + if (mask&XValue) { + gm.x = ap_gm.x; + if (mask&XNegative) { + gm.x += _scr->getWidth() - gm.width; + } + } + if (mask&YValue) { + gm.y = ap_gm.y; + if (mask&YNegative) { + gm.y += _scr->getHeight() - gm.height; + } + } +} + +//! @brief Finds free Frame ID. +//! @return First free Frame ID. +uint +Frame::findFrameID(void) +{ + uint id = 0; + + if (_frameid_list.size()) { + // Check for used Frame IDs + id = _frameid_list.back(); + _frameid_list.pop_back(); + } else { + // No free, get next number (Frame is not in list when this is called.) + id = _frame_list.size() + 1; + } + + return id; +} + +//! @brief Returns Frame ID to used frame id list. +//! @param id ID to return. +void +Frame::returnFrameID(uint id) +{ + vector::iterator it(_frameid_list.begin()); + for (; it != _frameid_list.end() && id < *it; ++it) + ; + _frameid_list.insert(it, id); +} + +/** + * Return decor name matching clients property, defaults to + * PDecor::DEFAULT_DECOR_NAME + */ +std::string +Frame::getClientDecorName(Client *client) +{ + DecorProperty *prop = WindowManager::instance()->getAutoProperties()->findDecorProperty(client->getClassHint()); + return prop ? prop->getName() : PDecor::DEFAULT_DECOR_NAME; +} + +//! @brief Resets Frame IDs. +void +Frame::resetFrameIDs(void) +{ + list::iterator it(_frame_list.begin()); + for (uint id = 1; it != _frame_list.end(); ++id, ++it) { + (*it)->setId(id); + } +} + + +//! @brief Removes the client from the Frame and creates a new Frame for it +void +Frame::detachClient(Client *client) +{ + if (client->getParent() != this) { + return; + } + + if (_child_list.size() > 1) { + removeChild(client); + + client->move(_gm.x, _gm.y + borderTop()); + Frame *frame = new Frame(client, 0); + + client->setParent(frame); + client->setWorkspace(Workspaces::instance()->getActive()); + + setFocused(false); + } +} + +//! @brief Makes sure the frame doesn't cover any struts / the harbour. +bool +Frame::fixGeometry(void) +{ + Geometry head, before; + PScreen::instance()->getHeadInfoWithEdge(getNearestHead(), head); + + before = _gm; + + // fix size + if (_gm.width > head.width) { + _gm.width = head.width; + } if (_gm.height > head.height) { + _gm.height = head.height; + } + + // fix position + if (_gm.x < head.x) { + _gm.x = head.x; + } else if ((_gm.x + _gm.width) > (head.x + head.width)) { + _gm.x = head.x + head.width - _gm.width; + } + if (_gm.y < head.y) { + _gm.y = head.y; + } else if ((_gm.y + _gm.height) > (head.y + head.height)) { + _gm.y = head.y + head.height - _gm.height; + } + + return (_gm != before); +} + +//! @brief Initiates grouping move, based on a XMotionEvent. +void +Frame::doGroupingDrag(XMotionEvent *ev, Client *client, bool behind) // FIXME: rewrite +{ + if (! client) { + return; + } + + int o_x, o_y; + o_x = ev ? ev->x_root : 0; + o_y = ev ? ev->y_root : 0; + + wstring name(L"Grouping "); + if (client->getTitle()->getVisible().size() > 0) { + name += client->getTitle()->getVisible(); + } else { + name += L"No Name"; + } + + bool status = _scr->grabPointer(_scr->getRoot(), + ButtonReleaseMask|PointerMotionMask, None); + if (status != true) { + return; + } + + StatusWindow *sw = StatusWindow::instance(); + + sw->draw(name); // resize window and render bg + sw->move(o_x, o_y); + sw->mapWindowRaised(); + sw->draw(name); // redraw after map + + XEvent e; + while (true) { // this breaks when we get an button release + XMaskEvent(_dpy, PointerMotionMask|ButtonReleaseMask, &e); + + switch (e.type) { + case MotionNotify: + // update the position + o_x = e.xmotion.x_root; + o_y = e.xmotion.y_root; + + sw->move(o_x, o_y); + sw->draw(name); + break; + + case ButtonRelease: + sw->unmapWindow(); + _scr->ungrabPointer(); + + Client *search = 0; + + // only group if we have grouping turned on + if (WindowManager::instance()->isAllowGrouping()) { + int x, y; + Window win; + + // find the frame we dropped the client on + XTranslateCoordinates(_dpy, _scr->getRoot(), _scr->getRoot(), + e.xmotion.x_root, e.xmotion.y_root, + &x, &y, &win); + + search = Client::findClient(win); + } + + // if we found a client, and it's not in the current frame and + // it has a "normal" ( make configurable? ) layer we group + if (search && search->getParent() && + (search->getParent() != this) && + (search->getLayer() > LAYER_BELOW) && + (search->getLayer() < LAYER_ONTOP)) { + + // if we currently have focus and the frame exists after we remove + // this client we need to redraw it as unfocused + bool focus = behind ? false : (_child_list.size() > 1); + + removeChild(client); + + Frame *frame = static_cast(search->getParent()); + frame->addChild(client); + if (! behind) { + frame->activateChild(client); + frame->giveInputFocus(); + } + + if (focus) { + setFocused(false); + } + + } else if (_child_list.size() > 1) { + // if we have more than one client in the frame detach this one + removeChild(client); + + client->move(e.xmotion.x_root, e.xmotion.y_root); + + Frame *frame = new Frame(client, 0); + client->setParent(frame); + + // make sure the client ends up on the current workspace + client->setWorkspace(Workspaces::instance()->getActive()); + + // make sure it get's focus + setFocused(false); + frame->giveInputFocus(); + } + + return; + } + } +} + +//! @brief Initiates resizing of a window based on motion event +void +Frame::doResize(XMotionEvent *ev) +{ + if (! ev) { + return; + } + + // figure out which part of the window we are in + bool left = false, top = false; + if (ev->x < signed(_gm.width / 2)) { + left = true; + } + if (ev->y < signed(_gm.height / 2)) { + top = true; + } + + doResize(left, true, top, true); +} + +//! @brief Initiates resizing of a window based border position +void +Frame::doResize(BorderPosition pos) +{ + bool x = false, y = false; + bool left = false, top = false, resize = true; + + switch (pos) { + case BORDER_TOP_LEFT: + x = y = left = top = true; + break; + case BORDER_TOP: + y = top = true; + break; + case BORDER_TOP_RIGHT: + x = y = top = true; + break; + case BORDER_LEFT: + x = left = true; + break; + case BORDER_RIGHT: + x = true; + break; + case BORDER_BOTTOM_LEFT: + x = y = left = true; + break; + case BORDER_BOTTOM: + y = true; + break; + case BORDER_BOTTOM_RIGHT: + x = y = true; + break; + default: + resize = false; + break; + } + + if (resize) { + doResize(left, x, top, y); + } +} + +//! @brief Resizes the frame by handling MotionNotify events. +void +Frame::doResize(bool left, bool x, bool top, bool y) +{ + if (! _client->allowResize() || isShaded()) { + return; + } + + if (! _scr->grabPointer(_scr->getRoot(), ButtonMotionMask|ButtonReleaseMask, + ScreenResources::instance()->getCursor(ScreenResources::CURSOR_RESIZE))) { + return; + } + + setShaded(STATE_UNSET); // make sure the frame isn't shaded + setStateFullscreen(STATE_UNSET); + + // Initialize variables + int start_x, new_x; + int start_y, new_y; + uint last_width, old_width; + uint last_height, old_height; + + start_x = new_x = left ? _gm.x : (_gm.x + _gm.width); + start_y = new_y = top ? _gm.y : (_gm.y + _gm.height); + last_width = old_width = _gm.width; + last_height = old_height = _gm.height; + + // the basepoint of our window + _click_x = left ? (_gm.x + _gm.width) : _gm.x; + _click_y = top ? (_gm.y + _gm.height) : _gm.y; + + int pointer_x = _gm.x, pointer_y = _gm.y; + _scr->getMousePosition(pointer_x, pointer_y); + + wchar_t buf[128]; + getDecorInfo(buf, 128); + + bool center_on_root = Config::instance()->isShowStatusWindowOnRoot(); + StatusWindow *sw = StatusWindow::instance(); + if (Config::instance()->isShowStatusWindow()) { + sw->draw(buf, true, center_on_root ? 0 : &_gm); + sw->mapWindowRaised(); + sw->draw(buf, true, center_on_root ? 0 : &_gm); + } + + bool outline = ! Config::instance()->getOpaqueResize(); + + // grab server, we don't want invert traces + if (outline) { + _scr->grabServer(); + } + + const long resize_mask = ButtonPressMask|ButtonReleaseMask|ButtonMotionMask; + XEvent ev; + bool exit = false; + while (exit != true) { + if (outline) { + drawOutline(_gm); + } + XMaskEvent(_dpy, resize_mask, &ev); + if (outline) { + drawOutline(_gm); // clear + } + + switch (ev.type) { + case MotionNotify: + // Flush all pointer motion, no need to redraw and redraw. + PScreen::removeMotionEvents(); + + if (x) { + new_x = start_x - pointer_x + ev.xmotion.x; + } + if (y) { + new_y = start_y - pointer_y + ev.xmotion.y; + } + + recalcResizeDrag(new_x, new_y, left, top); + + getDecorInfo(buf, 128); + if (Config::instance()->isShowStatusWindow()) { + sw->draw(buf, true, center_on_root ? 0 : &_gm); + } + + // only updated when needed when in opaque mode + if (! outline) { + if ((old_width != _gm.width) || (old_height != _gm.height)) { + moveResize(_gm.x, _gm.y, _gm.width, _gm.height); + } + old_width = _gm.width; + old_height = _gm.height; + } + break; + case ButtonRelease: + exit = true; + XPutBackEvent(_dpy, &ev); + break; + } + } + + if (Config::instance()->isShowStatusWindow()) { + sw->unmapWindow(); + } + + _scr->ungrabPointer(); + + // Make sure the state isn't set to maximized after we've resized. + if (_maximized_horz || _maximized_vert) { + _maximized_horz = false; + _maximized_vert = false; + _client->setMaximizedHorz(false); + _client->setMaximizedVert(false); + _client->updateEwmhStates(); + } + + if (outline) { + moveResize(_gm.x, _gm.y, _gm.width, _gm.height); + _scr->ungrabServer(true); + } +} + +//! @brief Updates the width, height of the frame when resizing it. +void +Frame::recalcResizeDrag(int nx, int ny, bool left, bool top) +{ + uint brdr_lr = borderLeft() + borderRight(); + uint brdr_tb = borderTop() + borderBottom(); + + if (left) { + if (nx >= signed(_click_x - brdr_lr)) + nx = _click_x - brdr_lr - 1; + } else { + if (nx <= signed(_click_x + brdr_lr)) + nx = _click_x + brdr_lr + 1; + } + + if (top) { + if (ny >= signed(_click_y - getTitleHeight() - brdr_tb)) + ny = _click_y - getTitleHeight() - brdr_tb - 1; + } else { + if (ny <= signed(_click_y + getTitleHeight() + brdr_tb)) + ny = _click_y + getTitleHeight() + brdr_tb + 1; + } + + uint width = left ? (_click_x - nx) : (nx - _click_x); + uint height = top ? (_click_y - ny) : (ny - _click_y); + + width -= brdr_lr; + height -= brdr_tb + getTitleHeight(); + + _client->getAspectSize(&width, &height, width, height); + + const XSizeHints *hints = _client->getXSizeHints(); + // check so we aren't overriding min or max size + if (hints->flags & PMinSize) { + if (signed(width) < hints->min_width) + width = hints->min_width; + if (signed(height) < hints->min_height) + height = hints->min_height; + } + + if (hints->flags & PMaxSize) { + if (signed(width) > hints->max_width) + width = hints->max_width; + if (signed(height) > hints->max_height) + height = hints->max_height; + } + + _gm.width = width + brdr_lr; + _gm.height = height + getTitleHeight() + brdr_tb; + + _gm.x = left ? (_click_x - _gm.width) : _click_x; + _gm.y = top ? (_click_y - _gm.height) : _click_y; +} + +//! @brief Moves the Frame to the screen edge ori ( considering struts ) +void +Frame::moveToEdge(OrientationType ori) +{ + uint head_nr; + Geometry head, real_head; + + head_nr = getNearestHead(); + _scr->getHeadInfo(head_nr, real_head); + _scr->getHeadInfoWithEdge(head_nr, head); + + switch (ori) { + case TOP_LEFT: + _gm.x = head.x; + _gm.y = head.y; + break; + case TOP_EDGE: + _gm.y = head.y; + break; + case TOP_CENTER_EDGE: + _gm.x = real_head.x + ((real_head.width - _gm.width) / 2); + _gm.y = head.y; + break; + case TOP_RIGHT: + _gm.x = head.x + head.width - _gm.width; + _gm.y = head.y; + break; + case BOTTOM_RIGHT: + _gm.x = head.x + head.width - _gm.width; + _gm.y = head.y + head.height - _gm.height; + break; + case BOTTOM_EDGE: + _gm.y = head.y + head.height - _gm.height; + break; + case BOTTOM_CENTER_EDGE: + _gm.x = real_head.x + ((real_head.width - _gm.width) / 2); + _gm.y = head.y + head.height - _gm.height; + break; + case BOTTOM_LEFT: + _gm.x = head.x; + _gm.y = head.y + head.height - _gm.height; + break; + case LEFT_EDGE: + _gm.x = head.x; + break; + case LEFT_CENTER_EDGE: + _gm.x = head.x; + _gm.y = real_head.y + ((real_head.height - _gm.height) / 2); + break; + case RIGHT_EDGE: + _gm.x = head.x + head.width - _gm.width; + break; + case RIGHT_CENTER_EDGE: + _gm.x = head.x + head.width - _gm.width; + _gm.y = real_head.y + ((real_head.height - _gm.height) / 2); + break; + case CENTER: + _gm.x = real_head.x + ((real_head.width - _gm.width) / 2); + _gm.y = real_head.y + ((real_head.height - _gm.height) / 2); + default: + // DO NOTHING + break; + } + + move(_gm.x, _gm.y); +} + +//! @brief Updates all inactive childrens geometry and state +void +Frame::updateInactiveChildInfo(void) +{ + if (! _client) { + return; + } + + list::iterator it(_child_list.begin()); + for (; it != _child_list.end(); ++it) { + if (*it != _client) { + applyState(static_cast(*it)); + (*it)->resize(getChildWidth(), getChildHeight()); + } + } +} + +// STATE actions begin + +//! @brief Toggles current clients max size +//! @param sa State to set +//! @param horz Include horizontal in (de)maximize +//! @param vert Include vertcical in (de)maximize +//! @param fill Limit size by other frame boundaries ( defaults to false ) +void +Frame::setStateMaximized(StateAction sa, bool horz, bool vert, bool fill) +{ + // we don't want to maximize transients + if (_client->isTransient()) { + return; + } + + setShaded(STATE_UNSET); + setStateFullscreen(STATE_UNSET); + + // make sure the two states are in sync if toggling + if ((horz == vert) && (sa == STATE_TOGGLE)) { + if (_maximized_horz != _maximized_vert) { + horz = ! _maximized_horz; + vert = ! _maximized_vert; + } + } + + XSizeHints *size_hint = _client->getXSizeHints(); // convenience + + Geometry head; + _scr->getHeadInfoWithEdge(getNearestHead(), head); + + int max_x, max_r, max_y, max_b; + max_x = head.x; + max_r = head.width + head.x; + max_y = head.y; + max_b = head.height + head.y; + + if (fill) { + getMaxBounds(max_x, max_r, max_y, max_b); + + // make sure vert and horz gets set if fill is on + sa = STATE_SET; + } + + if (horz && (fill || _client->allowMaximizeHorz())) { + // maximize + if ((sa == STATE_SET) || + ((sa == STATE_TOGGLE) && ! _maximized_horz)) { + uint h_decor = _gm.width - getChildWidth(); + + if (! fill) { + _old_gm.x = _gm.x; + _old_gm.width = _gm.width; + } + + _gm.x = max_x; + _gm.width = max_r - max_x; + + if ((size_hint->flags&PMaxSize) && + (_gm.width > (size_hint->max_width + h_decor))) { + _gm.width = size_hint->max_width + h_decor; + } + // demaximize + } else if ((sa == STATE_UNSET) || + ((sa == STATE_TOGGLE) && _maximized_horz)) { + _gm.x = _old_gm.x; + _gm.width = _old_gm.width; + } + + // we unset the maximized state if we use maxfill + _maximized_horz = fill ? false : ! _maximized_horz; + _client->setMaximizedHorz(_maximized_horz); + } + + if (vert && (fill || _client->allowMaximizeVert())) { + // maximize + if ((sa == STATE_SET) || + ((sa == STATE_TOGGLE) && ! _maximized_vert)) { + uint v_decor = _gm.height - getChildHeight(); + + if (! fill) { + _old_gm.y = _gm.y; + _old_gm.height = _gm.height; + } + + _gm.y = max_y; + _gm.height = max_b - max_y; + + if ((size_hint->flags&PMaxSize) && + (_gm.height > (size_hint->max_height + v_decor))) { + _gm.height = size_hint->max_height + v_decor; + } + // demaximize + } else if ((sa == STATE_UNSET) || + ((sa == STATE_TOGGLE) && _maximized_vert)) { + _gm.y = _old_gm.y; + _gm.height = _old_gm.height; + } + + // we unset the maximized state if we use maxfill + _maximized_vert = fill ? false : ! _maximized_vert; + _client->setMaximizedVert(_maximized_vert); + } + + fixGeometry(); // harbour already considered + downSize(_gm, true, true); // keep x and keep y ( make conform to inc size ) + + moveResize(_gm.x, _gm.y, _gm.width, _gm.height); + + _client->updateEwmhStates(); +} + +//! @brief Set fullscreen state +void +Frame::setStateFullscreen(StateAction sa) +{ + if (! ActionUtil::needToggle(sa, _fullscreen)) { + return; + } + + bool lock = _client->setConfigureRequestLock(true); + + if (_fullscreen) { + if ((_non_fullscreen_decor_state&DECOR_BORDER) != hasBorder()) { + setBorder(STATE_TOGGLE); + } + if ((_non_fullscreen_decor_state&DECOR_TITLEBAR) != hasTitlebar()) { + setTitlebar(STATE_TOGGLE); + } + _gm = _old_gm; + + } else { + _old_gm = _gm; + _non_fullscreen_decor_state = _client->getDecorState(); + _non_fullscreen_layer = _client->getLayer(); + + setBorder(STATE_UNSET); + setTitlebar(STATE_UNSET); + + Geometry head; + uint nr = getNearestHead(); + _scr->getHeadInfo(nr, head); + + _gm = head; + } + + _fullscreen = !_fullscreen; + _client->setFullscreen(_fullscreen); + + moveResize(_gm.x, _gm.y, _gm.width, _gm.height); + + // Re-stack window if fullscreen is above other windows. + if (Config::instance()->isFullscreenAbove()) { + setLayer(_fullscreen ? LAYER_ABOVE_DOCK : _non_fullscreen_layer); + raise(); + } + + _client->setConfigureRequestLock(lock); + _client->configureRequestSend(); + + _client->updateEwmhStates(); +} + +//! @brief +void +Frame::setStateSticky(StateAction sa) +{ + if (ActionUtil::needToggle(sa, _sticky)) { + stick(); + } +} + +void +Frame::setStateAlwaysOnTop(StateAction sa) +{ + if (! ActionUtil::needToggle(sa, getLayer() == LAYER_ONTOP)) { + return; + } + + _client->alwaysOnTop(getLayer() < LAYER_ONTOP); + setLayer(_client->getLayer()); + + raise(); +} + +void +Frame::setStateAlwaysBelow(StateAction sa) +{ + if (! ActionUtil::needToggle(sa, getLayer() == LAYER_BELOW)) { + return; + } + + _client->alwaysBelow(getLayer() > LAYER_BELOW); + setLayer(_client->getLayer()); + + lower(); +} + +//! @brief Hides/Shows the border depending on _client +//! @param sa State to set +void +Frame::setStateDecorBorder(StateAction sa) +{ + bool border = hasBorder(); + + setBorder(sa); + + // state changed, update client and atom state + if (border != hasBorder()) { + _client->setBorder(hasBorder()); + + // update the _PEKWM_FRAME_DECOR hint + AtomUtil::setLong(_client->getWindow(), + Atoms::getAtom(PEKWM_FRAME_DECOR), + _client->getDecorState()); + } +} + +//! @brief Hides/Shows the titlebar depending on _client +//! @param sa State to set +void +Frame::setStateDecorTitlebar(StateAction sa) +{ + bool titlebar = hasTitlebar(); + + setTitlebar(sa); + + // state changed, update client and atom state + if (titlebar != hasTitlebar()) { + _client->setTitlebar(hasTitlebar()); + + AtomUtil::setLong(_client->getWindow(), + Atoms::getAtom(PEKWM_FRAME_DECOR), + _client->getDecorState()); + } +} + +//! @brief +void +Frame::setStateIconified(StateAction sa) +{ + if (! ActionUtil::needToggle(sa, _iconified)) { + return; + } + + if (_iconified) { + mapWindow(); + } else { + iconify(); + } +} + +//! (Un)Sets the tagged Frame. +//! +//! @param sa Set/Unset or Toggle the state. +//! @param behind Should tagged actions be behind (non-focused). +void +Frame::setStateTagged(StateAction sa, bool behind) +{ + if (ActionUtil::needToggle(sa, (_tag_frame != 0))) { + _tag_frame = (this == _tag_frame) ? 0 : this; + _tag_behind = behind; + } +} + +//! @brief +void +Frame::setStateSkip(StateAction sa, uint skip) +{ + if (! ActionUtil::needToggle(sa, _skip&skip)) { + return; + } + + if (_skip&skip) { + _skip &= ~skip; + } else { + _skip |= skip; + } + + setSkip(_skip); +} + +//! @brief Sets client title +void +Frame::setStateTitle(StateAction sa, Client *client, const std::wstring &title) +{ + if (sa == STATE_SET) { + client->getTitle()->setUser(title); + + } else if (sa == STATE_UNSET) { + client->getTitle()->setUser(L""); + client->readName(); + } else { + if (client->getTitle()->isUserSet()) { + client->getTitle()->setUser(L""); + } else { + client->getTitle()->setUser(title); + } + } + + // Set PEKWM_TITLE atom to preserve title on client between sessions. + AtomUtil::setString(client->getWindow(), + Atoms::getAtom(PEKWM_TITLE), + Util::to_mb_str(client->getTitle()->getUser())); + + + renderTitle(); +} + +//! @brief Sets clients marked state. +//! @param sa +//! @param client +void +Frame::setStateMarked(StateAction sa, Client *client) +{ + if (! client || ! ActionUtil::needToggle(sa, client->isMarked())) { + return; + } + + // Set marked state and re-render title to update visual marker. + client->setStateMarked(sa); + renderTitle(); +} + +#ifdef OPACITY +void +Frame::setStateOpaque(StateAction sa) +{ + if (! ActionUtil::needToggle(sa, _opaque)) { + return; + } + _client->setOpaque(!_opaque); + setOpaque(!_opaque); +} +#endif // OPACITY +// STATE actions end + +//! @brief +void +Frame::getMaxBounds(int &max_x,int &max_r, int &max_y, int &max_b) +{ + int f_r, f_b; + int x, y, h, w, r, b; + + f_r = getRX(); + f_b = getBY(); + + list::iterator it = _frame_list.begin(); + for (; it != _frame_list.end(); ++it) { + if (! (*it)->isMapped()) { + continue; + } + + x = (*it)->getX(); + y = (*it)->getY(); + h = (*it)->getHeight(); + w = (*it)->getWidth(); + r = (*it)->getRX(); + b = (*it)->getBY(); + + // update max borders when other frame border lies between + // this border and prior max border (originally screen/head edge) + if ((r >= max_x) && (r <= _gm.x) && ! ((y >= f_b) || (b <= _gm.y))) { + max_x = r; + } + if ((x <= max_r) && (x >= f_r) && ! ((y >= f_b) || (b <= _gm.y))) { + max_r = x; + } + if ((b >= max_y) && (b <= _gm.y) && ! ((x >= f_r) || (r <= _gm.x))) { + max_y = b; + } + if ((y <= max_b) && (y >= f_b) && ! ((x >= f_r) || (r <= _gm.x))) { + max_b = y; + } + } +} + +//! @brief +void +Frame::growDirection(uint direction) +{ + Geometry head; + _scr->getHeadInfoWithEdge(getNearestHead(), head); + + switch (direction) { + case DIRECTION_UP: + _gm.height = getBY() - head.y; + _gm.y = head.y; + break; + case DIRECTION_DOWN: + _gm.height = head.y + head.height - _gm.y; + break; + case DIRECTION_LEFT: + _gm.width = getRX() - head.x; + _gm.x = head.x; + break; + case DIRECTION_RIGHT: + _gm.width = head.x + head.width - _gm.x; + break; + default: + break; + } + + downSize(_gm, (direction != DIRECTION_LEFT), (direction != DIRECTION_UP)); + + moveResize(_gm.x, _gm.y, _gm.width, _gm.height); +} + +//! @brief Closes the frame and all clients in it +void +Frame::close(void) +{ + list::iterator it(_child_list.begin()); + for (; it != _child_list.end(); ++it) { + static_cast(*it)->close(); + } +} + +//! @brief Reads autoprops for the active client. +//! @param type Defaults to APPLY_ON_RELOAD +void +Frame::readAutoprops(uint type) +{ + if ((type != APPLY_ON_RELOAD) && (type != APPLY_ON_WORKSPACE)) + return; + + _class_hint->title = _client->getTitle()->getReal(); + AutoProperty *data = + AutoProperties::instance()->findAutoProperty(_class_hint, _workspace, type); + _class_hint->title = L""; + + if (! data) { + return; + } + + // Set the correct group of the window + _class_hint->group = data->group_name; + + if (_class_hint == _client->getClassHint() + && (_client->isTransient() + && ! data->isApplyOn(APPLY_ON_TRANSIENT))) { + return; + } + + if (data->isMask(AP_STICKY) && _sticky != data->sticky) { + stick(); + } + if (data->isMask(AP_SHADED) && (isShaded() != data->shaded)) { + setShaded(STATE_UNSET); + } + if (data->isMask(AP_MAXIMIZED_HORIZONTAL) + && (_maximized_horz != data->maximized_horizontal)) { + setStateMaximized(STATE_TOGGLE, true, false, false); + } + if (data->isMask(AP_MAXIMIZED_VERTICAL) + && (_maximized_vert != data->maximized_vertical)) { + setStateMaximized(STATE_TOGGLE, false, true, false); + } + if (data->isMask(AP_FULLSCREEN) && (_fullscreen != data->fullscreen)) { + setStateFullscreen(STATE_TOGGLE); + } + + if (data->isMask(AP_ICONIFIED) && (_iconified != data->iconified)) { + if (_iconified) { + mapWindow(); + } else { + iconify(); + } + } + if (data->isMask(AP_WORKSPACE)) { + // In order to avoid eternal recursion, the workspace is only set here + // and then actually called PDecor::setWorkspace from Frame::setWorkspace + if (type == APPLY_ON_WORKSPACE) { + _workspace = data->workspace; + } else if (_workspace != data->workspace) { + // Call PDecor directly to avoid recursion. + PDecor::setWorkspace(data->workspace); + } + } + + if (data->isMask(AP_SHADED) && (isShaded() != data->shaded)) { + setShaded(STATE_TOGGLE); + } + if (data->isMask(AP_LAYER) && (data->layer <= LAYER_MENU)) { + _client->setLayer(data->layer); + raise(); // restack the frame + } + + if (data->isMask(AP_FRAME_GEOMETRY|AP_CLIENT_GEOMETRY)) { + setupAPGeometry(_client, data); + + // Apply changes + moveResize(_gm.x, _gm.y, _gm.width, _gm.height); + } + + if (data->isMask(AP_BORDER) && (hasBorder() != data->border)) { + setStateDecorBorder(STATE_TOGGLE); + } + if (data->isMask(AP_TITLEBAR) && (hasTitlebar() != data->titlebar)) { + setStateDecorTitlebar(STATE_TOGGLE); + } + + if (data->isMask(AP_SKIP)) { + _client->setSkip(data->skip); + setSkip(_client->getSkip()); + } + + if (data->isMask(AP_FOCUSABLE)) { + _client->setFocusable(data->focusable); + } +} + +//! @brief Figure out how large the frame is in cells. +void +Frame::calcSizeInCells(uint &width, uint &height) +{ + const XSizeHints *hints = _client->getXSizeHints(); + + if (hints->flags&PResizeInc) { + width = (getChildWidth() - hints->base_width) / hints->width_inc; + height = (getChildHeight() - hints->base_height) / hints->height_inc; + } else { + width = _gm.width; + height = _gm.height; + } +} + +//! @brief Calculates position based on current gravity +void +Frame::calcGravityPosition(int gravity, int x, int y, int &g_x, int &g_y) +{ + switch (gravity) { + case NorthEastGravity: // outside border corner + g_x = x - _gm.x; + g_y = y; + break; + case SouthWestGravity: // outside border corner + g_x = x; + g_y = y - _gm.y; + break; + case SouthEastGravity: // outside border corner + g_x = x - _gm.x; + g_y = y - _gm.y; + break; + + case NorthGravity: // outside border center + g_x = x - (_gm.x / 2); + g_y = y; + break; + case SouthGravity: // outside border center + g_x = x - (_gm.x / 2); + g_y = y - _gm.y; + break; + case WestGravity: // outside border center + g_x = x; + g_y = y - (_gm.y / 2); + break; + case EastGravity: // outside border center + g_x = x - _gm.x; + g_y = y - (_gm.y / 2); + break; + + case CenterGravity: // center of window + g_x = x - (_gm.x / 2); + g_y = y - (_gm.y / 2); + break; + case StaticGravity: // client top left + g_x = x - (_gm.width - getChildWidth()); + g_y = y - (_gm.height - getChildHeight()); + break; + case NorthWestGravity: // outside border corner + default: + g_x = x; + g_y = y; + break; + } +} + +/** + * Makes gm conform to _clients width and height inc. + */ +void +Frame::downSize(Geometry &gm, bool keep_x, bool keep_y) +{ + XSizeHints *size_hint = _client->getXSizeHints(); // convenience + + // conform to width_inc + if (size_hint->flags&PResizeInc) { + int o_r = getRX(); + int b_x = (size_hint->flags&PBaseSize) + ? size_hint->base_width + : (size_hint->flags&PMinSize) ? size_hint->min_width : 0; + + gm.width -= (getChildWidth() - b_x) % size_hint->width_inc; + if (! keep_x) { + gm.x = o_r - gm.width; + } + } + + // conform to height_inc + if (size_hint->flags&PResizeInc) { + int o_b = getBY(); + int b_y = (size_hint->flags&PBaseSize) + ? size_hint->base_height + : (size_hint->flags&PMinSize) ? size_hint->min_height : 0; + + gm.height -= (getChildHeight() - b_y) % size_hint->height_inc; + if (! keep_y) { + gm.y = o_b - gm.height; + } + } +} + +// Below this Client message handling is done + +//! @brief Handle XConfgiureRequestEvents +//! @todo Should we send a ConfigureRequest back to the Client if ignoring? +void +Frame::handleConfigureRequest(XConfigureRequestEvent *ev, Client *client) +{ + if (client != _client) { + return; // only handle the active client's events + } + + // Handled geometry, this is handled seperatley due to fullscreen + // detection + handleConfigureRequestGeometry(ev, client); + + // update the stacking + if (! client->isCfgDeny(CFG_DENY_STACKING)) { + if (ev->value_mask&CWStackMode) { + if (ev->value_mask&CWSibling) { + switch(ev->detail) { + case Above: + Workspaces::instance()->stackAbove(this, ev->above); + break; + case Below: + Workspaces::instance()->stackBelow(this, ev->above); + break; + case TopIf: + case BottomIf: + // FIXME: What does occlude mean? + break; + } + } else { + switch(ev->detail) { // FIXME: Is this broken? + case Above: + raise(); + break; + case Below: + lower(); + break; + case TopIf: + case BottomIf: + // FIXME: Why does the manual say that it should care about siblings + // even if we don't have any specified? + break; + } + } + } + } +} + +/** + * Handle size and position part of configure request, detects + * fullscreen mode if detection is enabled. + */ +void +Frame::handleConfigureRequestGeometry(XConfigureRequestEvent *ev, Client *client) +{ + if (isRequestGeometryFullscreen(ev, client)) { + client->configureRequestSend(); + return; + } + + bool change_geometry = false; + if (! client->isCfgDeny(CFG_DENY_SIZE) + && (ev->value_mask & (CWWidth|CWHeight))) { + resizeChild(ev->width, ev->height); + _client->setShaped(setShape()); + change_geometry = true; + } + + if (! client->isCfgDeny(CFG_DENY_POSITION) + && (ev->value_mask & (CWX|CWY)) ) { + calcGravityPosition(_client->getXSizeHints()->win_gravity, + ev->x, ev->y, _gm.x, _gm.y); + move(_gm.x, _gm.y); + change_geometry = true; + } + + // Remove fullscreen state if client changes it size + if (change_geometry && Config::instance()->isFullscreenDetect()) { + setStateFullscreen(STATE_UNSET); + } +} + +/** + * Check if requested size if "fullscreen" + */ +bool +Frame::isRequestGeometryFullscreen(XConfigureRequestEvent *ev, Client *client) +{ + bool is_fullscreen = false; + if (! client->isCfgDeny(CFG_DENY_SIZE) + && ! client->isCfgDeny(CFG_DENY_POSITION)) { + int nearest_head = _scr->getNearestHead(ev->x, ev->y); + Geometry gm_request(ev->x, ev->y, ev->width, ev->height); + Geometry gm_screen(_scr->getScreenGeometry()); + Geometry gm_head(_scr->getHeadGeometry(nearest_head)); + + if (gm_request == gm_screen || gm_request == gm_head) { + is_fullscreen = true; + } else { + downSize(gm_screen, true, true); + downSize(gm_head, true, true); + if (gm_request == gm_screen || gm_request == gm_head) { + is_fullscreen = true; + } + } + + if (is_fullscreen && Config::instance()->isFullscreenDetect()) { + setStateFullscreen(STATE_SET); + } + } + return is_fullscreen; +} + +/** + * Handle client message. + */ +void +Frame::handleClientMessage(XClientMessageEvent *ev, Client *client) +{ + if (ev->message_type == Atoms::getAtom(STATE)) { + handleClientStateMessage(ev, client); + } else if (ev->message_type == Atoms::getAtom(NET_ACTIVE_WINDOW)) { + if (! client->isCfgDeny(CFG_DENY_ACTIVE_WINDOW)) { + // Active child if it's not the active child + if (client != _client) { + activateChild(client); + } + + // If we aren't mapped we check if we make sure we're on the right + // workspace and then map the window. + if (! _mapped) { + if (_workspace != Workspaces::instance()->getActive() && + !isSticky()) { + Workspaces::instance()->setWorkspace(_workspace, false); + } + mapWindow(); + } + // Seems as if raising the window is implied in activating it + raise(); + giveInputFocus(); + } + } else if (ev->message_type == Atoms::getAtom(NET_CLOSE_WINDOW)) { + client->close(); + } else if (ev->message_type == Atoms::getAtom(NET_WM_DESKTOP)) { + if (client == _client) { + setWorkspace(ev->data.l[0]); + } + } else if (ev->message_type == Atoms::getAtom(WM_CHANGE_STATE) && + (ev->format == 32) && (ev->data.l[0] == IconicState)) { + if (client == _client) { + iconify(); + } + } +} + +/** + * Handle _NET_WM_STATE atom. + */ +void +Frame::handleClientStateMessage(XClientMessageEvent *ev, Client *client) +{ + StateAction sa = getStateActionFromMessage(ev); + handleStateAtom(sa, ev->data.l[1], client); + if (ev->data.l[2] != 0) { + handleStateAtom(sa, ev->data.l[2], client); + } + client->updateEwmhStates(); +} + +/** + * Get StateAction from NET_WM atom. + */ +StateAction +Frame::getStateActionFromMessage(XClientMessageEvent *ev) +{ + StateAction sa = STATE_SET; + if (ev->data.l[0]== NET_WM_STATE_REMOVE) { + sa = STATE_UNSET; + } else if (ev->data.l[0]== NET_WM_STATE_ADD) { + sa = STATE_SET; + } else if (ev->data.l[0]== NET_WM_STATE_TOGGLE) { + sa = STATE_TOGGLE; + } + return sa; +} + +/** + * Handle state atom for client. + */ +void +Frame::handleStateAtom(StateAction sa, Atom atom, Client *client) +{ + if (client == _client) { + handleCurrentClientStateAtom(sa, atom, client); + } + + switch (atom) { + case STATE_SKIP_TASKBAR: + client->setStateSkip(sa, SKIP_TASKBAR); + break; + case STATE_SKIP_PAGER: + client->setStateSkip(sa, SKIP_PAGER); + break; + case STATE_DEMANDS_ATTENTION: + client->setStateDemandsAttention(sa, true); + break; + } +} + +/** + * Handle state atom for actions that apply only on active client. + */ +void +Frame::handleCurrentClientStateAtom(StateAction sa, Atom atom, Client *client) +{ + if (atom == Atoms::getAtom(STATE_STICKY)) { + setStateSticky(sa); + } + if (atom == Atoms::getAtom(STATE_MAXIMIZED_HORZ) + && ! client->isCfgDeny(CFG_DENY_STATE_MAXIMIZED_HORZ)) { + setStateMaximized(sa, true, false, false); + } + if (atom == Atoms::getAtom(STATE_MAXIMIZED_VERT) + && ! client->isCfgDeny(CFG_DENY_STATE_MAXIMIZED_VERT)) { + setStateMaximized(sa, false, true, false); + } + if (atom == Atoms::getAtom(STATE_SHADED)) { + setShaded(sa); + } + if (atom == Atoms::getAtom(STATE_HIDDEN) + && ! client->isCfgDeny(CFG_DENY_STATE_HIDDEN)) { + setStateIconified(sa); + } + if (atom == Atoms::getAtom(STATE_FULLSCREEN) + && ! client->isCfgDeny(CFG_DENY_STATE_FULLSCREEN)) { + setStateFullscreen(sa); + } + if (atom == Atoms::getAtom(STATE_ABOVE) + && ! client->isCfgDeny(CFG_DENY_STATE_ABOVE)) { + setStateAlwaysOnTop(sa); + } + if (atom == Atoms::getAtom(STATE_BELOW) + && ! client->isCfgDeny(CFG_DENY_STATE_BELOW)) { + setStateAlwaysBelow(sa); + } +} + +//! @brief +void +Frame::handlePropertyChange(XPropertyEvent *ev, Client *client) +{ + if (ev->atom == Atoms::getAtom(NET_WM_DESKTOP)) { + if (client == _client) { + long workspace; + + if (AtomUtil::getLong(client->getWindow(), + Atoms::getAtom(NET_WM_DESKTOP), workspace)) { + if (workspace != signed(_workspace)) + setWorkspace(workspace); + } + } + } else if (ev->atom == Atoms::getAtom(NET_WM_STRUT)) { + client->getStrutHint(); + } else if (ev->atom == Atoms::getAtom(NET_WM_NAME) || ev->atom == XA_WM_NAME) { + handleTitleChange(client); + } else if (ev->atom == XA_WM_NORMAL_HINTS) { + client->getWMNormalHints(); + } else if (ev->atom == XA_WM_TRANSIENT_FOR) { + client->getTransientForHint(); + } +} + +/** + * Handle title change, find decoration rules based on changed title + * and update if changed. + */ +void +Frame::handleTitleChange(Client *client) +{ + // Update title + client->readName(); + + bool require_render = true; + if (client == _client) { + string new_decor_name(getClientDecorName(client)); + if (new_decor_name != _decor_name) { + require_render = ! setDecor(new_decor_name); + } + } + + // Render title if decoration was not updated + if (require_render) { + renderTitle(); + } +} diff --git a/pekwm/Frame.hh b/pekwm/Frame.hh new file mode 100644 index 0000000..4ed54e7 --- /dev/null +++ b/pekwm/Frame.hh @@ -0,0 +1,208 @@ +// +// Frame.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _FRAME_HH_ +#define _FRAME_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" +#include "Action.hh" +#include "PDecor.hh" + +class PScreen; +class PWinObj; +class Strut; +class Theme; +class ClassHint; +class AutoProperty; + +class Client; + +#include +#include +#include + +class Frame : public PDecor +{ +public: + Frame(Client *client, AutoProperty *ap); + virtual ~Frame(void); + + // START - PWinObj interface. + virtual void iconify(void); + virtual void stick(void); + + virtual void raise(void); + virtual void setWorkspace(unsigned int workspace); + virtual void setLayer(unsigned int layer); + + virtual ActionEvent *handleMotionEvent(XMotionEvent *ev); + virtual ActionEvent *handleEnterEvent(XCrossingEvent *ev); + virtual ActionEvent *handleLeaveEvent(XCrossingEvent *ev); + + virtual ActionEvent *handleMapRequest(XMapRequestEvent *ev); + virtual ActionEvent *handleUnmapEvent(XUnmapEvent *ev); + // END - PWinObj interface. + + virtual void handleShapeEvent(XAnyEvent *ev); + + // START - PDecor interface. + virtual bool allowMove(void) const; + + virtual void addChild(PWinObj *child, std::list::iterator *it = 0); + virtual void removeChild(PWinObj *child, bool do_delete = true); + virtual void activateChild(PWinObj *child); + + virtual void updatedChildOrder(void); + virtual void updatedActiveChild(void); + + virtual void getDecorInfo(wchar_t *buf, uint size); + + virtual void setShaded(StateAction sa); + virtual void setSkip(uint skip); + // END - PDecor interface. + + Client *getActiveClient(void); + + void addChildOrdered(Client *child); + + static Frame *findFrameFromWindow(Window win); + static Frame *findFrameFromID(uint id); + + // START - Iterators + static uint frame_size(void) { return _frame_list.size(); } + static std::list::iterator frame_begin(void) { + return _frame_list.begin(); + } + static std::list::iterator frame_end(void) { + return _frame_list.end(); + } + static std::list::reverse_iterator frame_rbegin(void) { + return _frame_list.rbegin(); + } + static std::list::reverse_iterator frame_rend(void) { + return _frame_list.rend(); + } + // END - Iterator + + inline uint getId(void) const { return _id; } + void setId(uint id); + + void detachClient(Client *client); + + inline const ClassHint* getClassHint(void) const { return _class_hint; } + + void growDirection(uint direction); + void moveToEdge(OrientationType ori); + + void updateInactiveChildInfo(void); + + // state actions + void setStateMaximized(StateAction sa, bool horz, bool vert, bool fill); + void setStateFullscreen(StateAction sa); + void setStateSticky(StateAction sa); + void setStateAlwaysOnTop(StateAction sa); + void setStateAlwaysBelow(StateAction sa); + void setStateDecorBorder(StateAction sa); + void setStateDecorTitlebar(StateAction sa); + void setStateIconified(StateAction sa); + void setStateTagged(StateAction sa, bool behind); + void setStateSkip(StateAction sa, uint skip); + void setStateTitle(StateAction sa, Client *client, const std::wstring &title); + void setStateMarked(StateAction sa, Client *client); +#ifdef OPACITY + void setStateOpaque(StateAction sa); +#endif // OPACITY + + void close(void); + + void readAutoprops(uint type = APPLY_ON_RELOAD); + + void doResize(XMotionEvent *ev); // redirects to doResize(bool... + void doResize(BorderPosition pos); // redirect to doResize(bool... + void doResize(bool left, bool x, bool top, bool y); + void doGroupingDrag(XMotionEvent *ev, Client *client, bool behind); + + bool fixGeometry(void); + + // client message handling + void handleConfigureRequest(XConfigureRequestEvent *ev, Client *client); + void handleClientMessage(XClientMessageEvent *ev, Client *client); + void handlePropertyChange(XPropertyEvent *ev, Client *client); + + static Frame *getTagFrame(void) { return _tag_frame; } + static bool getTagBehind(void) { return _tag_behind; } + + static void resetFrameIDs(void); + +protected: + // BEGIN - PDecor interface + virtual int resizeHorzStep(int diff) const; + virtual int resizeVertStep(int diff) const; + // END - PDecor interface + +private: + void handleClientStateMessage(XClientMessageEvent *ev, Client *client); + static StateAction getStateActionFromMessage(XClientMessageEvent *ev); + void handleStateAtom(StateAction sa, Atom atom, Client *client); + void handleCurrentClientStateAtom(StateAction sa, Atom atom, Client *client); + void handleConfigureRequestGeometry(XConfigureRequestEvent *ev, Client *client); + bool isRequestGeometryFullscreen(XConfigureRequestEvent *ev, Client *client); + + void recalcResizeDrag(int nx, int ny, bool left, bool top); + void getMaxBounds(int &max_x,int &max_r, int &max_y, int &max_b); + void calcSizeInCells(uint &width, uint &height); + void calcGravityPosition(int gravity, int x, int y, int &g_x, int &g_y); + void downSize(Geometry &gm, bool keep_x, bool keep_y); + + void handleTitleChange(Client *client); + + void getState(Client *cl); + void applyState(Client *cl); + + void setupAPGeometry(Client *client, AutoProperty *ap); + void applyAPGeometry(Geometry &gm, const Geometry &ap_gm, int mask); + + void setActiveTitle(void); + + static uint findFrameID(void); + static void returnFrameID(uint id); + + static std::string getClientDecorName(Client *client); + +private: + PScreen *_scr; + + uint _id; // unique id of the frame + + Client *_client; // to skip all the casts from PWinObj + ClassHint *_class_hint; + + // frame information used when maximizing / going fullscreen + Geometry _old_gm; // FIXME: move to PDecor? + uint _non_fullscreen_decor_state; // FIXME: move to PDecor? + uint _non_fullscreen_layer; + + // ID list, list of free Frame ids. + static std::list _frame_list; //!< List of all Frames. + static std::vector _frameid_list; //!< List of free Frame IDs. + + // Tagging, static as only one Frame can be tagged + static Frame *_tag_frame; //!< Pointer to tagged frame. + static bool _tag_behind; //!< Tagging actions will set behind. + + // EWMH + static const int NET_WM_STATE_REMOVE = 0; // remove/unset property + static const int NET_WM_STATE_ADD = 1; // add/set property + static const int NET_WM_STATE_TOGGLE = 2; // toggle property +}; + +#endif // _FRAME_HH_ diff --git a/pekwm/FrameListMenu.cc b/pekwm/FrameListMenu.cc new file mode 100644 index 0000000..f7b48a6 --- /dev/null +++ b/pekwm/FrameListMenu.cc @@ -0,0 +1,296 @@ +// +// FrameListMenu.cc for pekwm +// Copyright © 2002-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include +#include + +#include "Compat.hh" +#include "PWinObj.hh" +#include "PDecor.hh" +#include "PMenu.hh" +#include "WORefMenu.hh" +#include "FrameListMenu.hh" + +#include "Config.hh" +#include "Client.hh" +#include "Frame.hh" +#include "Workspaces.hh" +#include "WindowManager.hh" + +using std::cerr; +using std::endl; +using std::list; +using std::string; +using std::vector; +using std::wstring; +using std::swprintf; + +//! @brief FrameListMenu constructor. +//! @param scr Pointer to PScreen. +//! @param theme Pointer to Theme +//! @param type Type of menu. +//! @param title Title of menu. +//! @param name Name of menu +//! @param decor_name Decor name, defaults to MENU +FrameListMenu::FrameListMenu(PScreen *scr, Theme *theme, + MenuType type, + const std::wstring &title, const std::string &name, + const std::string &decor_name) + : WORefMenu(scr, theme, title, name, decor_name) +{ + _menu_type = type; +} + +//! @brief FrameListMenu destructor +FrameListMenu::~FrameListMenu(void) +{ +} + +// START - PWinObj interface. + +//! @brief Rebuilds the menu and if it has any items after it shows it. +void +FrameListMenu::mapWindow(void) +{ + updateFrameListMenu(); + if (size() > 0) { + WORefMenu::mapWindow(); + } +} + +// END - PWinObj interface. + +/** + * Execute item execution. + */ +void +FrameListMenu::handleItemExec(PMenu::Item *item) +{ + if (! item) { + return; + } + + Client *item_client = dynamic_cast(item->getWORef()); + Client *wo_ref_client = dynamic_cast(getWORef()); + + switch (_menu_type) { + case GOTOMENU_TYPE: + case GOTOCLIENTMENU_TYPE: + handleGotomenu(item_client); + break; + case ICONMENU_TYPE: + handleIconmenu(item_client); + break; + case ATTACH_CLIENT_TYPE: + case ATTACH_FRAME_TYPE: + handleAttach(wo_ref_client, item_client, + (_menu_type == ATTACH_FRAME_TYPE)); + break; + case ATTACH_CLIENT_IN_FRAME_TYPE: + case ATTACH_FRAME_IN_FRAME_TYPE: + handleAttach(item_client, wo_ref_client, + (_menu_type == ATTACH_FRAME_IN_FRAME_TYPE)); + break; + default: + // do nothing + break; + } +} + +//! @brief Rebuilds the menu. +void +FrameListMenu::updateFrameListMenu(void) +{ + removeAll(); + + wchar_t buf[16]; + wstring name; + + // need to add an action, otherwise it looks as if we don't have anything + // to exec and thus it doesn't get handled. + Action action; + ActionEvent ae; + ae.action_list.push_back(action); + + // Decide wheter to show clients and iconified. + bool show_clients = false, show_iconified_only = false; + if (_menu_type == ATTACH_CLIENT_TYPE || _menu_type == GOTOCLIENTMENU_TYPE) { + show_clients = true; + } else if (_menu_type == ICONMENU_TYPE) { + show_iconified_only = true; + } + + list::const_iterator it; + + // if we have 1 workspace, we won't put an workspace indicator + buf[0] = '\0'; + + for (uint i = 0; i < Workspaces::instance()->size(); ++i) { + if (Workspaces::instance()->size() > 1) { + swprintf(buf, 16, L"<%d> ", i + 1); + } + + for (it = Frame::frame_begin(); it != Frame::frame_end(); ++it) { + if (((*it)->getWorkspace() == i) && // sort by workspace + // don't include ourselves if we're not doing a gotoclient menu + ((_menu_type != GOTOCLIENTMENU_TYPE) + ? ((*it)->getActiveChild() != getWORef()) + : true) && + (show_iconified_only + ? (*it)->isIconified() + : !(*it)->isSkip(SKIP_MENUS))) { + name = buf; + + if (show_clients) { + buildFrameNames(*it, name); + + } else { + buildName(*it, name); + name.append(L"] "); + name.append(static_cast((*it)->getActiveChild())->getTitle()->getVisible()); + + insert(name, ae, (*it)->getActiveChild(), + static_cast((*it)->getActiveChild())->getIcon()); + } + } + } + } + + // remove the last separator, not needed + if (show_clients && (size() > 0)) { + remove(_item_list.back()); + } + + buildMenu(); +} + +//! @brief Builds the name for the frame. +void +FrameListMenu::buildName(Frame* frame, std::wstring &name) +{ + name.append(L"["); + if (frame->isSticky()) { + name.append(L"*"); + } + if (frame->isIconified()) { + name.append(L"."); + } + if (frame->isShaded()) { + name.append(L"^"); + } + if (frame->getActiveChild()->getLayer() > LAYER_NORMAL) { + name.append(L"+"); + } else if (frame->getActiveChild()->getLayer() < LAYER_NORMAL) { + name.append(L"-"); + } +} + +//! @brief Builds names for all the clients in a frame. +void +FrameListMenu::buildFrameNames(Frame *frame, std::wstring &pre_name) +{ + wstring name, status_name; + + // need to add an action, otherwise it looks as if we don't have anything + // to exec and thus it doesn't get handled. + Action action; + ActionEvent ae; + ae.action_list.push_back(action); + + buildName(frame, status_name); // add states to the name + + list::iterator it(frame->begin()); + for (; it != frame->end(); ++it) { + name = pre_name; + name.append(status_name); + if (frame->getActiveChild() == *it) { + name.append(L"A"); + } + name.append(L"] "); + name.append(static_cast(*it)->getTitle()->getVisible()); + + insert(name, ae, *it, static_cast(*it)->getIcon()); + } + + // add separator + PMenu::Item *item = new PMenu::Item(L""); + item->setType(PMenu::Item::MENU_ITEM_SEPARATOR); + insert(item); +} + +//! @brief Handles gotomeu presses +void +FrameListMenu::handleGotomenu(Client *client) +{ + if (! client) { + return; + } + Frame *frame = static_cast(client->getParent()); + + // make sure it's on correct workspace + if (! frame->isSticky() && + (frame->getWorkspace() != Workspaces::instance()->getActive())) { + Workspaces::instance()->setWorkspace(frame->getWorkspace(), false); + } + // make sure it isn't hidden + if (! frame->isMapped()) { + frame->mapWindow(); + } + + frame->activateChild(client); + frame->raise(); + frame->giveInputFocus(); +} + +//! @brief Handles iconmenu presses +void +FrameListMenu::handleIconmenu(Client *client) +{ + if (! client) { + return; + } + Frame *frame = static_cast(client->getParent()); + + // make sure it's on the current workspace + if (frame->getWorkspace() != Workspaces::instance()->getActive()) { + frame->setWorkspace(Workspaces::instance()->getActive()); + } + + frame->raise(); + frame->mapWindow(); +} + +//! @brief Handles attach*menu presses +void +FrameListMenu::handleAttach(Client *client_to, Client *client_from, bool frame) +{ + if (! client_to || ! client_from) { + return; + } + + Frame *frame_to = static_cast(client_to->getParent()); + Frame *frame_from = static_cast(client_from->getParent()); + + // insert frame + if (frame) { + frame_to->addDecor(frame_from); + // insert client + } else if (frame_to != frame_from) { + frame_from->removeChild(client_from); + client_from->setWorkspace(frame_to->getWorkspace()); + frame_to->addChild(client_from); + frame_to->activateChild(client_from); + frame_to->giveInputFocus(); + } +} diff --git a/pekwm/FrameListMenu.hh b/pekwm/FrameListMenu.hh new file mode 100644 index 0000000..b878836 --- /dev/null +++ b/pekwm/FrameListMenu.hh @@ -0,0 +1,56 @@ +// +// FrameListMenu.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _FRAMELISTMENU_HH_ +#define _FRAMELISTMENU_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" +#include "PMenu.hh" + +#include +#include + +class WORefMenu; +class PScreen; +class Theme; +class Frame; +class Client; + +class FrameListMenu : public WORefMenu +{ +public: + FrameListMenu(PScreen *scr, Theme *theme, + MenuType type, + const std::wstring &title, const std::string &name, + const std::string &decor_name = "MENU"); + virtual ~FrameListMenu(void); + + // START - PWinObj interface. + virtual void mapWindow(void); + // END - PWinObj interface. + + virtual void handleItemExec(PMenu::Item *item); + +private: + void updateFrameListMenu(void); + +private: + void buildName(Frame *frame, std::wstring &name); + void buildFrameNames(Frame *frame, std::wstring &pre_name); + + void handleGotomenu(Client *client); + void handleIconmenu(Client *client); + void handleAttach(Client *client_to, Client *client_from, bool frame); +}; + +#endif // _FRAMELISTMENU_HH_ + diff --git a/pekwm/Handler.hh b/pekwm/Handler.hh new file mode 100644 index 0000000..3c319a6 --- /dev/null +++ b/pekwm/Handler.hh @@ -0,0 +1,43 @@ +// +// Handler.hh for pekwm +// Copyright (C) 2004-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "../config.h" + +#ifndef _HANDLER_HH_ +#define _HANDLER_HH_ + +#include + +template +class HandlerEntry { +public: + HandlerEntry(const std::string &name) : _name(name), _ref(0) { } + virtual ~HandlerEntry(void) { } + + const std::string &getName(void) { return _name; } + + inline T getData(void) { return _data; } + inline void setData(T data) { _data = data; } + + inline uint getRef(void) const { return _ref; } + inline void incRef(void) { _ref++; } + inline void decRef(void) { if (_ref > 0) { _ref--; } + } + + inline bool operator==(const std::string &name) { + return (strcasecmp(_name.c_str(), name.c_str()) == 0); + } + +private: + std::string _name; // id + uint _ref; // ref count + + T _data; +}; + +#endif // _HANDLER_HH_ diff --git a/pekwm/Harbour.cc b/pekwm/Harbour.cc new file mode 100644 index 0000000..d082b83 --- /dev/null +++ b/pekwm/Harbour.cc @@ -0,0 +1,630 @@ +// +// Harbour.cc for pekwm +// Copyright © 2003-2009 Claes Nästen +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Harbour.hh" + +#include "PScreen.hh" +#include "Config.hh" +#include "PWinObj.hh" +#include "DockApp.hh" +#include "Workspaces.hh" +#include "AutoProperties.hh" +#include "PDecor.hh" +#include "PMenu.hh" +#include "HarbourMenu.hh" + +#include +#include + +using std::list; +using std::mem_fun; +using std::find; +#ifdef DEBUG +#include +using std::cerr; +using std::endl; +#endif // DEBUG + +//! @brief Harbour constructor +Harbour::Harbour(PScreen *s, Theme *t, Workspaces *w) : + _scr(s), _theme(t), _workspaces(w), + _harbour_menu(0), + _hidden(false), _size(0), _strut(0), + _last_button_x(0), _last_button_y(0) +{ + _strut = new Strut(); + _scr->addStrut(_strut); + _strut->head = Config::instance()->getHarbourHead(); + _harbour_menu = new HarbourMenu(_scr, _theme, this); +} + +//! @brief Harbour destructor +Harbour::~Harbour(void) +{ + removeAllDockApps(); + + _scr->removeStrut(_strut); + delete _strut; + delete _harbour_menu; +} + +//! @brief Adds a DockApp to the Harbour +//! @todo Add sort option +void +Harbour::addDockApp(DockApp *da) +{ + if (! da) { + return; + } + + // add to the list + if (AutoProperties::instance()->isHarbourSort()) { + insertDockAppSorted(da); + placeDockAppsSorted(); // place in sorted way + } else { + _da_list.push_back(da); + placeDockApp(da); // place it in a empty space + } + + da->setLayer(Config::instance()->isHarbourOntop() ? LAYER_DOCK : LAYER_DESKTOP); + _workspaces->insert(da); // add the dockapp to the stacking list + + if (! da->isMapped()) { // make sure it's visible + da->mapWindow(); + } + +#ifdef OPACITY + da->setOpacity(Config::instance()->getHarbourOpacity()); +#endif // OPACITY + updateHarbourSize(); +} + +//! @brief Removes a DockApp from the Harbour +void +Harbour::removeDockApp(DockApp *da) +{ + if (! da) + return; + + list::iterator it(find(_da_list.begin(), _da_list.end(), da)); + + if (it != _da_list.end()) { + _da_list.remove(da); + _workspaces->remove(da); // remove the dockapp to the stacking list + delete da; + + if (AutoProperties::instance()->isHarbourSort()) { + placeDockAppsSorted(); + } + } + + updateHarbourSize(); +} + +//! @brief Removes all DockApps from the Harbour +void +Harbour::removeAllDockApps(void) +{ + list::iterator it(_da_list.begin()); + for (; it != _da_list.end(); ++it) { + _workspaces->remove(*it); // remove the dockapp to the stacking list + delete (*it); + } + _da_list.clear(); +} + +//! @brief Tries to find a dockapp which uses the window win +DockApp* +Harbour::findDockApp(Window win) +{ + DockApp *dockapp = 0; + + list::iterator it(_da_list.begin()); + for (; it != _da_list.end(); ++it) { + if ((*it)->findDockApp(win)) { + dockapp = (*it); + break; + } + } + + return dockapp; +} + +//! @brief Tries to find a dockapp which has the window win as frame. +DockApp* +Harbour::findDockAppFromFrame(Window win) +{ + DockApp *dockapp = 0; + + list::iterator it(_da_list.begin()); + for (; it != _da_list.end(); ++it) { + if ((*it)->findDockAppFromFrame(win)) { + dockapp = (*it); + break; + } + } + + return dockapp; +} + +#ifdef HAVE_XRANDR +//! @brief Refetches the root-window size and takes appropriate actions. +void +Harbour::updateGeometry(void) +{ + list::iterator it(_da_list.begin()); + for (; it != _da_list.end(); ++it) { + placeDockAppInsideScreen(*it); + } +} +#endif // HAVE_XRANDR + +//! @brief Lowers or Raises all the DockApps in the harbour. +void +Harbour::restack(void) +{ + PScreen::instance()->removeStrut(_strut); + if (Config::instance()->isHarbourOntop() || + ! Config::instance()->isHarbourMaximizeOver()) { + + PScreen::instance()->addStrut(_strut); + } + uint l = Config::instance()->isHarbourOntop() ? LAYER_DOCK : LAYER_DESKTOP; + + list::iterator it(_da_list.begin()); + for (; it != _da_list.end(); ++it) { + (*it)->setLayer(l); + + if (Config::instance()->isHarbourOntop()) { + _workspaces->raise(*it); + } else { + _workspaces->lower(*it); + } + } +} + +//! @brief Goes through the DockApp list and places the dockapp. +void +Harbour::rearrange(void) +{ + _strut->head = Config::instance()->getHarbourHead(); + + if (AutoProperties::instance()->isHarbourSort ()) + placeDockAppsSorted(); + else + { + list::iterator it (_da_list.begin ()); + for (; it != _da_list.end (); ++it) + placeDockApp (*it); + } +} + +//! @brief Repaints all dockapps with the new theme +void +Harbour::loadTheme(void) +{ + for_each(_da_list.begin(), _da_list.end(), mem_fun(&DockApp::loadTheme)); +} + +//! @brief Updates the harbour max size variable. +void +Harbour::updateHarbourSize(void) +{ + _size = 0; + + list::iterator it(_da_list.begin()); + for (; it != _da_list.end(); ++it) + { + switch (Config::instance()->getHarbourPlacement()) + { + case TOP: + case BOTTOM: + if ((*it)->getHeight() > _size) + _size = (*it)->getHeight(); + break; + case LEFT: + case RIGHT: + if ((*it)->getWidth() > _size) + _size = (*it)->getWidth(); + break; + default: + // Do nothing. + break; + } + } + + updateStrutSize(); +} + +//! @brief Sets the Hidden state of the harbour +//! @param sa StateAction specifying state to set. +void +Harbour::setStateHidden(StateAction sa) +{ + // Check if there is anything to do + if (! ActionUtil::needToggle(sa, _hidden)) { + return; + } + + if (_hidden) { + // Show if currently hidden. + for_each(_da_list.begin(), _da_list.end(), mem_fun(&DockApp::mapWindow)); + + } else { + // Hide if currently visible. + for_each(_da_list.begin(), _da_list.end(), mem_fun(&DockApp::unmapWindow)); + } + + _hidden = !_hidden; +} + +//! @brief Updates Harbour strut size. +void +Harbour::updateStrutSize(void) +{ + _strut->left = _strut->right = _strut->top = _strut->bottom = 0; + + if (! Config::instance()->isHarbourMaximizeOver()) { + switch (Config::instance()->getHarbourPlacement()) { + case TOP: + _strut->top = _size; + break; + case BOTTOM: + _strut->bottom = _size; + break; + case LEFT: + _strut->left = _size; + break; + case RIGHT: + _strut->right = _size; + break; + } + } + + _scr->updateStrut(); +} + +//! @brief Handles XButtonEvents made on the DockApp's frames. +void +Harbour::handleButtonEvent(XButtonEvent* ev, DockApp* da) +{ + if (! da) { + return; + } + + _last_button_x = ev->x; + _last_button_y = ev->y; + + // FIXME: Make configurable + if (ev->type == ButtonPress) { + if (ev->button == BUTTON3) { + if (_harbour_menu->isMapped()) { + _harbour_menu->unmapWindow(); + } else { + _harbour_menu->setDockApp(da); + _harbour_menu->mapUnderMouse(); + } + } else if (_harbour_menu->isMapped()) { + _harbour_menu->unmapWindow(); + } + } +} + +//! @brief Initiates moving of a DockApp based on info from a XMotionEvent. +void +Harbour::handleMotionNotifyEvent(XMotionEvent* ev, DockApp* da) +{ + if (! da) { + return; + } + + Geometry head; + int x = 0, y = 0; + + _scr->getHeadInfo(Config::instance()->getHarbourHead(), head); + + switch(Config::instance()->getHarbourPlacement()) { + case TOP: + case BOTTOM: + x = ev->x_root - _last_button_x; + y = da->getY(); + if (x < head.x) { + x = head.x; + } else if ((x + da->getWidth()) > (head.x + head.width)) { + x = head.x + head.width - da->getWidth(); + } + break; + case LEFT: + case RIGHT: + x = da->getX(); + y = ev->y_root - _last_button_y; + if (y < head.y) { + y = head.y; + } else if ((y + da->getHeight()) > (head.y + head.height)) { + y = head.y + head.height - da->getHeight(); + } + break; + default: + // do nothing + break; + } + + da->move(x, y); +} + +//! @brief Handles XConfigureRequestEvents. +void +Harbour::handleConfigureRequestEvent(XConfigureRequestEvent* ev, DockApp* da) +{ + if (! da) { + return; + } + + list::iterator it(find(_da_list.begin(), _da_list.end(), da)); + + if (it != _da_list.end()) { + // Thing is that we doesn't listen to border width, position or + // stackign so the only thing that we'll alter is size if that's + // what we want to configure + + uint width = (ev->value_mask&CWWidth) ? ev->width : da->getClientWidth(); + uint height = (ev->value_mask&CWHeight) ? ev->height : da->getClientHeight(); + + da->resize(width, height); + + placeDockAppInsideScreen(da); + } +} + +//! @brief Tries to find a empty spot for the DockApp +void +Harbour::placeDockApp(DockApp *da) +{ + if (! da || ! _da_list.size ()) + return; + + bool right = (Config::instance()->getHarbourOrientation() == BOTTOM_TO_TOP); + + int test, x = 0, y = 0; + bool placed = false, increase = false, x_place = false; + + Geometry head; + PScreen::instance()->getHeadInfo(Config::instance()->getHarbourHead(), head); + + getPlaceStartPosition (da, x, y, x_place); + if (right) { + if (x_place) { + x -= da->getWidth (); + } else { + y -= da->getHeight (); + } + } + + list::iterator it; + if (x_place) { + x = test = right ? head.x + head.width - da->getWidth() : head.x; + + while (! placed + && (right + ? (test >= 0) + : ((test + da->getWidth()) < (head.x + head.width)))) + { + placed = increase = true; + + it = _da_list.begin(); + for (; placed && (it != _da_list.end()); ++it) { + if ((*it) == da) { + continue; // exclude ourselves + } + + if (((*it)->getX() < static_cast(test + da->getWidth())) && + ((*it)->getRX() > test)) { + placed = increase = false; + test = right ? (*it)->getX() - da->getWidth() : (*it)->getRX(); + } + } + + if (placed) { + x = test; + } else if (increase) { + test += right ? -1 : 1; + } + } + } else { // !x_place + y = test = right ? head.y + head.height - da->getHeight() : head.y; + + while (! placed + && (right + ? (test >= 0) + : ((test + da->getHeight()) < (head.y + head.height)))) + { + placed = increase = true; + + it = _da_list.begin(); + for (; placed && (it != _da_list.end()); ++it) { + if ((*it) == da) { + continue; // exclude ourselves + } + + if (((*it)->getY() < static_cast(test + da->getHeight())) && + ((*it)->getBY() > test)) { + placed = increase = false; + test = right ? (*it)->getY() - da->getHeight() : (*it)->getBY(); + } + } + + if (placed) { + y = test; + } else if (increase) { + test += right ? -1 : 1; + } + } + } + + da->move (x, y); +} + + +//! @brief Inserts DockApp and places all dockapps in sorted order +//! @todo Screen boundary checking +void +Harbour::placeDockAppsSorted(void) +{ + if (! _da_list.size ()) { + return; + } + + // place the dockapps + int x, y, x_real, y_real; + bool inc_x = false; + bool right = (Config::instance ()->getHarbourOrientation () == BOTTOM_TO_TOP); + + getPlaceStartPosition (_da_list.front (), x_real, y_real, inc_x); + + list::iterator it (_da_list.begin ()); + for (; it != _da_list.end (); ++it) { + getPlaceStartPosition (*it, x, y, inc_x); + + if (inc_x) { + if (right) { + (*it)->move (x_real - (*it)->getWidth (), y); + x_real -= -(*it)->getWidth (); + } else { + (*it)->move (x_real, y); + x_real += (*it)->getWidth (); + } + } else { + if (right) { + (*it)->move (x, y_real - (*it)->getHeight ()); + y_real -= (*it)->getHeight (); + } else { + (*it)->move (x, y_real); + y_real += (*it)->getHeight (); + } + } + } +} + +/** + * Make sure dock app is inside screen boundaries and placed on the + * right edge, usually called after resizing the dockapp. + */ +void +Harbour::placeDockAppInsideScreen(DockApp *da) +{ + Geometry head; + PScreen::instance()->getHeadInfo(Config::instance()->getHarbourHead(), head); + uint pos = Config::instance()->getHarbourPlacement(); + + // top or bottom placement + if ((pos == TOP) || (pos == BOTTOM)) { + // check horizontal position + if (da->getX() < head.x) { + da->move(head.x, da->getY()); + } else if (da->getRX() > static_cast(head.x + head.width)) { + da->move(head.x + head.width - da->getWidth(), da->getY()); + } + + // check vertical position + if ((pos == TOP) && (da->getY() != head.y)) { + da->move(da->getX(), head.y); + } else if ((pos == BOTTOM) && (da->getBY() != static_cast(head.y + head.height))) { + da->move(da->getX(), head.y + head.height - da->getHeight()); + } + + // left or right placement + } else { + // check vertical position + if (da->getY() < head.y) { + da->move(da->getX(), head.y); + } else if (da->getBY() > static_cast(head.y + head.height)) { + da->move(da->getX(), head.y + head.height - da->getHeight()); + } + + // check horizontal position + if ((pos == LEFT) && (da->getX() != head.x)) { + da->move(head.x, da->getY()); + } else if ((pos == RIGHT) && (da->getRX() != static_cast(head.x + head.width))) { + da->move(head.x + head.width - da->getWidth(), da->getY()); + } + } +} + +//! @brief +void +Harbour::getPlaceStartPosition(DockApp *da, int &x, int &y, bool &inc_x) +{ + if (! da) { + return; + } + + Geometry head; + PScreen::instance()->getHeadInfo(Config::instance()->getHarbourHead(), head); + bool right = (Config::instance()->getHarbourOrientation() == BOTTOM_TO_TOP); + + switch (Config::instance()->getHarbourPlacement()) { + case TOP: + inc_x = true; + x = right ? head.x + head.width : head.x; + y = head.y; + break; + case BOTTOM: + inc_x = true; + x = right ? head.x + head.width : head.x; + y = head.y + head.height - da->getHeight(); + break; + case LEFT: + x = head.x; + y = right ? head.y + head.height : head.y; + break; + case RIGHT: + x = head.x + head.width - da->getWidth(); + y = right ? head.y + head.height : head.y; + break; + } +} + + +//! @brief Inserts DockApp in sorted order in the list +void +Harbour::insertDockAppSorted(DockApp *da) +{ + list::iterator it(_da_list.begin()); + + // The order of this list doesn't make much sense to me when + // it comes to representing it in code, however it's perfectly sane + // for representing the order in the config files. + // anyway, order goes as follows: 1 2 3 0 0 0 -3 -2 -1 + + // Middle of the list. + if (da->getPosition () == 0) { + for (; (it != _da_list.end ()) && ((*it)->getPosition () >= 0); ++it) + ; + // Beginning of the list. + } else if (da->getPosition () > 0) { + for (; it != _da_list.end (); ++it) { + if (((*it)->getPosition () < 1) || // got to 0 or less + (da->getPosition () <= (*it)->getPosition ())) { + break; + } + } + // end of the list + } else { + for (; (it != _da_list.end ()) && ((*it)->getPosition () >= 0); ++it) + ; + for (; (it != _da_list.end ()) && (da->getPosition () >= (*it)->getPosition ()); ++it) + ; + } + + _da_list.insert (it, da); +} + diff --git a/pekwm/Harbour.hh b/pekwm/Harbour.hh new file mode 100644 index 0000000..e356a7e --- /dev/null +++ b/pekwm/Harbour.hh @@ -0,0 +1,84 @@ +// +// Harbour.hh for pekwm +// Copyright (C) 2003-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "../config.h" + +#ifndef _HARBOUR_HH_ +#define _HARBOUR_HH_ + +#include "pekwm.hh" + +class PScreen; +class Theme; +class Workspaces; +class DockApp; +class Strut; +class BaseMenu; +class HarbourMenu; + +#include "Action.hh" + +#include + +class Harbour +{ +public: + Harbour(PScreen *s, Theme *t, Workspaces *w); + ~Harbour(void); + + void addDockApp(DockApp* da); + void removeDockApp(DockApp* da); + void removeAllDockApps(void); + + DockApp* findDockApp(Window win); + DockApp* findDockAppFromFrame(Window win); + + HarbourMenu* getHarbourMenu(void) { return _harbour_menu; } + + inline uint getSize(void) const { return _size; } + +#ifdef HAVE_XRANDR + void updateGeometry(void); +#endif // HAVE_XRANDR + + void restack(void); + void rearrange(void); + void loadTheme(void); + void updateHarbourSize(void); + + void setStateHidden(StateAction sa); + + void handleButtonEvent(XButtonEvent* ev, DockApp* da); + void handleMotionNotifyEvent(XMotionEvent* ev, DockApp* da); + void handleConfigureRequestEvent(XConfigureRequestEvent* ev, DockApp* da); + +private: + void placeDockApp(DockApp *da); + void placeDockAppsSorted(void); + void placeDockAppInsideScreen(DockApp *da); + + void getPlaceStartPosition(DockApp *da, int &x, int &y, bool &inc_x); + void insertDockAppSorted(DockApp *da); + + void updateStrutSize(void); + +private: + PScreen *_scr; + Theme *_theme; + Workspaces *_workspaces; + + std::list _da_list; + HarbourMenu *_harbour_menu; + + bool _hidden; + uint _size; + Strut *_strut; + int _last_button_x, _last_button_y; +}; + +#endif // _HARBOUR_HH_ diff --git a/pekwm/HarbourMenu.cc b/pekwm/HarbourMenu.cc new file mode 100644 index 0000000..922badf --- /dev/null +++ b/pekwm/HarbourMenu.cc @@ -0,0 +1,62 @@ +// +// HarbourMenu.cc for pekwm +// Copyright © 2003-2009 Claes Nästen +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "PMenu.hh" +#include "PScreen.hh" +//#include "Action.hh" +#include "HarbourMenu.hh" +#include "DockApp.hh" + +//! @brief HarbourMenu constructor +HarbourMenu::HarbourMenu(PScreen *scr, Theme *theme, Harbour *harbour) + : PMenu(theme, L"Harbour", "HARBOUR"), + _harbour(harbour), _dockapp(0) +{ + ActionEvent ae; + Action action; + + action.setAction(ACTION_SET); + action.setParamI(0, ACTION_STATE_ICONIFIED); + ae.action_list.clear(); + ae.action_list.push_back(action); + + action.setAction(ACTION_CLOSE); + ae.action_list.clear(); + ae.action_list.push_back(action); + + buildMenu(); +} + +//! @brief HarbourMenu destructor +HarbourMenu::~HarbourMenu(void) +{ +} + +//! @brief +void +HarbourMenu::handleItemExec(PMenu::Item *item) +{ + if (! item || ! _dockapp) { + return; + } + + if (item->getAE().isOnlyAction(ACTION_SET)) { + _dockapp->iconify(); + + } else if (item->getAE().isOnlyAction(ACTION_CLOSE)) { + _dockapp->kill(); + _dockapp = 0; + } +} + diff --git a/pekwm/HarbourMenu.hh b/pekwm/HarbourMenu.hh new file mode 100644 index 0000000..40c9779 --- /dev/null +++ b/pekwm/HarbourMenu.hh @@ -0,0 +1,40 @@ +// +// HarbourMenu.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _HARBOURMENU_HH_ +#define _HARBOURMENU_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" +#include "PMenu.hh" + +class WORefMenu; +class PScreen; +class Theme; +class Harbour; +class DockApp; + +class HarbourMenu : public PMenu +{ +public: + HarbourMenu(PScreen *scr, Theme *theme, Harbour *harbour); + virtual ~HarbourMenu(void); + + virtual void handleItemExec(PMenu::Item *item); + + inline void setDockApp(DockApp *da) { _dockapp = da; } + +private: + Harbour *_harbour; + DockApp *_dockapp; +}; + +#endif // _HARBOURMENU_HH_ diff --git a/pekwm/ImageHandler.cc b/pekwm/ImageHandler.cc new file mode 100644 index 0000000..39bbb4a --- /dev/null +++ b/pekwm/ImageHandler.cc @@ -0,0 +1,192 @@ +// +// ImageHandler.cc for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include + +#include "Config.hh" +#include "PImage.hh" +#include "ImageHandler.hh" +#include "PImage.hh" +#include "PImageLoaderJpeg.hh" +#include "PImageLoaderPng.hh" +#include "PImageLoaderXpm.hh" +#include "PScreen.hh" +#include "Util.hh" + +using std::cerr; +using std::endl; +using std::list; +using std::vector; +using std::string; + +ImageHandler *ImageHandler::_instance = 0; + +//! @brief ImageHandler constructor +ImageHandler::ImageHandler(void) + : _free_on_return(false) +{ +#ifdef DEBUG + if (_instance) { + cerr << __FILE__ << "@" << __LINE__ << ": " + << "ImageHandler(" << this << ")::ImageHandler() *** _instance already set: " + << _instance << endl; + } +#endif // DEBUG + _instance = this; + + // setup parsing maps + _image_type_map[""] = IMAGE_TYPE_NO; + _image_type_map["TILED"] = IMAGE_TYPE_TILED; + _image_type_map["SCALED"] = IMAGE_TYPE_SCALED; + _image_type_map["FIXED"] = IMAGE_TYPE_FIXED; + +#ifdef HAVE_IMAGE_JPEG + PImage::loaderAdd(new PImageLoaderJpeg()); +#endif // HAVE_IMAGE_JPEG +#ifdef HAVE_IMAGE_PNG + PImage::loaderAdd(new PImageLoaderPng()); +#endif // HAVE_IMAGE_PNG +#ifdef HAVE_IMAGE_XPM + PImage::loaderAdd(new PImageLoaderXpm()); +#endif // HAVE_IMAGE_XPM +} + +//! @brief ImageHandler destructor +ImageHandler::~ImageHandler(void) +{ + if (_image_list.size()) { + cerr << " *** WARNING: ImageHandler not empty, " << _image_list.size() << " entries left:" << endl; + + while (_image_list.size()) { + cerr << " * " << _image_list.back().getName() << endl; + delete _image_list.back().getData(); + _image_list.pop_back(); + } + } + + PImage::loaderClear(); +} + +//! @brief Gets or creates a Image +PImage* +ImageHandler::getImage(const std::string &file) +{ + if (! file.size()) { + return 0; + } + + string real_file(file); + ImageType image_type = IMAGE_TYPE_TILED; + + // Split image in path # type parts. + vector tok; + if ((Util::splitString(file, tok, "#", 2, true)) == 2) { + real_file = tok[0]; + image_type = ParseUtil::getValue(tok[1], _image_type_map); + } + + // Load the image, try load paths if not an absolute image path + // already. + PImage *image = 0; + if (real_file[0] == '/') { + image = getImageFromPath(real_file); + } else { + list::reverse_iterator it(_search_path.rbegin()); + for (; ! image && it != _search_path.rend(); ++it) { + image = getImageFromPath(*it + real_file); + } + } + + // Image was found, set correct type. + if (image) { + image->setType(image_type); + } + + return image; +} + +/** + * Load image from absolute path, checks cache for hit before loading. + * + * @param file Path to image file. + * @return PImage or 0 if fails. + */ +PImage* +ImageHandler::getImageFromPath(const std::string &file) +{ + // Check cache for entry. + list >::iterator it(_image_list.begin()); + for (; it != _image_list.end(); ++it) { + if (*it == file) { + it->incRef(); + return it->getData(); + } + } + + // Try to load the image, setup cache only if it succeeds. + PImage *image; + try { + image = new PImage(PScreen::instance()->getDpy(), file); + } catch (LoadException&) { + image = 0; + } + + // Create new PImage and handler entry for it. + if (image) { + HandlerEntry entry(file); + entry.incRef(); + entry.setData(image); + _image_list.push_back(entry); + } + + return image; +} + +//! @brief Returns a Image +void +ImageHandler::returnImage(PImage *image) +{ + bool found = false; + + list >::iterator it(_image_list.begin()); + for (; it != _image_list.end(); ++it) { + if (it->getData() == image) { + found = true; + + it->decRef(); + if (_free_on_return || ! it->getRef()) { + delete it->getData(); + _image_list.erase(it); + } + break; + } + } + + if (! found) { + delete image; + } +} + +//! @brief Frees all images not beeing in use +void +ImageHandler::freeUnref(void) +{ + list >::iterator it(_image_list.begin()); + while (it != _image_list.end()) { + if (it->getRef() == 0) { + delete it->getData(); + it = _image_list.erase(it); + } else { + ++it; + } + } +} diff --git a/pekwm/ImageHandler.hh b/pekwm/ImageHandler.hh new file mode 100644 index 0000000..fa7f242 --- /dev/null +++ b/pekwm/ImageHandler.hh @@ -0,0 +1,61 @@ +// +// ImageHandler.hh for pekwm +// Copyright © 2003-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifndef _IMAGE_HANDLER_HH_ +#define _IMAGE_HANDLER_HH_ + +#include "Handler.hh" +#include "ParseUtil.hh" + +#include +#include + +class PImage; + +/** + * ImageHandler, a caching and image type transparent image handler. + */ +class ImageHandler { +public: + ImageHandler(void); + ~ImageHandler(void); + + //! @brief Returns the ImageHandler instance pointer. + static inline ImageHandler *instance(void) { return _instance; } + + /** Add path entry to the search path. */ + void path_push_back(const std::string &path) { _search_path.push_back(path); } + /** Remove newest entry in the search path. */ + void path_pop_back(void) { _search_path.pop_back(); } + /** Remove all entries from the search path. */ + void path_clear(void) { _search_path.clear(); } + + PImage *getImage(const std::string &file); + void returnImage(PImage *image); + + void freeUnref(void); + +private: + PImage *getImageFromPath(const std::string &file); + +private: + std::list _search_path; /**< List of directories to search. */ + std::list > _image_list; /**< List of loaded images. */ + + bool _free_on_return; /**< If true, images are deleted when returned. */ + + std::map _image_type_map; /**< Type name to type enum map. */ + + static ImageHandler *_instance; /**< Singleton instance pointer. */ +}; + +#endif // _IMAGE_HANDLER_HH_ diff --git a/pekwm/InputDialog.cc b/pekwm/InputDialog.cc new file mode 100644 index 0000000..f25a75e --- /dev/null +++ b/pekwm/InputDialog.cc @@ -0,0 +1,623 @@ +// +// CmdDialog.hh for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include + +#include "InputDialog.hh" +#include "KeyGrabber.hh" +#include "PScreen.hh" +#include "PixmapHandler.hh" +#include "ScreenResources.hh" +#include "Workspaces.hh" + +extern "C" { +#include +} + +using std::list; +using std::map; +using std::wstring; +using std::iswprint; + +map InputDialog::_keysym_map; + +/** + * InputDialog constructor. + */ +InputDialog::InputDialog(Theme *theme, const std::wstring &title) + : PDecor(theme, "INPUTDIALOG"), PWinObjReference(0), + _data(theme->getCmdDialogData()), + _pixmap_bg(None), _pos(0), _buf_off(0), _buf_chars(0) +{ + // PWinObj attributes + setLayer(LAYER_NONE); // hack, goes over LAYER_MENU + _hidden = true; // don't care about it when changing worskpace etc + + if (! _keysym_map.size()) { + reloadKeysymMap(); + } + + // Add action to list, going to be used from close and exec + ::Action action; + _ae.action_list.push_back(action); + + titleAdd(&_title); + titleSetActive(0); + setTitle(title); + + _text_wo = new PWinObj; + XSetWindowAttributes attr; + attr.override_redirect = false; + attr.event_mask = ButtonPressMask|ButtonReleaseMask|ButtonMotionMask| + FocusChangeMask|KeyPressMask|KeyReleaseMask; + _text_wo->setWindow(XCreateWindow(_dpy, _window, + 0, 0, 1, 1, 0, + CopyFromParent, InputOutput, CopyFromParent, + CWOverrideRedirect|CWEventMask, &attr)); + + addChild(_text_wo); + addChildWindow(_text_wo->getWindow()); + activateChild(_text_wo); + _text_wo->mapWindow(); + + // setup texture, size etc + loadTheme(); + + Workspaces::instance()->insert(this); + woListAdd(this); + _wo_map[_window] = this; +} + +/** + * InputDialog destructor. + */ +InputDialog::~InputDialog(void) +{ + Workspaces::instance()->remove(this); + _wo_map.erase(_window); + woListRemove(this); + + // Free resources + if (_text_wo) { + _child_list.remove(_text_wo); + removeChildWindow(_text_wo->getWindow()); + XDestroyWindow(_dpy, _text_wo->getWindow()); + delete _text_wo; + } + + unloadTheme(); +} + +void +InputDialog::reloadKeysymMap(void) +{ + _keysym_map.clear(); + + addKeysymToKeysymMap(XK_KP_0, L'0'); + addKeysymToKeysymMap(XK_KP_1, L'1'); + addKeysymToKeysymMap(XK_KP_2, L'2'); + addKeysymToKeysymMap(XK_KP_3, L'3'); + addKeysymToKeysymMap(XK_KP_4, L'4'); + addKeysymToKeysymMap(XK_KP_5, L'5'); + addKeysymToKeysymMap(XK_KP_6, L'6'); + addKeysymToKeysymMap(XK_KP_7, L'7'); + addKeysymToKeysymMap(XK_KP_8, L'8'); + addKeysymToKeysymMap(XK_KP_9, L'9'); +} + +void +InputDialog::addKeysymToKeysymMap(KeySym keysym, wchar_t chr) +{ + Display *dpy = PScreen::instance()->getDpy(); + + int keysyms_per_keycode; + KeyCode keycode = XKeysymToKeycode(dpy, keysym); + KeySym *keysyms = XGetKeyboardMapping(dpy, keycode, 1, &keysyms_per_keycode); + + for (int i = 0; i < keysyms_per_keycode; i++) { + if (keysyms[i] != NoSymbol) { + _keysym_map[keysyms[i]] = chr; + } + } + + XFree(keysyms); +} + +/** + * Handles ButtonPress, moving the text cursor + */ +ActionEvent* +InputDialog::handleButtonPress(XButtonEvent *ev) +{ + if (*_text_wo == ev->window) { + // FIXME: move cursor + return 0; + } else { + return PDecor::handleButtonPress(ev); + } +} + +/** + * Handles KeyPress, editing the buffer + */ +ActionEvent* +InputDialog::handleKeyPress(XKeyEvent *ev) +{ + ActionEvent *c_ae, *ae = 0; + + if ( (c_ae = KeyGrabber::instance()->findAction(ev, _type)) ) { + list::iterator it(c_ae->action_list.begin()); + for (; it != c_ae->action_list.end(); ++it) { + switch (it->getAction()) { + case INPUT_INSERT: + bufAdd(ev); + completeReset(); + break; + case INPUT_REMOVE: + bufRemove(); + break; + case INPUT_CLEAR: + bufClear(); + completeReset(); + break; + case INPUT_CLEARFROMCURSOR: + bufKill(); + completeReset(); + break; + case INPUT_EXEC: + ae = exec(); + break; + case INPUT_CLOSE: + ae = close(); + break; + case INPUT_COMPLETE: + complete(); + break; + case INPUT_COMPLETE_ABORT: + completeAbort(); + break; + case INPUT_CURS_NEXT: + bufChangePos(1); + completeReset(); + break; + case INPUT_CURS_PREV: + bufChangePos(-1); + completeReset(); + break; + case INPUT_CURS_BEGIN: + _pos = 0; + completeReset(); + break; + case INPUT_CURS_END: + _pos = _buf.size(); + completeReset(); + break; + case INPUT_HIST_NEXT: + histNext(); + completeReset(); + break; + case INPUT_HIST_PREV: + histPrev(); + completeReset(); + break; + case INPUT_NO_ACTION: + default: + // do nothing, shouldn't happen + break; + }; + } + + // something ( most likely ) changed, redraw the window + if (! ae) { + bufChanged(); + render(); + } + } + + return ae; +} + +/** + * Handles ExposeEvent, redraw when ev->count == 0 + */ +ActionEvent* +InputDialog::handleExposeEvent(XExposeEvent *ev) +{ + if (ev->count > 0) { + return 0; + } + render(); + return 0; +} + +/** + * Maps the InputDialog center on the PWinObj it executes actions on. + * + * @param buf Buffer content. + * @param focus Give input focus if true. + * @param wo_ref PWinObj reference, defaults to 0 which does not update. + */ +void +InputDialog::mapCentered(const std::string &buf, bool focus, PWinObj *wo_ref) +{ + // Setup data + _hist_it = _hist_list.end(); + + _buf = Util::to_wide_str(buf); + _pos = _buf.size(); + bufChanged(); + + // Update position + moveCentered(wo_ref); + + // Map and render + PDecor::mapWindowRaised(); + render(); + + // Give input focus if requested + if (focus) { + giveInputFocus(); + } +} + +/** + * Moves to center of wo. + * + * @param wo PWinObj to center on. + */ +void +InputDialog::moveCentered(PWinObj *wo) +{ + // Fallback wo on root. + if (! wo) { + wo = PWinObj::getRootPWinObj(); + } + + // Make sure position is inside head. + Geometry head; + uint head_nr = PScreen::instance()->getNearestHead(wo->getX() + (wo->getWidth() / 2), + wo->getY() + (wo->getHeight() / 2)); + PScreen::instance()->getHeadInfo(head_nr, head); + + // Make sure X is inside head. + int new_x = wo->getX() + (static_cast(wo->getWidth()) - static_cast(_gm.width)) / 2; + if (new_x < head.x) { + new_x = head.x; + } else if ((new_x + _gm.width) > (head.x + head.width)) { + new_x = head.x + head.width - _gm.width; + } + + // Make sure Y is inside head. + int new_y = wo->getY() + (static_cast(wo->getHeight()) - static_cast(_gm.height)) / 2; + if (new_y < head.y) { + new_y = head.y; + } else if ((new_y + _gm.height) > (head.y + head.height)) { + new_y = head.y + head.height - _gm.height; + } + + // Update position. + move(new_x, new_y); +} + +/** + * Sets title of decor + */ +void +InputDialog::setTitle(const std::wstring &title) +{ + _title.setReal(title); +} + +/** + * Maps window, overloaded to refresh content of window after mapping. + */ +void +InputDialog::mapWindow(void) +{ + if (! _mapped) { + // Correct size for current head before mapping + updateSize(); + + PDecor::mapWindow(); + render(); + } +} + +/** + * Sets background and size + */ +void +InputDialog::loadTheme(void) +{ + _data = _theme->getCmdDialogData(); + updateSize(); + updatePixmapSize(); +} + +/** + * Frees resources + */ +void +InputDialog::unloadTheme(void) +{ + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_pixmap_bg); +} + +/** + * Renders _buf onto _text_wo + */ +void +InputDialog::render(void) +{ + _text_wo->clear(); + + // draw buf content + _data->getFont()->setColor(_data->getColor()); + + _data->getFont()->draw(_text_wo->getWindow(), _data->getPad(PAD_LEFT), _data->getPad(PAD_UP), + _buf.c_str() + _buf_off, _buf_chars); + + // draw cursor + uint pos = _data->getPad(PAD_LEFT); + if (_pos > 0) { + pos = _data->getFont()->getWidth(_buf.c_str() + _buf_off, _pos - _buf_off) + 1; + } + + _data->getFont()->draw(_text_wo->getWindow(), pos, _data->getPad(PAD_UP), L"|"); +} + +/** + * Generates ACTION_CLOSE closing dialog. + * + * @return Pointer to ActionEvent. + */ +ActionEvent* +InputDialog::close(void) +{ + _ae.action_list.back().setAction(ACTION_NO); + return &_ae; +} + +/** + * Default implementation, fills in _buf_on_complete to safely run + * completeAbort. + */ +void +InputDialog::complete(void) +{ + _buf_on_complete = _buf; + _pos_on_complete = _pos; +} + +/** + * Restore buffer and clear completion buffers. + */ +void +InputDialog::completeAbort(void) +{ + if (_buf_on_complete.size()) { + _buf = _buf_on_complete; + _pos = _pos_on_complete; + } + + completeReset(); +} + +/** + * Clear the completion buffer. + */ +void +InputDialog::completeReset(void) +{ + // Old gcc doesn't know about .clear() + _buf_on_complete = _buf_on_complete_result = L""; + _pos_on_complete = 0; +} + +/** + * Adds char to buffer + */ +void +InputDialog::bufAdd(XKeyEvent *ev) +{ + KeySym keysym; + char c_return[64]; + memset(c_return, '\0', sizeof(c_return)); + + XLookupString(ev, c_return, sizeof(c_return), &keysym, 0); + if (_keysym_map.count(keysym)) { + _buf.insert(_buf.begin() + _pos++, _keysym_map[keysym]); + } else { + // Add wide string to buffer counting position + wstring buf_ret(Util::to_wide_str(c_return)); + for (unsigned int i = 0; i < buf_ret.size(); ++i) { + if (iswprint(buf_ret[i])) { + _buf.insert(_buf.begin() + _pos++, buf_ret[i]); + } + } + } +} + +/** + * Removes char from buffer + */ +void +InputDialog::bufRemove(void) +{ + if ((_pos > _buf.size()) || (_pos == 0) || (_buf.size() == 0)) { + return; + } + + _buf.erase(_buf.begin() + --_pos); +} + +/** + * Clears the buffer, resets status + */ +void +InputDialog::bufClear(void) +{ + _buf = _buf_on_complete = _buf_on_complete_result = L""; // old gcc doesn't know about .clear() + _pos = _pos_on_complete = _buf_off = _buf_chars = 0; +} + +/** + * Removes buffer content after cursor position + */ +void +InputDialog::bufKill(void) +{ + _buf.resize(_pos); +} + +/** + * Moves the marker + */ +void +InputDialog::bufChangePos(int off) +{ + if ((signed(_pos) + off) < 0) { + _pos = 0; + } else if (unsigned(_pos + off) > _buf.size()) { + _pos = _buf.size(); + } else { + _pos += off; + } +} + +/** + * Recalculates, _buf_off and _buf_chars + */ +void +InputDialog::bufChanged(void) +{ + PFont *font = _data->getFont(); // convenience + + // complete string doesn't fit in the window OR + // we don't fit in the first set + if ((_pos > 0) + && (font->getWidth(_buf.c_str()) > _text_wo->getWidth()) + && (font->getWidth(_buf.c_str(), _pos) > _text_wo->getWidth())) { + + // increase position until it all fits + for (_buf_off = 0; _buf_off < _pos; ++_buf_off) { + if (font->getWidth(_buf.c_str() + _buf_off, _buf.size() - _buf_off) + < _text_wo->getWidth()) { + break; + } + } + + _buf_chars = _buf.size() - _buf_off; + } else { + _buf_off = 0; + _buf_chars = _buf.size(); + } +} + +/** + * Sets the buffer to the next item in the history. + */ +void +InputDialog::histNext(void) +{ + if (_hist_it == _hist_list.end()) { + return; // nothing to do + } + + // get next item, if at the end, restore the edit buffer + ++_hist_it; + if (_hist_it == _hist_list.end()) { + _buf = _hist_new; + } else { + _buf = *_hist_it; + } + + // move cursor to the end of line + _pos = _buf.size(); +} + +/** + * Sets the buffer to the previous item in the history. + */ +void +InputDialog::histPrev(void) +{ + if (_hist_it == _hist_list.begin()) { + return; // nothing to do + } + + // save item so we can restore the edit buffer later + if (_hist_it == _hist_list.end()) { + _hist_new = _buf; + } + + // get prev item + _buf = *(--_hist_it); + + // move cursor to the end of line + _pos = _buf.size(); +} + +/** + * Update command dialog size for view on current head. + */ +void +InputDialog::updateSize(void) +{ + // Resize the child window and update the size depending. + uint old_width = _gm.width; + + unsigned int width, height; + getInputSize(width, height); + resizeChild(width, height); + + // If size was updated, replace the texture and recalculate display + // buffer. + if (old_width != _gm.width) { + updatePixmapSize(); + bufChanged(); + } +} + +/** + * Update background pixmap size and redraw. + */ +void +InputDialog::updatePixmapSize(void) +{ + // Get new pixmap and render texture + PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler(); + pm->returnPixmap(_pixmap_bg); + _pixmap_bg = pm->getPixmap(_text_wo->getWidth(), _text_wo->getHeight(), PScreen::instance()->getDepth()); + + _data->getTexture()->render(_pixmap_bg, 0, 0, _text_wo->getWidth(), _text_wo->getHeight()); + _text_wo->setBackgroundPixmap(_pixmap_bg); + _text_wo->clear(); +} + +/** + * Get size of the text input widget. + * + * @param width Fill in width. + * @param height Fill in height. + */ +void +InputDialog::getInputSize(unsigned int &width, unsigned int &height) +{ + Geometry head; + PScreen::instance()->getHeadInfo(PScreen::instance()->getNearestHead(_gm.x, _gm.y), head); + + width = head.width / 3; + height = _data->getFont()->getHeight() + _data->getPad(PAD_UP) + _data->getPad(PAD_DOWN); +} diff --git a/pekwm/InputDialog.hh b/pekwm/InputDialog.hh new file mode 100644 index 0000000..471f77b --- /dev/null +++ b/pekwm/InputDialog.hh @@ -0,0 +1,108 @@ +// +// CmdDialog.hh for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _INPUT_DIALOG_HH_ +#define _INPUT_DIALOG_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include + +extern "C" { +#include +} + +#include "PWinObj.hh" +#include "PWinObjReference.hh" +#include "PDecor.hh" +#include "Util.hh" + +/** + * Base for windows handling text input. + */ +class InputDialog : public PDecor, public PWinObjReference { +public: + InputDialog(Theme *theme, const std::wstring &title); + virtual ~InputDialog(void); + + // BEGIN - PWinObj interface + virtual void mapWindow(void); + + // PWinObj event interface + ActionEvent *handleButtonPress(XButtonEvent *ev); + ActionEvent *handleKeyPress(XKeyEvent *ev); + ActionEvent *handleExposeEvent(XExposeEvent *ev); + // END - PWinObj interface + + void setTitle(const std::wstring &title); + + void loadTheme(void); + void unloadTheme(void); + void render(void); + + static void reloadKeysymMap(void); + + virtual void mapCentered(const std::string &buf, bool focus, PWinObj *wo_ref = 0); + virtual void moveCentered(PWinObj *wo); + +protected: + virtual ActionEvent *close(void); + virtual ActionEvent *exec(void) { return 0; } + virtual void complete(void); + virtual void completeAbort(void); + virtual void completeReset(void); + + virtual void bufAdd(XKeyEvent *ev); + virtual void bufRemove(void); + virtual void bufClear(void); + virtual void bufKill(void); + virtual void bufChangePos(int off); + + virtual void bufChanged(void); // recalculates buf position + + virtual void histNext(void); + virtual void histPrev(void); + + virtual void updateSize(void); + virtual void updatePixmapSize(void); + + void getInputSize(unsigned int &width, unsigned int &height); + +private: + static void addKeysymToKeysymMap(KeySym keysym, wchar_t chr); + +protected: + Theme::TextDialogData *_data; + + ActionEvent _ae; //!< Action event for event handling + PWinObj *_text_wo; + PDecor::TitleItem _title; + + Pixmap _pixmap_bg; + + // content related + std::wstring _buf; + uint _pos, _buf_off, _buf_chars; // position, start and num display + + // Completion + std::wstring _buf_on_complete; /**< Buffer before completion. */ + std::wstring _buf_on_complete_result; /** Buffer after completion. */ + unsigned int _pos_on_complete; /**< Cursor position on completion start. */ + + // history + std::wstring _hist_new; // the one we started editing on + Util::file_backed_list _hist_list; + Util::file_backed_list::iterator _hist_it; + +private: + static std::map _keysym_map; +}; + +#endif // _INPUT_DIALOG_HH_ diff --git a/pekwm/Jamfile b/pekwm/Jamfile new file mode 100644 index 0000000..b523e77 --- /dev/null +++ b/pekwm/Jamfile @@ -0,0 +1,69 @@ +# +# $Id: Jamfile 2492 2009-02-20 13:10:21Z karijes $ +# +# Part of Equinox Desktop Environment (EDE). +# Copyright (c) 2011 EDE Authors. +# +# This program is licensed under terms of the +# GNU General Public License version 2 or newer. +# See COPYING for details. + +SubDir TOP pekwm ; + +SOURCE = ActionHandler.cc + ActionMenu.cc + Atoms.cc + AutoProperties.cc + CfgParser.cc + CfgParserKey.cc + CfgParserSource.cc + Client.cc + CmdDialog.cc + ColorHandler.cc + Compat.cc + Completer.cc + Config.cc + DecorMenu.cc + DockApp.cc + FontHandler.cc + Frame.cc + FrameListMenu.cc + Harbour.cc + HarbourMenu.cc + ImageHandler.cc + InputDialog.cc + KeyGrabber.cc + main.cc + ManagerWindows.cc + MenuHandler.cc + Observable.cc + PDecor.cc + PFont.cc + PImage.cc + PImageIcon.cc + PImageLoaderJpeg.cc + PImageLoaderPng.cc + PImageLoaderXpm.cc + PixmapHandler.cc + PMenu.cc + PScreen.cc + PTexturePlain.cc + PWinObj.cc + PWinObjReference.cc + RegexString.cc + ScreenResources.cc + SearchDialog.cc + StatusWindow.cc + TextureHandler.cc + Theme.cc + Util.cc + WindowManager.cc + WORefMenu.cc + WorkspaceIndicator.cc + Workspaces.cc ; + + +Program pekwm : $(SOURCE) ; +ObjectC++Flags $(SOURCE) : $(GLOBALFLAGS) $(PEKWM_CXXFLAGS) -DDATADIR=\\\"$(datadir)\\\" -DSYSCONFDIR=\\\"$(PEKWM_CONFIG_DIR)\\\" -DVERSION=\\\"0.1.13\\\" ; +LinkAgainst pekwm : $(PEKWM_LIBS) -lX11 -lXext $(STDLIB) ; +InstallEdeProgram pekwm ; diff --git a/pekwm/KeyGrabber.cc b/pekwm/KeyGrabber.cc new file mode 100644 index 0000000..ea8c4f7 --- /dev/null +++ b/pekwm/KeyGrabber.cc @@ -0,0 +1,390 @@ +// +// KeyGrabber.cc for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "KeyGrabber.hh" + +#include "Config.hh" +#include "PScreen.hh" +#include "CfgParser.hh" +#include "Util.hh" + +#include + +extern "C" { +#include +#include +} + +using std::cerr; +using std::endl; +using std::string; +using std::list; +using std::vector; +using std::find; + +KeyGrabber* KeyGrabber::_instance = 0; + +//! @brief Constructor for Chain class +KeyGrabber::Chain::Chain(uint mod, uint key) : + _mod(mod), _key(key) +{ +} + +//! @brief Destructor for Chain class +KeyGrabber::Chain::~Chain(void) +{ + unload(); +} + +//! @brief Unloads all chains and keys +void +KeyGrabber::Chain::unload(void) +{ + list::iterator it(_chains.begin()); + for (; it != _chains.end(); ++it) { + delete *it; + } + _chains.clear(); + _keys.clear(); +} + +//! @brief Searches the _chains list for an action +KeyGrabber::Chain* +KeyGrabber::Chain::findChain(XKeyEvent *ev) +{ + list::iterator it(_chains.begin()); + + for (; it != _chains.end(); ++it) { + if ((((*it)->getMod() == MOD_ANY) || ((*it)->getMod() == ev->state)) && + (((*it)->getKey() == 0) || ((*it)->getKey() == ev->keycode))) { + return *it; + } + } + + return 0; +} + +//! @brief Searches the _keys list for an action +ActionEvent* +KeyGrabber::Chain::findAction(XKeyEvent *ev) +{ + list::iterator it(_keys.begin()); + + for (; it != _keys.end(); ++it) { + if (((it->mod == MOD_ANY) || (it->mod == ev->state)) && + ((it->sym == 0) || (it->sym == ev->keycode))) { + return &*it; + } + } + + return 0; +} + +//! @brief KeyGrabber constructor +KeyGrabber::KeyGrabber(PScreen *scr) + : _scr(scr), _menu_chain(0, 0), + _global_chain(0, 0), _moveresize_chain(0, 0), + _input_dialog_chain(0, 0) +{ +#ifdef DEBUG + if (_instance) { + cerr << __FILE__ << "@" << __LINE__ << ": " + << "KeyGrabber(" << this << ")::KeyGrabber(" << scr << ")" << endl + << " *** _instance already set: " << _instance << endl; + } +#endif // DEBUG + _instance = this; + + _num_lock = _scr->getNumLock(); + _scroll_lock = _scr->getScrollLock(); +} + +//! @brief Destructor for KeyGrabber class +KeyGrabber::~KeyGrabber(void) +{ + _instance = 0; +} + +//! @brief Parses the "KeyFile" and inserts into _global_keys. +//! If _global_keys holds any keygrabs they will be flushed before +//! reloading the new keybindings. +bool +KeyGrabber::load(const std::string &file, bool force) +{ + if (! force && ! Util::requireReload(_cfg_state, file)) { + return false; + } + + CfgParser key_cfg; + if (! key_cfg.parse(file, CfgParserSource::SOURCE_FILE)) { + _cfg_state.clear(); + if (! key_cfg.parse(SYSCONFDIR "/keys", CfgParserSource::SOURCE_FILE, true)) { + cerr << __FILE__ << "@" << __LINE__ << "Error: no keyfile at " << file + << " or " << SYSCONFDIR "/keys" << endl; + return false; + } + } + + if (key_cfg.is_dynamic_content()) { + _cfg_state.clear(); + } else { + _cfg_state = key_cfg.get_file_list(); + } + + CfgParser::Entry *section; + + section = key_cfg.get_entry_root()->find_section("GLOBAL"); + if (section) { + _global_chain.unload(); + parseGlobalChain(section, &_global_chain); + } + + section = key_cfg.get_entry_root ()->find_section("MOVERESIZE"); + if (section) { + _moveresize_chain.unload (); + parseMoveResizeChain (section, &_moveresize_chain); + } + + // Previously there was only a CmdDialog section, however the text + // handling parts have been moved into InputDialog but to keep + // compatibility this check exists. + section = key_cfg.get_entry_root()->find_section("INPUTDIALOG"); + if (! section) { + section = key_cfg.get_entry_root()->find_section("CMDDIALOG"); + } + + if (section) { + _input_dialog_chain.unload (); + parseInputDialogChain (section, &_input_dialog_chain); + } + + section = key_cfg.get_entry_root ()->find_section("MENU"); + if (section) { + _menu_chain.unload(); + parseMenuChain(section, &_menu_chain); + } + + return true; +} + +//! @brief Parses chain, getting actions as plain ActionEvents +void +KeyGrabber::parseGlobalChain(CfgParser::Entry *section, KeyGrabber::Chain *chain) +{ + ActionEvent ae; + uint key, mod; + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + if ((*it)->get_section() && *(*it) == "CHAIN") { + // Figure out mod and key, create a new chain. + if (Config::instance()->parseKey((*it)->get_value(), mod, key)) { + KeyGrabber::Chain *sub_chain = new KeyGrabber::Chain(mod, key); + parseGlobalChain((*it)->get_section(), sub_chain); + chain->addChain(sub_chain); + } + } else if (Config::instance()->parseActionEvent((*it), ae, KEYGRABBER_OK, false)) { + chain->addAction(ae); + } + } +} + +//! @brief Parses chain, getting actions as MoveResizeEvents +void +KeyGrabber::parseMoveResizeChain(CfgParser::Entry *section, KeyGrabber::Chain *chain) +{ + ActionEvent ae; + uint key, mod; + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + if ((*it)->get_section() && *(*it) == "CHAIN") { + // Figure out mod and key, create a new chain. + if (Config::instance()->parseKey((*it)->get_value(), mod, key)) { + KeyGrabber::Chain *sub_chain = new KeyGrabber::Chain(mod, key); + parseMoveResizeChain((*it)->get_section(), sub_chain); + chain->addChain(sub_chain); + } + } else if (Config::instance()->parseMoveResizeEvent((*it), ae)) { + chain->addAction(ae); + } + } +} + +//! @brief Parses chain, getting actions as InputDialog Events +void +KeyGrabber::parseInputDialogChain(CfgParser::Entry *section, KeyGrabber::Chain *chain) +{ + ActionEvent ae; + uint key, mod; + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + if ((*it)->get_section() && *(*it) == "CHAIN") { + // Figure out mod and key, create a new chain. + if (Config::instance()->parseKey((*it)->get_value(), mod, key)) { + KeyGrabber::Chain *sub_chain = new KeyGrabber::Chain(mod, key); + parseInputDialogChain((*it)->get_section(), sub_chain); + chain->addChain(sub_chain); + } + } else if (Config::instance()->parseInputDialogEvent((*it), ae)) { + chain->addAction(ae); + } + } +} + +//! @brief Parses chain, getting actions as MenuEvents +void +KeyGrabber::parseMenuChain(CfgParser::Entry *section, KeyGrabber::Chain *chain) +{ + ActionEvent ae; + uint key, mod; + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + if ((*it)->get_section() && *(*it) == "CHAIN") { + // Figure out mod and key, create a new chain. + if (Config::instance()->parseKey((*it)->get_value(), mod, key)) { + KeyGrabber::Chain *sub_chain = new KeyGrabber::Chain (mod, key); + parseGlobalChain((*it)->get_section(), sub_chain); + chain->addChain(sub_chain); + } + } else if (Config::instance()->parseMenuEvent((*it), ae)) { + chain->addAction(ae); + } + } +} + +//! @brief Grabs all the keybindings in _keybindings on the Window win. +//! @param win Window to grab the keys on. +void +KeyGrabber::grabKeys(Window win) +{ + const list chains = _global_chain.getChainList(); + list::const_iterator c_it = chains.begin(); + for (; c_it != chains.end(); ++c_it) + grabKey(win, (*c_it)->getMod(), (*c_it)->getKey()); + + const list keys = _global_chain.getKeyList(); + list::const_iterator k_it = keys.begin(); + for (; k_it != keys.end(); ++k_it) + grabKey(win, k_it->mod, k_it->sym); +} + +//! @brief Grabs a key with state on Window win with "all possible" modifiers. +//! @param win Window to grab the key on. +//! @param mod Modifier state to grab. +//! @param key Key state to grab. +void +KeyGrabber::grabKey(Window win, uint mod, uint key) +{ + Display *dpy = _scr->getDpy(); // convenience + + XGrabKey(dpy, key, mod, win, true, GrabModeAsync, GrabModeAsync); + XGrabKey(dpy, key, mod|LockMask, win, true, GrabModeAsync, GrabModeAsync); + + if (_num_lock) { + XGrabKey(dpy, key, mod|_num_lock, + win, true, GrabModeAsync, GrabModeAsync); + XGrabKey(dpy, key, mod|_num_lock|LockMask, + win, true, GrabModeAsync, GrabModeAsync); + } + if (_scroll_lock) { + XGrabKey(dpy, key, mod|_scroll_lock, + win, true, GrabModeAsync, GrabModeAsync); + XGrabKey(dpy, key, mod|_scroll_lock|LockMask, + win, true, GrabModeAsync, GrabModeAsync); + } + if (_num_lock && _scroll_lock) { + XGrabKey(dpy, key, mod|_num_lock|_scroll_lock, + win, true, GrabModeAsync, GrabModeAsync); + XGrabKey(dpy, key, mod|_num_lock|_scroll_lock|LockMask, + win, true, GrabModeAsync, GrabModeAsync); + } +} + +//! @brief Ungrabs all the keybindings on the Window win. +//! @param win Window to ungrab keys on. +void +KeyGrabber::ungrabKeys(Window win) +{ + XUngrabKey(_scr->getDpy(), AnyKey, AnyModifier, win); +} + +//! @brief Tries to match the XKeyEvent to an usefull action and return it +//! @param ev XKeyEvent to match. +ActionEvent* +KeyGrabber::findAction(XKeyEvent *ev, KeyGrabber::Chain *chain) +{ + if (! ev) { + return 0; + } + + PScreen::stripStateModifiers(&ev->state); + + ActionEvent *action = 0; + KeyGrabber::Chain *sub_chain = _global_chain.findChain(ev); + if (sub_chain && _scr->grabKeyboard(_scr->getRoot())) { + XEvent c_ev; + KeyGrabber::Chain *last_chain; + bool exit = false; + + while (! exit) { + XMaskEvent(_scr->getDpy(), KeyPressMask, &c_ev); + PScreen::stripStateModifiers(&c_ev.xkey.state); + + if (IsModifierKey(XKeycodeToKeysym(_scr->getDpy(), + c_ev.xkey.keycode, 0))) { + // do nothing + } else if ((last_chain = sub_chain->findChain(&c_ev.xkey))) { + sub_chain = last_chain; + } else { + action = sub_chain->findAction(&c_ev.xkey); + exit = true; + } + } + + _scr->ungrabKeyboard(); + } else { + action = chain->findAction(ev); + } + + return action; +} + +//! @brief Finds action matching ev, continues chain if needed +ActionEvent* +KeyGrabber::findAction(XKeyEvent *ev, PWinObj::Type type) +{ + ActionEvent *ae = 0; + + if (type == PWinObj::WO_MENU) { + ae = findAction(ev, &_menu_chain); + } + if (type == PWinObj::WO_CMD_DIALOG || type == PWinObj::WO_SEARCH_DIALOG) { + ae = findAction(ev, &_input_dialog_chain); + } + + // no action the menu list, try the global list + if (! ae) { + ae = findAction(ev, &_global_chain); + } + + return ae; +} + +//! @brief Searches the _moveresize_chain for actions. +ActionEvent* +KeyGrabber::findMoveResizeAction(XKeyEvent *ev) +{ + return findAction(ev, &_moveresize_chain); +} diff --git a/pekwm/KeyGrabber.hh b/pekwm/KeyGrabber.hh new file mode 100644 index 0000000..a503869 --- /dev/null +++ b/pekwm/KeyGrabber.hh @@ -0,0 +1,108 @@ +// +// KeyGrabber.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _KEYGRABBER_HH_ +#define _KEYGRABBER_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" + +#include "Action.hh" +#include "CfgParser.hh" +#include "PWinObj.hh" + +class PScreen; + +#include +#include + +extern "C" { +#include +} + +//! @brief Key grabbing and matching routines with key chain support. +class KeyGrabber +{ +public: + //! @brief Key chain state information. + class Chain + { + public: + Chain(uint mod, uint key); + ~Chain(void); + + void unload(void); + + //! @brief Returns modifier state Chain represents. + inline uint getMod(void) const { return _mod; } + //! @brief Returns key state Chain represents. + inline uint getKey(void) const { return _key; } + + //! @brief Returns list of Chains that follows. + inline const std::list &getChainList(void) const { return _chains; } + //! @brief Returns list of Keys in chain. + inline const std::list &getKeyList(void) const { return _keys; } + + //! @brief Adds chain to Chain list. + inline void addChain(Chain *chain) { _chains.push_back(chain); } + //! @brief Adds action to Key list. + inline void addAction(const ActionEvent &key) { _keys.push_back(key); } + + Chain *findChain(XKeyEvent *ev); + ActionEvent *findAction(XKeyEvent *ev); + + private: + uint _mod, _key; + + std::list _chains; + std::list _keys; + }; + + KeyGrabber(PScreen *scr); + ~KeyGrabber(void); + + //! @brief Returns the KeyGrabber instance pointer. + static KeyGrabber *instance(void) { return _instance; } + + bool load(const std::string &file, bool force=false); + void grabKeys(Window win); + void ungrabKeys(Window win); + + ActionEvent *findAction(XKeyEvent *ev, PWinObj::Type type); + ActionEvent *findMoveResizeAction(XKeyEvent *ev); + +private: + void grabKey(Window win, uint mod, uint key); + + void parseGlobalChain(CfgParser::Entry *section, KeyGrabber::Chain *chain); + void parseMoveResizeChain(CfgParser::Entry *section, KeyGrabber::Chain *chain); + void parseInputDialogChain(CfgParser::Entry *section, KeyGrabber::Chain *chain); + void parseMenuChain(CfgParser::Entry *section, KeyGrabber::Chain *chain); + + ActionEvent *findAction(XKeyEvent *ev, KeyGrabber::Chain *chain); + +private: + PScreen *_scr; + + std::map _cfg_state; /**< Map of file mtime for all files touched by a configuration. */ + + KeyGrabber::Chain _menu_chain; + KeyGrabber::Chain _global_chain; + KeyGrabber::Chain _moveresize_chain; + KeyGrabber::Chain _input_dialog_chain; + + uint _num_lock; + uint _scroll_lock; + + static KeyGrabber *_instance; +}; + +#endif // _KEYGRABBER_HH_ diff --git a/pekwm/Makefile b/pekwm/Makefile new file mode 100644 index 0000000..56a952c --- /dev/null +++ b/pekwm/Makefile @@ -0,0 +1,623 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# src/Makefile. Generated from Makefile.in by configure. + +# 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. + + + + +pkgdatadir = $(datadir)/pekwm +pkgincludedir = $(includedir)/pekwm +pkglibdir = $(libdir)/pekwm +pkglibexecdir = $(libexecdir)/pekwm +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 = i686-pc-linux-gnu +host_triplet = i686-pc-linux-gnu +bin_PROGRAMS = pekwm$(EXEEXT) +subdir = src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_pekwm_OBJECTS = ActionHandler.$(OBJEXT) ActionMenu.$(OBJEXT) \ + Atoms.$(OBJEXT) AutoProperties.$(OBJEXT) CfgParser.$(OBJEXT) \ + CfgParserKey.$(OBJEXT) CfgParserSource.$(OBJEXT) \ + Compat.$(OBJEXT) Completer.$(OBJEXT) ColorHandler.$(OBJEXT) \ + Client.$(OBJEXT) CmdDialog.$(OBJEXT) Config.$(OBJEXT) \ + DecorMenu.$(OBJEXT) DockApp.$(OBJEXT) Frame.$(OBJEXT) \ + FontHandler.$(OBJEXT) FrameListMenu.$(OBJEXT) \ + Harbour.$(OBJEXT) HarbourMenu.$(OBJEXT) ImageHandler.$(OBJEXT) \ + InputDialog.$(OBJEXT) KeyGrabber.$(OBJEXT) \ + ManagerWindows.$(OBJEXT) MenuHandler.$(OBJEXT) \ + Observable.$(OBJEXT) PDecor.$(OBJEXT) PFont.$(OBJEXT) \ + PMenu.$(OBJEXT) PTexturePlain.$(OBJEXT) PWinObj.$(OBJEXT) \ + PWinObjReference.$(OBJEXT) PImage.$(OBJEXT) \ + PImageIcon.$(OBJEXT) PImageLoaderJpeg.$(OBJEXT) \ + PImageLoaderPng.$(OBJEXT) PImageLoaderXpm.$(OBJEXT) \ + PixmapHandler.$(OBJEXT) RegexString.$(OBJEXT) \ + PScreen.$(OBJEXT) ScreenResources.$(OBJEXT) \ + StatusWindow.$(OBJEXT) SearchDialog.$(OBJEXT) Theme.$(OBJEXT) \ + TextureHandler.$(OBJEXT) Util.$(OBJEXT) WORefMenu.$(OBJEXT) \ + WindowManager.$(OBJEXT) Workspaces.$(OBJEXT) \ + WorkspaceIndicator.$(OBJEXT) main.$(OBJEXT) +pekwm_OBJECTS = $(am_pekwm_OBJECTS) +pekwm_LDADD = $(LDADD) +DEFAULT_INCLUDES = -I. -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +CXXLD = $(CXX) +CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ + -o $@ +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(pekwm_SOURCES) +DIST_SOURCES = $(pekwm_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = ${SHELL} /home/sanel/progams/pekwm-0.1.13/missing --run aclocal-1.11 +AMTAR = ${SHELL} /home/sanel/progams/pekwm-0.1.13/missing --run tar +AUTOCONF = ${SHELL} /home/sanel/progams/pekwm-0.1.13/missing --run autoconf +AUTOHEADER = ${SHELL} /home/sanel/progams/pekwm-0.1.13/missing --run autoheader +AUTOMAKE = ${SHELL} /home/sanel/progams/pekwm-0.1.13/missing --run automake-1.11 +AWK = gawk +CC = gcc +CCDEPMODE = depmode=gcc3 +CFLAGS = -g -O2 +CPPFLAGS = +CXX = g++ +CXXCPP = g++ -E +CXXDEPMODE = depmode=gcc3 +CXXFLAGS = -g -O2 -I/usr/include/freetype2 -I/usr/include/libpng12 -DSYSCONFDIR=\"$(sysconfdir)/pekwm\" -DDATADIR=\"$(datadir)\" +CYGPATH_W = echo +DEFS = -DHAVE_CONFIG_H +DEPDIR = .deps +ECHO_C = +ECHO_N = -n +ECHO_T = +EGREP = /bin/grep -E +EXEEXT = +GREP = /bin/grep +INSTALL = /usr/bin/install -c +INSTALL_DATA = ${INSTALL} -m 644 +INSTALL_PROGRAM = ${INSTALL} +INSTALL_SCRIPT = ${INSTALL} +INSTALL_STRIP_PROGRAM = $(install_sh) -c -s +LDFLAGS = -lSM -lICE +LIBICONV = +LIBOBJS = +LIBS = -lX11 -lXext -lXft -lXpm -ljpeg -lpng12 -lXrandr +LTLIBICONV = +LTLIBOBJS = +MAKEINFO = ${SHELL} /home/sanel/progams/pekwm-0.1.13/missing --run makeinfo +MKDIR_P = /bin/mkdir -p +OBJEXT = o +PACKAGE = pekwm +PACKAGE_BUGREPORT = +PACKAGE_NAME = pekwm +PACKAGE_STRING = pekwm 0.1.13 +PACKAGE_TARNAME = pekwm +PACKAGE_URL = +PACKAGE_VERSION = 0.1.13 +PATH_SEPARATOR = : +PKG_CONFIG = /usr/bin/pkg-config +SED = /bin/sed +SET_MAKE = +SH = /bin/sh +SHELL = /bin/sh +STRIP = +THEME = default +VERSION = 0.1.13 +XMKMF = +X_CFLAGS = +X_EXTRA_LIBS = +X_LIBS = +X_PRE_LIBS = -lSM -lICE +abs_builddir = /home/sanel/progams/pekwm-0.1.13/src +abs_srcdir = /home/sanel/progams/pekwm-0.1.13/src +abs_top_builddir = /home/sanel/progams/pekwm-0.1.13 +abs_top_srcdir = /home/sanel/progams/pekwm-0.1.13 +ac_ct_CC = gcc +ac_ct_CXX = g++ +am__include = include +am__leading_dot = . +am__quote = +am__tar = ${AMTAR} chof - "$$tardir" +am__untar = ${AMTAR} xf - +bindir = ${exec_prefix}/bin +build = i686-pc-linux-gnu +build_alias = +build_cpu = i686 +build_os = linux-gnu +build_vendor = pc +builddir = . +datadir = ${datarootdir} +datarootdir = ${prefix}/share +docdir = ${datarootdir}/doc/${PACKAGE_TARNAME} +dvidir = ${docdir} +exec_prefix = ${prefix} +host = i686-pc-linux-gnu +host_alias = +host_cpu = i686 +host_os = linux-gnu +host_vendor = pc +htmldir = ${docdir} +includedir = ${prefix}/include +infodir = ${datarootdir}/info +install_sh = ${SHELL} /home/sanel/progams/pekwm-0.1.13/install-sh +libdir = ${exec_prefix}/lib +libexecdir = ${exec_prefix}/libexec +libpng12_CFLAGS = -I/usr/include/libpng12 +libpng12_LIBS = -lpng12 +libpng_CFLAGS = +libpng_LIBS = +localedir = ${datarootdir}/locale +localstatedir = ${prefix}/var +mandir = ${datarootdir}/man +mkdir_p = /bin/mkdir -p +oldincludedir = /usr/include +pdfdir = ${docdir} +prefix = /opt/ede +program_transform_name = s,x,x, +psdir = ${docdir} +sbindir = ${exec_prefix}/sbin +sharedstatedir = ${prefix}/com +srcdir = . +sysconfdir = ${prefix}/etc +target_alias = +top_build_prefix = ../ +top_builddir = .. +top_srcdir = .. +xft_CFLAGS = -I/usr/include/freetype2 +xft_LIBS = -lXft +xrandr_CFLAGS = +xrandr_LIBS = -lXrandr +AUTOMAKE_OPTIONS = foreign +pekwm_SOURCES = \ + Action.hh \ + ActionHandler.cc ActionHandler.hh \ + ActionMenu.cc ActionMenu.hh \ + Atoms.cc Atoms.hh \ + AutoProperties.cc AutoProperties.hh \ + CfgParser.cc CfgParser.hh \ + CfgParserKey.cc CfgParserKey.hh \ + CfgParserSource.cc CfgParserSource.hh \ + Compat.cc Compat.hh \ + Completer.cc Completer.hh \ + ColorHandler.cc ColorHandler.hh \ + Client.cc Client.hh \ + CmdDialog.cc CmdDialog.hh \ + Config.cc Config.hh \ + DecorMenu.cc DecorMenu.hh \ + DockApp.cc DockApp.hh \ + Exception.hh \ + Frame.cc Frame.hh \ + FontHandler.cc FontHandler.hh \ + FrameListMenu.cc FrameListMenu.hh \ + Harbour.cc Harbour.hh \ + Handler.hh \ + HarbourMenu.cc HarbourMenu.hh \ + ImageHandler.cc ImageHandler.hh \ + InputDialog.cc InputDialog.hh \ + KeyGrabber.cc KeyGrabber.hh \ + ManagerWindows.cc ManagerWindows.hh \ + MenuHandler.cc MenuHandler.hh \ + Observer.hh \ + Observable.cc Observable.hh \ + PDecor.cc PDecor.hh \ + PFont.cc PFont.hh \ + PMenu.cc PMenu.hh \ + PTexture.hh \ + PTexturePlain.cc PTexturePlain.hh \ + PWinObj.cc PWinObj.hh \ + PWinObjReference.cc PWinObjReference.hh \ + PImage.cc PImage.hh \ + PImageIcon.cc PImageIcon.hh \ + PImageLoader.hh \ + PImageLoaderJpeg.cc PImageLoaderJpeg.hh \ + PImageLoaderPng.cc PImageLoaderPng.hh \ + PImageLoaderXpm.cc PImageLoaderXpm.hh \ + PixmapHandler.cc PixmapHandler.hh \ + RegexString.cc RegexString.hh \ + ParseUtil.hh \ + PScreen.cc PScreen.hh \ + ScreenResources.cc ScreenResources.hh \ + StatusWindow.cc StatusWindow.hh \ + SearchDialog.cc SearchDialog.hh \ + Theme.cc Theme.hh \ + TextureHandler.cc TextureHandler.hh \ + Timer.hh Types.hh \ + Util.cc Util.hh \ + WORefMenu.cc WORefMenu.hh \ + WindowManager.cc WindowManager.hh \ + Workspaces.cc Workspaces.hh \ + WorkspaceIndicator.cc WorkspaceIndicator.hh \ + main.cc \ + pekwm.hh + +all: all-am + +.SUFFIXES: +.SUFFIXES: .cc .o .obj +$(srcdir)/Makefile.in: $(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) --foreign src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/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: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +pekwm$(EXEEXT): $(pekwm_OBJECTS) $(pekwm_DEPENDENCIES) + @rm -f pekwm$(EXEEXT) + $(CXXLINK) $(pekwm_OBJECTS) $(pekwm_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +include ./$(DEPDIR)/ActionHandler.Po +include ./$(DEPDIR)/ActionMenu.Po +include ./$(DEPDIR)/Atoms.Po +include ./$(DEPDIR)/AutoProperties.Po +include ./$(DEPDIR)/CfgParser.Po +include ./$(DEPDIR)/CfgParserKey.Po +include ./$(DEPDIR)/CfgParserSource.Po +include ./$(DEPDIR)/Client.Po +include ./$(DEPDIR)/CmdDialog.Po +include ./$(DEPDIR)/ColorHandler.Po +include ./$(DEPDIR)/Compat.Po +include ./$(DEPDIR)/Completer.Po +include ./$(DEPDIR)/Config.Po +include ./$(DEPDIR)/DecorMenu.Po +include ./$(DEPDIR)/DockApp.Po +include ./$(DEPDIR)/FontHandler.Po +include ./$(DEPDIR)/Frame.Po +include ./$(DEPDIR)/FrameListMenu.Po +include ./$(DEPDIR)/Harbour.Po +include ./$(DEPDIR)/HarbourMenu.Po +include ./$(DEPDIR)/ImageHandler.Po +include ./$(DEPDIR)/InputDialog.Po +include ./$(DEPDIR)/KeyGrabber.Po +include ./$(DEPDIR)/ManagerWindows.Po +include ./$(DEPDIR)/MenuHandler.Po +include ./$(DEPDIR)/Observable.Po +include ./$(DEPDIR)/PDecor.Po +include ./$(DEPDIR)/PFont.Po +include ./$(DEPDIR)/PImage.Po +include ./$(DEPDIR)/PImageIcon.Po +include ./$(DEPDIR)/PImageLoaderJpeg.Po +include ./$(DEPDIR)/PImageLoaderPng.Po +include ./$(DEPDIR)/PImageLoaderXpm.Po +include ./$(DEPDIR)/PMenu.Po +include ./$(DEPDIR)/PScreen.Po +include ./$(DEPDIR)/PTexturePlain.Po +include ./$(DEPDIR)/PWinObj.Po +include ./$(DEPDIR)/PWinObjReference.Po +include ./$(DEPDIR)/PixmapHandler.Po +include ./$(DEPDIR)/RegexString.Po +include ./$(DEPDIR)/ScreenResources.Po +include ./$(DEPDIR)/SearchDialog.Po +include ./$(DEPDIR)/StatusWindow.Po +include ./$(DEPDIR)/TextureHandler.Po +include ./$(DEPDIR)/Theme.Po +include ./$(DEPDIR)/Util.Po +include ./$(DEPDIR)/WORefMenu.Po +include ./$(DEPDIR)/WindowManager.Po +include ./$(DEPDIR)/WorkspaceIndicator.Po +include ./$(DEPDIR)/Workspaces.Po +include ./$(DEPDIR)/main.Po + +.cc.o: + $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< + $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +# source='$<' object='$@' libtool=no \ +# DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) \ +# $(CXXCOMPILE) -c -o $@ $< + +.cc.obj: + $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` + $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +# source='$<' object='$@' libtool=no \ +# DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) \ +# $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +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: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +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-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-local 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-binPROGRAMS + +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 + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-local distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-binPROGRAMS 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 pdf pdf-am ps ps-am \ + tags uninstall uninstall-am uninstall-binPROGRAMS + + +distclean-local: + rm -f *\~ .\#* + +# 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/pekwm/Makefile.am b/pekwm/Makefile.am new file mode 100644 index 0000000..fc4b582 --- /dev/null +++ b/pekwm/Makefile.am @@ -0,0 +1,70 @@ +AUTOMAKE_OPTIONS = foreign + +CXXFLAGS = @CXXFLAGS@ -DSYSCONFDIR=\"$(sysconfdir)/pekwm\" -DDATADIR=\"$(datadir)\" + +bin_PROGRAMS = pekwm +pekwm_SOURCES = \ + Action.hh \ + ActionHandler.cc ActionHandler.hh \ + ActionMenu.cc ActionMenu.hh \ + Atoms.cc Atoms.hh \ + AutoProperties.cc AutoProperties.hh \ + CfgParser.cc CfgParser.hh \ + CfgParserKey.cc CfgParserKey.hh \ + CfgParserSource.cc CfgParserSource.hh \ + Compat.cc Compat.hh \ + Completer.cc Completer.hh \ + ColorHandler.cc ColorHandler.hh \ + Client.cc Client.hh \ + CmdDialog.cc CmdDialog.hh \ + Config.cc Config.hh \ + DecorMenu.cc DecorMenu.hh \ + DockApp.cc DockApp.hh \ + Exception.hh \ + Frame.cc Frame.hh \ + FontHandler.cc FontHandler.hh \ + FrameListMenu.cc FrameListMenu.hh \ + Harbour.cc Harbour.hh \ + Handler.hh \ + HarbourMenu.cc HarbourMenu.hh \ + ImageHandler.cc ImageHandler.hh \ + InputDialog.cc InputDialog.hh \ + KeyGrabber.cc KeyGrabber.hh \ + ManagerWindows.cc ManagerWindows.hh \ + MenuHandler.cc MenuHandler.hh \ + Observer.hh \ + Observable.cc Observable.hh \ + PDecor.cc PDecor.hh \ + PFont.cc PFont.hh \ + PMenu.cc PMenu.hh \ + PTexture.hh \ + PTexturePlain.cc PTexturePlain.hh \ + PWinObj.cc PWinObj.hh \ + PWinObjReference.cc PWinObjReference.hh \ + PImage.cc PImage.hh \ + PImageIcon.cc PImageIcon.hh \ + PImageLoader.hh \ + PImageLoaderJpeg.cc PImageLoaderJpeg.hh \ + PImageLoaderPng.cc PImageLoaderPng.hh \ + PImageLoaderXpm.cc PImageLoaderXpm.hh \ + PixmapHandler.cc PixmapHandler.hh \ + RegexString.cc RegexString.hh \ + ParseUtil.hh \ + PScreen.cc PScreen.hh \ + ScreenResources.cc ScreenResources.hh \ + StatusWindow.cc StatusWindow.hh \ + SearchDialog.cc SearchDialog.hh \ + Theme.cc Theme.hh \ + TextureHandler.cc TextureHandler.hh \ + Timer.hh Types.hh \ + Util.cc Util.hh \ + WORefMenu.cc WORefMenu.hh \ + WindowManager.cc WindowManager.hh \ + Workspaces.cc Workspaces.hh \ + WorkspaceIndicator.cc WorkspaceIndicator.hh \ + main.cc \ + pekwm.hh + +distclean-local: + rm -f *\~ .\#* + diff --git a/pekwm/Makefile.in b/pekwm/Makefile.in new file mode 100644 index 0000000..476a51c --- /dev/null +++ b/pekwm/Makefile.in @@ -0,0 +1,623 @@ +# 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@ +bin_PROGRAMS = pekwm$(EXEEXT) +subdir = src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_pekwm_OBJECTS = ActionHandler.$(OBJEXT) ActionMenu.$(OBJEXT) \ + Atoms.$(OBJEXT) AutoProperties.$(OBJEXT) CfgParser.$(OBJEXT) \ + CfgParserKey.$(OBJEXT) CfgParserSource.$(OBJEXT) \ + Compat.$(OBJEXT) Completer.$(OBJEXT) ColorHandler.$(OBJEXT) \ + Client.$(OBJEXT) CmdDialog.$(OBJEXT) Config.$(OBJEXT) \ + DecorMenu.$(OBJEXT) DockApp.$(OBJEXT) Frame.$(OBJEXT) \ + FontHandler.$(OBJEXT) FrameListMenu.$(OBJEXT) \ + Harbour.$(OBJEXT) HarbourMenu.$(OBJEXT) ImageHandler.$(OBJEXT) \ + InputDialog.$(OBJEXT) KeyGrabber.$(OBJEXT) \ + ManagerWindows.$(OBJEXT) MenuHandler.$(OBJEXT) \ + Observable.$(OBJEXT) PDecor.$(OBJEXT) PFont.$(OBJEXT) \ + PMenu.$(OBJEXT) PTexturePlain.$(OBJEXT) PWinObj.$(OBJEXT) \ + PWinObjReference.$(OBJEXT) PImage.$(OBJEXT) \ + PImageIcon.$(OBJEXT) PImageLoaderJpeg.$(OBJEXT) \ + PImageLoaderPng.$(OBJEXT) PImageLoaderXpm.$(OBJEXT) \ + PixmapHandler.$(OBJEXT) RegexString.$(OBJEXT) \ + PScreen.$(OBJEXT) ScreenResources.$(OBJEXT) \ + StatusWindow.$(OBJEXT) SearchDialog.$(OBJEXT) Theme.$(OBJEXT) \ + TextureHandler.$(OBJEXT) Util.$(OBJEXT) WORefMenu.$(OBJEXT) \ + WindowManager.$(OBJEXT) Workspaces.$(OBJEXT) \ + WorkspaceIndicator.$(OBJEXT) main.$(OBJEXT) +pekwm_OBJECTS = $(am_pekwm_OBJECTS) +pekwm_LDADD = $(LDADD) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +CXXLD = $(CXX) +CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ + -o $@ +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(pekwm_SOURCES) +DIST_SOURCES = $(pekwm_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ -DSYSCONFDIR=\"$(sysconfdir)/pekwm\" -DDATADIR=\"$(datadir)\" +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +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@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SH = @SH@ +SHELL = @SHELL@ +STRIP = @STRIP@ +THEME = @THEME@ +VERSION = @VERSION@ +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_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +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@ +libpng12_CFLAGS = @libpng12_CFLAGS@ +libpng12_LIBS = @libpng12_LIBS@ +libpng_CFLAGS = @libpng_CFLAGS@ +libpng_LIBS = @libpng_LIBS@ +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@ +xft_CFLAGS = @xft_CFLAGS@ +xft_LIBS = @xft_LIBS@ +xrandr_CFLAGS = @xrandr_CFLAGS@ +xrandr_LIBS = @xrandr_LIBS@ +AUTOMAKE_OPTIONS = foreign +pekwm_SOURCES = \ + Action.hh \ + ActionHandler.cc ActionHandler.hh \ + ActionMenu.cc ActionMenu.hh \ + Atoms.cc Atoms.hh \ + AutoProperties.cc AutoProperties.hh \ + CfgParser.cc CfgParser.hh \ + CfgParserKey.cc CfgParserKey.hh \ + CfgParserSource.cc CfgParserSource.hh \ + Compat.cc Compat.hh \ + Completer.cc Completer.hh \ + ColorHandler.cc ColorHandler.hh \ + Client.cc Client.hh \ + CmdDialog.cc CmdDialog.hh \ + Config.cc Config.hh \ + DecorMenu.cc DecorMenu.hh \ + DockApp.cc DockApp.hh \ + Exception.hh \ + Frame.cc Frame.hh \ + FontHandler.cc FontHandler.hh \ + FrameListMenu.cc FrameListMenu.hh \ + Harbour.cc Harbour.hh \ + Handler.hh \ + HarbourMenu.cc HarbourMenu.hh \ + ImageHandler.cc ImageHandler.hh \ + InputDialog.cc InputDialog.hh \ + KeyGrabber.cc KeyGrabber.hh \ + ManagerWindows.cc ManagerWindows.hh \ + MenuHandler.cc MenuHandler.hh \ + Observer.hh \ + Observable.cc Observable.hh \ + PDecor.cc PDecor.hh \ + PFont.cc PFont.hh \ + PMenu.cc PMenu.hh \ + PTexture.hh \ + PTexturePlain.cc PTexturePlain.hh \ + PWinObj.cc PWinObj.hh \ + PWinObjReference.cc PWinObjReference.hh \ + PImage.cc PImage.hh \ + PImageIcon.cc PImageIcon.hh \ + PImageLoader.hh \ + PImageLoaderJpeg.cc PImageLoaderJpeg.hh \ + PImageLoaderPng.cc PImageLoaderPng.hh \ + PImageLoaderXpm.cc PImageLoaderXpm.hh \ + PixmapHandler.cc PixmapHandler.hh \ + RegexString.cc RegexString.hh \ + ParseUtil.hh \ + PScreen.cc PScreen.hh \ + ScreenResources.cc ScreenResources.hh \ + StatusWindow.cc StatusWindow.hh \ + SearchDialog.cc SearchDialog.hh \ + Theme.cc Theme.hh \ + TextureHandler.cc TextureHandler.hh \ + Timer.hh Types.hh \ + Util.cc Util.hh \ + WORefMenu.cc WORefMenu.hh \ + WindowManager.cc WindowManager.hh \ + Workspaces.cc Workspaces.hh \ + WorkspaceIndicator.cc WorkspaceIndicator.hh \ + main.cc \ + pekwm.hh + +all: all-am + +.SUFFIXES: +.SUFFIXES: .cc .o .obj +$(srcdir)/Makefile.in: $(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) --foreign src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/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: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +pekwm$(EXEEXT): $(pekwm_OBJECTS) $(pekwm_DEPENDENCIES) + @rm -f pekwm$(EXEEXT) + $(CXXLINK) $(pekwm_OBJECTS) $(pekwm_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ActionHandler.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ActionMenu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Atoms.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/AutoProperties.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CfgParser.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CfgParserKey.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CfgParserSource.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Client.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CmdDialog.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ColorHandler.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Compat.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Completer.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Config.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DecorMenu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DockApp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FontHandler.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Frame.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FrameListMenu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Harbour.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HarbourMenu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ImageHandler.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/InputDialog.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/KeyGrabber.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ManagerWindows.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MenuHandler.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Observable.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PDecor.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PFont.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PImage.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PImageIcon.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PImageLoaderJpeg.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PImageLoaderPng.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PImageLoaderXpm.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PMenu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PScreen.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PTexturePlain.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PWinObj.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PWinObjReference.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PixmapHandler.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RegexString.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ScreenResources.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SearchDialog.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/StatusWindow.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TextureHandler.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Theme.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Util.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WORefMenu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WindowManager.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WorkspaceIndicator.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Workspaces.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ + +.cc.o: +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +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: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +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-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-local 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-binPROGRAMS + +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 + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-local distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-binPROGRAMS 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 pdf pdf-am ps ps-am \ + tags uninstall uninstall-am uninstall-binPROGRAMS + + +distclean-local: + rm -f *\~ .\#* + +# 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/pekwm/ManagerWindows.cc b/pekwm/ManagerWindows.cc new file mode 100644 index 0000000..360d457 --- /dev/null +++ b/pekwm/ManagerWindows.cc @@ -0,0 +1,532 @@ +// +// ManagerWindows.cc for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Config.hh" +#include "ActionHandler.hh" +#include "Atoms.hh" +#include "ManagerWindows.hh" +#include "Util.hh" + +#include + +extern "C" { +#include +} + +using std::cerr; +using std::endl; +using std::string; + +// Static initializers +const string HintWO::WM_NAME = string("pekwm"); +HintWO *HintWO::_instance = 0; +const unsigned int HintWO::DISPLAY_WAIT = 10; + +const unsigned long RootWO::EVENT_MASK = + StructureNotifyMask|PropertyChangeMask| + SubstructureNotifyMask|SubstructureRedirectMask| + ColormapChangeMask|FocusChangeMask|EnterWindowMask| + ButtonPressMask|ButtonReleaseMask|ButtonMotionMask; +const unsigned long RootWO::EXPECTED_DESKTOP_NAMES_LENGTH = 256; + +/** + * Hint window constructor, creates window and sets supported + * protocols. + */ +HintWO::HintWO(Window root, bool replace) throw (std::string&) + : PWinObj() +{ + if (_instance) { + throw string("trying to create HintWO which is already created."); + } + _instance = this; + + _type = WO_SCREEN_HINT; + setLayer(LAYER_NONE); + _sticky = true; // Hack, do not map/unmap this window + _iconified = true; // Hack, to be ignored when placing + + // Create window + _window = XCreateSimpleWindow(_dpy, root, -200, -200, 5, 5, 0, 0, 0); + + // Remove override redirect from window + XSetWindowAttributes attr; + attr.override_redirect = True; + attr.event_mask = PropertyChangeMask; + XChangeWindowAttributes(_dpy, _window, CWEventMask|CWOverrideRedirect, &attr); + + // Set hints not being updated + AtomUtil::setString(_window, Atoms::getAtom(NET_WM_NAME), WM_NAME); + AtomUtil::setWindow(_window, Atoms::getAtom(NET_SUPPORTING_WM_CHECK), _window); + + if (! claimDisplay(replace)) { + throw string("unable to claim display"); + } +} + +/** + * Hint WO destructor, destroy hint window. + */ +HintWO::~HintWO(void) +{ + XDestroyWindow(PScreen::instance()->getDpy(), _window); + _instance = 0; +} + +/** + * Get current time of server by generating an event and reading the + * timestamp on it. + * + * @return Time on server. + */ +Time +HintWO::getTime(void) +{ + XEvent event; + + // Generate event on ourselves + XChangeProperty(_dpy, _window, + Atoms::getAtom(WM_CLASS), Atoms::getAtom(STRING), + 8, PropModeAppend, 0, 0); + XWindowEvent(_dpy, _window, PropertyChangeMask, &event); + + return event.xproperty.time; +} + +/** + * Claim ownership over the current display. + * + * @param replace Replace current running window manager. + */ +bool +HintWO::claimDisplay(bool replace) +{ + bool status = true; + + // Get atom for the current screen and it's owner + string session_name("WM_S" + Util::to_string(DefaultScreen(_dpy))); + Atom session_atom = XInternAtom(_dpy, session_name.c_str(), false); + Window session_owner = XGetSelectionOwner(_dpy, session_atom); + + if (session_owner && session_owner != _window) { + if (! replace) { + cerr << " *** WARNING: window manager already running." << endl; + return false; + } + + XSync(_dpy, false); + setXErrorsIgnore(true); + uint errors_before = xerrors_count; + + // Select event to get notified when current owner dies. + XSelectInput(_dpy, session_owner, StructureNotifyMask); + + XSync(_dpy, false); + setXErrorsIgnore(false); + if (errors_before != xerrors_count) { + session_owner = None; + } + } + + Time timestamp = getTime(); + + XSetSelectionOwner(_dpy, session_atom, _window, timestamp); + if (XGetSelectionOwner(_dpy, session_atom) == _window) { + if (session_owner) { + // Wait for the previous window manager to go away and update owner. + status = claimDisplayWait(session_owner); + if (status) { + claimDisplayOwner(session_atom, timestamp); + } + } + } else { + cerr << "pekwm: unable to replace current window manager." << endl; + status = false; + } + + return status; +} + + +/** + * After claiming the display, wait for the previous window manager to + * go away. + */ +bool +HintWO::claimDisplayWait(Window session_owner) +{ + XEvent event; + + cerr << " *** INFO: waiting for previous window manager to exit. " << endl; + + for (uint waited = 0; waited < HintWO::DISPLAY_WAIT; ++waited) { + if (XCheckWindowEvent(_dpy, session_owner, StructureNotifyMask, &event) + && event.type == DestroyNotify) { + return true; + } + + sleep(1); + } + + cerr << " *** INFO: previous window manager did not exit. " << endl; + + return false; +} + +/** + * Send message updating the owner of the screen. + */ +void +HintWO::claimDisplayOwner(Window session_atom, Time timestamp) +{ + XEvent event; + // FIXME: One should use _root_wo here? + Window root = RootWindow(_dpy, DefaultScreen(_dpy)); + + event.xclient.type = ClientMessage; + event.xclient.message_type = Atoms::getAtom(MANAGER); + event.xclient.display = _dpy; + event.xclient.window = root; + event.xclient.format = 32; + event.xclient.data.l[0] = timestamp; + event.xclient.data.l[1] = session_atom; + event.xclient.data.l[2] = _window; + event.xclient.data.l[3] = 0; + + XSendEvent(_dpy, root, false, SubstructureNotifyMask, &event); +} + +/** + * Root window constructor, reads geometry and sets basic atoms. + */ +RootWO::RootWO(Window root) + : PWinObj() +{ + _type = WO_SCREEN_ROOT; + setLayer(LAYER_NONE); + _mapped = true; + + _window = root; + _gm.width = PScreen::instance()->getWidth(); + _gm.height = PScreen::instance()->getHeight(); + + + XSync(_dpy, false); + setXErrorsIgnore(true); + uint errors_before = xerrors_count; + + // Select window events + XSelectInput(_dpy, _window, RootWO::EVENT_MASK); + + XSync(_dpy, false); + setXErrorsIgnore(false); + if (errors_before != xerrors_count) { + cerr << "pekwm: root window unavailable, can't start!" << endl; + exit(1); + } + + // Set hits on the hint window, these are not updated so they are + // set in the constructor. + AtomUtil::setLong(_window, Atoms::getAtom(NET_WM_PID), static_cast(getpid())); + AtomUtil::setString(_window, Atoms::getAtom(WM_CLIENT_MACHINE), Util::getHostname()); + + AtomUtil::setWindow(_window, Atoms::getAtom(NET_SUPPORTING_WM_CHECK), HintWO::instance()->getWindow()); + Atoms::setEwmhAtomsSupport(_window); + AtomUtil::setLong(_window, Atoms::getAtom(NET_NUMBER_OF_DESKTOPS), Config::instance()->getWorkspaces()); + AtomUtil::setLong(_window, Atoms::getAtom(NET_CURRENT_DESKTOP), 0); + + long desktop_geometry[2] = { _gm.width, _gm.height }; + AtomUtil::setLongs(_window, Atoms::getAtom(NET_DESKTOP_GEOMETRY), desktop_geometry, 2); + + woListAdd(this); + _wo_map[_window] = this; +} + +/** + * Root window destructor, clears atoms set. + */ +RootWO::~RootWO(void) +{ + // Remove atoms, PID will not be valid on shutdown. + AtomUtil::unsetProperty(_window, Atoms::getAtom(NET_WM_PID)); + AtomUtil::unsetProperty(_window, Atoms::getAtom(WM_CLIENT_MACHINE)); + + _wo_map.erase(_window); + woListRemove(this); +} + +/** + * Button press event handler, gets actions from root list. + */ +ActionEvent* +RootWO::handleButtonPress(XButtonEvent *ev) +{ + return ActionHandler::findMouseAction(ev->button, ev->state, MOUSE_EVENT_PRESS, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_ROOT)); +} + +/** + * Button release event handler, gets actions from root list. + */ +ActionEvent* +RootWO::handleButtonRelease(XButtonEvent *ev) +{ + MouseEventType mb = MOUSE_EVENT_RELEASE; + + // first we check if it's a double click + if (PScreen::instance()->isDoubleClick(ev->window, ev->button - 1, ev->time, + Config::instance()->getDoubleClickTime())) { + PScreen::instance()->setLastClickID(ev->window); + PScreen::instance()->setLastClickTime(ev->button - 1, 0); + + mb = MOUSE_EVENT_DOUBLE; + + } else { + PScreen::instance()->setLastClickID(ev->window); + PScreen::instance()->setLastClickTime(ev->button - 1, ev->time); + } + + return ActionHandler::findMouseAction(ev->button, ev->state, mb, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_ROOT)); +} + +/** + * Motion event handler, gets actions from root list. + */ +ActionEvent* +RootWO::handleMotionEvent(XMotionEvent *ev) +{ + unsigned int button = PScreen::instance()->getButtonFromState(ev->state); + + return ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_ROOT)); +} + +/** + * Enter event handler, gets actions from root list. + */ +ActionEvent* +RootWO::handleEnterEvent(XCrossingEvent *ev) +{ + return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_ENTER, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_ROOT)); +} + +/** + * Leave event handler, gets actions from root list. + */ +ActionEvent* +RootWO::handleLeaveEvent(XCrossingEvent *ev) +{ + return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_LEAVE, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_ROOT)); +} + +/** + * Update _NET_WORKAREA property. + * + * @param workarea Geometry with work area. + */ +void +RootWO::setEwmhWorkarea(const Geometry &workarea) +{ + long workarea_array[4] = { workarea.x, workarea.y, workarea.width, workarea.height }; + AtomUtil::setLongs(_window, Atoms::getAtom(NET_WORKAREA), workarea_array, 4); +} + +/** + * Update _NET_ACTIVE_WINDOW property. + * + * @param win Window to set as active window. + */ +void +RootWO::setEwmhActiveWindow(Window win) +{ + AtomUtil::setWindow(PScreen::instance()->getRoot(), Atoms::getAtom(NET_ACTIVE_WINDOW), win); +} + +/** + * Reads the _NET_DESKTOP_NAMES hint and sets the workspaces names accordingly. + */ +void +RootWO::readEwmhDesktopNames(void) +{ + uchar *data; + ulong data_length; + if (AtomUtil::getProperty(PScreen::instance()->getRoot(), + Atoms::getAtom(NET_DESKTOP_NAMES), + Atoms::getAtom(UTF8_STRING), + EXPECTED_DESKTOP_NAMES_LENGTH, &data, &data_length)) { + Config::instance()->setDesktopNamesUTF8(reinterpret_cast(data), data_length); + + XFree(data); + } +} + +/** + * Update _NET_DESKTOP_NAMES property on the root window. + */ +void +RootWO::setEwmhDesktopNames(void) +{ + unsigned char *desktopnames = 0; + unsigned int length = 0; + Config::instance()->getDesktopNamesUTF8(&desktopnames, &length); + + if (desktopnames) { + AtomUtil::setUtf8StringArray(PScreen::instance()->getRoot(), + Atoms::getAtom(NET_DESKTOP_NAMES), desktopnames, length); + delete [] desktopnames; + } +} + + +/** + * Edge window constructor, create window, setup strut and register + * window. + */ +EdgeWO::EdgeWO(Window root, EdgeType edge, bool set_strut) + : PWinObj(), + _edge(edge) +{ + _type = WO_SCREEN_EDGE; + setLayer(LAYER_NONE); // hack, goes over LAYER_MENU + _sticky = true; // don't map/unmap + _iconified = true; // hack, to be ignored when placing + _focusable = false; // focusing input only windows crashes X + + XSetWindowAttributes sattr; + sattr.override_redirect = True; + sattr.event_mask = EnterWindowMask|LeaveWindowMask|ButtonPressMask|ButtonReleaseMask; + + _window = XCreateWindow(_dpy, root, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, + CWOverrideRedirect|CWEventMask, &sattr); + + configureStrut(set_strut); + PScreen::instance()->addStrut(&_strut); + + woListAdd(this); + _wo_map[_window] = this; +} + +/** + * Edge window destructor, remove strut and destroy window resources. + */ +EdgeWO::~EdgeWO(void) +{ + PScreen::instance()->removeStrut(&_strut); + _wo_map.erase(_window); + woListRemove(this); + + XDestroyWindow(_dpy, _window); +} + +/** + * Configure strut on edge window. + * + * @param set_strut If true, set actual values on the strut, false sets all to 0. + */ +void +EdgeWO::configureStrut(bool set_strut) +{ + // Reset value, on strut to zero. + _strut.left = _strut.right = _strut.top = _strut.bottom = 0; + + // Set strut if requested. + if (set_strut) { + switch (_edge) { + case SCREEN_EDGE_TOP: + _strut.top = _gm.height; + break; + case SCREEN_EDGE_BOTTOM: + _strut.bottom = _gm.height; + break; + case SCREEN_EDGE_LEFT: + _strut.left = _gm.width; + break; + case SCREEN_EDGE_RIGHT: + _strut.right = _gm.width; + break; + case SCREEN_EDGE_NO: + default: + // do nothing + break; + } + } +} + +/** + * Edge version of mapped window, makes sure the iconified state is + * set at all times in order to avoid counting the edge windows when + * snapping windows etc. + */ +void +EdgeWO::mapWindow(void) +{ + if (_mapped) { + return; + } + + PWinObj::mapWindow(); + _iconified = true; +} + +/** + * Enter event handler, gets actions from EdgeList on _edge. + */ +ActionEvent* +EdgeWO::handleEnterEvent(XCrossingEvent *ev) +{ + return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_ENTER, + Config::instance()->getEdgeListFromPosition(_edge)); +} + +/** + * Button press event handler, gets actions from EdgeList on _edge. + */ +ActionEvent* +EdgeWO::handleButtonPress(XButtonEvent *ev) +{ + return ActionHandler::findMouseAction(ev->button, ev->state, MOUSE_EVENT_PRESS, + Config::instance()->getEdgeListFromPosition(_edge)); +} + +/** + * Button release event handler, gets actions from EdgeList on _edge. + */ +ActionEvent* +EdgeWO::handleButtonRelease(XButtonEvent *ev) +{ + // Make sure the release is on the actual window. This probably + // could be done smarter. + if (ev->x_root < _gm.x || ev->x_root > static_cast(_gm.x + _gm.width) + || ev->y_root < _gm.y || ev->y_root > static_cast(_gm.y + _gm.height)) { + return 0; + } + + MouseEventType mb = MOUSE_EVENT_RELEASE; + + // first we check if it's a double click + if (PScreen::instance()->isDoubleClick(ev->window, ev->button - 1, ev->time, + Config::instance()->getDoubleClickTime())) { + PScreen::instance()->setLastClickID(ev->window); + PScreen::instance()->setLastClickTime(ev->button - 1, 0); + + mb = MOUSE_EVENT_DOUBLE; + } else { + PScreen::instance()->setLastClickID(ev->window); + PScreen::instance()->setLastClickTime(ev->button - 1, ev->time); + } + + return ActionHandler::findMouseAction(ev->button, ev->state, mb, + Config::instance()->getEdgeListFromPosition(_edge)); +} diff --git a/pekwm/ManagerWindows.hh b/pekwm/ManagerWindows.hh new file mode 100644 index 0000000..a85c11c --- /dev/null +++ b/pekwm/ManagerWindows.hh @@ -0,0 +1,104 @@ +// +// ManagerWindows.hh for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _MANAGER_WINDOWS_H_ +#define _MANAGER_WINDOWS_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" + +#include "PScreen.hh" +#include "PWinObj.hh" + +#include + +/** + * Window for handling of EWMH hints, sets supported attributes etc. + */ +class HintWO : public PWinObj +{ +public: + HintWO(Window root, bool replace) throw (std::string&); + virtual ~HintWO(void); + + inline static HintWO *instance(void) { return _instance; } + +private: + Time getTime(void); + bool claimDisplay(bool replace); + bool claimDisplayWait(Window session_owner); + void claimDisplayOwner(Window session_atom, Time timestamp); + +private: + static const std::string WM_NAME; /**< Name of the window manager, that is pekwm. */ + static HintWO *_instance; /**< Singleton HintWO pointer. */ + static const unsigned int DISPLAY_WAIT; /**< Max wait time for previous WM. */ +}; + +/** + * Window object representing the Root window, handles actions and + * sets atoms on the window. + */ +class RootWO : public PWinObj +{ +public: + RootWO(Window root); + virtual ~RootWO(void); + + /** Resize root window, does no actual resizing but updates the + geometry of the window. */ + virtual void resize(uint width, uint height) { + _gm.width = width; + _gm.height = height; + } + + virtual ActionEvent *handleButtonPress(XButtonEvent *ev); + virtual ActionEvent *handleButtonRelease(XButtonEvent *ev); + virtual ActionEvent *handleMotionEvent(XMotionEvent *ev); + virtual ActionEvent *handleEnterEvent(XCrossingEvent *ev); + virtual ActionEvent *handleLeaveEvent(XCrossingEvent *ev); + + void setEwmhWorkarea(const Geometry &workarea); + void setEwmhActiveWindow(Window win); + void readEwmhDesktopNames(void); + void setEwmhDesktopNames(void); + +private: + static const unsigned long EVENT_MASK; /**< Root window event mask. */ + static const unsigned long EXPECTED_DESKTOP_NAMES_LENGTH; /**< Expected length of desktop hint. */ +}; + +/** + * Window object used as a screen border, input only window that only + * handles actions. + */ +class EdgeWO : public PWinObj +{ +public: + EdgeWO(Window root, EdgeType edge, bool set_strut); + virtual ~EdgeWO(void); + + void configureStrut(bool set_strut); + + virtual void mapWindow(void); + + virtual ActionEvent *handleButtonPress(XButtonEvent *ev); + virtual ActionEvent *handleButtonRelease(XButtonEvent *ev); + virtual ActionEvent *handleEnterEvent(XCrossingEvent *ev); + + inline EdgeType getEdge(void) const { return _edge; } + +private: + EdgeType _edge; /**< Edge position. */ + Strut _strut; /*< Strut for reserving screen edge space. */ +}; + +#endif // _MANAGER_WINDOWS_H_ diff --git a/pekwm/MenuHandler.cc b/pekwm/MenuHandler.cc new file mode 100644 index 0000000..85b47b4 --- /dev/null +++ b/pekwm/MenuHandler.cc @@ -0,0 +1,294 @@ +// +// MenuHandler.cc for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include + +#include "PWinObj.hh" +#include "PMenu.hh" +#include "PScreen.hh" +#include "MenuHandler.hh" +#include "ActionHandler.hh" + +#include "WORefMenu.hh" +#include "ActionMenu.hh" +#include "FrameListMenu.hh" +#include "DecorMenu.hh" + +using std::map; +using std::string; + +MenuHandler *MenuHandler::_instance = 0; + +/** + * List of reserved names of built-in menus. + */ +const char *MenuHandler::MENU_NAMES_RESERVED[] = { + "ATTACHCLIENTINFRAME", + "ATTACHCLIENT", + "ATTACHFRAMEINFRAME", + "ATTACHFRAME", + "DECORMENU", + "GOTOCLIENT", + "GOTO", + "ICON", + "ROOTMENU", + "ROOT", // To avoid name conflict, ROOTMENU -> ROOT + "WINDOWMENU", + "WINDOW" // To avoid name conflict, WINDOWMENU -> WINDOW +}; + +const unsigned int MenuHandler::MENU_NAMES_RESERVED_COUNT = + sizeof(MenuHandler::MENU_NAMES_RESERVED) + / sizeof(MenuHandler::MENU_NAMES_RESERVED[0]); + +/** + * Comparsion for binary search + */ +bool +str_comparator(const string &lhs, const string &rhs) { + return strcasecmp(lhs.c_str(), rhs.c_str()) < 0; +} + +/** + * Initialize menu handler instance. + */ +void +MenuHandler::init(Theme *theme) +{ + if (_instance) { + delete _instance; + } + _instance = new MenuHandler(theme); +} + +/** + * Destroy menu handler instance. + */ +void +MenuHandler::destroy(void) +{ + delete _instance; + _instance = 0; +} + +/** + * Menu handler constructor, create menus. + * + * Requires Config, PScreen and ActionHandler to be constructed. + */ +MenuHandler::MenuHandler(Theme *theme) + : _theme(theme) +{ + createMenus(); +} + +/** + * Menu handler destructor, delete menus. + */ +MenuHandler::~MenuHandler(void) +{ + deleteMenus(); +} + +/** + * Hides all menus + */ +void +MenuHandler::hideAllMenus(void) +{ + map::iterator it(_menu_map.begin()); + for (; it != _menu_map.end(); ++it) { + it->second->unmapAll(); + } +} + +/** + * Creates reserved menus and populates _menu_map + */ +void +MenuHandler::createMenus(void) +{ + PScreen *screen = PScreen::instance(); + ActionHandler *action_handler = ActionHandler::instance(); + PMenu *menu = 0; + + menu = new FrameListMenu(screen, _theme, ATTACH_CLIENT_IN_FRAME_TYPE, + L"Attach Client In Frame", + "AttachClientInFrame"); + _menu_map["ATTACHCLIENTINFRAME"] = menu; + menu = new FrameListMenu(screen, _theme, ATTACH_CLIENT_TYPE, + L"Attach Client", "AttachClient"); + _menu_map["ATTACHCLIENT"] = menu; + menu = new FrameListMenu(screen, _theme, ATTACH_FRAME_IN_FRAME_TYPE, + L"Attach Frame In Frame", + "AttachFrameInFrame"); + _menu_map["ATTACHFRAMEINFRAME"] = menu; + menu = new FrameListMenu(screen, _theme, ATTACH_FRAME_TYPE, + L"Attach Frame", "AttachFrame"); + _menu_map["ATTACHFRAME"] = menu; + menu = new FrameListMenu(screen, _theme, GOTOCLIENTMENU_TYPE, + L"Focus Client", "GotoClient"); + _menu_map["GOTOCLIENT"] = menu; + menu = new FrameListMenu(screen, _theme, GOTOMENU_TYPE, + L"Focus Frame", "Goto"); + _menu_map["GOTO"] = menu; + menu = new FrameListMenu(screen, _theme, ICONMENU_TYPE, + L"Focus Iconified Frame", "Icon"); + _menu_map["ICON"] = menu; + menu = new DecorMenu(screen, _theme, action_handler, "DecorMenu"); + _menu_map["DECORMENU"] = menu; + menu = new ActionMenu(ROOTMENU_TYPE, L"", "RootMenu"); + _menu_map["ROOT"] = menu; + menu = new ActionMenu(WINDOWMENU_TYPE, L"", "WindowMenu"); + _menu_map["WINDOW"] = menu; + + // As the previous step is done manually, make sure it's done correct. + assert(_menu_map.size() == (MENU_NAMES_RESERVED_COUNT - 2)); + + createMenusLoadConfiguration(); +} + +/** + * Initial load of menu configuration. + */ +void +MenuHandler::createMenusLoadConfiguration(void) +{ + // Load configuration, pass specific section to loading + CfgParser menu_cfg; + if (menu_cfg.parse(Config::instance()->getMenuFile()) + || menu_cfg.parse (string(SYSCONFDIR "/menu"))) { + _menu_state = menu_cfg.get_file_list(); + CfgParser::Entry *root_entry = menu_cfg.get_entry_root(); + + // Load standard menus + map::iterator it = _menu_map.begin(); + for (; it != _menu_map.end(); ++it) { + it->second->reload(root_entry->find_section(it->second->getName())); + } + + // Load standalone menus + reloadStandaloneMenus(menu_cfg.get_entry_root()); + } +} + +/** + * (re)loads the menus in the menu configuration if the file has been + * updated since last load. + */ +void +MenuHandler::reloadMenus(void) +{ + string menu_file(Config::instance()->getMenuFile()); + if (! Util::requireReload(_menu_state, menu_file)) { + return; + } + + CfgParser cfg; + bool cfg_ok = loadMenuConfig(menu_file, cfg); + CfgParser::Entry *root = cfg.get_entry_root(); + + // Update, delete standalone root menus, load decors on others + map::iterator it(_menu_map.begin()); + for (; it != _menu_map.end(); ++it) { + if (it->second->getMenuType() == ROOTMENU_STANDALONE_TYPE) { + delete it->second; + _menu_map.erase(it); + } else if (cfg_ok) { + // Only reload the menu if we got a ok configuration + it->second->reload(root->find_section(it->second->getName())); + } + } + + // Update standalone root menus (name != ROOTMENU) + reloadStandaloneMenus(root); +} + +/** + * Load menu configuration from menu_file resetting menu state. + */ +bool +MenuHandler::loadMenuConfig(const std::string &menu_file, CfgParser &menu_cfg) +{ + bool cfg_ok = true; + + if (! menu_cfg.parse(menu_file)) { + if (! menu_cfg.parse(string(SYSCONFDIR "/menu"))) { + cfg_ok = false; + } + } + + // Make sure menu is reloaded next time as content is dynamically + // generated from the configuration file. + if (! cfg_ok || menu_cfg.is_dynamic_content()) { + _menu_state.clear(); + } else { + _menu_state = menu_cfg.get_file_list(); + } + + return cfg_ok; +} + +/** + * Updates standalone root menus + */ +void +MenuHandler::reloadStandaloneMenus(CfgParser::Entry *section) +{ + // Temporary name, as names are stored uppercase + string menu_name, menu_name_upper; + + // Go through all but reserved section names and create menus + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + // Uppercase name + menu_name = (*it)->get_name(); + menu_name_upper = menu_name; + Util::to_upper(menu_name_upper); + + // Create new menus, if the name is not reserved and not used + if (! isReservedName(menu_name_upper) && ! getMenu(menu_name_upper)) { + // Create, parse and add to map + PMenu *menu = new ActionMenu(ROOTMENU_STANDALONE_TYPE, + L"", menu_name); + menu->reload((*it)->get_section()); + _menu_map[menu_name_upper] = menu; + } + } +} + +/** + * Clears the menu map and frees up resources used by menus + */ +void +MenuHandler::deleteMenus(void) +{ + map::iterator it(_menu_map.begin()); + for (; it != _menu_map.end(); ++it) { + delete it->second; + } + _menu_map.clear(); +} + +/** + * Check if name is reserved, return true if it is. + */ +bool +MenuHandler::isReservedName(const std::string &name) +{ + return binary_search(MENU_NAMES_RESERVED, + MENU_NAMES_RESERVED + MENU_NAMES_RESERVED_COUNT, + name, str_comparator); +} + diff --git a/pekwm/MenuHandler.hh b/pekwm/MenuHandler.hh new file mode 100644 index 0000000..825089f --- /dev/null +++ b/pekwm/MenuHandler.hh @@ -0,0 +1,75 @@ +// +// MenuHandler.hh for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include + +#include "CfgParser.hh" +#include "PMenu.hh" +#include "Theme.hh" + +/** + * Menu manager, creates, reloads and delete menus. + */ +class MenuHandler { +public: + static void init(Theme *theme); + static void destroy(void); + static MenuHandler *instance(void) { return _instance; } + + PMenu *getMenu(const std::string &name) { + std::map::iterator it = _menu_map.find(name); + return (it != _menu_map.end()) ? it->second : 0; + } + + /** + * Return list with names of loaded menus. + */ + std::list getMenuNames(void) { + std::list menu_names; + std::map::iterator it(_menu_map.begin()); + for (; it != _menu_map.end(); ++it) { + menu_names.push_back(it->second->getName()); + } + return menu_names; + } + + void hideAllMenus(void); + void reloadMenus(void); + +private: + MenuHandler(Theme *theme); + MenuHandler(MenuHandler &menu_handler); + ~MenuHandler(void); + + bool loadMenuConfig(const std::string &menu_file, CfgParser &menu_cfg); + + void createMenus(void); + void createMenusLoadConfiguration(void); + void deleteMenus(void); + + void reloadStandaloneMenus(CfgParser::Entry *section); + + static bool isReservedName(const std::string &name); + +private: + Theme *_theme; /**< Theme in use. */ + + std::map _menu_state; /**< Map of file mtime for all files touched by a configuration. */ + std::map _menu_map; /**< Map from menu name to menu */ + + static MenuHandler *_instance; /**< Instance pointer for MenuHandler. */ + + static const char *MENU_NAMES_RESERVED[]; + static const unsigned int MENU_NAMES_RESERVED_COUNT; +}; diff --git a/pekwm/Observable.cc b/pekwm/Observable.cc new file mode 100644 index 0000000..fa24edf --- /dev/null +++ b/pekwm/Observable.cc @@ -0,0 +1,50 @@ +// +// Observable.cc for pekwm +// Copyright © 2009 Claes Nästen +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Observable.hh" +#include "Observer.hh" + +using SLIST_NAMESPACE::slist; + +/** + * Notify all observers. + */ +void +Observable::notifyObservers(Observation *observation) +{ + if (_observers.size()) { + slist::iterator it(_observers.begin()); + for (; it != _observers.end(); ++it) { + (*it)->notify(this, observation); + } + } +} + +/** + * Add observer. + */ +void +Observable::addObserver(Observer *observer) +{ + _observers.push_front(observer); +} + +/** + * Remove observer from list. + */ +void +Observable::removeObserver(Observer *observer) +{ + if (_observers.size()) { + _observers.remove(observer); + } +} diff --git a/pekwm/Observable.hh b/pekwm/Observable.hh new file mode 100644 index 0000000..07df9db --- /dev/null +++ b/pekwm/Observable.hh @@ -0,0 +1,42 @@ +// +// Observable.hh for pekwm +// Copyright © 2009 Claes Nästen +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _OBSERVABLE_HH_ +#define _OBSERVABLE_HH_ + +#ifdef HAVE_SLIST +#include +#else // HAVE_EXT_SLIST +#include +#endif // HAVE_SLIST + +class Observer; + +/** + * Message sent to observer. + */ +class Observation { +public: + virtual ~Observation(void) { }; +}; + +class Observable { +public: + Observable(void) { } + virtual ~Observable(void) { } + + void notifyObservers(Observation *observation); + + void addObserver(Observer *observer); + void removeObserver(Observer *observer); + +private: + SLIST_NAMESPACE::slist _observers; /**< List of observers. */ +}; + +#endif // _OBSERVABLE_HH_ diff --git a/pekwm/Observer.hh b/pekwm/Observer.hh new file mode 100644 index 0000000..b3ae469 --- /dev/null +++ b/pekwm/Observer.hh @@ -0,0 +1,27 @@ +// +// Observer.hh for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _OBSERVER_HH_ +#define _OBSERVER_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +class Observable; +class Observation; + +class Observer { +public: + Observer(void) { } + virtual ~Observer(void) { } + + virtual void notify(Observable *observable, Observation *observation) { } +}; + +#endif // _OBSERVER_HH_ diff --git a/pekwm/PDecor.cc b/pekwm/PDecor.cc new file mode 100644 index 0000000..f07e686 --- /dev/null +++ b/pekwm/PDecor.cc @@ -0,0 +1,2401 @@ +// +// PDecor.cc for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#ifdef HAVE_SHAPE +#include +#endif // HAVE_SHAPE +} + +#include "Compat.hh" +#include "Config.hh" +#include "PWinObj.hh" +#include "PFont.hh" +#include "PDecor.hh" +#include "PScreen.hh" +#include "PTexture.hh" +#include "PTexturePlain.hh" // PTextureSolid +#include "ActionHandler.hh" +#include "ScreenResources.hh" +#include "StatusWindow.hh" +#include "KeyGrabber.hh" +#include "Theme.hh" +#include "PixmapHandler.hh" +#include "Workspaces.hh" + +using std::cerr; +using std::endl; +using std::find; +using std::list; +using std::map; +using std::mem_fun; +using std::string; +using std::vector; +using std::swprintf; + +// PDecor::Button + +//! @brief PDecor::Button constructor +PDecor::Button::Button(PWinObj *parent, Theme::PDecorButtonData *data, uint width, uint height) + : PWinObj(), + _data(data), _state(BUTTON_STATE_UNFOCUSED), + _left(_data->isLeft()) +{ + _parent = parent; + + _gm.width = width; + _gm.height = height; + + XSetWindowAttributes attr; + attr.event_mask = EnterWindowMask|LeaveWindowMask; + attr.override_redirect = True; + _window = + XCreateWindow(_dpy, _parent->getWindow(), + -_gm.width, -_gm.height, _gm.width, _gm.height, 0, + CopyFromParent, InputOutput, CopyFromParent, + CWEventMask|CWOverrideRedirect, &attr); + + _bg = ScreenResources::instance()->getPixmapHandler()->getPixmap(_gm.width, _gm.height, PScreen::instance()->getDepth()); + + setBackgroundPixmap(_bg); + setState(_state); +} + +//! @brief PDecor::Button destructor +PDecor::Button::~Button(void) +{ + XDestroyWindow(_dpy, _window); + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_bg); +} + +//! @brief Searches the PDecorButtonData for an action matching ev +ActionEvent* +PDecor::Button::findAction(XButtonEvent *ev) +{ + list::iterator it(_data->begin()); + for (; it != _data->end(); ++it) { + if (it->mod == ev->state && it->sym == ev->button) + return &*it; + } + return 0; +} + +//! @brief Sets the state of the button +void +PDecor::Button::setState(ButtonState state) +{ + if (state == BUTTON_STATE_NO) { + return; + } + + // Only update if we don't hoover, we want to be able to turn back + if (state != BUTTON_STATE_HOVER) { + _state = state; + } + + PTexture *texture = _data->getTexture(state); + if (texture) { + texture->render(_bg, 0, 0, _gm.width, _gm.height); + +#ifdef HAVE_SHAPE + // Get shape mask + bool need_free; + Pixmap shape = _data->getTexture(state)->getMask(0, 0, need_free); + if (shape != None) { + XShapeCombineMask(_dpy, _window, ShapeBounding, 0, 0, shape, ShapeSet); + if (need_free) { + ScreenResources::instance()->getPixmapHandler()->returnPixmap(shape); + } + } else { + XRectangle rect = {0 /* x */, 0 /* y */, _gm.width, _gm.height }; + XShapeCombineRectangles(_dpy, _window, ShapeBounding, + 0, 0, &rect, 1, ShapeSet, YXBanded); + } +#endif // HAVE_SHAPE + clear(); + } +} + +//! @brief Update visible version of title +void +PDecor::TitleItem::updateVisible(void) { + // Start with empty string + _visible = L""; + + // Add client info to title + if ((_info != 0) + && ((_info != INFO_ID) || Config::instance()->isShowClientID())) { + _visible.append(L"["); + + if (infoIs(INFO_ID) && Config::instance()->isShowClientID()) { + _visible.append(Util::to_wide_str(Util::to_string(_id))); + } + if (infoIs(INFO_MARKED)) { + _visible.append(L"M"); + } + + _visible.append(L"] "); + } + + // Add title + if (_user.size() > 0) { + _visible.append(_user); + } else if (_custom.size() > 0) { + _visible.append(_custom); + } else { + _visible.append(_real); + } + + // Add client number to title + if (_count > 0) { + _visible.append(Util::to_wide_str(Config::instance()->getClientUniqueNamePre())); + _visible.append(Util::to_wide_str(Util::to_string(_count))); + _visible.append(Util::to_wide_str(Config::instance()->getClientUniqueNamePost())); + } +} + +// PDecor + +const string PDecor::DEFAULT_DECOR_NAME = string("DEFAULT"); +const string PDecor::DEFAULT_DECOR_NAME_BORDERLESS = string("BORDERLESS"); +const string PDecor::DEFAULT_DECOR_NAME_TITLEBARLESS = string("TITLEBARLESS"); + +list PDecor::_pdecor_list = list(); + +//! @brief PDecor constructor +//! @param dpy Display +//! @param theme Theme +//! @param decor_name String, if not DEFAULT_DECOR_NAME sets _decor_name_override +PDecor::PDecor(Theme *theme, + const std::string decor_name, const Window child_window) + : PWinObj(), + _theme(theme),_decor_name(decor_name), + _child(0), _button(0), _button_press_win(None), + _pointer_x(0), _pointer_y(0), + _click_x(0), _click_y(0), + _decor_cfg_keep_empty(false), _decor_cfg_child_move_overloaded(false), + _decor_cfg_bpr_replay_pointer(false), + _decor_cfg_bpr_al_child(MOUSE_ACTION_LIST_CHILD_OTHER), + _decor_cfg_bpr_al_title(MOUSE_ACTION_LIST_TITLE_OTHER), + _maximized_vert(false), _maximized_horz(false), + _fullscreen(false), _skip(0), _data(0), + _border(true), _titlebar(true), _shaded(false), + _need_shape(false), _need_client_shape(false), + _dirty_resized(true), _real_height(1), + _title_wo(), _title_bg(None), + _title_active(0), _titles_left(0), _titles_right(1) +{ + if (_decor_name != PDecor::DEFAULT_DECOR_NAME) { + _decor_name_override = _decor_name; + } + + // we be reset in loadDecor later on, inlines using the _data used before + // loadDecor needs this though + _data = _theme->getPDecorData(_decor_name); + if (! _data) { + _data = _theme->getPDecorData(DEFAULT_DECOR_NAME); + } + + CreateWindowParams window_params; + getParentWindowAttributes(window_params, child_window); + createParentWindow(window_params); + if (window_params.mask & CWColormap) { + window_params.depth = PScreen::instance()->getDepth(); + window_params.visual = PScreen::instance()->getVisual()->getXVisual(); + window_params.attr.colormap = PScreen::instance()->getColormap(); + } + createTitle(window_params); + createBorder(window_params); + + // sets buttons etc up + loadDecor(); + + // map title and border windows + XMapSubwindows(_dpy, _window); + + _pdecor_list.push_back(this); +} + +/** + * Create window attributes + */ +void +PDecor::getParentWindowAttributes(CreateWindowParams ¶ms, + Window child_window) +{ + params.mask = CWOverrideRedirect|CWEventMask|CWBorderPixel|CWBackPixel; + params.depth = CopyFromParent; + params.visual = CopyFromParent; + params.attr.override_redirect = True; + params.attr.border_pixel = 0; + params.attr.background_pixel = 0; + + if (child_window != None) { + getChildWindowAttributes(params, child_window); + } +} + +/** + * Get window attributes from child window + */ +void +PDecor::getChildWindowAttributes(CreateWindowParams ¶ms, + Window child_window) +{ + XWindowAttributes attr; + Status status = XGetWindowAttributes(_dpy, child_window, &attr); + if (status != BadDrawable && status != BadWindow && attr.depth == 32) { + params.mask |= CWColormap; + params.depth = attr.depth; + params.visual = attr.visual; + params.attr.colormap = XCreateColormap(_dpy, + PScreen::instance()->getRoot(), + params.visual, AllocNone); + } +} + +/** + * Create container window. + */ +void +PDecor::createParentWindow(CreateWindowParams ¶ms) +{ + params.attr.event_mask = ButtonPressMask|ButtonReleaseMask| + ButtonMotionMask|EnterWindowMask|SubstructureRedirectMask| + SubstructureNotifyMask; + _window = XCreateWindow(_dpy, PScreen::instance()->getRoot(), + _gm.x, _gm.y, _gm.width, _gm.height, 0, + params.depth, InputOutput, params.visual, + params.mask, ¶ms.attr); +} + +/** + * Create title window. + */ +void +PDecor::createTitle(CreateWindowParams ¶ms) +{ + params.attr.event_mask = ButtonPressMask|ButtonReleaseMask| + ButtonMotionMask|EnterWindowMask; + Window title = XCreateWindow(_dpy, _window, + borderLeft(), borderTop(), 1, 1, 0, + params.depth, InputOutput, params.visual, + params.mask, ¶ms.attr); + _title_wo.setWindow(title); + addChildWindow(_title_wo.getWindow()); +} + +/** + * Create border windows + */ +void +PDecor::createBorder(CreateWindowParams ¶ms) +{ + params.attr.event_mask = ButtonPressMask|ButtonReleaseMask| + ButtonMotionMask|EnterWindowMask; + + ScreenResources *sr = ScreenResources::instance(); + for (uint i = 0; i < BORDER_NO_POS; ++i) { + params.attr.cursor = sr->getCursor(ScreenResources::CursorType(i)); + + _border_win[i] = + XCreateWindow(_dpy, _window, -1, -1, 1, 1, 0, + params.depth, InputOutput, params.visual, + params.mask|CWCursor, ¶ms.attr); + _border_pos_map[BorderPosition(i)] = None; + addChildWindow(_border_win[i]); + } +} + +//! @brief PDecor destructor +PDecor::~PDecor(void) +{ + _pdecor_list.remove(this); + + if (_child_list.size() > 0) { + while (_child_list.size() != 0) { + removeChild(_child_list.back(), false); // Don't call delete this. + } + } + + // Make things look smoother, buttons will be noticed as deleted + // otherwise. Using X call directly to avoid re-drawing and other + // special features not required when removing the window. + XUnmapWindow(_dpy, _window); + + // free buttons + unloadDecor(); + + // free border pixmaps + map::iterator it(_border_pos_map.begin()); + for (; it != _border_pos_map.end(); ++it) { + if (it->second != None) { + ScreenResources::instance()->getPixmapHandler()->returnPixmap(it->second); + } + } + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_title_bg); + + removeChildWindow(_title_wo.getWindow()); + XDestroyWindow(_dpy, _title_wo.getWindow()); + for (uint i = 0; i < BORDER_NO_POS; ++i) { + removeChildWindow(_border_win[i]); + XDestroyWindow(_dpy, _border_win[i]); + } + XDestroyWindow(_dpy, _window); +} + +// START - PWinObj interface. + +//! @brief Map decor and all children +void +PDecor::mapWindow(void) +{ + if (! _mapped) { + PWinObj::mapWindow(); + for_each(_child_list.begin(), _child_list.end(), + mem_fun(&PWinObj::mapWindow)); + } +} + +//! @brief Maps the window raised +void +PDecor::mapWindowRaised(void) +{ + if (_mapped) { + return; + } + + mapWindow(); + + raise(); // XMapRaised wouldn't preserver layers +} + +//! @brief Unmaps decor and all children +void +PDecor::unmapWindow(void) +{ + if (_mapped) { + if (_iconified) { + for_each(_child_list.begin(), _child_list.end(), + mem_fun(&PWinObj::iconify)); + } else { + for_each(_child_list.begin(), _child_list.end(), + mem_fun(&PWinObj::unmapWindow)); + } + PWinObj::unmapWindow(); + } +} + +//! @brief Moves the decor +void +PDecor::move(int x, int y) +{ + // update real position + PWinObj::move(x, y); + if (_child && (_decor_cfg_child_move_overloaded)) { + _child->move(x + borderLeft(), y + borderTop() + getTitleHeight()); + } +} + +//! @brief Resizes the decor, and active child if any +void +PDecor::resize(uint width, uint height) +{ + // If shaded, don't resize to the size specified just update width + // and set _real_height to height + if (_shaded) { + _real_height = height; + + height = getTitleHeight(); + // Shading in non full width title mode will make border go away + if (! _data->getTitleWidthMin()) { + height += borderTop() + borderBottom(); + } + } + + PWinObj::resize(width, height); + + // Update size before moving and shaping the rest as shaping + // depends on the child window + if (_child) { + _child->resize(getChildWidth(), getChildHeight()); + } + + // place / resize title and border + resizeTitle(); + placeBorder(); + + // render title and border + _dirty_resized = true; + + // Set and apply shape on window, all parts of the border can now + // be shaped. + setBorderShape(); + applyBorderShape(); + + renderTitle(); + renderBorder(); + + _dirty_resized = false; +} + +//! @brief Move and resize window. +void +PDecor::moveResize(int x, int y, uint width, uint height) +{ + // If shaded, don't resize to the size specified just update width + // and set _real_height to height + if (_shaded) { + _real_height = height; + + height = getTitleHeight(); + // Shading in non full width title mode will make border go away + if (! _data->getTitleWidthMin()) { + height += borderTop() + borderBottom(); + } + } + + PWinObj::moveResize(x, y, width, height); + + // Update size before moving and shaping the rest as shaping + // depends on the child window + if (_child) { + _child->moveResize(x + borderLeft(), y + borderTop() + getTitleHeight(), + getChildWidth(), getChildHeight()); + + // The client window may have its window gravity set to something different + // than NorthWestGravity (see Xlib manual chapter 3.2.3). Therefore the + // X server may not move its top left corner along with the decoration. + // We correct these cases by calling alignChild(). It is called only for + // _child and not all members of _child_list, because activateChild.*() + // does it itself. + alignChild(_child); + } + + // Place and resize title and border + resizeTitle(); + placeBorder(); + + // render title and border + _dirty_resized = true; + + // Apply shape on window, all parts of the border can now be shaped. + setBorderShape(); + applyBorderShape(); + + renderTitle(); + renderBorder(); + + _dirty_resized = false; +} + +//! @brief +void +PDecor::resizeTitle(void) +{ + if (getTitleHeight()) { + _title_wo.resize(calcTitleWidth(), getTitleHeight()); + calcTabsWidth(); + } + + // place buttons, also updates title information + placeButtons(); +} + +//! @brief Raises the window, taking _layer into account +void +PDecor::raise(void) +{ + Workspaces::instance()->raise(this); + Workspaces::instance()->updateClientStackingList(); +} + +//! @brief Lowers the window, taking _layer into account +void +PDecor::lower(void) +{ + Workspaces::instance()->lower(this); + Workspaces::instance()->updateClientStackingList(); +} + +//! @brief +void +PDecor::setFocused(bool focused) +{ + if (_focused != focused) { // save repaints + PWinObj::setFocused(focused); + + renderTitle(); + renderButtons(); + + renderBorder(); + setBorderShape(); + applyBorderShape(); + } +} + +//! @brief +void +PDecor::setWorkspace(uint workspace) +{ + if (workspace != NET_WM_STICKY_WINDOW) { + if (workspace >= Workspaces::instance ()->size ()) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PDecor(" << this << ")::setWorkspace(" << workspace << ")" + << endl << " *** workspace > number of workspaces:" + << Workspaces::instance ()->size () << endl; +#endif // DEBUG + workspace = Workspaces::instance()->size() - 1; + } + _workspace = workspace; + } + + list::iterator it (_child_list.begin ()); + for (; it != _child_list.end (); ++it) { + (*it)->setWorkspace (workspace); + } + + if (! _mapped && ! _iconified) { + if (_sticky || (_workspace == Workspaces::instance()->getActive())) { + mapWindow(); + } + } else if (! _sticky && (_workspace != Workspaces::instance()->getActive())) { + unmapWindow(); + } +} + +//! @brief Gives decor input focus, fails if not mapped or not visible +void +PDecor::giveInputFocus(void) +{ + if (_mapped && _child) { + _child->giveInputFocus(); + } else { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PDecor::giveInputFocus(" << this << ")" << endl << " *** reverting to root" << endl; +#endif // DEBUG + PWinObj::getRootPWinObj()->giveInputFocus(); + } +} + +/** + * Handle button press events. + */ +ActionEvent* +PDecor::handleButtonPress(XButtonEvent *ev) +{ + ActionEvent *ae = 0; + list *actions = 0; + + // Remove state modifiers from event + PScreen::stripStateModifiers(&ev->state); + PScreen::stripButtonModifiers(&ev->state); + + // Try to do something about frame buttons + if (ev->subwindow != None && (_button = findButton(ev->subwindow)) != 0) { + ae = handleButtonPressButton(ev, _button); + + } else { + // Record position, used in the motion handler when doing a window move. + _click_x = _gm.x; + _click_y = _gm.y; + _pointer_x = ev->x_root; + _pointer_y = ev->y_root; + + // Allow us to get clicks from anywhere on the window. + if (_decor_cfg_bpr_replay_pointer) { + XAllowEvents(_dpy, ReplayPointer, CurrentTime); + } + + if (ev->window == _child->getWindow() + || (ev->state == 0 && ev->subwindow == _child->getWindow())) { + // Clicks on the child window + // NOTE: If the we're matching against the subwindow we need to make + // sure that the state is 0, meaning we didn't have any modifier + // because if we don't care about the modifier we'll get two actions + // performed when using modifiers. + _button_press_win = _child->getWindow(); + actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_child); + + } else if (_title_wo == ev->window) { + // Clicks on the decor title + _button_press_win = ev->window; + actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_title); + + } else { + // Clicks on the decor border, default case. Try both window and sub-window. + uint pos = getBorderPosition(ev->window); + if (pos != BORDER_NO_POS) { + _button_press_win = ev->window; + actions = Config::instance()->getBorderListFromPosition(pos); + } + } + } + + if (! ae && actions) { + ae = ActionHandler::findMouseAction(ev->button, ev->state, MOUSE_EVENT_PRESS, actions); + } + + return ae; +} + +/** + * Handle button press events pressing decor buttons. + */ +ActionEvent* +PDecor::handleButtonPressButton(XButtonEvent *ev, PDecor::Button *button) +{ + // Keep track of pressed button. + _button->setState(BUTTON_STATE_PRESSED); + + ActionEvent *ae = _button->findAction(ev); + + // if the button is used for resizing, we don't want to wait for release + if (ae && ae->isOnlyAction(ACTION_RESIZE)) { + _button->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED); + _button = 0; + } else { + ae = 0; + } + + return ae; +} + +/** + * Handle button release events. + */ +ActionEvent* +PDecor::handleButtonRelease(XButtonEvent *ev) +{ + ActionEvent *ae = 0; + list *actions = 0; + MouseEventType mb = MOUSE_EVENT_RELEASE; + + // Remove state modifiers from event + PScreen::stripStateModifiers(&ev->state); + PScreen::stripButtonModifiers(&ev->state); + + // handle titlebar buttons + if (_button) { + ae = handleButtonReleaseButton(ev, _button); + + } else { + // Allow us to get clicks from anywhere on the window. + if (_decor_cfg_bpr_replay_pointer) { + XAllowEvents(_dpy, ReplayPointer, CurrentTime); + } + + // clicks on the child window + if (ev->window == _child->getWindow() + || (ev->state == 0 && ev->subwindow == _child->getWindow())) { + // NOTE: If the we're matching against the subwindow we need to make + // sure that the state is 0, meaning we didn't have any modifier + // because if we don't care about the modifier we'll get two actions + // performed when using modifiers. + if (_button_press_win == _child->getWindow()) { + actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_child); + } + + } else if (_title_wo == ev->window) { + if (_button_press_win == ev->window) { + // Handle clicks on the decor title, checking double clicks first. + if (PScreen::instance()->isDoubleClick(ev->window, ev->button - 1, ev->time, + Config::instance()->getDoubleClickTime())) { + PScreen::instance()->setLastClickID(ev->window); + PScreen::instance()->setLastClickTime(ev->button - 1, 0); + mb = MOUSE_EVENT_DOUBLE; + } else { + PScreen::instance()->setLastClickID(ev->window); + PScreen::instance()->setLastClickTime(ev->button - 1, ev->time); + } + + actions = Config::instance()->getMouseActionList(_decor_cfg_bpr_al_title); + } + } else { + // Clicks on the decor border, check subwindow then window. + uint pos = getBorderPosition(ev->window); + if (pos != BORDER_NO_POS && (_button_press_win == ev->window)) { + actions = Config::instance()->getBorderListFromPosition(pos); + } + } + } + + if (! ae && actions) { + ae = ActionHandler::findMouseAction(ev->button, ev->state, mb, actions); + } + + return ae; +} + +/** + * Handle button release events when button is in pressed state. + */ +ActionEvent* +PDecor::handleButtonReleaseButton(XButtonEvent *ev, PDecor::Button *button) +{ + // First restore the pressed buttons state + _button->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED); + + ActionEvent *ae = 0; + + // Then see if the button was released over ( to execute an action ) + if (*_button == ev->subwindow) { + ae = _button->findAction(ev); + + // This is a little hack, resizing isn't wanted on both press and release + if (ae && ae->isOnlyAction(ACTION_RESIZE)) { + ae = 0; + } + } + + _button = 0; + + return ae; +} + +//! @brief +ActionEvent* +PDecor::handleMotionEvent(XMotionEvent *ev) +{ + uint button = PScreen::instance()->getButtonFromState(ev->state); + return ActionHandler::findMouseAction(button, ev->state, + MOUSE_EVENT_MOTION, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_OTHER)); +} + +/** + * Handle enter event, find action and toggle hoover state if enter + * was on a button. + */ +ActionEvent* +PDecor::handleEnterEvent(XCrossingEvent *ev) +{ + PDecor::Button *button = findButton(ev->window); + if (button) { + button->setState(BUTTON_STATE_HOVER); + } + + return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_ENTER, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_OTHER)); +} + +/** + * Handle leave event, find action and toggle hoover state if leave + * was from a button. + */ +ActionEvent* +PDecor::handleLeaveEvent(XCrossingEvent *ev) +{ + PDecor::Button *button = findButton(ev->window); + if (button) { + button->setState(button->getState()); + } + + return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_LEAVE, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_OTHER)); +} + +// END - PWinObj interface. + +//! @brief Adds a children in another decor to this decor +void +PDecor::addDecor(PDecor *decor) +{ + if (this == decor) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PDecor(" << this << ")::addDecor(" << decor << ")" << " *** this == decor" << endl; +#endif // DEBUG + return; + } + + list::iterator it(decor->begin()); + for (; it != decor->end(); ++it) { + addChild(*it); + (*it)->setWorkspace(_workspace); + } + + decor->_child_list.clear(); + delete decor; +} + +//! @brief Sets preferred decor_name +//! @return True on change, False if unchanged +bool +PDecor::setDecor(const std::string &name) +{ + string new_name(_decor_name_override); + if (new_name.size() == 0) { + new_name = name; + } + + if (_decor_name == new_name) { + return false; + } + + _decor_name = new_name; + loadDecor(); + + return true; +} + +//! @brief Sets override decor name +void +PDecor::setDecorOverride(StateAction sa, const std::string &name) +{ + if ((sa == STATE_SET) || + ((sa == STATE_TOGGLE) && (_decor_name_override.size() == 0))) { + _decor_name_override = name; + setDecor(""); + + } else if ((sa == STATE_UNSET) || + ((sa == STATE_TOGGLE) && (_decor_name_override.size() > 0))) { + _decor_name_override = ""; // .clear() doesn't work with old g++ + updateDecorName(); + } +} + +//! @brief Load and update decor. +void +PDecor::loadDecor(void) +{ + unloadDecor(); + + // Get decordata with name. + _data = _theme->getPDecorData(_decor_name); + if (! _data) { + _data = _theme->getPDecorData(DEFAULT_DECOR_NAME); + } + assert(_data); + + // Load decor. + list::iterator b_it(_data->buttonBegin()); + for (; b_it != _data->buttonEnd(); ++b_it) { + uint width = std::max(static_cast(1), (*b_it)->getWidth() ? (*b_it)->getWidth() : getTitleHeight()); + uint height = std::max(static_cast(1), (*b_it)->getHeight() ? (*b_it)->getHeight() : getTitleHeight()); + + _button_list.push_back(new PDecor::Button(&_title_wo, *b_it, width, height)); + _button_list.back()->mapWindow(); + addChildWindow(_button_list.back()->getWindow()); + } + + // Update title position. + if (_data->getTitleWidthMin() == 0) { + _title_wo.move(borderTopLeft(), borderTop()); + _need_shape = false; + } else { + _title_wo.move(0, 0); + _need_shape = true; + } + + // Update child positions. + list::iterator c_it(_child_list.begin()); + for (; c_it != _child_list.end(); ++c_it) { + alignChild(*c_it); + } + + // Make sure it gets rendered correctly. + _dirty_resized = true; + _focused = ! _focused; + setFocused(! _focused); + _dirty_resized = false; + + // Update the dimension of the frame. + if (_child) { + resizeChild(_child->getWidth(), _child->getHeight()); + } + + // Child theme change. + loadTheme(); +} + +//! @brief Frees resources used by PDecor. +void +PDecor::unloadDecor(void) +{ + // Set active button to 0 as it can not be valid after deleting + // the current buttons. + _button = 0; + + list::iterator it(_button_list.begin()); + for (; it != _button_list.end(); ++it) { + removeChildWindow((*it)->getWindow()); + delete *it; + } + _button_list.clear(); +} + +//! @brief +PDecor::Button* +PDecor::findButton(Window win) +{ + list::iterator it(_button_list.begin()); + for (; it != _button_list.end(); ++it) { + if (**it == win) { + return *it; + } + } + + return 0; +} + +//! @brief +PWinObj* +PDecor::getChildFromPos(int x) +{ + if (! _child_list.size() || (_child_list.size() != _title_list.size())) + return 0; + if (_child_list.size() == 1) + return _child_list.front(); + + if (x < static_cast(_titles_left)) { + return _child_list.front(); + } else if (x > static_cast(_title_wo.getWidth() - _titles_right)) { + return _child_list.back(); + } + + list::iterator c_it(_child_list.begin()); + list::iterator t_it(_title_list.begin()); + + // FIXME: make getChildFromPos separator aware! + uint pos = _titles_left, xx = x; + for (uint i = 0; i < _title_list.size(); ++i, ++t_it, ++c_it) { + if ((xx >= pos) && (xx <= (pos + (*t_it)->getWidth()))) { + return *c_it; + } + pos += (*t_it)->getWidth(); + } + + return 0; +} + +//! @brief Moves making the child be positioned at x y +void +PDecor::moveChild(int x, int y) +{ + move(x - borderLeft(), y - borderTop() - getTitleHeight()); +} + +//! @brief Resizes the decor, giving width x height space for the child +void +PDecor::resizeChild(uint width, uint height) +{ + resize(width + borderLeft() + borderRight(), + height + borderTop() + borderBottom() + getTitleHeight()); +} + +//! @brief Sets border state of the decor +void +PDecor::setBorder(StateAction sa) +{ + if (! ActionUtil::needToggle(sa, _border)) { + return; + } + + // If we are going to remove the border, we need to check carefully + // that we don't try to make the window disappear. + if (! _border) { + _border = true; + } else if (! _shaded || _titlebar) { + _border = false; + } + + restackBorder(); + if (! updateDecorName() && _child) + resizeChild(_child->getWidth(), _child->getHeight()); +} + +//! @brief Sets titlebar state of the decor +void +PDecor::setTitlebar(StateAction sa) +{ + if (! ActionUtil::needToggle(sa, _titlebar)) + return; + + // If we are going to remove the titlebar, we need to check carefully + // that we don't try to make the window disappear. + if (! _titlebar) { + _title_wo.mapWindow(); + _titlebar = true; + } else if (! _shaded || _border) { + _title_wo.unmapWindow(); + _titlebar = false; + } + + // If updateDecorName returns true, it already loaded decor stuff for us. + if (! updateDecorName() && _child) { + alignChild(_child); + resizeChild(_child->getWidth(), _child->getHeight()); + } +} + +//! @brief Calculate title height, 0 if titlebar is disabled. +uint +PDecor::getTitleHeight(void) const +{ + if (! _titlebar) { + return 0; + } + + if (_data->isTitleHeightAdapt()) { + return getFont(getFocusedState(false))->getHeight() + + _data->getPad(PAD_UP) + _data->getPad(PAD_DOWN); + } else { + return _data->getTitleHeight(); + } +} + +//! @brief Adds a child to the decor, reparenting the window +void +PDecor::addChild(PWinObj *child, std::list::iterator *it) +{ + child->reparent(this, borderLeft(), borderTop() + getTitleHeight()); + if (it == 0) { + _child_list.push_back(child); + } else { + _child_list.insert(*it, child); + } + + updatedChildOrder(); + + // Sync focused state if it is the first child, the child will be + // activated later on. If there are children here already fit the + // child into the decor. + if (_child_list.size() == 1) { + _focused = ! _focused; + setFocused(! _focused); + } else { + alignChild(child); + child->resize(getChildWidth(), getChildHeight()); + } +} + +//! @brief Removes PWinObj from this PDecor. +//! @param child PWinObj to remove from the this PDecor. +//! @param do_delete Wheter to call delete this when empty. (Defaults to true) +void +PDecor::removeChild(PWinObj *child, bool do_delete) +{ + child->reparent(PWinObj::getRootPWinObj(), + _gm.x + borderLeft(), + _gm.y + borderTop() + getTitleHeight()); + + list::iterator it(find(_child_list.begin(), _child_list.end(), child)); + if (it == _child_list.end()) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PDecor(" << this << ")::removeChild(" << child << ")" << endl + << " *** child not in _child_list, bailing out." << endl; +#endif // DEBUG + return; + } + + it = _child_list.erase(it); + + if (_child_list.size() > 0) { + if (_child == child) { + if (it == _child_list.end()) { + --it; + } + + activateChild(*it); + } + + updatedChildOrder(); + + } else if (_decor_cfg_keep_empty) { + updatedChildOrder(); + + } else if (do_delete) { + delete this; // no children, and we don't want empty PDecors + } +} + +//! @brief Activates the child, updating size and position +void +PDecor::activateChild(PWinObj *child) +{ + // sync state + _focusable = child->isFocusable(); + + alignChild(child); // place correct acording to border and title + child->resize(getChildWidth(), getChildHeight()); + child->raise(); + _child = child; + + restackBorder(); + + updatedChildOrder(); +} + +//! @brief +void +PDecor::getDecorInfo(wchar_t *buf, uint size) +{ + swprintf(buf, size, L"%dx%d+%d+%d", _gm.width, _gm.height, _gm.x, _gm.y); +} + +//! @brief +void +PDecor::activateChildNum(uint num) +{ + if (num >= _child_list.size()) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PDecor(" << this << ")::activeChildNum(" << num << ")" << endl + << " *** num > _child_list.size()" << endl; +#endif // DEBUG + return; + } + + list::iterator it(_child_list.begin()); + for (uint i = 0; i < num; ++i, ++it); + + activateChild(*it); +} + +//! @brief +void +PDecor::activateChildRel(int off) +{ + PWinObj *child = getChildRel(off); + if (! child) { + child = _child_list.front(); + } + activateChild(child); +} + +//! @brief Moves the current child off steps +//! @param off - for left and + for right +void +PDecor::moveChildRel(int off) +{ + PWinObj *child = getChildRel(off); + if (! child || (child == _child)) { + return; + } + + bool at_begin = (_child == _child_list.front()); + bool at_end = (_child == _child_list.back()); + + // switch place + _child_list.remove(_child); + list::iterator it(find(_child_list.begin(), _child_list.end(), child)); + if (off > 0) { + // when wrapping, we want to be able to insert at first place + if (at_begin || (*it != _child_list.front())) { + ++it; + } + + _child_list.insert(it, _child); + } else { + if (! at_end && (*it == _child_list.back())) { + ++it; + } + _child_list.insert(it, _child); + } + + updatedChildOrder(); +} + +//! @brief +PWinObj* +PDecor::getChildRel(int off) +{ + if ((off == 0) || (_child_list.size() < 2)) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PDecor(" << this << ")::getChildRel(" << off << ")" << endl + << " *** off == 0 or not enough children" << endl; +#endif // DEBUG + return 0; + } + + list::iterator it(find(_child_list.begin(), _child_list.end(), _child)); + // if no active child, use first + if (it == _child_list.end()) { + it = _child_list.begin(); + } + + int dir = (off > 0) ? 1 : -1; + off = abs(off); + + for (int i = 0; i < off; ++i) { + if (dir == 1) { // forward + if (++it == _child_list.end()) { + it = _child_list.begin(); + } + + } else { // backward + if (it == _child_list.begin()) { + it = _child_list.end(); + } + --it; + } + } + + return *it; +} + +//! @brief Do move of Decor with the mouse. +//! @param x_root X position of the pointer when event was triggered. +//! @param y_root Y position of the pointer when event was triggered. +void +PDecor::doMove(int x_root, int y_root) +{ + if (! allowMove()) { + return; + } + + PScreen *scr = PScreen::instance(); // convenience + StatusWindow *sw = StatusWindow::instance(); // convenience + + if (! scr->grabPointer(scr->getRoot(), ButtonMotionMask|ButtonReleaseMask, + ScreenResources::instance()->getCursor(ScreenResources::CURSOR_MOVE))) { + return; + } + + // Get relative position to root + int x = x_root - _gm.x; + int y = y_root - _gm.y; + + bool outline = ! Config::instance()->getOpaqueMove(); + bool center_on_root = Config::instance()->isShowStatusWindowOnRoot(); + EdgeType edge; + + // grab server, we don't want invert traces + if (outline) { + scr->grabServer(); + } + + wchar_t buf[128]; + getDecorInfo(buf, 128); + + if (Config::instance()->isShowStatusWindow()) { + sw->draw(buf, true, center_on_root ? 0 : &_gm); + sw->mapWindowRaised(); + sw->draw(buf, true, center_on_root ? 0 : &_gm); + } + + Geometry last_gm(_gm); + + XEvent e; + const long move_mask = ButtonPressMask|ButtonReleaseMask|PointerMotionMask; + bool exit = false; + while (! exit) { + if (outline) { + drawOutline(_gm); + } + XMaskEvent(_dpy, move_mask, &e); + if (outline) { + drawOutline(_gm); // clear + } + + switch (e.type) { + case MotionNotify: + // Flush all pointer motion, no need to redraw and redraw. + PScreen::removeMotionEvents(); + + _gm.x = e.xmotion.x_root - x; + _gm.y = e.xmotion.y_root - y; + checkSnap(); + + if (! outline && _gm != last_gm) { + last_gm = _gm; + move(_gm.x, _gm.y); + } + + edge = doMoveEdgeFind(e.xmotion.x_root, e.xmotion.y_root); + if (edge != SCREEN_EDGE_NO) { + doMoveEdgeAction(&e.xmotion, edge); + } + + getDecorInfo(buf, 128); + if (Config::instance()->isShowStatusWindow()) { + sw->draw(buf, true, center_on_root ? 0 : &_gm); + } + + break; + case ButtonRelease: + exit = true; + break; + } + } + + if (Config::instance()->isShowStatusWindow()) { + sw->unmapWindow(); + } + + // ungrab the server + if (outline) { + move(_gm.x, _gm.y); + scr->ungrabServer(true); + } + + scr->ungrabPointer(); +} + +//! @brief Matches cordinates against screen edge +EdgeType +PDecor::doMoveEdgeFind(int x, int y) +{ + EdgeType edge = SCREEN_EDGE_NO; + if (x <= signed(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_LEFT))) { + edge = SCREEN_EDGE_LEFT; + } else if (x >= signed(PScreen::instance()->getWidth() - + Config::instance()->getScreenEdgeSize(SCREEN_EDGE_RIGHT))) { + edge = SCREEN_EDGE_RIGHT; + } else if (y <= signed(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_TOP))) { + edge = SCREEN_EDGE_TOP; + } else if (y >= signed(PScreen::instance()->getHeight() - + Config::instance()->getScreenEdgeSize(SCREEN_EDGE_BOTTOM))) { + edge = SCREEN_EDGE_BOTTOM; + } + + return edge; +} + +//! @brief Finds and executes action if any +void +PDecor::doMoveEdgeAction(XMotionEvent *ev, EdgeType edge) +{ + ActionEvent *ae; + + uint button = PScreen::instance()->getButtonFromState(ev->state); + ae = ActionHandler::findMouseAction(button, ev->state, + MOUSE_EVENT_ENTER_MOVING, + Config::instance()->getEdgeListFromPosition(edge)); + + if (ae) { + ActionPerformed ap(this, *ae); + ap.type = ev->type; + ap.event.motion = ev; + + ActionHandler::instance()->handleAction(ap); + } +} + +/** + * Move and resize window with keybindings. + */ +void +PDecor::doKeyboardMoveResize(void) +{ + PScreen *scr = PScreen::instance(); // convenience + StatusWindow *sw = StatusWindow::instance(); // convenience + + if (! scr->grabPointer(scr->getRoot(), NoEventMask, + ScreenResources::instance()->getCursor(ScreenResources::CURSOR_MOVE))) { + return; + } + if (! scr->grabKeyboard(scr->getRoot())) { + scr->ungrabPointer(); + return; + } + + Geometry old_gm = _gm; // backup geometry if we cancel + bool outline = (! Config::instance()->getOpaqueMove() || + ! Config::instance()->getOpaqueResize()); + ActionEvent *ae; + list::iterator it; + + wchar_t buf[128]; + getDecorInfo(buf, 128); + + bool center_on_root = Config::instance()->isShowStatusWindowOnRoot(); + if (Config::instance()->isShowStatusWindow()) { + sw->draw(buf, true, center_on_root ? 0 : &_gm); + sw->mapWindowRaised(); + sw->draw(buf, true, center_on_root ? 0 : &_gm); + } + + if (outline) { + PScreen::instance()->grabServer(); + } + + XEvent e; + bool exit = false; + while (! exit) { + if (outline) { + drawOutline(_gm); + } + XMaskEvent(_dpy, KeyPressMask, &e); + if (outline) { + drawOutline(_gm); // clear + } + + if ((ae = KeyGrabber::instance()->findMoveResizeAction(&e.xkey)) != 0) { + for (it = ae->action_list.begin(); it != ae->action_list.end(); ++it) { + switch (it->getAction()) { + case MOVE_HORIZONTAL: + _gm.x += it->getParamI(0); + if (! outline) { + move(_gm.x, _gm.y); + } + break; + case MOVE_VERTICAL: + _gm.y += it->getParamI(0); + if (! outline) { + move(_gm.x, _gm.y); + } + break; + case RESIZE_HORIZONTAL: + _gm.width += resizeHorzStep(it->getParamI(0)); + if (! outline) { + resize(_gm.width, _gm.height); + } + break; + case RESIZE_VERTICAL: + _gm.height += resizeVertStep(it->getParamI(0)); + if (! outline) { + resize(_gm.width, _gm.height); + } + break; + case MOVE_SNAP: + checkSnap(); + if (! outline) { + move(_gm.x, _gm.y); + } + break; + case MOVE_CANCEL: + _gm = old_gm; // restore position + + if (! outline) { + move(_gm.x, _gm.y); + resize(_gm.width, _gm.height); + } + + exit = true; + break; + case MOVE_END: + if (outline) { + if ((_gm.x != old_gm.x) || (_gm.y != old_gm.y)) + move(_gm.x, _gm.y); + if ((_gm.width != old_gm.width) || (_gm.height != old_gm.height)) + resize(_gm.width, _gm.height); + } + exit = true; + break; + default: + // do nothing + break; + } + + getDecorInfo(buf, 128); + if (Config::instance()->isShowStatusWindow()) { + sw->draw(buf, true, center_on_root ? 0 : &_gm); + } + } + } + } + + if (Config::instance()->isShowStatusWindow()) { + sw->unmapWindow(); + } + + if (outline) { + scr->ungrabServer(true); + } + + scr->ungrabKeyboard(); + scr->ungrabPointer(); +} + +//! @brief Sets shaded state +void +PDecor::setShaded(StateAction sa) +{ + if (! ActionUtil::needToggle(sa, _shaded)) + return; + + // If we are going to shade the window, we need to check carefully + // that we don't try to make the window disappear. + if (_shaded) { + _gm.height = _real_height; + _shaded = false; + // If we have a titlebar OR border (border will only be visible in + // full-width mode only) + } else if (_titlebar || (_border && ! _data->getTitleWidthMin())) { + _real_height = _gm.height; + _gm.height = getTitleHeight(); + // shading in non full width title mode will make border go away + if (! _data->getTitleWidthMin()) { + _gm.height += borderTop() + borderBottom(); + } + _shaded = true; + } + + PWinObj::resize(_gm.width, _gm.height); + setBorderShape(); + placeBorder(); + restackBorder(); +} + +//! @brief Sets skip state. +void +PDecor::setSkip(uint skip) +{ + _skip = skip; +} + +//! @brief Remove iconified state. +void PDecor::deiconify(void) { + if (_iconified) { + if (_workspace == Workspaces::instance()->getActive()) { + mapWindow(); + } + _iconified = false; + } +} + +//! @brief Renders and sets title background +void +PDecor::renderTitle(void) +{ + if (! getTitleHeight()) { + return; + } + + bool force_update = false; + if (_data->getTitleWidthMin()) { + uint width_before = _title_wo.getWidth(); + + resizeTitle(); + applyBorderShape(); // update title shape + + if (width_before != _title_wo.getWidth()) { + force_update = true; + } + } else { + calcTabsWidth(); + } + + PFont *font = getFont(getFocusedState(false)); + PTexture *t_sep = _data->getTextureSeparator(getFocusedState(false)); + PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler(); + + // Get new title pixmap + if (_dirty_resized || force_update) { + pm->returnPixmap(_title_bg); + _title_bg = pm->getPixmap(_title_wo.getWidth(), _title_wo.getHeight(), PScreen::instance()->getDepth()); + _title_wo.setBackgroundPixmap(_title_bg); + } + + // Render main background on pixmap + _data->getTextureMain(getFocusedState(false))->render(_title_bg, 0, 0, + _title_wo.getWidth(), _title_wo.getHeight()); + + bool sel; // Current tab selected flag + uint x = _titles_left; // Position + uint pad_horiz = _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT); // Amount of horizontal padding + + list::iterator it(_title_list.begin()); + for (uint i = 0; it != _title_list.end(); ++i, ++it) { + sel = (_title_active == i); + + // render tab + _data->getTextureTab(getFocusedState(sel))->render(_title_bg, x, 0, + (*it)->getWidth(), _title_wo.getHeight()); + + font = getFont(getFocusedState(sel)); + font->setColor(_data->getFontColor(getFocusedState(sel))); + + if ((*it)) { + PFont::TrimType trim = PFont::FONT_TRIM_MIDDLE; + if ((*it)->isCustom() || (*it)->isUserSet()) { + trim = PFont::FONT_TRIM_END; + } + + font->draw(_title_bg, + x + _data->getPad(PAD_LEFT), // X position + _data->getPad(PAD_UP), // Y position + (*it)->getVisible(), 0, // Text and max chars + (*it)->getWidth() - pad_horiz, // Available width + trim); // Type of trim + } + + // move to next tab (or separator if any) + x += (*it)->getWidth(); + + // draw separator + if ((_title_list.size() > 1) && (i < (_title_list.size() - 1))) { + t_sep->render(_title_bg, x, 0, 0, 0); + x += t_sep->getWidth(); + } + } + + _title_wo.clear(); +} + +//! @brief +void +PDecor::renderButtons(void) +{ + list::iterator it(_button_list.begin()); + for (; it != _button_list.end(); ++it) { + (*it)->setState(_focused ? BUTTON_STATE_FOCUSED : BUTTON_STATE_UNFOCUSED); + } +} + +//! @brief Renders the border of the decor. +void +PDecor::renderBorder(void) +{ + if (! _border) { + return; + } + + map::iterator it; + PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler(); + + // Return pixmaps used for the border + if (_dirty_resized) { + for (it = _border_pos_map.begin(); it != _border_pos_map.end(); ++it) { + pm->returnPixmap(it->second); + it->second = None; + } + } + + PTexture *tex; + FocusedState state = getFocusedState(false); + uint width, height; + + for (it = _border_pos_map.begin(); it != _border_pos_map.end(); ++it) { + tex = _data->getBorderTexture(state, it->first); + + // Solid texture, get the color and set as bg, no need to render pixmap + if (tex->getType() == PTexture::TYPE_SOLID) { + XSetWindowBackground(_dpy, _border_win[it->first], + static_cast(tex)->getColor()->pixel); + + } else { + // not a solid texture, get pixmap, render, set as bg + getBorderSize(it->first, width, height); + + if ((width > 0) && (height > 0)) { + if (_dirty_resized) { + it->second = pm->getPixmap(width, height, + PScreen::instance()->getDepth()); + XSetWindowBackgroundPixmap(_dpy, _border_win[it->first], + it->second); + } + + tex->render(it->second, 0, 0, width, height); + } + } + + XClearWindow(_dpy, _border_win[it->first]); + } +} + +//! @brief Sets shape on the border windows. +void +PDecor::setBorderShape(void) +{ +#ifdef HAVE_SHAPE + Pixmap pix; + bool do_free; + unsigned int width, height; + FocusedState state = getFocusedState(false); + + XRectangle rect = {0, 0, 0, 0}; + + map::iterator it(_border_pos_map.begin()); + for (; it != _border_pos_map.end(); ++it) { + // Get the size of the border at position + getBorderSize(it->first, width, height); + + // Get shape pixmap + pix = _data->getBorderTexture(state, it->first)->getMask(width, height, + do_free); + if (pix != None) { + _need_shape = true; + XShapeCombineMask(_dpy, _border_win[it->first], + ShapeBounding, 0, 0, pix, ShapeSet); + if (do_free) { + ScreenResources::instance()->getPixmapHandler()->returnPixmap(pix); + } + } else { + rect.width = width; + rect.height = height; + XShapeCombineRectangles(_dpy, _border_win[it->first], ShapeBounding, + 0, 0, &rect, 1, ShapeSet, YXBanded); + } + } +#endif // HAVE_SHAPE +} + +//! @brief Finds the Head closest to x y from the center of the decor +uint +PDecor::getNearestHead(void) +{ + return PScreen::instance()->getNearestHead(_gm.x + (_gm.width / 2), _gm.y + (_gm.height / 2)); +} + +//! @brief +void +PDecor::checkSnap(void) +{ + if ((Config::instance()->getWOAttract() > 0) || + (Config::instance()->getWOResist() > 0)) { + checkWOSnap(); + } + if ((Config::instance()->getEdgeAttract() > 0) || + (Config::instance()->getEdgeResist() > 0)) { + checkEdgeSnap(); + } +} + +inline bool +isBetween(int x1, int x2, int t1, int t2) +{ + if (x1 > t1) { + if (x1 < t2) { + return true; + } + } else if (x2 > t1) { + return true; + } + return false; +} + +//! @brief +//! @todo PDecor/PWinObj doesn't have _skip property +void +PDecor::checkWOSnap(void) +{ + PDecor *decor; + Geometry gm = _gm; + + int x = getRX(); + int y = getBY(); + + int attract = Config::instance()->getWOAttract(); + int resist = Config::instance()->getWOResist(); + + bool snapped; + + vector::reverse_iterator it = _wo_list.rbegin(); + for (; it != _wo_list.rend(); ++it) { + if (((*it) == this) || ! (*it)->isMapped() || ((*it)->getType() != PWinObj::WO_FRAME)) { + continue; + } + + // Skip snapping, only valid on PDecor and up. + decor = dynamic_cast(*it); + if (decor && decor->isSkip(SKIP_SNAP)) { + continue; + } + + snapped = false; + + // check snap + if ((x >= ((*it)->getX() - attract)) && (x <= ((*it)->getX() + resist))) { + if (isBetween(gm.y, y, (*it)->getY(), (*it)->getBY())) { + _gm.x = (*it)->getX() - gm.width; + snapped = true; + } + } else if ((gm.x >= signed((*it)->getRX() - resist)) && + (gm.x <= signed((*it)->getRX() + attract))) { + if (isBetween(gm.y, y, (*it)->getY(), (*it)->getBY())) { + _gm.x = (*it)->getRX(); + snapped = true; + } + } + + if (y >= ((*it)->getY() - attract) && (y <= (*it)->getY() + resist)) { + if (isBetween(gm.x, x, (*it)->getX(), (*it)->getRX())) { + _gm.y = (*it)->getY() - gm.height; + if (snapped) + break; + } + } else if ((gm.y >= signed((*it)->getBY() - resist)) && + (gm.y <= signed((*it)->getBY() + attract))) { + if (isBetween(gm.x, x, (*it)->getX(), (*it)->getRX())) { + _gm.y = (*it)->getBY(); + if (snapped) + break; + } + } + } +} + +//! @brief Snaps decor agains head edges. Only updates _gm, no real move. +//! @todo Add support for checking for harbour and struts +void +PDecor::checkEdgeSnap(void) +{ + int attract = Config::instance()->getEdgeAttract(); + int resist = Config::instance()->getEdgeResist(); + + Geometry head; + PScreen::instance()->getHeadInfoWithEdge(PScreen::instance()->getNearestHead(_gm.x, _gm.y), head); + + if ((_gm.x >= (head.x - resist)) && (_gm.x <= (head.x + attract))) { + _gm.x = head.x; + } else if ((_gm.x + _gm.width) >= (head.x + head.width - attract) && + ((_gm.x + _gm.width) <= (head.x + head.width + resist))) { + _gm.x = head.x + head.width - _gm.width; + } + if ((_gm.y >= (head.y - resist)) && (_gm.y <= (head.y + attract))) { + _gm.y = head.y; + } else if (((_gm.y + _gm.height) >= (head.y + head.height - attract)) && + ((_gm.y + _gm.height) <= (head.y + head.height + resist))) { + _gm.y = head.y + head.height - _gm.height; + } +} + + +//! @brief Set shapes of decor +//! @return true if shape is on, else false. +bool +PDecor::setShape(void) +{ +#ifdef HAVE_SHAPE + if (! _child) { + return false; + } + + int num, t; + XRectangle *rect; + + rect = XShapeGetRectangles(_dpy, _child->getWindow(), ShapeBounding, + &num, &t); + // If there is more than one Rectangle it has an irregular shape. + _need_client_shape = (num > 1); + + // Will set or unset shape depending on _need_shape and _need_client_shape. + applyBorderShape(); + + XFree(rect); + + return (num > 1); +#else // !HAVE_SHAPE + return false; +#endif // HAVE_SHAPE +} + +/** + * Move child into position with regards to title and border. + */ +void +PDecor::alignChild(PWinObj *child) +{ + if (child) { + XMoveWindow(_dpy, child->getWindow(), borderLeft(), borderTop() + getTitleHeight()); + } +} + +//! @brief Draws outline of the decor with geometry gm +void +PDecor::drawOutline(const Geometry &gm) +{ + XDrawRectangle(_dpy, PScreen::instance()->getRoot(), + _theme->getInvertGC(), + gm.x, gm.y, gm.width, + _shaded ? _gm.height : gm.height); +} + +//! @brief Places decor buttons +void +PDecor::placeButtons(void) +{ + _titles_left = 0; + _titles_right = 0; + + list::iterator it(_button_list.begin()); + for (; it != _button_list.end(); ++it) { + if ((*it)->isLeft()) { + (*it)->move(_titles_left, 0); + _titles_left += (*it)->getWidth(); + } else { + _titles_right += (*it)->getWidth(); + (*it)->move(_title_wo.getWidth() - _titles_right, 0); + } + } +} + +//! @brief Places border windows +void +PDecor::placeBorder(void) +{ + // if we have tab min == 0 then we have full width title and place the + // border ontop, else we put the border under the title + uint bt_off = (_data->getTitleWidthMin() > 0) ? getTitleHeight() : 0; + + if (borderTop() > 0) { + XMoveResizeWindow(_dpy, _border_win[BORDER_TOP], + borderTopLeft(), bt_off, + _gm.width - borderTopLeft() - borderTopRight(), borderTop()); + + XMoveResizeWindow(_dpy, _border_win[BORDER_TOP_LEFT], + 0, bt_off, + borderTopLeft(), borderTopLeftHeight()); + XMoveResizeWindow(_dpy, _border_win[BORDER_TOP_RIGHT], + _gm.width - borderTopRight(), bt_off, + borderTopRight(), borderTopRightHeight()); + + if (borderLeft()) { + XMoveResizeWindow(_dpy, _border_win[BORDER_LEFT], + 0, borderTopLeftHeight() + bt_off, + borderLeft(), _gm.height - borderTopLeftHeight() - borderBottomLeftHeight()); + } + + if (borderRight()) { + XMoveResizeWindow(_dpy, _border_win[BORDER_RIGHT], + _gm.width - borderRight(), borderTopRightHeight() + bt_off, + borderRight(), _gm.height - borderTopRightHeight() - borderBottomRightHeight()); + } + } else { + if (borderLeft()) { + XMoveResizeWindow(_dpy, _border_win[BORDER_LEFT], + 0, getTitleHeight(), + borderLeft(), _gm.height - getTitleHeight() - borderBottom()); + } + + if (borderRight()) { + XMoveResizeWindow(_dpy, _border_win[BORDER_RIGHT], + _gm.width - borderRight(), getTitleHeight(), + borderRight(), _gm.height - getTitleHeight() - borderBottom()); + } + } + + if (borderBottom()) { + XMoveResizeWindow(_dpy, _border_win[BORDER_BOTTOM], + borderBottomLeft(), _gm.height - borderBottom(), + _gm.width - borderBottomLeft() - borderBottomRight(), borderBottom()); + XMoveResizeWindow(_dpy, _border_win[BORDER_BOTTOM_LEFT], + 0, _gm.height - borderBottomLeftHeight(), + borderBottomLeft(), borderBottomLeftHeight()); + XMoveResizeWindow(_dpy, _border_win[BORDER_BOTTOM_RIGHT], + _gm.width - borderBottomRight(), _gm.height - borderBottomRightHeight(), + borderBottomRight(), borderBottomRightHeight()); + } + + applyBorderShape(); +} + +/** + * Apply shaping on the window based on the shape of the client and + * the borders. + */ +void +PDecor::applyBorderShape(void) +{ +#ifdef HAVE_SHAPE + if (_need_shape || _need_client_shape) { + // if we have tab min == 0 then we have full width title and place the + // border ontop, else we put the border under the title + uint bt_off = (_data->getTitleWidthMin() > 0) ? getTitleHeight() : 0; + + Window shape; + shape = XCreateSimpleWindow(_dpy, PScreen::instance()->getRoot(), + 0, 0, _gm.width, _gm.height, 0, 0, 0); + + if (_child) { + XShapeCombineShape(_dpy, shape, ShapeBounding, + borderLeft(), borderTop() + getTitleHeight(), + _child->getWindow(), ShapeBounding, ShapeSet); + } + + // Apply border shape. Need to be carefull wheter or not to include it. + if (_border + && ! (_shaded && bt_off) // Shaded in non-full-width mode removes border. + && ! (_need_client_shape)) { // Shaped clients should appear bordeless. + // top + if (borderTop() > 0) { + XShapeCombineShape(_dpy, shape, ShapeBounding, + 0, bt_off, _border_win[BORDER_TOP_LEFT], + ShapeBounding, ShapeUnion); + + XShapeCombineShape(_dpy, shape, ShapeBounding, + borderTopLeft(), bt_off, _border_win[BORDER_TOP], + ShapeBounding, ShapeUnion); + + XShapeCombineShape(_dpy, shape, ShapeBounding, + _gm.width - borderTopRight(), bt_off, _border_win[BORDER_TOP_RIGHT], + ShapeBounding, ShapeUnion); + } + + bool use_bt_off = bt_off || borderTop(); + // Left border + if (borderLeft() > 0) { + XShapeCombineShape(_dpy, shape, ShapeBounding, + 0, use_bt_off ? bt_off + borderTopLeftHeight() : getTitleHeight(), + _border_win[BORDER_LEFT], + ShapeBounding, ShapeUnion); + } + + // Right border + if (borderRight() > 0) { + XShapeCombineShape(_dpy, shape, ShapeBounding, + _gm.width - borderRight(), use_bt_off ? bt_off + borderTopRightHeight() : getTitleHeight(), + _border_win[BORDER_RIGHT], + ShapeBounding, ShapeUnion); + } + + // bottom + if (borderBottom() > 0) { + XShapeCombineShape(_dpy, shape, ShapeBounding, + 0, _gm.height - borderBottomLeftHeight(), _border_win[BORDER_BOTTOM_LEFT], + ShapeBounding, ShapeUnion); + + XShapeCombineShape(_dpy, shape, ShapeBounding, + borderBottomLeft(), _gm.height - borderBottom(), _border_win[BORDER_BOTTOM], + ShapeBounding, ShapeUnion); + + XShapeCombineShape(_dpy, shape, ShapeBounding, + _gm.width - borderBottomRight(), _gm.height - borderBottomRightHeight(), + _border_win[BORDER_BOTTOM_RIGHT], + ShapeBounding, ShapeUnion); + } + } + if (_titlebar) { + // apply title shape + XShapeCombineShape(_dpy, shape, ShapeBounding, + _title_wo.getX(), _title_wo.getY(), _title_wo.getWindow(), + ShapeBounding, ShapeUnion); + } + + // Apply the shape mask to the window + XShapeCombineShape(_dpy, _window, ShapeBounding, 0, 0, shape, ShapeBounding, ShapeSet); + + XDestroyWindow(_dpy, shape); + } else { + // No shaping is required, apply a square shape to remove + // possible stale shaping. + XRectangle rect_square; + rect_square.x = 0; + rect_square.y = 0; + rect_square.width = _gm.width; + rect_square.height = _gm.height; + + XShapeCombineRectangles(_dpy, _window, ShapeBounding, 0, 0, &rect_square, 1, ShapeSet, YXBanded); + } +#endif // HAVE_SHAPE +} + +//! @brief Restacks child, title and border windows. +void +PDecor::restackBorder(void) +{ + // List of windows, adding two possible for title and child window + int extra = 0; + Window windows[BORDER_NO_POS + 2]; + + + // Only put the Child over if not shaded. + if (_child && ! _shaded) { + windows[extra++] = _child->getWindow(); + } + // Add title if any + if (_titlebar) { + windows[extra++] = _title_wo.getWindow(); + } + + // Add border windows + for (int i = 0; i < BORDER_NO_POS; ++i) { + windows[i + extra] = _border_win[i]; + } + + // Raise the top window so actual restacking is done. + XRaiseWindow(_dpy, windows[0]); + XRestackWindows(_dpy, windows, BORDER_NO_POS + extra); +} + +//! @brief Updates _decor_name to represent decor state +//! @return true if decor was changed +bool +PDecor::updateDecorName(void) +{ + string name; + + if (_titlebar && _border) { + name = PDecor::DEFAULT_DECOR_NAME; + } else if (_titlebar) { + name = PDecor::DEFAULT_DECOR_NAME_BORDERLESS; + } else if (_border) { + name = PDecor::DEFAULT_DECOR_NAME_TITLEBARLESS; + } + + return setDecor(name); +} + +/** + * Get size of border at position. + * + * @param pos Position to get size for. + * @param width Width of border at position. + * @param height Height of border at position. + */ +void +PDecor::getBorderSize(BorderPosition pos, uint &width, uint &height) +{ + FocusedState state = getFocusedState(false); // convenience + + switch (pos) { + case BORDER_TOP_LEFT: + case BORDER_TOP_RIGHT: + case BORDER_BOTTOM_LEFT: + case BORDER_BOTTOM_RIGHT: + width = _data->getBorderTexture(state, pos)->getWidth(); + height = _data->getBorderTexture(state, pos)->getHeight(); + break; + case BORDER_TOP: + if ((borderTopLeft() + borderTopRight()) < _gm.width) { + width = _gm.width - borderTopLeft() - borderTopRight(); + } else { + width = 1; + } + height = _data->getBorderTexture(state, pos)->getHeight(); + break; + case BORDER_BOTTOM: + if ((borderBottomLeft() + borderBottomRight()) < _gm.width) { + width = _gm.width - borderBottomLeft() - borderBottomRight(); + } else { + width = 1; + } + height = _data->getBorderTexture(state, pos)->getHeight(); + break; + case BORDER_LEFT: + width = _data->getBorderTexture(state, pos)->getWidth(); + if ((borderTopLeftHeight() + borderBottomLeftHeight()) < _gm.height) { + height = _gm.height - borderTopLeftHeight() - borderBottomLeftHeight(); + } else { + height = 1; + } + break; + case BORDER_RIGHT: + width = _data->getBorderTexture(state, pos)->getWidth(); + if ((borderTopRightHeight() + borderBottomRightHeight()) < _gm.height) { + height = _gm.height - borderTopRightHeight() - borderBottomRightHeight(); + } else { + height = 1; + } + break; + default: +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PDecor(" << this << ")::getBorderSize()" << endl << " *** invalid border position" << endl; +#endif // DEBUG + width = 1; + height = 1; + break; + }; +} + +/** + * Calculate width of title, if width min is 0 use the full available + * size minus borders. Else get width from content. + */ +uint +PDecor::calcTitleWidth(void) +{ + uint width = 0; + + if (_data->getTitleWidthMin() == 0) { + width = _gm.width; + if (width > (borderTopLeft() + borderTopRight())) { + width -= borderTopLeft() + borderTopRight(); + } + + } else { + uint width_max = 0; + // FIXME: what about selected tabs? + PFont *font = getFont(getFocusedState(false)); + + if (_data->isTitleWidthSymetric()) { + // Symetric mode, get max tab width, multiply with number of tabs + list::iterator it(_title_list.begin()); + for (; it != _title_list.end(); ++it) { + width = font->getWidth((*it)->getVisible()); + if (width > width_max) { + width_max = width; + } + } + + width = width_max + _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT); + width *= _title_list.size(); + + } else { + // Asymetric mode, get individual widths + list::iterator it(_title_list.begin()); + for (; it != _title_list.end(); ++it) { + width += font->getWidth((*it)->getVisible()) + + _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT); + } + } + + // Add width of separators and buttons + width += (_title_list.size() - 1) + * _data->getTextureSeparator(getFocusedState(false))->getWidth(); + width += _titles_left + _titles_right; + + // Validate sizes, make sure it is not less than width min + // pixels and more than width max percent. + if (width < static_cast(_data->getTitleWidthMin())) { + width = _data->getTitleWidthMin(); + } + if (width > (_gm.width * _data->getTitleWidthMax() / 100)) { + width = _gm.width * _data->getTitleWidthMax() / 100; + } + } + + return width; +} + +/** + * Calculate tab width, wrapper to choose correct algorithm.. + */ +void +PDecor::calcTabsWidth(void) +{ + if (! _title_list.size()) { + return; + } + + if (_data->isTitleWidthSymetric()) { + calcTabsWidthSymetric(); + } else { + calcTabsWidthAsymetric(); + } +} + +/** + * Get available tabs width and average width of a single tab. + * + * @param width_avail Number of pixels available in the titlebar. + * @param tab_width Width in pixels to use for one tab. + * @param off Number of pixels left with tab_width pixels wide tabs. + */ +void +PDecor::calcTabsGetAvailAndTabWidth(uint &width_avail, uint &tab_width, int &off) +{ + // Calculate width + width_avail = _title_wo.getWidth(); + + // Remove spacing if enough space is available + if (width_avail > (_titles_left + _titles_right)) { + width_avail -= _titles_left + _titles_right; + } + + // Remove separators if enough space is available + uint sep_width = (_title_list.size() - 1) + * _data->getTextureSeparator(getFocusedState(false))->getWidth(); + if (width_avail > sep_width) { + width_avail -= sep_width; + } + + tab_width = width_avail / _title_list.size(); + off = width_avail % _title_list.size(); +} + +//! @brief Calculate tab width symetric, sets up _title_list widths. +void +PDecor::calcTabsWidthSymetric(void) +{ + int off; + uint width_avail, tab_width; + calcTabsGetAvailAndTabWidth(width_avail, tab_width, off); + + // Assign width to elements + list::iterator it(_title_list.begin()); + for (; it != _title_list.end(); ++it) { + (*it)->setWidth(tab_width + ((off-- > 0) ? 1 : 0)); + } +} + +/** + * Calculate tab width asymmetrically. This is done with the following + * priority: + * + * 1. Give tabs their required width, if it fits this is complete. + * 2. Tabs did not fit, calculate average width. + * 3. Add width of tabs requiring less space to average. + * 4. Re-assign width equally to tabs using more than average. + */ +void +PDecor::calcTabsWidthAsymetric(void) +{ + int off; + uint width, width_avail, tab_width; + calcTabsGetAvailAndTabWidth(width_avail, tab_width, off); + + // Convenience + PFont *font = getFont(getFocusedState(false)); + + // 1. give tabs their required width. + uint width_total = 0; + list::iterator it(_title_list.begin()); + for (; it != _title_list.end(); ++it) { + // This should set the tab width to be only the size needed + width = font->getWidth((*it)->getVisible().c_str()) + + _data->getPad(PAD_LEFT) + _data->getPad(PAD_RIGHT) + ((off-- > 0) ? 1 : 0); + width_total += width; + (*it)->setWidth(width); + } + + if (width_total > width_avail) { + calcTabsWidthAsymetricShrink(width_avail, tab_width); + } else if (width_total < static_cast(_data->getTitleWidthMin())) { + calcTabsWidthAsymetricGrow(width_avail, tab_width); + } +} + +/** + * This is called to shrink the tabs that are over the tab_width in + * order to make room for all + */ +void +PDecor::calcTabsWidthAsymetricShrink(uint width_avail, uint tab_width) +{ + // 2. Tabs did not fit + uint tabs_left = _title_list.size(); + list::iterator it(_title_list.begin()); + for (; it != _title_list.end(); ++it) { + if ((*it)->getWidth() < tab_width) { + // 3. Add width of tabs requiring less space to the average. + tabs_left--; + width_avail -= (*it)->getWidth(); + } + } + + // 4. Re-assign width equally to tabs using more than average + tab_width = width_avail / tabs_left; + uint off = width_avail % tabs_left; + for (it = _title_list.begin(); it != _title_list.end(); ++it) { + if ((*it)->getWidth() >= tab_width) { + (*it)->setWidth(tab_width + ((off-- > 0) ? 1 : 0)); + } + } +} + +/** + * This is called when tabs in the decor are using less space than + * width min, grow tabs that are smaller than the average. + */ +void +PDecor::calcTabsWidthAsymetricGrow(uint width_avail, uint tab_width) +{ + // FIXME: Implement growing of asymetric tabs. +} diff --git a/pekwm/PDecor.hh b/pekwm/PDecor.hh new file mode 100644 index 0000000..d72b4d6 --- /dev/null +++ b/pekwm/PDecor.hh @@ -0,0 +1,462 @@ +// +// PDecor.hh for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _PDECOR_HH_ +#define _PDECOR_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Config.hh" +#include "Theme.hh" // for Theme::FrameData inlines +#include "PTexture.hh" // for border/image inlines +#include "Util.hh" + +#include + +class ActionEvent; +class PFont; +class PWinObj; + +/** + * Create window attributes. + */ +class CreateWindowParams { +public: + int depth; + long mask; + XSetWindowAttributes attr; + Visual *visual; +}; + +//! @brief PWinObj container class with fancy decor. +class PDecor : public PWinObj +{ +public: + //! @brief Decor title button class. +class Button : public PWinObj { + public: + Button(PWinObj *parent, Theme::PDecorButtonData *data, uint width, uint height); + ~Button(void); + + ActionEvent *findAction(XButtonEvent *ev); + ButtonState getState(void) const { return _state; } + void setState(ButtonState state); + + //! @brief Returns wheter the button is positioned relative the left title edge. + inline bool isLeft(void) const { return _left; } + + private: + Theme::PDecorButtonData *_data; + ButtonState _state; + + Pixmap _bg; + bool _left; + }; + + //! @brief Decor title item class. + class TitleItem { + public: + //! Info bitmask enum. + enum Info { + INFO_MARKED = (1 << 1), + INFO_ID = (1 << 2) + }; + + TitleItem(void) : _count(0), _id(0), _info(0), _width(0) { } + + inline const std::wstring &getVisible(void) const { return _visible; } + inline const std::wstring &getReal(void) const { return _real; } + inline const std::wstring &getCustom(void) const { return _custom; } + inline const std::wstring &getUser(void) const { return _user; } + + inline uint getCount(void) const { return _count; } + inline uint getId(void) const { return _id; } + inline bool isUserSet(void) const { return (_user.size() > 0); } + inline bool isCustom(void) const { return (_custom.size() > 0); } + inline uint getWidth(void) const { return _width; } + + inline void infoAdd(enum Info info) { _info |= info; } + inline void infoRemove(enum Info info) { _info &= ~info; } + inline bool infoIs(enum Info info) { return (_info&info); } + + void setReal(const std::wstring &real) { + _real = real; + if (! isUserSet() && ! isCustom()) { + updateVisible(); + } + } + void setCustom(const std::wstring &custom) { + _custom = custom; + if (_custom.size() > 0 && ! isUserSet()) { + updateVisible(); + } + } + void setUser(const std::wstring &user) { + _user = user; + updateVisible(); + } + void setCount(uint count) { _count = count; } + void setId(uint id) { _id = id; } + inline void setWidth(uint width) { _width = width; } + + void updateVisible(void); + + private: + std::wstring _visible; //!< Visible version of title + + std::wstring _real; //!< Title from client + std::wstring _custom; //!< Custom (title rule) set version of title + std::wstring _user; //!< User set version of title + + uint _count; //!< Number of title + uint _id; //!< ID of title + uint _info; //!< Info bitmask for extra title info + + uint _width; + }; + + PDecor(Theme *theme, + const std::string decor_name = DEFAULT_DECOR_NAME, + const Window child_window = None); + virtual ~PDecor(void); + + // START - PWinObj interface. + virtual void mapWindow(void); + virtual void mapWindowRaised(void); + virtual void unmapWindow(void); + + virtual void move(int x, int y); + virtual void resize(uint width, uint height); + virtual void moveResize(int x, int y, uint width, uint height); + virtual void raise(void); + virtual void lower(void); + + virtual void setFocused(bool focused); + virtual void setWorkspace(uint workspace); + + virtual void giveInputFocus(void); + + virtual ActionEvent *handleButtonPress(XButtonEvent *ev); + virtual ActionEvent *handleButtonRelease(XButtonEvent *ev); + virtual ActionEvent *handleMotionEvent(XMotionEvent *ev); + virtual ActionEvent *handleEnterEvent(XCrossingEvent *ev); + virtual ActionEvent *handleLeaveEvent(XCrossingEvent *ev); + + virtual bool operator == (const Window &window) { + if ((_window == window) || (_title_wo == window) || + findButton(window) || + (getBorderPosition(window) != BORDER_NO_POS) || + ((_child) ? (*_child == window) : false)) { + return true; + } + return false; + } + virtual bool operator != (const Window &window) { + return !(*this == window); + } + // END - PWinObj interface. + + // START - PDecor interface. + virtual bool allowMove(void) const { return true; } + + virtual void addChild(PWinObj *child, std::list::iterator *it = 0); + virtual void removeChild(PWinObj *child, bool do_delete = true); + virtual void activateChild(PWinObj *child); + + virtual void updatedChildOrder(void) { } + virtual void updatedActiveChild(void) { } + + virtual void getDecorInfo(wchar_t *buf, uint size); + + virtual void setShaded(StateAction sa); + virtual void setSkip(uint skip); + // END - PDecor interface. + + static std::list::iterator pdecor_begin(void) { return _pdecor_list.begin(); } + static std::list::iterator pdecor_end(void) { return _pdecor_list.end(); } + + inline bool isSkip(uint skip) const { return (_skip&skip); } + + void addDecor(PDecor *decor); + + bool setDecor(const std::string &name); + void setDecorOverride(StateAction sa, const std::string &name); + void loadDecor(void); + + //! @brief Returns title Window. + inline Window getTitleWindow(void) const { return _title_wo.getWindow(); } + PDecor::Button *findButton(Window win); + + //! @brief Returns height of PDecor ignoring shaded state. + inline uint getRealHeight(void) const { + return (_shaded ? _real_height : _gm.height); + } + + //! @brief Returns last click x root position. + inline int getPointerX(void) const { return _pointer_x; } + //! @brief Returns last click y root position. + inline int getPointerY(void) const { return _pointer_y; } + //! @brief Returns last click x window position. + inline int getClickX(void) const { return _click_x; } + //! @brief Returns last click y window position. + inline int getClickY(void) const { return _click_y; } + + //! @brief Returns width of child container. + inline uint getChildWidth(void) const { + if ((borderLeft() + borderRight()) >= _gm.width) { + return 1; + } + return (_gm.width - borderLeft() - borderRight()); + } + //! @brief Returns height of child container. + inline uint getChildHeight(void) const { + if ((borderTop() + borderBottom() + getTitleHeight()) >= getRealHeight()) { + return 1; + } + return (getRealHeight() - borderTop() - borderBottom() - getTitleHeight()); + } + + // child list actions + + //! @brief Returns number of children in PDecor. + inline uint size(void) const { return _child_list.size(); } + //! @brief Returns iterator to the first child in PDecor. + inline std::list::iterator begin(void) { + return _child_list.begin(); + } + //! @brief Returns iterator to the last+1 child in PDecor. + inline std::list::iterator end(void) { return _child_list.end(); } + + //! @brief Returns pointer to active PWinObj. + inline PWinObj *getActiveChild(void) { return _child; } + PWinObj *getChildFromPos(int x); + + void activateChildNum(uint num); + void activateChildRel(int off); // +/- relative from active + void moveChildRel(int off); // +/- relative from active + + // title + + //! @brief Adds TitleItem to title list. + inline void titleAdd(PDecor::TitleItem *ct) { _title_list.push_back(ct); } + //! @brief Removes all TitleItems from title list. + inline void titleClear(void) { _title_list.clear(); } + //! @brief Sets active TitleItem. + void titleSetActive(uint num) { + _title_active = (num > _title_list.size()) ? 0 : num; + } + + // move and resize relative to the child instead of decor + void moveChild(int x, int y); + void resizeChild(uint width, uint height); + + // decor state + + //! @brief Returns wheter we have a border or not. + inline bool hasBorder(void) const { return _border; } + //! @brief Returns wheter we have a titlebar or not. + inline bool hasTitlebar(void) const { return _titlebar; } + //! @brief Returns wheter we are shaded or not. + inline bool isShaded(void) const { return _shaded; } + void setBorder(StateAction sa); + void setTitlebar(StateAction sa); + + // decor element sizes + uint getTitleHeight(void) const; + + // common actions like doMove + void doMove(int x_root, int y_root); + void doKeyboardMoveResize(void); + + //! @brief Returns border position Window win is at. + inline BorderPosition getBorderPosition(Window win) const { + for (uint i = 0; i < BORDER_NO_POS; ++i) { + if (_border_win[i] == win) { + return BorderPosition(i); + } + } + return BORDER_NO_POS; + + } + inline uint borderTop(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_TOP)->getHeight() : 0); + } + inline uint borderTopLeft(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_TOP_LEFT)->getWidth() : 0); + } + inline uint borderTopLeftHeight(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_TOP_LEFT)->getHeight() : 0); + } + inline uint borderTopRight(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_TOP_RIGHT)->getWidth() : 0); + } + inline uint borderTopRightHeight(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_TOP_RIGHT)->getHeight() : 0); + } + inline uint borderBottom(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_BOTTOM)->getHeight() : 0); + } + inline uint borderBottomLeft(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_BOTTOM_LEFT)->getWidth() : 0); + } + inline uint borderBottomLeftHeight(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_BOTTOM_LEFT)->getHeight() : 0); + } + inline uint borderBottomRight(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_BOTTOM_RIGHT)->getWidth() : 0); + } + inline uint borderBottomRightHeight(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_BOTTOM_RIGHT)->getHeight() : 0); + } + inline uint borderLeft(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_LEFT)->getWidth() : 0); + } + inline uint borderRight(void) const { + return (_border ? _data->getBorderTexture(getFocusedState(false), BORDER_RIGHT)->getWidth() : 0); + } + + void deiconify(void); +protected: + // START - PDecor interface. + virtual void renderTitle(void); + virtual void renderButtons(void); + virtual void renderBorder(void); + virtual void setBorderShape(void); // shapes border corners + virtual void applyBorderShape(void); // applies shape onto window + + virtual void loadTheme(void) { } // called after loadDecor, render child + + virtual int resizeHorzStep(int diff) const { return diff; } + virtual int resizeVertStep(int diff) const { return diff; } + // END - PDecor interface. + + void resizeTitle(void); + + //! @brief Returns font used at FocusedState state. + inline PFont *getFont(FocusedState state) const { return _data->getFont(state); } + + uint getNearestHead(void); + + void checkSnap(void); + void checkWOSnap(void); + void checkEdgeSnap(void); + + bool setShape(void); + void alignChild(PWinObj *child); + void drawOutline(const Geometry &gm); + + inline FocusedState getFocusedState(bool selected) const { + if (selected) { + return (_focused) ? FOCUSED_STATE_FOCUSED_SELECTED : FOCUSED_STATE_UNFOCUSED_SELECTED; + } else { + return (_focused) ? FOCUSED_STATE_FOCUSED : FOCUSED_STATE_UNFOCUSED; + } + } + +private: + void getParentWindowAttributes(CreateWindowParams ¶ms, + Window child_window); + void getChildWindowAttributes(CreateWindowParams ¶ms, + Window child_window); + void createParentWindow(CreateWindowParams ¶ms); + void createTitle(CreateWindowParams ¶ms); + void createBorder(CreateWindowParams ¶ms); + + void unloadDecor(void); + + ActionEvent *handleButtonPressButton(XButtonEvent *ev, PDecor::Button *button); + ActionEvent *handleButtonPressBorder(XButtonEvent *ev); + ActionEvent *handleButtonReleaseButton(XButtonEvent *ev, PDecor::Button *button); + ActionEvent *handleButtonReleaseBorder(XButtonEvent *ev); + + EdgeType doMoveEdgeFind(int x, int y); + void doMoveEdgeAction(XMotionEvent *ev, EdgeType edge); + + void placeButtons(void); + void placeBorder(void); + void shapeBorder(void); + void restackBorder(void); // shaded, borderless, no border visible + + bool updateDecorName(void); + + PWinObj *getChildRel(int off); // +/- relative from active + void getBorderSize(BorderPosition pos, uint &width, uint &height); + + uint calcTitleWidth(void); + void calcTabsWidth(void); + void calcTabsGetAvailAndTabWidth(uint &width_avail, uint &tab_width, int &off); + void calcTabsWidthSymetric(void); + void calcTabsWidthAsymetric(void); + void calcTabsWidthAsymetricGrow(uint width_avail, uint tab_width); + void calcTabsWidthAsymetricShrink(uint width_avail, uint tab_width); + +protected: + Theme *_theme; //!< Pointer to Theme used by PDecor. + std::string _decor_name; /**< Name of decoration used. */ + std::string _decor_name_override; /**< Overridden decoration name. */ + + PWinObj *_child; //!< Pointer to active child in PDecor. + std::list _child_list; //!< List of children in PDecor. + + PDecor::Button *_button; /**< Active title button in PDecor. */ + Window _button_press_win; /**< Active border window, for button release handling. */ + + // used for treshold calculation + int _pointer_x; //!< Last click x root position. + int _pointer_y; //!< Last click y root position. + int _click_x; //!< Last click x window position. + int _click_y; //!< Last click y window position. + + // how the decor should behave + bool _decor_cfg_keep_empty; //!< Boolean to configure allowing empty PDecors. + bool _decor_cfg_child_move_overloaded; //!< Boolean to set wheter ::move is overloaded. + + // button{press,release} handling cfg + bool _decor_cfg_bpr_replay_pointer; //!< Boolean to configure wheter to call XReplayPointer on clicks. + MouseActionListName _decor_cfg_bpr_al_child; //!< What list to search for child actions. + MouseActionListName _decor_cfg_bpr_al_title; //!< What list to search for title actions. + + static const std::string DEFAULT_DECOR_NAME; //!< Default decor name in normal state. + static const std::string DEFAULT_DECOR_NAME_BORDERLESS; //!< Default decor name in borderless state. + static const std::string DEFAULT_DECOR_NAME_TITLEBARLESS; //!< Default decor name in titlebarless state. + + // state switches, commonly not used by all decors + bool _maximized_vert; + bool _maximized_horz; + bool _fullscreen; + uint _skip; + + Window _border_win[BORDER_NO_POS]; /** Array of border windows. */ + std::map _border_pos_map; + +private: + Theme::PDecorData *_data; + + // decor state + bool _border, _titlebar, _shaded; + bool _need_shape; + bool _need_client_shape; + bool _dirty_resized; //!< Flag set when decor has been resized. + uint _real_height; + + PWinObj _title_wo; + std::list _button_list; + + Pixmap _title_bg; + + // variable decor data + uint _title_active; + std::list _title_list; + uint _titles_left, _titles_right; // area where to put titles + + static std::list _pdecor_list; /**< List of PDecors */ +}; + +#endif // _PDECOR_HH_ diff --git a/pekwm/PFont.cc b/pekwm/PFont.cc new file mode 100644 index 0000000..c9f5832 --- /dev/null +++ b/pekwm/PFont.cc @@ -0,0 +1,615 @@ +// +// PFont.cc for pekwm +// Copyright © 2003-2009 Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include + +#include "PFont.hh" +#include "PScreen.hh" +#include "Util.hh" + +using std::cerr; +using std::endl; +using std::string; +using std::vector; +using std::wstring; + +wstring PFont::_trim_string = wstring(); +const char *FALLBACK_FONT = "fixed"; + +// PFont + +//! @brief PFont constructor +PFont::PFont(PScreen *scr) : + _scr(scr), + _height(0), _ascent(0), _descent(0), + _offset_x(0), _offset_y(0), _justify(FONT_JUSTIFY_LEFT) +{ + _type = FONT_TYPE_NO; +} + +//! @brief PFont destructor +PFont::~PFont(void) +{ + unload(); +} + +//! @brief Draws the text on the drawable. +//! @param dest destination Drawable +//! @param x x start position +//! @param y y start position +//! @param text text to draw +//! @param max_chars max nr of chars, defaults to 0 == strlen(text) +//! @param max_width max nr of pixels, defaults to 0 == infinity +//! @param trim_type how to trim title if not enough space, defaults to FONT_TRIM_END +void +PFont::draw(Drawable dest, int x, int y, const std::wstring &text, + uint max_chars, uint max_width, PFont::TrimType trim_type) +{ + if (! text.size()) { + return; + } + + uint offset = x, chars = max_chars; + wstring real_text(text); + + if (max_width > 0) { + // If max width set, make sure text fits in max_width pixels. + trim(real_text, trim_type, max_width); + + offset += justify(real_text, max_width, 0, + (chars == 0) ? real_text.size() : chars); + } else if (chars == 0) { + // Just set to complete string. + chars = real_text.size(); + } + + // Draw shadowed font if x or y offset is specified + if (_offset_x || _offset_y) { + drawText(dest, offset + _offset_x, y + _ascent + _offset_y, + real_text, chars, false); // false as in bg + } + + // Draw main font + drawText(dest, offset, y + _ascent, real_text, chars, true); // true as in fg +} + +//! @brief Trims the text making it max max_width wide +//! @param text +//! @param trim_type +//! @param max_width +//! @param chars +void +PFont::trim(std::wstring &text, PFont::TrimType trim_type, uint max_width) +{ + if (getWidth(text) > max_width) { + if ((_trim_string.size() > 0) && (trim_type == FONT_TRIM_MIDDLE)) { + // Trim middle before end if trim string is set. + trimMiddle(text, max_width); + } + + trimEnd(text, max_width); + } +} + +//! @brief Figures how many charachters to have before exceding max_width +void +PFont::trimEnd(std::wstring &text, uint max_width) +{ + for (uint i = text.size(); i > 0; --i) { + if (getWidth(text, i) <= max_width) { + text = text.substr(0, i); + break; + } + } +} + +//! @brief Replace middle of string with _trim_string making it max_width wide +void +PFont::trimMiddle(std::wstring &text, uint max_width) +{ + // Get max and separator width + uint max_side = (max_width / 2); + uint sep_width = getWidth(_trim_string.c_str(), + _trim_string.size() / 2 + _trim_string.size() % 2); + + uint pos = 0; + wstring dest; + + // If the trim string is too large, do nothing and let trimEnd handle this. + if (max_side <= sep_width) { + return; + } + // Add space for the trim string + max_side -= sep_width; + + // Get numbers of chars before trim string (..) + for (uint i = text.size(); i > 0; --i) { + if (getWidth(text, i) < max_side) { + pos = i; + dest.insert(0, text.substr(0, i)); + break; + } + } + + // get numbers of chars after ... + if (pos < text.size()) { + for (uint i = pos; i < text.size(); ++i) { + wstring second_part(text.substr(i, text.size() - i)); + if (getWidth(second_part, 0) < max_side) { + dest.insert(dest.size(), second_part); + break; + } + } + + // Got a char after and before, if not do nothing and trimEnd will handle + // trimming after this call. + if (dest.size() > 1) { + if ((getWidth(dest) + getWidth(_trim_string)) < max_width) { + dest.insert(pos, _trim_string); + } + + // Update original string + text = dest; + } + } +} + +//! @brief Justifies the string based on _justify property of the Font +uint +PFont::justify(const std::wstring &text, uint max_width, + uint padding, uint chars) +{ + uint x; + uint width = getWidth(text, chars); + + switch(_justify) { + case FONT_JUSTIFY_CENTER: + x = (max_width - width) / 2; + break; + case FONT_JUSTIFY_RIGHT: + x = max_width - width - padding; + break; + default: + x = padding; + break; + } + + return x; +} + +// PFontX11 + +//! @brief PFontX11 constructor +PFontX11::PFontX11(PScreen *scr) + : PFont(scr), + _font(0), _gc_fg(None), _gc_bg(None) +{ + _type = FONT_TYPE_X11; +} + +//! @brief PFontX11 destructor +PFontX11::~PFontX11(void) +{ + unload(); +} + +//! @brief Loads the X11 font font_name +bool +PFontX11::load(const std::string &font_name) +{ + unload(); + + _font = XLoadQueryFont(_scr->getDpy(), font_name.c_str()); + if (! _font) { + _font = XLoadQueryFont(_scr->getDpy(), FALLBACK_FONT); + if (! _font) { + return false; + } + } + + _height = _font->ascent + _font->descent; + _ascent = _font->ascent; + _descent = _font->descent; + + // create GC's + XGCValues gv; + + uint mask = GCFunction|GCFont; + gv.function = GXcopy; + gv.font = _font->fid; + _gc_fg = XCreateGC(_scr->getDpy(), _scr->getRoot(), mask, &gv); + _gc_bg = XCreateGC(_scr->getDpy(), _scr->getRoot(), mask, &gv); + + return true; +} + +//! @brief Unloads font resources +void +PFontX11::unload(void) +{ + if (_font) { + XFreeFont(_scr->getDpy(), _font); + _font = 0; + } + if (_gc_fg != None) { + XFreeGC(_scr->getDpy(), _gc_fg); + _gc_fg = None; + } + if (_gc_bg != None) { + XFreeGC(_scr->getDpy(), _gc_bg); + _gc_bg = None; + } +} + +//! @brief Gets the width the text would take using this font +uint +PFontX11::getWidth(const wstring &text, uint max_chars) +{ + if (! text.size()) { + return 0; + } + + // Make sure the max_chars has a decent value, if it's less than 1 wich it + // defaults to set it to the numbers of chars in the string text + if (! max_chars || (max_chars > text.size())) { + max_chars = text.size(); + } + + uint width = 0; + if (_font) { + // No UTF8 support, convert to locale encoding. + string mb_text(Util::to_mb_str(text.substr(0, max_chars))); + width = XTextWidth(_font, mb_text.c_str(), mb_text.size()); + } + + return (width + _offset_x); +} + +//! @brief Draws the text on dest +//! @param dest Drawable to draw on +//! @param chars Max numbers of characthers to print +//! @param fg Bool, to use foreground or background colors +void +PFontX11::drawText(Drawable dest, int x, int y, + const std::wstring &text, uint chars, bool fg) +{ + GC gc = fg ? _gc_fg : _gc_bg; + + if (_font && (gc != None)) { + string mb_text; + if (chars != 0) { + mb_text = Util::to_mb_str(text.substr(0, chars)); + } else { + mb_text = Util::to_mb_str(text); + } + + XDrawString(_scr->getDpy(), dest, gc, x, y, + mb_text.c_str(), mb_text.size()); + } +} + +//! @brief Sets the color that should be used when drawing +void +PFontX11::setColor(PFont::Color *color) +{ + if (color->hasFg()) { + XSetForeground(_scr->getDpy(), _gc_fg, color->getFg()->pixel); + } + + if (color->hasBg()) { + XSetForeground(_scr->getDpy(), _gc_bg, color->getBg()->pixel); + } +} + +// PFontXmb + +const char* PFontXmb::DEFAULT_FONTSET = "fixed*"; + +//! @brief PFontXmb constructor +PFontXmb::PFontXmb(PScreen *scr) + : PFont(scr), + _fontset(0), _gc_fg(None), _gc_bg(None) +{ + _type = FONT_TYPE_XMB; +} + +//! @brief PFontXmb destructor +PFontXmb::~PFontXmb(void) +{ + unload(); +} + +//! @brief Loads Xmb font font_name +bool +PFontXmb::load(const std::string &font_name) +{ + unload(); + + string basename(font_name); + if (font_name.rfind(",*") != (font_name.size() - 2)) { + basename.append(",*"); + } + + // Create GC + XGCValues gv; + + uint mask = GCFunction; + gv.function = GXcopy; + _gc_fg = XCreateGC(_scr->getDpy(), _scr->getRoot(), mask, &gv); + _gc_bg = XCreateGC(_scr->getDpy(), _scr->getRoot(), mask, &gv); + + // Try to load font + char *def_string; + char **missing_list, **font_names; + int missing_count; + + _fontset = XCreateFontSet(_scr->getDpy(), basename.c_str(), &missing_list, &missing_count, &def_string); + if (! _fontset) { + cerr << "PFontXmb::load(): Failed to create fontset for " << font_name << endl; + _fontset = XCreateFontSet(_scr->getDpy(), DEFAULT_FONTSET, &missing_list, &missing_count, &def_string); + } + + if (_fontset) { + for (int i = 0; i < missing_count; ++i) { + cerr << "PFontXmb::load(): Missing charset" << missing_list[i] << endl; + } + + _ascent = _descent = 0; + + XFontStruct **fonts; + int fnum = XFontsOfFontSet (_fontset, &fonts, &font_names); + for (int i = 0; i < fnum; ++i) { + if (signed(_ascent) < fonts[i]->ascent) { + _ascent = fonts[i]->ascent; + } + if (signed(_descent) < fonts[i]->descent) { + _descent = fonts[i]->descent; + } + } + + _height = _ascent + _descent; + + } else { + return false; + } + + return true; +} + +//! @brief Unloads font resources +void +PFontXmb::unload(void) +{ + if (_fontset) { + XFreeFontSet(_scr->getDpy(), _fontset); + _fontset = 0; + } +} + +//! @brief Gets the width the text would take using this font +uint +PFontXmb::getWidth(const std::wstring &text, uint max_chars) +{ + if (! text.size()) { + return 0; + } + + // Make sure the max_chars has a decent value, if it's less than 1 wich it + // defaults to set it to the numbers of chars in the string text + if (! max_chars || (max_chars > text.size())) { + max_chars = text.size(); + } + + uint width = 0; + if (_fontset) { + XRectangle ink, logical; + XwcTextExtents(_fontset, text.c_str(), max_chars, &ink, &logical); + width = logical.width; + } + + return (width + _offset_x); +} + +//! @brief Draws the text on dest +//! @param dest Drawable to draw on +//! @param chars Max numbers of characthers to print +//! @param fg Bool, to use foreground or background colors +void +PFontXmb::drawText(Drawable dest, int x, int y, + const std::wstring &text, uint chars, bool fg) +{ + GC gc = fg ? _gc_fg : _gc_bg; + + if (_fontset && (gc != None)) { + XwcDrawString(_scr->getDpy(), dest, _fontset, gc, + x, y, text.c_str(), chars ? chars : text.size()); + } +} + +//! @brief Sets the color that should be used when drawing +void +PFontXmb::setColor(PFont::Color *color) +{ + if (color->hasFg()) { + XSetForeground(_scr->getDpy(), _gc_fg, color->getFg()->pixel); + } + + if (color->hasBg()) { + XSetForeground(_scr->getDpy(), _gc_bg, color->getBg()->pixel); + } +} + +// PFontXft + +#ifdef HAVE_XFT + +//! @brief PFontXft constructor +PFontXft::PFontXft(PScreen *scr) + : PFont(scr), + _draw(0), _font(0), _cl_fg(0), _cl_bg(0) +{ + _type = FONT_TYPE_XFT; + _draw = XftDrawCreate(_scr->getDpy(), _scr->getRoot(), + _scr->getVisual()->getXVisual(), _scr->getColormap()); +} + +//! @brief PFontXft destructor +PFontXft::~PFontXft(void) +{ + unload(); + + XftDrawDestroy(_draw); + + if (_cl_fg) { + XftColorFree(_scr->getDpy(), _scr->getVisual()->getXVisual(), + _scr->getColormap(), _cl_fg); + delete _cl_fg; + } + + if (_cl_bg) { + XftColorFree(_scr->getDpy(), _scr->getVisual()->getXVisual(), + _scr->getColormap(), _cl_bg); + delete _cl_bg; + } +} + +//! @brief Loads the Xft font font_name +bool +PFontXft::load(const std::string &font_name) +{ + unload(); + + _font = XftFontOpenName(_scr->getDpy(), _scr->getScreenNum(), + font_name.c_str()); + if (! _font) { + _font = XftFontOpenXlfd(_scr->getDpy(), _scr->getScreenNum(), + font_name.c_str()); + } + + if (_font) { + _ascent = _font->ascent; + _descent = _font->descent; + _height = _font->height; + + return true; + } + return false; +} + +//! @brief Unloads font resources +void +PFontXft::unload(void) +{ + if (_font) { + XftFontClose(_scr->getDpy(), _font); + _font = 0; + } +} + +//! @brief Gets the width the text would take using this font +uint +PFontXft::getWidth(const std::wstring &text, uint max_chars) +{ + if (! text.size()) { + return 0; + } + + // Make sure the max_chars has a decent value, if it's less than 1 wich it + // defaults to set it to the numbers of chars in the string text + if (! max_chars || (max_chars > text.size())) { + max_chars = text.size(); + } + + uint width = 0; + if (_font) { + string utf8_text(Util::to_utf8_str(text.substr(0, max_chars))); + + XGlyphInfo extents; + XftTextExtentsUtf8(_scr->getDpy(), _font, + reinterpret_cast(utf8_text.c_str()), + utf8_text.size(), &extents); + + width = extents.xOff; + } + + return (width + _offset_x); +} + +//! @brief Draws the text on dest +//! @param dest Drawable to draw on +//! @param chars Max numbers of characthers to print +//! @param fg Bool, to use foreground or background colors +void +PFontXft::drawText(Drawable dest, int x, int y, + const std::wstring &text, uint chars, bool fg) +{ + XftColor *cl = fg ? _cl_fg : _cl_bg; + + if (_font && cl) { + string utf8_text; + if (chars != 0) { + utf8_text = Util::to_utf8_str(text.substr(0, chars)); + } else { + utf8_text = Util::to_utf8_str(text); + } + + XftDrawChange(_draw, dest); + XftDrawStringUtf8(_draw, cl, _font, x, y, + reinterpret_cast(utf8_text.c_str()), + utf8_text.size()); + } +} + +//! @brief Sets the color that should be used when drawing +void +PFontXft::setColor(PFont::Color *color) +{ + if (_cl_fg) { + XftColorFree(_scr->getDpy(), _scr->getVisual()->getXVisual(), + _scr->getColormap(), _cl_fg); + delete _cl_fg; + _cl_fg = 0; + } + if (_cl_bg) { + XftColorFree(_scr->getDpy(), _scr->getVisual()->getXVisual(), + _scr->getColormap(), _cl_bg); + delete _cl_bg; + _cl_bg = 0; + } + + if (color->hasFg()) { + _xrender_color.red = color->getFg()->red; + _xrender_color.green = color->getFg()->green; + _xrender_color.blue = color->getFg()->blue; + _xrender_color.alpha = color->getFgAlpha(); + + _cl_fg = new XftColor; + + XftColorAllocValue(_scr->getDpy(), _scr->getVisual()->getXVisual(), + _scr->getColormap(), &_xrender_color, _cl_fg); + } + + if (color->hasBg()) { + _xrender_color.red = color->getBg()->red; + _xrender_color.green = color->getBg()->green; + _xrender_color.blue = color->getBg()->blue; + _xrender_color.alpha = color->getBgAlpha(); + + _cl_bg = new XftColor; + + XftColorAllocValue(_scr->getDpy(), _scr->getVisual()->getXVisual(), + _scr->getColormap(), &_xrender_color, _cl_bg); + } +} + +#endif // HAVE_XFT diff --git a/pekwm/PFont.hh b/pekwm/PFont.hh new file mode 100644 index 0000000..9080868 --- /dev/null +++ b/pekwm/PFont.hh @@ -0,0 +1,190 @@ +// +// PFont.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _PFONT_HH_ +#define _PFONT_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#ifdef HAVE_LIMITS +#include +#endif // HAVE_LIMITS + +#include "pekwm.hh" + +extern "C" { +#ifdef HAVE_XFT +#include +#endif // HAVE_XFT +} + +class PScreen; + +class PFont +{ +public: + enum Type { + FONT_TYPE_X11, FONT_TYPE_XFT, FONT_TYPE_XMB, FONT_TYPE_NO + }; + enum TrimType { + FONT_TRIM_END, FONT_TRIM_MIDDLE + }; + + class Color { + public: + Color(void) : _has_fg(false), _has_bg(false), + _fg_alpha(65535), _bg_alpha(65535) + { } + ~Color(void) { } + + inline XColor *getFg(void) { return _fg; } + inline XColor *getBg(void) { return _bg; } + inline void setFg(XColor *xc) { _fg = xc; } + inline void setBg(XColor *xc) { _bg = xc; } + + inline uint getFgAlpha(void) const { return _fg_alpha; } + inline uint getBgAlpha(void) const { return _bg_alpha; } + inline void setFgAlpha(uint alpha) { _fg_alpha = alpha; } + inline void setBgAlpha(uint alpha) { _bg_alpha = alpha; } + + inline bool hasFg(void) const { return _has_fg; } + inline bool hasBg(void) const { return _has_bg; } + inline void setHasFg(bool f) { _has_fg = f; } + inline void setHasBg(bool b) { _has_bg = b; } + + private: + XColor *_fg, *_bg; + bool _has_fg, _has_bg; + + uint _fg_alpha, _bg_alpha; + }; + + PFont(PScreen *scr); + virtual ~PFont(void); + + inline Type getType(void) const { return _type; } + inline uint getJustify(void) const { return _justify; } + + inline void setJustify(uint j) { _justify = j; } + inline void setOffset(uint x, uint y) { _offset_x = x; _offset_y = y; } + + void draw(Drawable dest, int x, int y, const std::wstring &text, + uint max_chars = 0, uint max_width = 0, + PFont::TrimType trim_type = FONT_TRIM_END); + + void trim(std::wstring &text, TrimType trim_type, uint max_width); + void trimEnd(std::wstring &text, uint max_width); + void trimMiddle(std::wstring &text, uint max_width); + + static void setTrimString(const std::wstring &trim) { _trim_string = trim; } + + uint justify(const std::wstring &text, uint max_width, + uint padding, uint chars); + + // virtual interface + virtual bool load(const std::string &font_name) { return true; } + virtual void unload(void) { } + + virtual uint getWidth(const std::wstring &text, uint max_chars = 0) { + return 0; + } + virtual uint getHeight(void) { return _height; } + + virtual void setColor(PFont::Color *color) { } + +private: + virtual void drawText(Drawable dest, int x, int y, const std::wstring &text, + uint chars, bool fg) { } + +protected: + PScreen *_scr; + Type _type; + + uint _height, _ascent, _descent; + uint _offset_x, _offset_y, _justify; + + static std::wstring _trim_string; +}; + +class PFontX11 : public PFont { +public: + PFontX11(PScreen *scr); + virtual ~PFontX11(void); + + // virtual interface + virtual bool load(const std::string &name); + virtual void unload(void); + + virtual uint getWidth(const std::wstring &text, uint max_chars = 0); + + virtual void setColor(PFont::Color *color); + +private: + virtual void drawText(Drawable dest, int x, int y, const std::wstring &text, + uint chars, bool fg); + +private: + XFontStruct *_font; + GC _gc_fg, _gc_bg; +}; + +class PFontXmb : public PFont { +public: + PFontXmb(PScreen *scr); + virtual ~PFontXmb(void); + + // virtual interface + virtual bool load(const std::string &name); + virtual void unload(void); + + virtual uint getWidth(const std::wstring &text, uint max_chars = 0); + + virtual void setColor(PFont::Color *color); + +private: + virtual void drawText(Drawable dest, int x, int y, const std::wstring &text, + uint chars, bool fg); + +private: + XFontSet _fontset; + GC _gc_fg, _gc_bg; + + static const char *DEFAULT_FONTSET; /**< Default fallback fontset. */ +}; + +#ifdef HAVE_XFT +class PFontXft : public PFont { +public: + PFontXft(PScreen *scr); + virtual ~PFontXft(void); + + // virtual interface + virtual bool load(const std::string &font_name); + virtual void unload(void); + + virtual uint getWidth(const std::wstring &text, uint max_chars = 0); + + virtual void setColor(PFont::Color *color); + +private: + virtual void drawText(Drawable dest, int x, int y, const std::wstring &text, + uint chars, bool fg); + +private: + XftDraw *_draw; + XftFont *_font; + XftColor *_cl_fg, *_cl_bg; + + XRenderColor _xrender_color; +}; +#endif // HAVE_XFT + +#endif // _PFONT_HH_ diff --git a/pekwm/PImage.cc b/pekwm/PImage.cc new file mode 100644 index 0000000..ed9c10e --- /dev/null +++ b/pekwm/PImage.cc @@ -0,0 +1,544 @@ +// +// PImage.cc for pekwm +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "PImage.hh" +#include "PScreen.hh" +#include "ScreenResources.hh" +#include "PixmapHandler.hh" +#include "Util.hh" + +#include + +extern "C" { +#include +} + +using std::cerr; +using std::endl; +using std::list; +using std::string; + +list PImage::_loader_list = list(); + +/** + * PImage constructor, loads image if one is specified. + * + * @param dpy Display image is valid on. + * @param path Path to image file, if specified this is loaded. + */ +PImage::PImage(Display *dpy, const std::string &path) throw(LoadException&) + : _dpy(dpy), _type(IMAGE_TYPE_NO), _pixmap(None), _mask(None), _width(0), _height(0), + _data(0), _has_alpha(false), _use_alpha(false) +{ + if (path.size()) { + if (! load(path)) { + throw LoadException(path.c_str()); + } + } +} + +//! @brief PImage destructor. +PImage::~PImage(void) +{ + unload(); +} + +//! @brief Loads image from file. +//! @param file File to load. +//! @return Returns true on success, else false. +bool +PImage::load(const std::string &file) +{ + string ext(Util::getFileExt(file)); + if (! ext.size()) { + cerr << " *** WARNING: no file extension on " << file << "!" << endl; + return false; + } + + list::iterator it(_loader_list.begin()); + for (; it != _loader_list.end(); ++it) { + if (! strcasecmp((*it)->getExt(), ext.c_str())) { + _data = (*it)->load(file, _width, _height, _has_alpha, _use_alpha); + if (_data) { + _pixmap = createPixmap(_data, _width, _height); + _mask = createMask(_data, _width, _height); + break; + } + } + } + + return (_data); +} + +//! @brief Frees resources used by image. +void +PImage::unload(void) +{ + if (_data) { + delete [] _data; + _data = 0; + } + if (_pixmap) { + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_pixmap); + } + if (_mask) { + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_mask); + } + + _pixmap = None; + _mask = None; + _width = 0; + _height = 0; +} + +//! @brief Draws image on drawable. +//! @param draw Drawable to draw on. +//! @param x Destination x coordinate. +//! @param y Destination y coordinate. +//! @param width Destination width, defaults to 0 which expands to image size. +//! @param height Destination height, defaults to 0 which expands to image size. +void +PImage::draw(Drawable draw, int x, int y, uint width, uint height) +{ + if (! _data) { + return; + } + + // Expand variables. + if (! width) { + width = _width; + } if (! height) { + height = _height; + } + + // Draw image, select correct drawing method depending on image type, + // size and if alpha exists. + if ((_type == IMAGE_TYPE_FIXED) + || ((_type == IMAGE_TYPE_SCALED) + && (_width == width) && (_height == height))) { + if (_use_alpha) { + drawAlphaFixed(draw, x, y, width, height); + } else { + drawFixed(draw, x, y, width, height); + } + } else if (_type == IMAGE_TYPE_SCALED) { + if (_use_alpha) { + drawAlphaScaled(draw, x, y, width, height); + } else { + drawScaled(draw, x, y, width, height); + } + } else if (_type == IMAGE_TYPE_TILED) { + if (_use_alpha) { + drawAlphaTiled(draw, x, y, width, height); + } else { + drawTiled(draw, x, y, width, height); + } + } +} + +//! @brief Returns pixmap at sizen. +//! @param need_free Is set to wheter shape mask returned needs to be freed. +//! @param width Pixmap width, defaults to 0 which expands to image size. +//! @param height Pixmap height, defaults to 0 which expands to image size. +//! @return Returns pixmap at size or None on error. +Pixmap +PImage::getPixmap(bool &need_free, uint width, uint height) +{ + // Default + Pixmap pix = None; + need_free = false; + + // Expand parameters. + if (! width) { + width = _width; + } + + if (! height) { + height = _height; + } + + // Same size, return _pixmap. + if ((width == _width) && (height == _height)) { + pix = _pixmap; + } else { + uchar *scaled_data; + + scaled_data = getScaledData(width, height); + if (scaled_data) { + need_free = true; + pix = createPixmap(scaled_data, width, height); + delete [] scaled_data; + } + } + + return pix; +} + +//! @brief Returns shape mask at size, if any. +//! @param need_free Is set to wheter shape mask returned needs to be freed. +//! @param width Shape mask width, defaults to 0 which expands to image size. +//! @param height Shape mask height, defaults to 0 which expands to image size. +//! @return Returns shape mask at size or None if there is no mask information. +Pixmap +PImage::getMask(bool &need_free, uint width, uint height) +{ + // Default + Pixmap pix = None; + need_free = false; + + // Expand parameters. + if (! width) { + width = _width; + } + + if (! height) { + height = _height; + } + + // Same size, return _mask. + if ((width == _width) && (height == _height)) { + pix = _mask; + } else { + uchar *scaled_data; + + scaled_data = getScaledData(width, height); + if (scaled_data) { + need_free = true; + pix = createMask(scaled_data, width, height); + delete [] scaled_data; + } + } + + return pix; +} + +//! @brief Scales image to size. +//! @param width Width to scale image to. +//! @param height Height to scale image to. +void +PImage::scale(uint width, uint height) +{ + // Invalid width or height or no need to scale. + if (! width || ! height || ((width == _width) && (height == height))) { + return; + } + uchar *scaled_data; + + scaled_data = getScaledData(width, height); + if (scaled_data) { + // Free old resources. + unload(); + + // Set data pointer and create pixmap and mask at new size. + _data = scaled_data; + _pixmap = createPixmap(_data, width, height); + _mask = createMask(_data, width, height); + _width = width; + _height = height; + } +} + +//! @brief Draw image at position, not scaling. +void +PImage::drawFixed(Drawable dest, int x, int y, uint width, uint height) +{ + // Plain copy of the pixmap onto Drawable. + XCopyArea(_dpy, _pixmap, dest, PScreen::instance()->getGC(), + 0, 0, width, height, x, y); + +} + +//! @brief Draw image scaled to fit width and height. +void +PImage::drawScaled(Drawable dest, int x, int y, uint width, uint height) +{ + uchar *scaled_data; + // Create scaled representation of image. + scaled_data = getScaledData(width, height); + if (scaled_data) { + Pixmap pix; + // Create pixmap. + pix = createPixmap(scaled_data, width, height); + if (pix) { + XCopyArea(_dpy, pix, dest, PScreen::instance()->getGC(), + 0, 0, width, height, x, y); + ScreenResources::instance()->getPixmapHandler()->returnPixmap(pix); + } + + delete [] scaled_data; + } +} + +//! @brief Draw image tiled to fit width and height. +void +PImage::drawTiled(Drawable dest, int x, int y, uint width, uint height) +{ + // Create a GC with _pixmap as tile and tiled fill style. + GC gc; + XGCValues gv; + + gv.fill_style = FillTiled; + gv.tile = _pixmap; + gv.ts_x_origin = x; + gv.ts_y_origin = y; + + gc = XCreateGC(_dpy , dest, + GCFillStyle|GCTile|GCTileStipXOrigin|GCTileStipYOrigin, &gv); + + // Tile the image onto drawable. + XFillRectangle(_dpy, dest, gc, x, y, width, height); + + XFreeGC(_dpy, gc); +} + +//! @brief Draw image at position, not scaling. +void +PImage::drawAlphaFixed(Drawable dest, int x, int y, uint width, uint height, uchar *data) +{ + XImage *dest_image = XGetImage(_dpy, dest, x, y, width, height, AllPlanes, ZPixmap); + if (! dest_image) { + cerr << " *** ERROR: failed to get image for destination." << endl; + return; + } + + // Get mask from visual + Visual *visual = PScreen::instance()->getVisual()->getXVisual(); + dest_image->red_mask = visual->red_mask; + dest_image->green_mask = visual->green_mask; + dest_image->blue_mask = visual->blue_mask; + + uchar *src; + uchar r, g, b, a; + uchar d_r = 0, d_g = 0, d_b = 0; + float a_percent, a_percent_inv; + + if (data) { + src = data; + } else { + src = _data; + width = std::min(width, _width); + height = std::min(height, _height); + } + + for (uint i_y = 0; i_y < height; ++i_y) { + for (uint i_x = 0; i_x < width; ++i_x) { + // Get pixel value, copy them directly if alpha is set to 255. + r = *src++; + g = *src++; + b = *src++; + a = *src++; + + // Alpha not 100% solid, blend + if (a != 255) { + // Get RGB values from pixel. + getRgbFromPixel(dest_image, XGetPixel(dest_image, i_x, i_y), + d_r, d_g, d_b); + + a_percent = static_cast(a) / 255; + a_percent_inv = 1 - a_percent; + + r = static_cast((a_percent_inv * d_r) + (a_percent * r)); + g = static_cast((a_percent_inv * d_g) + (a_percent * g)); + b = static_cast((a_percent_inv * d_b) + (a_percent * b)); + } + + XPutPixel(dest_image, i_x, i_y, getPixelFromRgb(dest_image, r, g, b)); + } + } + + XPutImage(_dpy, dest, PScreen::instance()->getGC(), dest_image, + 0, 0, x, y, width, height); + XDestroyImage(dest_image); +} + +//! @brief Draw image scaled to fit width and height. +void +PImage::drawAlphaScaled(Drawable dest, int x, int y, uint width, uint height) +{ + uchar *scaled_data = getScaledData(width, height); + if (scaled_data) { + drawAlphaFixed(dest, x, y, width, height, scaled_data); + delete [] scaled_data; + } +} + +//! @brief Draw image tiled to fit width and height. +void +PImage::drawAlphaTiled(Drawable dest, int x, int y, uint width, uint height) +{ + // FIXME: Implement tiled rendering with alpha support + drawTiled(dest, x, y, width, height); +} + +//! @brief Creates Pixmap from data. +//! @param data Pointer to data to create pixmap from. +//! @param width Width of image data is representing. +//! @param height Height of image data is representing. +//! @return Returns Pixmap on success, else None. +Pixmap +PImage::createPixmap(uchar *data, uint width, uint height) +{ + XImage *ximage; + Pixmap pix = None; + + ximage = createXImage(data, width, height); + if (ximage) { + pix = ScreenResources::instance()->getPixmapHandler()->getPixmap(width, + height, PScreen::instance()->getDepth()); + + XPutImage(_dpy, pix, PScreen::instance()->getGC(), ximage, + 0, 0, 0, 0, width, height); + + delete [] ximage->data; + ximage->data = 0; + XDestroyImage(ximage); + } + + return pix; +} + +//! @brief Creates shape mask Pixmap from data. +//! @param data Pointer to data to create mask from. +//! @param width Width of image data is representing. +//! @param height Height of image data is representing. +//! @return Returns Pixmap mask on success, else None. +Pixmap +PImage::createMask(uchar *data, uint width, uint height) +{ + if (! _has_alpha) { + return None; + } + + // Create XImage + XImage *ximage; + ximage = XCreateImage(_dpy, PScreen::instance()->getVisual()->getXVisual(), + 1, ZPixmap, 0, 0, width, height, 32, 0); + if (! ximage) { + cerr << " *** WARNING: unable to create XImage!" << endl; + return None; + } + + // Alocate ximage data storage. + ximage->data = new char[ximage->bytes_per_line * height / sizeof(char)]; + + uchar *src = data + 3; // Skip R, G and B. + ulong pixel_trans, pixel_solid; + + pixel_trans = PScreen::instance()->getBlackPixel(); + pixel_solid = PScreen::instance()->getWhitePixel(); + + for (uint y = 0; y < height; ++y) { + for (uint x = 0; x < width; ++x) { + XPutPixel(ximage, x, y, (*src > 127) ? pixel_solid : pixel_trans); + src += 4; // Skip R, G, B and A + } + } + + // Create Pixmap + Pixmap pix; + pix = ScreenResources::instance()->getPixmapHandler()->getPixmap(width, height, 1); + + GC gc = XCreateGC(_dpy, pix, 0, 0); + XPutImage(_dpy, pix, gc, ximage, 0, 0, 0, 0, width, height); + XFreeGC(_dpy, gc); + + delete [] ximage->data; + ximage->data = 0; + XDestroyImage(ximage); + + return pix; +} + +//! @brief Createx XImage from data. +//! @param data Pointer to data to create XImage from. +//! @param width Width of image data is representing. +//! @param height Height of image data is representing. +XImage* +PImage::createXImage(uchar *data, uint width, uint height) +{ + // Create XImage + XImage *ximage; + ximage = XCreateImage(_dpy, PScreen::instance()->getVisual()->getXVisual(), + PScreen::instance()->getDepth(), ZPixmap, 0, 0, + width, height, 32, 0); + if (! ximage) { + cerr << " *** WARNING: unable to create XImage!" << endl; + return 0; + } + + // Allocate ximage data storage. + ximage->data = new char[ximage->bytes_per_line * height / sizeof(char)]; + + uchar *src = data; + uchar r, g, b; + + // Put data into XImage. + for (uint y = 0; y < height; ++y) { + for (uint x = 0; x < width; ++x) { + r = *src++; + g = *src++; + b = *src++; + if (_has_alpha) { + *src++; + } + + XPutPixel(ximage, x, y, getPixelFromRgb(ximage, r, g, b)); + } + } + + return ximage; +} + +//! @brief Scales image data and returns pointer to new data. +//! @param width Width of image data to return. +//! @param height Height of image data to return. +//! @return Pointer to image data on success, else 0. +//! @todo Implement decent scaling routine with data [] cache? +uchar* +PImage::getScaledData(uint width, uint height) +{ + if (! width || ! height) { + return 0; + } + + // Calculate aspect ratio. + float ratio_x, ratio_y; + ratio_x = static_cast(_width) / static_cast(width); + ratio_y = static_cast(_height) / static_cast(height); + + // Allocate memory. + uchar *scaled_data, *dest; + scaled_data = new uchar[width * height * (_has_alpha ? 4 : 3)]; + dest = scaled_data; + + // Scale image. + int i_src; + float f_src; + for (uint y = 0; y < height; ++y) { + f_src = static_cast(ratio_y * y) * _width; + for (uint x = 0; x < width; ++x) { + i_src = static_cast(f_src); + i_src *= (_has_alpha ? 4 : 3); + + *dest++ = _data[i_src++]; + *dest++ = _data[i_src++]; + *dest++ = _data[i_src++]; + if (_has_alpha) { + *dest++ = _data[i_src++]; + } + + f_src += ratio_x; + } + } + + return scaled_data; +} diff --git a/pekwm/PImage.hh b/pekwm/PImage.hh new file mode 100644 index 0000000..7ddbe10 --- /dev/null +++ b/pekwm/PImage.hh @@ -0,0 +1,158 @@ +// +// PImage.hh for pekwm +// Copyright © 2004-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifndef _PIMAGE_HH_ +#define _PIMAGE_HH_ + +#include "pekwm.hh" +#include "PImageLoader.hh" + +#include +#include + +//! @brief Image baseclass defining interface for image handling. +class PImage { +public: + //! @brief PImage constructor. + PImage(Display *dpy, const std::string &path="") throw(LoadException&); + //! @brief PImage destructor. + virtual ~PImage(void); + + //! @brief Add loader to loader list. + static void loaderAdd(PImageLoader *loader) { + _loader_list.push_back(loader); + } + //! @brief Removes and frees all loaders. + static void loaderClear(void) { + while (_loader_list.size()) { + delete _loader_list.back(); + _loader_list.pop_back(); + } + } + + //! @brief Returns type of image. + inline ImageType getType(void) const { return _type; } + //! @brief Sets type of image. + inline void setType(ImageType type) { _type = type; } + + //! @brief Returns width of image. + inline uint getWidth(void) const { return _width; } + //! @brief Returns height of image. + inline uint getHeight(void) const { return _height; } + + virtual bool load(const std::string &file); + virtual void unload(void); + virtual void draw(Drawable dest, int x, int y, + uint width = 0, uint height = 0); + virtual Pixmap getPixmap(bool &need_free, uint width = 0, uint height = 0); + virtual Pixmap getMask(bool &need_free, uint width = 0, uint height = 0); + virtual void scale(uint width, uint height); + +protected: + void drawFixed(Drawable dest, int x, int y, uint width, uint height); + void drawScaled(Drawable dest, int x, int y, uint widht, uint height); + void drawTiled(Drawable dest, int x, int y, uint widht, uint height); + void drawAlphaFixed(Drawable dest, int x, int y, uint widht, uint height, + uchar *data = 0); + void drawAlphaScaled(Drawable dest, int x, int y, uint widht, uint height); + void drawAlphaTiled(Drawable dest, int x, int y, uint widht, uint height); + + Pixmap createPixmap(uchar *data, uint width, uint height); + Pixmap createMask(uchar *data, uint width, uint height); + +private: + XImage *createXImage(uchar *data, uint width, uint height); + uchar *getScaledData(uint width, uint height); + + /** + * Create pixel value suitable for ximage from R, G and B. + */ + inline ulong + getPixelFromRgb(XImage *ximage, uchar r, uchar g, uchar b) + { + // 5 R, 5 G, 5 B (15 bit display) + if ((ximage->red_mask == 0x7c00) + && (ximage->green_mask == 0x3e0) && (ximage->blue_mask == 0x1f)) { + return ((r << 7) & 0x7c00) + | ((g << 2) & 0x03e0) | ((b >> 3) & 0x001f); + + // 5 R, 6 G, 5 B (16 bit display) + } else if ((ximage->red_mask == 0xf800) + && (ximage->green_mask == 0x07e0) + && (ximage->blue_mask == 0x001f)) { + return ((r << 8) & 0xf800) + | ((g << 3) & 0x07e0) | ((b >> 3) & 0x001f); + + // 8 R, 8 G, 8 B (24/32 bit display) + } else if ((ximage->red_mask == 0xff0000) + && (ximage->green_mask == 0xff00) + && (ximage->blue_mask == 0xff)) { + return ((r << 16) & 0xff0000) + | ((g << 8) & 0x00ff00) | (b & 0x0000ff); + } else { + return 0; + } + } + + /** + * Fill in RGB values from pixel value from ximage. + */ + inline void + getRgbFromPixel(XImage *ximage, ulong pixel, uchar &r, uchar &g, uchar &b) + { + // 5 R, 5 G, 5 B (15 bit display) + if ((ximage->red_mask == 0x7c00) + && (ximage->green_mask == 0x3e0) && (ximage->blue_mask == 0x1f)) { + r = (pixel >> 7) & 0x7c; + g = (pixel >> 2) & 0x3e; + b = (pixel << 3) & 0x1f; + // 5 R, 6 G, 5 B (16 bit display) + } else if ((ximage->red_mask == 0xf800) + && (ximage->green_mask == 0x07e0) + && (ximage->blue_mask == 0x001f)) { + r = (pixel >> 8) & 0xf8; + g = (pixel >> 3) & 0x7e; + b = (pixel << 3) & 0x1f; + // 8 R, 8 G, 8 B (24/32 bit display) + } else if ((ximage->red_mask == 0xff0000) + && (ximage->green_mask == 0xff00) + && (ximage->blue_mask == 0xff)) { + r = (pixel >> 16) & 0xff; + g = (pixel >> 8) & 0xff; + b = pixel & 0xff; + } else { + r = 0; + g = 0; + b = 0; + } + } + +protected: + Display *_dpy; //!< Display image is on. + + ImageType _type; //!< Type of image. + + Pixmap _pixmap; //!< Pixmap representation of image. + Pixmap _mask; //!< Pixmap representation of image shape mask. + + uint _width; //!< Width of image. + uint _height; //!< Height of image. + + uchar *_data; //!< Data describing image. + bool _has_alpha; //!< Wheter image has alpha channel. + bool _use_alpha; //!< Wheter image has alpha < 100% + +private: + static std::list _loader_list; //!< List of loaders. +}; + +#endif // _PIMAGE_HH_ diff --git a/pekwm/PImageIcon.cc b/pekwm/PImageIcon.cc new file mode 100644 index 0000000..5398a68 --- /dev/null +++ b/pekwm/PImageIcon.cc @@ -0,0 +1,99 @@ +// +// PImage.hh for pekwm +// Copyright © 2007-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include + +#include "Atoms.hh" +#include "PImageIcon.hh" + +using std::cerr; +using std::endl; + +//! @brief PImageIcon constructor. +//! @param dpy Display to load icon from. +PImageIcon::PImageIcon(Display *dpy) + : PImage(dpy) +{ + _type = IMAGE_TYPE_SCALED; + _has_alpha = true; + _use_alpha = true; +} + +//! @brief PImage destructor. +PImageIcon::~PImageIcon(void) +{ +} + +/** + * Load icon from window (if atom is set) + */ +bool +PImageIcon::loadFromWindow(Window win) +{ + bool status = false; + uchar *udata = 0; + ulong expected = 2, actual; + if (AtomUtil::getProperty(win, Atoms::getAtom(NET_WM_ICON), XA_CARDINAL, + expected, &udata, &actual)) { + if (actual >= expected) { + status = setImageFromData(udata, actual); + } + + XFree(udata); + } + + return status; +} + +/** + * Do the actual reading and loading of the icon data in ARGB data. + */ +bool +PImageIcon::setImageFromData(uchar *udata, ulong actual) +{ + // Icon size successfully read, proceed with loading the actual icon data. + long *from_data = reinterpret_cast(udata); + uint width = from_data[0]; + uint height = from_data[1]; + if (actual < (width * height + 2)) { + return false; + } + + _width = width; + _height = height; + + _data = new uchar[_width * _height * 4]; + convertARGBtoRGBA(_width * _height, from_data, _data); + + _pixmap = createPixmap(_data, _width, _height); + _mask = createMask(_data, _width, _height); + + return true; +} + +/** + * Convert data, source data is ARGB one pixel per 32bit and + * destination is RGBA in 4x8bit. + */ +void +PImageIcon::convertARGBtoRGBA(ulong size, long *from_data, uchar *to_data) +{ + uchar *p = to_data; + int pixel; + for (ulong i = 2; i < size; i += 1) { + pixel = from_data[i]; // in case 64bit system drop the unneeded bits + *p++ = pixel >> 16 & 0xff; + *p++ = pixel >> 8 & 0xff; + *p++ = pixel & 0xff; + *p++ = pixel >> 24 & 0xff; + } +} diff --git a/pekwm/PImageIcon.hh b/pekwm/PImageIcon.hh new file mode 100644 index 0000000..0ff658e --- /dev/null +++ b/pekwm/PImageIcon.hh @@ -0,0 +1,31 @@ +// +// PImage.hh for pekwm +// Copyright © 2007-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _PIMAGE_NATIVE_ICON_HH_ +#define _PIMAGE_NATIVE_ICON_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "PImage.hh" + +//! @brief Image class with pekwm native backend. +class PImageIcon : public PImage { +public: + PImageIcon(Display *dpy); + virtual ~PImageIcon(void); + + bool loadFromWindow(Window win); + +private: + bool setImageFromData(uchar *data, ulong actual); + static void convertARGBtoRGBA(ulong size, long *from_data, uchar *to_data); +}; + +#endif // _PIMAGE_NATIVE_ICON_HH_ diff --git a/pekwm/PImageLoader.hh b/pekwm/PImageLoader.hh new file mode 100644 index 0000000..0c3b1f3 --- /dev/null +++ b/pekwm/PImageLoader.hh @@ -0,0 +1,39 @@ +// +// PImageLoader.hh for pekwm +// Copyright © 2005-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifndef _PIMAGE_NATIVE_LOADER_HH_ +#define _PIMAGE_NATIVE_LOADER_HH_ + +#include + +//! @brief Image loader base class. +class PImageLoader { +public: + //! @brief PImageLoader constructor. + PImageLoader(const char *ext) : _ext(ext) { } + //! @brief PImageLoader destructor. + virtual ~PImageLoader(void) { } + + //! @brief Return file extension matching loader. + inline const char *getExt(void) const { return _ext; } + + //! @brief Loads file into data. (empty method, interface) + virtual uchar *load(const std::string &file, uint &width, uint &height, bool &alpha, bool &use_alpha) { + return 0; + } + +private: + const char *_ext; +}; + +#endif // _PIMAGE_NATIVE_LOADER_HH_ + diff --git a/pekwm/PImageLoaderJpeg.cc b/pekwm/PImageLoaderJpeg.cc new file mode 100644 index 0000000..022df4e --- /dev/null +++ b/pekwm/PImageLoaderJpeg.cc @@ -0,0 +1,104 @@ +// +// PImageLoaderJpeg.cc for pekwm +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifdef HAVE_IMAGE_JPEG + +#include "PImageLoaderJpeg.hh" + +#include + +extern "C" { +#include +#include +} + +using std::string; +using std::cerr; +using std::endl; + +//! @brief PImageLoaderJpeg constructor. +PImageLoaderJpeg::PImageLoaderJpeg(void) + : PImageLoader("JPG") +{ +} + +//! @brief PImageLoaderJpeg destructor. +PImageLoaderJpeg::~PImageLoaderJpeg(void) +{ +} + +//! @brief Loads file into data. +//! @param file File to load data from. +//! @param width Set to the width of image. +//! @param height Set to the height of image. +//! @param alpha Set to wheter image has alpha channel. +//! @return Pointer to data on success, else 0. +uchar* +PImageLoaderJpeg::load(const std::string &file, uint &width, uint &height, + bool &alpha, bool &use_alpha) +{ + FILE *fp; + + fp= fopen(file.c_str(), "rb"); + if (! fp) { + cerr << " *** WARNING: unable to open " << file << " for reading!" << endl; + return 0; + } + + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + + jpeg_stdio_src(&cinfo, fp); + + // Read jpeg header. + jpeg_read_header(&cinfo, TRUE); + + // Make sure we get data in 24bit RGB. + if (cinfo.out_color_space != JCS_RGB) + cinfo.out_color_space = JCS_RGB; + + jpeg_start_decompress(&cinfo); + + uint channels; + uint rowbytes; + + width = cinfo.output_width; + height = cinfo.output_height; + channels = cinfo.output_components; + rowbytes = width * channels; + + // Allocate image data. + uchar *data = new uchar[width * height * channels]; + + // Read image. + JSAMPROW row; + for (uint i = 0; i < height; ++i) { + row = data + i * rowbytes; + jpeg_read_scanlines(&cinfo, &row, 1); + } + + // Clean up. + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + fclose(fp); + + alpha = false; // Jpeg doesn't support alpha. + use_alpha = false; // Jpeg doesn't support alpha. + + return data; +} + +#endif // HAVE_IMAGE_JPEG diff --git a/pekwm/PImageLoaderJpeg.hh b/pekwm/PImageLoaderJpeg.hh new file mode 100644 index 0000000..28d4b46 --- /dev/null +++ b/pekwm/PImageLoaderJpeg.hh @@ -0,0 +1,36 @@ +// +// PImageLoaderJpeg.hh for pekwm +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _PIMAGE_NATIVE_LOADER_JPEG_HH_ +#define _PIMAGE_NATIVE_LOADER_JPEG_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifdef HAVE_IMAGE_JPEG + +#include "pekwm.hh" +#include "PImageLoader.hh" + +#include + +//! @brief Jpeg Loader class. +class PImageLoaderJpeg : public PImageLoader +{ +public: + PImageLoaderJpeg(void); + virtual ~PImageLoaderJpeg(void); + + virtual uchar *load(const std::string &file, uint &width, uint &height, + bool &alpha, bool &use_alpha); +}; + +#endif // HAVE_IMAGE_JPEG + +#endif // _PIMAGE_NATIVE_LOADER_JPEG_HH_ diff --git a/pekwm/PImageLoaderPng.cc b/pekwm/PImageLoaderPng.cc new file mode 100644 index 0000000..2508a4e --- /dev/null +++ b/pekwm/PImageLoaderPng.cc @@ -0,0 +1,187 @@ +// +// PImageLoaderPng.cc for pekwm +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifdef HAVE_IMAGE_PNG + +#include "PImageLoaderPng.hh" + +#include + +extern "C" { +#include +} + +using std::string; +using std::cerr; +using std::endl; + +const int PImageLoaderPng::PNG_SIG_BYTES = 8; + +//! @brief PImageLoaderPng constructor. +PImageLoaderPng::PImageLoaderPng(void) + : PImageLoader("PNG") +{ +} + +//! @brief PImageLoaderPng destructor. +PImageLoaderPng::~PImageLoaderPng(void) +{ +} + +//! @brief Loads file into data. +//! @param file File to load data from. +//! @param width Set to the width of image. +//! @param height Set to the height of image. +//! @param alpha Set to wheter image has alpha channel. +//! @return Pointer to data on success, else 0. +uchar* +PImageLoaderPng::load(const std::string &file, uint &width, uint &height, + bool &alpha, bool &use_alpha) +{ + FILE *fp; + + fp = fopen(file.c_str(), "rb"); + if (! fp) { + cerr << " *** WARNING: unable to open " << file << " for reading!" << endl; + return 0; + } + + if (! checkSignature(fp)) { + cerr << " *** WARNING: " << file << " not a PNG file!" << endl; + fclose(fp); + return 0; + } + + // Start PNG loading. + png_structp png_ptr; + png_infop info_ptr; + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); + if (! png_ptr) { + cerr << " *** ERROR: out of memory, png_create_read_struct failed!" << endl; + fclose(fp); + return 0; + } + + info_ptr = png_create_info_struct(png_ptr); + if (! info_ptr) { + cerr << " *** ERROR: out of memory, png_create_info_struct failed!" << endl; + png_destroy_read_struct(&png_ptr, 0, 0); + fclose(fp); + return 0; + } + + // Setup error handling. + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, 0); + fclose(fp); + return 0; + } + + png_init_io(png_ptr, fp); + png_set_sig_bytes(png_ptr, PImageLoaderPng::PNG_SIG_BYTES); + png_read_info(png_ptr, info_ptr); + + int color_type, bpp; + png_uint_32 png_width = 1, png_height = 1; + png_get_IHDR(png_ptr, info_ptr, &png_width, &png_height, &bpp, &color_type, 0, 0, 0); + + width = png_width; + height = png_height; + + // Setup read information, we want to make sure data get read in + // 16 bit RGB(A) + + // palette -> RGB mode + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + } + + // gray -> 8 bit gray + if (color_type == PNG_COLOR_TYPE_GRAY && (bpp < 8)) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + } + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + } + + if (bpp == 16) { + png_set_strip_16(png_ptr); + } + // gray, gray alpha -> to RGB + if ((color_type == PNG_COLOR_TYPE_GRAY) || (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) { + png_set_gray_to_rgb(png_ptr); + } + + // Now load image data. + uchar *data; + size_t data_size; + png_uint_32 rowbytes, channels; + png_bytepp row_pointers; + + png_read_update_info(png_ptr, info_ptr); + + rowbytes = png_get_rowbytes(png_ptr, info_ptr); + channels = png_get_channels(png_ptr, info_ptr); + + data_size = rowbytes * height / sizeof(uchar); + data = new uchar[data_size]; + row_pointers = new png_bytep[height]; + + for (png_uint_32 y = 0; y < height; ++y) { + row_pointers[y] = data + y * rowbytes; + } + + png_read_image(png_ptr, row_pointers); + + delete [] row_pointers; + + png_read_end(png_ptr, 0); + + // Clean up. + png_destroy_read_struct(&png_ptr, &info_ptr, 0); + + fclose(fp); + + alpha = (channels == 4); + use_alpha = false; + + if (alpha) { + uchar *p = data + 3, *end = data + data_size; + for (; p != end && ! use_alpha; p += 4) { + if (*p != 255) { + use_alpha = true; + } + } + } + + return data; +} + +//! @brief Checks file signature to see if it's a PNG file. +//! @param fp Pointer to open (rb) FILE. +//! @return true if fp is a PNG file, else false. +bool +PImageLoaderPng::checkSignature(FILE *fp) +{ + int status; + uchar sig[PImageLoaderPng::PNG_SIG_BYTES]; + + status = fread(sig, 1, PImageLoaderPng::PNG_SIG_BYTES, fp); + if (status == PImageLoaderPng::PNG_SIG_BYTES) { + return (png_sig_cmp(sig,0,PImageLoaderPng::PNG_SIG_BYTES) == 0); + } + return false; +} + +#endif // HAVE_IMAGE_PNG diff --git a/pekwm/PImageLoaderPng.hh b/pekwm/PImageLoaderPng.hh new file mode 100644 index 0000000..6f973e4 --- /dev/null +++ b/pekwm/PImageLoaderPng.hh @@ -0,0 +1,43 @@ +// +// PImageLoaderPng.hh for pekwm +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _PIMAGE_NATIVE_LOADER_PNG_HH_ +#define _PIMAGE_NATIVE_LOADER_PNG_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifdef HAVE_IMAGE_PNG + +#include "pekwm.hh" +#include "PImageLoader.hh" + +#include + +/** + * PNG Loader class. + */ +class PImageLoaderPng : public PImageLoader +{ +public: + PImageLoaderPng(void); + virtual ~PImageLoaderPng(void); + + virtual uchar *load(const std::string &file, uint &width, uint &height, + bool &alpha, bool &use_alpha); + +private: + bool checkSignature(std::FILE *fp); + + static const int PNG_SIG_BYTES; +}; + +#endif // HAVE_IMAGE_PNG + +#endif // _PIMAGE_NATIVE_LOADER_PNG_HH_ diff --git a/pekwm/PImageLoaderXpm.cc b/pekwm/PImageLoaderXpm.cc new file mode 100644 index 0000000..78e8c2d --- /dev/null +++ b/pekwm/PImageLoaderXpm.cc @@ -0,0 +1,171 @@ +// +// PImageLoaderXpm.cc for pekwm +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifdef HAVE_IMAGE_XPM + +#include "PScreen.hh" +#include "PImageLoaderXpm.hh" + +#include +#include +#include + +using std::cerr; +using std::endl; +using std::string; +using std::sscanf; +using std::strlen; + +const uint PImageLoaderXpm::CHANNELS = 4; +const uint PImageLoaderXpm::ALPHA_SOLID = 255; +const uint PImageLoaderXpm::ALPHA_TRANSPARENT = 0; +const char *PImageLoaderXpm::COLOR_DEFAULT = "#000000"; + +//! @brief PImageLoaderXpm constructor. +PImageLoaderXpm::PImageLoaderXpm(void) + : PImageLoader("XPM") +{ +} + +//! @brief PImageLoaderXpm destructor. +PImageLoaderXpm::~PImageLoaderXpm(void) +{ +} + +//! @brief Loads file into data. +//! @param file File to load data from. +//! @param width Set to the width of image. +//! @param height Set to the height of image. +//! @param alpha Set to wheter image has alpha channel. +//! @return Pointer to data on success, else 0. +uchar* +PImageLoaderXpm::load(const std::string &file, uint &width, uint &height, + bool &alpha, bool &use_alpha) +{ + XpmImage xpm_image; + XpmInfo xpm_info; + + // Read XPM to XpmImage format. + if (XpmReadFileToXpmImage((char*) file.c_str(), &xpm_image, &xpm_info) != Success) { + cerr << " *** WARNING: " << file << " not a valid XPM file!" << endl; + return 0; + } + + if (! xpm_image.ncolors || ! xpm_image.data || ! xpm_image.width || ! xpm_image.height) { + cerr << " *** WARNING: " << file << " invalid file information!" << endl; + return 0; + } + + // Build XpmColor -> RGBA Table. + uchar *xpm_to_rgba; + xpm_to_rgba = createXpmToRgbaTable(&xpm_image); + + width = xpm_image.width; + height = xpm_image.height; + + // Allocate data. + uchar *data, *dest; + uint *src; + + data = new uchar[width * height * CHANNELS]; + dest = data; + src = xpm_image.data; + + alpha = true; + use_alpha = false; + + // Fill data. + for (uint y = 0; y < height; ++y) { + for (uint x = 0; x < width; ++x) { + *dest++ = xpm_to_rgba[*src * CHANNELS]; // R + *dest++ = xpm_to_rgba[*src * CHANNELS + 1]; // G + *dest++ = xpm_to_rgba[*src * CHANNELS + 2]; // B + *dest++ = xpm_to_rgba[*src * CHANNELS + 3]; // A + + // Check for active use of alpha + if (! use_alpha && xpm_to_rgba[*src * CHANNELS + 3] != 255) { + use_alpha = true; + } + + *src++; + } + } + + // Cleanup. + delete [] xpm_to_rgba; + + XpmFreeXpmImage(&xpm_image); + XpmFreeXpmInfo(&xpm_info); + + return data; +} + +//! @brief Creates an color number to RGBA conversion table. +//! @param xpm_image Pointer to XpmImage. +//! @return Pointer to color number to RGBA conversion table. +uchar* +PImageLoaderXpm::createXpmToRgbaTable(XpmImage *xpm_image) +{ + uint c_tmp; // Temporary color value. + char c_buf[3] = { '\0', '\0', '\0' }; // Temporary hex color string. + const char *color; + uchar *xpm_to_rgba, *dest; + XColor xcolor_exact; + + xpm_to_rgba = new uchar[xpm_image->ncolors * CHANNELS]; + dest = xpm_to_rgba; + for (uint i = 0; i < xpm_image->ncolors; ++i) { + if (xpm_image->colorTable[i].c_color) { + color = xpm_image->colorTable[i].c_color; + } else if (xpm_image->colorTable[i].g_color) { + color = xpm_image->colorTable[i].g_color; + } else if (xpm_image->colorTable[i].g4_color) { + color = xpm_image->colorTable[i].g4_color; + } else if (xpm_image->colorTable[i].m_color) { + color = xpm_image->colorTable[i].m_color; + } else { + color = COLOR_DEFAULT; + } + + if (color && color[0] == '#' && strlen(color) == 7) { + // Color in format #RRGGBB + for (uint j = 0; j < 3; ++j) { + c_buf[0] = color[j * 2 + 1]; + c_buf[1] = color[j * 2 + 2]; + if (sscanf(c_buf, "%x", &c_tmp) == 1) { + *dest++ = c_tmp; + } else { + *dest++ = 0; + cerr << " *** WARNING: " << c_buf << " invalid color value!" << endl; + } + } + *dest++ = ALPHA_SOLID; + + } else if (color && XParseColor(PScreen::instance()->getDpy(), PScreen::instance()->getColormap(), + color, &xcolor_exact)) { + *dest++ = xcolor_exact.red; + *dest++ = xcolor_exact.green; + *dest++ = xcolor_exact.blue; + *dest++ = ALPHA_SOLID; + } else { + // Invalid format or None, set it transparent + *dest++ = 0; // R + *dest++ = 0; // G + *dest++ = 0; // B + *dest++ = ALPHA_TRANSPARENT; // A + } + } + + return xpm_to_rgba; +} + +#endif // HAVE_IMAGE_XPM diff --git a/pekwm/PImageLoaderXpm.hh b/pekwm/PImageLoaderXpm.hh new file mode 100644 index 0000000..55cc344 --- /dev/null +++ b/pekwm/PImageLoaderXpm.hh @@ -0,0 +1,47 @@ +// +// PImageLoaderXpm.hh for pekwm +// Copyright © 2005-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _PIMAGE_NATIVE_LOADER_XPM_HH_ +#define _PIMAGE_NATIVE_LOADER_XPM_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifdef HAVE_IMAGE_XPM + +#include "pekwm.hh" +#include "PImageLoader.hh" + +extern "C" { +#include +} + +//! @brief Xpm Loader class. +class PImageLoaderXpm : public PImageLoader +{ +public: + PImageLoaderXpm(void); + virtual ~PImageLoaderXpm(void); + + virtual uchar *load(const std::string &file, uint &width, uint &height, + bool &alpha, bool &use_alpha); + +private: + uchar *createXpmToRgbaTable(XpmImage *xpm_image); + +private: + static const uint CHANNELS; //!< Number of channels for Image data. + static const uint ALPHA_SOLID; //!< Alpha value for no transperency. + static const uint ALPHA_TRANSPARENT; //!< Alpha value for fully transperency. + static const char *COLOR_DEFAULT; //!< Default Color if translation fails. +}; + +#endif // HAVE_IMAGE_XPM + +#endif // _PIMAGE_NATIVE_LOADER_XPM_HH_ diff --git a/pekwm/PMenu.cc b/pekwm/PMenu.cc new file mode 100644 index 0000000..f3da3d9 --- /dev/null +++ b/pekwm/PMenu.cc @@ -0,0 +1,1101 @@ +// +// PMenu.cc for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "PFont.hh" +#include "PMenu.hh" +#include "PTexture.hh" +#include "PTexturePlain.hh" +#include "PScreen.hh" +#include "ActionHandler.hh" +#include "Config.hh" +#include "ScreenResources.hh" +#include "TextureHandler.hh" +#include "Theme.hh" +#include "PixmapHandler.hh" +#include "Workspaces.hh" +#include "AutoProperties.hh" + +#include +#include + +#ifdef DEBUG +#include +using std::cerr; +using std::endl; +#endif // DEBUG + +using std::list; +using std::map; +using std::string; +using std::wstring; +using std::find; + +PMenu::Item::Item(const std::wstring &name, PWinObj *wo_ref, PTexture *icon) + : PWinObjReference(wo_ref), + _x(0), _y(0), _name(name), + _type(MENU_ITEM_NORMAL), _icon(icon), _creator(0) +{ + if (_icon) { + TextureHandler::instance()->referenceTexture(_icon); + } +} + +PMenu::Item::~Item(void) +{ + if (_icon) { + TextureHandler::instance()->returnTexture(_icon); + } +} + +map PMenu::_menu_map = map(); + +//! @brief Constructor for PMenu class +PMenu::PMenu(Theme *theme, const std::wstring &title, + const std::string &name, const std::string decor_name) + : PDecor(theme, decor_name), + _name(name), + _menu_parent(0), _class_hint(L"pekwm", L"Menu", L"", L"", L""), + _menu_wo(0), + _menu_bg_fo(None), _menu_bg_un(None), _menu_bg_se(None), + _menu_width(0), + _item_height(0), _item_width_max(0), _item_width_max_avail(0), + _icon_width(0), _icon_height(0), + _separator_height(0), + _rows(0), _cols(0), _scroll(false), _has_submenu(false) +{ + // initiate items + _item_curr = _item_list.end(); + + // PWinObj attributes + _type = PWinObj::WO_MENU; + setLayer(LAYER_MENU); + _hidden = true; // don't care about it when changing worskpace etc + + // create menu content child + _menu_wo = new PWinObj; + XSetWindowAttributes attr; + attr.override_redirect = True; + attr.event_mask = ButtonPressMask|ButtonReleaseMask|ButtonMotionMask| + FocusChangeMask|KeyPressMask|KeyReleaseMask|PointerMotionMask; + _menu_wo->setWindow(XCreateWindow(_dpy, _window, + 0, 0, 1, 1, 0, + CopyFromParent, InputOutput, CopyFromParent, + CWOverrideRedirect|CWEventMask, &attr)); + + titleAdd(&_title); + titleSetActive(0); + setTitle(title); + + addChild(_menu_wo); + addChildWindow(_menu_wo->getWindow()); + activateChild(_menu_wo); + _menu_wo->mapWindow(); + + Workspaces::instance()->insert(this); + _menu_map[_window] = this; // add to menu map + woListAdd(this); + _wo_map[_window] = this; +#ifdef OPACITY + setOpacity(Config::instance()->getMenuFocusOpacity(), + Config::instance()->getMenuUnfocusOpacity()); +#endif // OPACITY +} + +//! @brief Destructor for PMenu class +PMenu::~PMenu(void) +{ + _wo_map.erase(_window); + woListRemove(this); + _menu_map.erase(_window); // remove from menu map + Workspaces::instance()->remove(this); + + // Free resources + if (_menu_wo) { + _child_list.remove(_menu_wo); + removeChildWindow(_menu_wo->getWindow()); + XDestroyWindow(_dpy, _menu_wo->getWindow()); + delete _menu_wo; + } + + list::iterator it(_item_list.begin()); + for (; it != _item_list.end(); ++it) { + delete *it; + } + + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_menu_bg_fo); + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_menu_bg_un); + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_menu_bg_se); +} + +// START - PWinObj interface. + +//! @brief Unmapping, deselecting current item and unsticking. +void +PMenu::unmapWindow(void) +{ + _item_curr = _item_list.end(); + _sticky = false; + + PDecor::unmapWindow(); +} + +//! @brief +void +PMenu::setFocused(bool focused) +{ + if (_focused != focused) { + PDecor::setFocused(focused); + + _menu_wo->setBackgroundPixmap(_focused ? _menu_bg_fo : _menu_bg_un); + _menu_wo->clear(); + if (_item_curr != _item_list.end()) { + list::iterator item(_item_curr); + _item_curr = _item_list.end(); + selectItem(item); + } + } +} + +/** + * Set focusable, includes the _menu_wo as well as the decor. + */ +void +PMenu::setFocusable(bool focusable) +{ + PDecor::setFocusable(focusable); + _menu_wo->setFocusable(focusable); +} + +//! @brief +ActionEvent* +PMenu::handleButtonPress(XButtonEvent *ev) +{ + if (*_menu_wo == ev->window) { + handleItemEvent(MOUSE_EVENT_PRESS, ev->x, ev->y); + + // update pointer position + _pointer_x = ev->x_root; + _pointer_y = ev->y_root; + + return ActionHandler::findMouseAction(ev->button, ev->state, + MOUSE_EVENT_PRESS, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_MENU)); + } else { + return PDecor::handleButtonPress(ev); + } +} + +/** + * Handle button release. + */ +ActionEvent* +PMenu::handleButtonRelease(XButtonEvent *ev) +{ + if (_window == ev->subwindow) { + ev->window = _menu_wo->getWindow(); + ev->x -= _gm.x; + ev->y -= _gm.y + getTitleHeight(); + } + + if (*_menu_wo == ev->window) { + MouseEventType mb = MOUSE_EVENT_RELEASE; + + // first we check if it's a double click + if (PScreen::instance()->isDoubleClick(ev->window, ev->button - 1, ev->time, + Config::instance()->getDoubleClickTime())) { + PScreen::instance()->setLastClickID(ev->window); + PScreen::instance()->setLastClickTime(ev->button - 1, 0); + + mb = MOUSE_EVENT_DOUBLE; + + } else { + PScreen::instance()->setLastClickID(ev->window); + PScreen::instance()->setLastClickTime(ev->button - 1, ev->time); + } + + handleItemEvent(mb, ev->x, ev->y); + + return ActionHandler::findMouseAction(ev->button, ev->state, mb, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_MENU)); + } else { + return PDecor::handleButtonRelease(ev); + } +} + +/** + * Handle motion event, select buttons and execute actions. + * + * @param ev Handle motion event. + */ +ActionEvent* +PMenu::handleMotionEvent(XMotionEvent *ev) +{ + if (_window == ev->subwindow) { + ev->window = _menu_wo->getWindow(); + ev->x -= _gm.x; + ev->y -= _gm.y + getTitleHeight(); + } + + if (*_menu_wo == ev->window) { + uint button = PScreen::instance()->getButtonFromState(ev->state); + handleItemEvent(button ? MOUSE_EVENT_MOTION_PRESSED : MOUSE_EVENT_MOTION, ev->x, ev->y); + + ActionEvent *ae; + PScreen::stripButtonModifiers(&ev->state); + ae = ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_MENU)); + + // check motion threshold + if (ae && (ae->threshold > 0)) { + if (! ActionHandler::checkAEThreshold(ev->x_root, ev->y_root, + _pointer_x, _pointer_y, ae->threshold)) { + ae = 0; + } + } + return ae; + } else { + return PDecor::handleMotionEvent(ev); + } +} + +//! @brief +ActionEvent* +PMenu::handleEnterEvent(XCrossingEvent *ev) +{ + if (*_menu_wo == ev->window) { + return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_ENTER, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_MENU)); + } else { + return PDecor::handleEnterEvent(ev); + } +} + +//! @brief +ActionEvent* +PMenu::handleLeaveEvent(XCrossingEvent *ev) +{ + if (*_menu_wo == ev->window) { + return ActionHandler::findMouseAction(BUTTON_ANY, ev->state, + MOUSE_EVENT_LEAVE, + Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_MENU)); + } else { + return PDecor::handleLeaveEvent(ev); + } +} + +// END - PWinObj interface. + +// START - PDecor interface. + +/** + * Load menu theme after border has been updated. + */ +void +PMenu::loadTheme(void) +{ + buildMenuRender(); +} + +// END - PDecor interface. + +/** + * Handle event on menu item at position, ignores event if no item + * exists at the position. + */ +void +PMenu::handleItemEvent(MouseEventType type, int x, int y) +{ + PMenu::Item *item = findItem(x, y); + if (! item) { + return; + } + + // Unmap submenu if we enter them on the same event as selecting. + if (((_item_curr == _item_list.end()) || (item != *_item_curr)) + && Config::instance()->isMenuSelectOn(type)) { + select(item, Config::instance()->isMenuEnterOn(type)); + } + + if (Config::instance()->isMenuEnterOn(type)) { + if (item->getWORef() + && (item->getWORef()->getType() == PWinObj::WO_MENU)) { + // Special case for motion, would flicker like crazy if we didn't check + if ((type != MOUSE_EVENT_MOTION) && (type != MOUSE_EVENT_MOTION_PRESSED) + && item->getWORef()->isMapped()) { + static_cast(item->getWORef())->unmapSubmenus(); + item->getWORef()->unmapWindow(); + + } else if (! item->getWORef()->isMapped()) { + // unmap previous opened submenu if any + unmapSubmenus(); + mapSubmenu(static_cast(item->getWORef())); + } + } + } + + // Submenus don't have any actions, so we don't exec ( and close them ) + if (item->getAE().action_list.size() && Config::instance()->isMenuExecOn(type)) { + exec(item); + } +} + +//! @brief Sets the position of the items and determine size of the menu +void +PMenu::buildMenu(void) +{ + // calculate geometry, if to enable scrolling etc + buildMenuCalculate(); + + // not necessary to do this if we don't have any visible items + if (_size > 0) { + // place menu items + buildMenuPlace(); + + // render items on the menu + buildMenuRender(); + } +} + +//! @brief Calculates how much space and how many rows/cols will be needed +void +PMenu::buildMenuCalculate(void) +{ + _has_submenu = false; + + // Get how many visible objects we have + unsigned int sep = 0; + list::iterator it(_item_list.begin()); + for (_size = 0; it != _item_list.end(); ++it) { + if ((*it)->getType() == PMenu::Item::MENU_ITEM_NORMAL) { + ++_size; + } else if ((*it)->getType() == PMenu::Item::MENU_ITEM_SEPARATOR) { + ++sep; + } + } + + if (_size == 0) { + return; + } + + unsigned int width = 1, height = 1; + buildMenuCalculateMaxWidth(width, height); + + // FIXME: Remove extra padding from calculation + if (_menu_width) { + _item_width_max = _menu_width; + } + + // This is the available width for drawing text on, the rest is reserved + // for submenu indicator, padding etc. + _item_width_max_avail = _item_width_max; + + // Continue add padding etc. + _item_width_max += _theme->getMenuData()->getPad(PAD_LEFT) + + _theme->getMenuData()->getPad(PAD_RIGHT); + if (Config::instance()->isDisplayMenuIcons()) { + _item_width_max += _icon_width; + } + + // If we have any submenus, increase the maximum width with arrow width + + // right pad as we are going to pad the arrow from the text too. + if (_has_submenu) { + _item_width_max += _theme->getMenuData()->getPad(PAD_RIGHT) + + _theme->getMenuData()->getTextureArrow(OBJECT_STATE_FOCUSED)->getWidth(); + } + + // Remove padding etc from avail and item width. + if (_menu_width) { + unsigned int padding = _item_width_max - _item_width_max_avail; + _item_width_max -= padding; + _item_width_max_avail -= padding; + } + + // Calculate item height + _item_height = std::max(_theme->getMenuData()->getFont(OBJECT_STATE_FOCUSED)->getHeight(), _icon_height) + + _theme->getMenuData()->getPad(PAD_UP) + + _theme->getMenuData()->getPad(PAD_DOWN); + _separator_height = _theme->getMenuData()->getTextureSeparator(OBJECT_STATE_FOCUSED)->getHeight(); + + height = (_item_height * _size) + (_separator_height * sep); + + if (_size) { + _size += sep; + } + + buildMenuCalculateColumns(width, height); + + // Check if we need to enable scrolling + _scroll = (width > PScreen::instance()->getWidth()); + + resizeChild(width, height); +} + +/** + * Get maximum item width and icon size. + */ +void +PMenu::buildMenuCalculateMaxWidth(unsigned int &width, unsigned int &height) +{ + // Calculate max item width, to be used if/when splitting a menu + // up in rows because of limited vertical space. + _item_width_max = 1; + _icon_width = _icon_height = 0; + + list::iterator it(_item_list.begin()); + for (it = _item_list.begin(); it != _item_list.end(); ++it) { + // Only include standard items + if ((*it)->getType() != PMenu::Item::MENU_ITEM_NORMAL) { + continue; + } + + // Check if we have a submenu item + if (! _has_submenu && (*it)->getWORef() && + ((*it)->getWORef()->getType() == PWinObj::WO_MENU)) { + _has_submenu = true; + } + + // Get icon height if any + if ((*it)->getIcon()) { + if ((*it)->getIcon()->getWidth() > _icon_width) { + _icon_width = (*it)->getIcon()->getWidth(); + } + if ((*it)->getIcon()->getHeight() > _icon_height) { + _icon_height = (*it)->getIcon()->getHeight(); + } + } + + width = _theme->getMenuData()->getFont(OBJECT_STATE_FOCUSED)->getWidth((*it)->getName().c_str()); + if (width > _item_width_max) { + _item_width_max = width; + } + } + + + // Make sure icon width and height are not larger than configured. + if (_icon_width) { + _icon_width = Util::between(_icon_width, + Config::instance()->getMenuIconLimit(_icon_width, WIDTH_MIN, _name), + Config::instance()->getMenuIconLimit(_icon_width, WIDTH_MAX, _name)); + _icon_height = Util::between(_icon_height, + Config::instance()->getMenuIconLimit(_icon_height, HEIGHT_MIN, _name), + Config::instance()->getMenuIconLimit(_icon_height, HEIGHT_MAX, _name)); + } +} + +/** + * Calculate number of columns, this does not apply to static width + * menus. + */ +void +PMenu::buildMenuCalculateColumns(unsigned int &width, unsigned int &height) +{ + // Check if the menu fits or is static width + if (_menu_width + || (height + getTitleHeight()) <= PScreen::instance()->getHeight()) { + _cols = 1; + width = _menu_width ? _menu_width : _item_width_max; + _rows = _size; + return; + } + + _cols = height / (PScreen::instance()->getHeight() - getTitleHeight()); + if ((height % (PScreen::instance()->getHeight() - getTitleHeight())) != 0) { + ++_cols; + } + _rows = _size / _cols; + if ((_size % _cols) != 0) { + ++_rows; + } + + width = _cols * _item_width_max; + // need to calculate max height, the one with most separators if any + if (_cols > 1) { + uint i, j, row_height; + height = 0; + + list::iterator it(_item_list.begin()); + for (i = 0, it = _item_list.begin(); i < _cols; ++i) { + row_height = 0; + for (j = 0; (j < _rows) && (it != _item_list.end()); ++it, ++j) { + switch ((*it)->getType()) { + case PMenu::Item::MENU_ITEM_NORMAL: + row_height += _item_height; + break; + case PMenu::Item::MENU_ITEM_SEPARATOR: + row_height += _separator_height; + break; + case PMenu::Item::MENU_ITEM_HIDDEN: + default: + break; + } + } + + if (row_height > height) { + height = row_height; + } + } + } +} + +//! @brief Places the items in the menu +void +PMenu::buildMenuPlace(void) +{ + uint x, y; + list::iterator it; + + x = 0; + it = _item_list.begin(); + // cols + for (uint i = 0; i < _cols; ++i) { + y = 0; + // rows + for (uint j = 0; (j < _rows) && (it != _item_list.end()); ++it) { + if ((*it)->getType() != PMenu::Item::MENU_ITEM_HIDDEN) { + (*it)->setX(x); + (*it)->setY(y); + if ((*it)->getType() == PMenu::Item::MENU_ITEM_NORMAL) { + y += _item_height; + ++j; // only count real menu items + } else if ((*it)->getType() == PMenu::Item::MENU_ITEM_SEPARATOR) { + y += _separator_height; + } + } + } + x += _item_width_max; + } +} + +//! @brief Renders focused, unfocused and selected pixmaps for menu +void +PMenu::buildMenuRender(void) +{ + buildMenuRenderState(_menu_bg_fo, OBJECT_STATE_FOCUSED); + buildMenuRenderState(_menu_bg_un, OBJECT_STATE_UNFOCUSED); + buildMenuRenderState(_menu_bg_se, OBJECT_STATE_SELECTED); + + _menu_wo->setBackgroundPixmap(_focused ? _menu_bg_fo : _menu_bg_un); + _menu_wo->clear(); +} + +//! @brief Renders menu content on pix, with state state +void +PMenu::buildMenuRenderState(Pixmap &pix, ObjectState state) +{ + PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler(); + + // get a fresh pixmap for the menu + pm->returnPixmap(pix); + pix = pm->getPixmap(getChildWidth(), getChildHeight(), + PScreen::instance()->getDepth()); + + PTexture *tex; + PFont *font; + + tex = _theme->getMenuData()->getTextureMenu(state); + tex->render(pix, 0, 0, getChildWidth(), getChildHeight()); + + font = _theme->getMenuData()->getFont(state); + font->setColor(_theme->getMenuData()->getColor(state)); + + list::iterator it(_item_list.begin()); + for (; it != _item_list.end(); ++it) { + if ((*it)->getType() != PMenu::Item::MENU_ITEM_HIDDEN) { + buildMenuRenderItem(pix, state, *it); + } + } +} + +//! @brief Renders item on pix, with state state +void +PMenu::buildMenuRenderItem(Pixmap pix, ObjectState state, PMenu::Item *item) +{ + PTexture *tex; + Theme::PMenuData *md = _theme->getMenuData(); + + if (item->getType() == PMenu::Item::MENU_ITEM_NORMAL) { + tex = md->getTextureItem(state); + tex->render(pix, item->getX(), item->getY(), _item_width_max, _item_height); + + uint start_x, start_y, icon_width, icon_height; + // If entry has an icon, draw it + if (item->getIcon() && Config::instance()->isDisplayMenuIcons()) { + icon_width = Util::between(item->getIcon()->getWidth(), + Config::instance()->getMenuIconLimit(_icon_width, WIDTH_MIN, _name), + Config::instance()->getMenuIconLimit(_icon_width, WIDTH_MAX, _name)); + + icon_height = Util::between(item->getIcon()->getHeight(), + Config::instance()->getMenuIconLimit(_icon_height, HEIGHT_MIN, _name), + Config::instance()->getMenuIconLimit(_icon_height, HEIGHT_MAX, _name)); + + start_x = item->getX() + md->getPad(PAD_LEFT) + (_icon_width - icon_width) / 2; + start_y = item->getY() + (_item_height - icon_height) / 2; + item->getIcon()->render(pix, start_x, start_y, icon_width, icon_height); + } else { + icon_width = 0; + icon_height = 0; + } + + // If entry has a submenu, lets draw our submenu "arrow" + if (item->getWORef() && (item->getWORef()->getType() == PWinObj::WO_MENU)) { + tex = md->getTextureArrow(state); + uint arrow_width = tex->getWidth(); + uint arrow_height = tex->getHeight(); + uint arrow_y = static_cast((_item_height / 2) - (arrow_height / 2)); + + start_x = item->getX() + _item_width_max - arrow_width - md->getPad(PAD_RIGHT); + start_y = item->getY() + arrow_y; + tex->render(pix, start_x, start_y, arrow_width, arrow_height); + } + + PFont *font = md->getFont(state); + start_x = item->getX() + md->getPad(PAD_LEFT); + // Add icon width to starting x position if frame icons are enabled. + if (Config::instance()->isDisplayMenuIcons()) { + start_x += _icon_width; + } + + start_y = item->getY() + md->getPad(PAD_UP) + + (_item_height - font->getHeight() - md->getPad(PAD_UP) - md->getPad(PAD_DOWN)) / 2; + + // Render item text. + font->draw(pix, start_x, start_y, item->getName().c_str(), 0, _item_width_max_avail); + + } else if ((item->getType() == PMenu::Item::MENU_ITEM_SEPARATOR) && + (state < OBJECT_STATE_SELECTED)) { + tex = md->getTextureSeparator(state); + tex->render(pix, item->getX(), item->getY(), _item_width_max, _separator_height); + } +} + +#define COPY_ITEM_AREA(ITEM, PIX) \ + XCopyArea(_dpy, PIX, _menu_wo->getWindow(), PScreen::instance()->getGC(), \ + (ITEM)->getX(), (ITEM)->getY(), _item_width_max, _item_height, \ + (ITEM)->getX(), (ITEM)->getY()); + +//! @brief Renders item as selected +//! @param item Item to select +//! @param unmap_submenu Defaults to true +void +PMenu::selectItem(std::list::iterator item, bool unmap_submenu) +{ + if (_item_curr == item) { + return; + } + + deselectItem(unmap_submenu); + _item_curr = item; + + if (_mapped) { + COPY_ITEM_AREA((*item), _menu_bg_se); + } +} + +//! @brief Deselects selected item +//! @param unmap_submenu Defaults to true +void +PMenu::deselectItem(bool unmap_submenu) +{ + // deselect previous item + if ((_item_curr != _item_list.end()) + && ((*_item_curr)->getType() != PMenu::Item::MENU_ITEM_HIDDEN)) { + if (_mapped) + COPY_ITEM_AREA((*_item_curr), (_focused ? _menu_bg_fo : _menu_bg_un)); + + if (unmap_submenu && (*_item_curr)->getWORef() + && ((*_item_curr)->getWORef()->getType() == PWinObj::WO_MENU)) { + static_cast((*_item_curr)->getWORef())->unmapSubmenus(); + (*_item_curr)->getWORef()->unmapWindow(); + } + } +} + +#undef COPY_ITEM_AREA + +//! @brief Selects next item ( wraps ). First item if none is selected. +void +PMenu::selectNextItem(void) +{ + if (_size == 0) { + return; + } + + list::iterator item(_item_curr); + + // no item selected, select the first item + if (item == _item_list.end()) { + item = _item_list.begin(); + + // select next item, wrap if needed + } else { + ++item; + if (item == _item_list.end()) { + item = _item_list.begin(); + } + } + + // skip to next if separator/hidden + if ((*item)->getType() != PMenu::Item::MENU_ITEM_NORMAL) { + deselectItem(); // otherwise, last selected won't get deselcted + _item_curr = item; + selectNextItem(); + } else { + selectItem(item); + } +} + +//! @brief Selects previous item ( wraps ). Last item if none is selected. +void +PMenu::selectPrevItem(void) +{ + if (_size == 0) { + return; + } + + list::iterator item( _item_curr); + + // no item selected, select the last item OR + // we're at the beginning and need to wrap to the end + if ((item == _item_list.end()) || (item == _item_list.begin())) { + item = _item_list.end(); + } + --item; + + // skip to prev if separator/hidden + if ((*item)->getType() != PMenu::Item::MENU_ITEM_NORMAL) { + deselectItem(); // otherwise, last selected won't get deselcted + _item_curr = item; + selectPrevItem(); + } else { + selectItem(item); + } +} + +//! @brief Sets title of the menu/decor +void +PMenu::setTitle(const std::wstring &title) +{ + _title.setReal(title); + + // Apply title rules to allow title rewriting + applyTitleRules(title); +} + +/** + * Applies title rules to menu. + */ +void +PMenu::applyTitleRules(const std::wstring &title) +{ + _class_hint.title = title; + TitleProperty *data = AutoProperties::instance()->findTitleProperty(&_class_hint); + + if (data) { + wstring new_title(title); + if (data->getTitleRule().ed_s(new_title)) { + _title.setCustom(new_title); + } + } +} + +//! @brief Inserts item into the menu ( without rebuilding ) +void +PMenu::insert(PMenu::Item *item) +{ + checkItemWORef(item); + _item_list.push_back(item); +} + +//! @brief Creates and inserts Item +//! @param name Name of objet to create and insert +//! @param wo_ref PWinObj to refer to, defaults to 0 +void +PMenu::insert(const std::wstring &name, PWinObj *wo_ref, PTexture *icon) +{ + PMenu::Item *item; + + item = new PMenu::Item(name, wo_ref, icon); + + insert(item); +} + +//! @brief Creates and inserts Item +//! @param name Name of object to create and insert +//! @param ae ActionEvent for the object +//! @param wo_ref PWinObj to refer to, defaults to 0 +void +PMenu::insert(const std::wstring &name, const ActionEvent &ae, PWinObj *wo_ref, PTexture *icon) +{ + PMenu::Item *item; + + item = new PMenu::Item(name, wo_ref, icon); + item->setAE(ae); + + insert(item); +} + +//! @brief Removes an item from the menu, without rebuilding. +void +PMenu::remove(PMenu::Item *item) +{ + if (! item) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PMenu(" << this << ")::remove(" << item << ")" << endl + << " *** item == 0" << endl; +#endif // DEBUG + return; + } + + if ((_item_curr != _item_list.end()) && (item == *_item_curr)) { + _item_curr = _item_list.end(); + } + + delete item; + _item_list.remove(item); +} + +//! @brief Removes all items from the menu, without rebuilding. +void +PMenu::removeAll(void) +{ + list::iterator it(_item_list.begin()); + for (; it != _item_list.end(); ++it) { + delete *it; + } + _item_list.clear(); + _item_curr = _item_list.end(); +} + +//! @brief Places the menu under the mouse and maps it. +void +PMenu::mapUnderMouse(void) +{ + int x, y; + + PScreen::instance()->getMousePosition(x, y); + + // this might seem a bit silly but the menu won't get updated before + // it has been mapped (if dynamic) so we're doing it twice to reduce the + // "flickering" risk but it's not 100% so it's done twice. + makeInsideScreen(x, y); + mapWindowRaised(); + makeInsideScreen(x, y); +} + +//! @brief Maps menu relative to the this menu +//! @param menu Submenu to map +//! @param focus Give input focus and select first item. Defaults to false. +void +PMenu::mapSubmenu(PMenu *menu, bool focus) +{ + int x, y; + + x = getRX(); + if (_item_curr != _item_list.end()) { + y = _gm.y + (*_item_curr)->getY(); + } else { + y = _gm.y; + } + + // this might seem a bit silly but the menu won't get updated before + // it has been mapped (if dynamic) so we're doing it twice to reduce the + // "flickering" risk but it's not 100% so it's done twice. + menu->makeInsideScreen(x, y); + menu->mapWindowRaised(); + menu->makeInsideScreen(x, y); + + if (focus) { + menu->giveInputFocus(); + menu->selectItemNum(0); + } +} + +//! @brief Unmaps all ( recursive ) submenus open under this menu +void +PMenu::unmapSubmenus(void) +{ + list::iterator it(_item_list.begin()); + for (; it != _item_list.end(); ++it) { + if ((*it)->getWORef() && (*it)->getWORef()->getType() == PWinObj::WO_MENU) { + // Sub-menus will be deleted when unmapping this, so no need + // to continue. + static_cast((*it)->getWORef())->unmapSubmenus(); + (*it)->getWORef()->unmapWindow(); + } + } +} + +//! @brief Unmaps all menus belonging to this menu +void +PMenu::unmapAll(void) +{ + if (_menu_parent) { + _menu_parent->unmapAll(); + } else { + unmapSubmenus(); + unmapWindow(); + } +} + +//! @brief Gives input focus to parent and unmaps submenus +void +PMenu::gotoParentMenu(void) +{ + if (! _menu_parent) { + return; + } + + _menu_parent->unmapSubmenus(); + _menu_parent->giveInputFocus(); +} + +//! @brief Selects item, if 0/not in list current item is deselected +//! @param item Item to select +//! @param unmap_submenu Defaults to true +void +PMenu::select(PMenu::Item *item, bool unmap_submenu) +{ + selectItem(find(_item_list.begin(), _item_list.end(), item), unmap_submenu); +} + +//! @brief Selects item number num in menu +void +PMenu::selectItemNum(uint num) +{ + if (num > _item_list.size()) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PMenu(" << this << ")::selectItem(" << num << ")" + << " *** num > _item_list_size()[" << _item_list.size() + << "]" << endl; +#endif // DEBUG + return; + } + + list::iterator it(_item_list.begin()); + for (uint i = 0; i < num; ++i, ++it) + ; + + selectItem(it); +} + +//! @brief Selects item relative to the selected +void +PMenu::selectItemRel(int off) +{ + if (off == 0) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PMenu(" << this << ")::selectItemRel(" << off << ")" + << " *** off == 0" << endl; +#endif // DEBUG + return; + } + + // if no selected item, use first + list::iterator it((_item_curr == _item_list.end()) ? _item_list.begin() : _item_curr); + + int dir = (off > 0) ? 1 : -1; + off = abs(off); + + for (int i = 0; i < off; ++i) { + if (dir == 1) { // forward + if (++it == _item_list.end()) { + it = _item_list.begin(); + } + + } else { // backward + if (it == _item_list.begin()) { + it = _item_list.end(); + } + --it; + } + } + + selectItem(it); +} + +//! @brief Executes items action, sending it to the parent menu if availible +void +PMenu::exec(PMenu::Item *item) +{ + if (_menu_parent) { + _menu_parent->exec(item); + } else { + handleItemExec(item); + if (! _sticky) { + unmapAll(); + } + } +} + +//! @brief Sets up children _menu_parent field, if item's _wo_ref is a menu +void +PMenu::checkItemWORef(PMenu::Item *item) +{ + if (item->getWORef() && + (item->getWORef()->getType() == PWinObj::WO_MENU)) { + PMenu *child = static_cast(item->getWORef()); + child->_menu_parent = this; + } +} + +//! @brief Searches for item at x, y +PMenu::Item* +PMenu::findItem(int x, int y) +{ + list::iterator it(_item_list.begin()); + for (; it != _item_list.end(); ++it) { + if (((*it)->getType() == PMenu::Item::MENU_ITEM_NORMAL) && + (x >= (*it)->getX()) && (x <= signed((*it)->getX() + _item_width_max)) && + (y >= (*it)->getY()) && (y <= signed((*it)->getY() + _item_height))) { + return *it; + } + } + return 0; +} + +//! @brief Moves the menu relative to it's parent to make it fit on screen +//! @param x Use x instead of _gm.x ( optional ) +//! @param y Use y instead of _gm.y ( optional ) +void +PMenu::makeInsideScreen(int x, int y) +{ + Geometry head; + PScreen::instance()->getHeadInfo(PScreen::instance()->getCurrHead(), head); + + x = (x == -1) ? _gm.x : x; + y = (y == -1) ? _gm.y : y; + + // we map on submenus on the right side so this only happens on the + // top-level menu + if (x < head.x) { + x = head.x; + } else if ((x + _gm.width) > (head.x + head.width)) { + if (_menu_parent) { + x = _menu_parent->_gm.x - _gm.width; // not using getX(), refers to child + } else { + x = head.x + head.width - _gm.width; + } + } + + if (y < head.y) { + y = head.y; + } else if ((y + _gm.height) > (head.y + head.height)) { + y = head.y + head.height - _gm.height; + } + + move(x, y); +} diff --git a/pekwm/PMenu.hh b/pekwm/PMenu.hh new file mode 100644 index 0000000..be9837c --- /dev/null +++ b/pekwm/PMenu.hh @@ -0,0 +1,188 @@ +// +// PMenu.hh for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _PMENU_HH_ +#define _PMENU_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include + +#include "pekwm.hh" +#include "AutoProperties.hh" +#include "CfgParser.hh" +#include "PDecor.hh" +#include "PWinObjReference.hh" + +class PTexture; +class ActionEvent; +class Theme; + +class PMenu : public PDecor { +public: + class Item : public PWinObjReference { + public: + enum Type { + MENU_ITEM_NORMAL, MENU_ITEM_SEPARATOR, MENU_ITEM_HIDDEN + }; + Item(const std::wstring &name, PWinObj *wo_ref = 0, PTexture *icon = 0); + virtual ~Item(void); + + inline int getX(void) const { return _x; } + inline int getY(void) const { return _y; } + inline const std::wstring &getName(void) const { return _name; } + inline const ActionEvent &getAE(void) const { return _ae; } + inline PTexture *getIcon(void) { return _icon; } + inline PMenu::Item::Type getType(void) const { return _type; } + + inline void setX(int x) { _x = x; } + inline void setY(int y) { _y = y; } + inline void setName(const std::wstring &name) { _name = name; } + inline void setAE(const ActionEvent &ae) { _ae = ae; } + inline void setType(PMenu::Item::Type type) { _type = type; } + + inline void setCreator(PMenu::Item *c) { _creator = c; } + inline PMenu::Item *getCreator(void) const { return _creator; } + + private: + int _x, _y; + std::wstring _name; + + ActionEvent _ae; // used for specifying action of the entry + + PMenu::Item::Type _type; // normal, separator or hidden item + PTexture *_icon; // icon texture. + + PMenu::Item *_creator; /**< pointer to the dynamic action item + that created this item. */ + }; + + PMenu(Theme *theme, const std::wstring &title, + const std::string &name, const std::string decor_name = "MENU"); + virtual ~PMenu(void); + + // START - PWinObj interface. + virtual void unmapWindow(void); + + virtual void setFocused(bool focused); + virtual void setFocusable(bool focusable); + + virtual ActionEvent *handleButtonPress(XButtonEvent *ev); + virtual ActionEvent *handleButtonRelease(XButtonEvent *ev); + virtual ActionEvent *handleMotionEvent(XMotionEvent *ev); + virtual ActionEvent *handleEnterEvent(XCrossingEvent *ev); + virtual ActionEvent *handleLeaveEvent(XCrossingEvent *ev); + // END - PWinObj interface. + + // START - PDecor interface. + virtual void loadTheme(void); + // END - PDecor interface. + + static inline PMenu *findMenu(Window win) { + std::map::iterator it = _menu_map.find(win); + return (it != _menu_map.end()) ? it->second : 0; + } + + inline const std::string &getName(void) { return _name; } + inline PMenu::Item *getItemCurr(void) { return (_item_curr == _item_list.end()) ? 0 : *_item_curr; } + void selectNextItem(void); + void selectPrevItem(void); + + // modifying menu content + void setTitle(const std::wstring &title); + void setMenuWidth(uint width) { _menu_width = width; } + + virtual void insert(PMenu::Item *item); + virtual void insert(const std::wstring &name, PWinObj *wo_ref = 0, PTexture *icon = 0); + virtual void insert(const std::wstring &name, const ActionEvent &ae, + PWinObj *wo_ref = 0, PTexture *icon = 0); + virtual void remove(PMenu::Item *item); + virtual void removeAll(void); + + virtual void reload(CfgParser::Entry *section) { } + void buildMenu(void); + + inline uint size(void) const { return _item_list.size(); } + inline std::list::iterator m_begin(void) { return _item_list.begin(); } + inline std::list::iterator m_end(void) { return _item_list.end(); } + + inline MenuType getMenuType(void) const { return _menu_type; } + + virtual void handleItemExec(PMenu::Item *item) { } + + // control ( mapping, unmapping etc ) + void mapUnderMouse(void); + void mapSubmenu(PMenu *menu, bool focus = false); + void unmapSubmenus(void); + void unmapAll(void); + void gotoParentMenu(void); + + void select(PMenu::Item *item, bool unmap_submenu = true); + void selectItem(std::list::iterator item, bool unmap_submenu = true); + void deselectItem(bool unmap_submenu = true); + void selectItemNum(uint num); + void selectItemRel(int off); + void exec(PMenu::Item *item); + +protected: + void checkItemWORef(PMenu::Item *item); + +private: + void handleItemEvent(MouseEventType type, int x, int y); + + void buildMenuCalculate(void); + void buildMenuCalculateMaxWidth(unsigned int &width, unsigned int &height); + void buildMenuCalculateColumns(unsigned int &width, unsigned int &height); + void buildMenuPlace(void); + void buildMenuRender(void); + void buildMenuRenderState(Pixmap &pix, ObjectState state); + void buildMenuRenderItem(Pixmap pix, ObjectState state, PMenu::Item *item); + + PMenu::Item *findItem(int x, int y); + void makeInsideScreen(int x, int y); + + void applyTitleRules(const std::wstring &title); + +protected: + std::string _name; //!< Name of menu, must be unique + MenuType _menu_type; //!< Type of menu + PMenu *_menu_parent; + + ClassHint _class_hint; /**< Class information for menu. */ + + // menu content data + std::list _item_list; + std::list::iterator _item_curr; + +private: + static std::map _menu_map; + + PWinObj *_menu_wo; + PDecor::TitleItem _title; + + // menu render data + Pixmap _menu_bg_fo, _menu_bg_un, _menu_bg_se; + + // menu disp data + uint _menu_width; /**< Static set menu width. */ + uint _item_height, _item_width_max, _item_width_max_avail; + uint _icon_width; + uint _icon_height; + uint _separator_height; + + uint _size; // size, hidden items excluded + uint _rows, _cols; + bool _scroll; + bool _has_submenu; +}; + +#endif // _PMENU_HH_ diff --git a/pekwm/PScreen.cc b/pekwm/PScreen.cc new file mode 100644 index 0000000..516f269 --- /dev/null +++ b/pekwm/PScreen.cc @@ -0,0 +1,709 @@ +// +// PScreen.cc for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include +#include // required for memset in FD_ZERO + +#ifdef HAVE_LIMITS +#include +using std::numeric_limits; +#endif // HAVE_LIMITS + +extern "C" { +#include +#ifdef HAVE_SHAPE +#include +#include +#endif // HAVE_SHAPE +#ifdef HAVE_XRANDR +#include +#endif // HAVE_XRANDR +#include // For XK_ entries +#include + +#ifdef DEBUG +bool xerrors_ignore = false; +#endif // DEBUG + +unsigned int xerrors_count = 0; +} + +#include "PScreen.hh" +// FIXME: Remove when strut handling is moved away from here. +#include "PWinObj.hh" +#include "ManagerWindows.hh" + +using std::cerr; +using std::endl; +using std::vector; +using std::list; +using std::map; +using std::string; +using std::memset; // required for FD_ZERO + +const uint PScreen::MODIFIER_TO_MASK[] = { + ShiftMask, LockMask, ControlMask, + Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask +}; +const uint PScreen::MODIFIER_TO_MASK_NUM = sizeof(PScreen::MODIFIER_TO_MASK) / sizeof(PScreen::MODIFIER_TO_MASK[0]); + +PScreen* PScreen::_instance = 0; + + +extern "C" { + /** + * XError handler, prints error. + */ + static int + handleXError(Display *dpy, XErrorEvent *ev) + { + ++xerrors_count; + +#ifdef DEBUG + if (xerrors_ignore) { + return 0; + } + + char error_buf[256]; + XGetErrorText(dpy, ev->error_code, error_buf, 256); + cerr << "XError: " << error_buf << " id: " << ev->resourceid << endl; +#endif // DEBUG + + return 0; + } +} + +//! @brief PScreen::Visual constructor. +//! @param x_visual X Visual to wrap. +PScreen::PVisual::PVisual(Visual *x_visual) : _x_visual(x_visual), + _r_shift(0), _r_prec(0), + _g_shift(0), _g_prec(0), + _b_shift(0), _b_prec(0) +{ + getShiftPrecFromMask(_x_visual->red_mask, _r_shift, _r_prec); + getShiftPrecFromMask(_x_visual->green_mask, _g_shift, _g_prec); + getShiftPrecFromMask(_x_visual->blue_mask, _b_shift, _b_prec); +} + +//! @brief PScreen::Visual destructor. +PScreen::PVisual::~PVisual(void) +{ +} + +//! @brief Gets shift and prec from mask. +//! @param mask red,green,blue mask of Visual. +//! @param shift Set to the shift of mask. +//! @param prec Set to the prec of mask. +void +PScreen::PVisual::getShiftPrecFromMask(ulong mask, int &shift, int &prec) +{ + for (shift = 0; ! (mask&0x1); ++shift) { + mask >>= 1; + } + + for (prec = 0; (mask&0x1); ++prec) { + mask >>= 1; + } +} + +//! @brief PScreen constructor +PScreen::PScreen(Display *dpy, bool honour_randr) + : _honour_randr(honour_randr), _fd(-1), + _screen(-1), _depth(-1), + _root(None), _visual(0), _colormap(None), + _modifier_map(0), + _has_extension_shape(false), _event_shape(-1), + _has_extension_xinerama(false), + _has_extension_xrandr(false), _event_xrandr(-1), + _server_grabs(0), _last_event_time(0), _last_click_id(None) +{ + if (_instance) { + throw string("PScreen, trying to create multiple instances"); + } + _instance = this; + + XSetErrorHandler(handleXError); + + _dpy = dpy; + + XGrabServer(_dpy); + + _fd = ConnectionNumber(dpy); + _screen = DefaultScreen(_dpy); + _root = RootWindow(_dpy, _screen); + + _depth = DefaultDepth(_dpy, _screen); + _visual = new PScreen::PVisual(DefaultVisual(_dpy, _screen)); + _colormap = DefaultColormap(_dpy, _screen); + _modifier_map = XGetModifierMapping(_dpy); + + _screen_gm.width = WidthOfScreen(ScreenOfDisplay(_dpy, _screen)); + _screen_gm.height = HeightOfScreen(ScreenOfDisplay(_dpy, _screen)); + +#ifdef HAVE_SHAPE + { + int dummy_error; + _has_extension_shape = XShapeQueryExtension(_dpy, &_event_shape, &dummy_error); + } +#endif // HAVE_SHAPE + +#ifdef HAVE_XRANDR + { + int dummy_error; + _has_extension_xrandr = XRRQueryExtension(_dpy, &_event_xrandr, &dummy_error); + } +#endif // HAVE_XRANDR + + // Now screen geometry has been read and extensions have been + // looked for, read head information. + initHeads(); + + // initialize array values + for (uint i = 0; i < (BUTTON_NO - 1); ++i) { + _last_click_time[i] = 0; + } + + // Figure out what keys the Num and Scroll Locks are + setLockKeys(); + + XSync(_dpy, false); + XUngrabServer(_dpy); +} + +//! @brief PScreen destructor +PScreen::~PScreen(void) { + delete _visual; + + if (_modifier_map) { + XFreeModifiermap(_modifier_map); + } + + _instance = 0; +} + +/** + * Figure out what keys the Num and Scroll Locks are + */ +void +PScreen::setLockKeys(void) +{ + _num_lock = getMaskFromKeycode(XKeysymToKeycode(_dpy, XK_Num_Lock)); + _scroll_lock = getMaskFromKeycode(XKeysymToKeycode(_dpy, XK_Scroll_Lock)); +} + +//! @brief Get next event using select to avoid signal blocking +//! @param ev Event to fill in. +//! @return true if event was fetched, else false. +bool +PScreen::getNextEvent(XEvent &ev) +{ + if (XPending(_dpy) > 0) { + XNextEvent(_dpy, &ev); + return true; + } + + int ret; + fd_set rfds; + + XFlush(_dpy); + + FD_ZERO(&rfds); + FD_SET(_fd, &rfds); + + ret = select(_fd + 1, &rfds, 0, 0, 0); + if (ret > 0) { + XNextEvent(_dpy, &ev); + } + + return ret > 0; +} + +//! @brief Grabs the server, counting number of grabs +bool +PScreen::grabServer(void) +{ + if (_server_grabs == 0) { + XGrabServer(_dpy); + } + + ++_server_grabs; + return (_server_grabs == 1); // was actually grabbed +} + +//! @brief Ungrabs the server, counting number of grabs +bool +PScreen::ungrabServer(bool sync) +{ + if (_server_grabs > 0) { + --_server_grabs; + + if (_server_grabs == 0) { // no more grabs left + if (sync) { + XSync(_dpy, false); + } + XUngrabServer(_dpy); + } + } + return (_server_grabs == 0); // is actually ungrabbed +} + +//! @brief Grabs the keyboard +bool +PScreen::grabKeyboard(Window win) +{ + if (XGrabKeyboard(_dpy, win, false, GrabModeAsync, GrabModeAsync, + CurrentTime) == GrabSuccess) { + return true; + } +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PScreen(" << this << ")::grabKeyboard(" << win << ")" << endl + << " *** unable to grab keyboard." << endl; +#endif // DEBUG + return false; +} + +//! @brief Ungrabs the keyboard +bool +PScreen::ungrabKeyboard(void) +{ + XUngrabKeyboard(_dpy, CurrentTime); + return true; +} + +//! @brief Grabs the pointer +bool +PScreen::grabPointer(Window win, uint event_mask, Cursor cursor) +{ + if (XGrabPointer(_dpy, win, false, event_mask, GrabModeAsync, GrabModeAsync, + None, cursor, CurrentTime) == GrabSuccess) { + return true; + } +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PScreen(" << this << ")::grabPointer(" << win << "," + << event_mask << "," << cursor << ")" << endl + << " *** unable to grab pointer." << endl; +#endif // DEBUG + return false; +} + +//! @brief Ungrabs the pointer +bool +PScreen::ungrabPointer(void) +{ + XUngrabPointer(_dpy, CurrentTime); + return true; +} + +//! @brief Refetches the root-window size. +void +PScreen::updateGeometry(uint width, uint height) +{ +#ifdef HAVE_XRANDR + if (! _honour_randr || ! _has_extension_xrandr) { + return; + } + + // The screen has changed geometry in some way. To handle this the + // head information is read once again, the root window is re sized + // and strut information is updated. + initHeads(); + + _screen_gm.width = width; + _screen_gm.height = height; + PWinObj::getRootPWinObj()->resize(width, height); + + updateStrut(); +#endif // HAVE_XRANDR +} + +//! @brief Searches for the head closest to the coordinates x,y. +//! @return The nearest head. Head numbers are indexed from 0. +uint +PScreen::getNearestHead(int x, int y) +{ + if(_heads.size() > 1) { + // set distance to the highest uint value +#ifdef HAVE_LIMITS + uint min_distance = numeric_limits::max(); +#else //! HAVE_LIMITS + uint min_distance = ~0; +#endif // HAVE_LIMITS + uint nearest_head = 0; + + uint distance; + int head_t, head_b, head_l, head_r; + for(uint head = 0; head < _heads.size(); ++head) { + head_t = _heads[head].y; + head_b = _heads[head].y + _heads[head].height; + head_l = _heads[head].x; + head_r = _heads[head].x + _heads[head].width; + + if(x > head_r) { + if(y < head_t) { + // above and right of the head + distance = calcDistance(x, y, head_r, head_t); + } else if(y > head_b) { + // below and right of the head + distance = calcDistance(x, y, head_r, head_b); + } else { + // right of the head + distance = calcDistance(x, head_r); + } + } else if(x < head_l) { + if(y < head_t) { + // above and left of the head + distance = calcDistance(x, y, head_l, head_t); + } else if(y > head_b) { + // below and left of the head + distance = calcDistance(x, y, head_l, head_b); + } else { + // left of the head + distance = calcDistance(x, head_l); + } + } else { + if(y < head_t) { + // above the head + distance = calcDistance(y, head_t); + } else if(y > head_b) { + // below the head + distance = calcDistance(y, head_b); + } else { + // on the head + return head; + } + } + +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": PScreen::getNearestHead( " << x << "," << y << ") " + << "head boundaries " << head_t << "," << head_b << "," << head_l << "," << head_r << " " + << "distance " << distance << " min_distance " << min_distance << endl; +#endif // DEBUG + + if(distance < min_distance) { + min_distance = distance; + nearest_head = head; + } + } + return nearest_head; + } else { + return 0; + } +} + +//! @brief Searches for the head that the pointer currently is on. +//! @return Active head number +uint +PScreen::getCurrHead(void) +{ + uint head = 0; + + if (_heads.size() > 1) { + int x = 0, y = 0; + getMousePosition(x, y); + head = getNearestHead(x, y); +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": PScreen::getCurrHead() got head " + << head << " from mouse position " << x << "," << y << endl; +#endif // DEBUG + } + + return head; +} + +//! @brief Fills head_info with info about head nr head +//! @param head Head number to examine +//! @param head_info Returning info about the head +//! @return true if xinerama is off or head exists. +bool +PScreen::getHeadInfo(uint head, Geometry &head_info) +{ + if (head < _heads.size()) { + head_info.x = _heads[head].x; + head_info.y = _heads[head].y; + head_info.width = _heads[head].width; + head_info.height = _heads[head].height; + return true; + } else { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": Head: " << head << " doesn't exist!" << endl; +#endif // DEBUG + return false; + } +} + +/** + * Same as getHeadInfo but returns Geometry instead of filling it in. + */ +Geometry +PScreen::getHeadGeometry(uint head) +{ + Geometry gm(_screen_gm); + getHeadInfo(head, gm); + return gm; +} + +//! @brief Fill information about head and the strut. +void +PScreen::getHeadInfoWithEdge(uint num, Geometry &head) +{ + if (! getHeadInfo(num, head)) { + return; + } + + int strut_val; + Strut strut(_heads[num].strut); // Convenience + + // Remove the strut area from the head info + strut_val = (head.x == 0) ? std::max(_strut.left, strut.left) : strut.left; + head.x += strut_val; + head.width -= strut_val; + + strut_val = ((head.x + head.width) == _screen_gm.width) ? std::max(_strut.right, strut.right) : strut.right; + head.width -= strut_val; + + strut_val = (head.y == 0) ? std::max(_strut.top, strut.top) : strut.top; + head.y += strut_val; + head.height -= strut_val; + + strut_val = (head.y + head.height == _screen_gm.height) ? std::max(_strut.bottom, strut.bottom) : strut.bottom; + head.height -= strut_val; +} + +void +PScreen::getMousePosition(int &x, int &y) +{ + Window d_root, d_win; + int win_x, win_y; + uint mask; + + XQueryPointer(_dpy, _root, &d_root, &d_win, &x, &y, &win_x, &win_y, &mask); +} + +uint +PScreen::getButtonFromState(uint state) +{ + uint button = 0; + + if (state&Button1Mask) + button = BUTTON1; + else if (state&Button2Mask) + button = BUTTON2; + else if (state&Button3Mask) + button = BUTTON3; + else if (state&Button4Mask) + button = BUTTON4; + else if (state&Button5Mask) + button = BUTTON5; + + return button; +} + +//! @brief Adds a strut to the strut list, updating max strut sizes +void +PScreen::addStrut(Strut *strut) +{ + assert(strut); + _strut_list.push_back(strut); + + updateStrut(); +} + +//! @brief Removes a strut from the strut list +void +PScreen::removeStrut(Strut *strut) +{ + assert(strut); + _strut_list.remove(strut); + + updateStrut(); +} + +//! @brief Updates strut max size. +void +PScreen::updateStrut(void) +{ + // Reset strut data. + _strut.left = 0; + _strut.right = 0; + _strut.top = 0; + _strut.bottom = 0; + + for (vector::iterator it(_heads.begin()); it != _heads.end(); ++it) { + it->strut.left = 0; + it->strut.right = 0; + it->strut.top = 0; + it->strut.bottom = 0; + } + + Strut *strut; + for(list::iterator it(_strut_list.begin()); it != _strut_list.end(); ++it) { + if ((*it)->head < 0) { + strut = &_strut; + } else if (static_cast((*it)->head) < _heads.size()) { + strut = &(_heads[(*it)->head].strut); + } else { + continue; + } + + if (strut->left < (*it)->left) { + strut->left = (*it)->left; + } + if (strut->right < (*it)->right) { + strut->right = (*it)->right; + } + if (strut->top < (*it)->top) { + strut->top = (*it)->top; + } + if (strut->bottom < (*it)->bottom) { + strut->bottom = (*it)->bottom; + } + } + + // Update hints on the root window + Geometry workarea(_strut.left, _strut.top, + _screen_gm.width - _strut.left - _strut.right, _screen_gm.height - _strut.top - _strut.bottom); + + static_cast(PWinObj::getRootPWinObj())->setEwmhWorkarea(workarea); +} + +//! @brief Initialize head information +void +PScreen::initHeads(void) +{ + _heads.clear(); + + // Read head information, randr has priority over xinerama then + // comes ordinary X11 information. + + initHeadsRandr(); + if (! _heads.size()) { + initHeadsXinerama(); + + if (! _heads.size()) { + _heads.push_back(Head(0, 0, _screen_gm.width, _screen_gm.height)); + } + } +} + +//! @brief Initialize head information from Xinerama +void +PScreen::initHeadsXinerama(void) +{ +#ifdef HAVE_XINERAMA + // Check if there are heads already initialized from example Randr + if (! XineramaIsActive(_dpy)) { + return; + } + + int num_heads = 0; + XineramaScreenInfo *infos = XineramaQueryScreens(_dpy, &num_heads); + + for (int i = 0; i < num_heads; ++i) { + _heads.push_back(Head(infos[i].x_org, infos[i].y_org, infos[i].width, infos[i].height)); + } + + XFree(infos); +#endif // HAVE_XINERAMA +} + +//! @brief Initialize head information from RandR +void +PScreen::initHeadsRandr(void) +{ +#ifdef HAVE_XRANDR + if (! _honour_randr || ! _has_extension_xrandr) { + return; + } + + XRRScreenResources *resources = XRRGetScreenResources(_dpy, _root); + if (! resources) { + return; + } + + for (int i = 0; i < resources->noutput; ++i) { + XRROutputInfo *output = XRRGetOutputInfo(_dpy, resources, resources->outputs[i]); + + if (output->crtc) { + XRRCrtcInfo *crtc = XRRGetCrtcInfo(_dpy, resources, output->crtc); + + _heads.push_back(Head(crtc->x, crtc->y, crtc->width, crtc->height)); +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": PScreen::initHeadsRandr() added head " + << crtc->x << "," << crtc->y << "," << crtc->width << "," << crtc->height << endl; +#endif // DEBUG + + XRRFreeCrtcInfo (crtc); + } + + XRRFreeOutputInfo (output); + } + + XRRFreeScreenResources (resources); +#endif // HAVE_XRANDR +} + +/** + * Lookup mask from keycode. + * + * @param keycode KeyCode to lookup. + * @return Mask for keycode, 0 if something fails. + */ +uint +PScreen::getMaskFromKeycode(KeyCode keycode) +{ + // Make sure modifier mappings were looked up ok + if (! _modifier_map || _modifier_map->max_keypermod < 1) { + return 0; + } + + // .h files states that modifiermap is an 8 * max_keypermod array. + int max_info = _modifier_map->max_keypermod * 8; + for (int i = 0; i < max_info; ++i) { + if (_modifier_map->modifiermap[i] == keycode) { + return MODIFIER_TO_MASK[i / _modifier_map->max_keypermod]; + } + } + + return 0; +} + +/** + * Figure out what key you can press to generate mask + * + * @param mask Modifier mask to get keycode for. + * @return KeyCode for mask, 0 if failing. + */ +KeyCode +PScreen::getKeycodeFromMask(uint mask) +{ + // Make sure modifier mappings were looked up ok + if (! _modifier_map || _modifier_map->max_keypermod < 1) { + return 0; + } + + for (int i = 0; i < 8; ++i) { + if (MODIFIER_TO_MASK[i] == mask) { + // FIXME: Is iteration over the range required? + return _modifier_map->modifiermap[i * _modifier_map->max_keypermod]; + } + } + + return 0; +} + +Display *PScreen::_dpy; +uint PScreen::_num_lock = 0; +uint PScreen::_scroll_lock = 0; diff --git a/pekwm/PScreen.hh b/pekwm/PScreen.hh new file mode 100644 index 0000000..725c67f --- /dev/null +++ b/pekwm/PScreen.hh @@ -0,0 +1,282 @@ +// +// Screen.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifndef _SCREENINFO_HH_ +#define _SCREENINFO_HH_ + +#include "pekwm.hh" + +extern "C" { +#include +#ifdef HAVE_XINERAMA +#include +#endif // HAVE_XINERAMA + +extern bool xerrors_ignore; /**< If true, ignore X errors. */ +extern unsigned int xerrors_count; /**< Number of X errors occured. */ + +#ifdef DEBUG +#define setXErrorsIgnore(X) xerrors_ignore = (X) +#else // ! DEBUG +#define setXErrorsIgnore(X) +#endif // DEBUG + +} + +#include +#include +#include + +/** + * Class holding information about screen edge allocation. + */ +class Strut { +public: + Strut(long l=0, long r=0, long t=0, long b=0, int nhead=-1) + : left(l), right(r), top(t), bottom(b), head(nhead) { }; + ~Strut(void) { }; +public: // member variables + long left; /**< Pixels allocated on the left of the head. */ + long right; /** _heads; //! Array of head information + uint _last_head; //! Last accessed head + + uint _server_grabs; + + Time _last_event_time; + // information for dobule clicks + Window _last_click_id; + Time _last_click_time[BUTTON_NO - 1]; + + Strut _strut; + std::list _strut_list; + + static PScreen *_instance; +}; + +#endif // _SCREENINFO_HH_ diff --git a/pekwm/PTexture.hh b/pekwm/PTexture.hh new file mode 100644 index 0000000..c15d262 --- /dev/null +++ b/pekwm/PTexture.hh @@ -0,0 +1,45 @@ +// +// PTexture.cc for pekwm +// Copyright (C) 2004-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "../config.h" + +#ifndef _PTEXTURE_HH_ +#define _PTEXTURE_HH_ + +#include "pekwm.hh" + +class PTexture { +public: + enum Type { + TYPE_SOLID, TYPE_SOLID_RAISED, + TYPE_IMAGE, TYPE_EMPTY, TYPE_NO + }; + + PTexture(Display *dpy) : _dpy(dpy), _ok(false), _width(0), _height(0), _type(PTexture::TYPE_NO) { } + virtual ~PTexture(void) { } + + virtual void render(Drawable draw, int x, int y, uint width, uint height) { } + virtual Pixmap getMask(uint width, uint height, bool &do_free) { return None; } + + inline bool isOk(void) const { return _ok; } + inline uint getWidth(void) const { return _width; } + inline uint getHeight(void) const { return _height; } + inline PTexture::Type getType(void) const { return _type; } + + inline void setWidth(uint width) { _width = width; } + inline void setHeight(uint height) { _height = height; } + +protected: + Display *_dpy; + + bool _ok; // Texture successfully loaded + uint _width, _height; // for images etc, 0 for infinite like in stretch + PTexture::Type _type; // Type of texture +}; + +#endif // _PTEXTURE_HH_ diff --git a/pekwm/PTexturePlain.cc b/pekwm/PTexturePlain.cc new file mode 100644 index 0000000..9a8e078 --- /dev/null +++ b/pekwm/PTexturePlain.cc @@ -0,0 +1,292 @@ +// +// PTexturePlain.cc for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "PTexture.hh" +#include "PTexturePlain.hh" +#include "ColorHandler.hh" +#include "PImage.hh" +#include "ImageHandler.hh" + +using std::string; + +// PTextureSolid + +//! @brief PTextureSolid constructor +PTextureSolid::PTextureSolid(Display *dpy, const std::string &color) + : PTexture(dpy), + _xc(0) +{ + // PTexture attributes + _type = PTexture::TYPE_SOLID; + + XGCValues gv; + gv.function = GXcopy; + _gc = XCreateGC(_dpy, RootWindow(_dpy, DefaultScreen(_dpy)), GCFunction, &gv); + + setColor(color); +} + +//! @brief PTextureSolid destructor +PTextureSolid::~PTextureSolid(void) +{ + XFreeGC(_dpy, _gc); + + unsetColor(); +} + +// BEGIN - PTexture interface. + +//! @brief Renders texture on drawable draw +void +PTextureSolid::render(Drawable draw, int x, int y, uint width, uint height) +{ + if (width == 0) { + width = _width; + } + if (height == 0) { + height = _height; + } + + XFillRectangle(_dpy, draw, _gc, x, y, width, height); +} + +// END - PTexture interface. + +//! @brief Loads color resources +bool +PTextureSolid::setColor(const std::string &color) +{ + unsetColor(); // unload used resources + + _xc = ColorHandler::instance()->getColor(color); + XSetForeground(_dpy, _gc, _xc->pixel); + + _ok = true; + + return _ok; +} + +//! @brief Frees color resources used by texture +void +PTextureSolid::unsetColor(void) +{ + if (_xc) { + ColorHandler::instance()->returnColor(_xc); + + _xc = 0; + _ok = false; + } +} + +// PTextureSolidRaised + +//! @brief PTextureSolidRaised constructor +PTextureSolidRaised::PTextureSolidRaised(Display *dpy, const std::string &base, const std::string &hi, const std::string &lo) + : PTexture(dpy), + _xc_base(0), _xc_hi(0), _xc_lo(0), + _lw(1), _loff(0), _loff2(0), + _draw_top(true), _draw_bottom(true), + _draw_left(true), _draw_right(true) +{ + // PTexture attributes + _type = PTexture::TYPE_SOLID_RAISED; + + XGCValues gv; + gv.function = GXcopy; + gv.line_width = _lw; + _gc = XCreateGC(_dpy, RootWindow(_dpy, DefaultScreen(_dpy)), + GCFunction|GCLineWidth, &gv); + + setColor(base, hi, lo); +} + +//! @brief PTextureSolidRasied destructor +PTextureSolidRaised::~PTextureSolidRaised(void) +{ + XFreeGC(_dpy, _gc); + + unsetColor(); +} + +// START - PTexture interface. + +//! @brief Renders texture on drawable draw +void +PTextureSolidRaised::render(Drawable draw, int x, int y, uint width, uint height) +{ + if (width == 0) { + width = _width; + } + if (height == 0) { + height = _height; + } + + // base rectangle + XSetForeground(_dpy, _gc, _xc_base->pixel); + XFillRectangle(_dpy, draw, _gc, x, y, width, height); + + // hi line ( consisting of two lines ) + XSetForeground(_dpy, _gc, _xc_hi->pixel); + if (_draw_top) { + XDrawLine(_dpy, draw, _gc, + x + _loff, y + _loff, x + width - _loff - _lw, y + _loff); + } + if (_draw_left) { + XDrawLine(_dpy, draw, _gc, + x + _loff, y + _loff, x + _loff, y + height - _loff - _lw); + } + + // lo line ( consisting of two lines ) + XSetForeground(_dpy, _gc, _xc_lo->pixel); + if (_draw_bottom) { + XDrawLine(_dpy, draw, _gc, + x + _loff + _lw, + y + height - _loff - (_lw ? _lw : 1), + x + width - _loff - _lw, + y + height - _loff - (_lw ? _lw : 1)); + } + if (_draw_right) { + XDrawLine(_dpy, draw, _gc, + x + width - _loff - (_lw ? _lw : 1), + y + _loff + _lw, + x + width - _loff - (_lw ? _lw : 1), + y + height - _loff - _lw); + } +} + +// END - PTexture interface. + +//! @brief Sets line width +void +PTextureSolidRaised::setLineWidth(uint lw) +{ + _lw = lw; + // This is a hack to be able to rid the spacing lw == 1 does. + if (! lw) { + lw = 1; + } + + XGCValues gv; + gv.line_width = lw; + XChangeGC(_dpy, _gc, GCLineWidth, &gv); +} + +//! @brief Loads color resources +bool +PTextureSolidRaised::setColor(const std::string &base, const std::string &hi, const std::string &lo) +{ + unsetColor(); // unload used resources + + _xc_base = ColorHandler::instance()->getColor(base); + _xc_hi = ColorHandler::instance()->getColor(hi); + _xc_lo = ColorHandler::instance()->getColor(lo); + + _ok = true; + + return _ok; +} + +//! @brief Unloads color resources +void +PTextureSolidRaised::unsetColor(void) +{ + _ok = false; + + ColorHandler::instance()->returnColor(_xc_base); + ColorHandler::instance()->returnColor(_xc_hi); + ColorHandler::instance()->returnColor(_xc_lo); + + _xc_base = _xc_hi = _xc_lo = 0; +} + +// PTextureImage + +//! @brief PTextureImage constructor +PTextureImage::PTextureImage(Display *dpy) + : PTexture(dpy), + _image(0) +{ + // PTexture attributes + _type = PTexture::TYPE_IMAGE; +} + + +//! @brief PTextureImage constructor +PTextureImage::PTextureImage(Display *dpy, const std::string &image) + : PTexture(dpy), + _image(0) +{ + // PTexture attributes + _type = PTexture::TYPE_IMAGE; + + setImage(image); +} + +//! @brief PTextureImage destructor +PTextureImage::~PTextureImage(void) +{ + unsetImage(); +} + + +//! @brief Renders texture on drawable draw +void +PTextureImage::render(Drawable draw, int x, int y, uint width, uint height) +{ + _image->draw(draw, x, y, width, height); +} + +//! @brief +Pixmap +PTextureImage::getMask(uint width, uint height, bool &do_free) +{ + return _image->getMask(do_free, width, height); +} + +//! @brief Loads image resources +void +PTextureImage::setImage(PImage *image) +{ + unsetImage(); + _image = image; + _width = _image->getWidth(); + _height = _image->getHeight(); + _ok = true; +} + +//! @brief Loads image resources +bool +PTextureImage::setImage(const std::string &image) +{ + unsetImage(); + _image = ImageHandler::instance()->getImage(image); + if (_image) { + _width = _image->getWidth(); + _height = _image->getHeight(); + _ok = true; + } else { + _ok = false; + } + return _ok; +} + +//! @brief Unloads image resources +void +PTextureImage::unsetImage(void) +{ + ImageHandler::instance()->returnImage(_image); + _image = 0; + _width = 1; + _height = 1; + _ok = false; +} + diff --git a/pekwm/PTexturePlain.hh b/pekwm/PTexturePlain.hh new file mode 100644 index 0000000..5341580 --- /dev/null +++ b/pekwm/PTexturePlain.hh @@ -0,0 +1,100 @@ +// +// PTexturePlain.hh for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// +// $Id$ +// + +#ifndef _PTEXTURE_PLAIN_HH_ +#define _PTEXTURE_PLAIN_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include + +#include "pekwm.hh" +#include "PImage.hh" + +// PTextureSolid + +class PTextureSolid : public PTexture { +public: + PTextureSolid(Display *dpy, const std::string &color); + virtual ~PTextureSolid(void); + + // START - PTexture interface. + virtual void render(Drawable draw, int x, int y, uint width, uint height); + // END - PTexture interface. + + inline XColor *getColor(void) { return _xc; } + bool setColor(const std::string &color); + void unsetColor(void); + + +private: + GC _gc; + XColor *_xc; +}; + +// PTextureSolidRaised + +class PTextureSolidRaised : public PTexture { +public: + PTextureSolidRaised(Display *dpy, const std::string &base, const std::string &hi, const std::string &lo); + virtual ~PTextureSolidRaised(void); + + // START - PTexture interface. + virtual void render(Drawable draw, int x, int y, uint width, uint height); + // END - PTexture interface. + + inline void setLineOff(uint loff) { _loff = loff; _loff2 = loff * 2; } + inline void setDraw(bool top, bool bottom, bool left, bool right) { + _draw_top = top; + _draw_bottom = bottom; + _draw_left = left; + _draw_right = right; + } + + void setLineWidth(uint lw); + bool setColor(const std::string &base, const std::string &hi, const std::string &lo); + void unsetColor(void); + +private: + GC _gc; + + XColor *_xc_base, *_xc_hi, *_xc_lo; + + uint _lw, _loff, _loff2; + bool _draw_top; + bool _draw_bottom; + bool _draw_left; + bool _draw_right; +}; + +// PTextureImage + +class PTextureImage : public PTexture { +public: + PTextureImage(Display *dpy); + PTextureImage(Display *dpy, const std::string &image); + virtual ~PTextureImage(void); + + // START - PTexture interface. + virtual void render(Drawable draw, int x, int y, uint width, uint height); + virtual Pixmap getMask(uint width, uint height, bool &do_free); + // END - PTexture interface. + + bool setImage(const std::string &image); + void setImage(PImage *image); + void unsetImage(void); + +private: + PImage *_image; +}; + +#endif // _PTEXTURE_PLAIN_HH_ diff --git a/pekwm/PWinObj.cc b/pekwm/PWinObj.cc new file mode 100644 index 0000000..cf2868a --- /dev/null +++ b/pekwm/PWinObj.cc @@ -0,0 +1,318 @@ +// +// PWinObj.cc for pekwm +// Copyright © 2003-2009 Claes Nästen +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include + +#include "PWinObj.hh" + +#ifdef OPACITY +#include "Atoms.hh" +#endif // OPACITY + +using std::cerr; +using std::endl; +using std::find; +using std::vector; +using std::map; + +Display *PWinObj::_dpy; +PWinObj* PWinObj::_focused_wo = (PWinObj*) 0; +PWinObj* PWinObj::_root_wo = (PWinObj*) 0; +vector PWinObj::_wo_list = vector(); +map PWinObj::_wo_map = map(); + +//! @brief PWinObj constructor. +PWinObj::PWinObj(void) + : _window(None), + _parent(0), _type(WO_NO_TYPE), + _workspace(0), _layer(LAYER_NORMAL), + _mapped(false), _iconified(false), _hidden(false), + _focused(false), _sticky(false), + _focusable(true) +#ifdef OPACITY + ,_opaque(true) +#endif // OPACITY +{ +} + +//! @brief PWinObj destructor. +PWinObj::~PWinObj(void) +{ + if (_focused_wo == this) { + _focused_wo = 0; + } + notifyObservers(0); +} + +//! @brief Associates Window with PWinObj +void +PWinObj::addChildWindow(Window win) +{ + _wo_map[win] = this; +} + +//! @brief Removes association of Window with PWinObj +void +PWinObj::removeChildWindow(Window win) +{ + map::iterator it(_wo_map.find(win)); + if (it != _wo_map.end()) { + _wo_map.erase(it); + } +} + +#ifdef OPACITY +//! @brief Sets the desired opacity values for focused/unfocused states +void +PWinObj::setOpacity(uint focused, uint unfocused, bool enabled) +{ + _opacity.focused = focused; + _opacity.unfocused = unfocused; + _opaque = !enabled; + updateOpacity(); +} + +//! @brief Updates the opacity hint based on focused state +void +PWinObj::updateOpacity(void) +{ + uint opacity; + if (_opaque) { + opacity = EWMH_OPAQUE_WINDOW; + } else { + opacity = isFocused()?_opacity.focused:_opacity.unfocused; + } + + if (_opacity.current != opacity) { + _opacity.current = opacity; + AtomUtil::setLong(_window, Atoms::getAtom(NET_WM_WINDOW_OPACITY), opacity); + } +} +#endif // OPACITY + +//! @brief Maps the window and sets _mapped to true. +void +PWinObj::mapWindow(void) +{ + if (_mapped) { + return; + } + _mapped = true; + _iconified = false; + + XMapWindow(_dpy, _window); +} + +//! @brief Maps the window and raises it +void +PWinObj::mapWindowRaised(void) +{ + if (_mapped) { + return; + } + _mapped = true; + _iconified = false; + + XMapRaised(_dpy, _window); +} + +//! @brief Unmaps the window and sets _mapped to false. +void +PWinObj::unmapWindow(void) +{ + if (! _mapped) { + return; + } + + _mapped = false; + + // Make sure unmapped windows drops focus + setFocused(false); + + XUnmapWindow(_dpy, _window); +} + +//! @brief Only sets _iconified to true. +void +PWinObj::iconify(void) +{ + if (_iconified) { + return; + } + + _iconified = true; +} + +//! @brief Toggles _sticky +void +PWinObj::stick(void) +{ + _sticky = !_sticky; +} + +//! @brief Moves the window and updates _gm. +//! @param x X position +//! @param y Y position +void +PWinObj::move(int x, int y) +{ + _gm.x = x; + _gm.y = y; + + XMoveWindow(_dpy, _window, _gm.x, _gm.y); +} + +//! @brief Resizes the window and updates _gm. +void +PWinObj::resize(uint width, uint height) +{ + if (! width || ! height) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PWinObj(" << this << ")::resize(" << width << "," << height + << ")" << endl << " *** invalid geometry, _window = " + << _window << endl; +#endif // DEBUG + return; + } + + _gm.width = width; + _gm.height = height; + + XResizeWindow(_dpy, _window, _gm.width, _gm.height); +} + +//! @brief Move and resize window in one call. +//! @param x X position. +//! @param y Y Position. +//! @param width Width. +//! @param height Height. +void +PWinObj::moveResize(int x, int y, uint width, uint height) +{ + if (! width || ! height) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PWinObj(" << this << ")::moveResize(" << width << "," << height + << ")" << endl << " *** invalid geometry, _window = " + << _window << endl; +#endif // DEBUG + return; + } + + _gm.x = x; + _gm.y = y; + _gm.width = width; + _gm.height = height; + + XMoveResizeWindow(_dpy, _window, _gm.x, _gm.y, _gm.width, _gm.height); +} + +//! @brief Only sets _workspace to workspace. +void +PWinObj::setWorkspace(uint workspace) +{ + _workspace = workspace; +} + +//! @brief Only sets _layer to layer. +void +PWinObj::setLayer(uint layer) +{ + _layer = layer; +} + +//! @brief Sets _focused to focused and updates opacity as needed. +void +PWinObj::setFocused(bool focused) +{ + _focused = focused; +#ifdef OPACITY + updateOpacity(); +#endif // OPACITY +} + +//! @brief Only sets _sticky to sticky. +void +PWinObj::setSticky(bool sticky) +{ + _sticky = sticky; +} + +#ifdef OPACITY +//! @brief Updates opaque state +void +PWinObj::setOpaque(bool opaque) +{ + _opaque = opaque; + updateOpacity(); +} +#endif // OPACITY + +//! @brief Only sets _hidden to hidden. +void +PWinObj::setHidden(bool hidden) +{ + _hidden = hidden; +} + +//! @brief Executes XSetInputFocus on the appropriate window. +void +PWinObj::giveInputFocus(void) +{ + if (! _mapped || ! _focusable) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "PWinObj(" << this << ")::giveInputFocus()" << endl + << " *** non focusable window." + << " mapped: " << _mapped << " focusable: " << _focusable << endl; +#endif // DEBUG + return; + } + + XSetInputFocus(_dpy, _window, RevertToPointerRoot, CurrentTime); +} + +//! @brief Reparents and sets _parent member +void +PWinObj::reparent(PWinObj *wo, int x, int y) +{ + _parent = wo; + XReparentWindow(_dpy, _window, wo->getWindow(), x, y); +} + +//! @brief Get required size to hold content for window +//! @param request Geometry filled with size request. +//! @return true if geometry is filled in, else false +bool +PWinObj::getSizeRequest(Geometry &request) +{ + return false; +} + +//! @brief Adds PWinObj to _wo_list. +void +PWinObj::woListAdd(PWinObj *wo) +{ + _wo_list.push_back(wo); +} + +//! @brief Remove PWinObj from _wo_list. +void +PWinObj::woListRemove(PWinObj *wo) +{ + vector::iterator it(find(_wo_list.begin(), _wo_list.end(), wo)); + if (it != _wo_list.end()) { + _wo_list.erase(it); + } +} diff --git a/pekwm/PWinObj.hh b/pekwm/PWinObj.hh new file mode 100644 index 0000000..9ec608e --- /dev/null +++ b/pekwm/PWinObj.hh @@ -0,0 +1,256 @@ +// +// PWinObj.hh for pekwm +// Copyright © 2003-2009 Claes Nästen +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _PWIN_OBJ_HH_ +#define _PWIN_OBJ_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include + +#include "pekwm.hh" +#include "Action.hh" +#include "Observable.hh" + +//! @brief X11 Window wrapper class. +class PWinObj : public Observable +{ +public: + //! @brief PWinObj inherited types. + enum Type { + WO_FRAME = (1<<1), //!< Frame type. + WO_CLIENT = (1<<2), //!< Client type. + WO_MENU = (1<<3), //!< PMenu type. + WO_DOCKAPP = (1<<4), //!< DockApp type. + WO_SCREEN_EDGE = (1<<5), //!< ScreenEdge type. + WO_SCREEN_ROOT = (1<<6), //!< PWinObj type for root Window. + WO_CMD_DIALOG = (1<<7), //!< CmdDialog type. + WO_STATUS = (1<<8), //!< StatusWindow type. + WO_WORKSPACE_INDICATOR = (1<<9), //!< WorkspaceIndicator type. + WO_SCREEN_HINT = (1<<10), /**< Invisible hint window. */ + WO_SEARCH_DIALOG = (1<<11), //!< SearchDialog type + WO_NO_TYPE = 0 //!< No type. + }; + + PWinObj(void); + virtual ~PWinObj(void); + + //! @brief Set the X Display structure. + static void setDisplay(Display *dpy) { _dpy = dpy; } + + //! @brief Returns the focused PWinObj. + static inline PWinObj *getFocusedPWinObj(void) { return _focused_wo; } + //! @brief Returns the PWinObj representing the root Window. + static inline PWinObj *getRootPWinObj(void) { return _root_wo; } + //! @brief Sets the focused PWinObj. + static inline void setFocusedPWinObj(PWinObj *wo) { _focused_wo = wo; } + //! @brief Sets the PWinObj representing the root Window. + static inline void setRootPWinObj(PWinObj *wo) { _root_wo = wo; } + //! @brief Checks if focused window is of type + static inline bool isFocusedPWinObj(Type type) { + return _focused_wo ? _focused_wo->getType() == type : false; + } + + //! @brief Searches for the PWinObj matching Window win. + //! @param win Window to match PWinObjs against. + //! @return PWinObj pointer on match, else 0. + static inline PWinObj *findPWinObj(Window win) { + std::map::iterator it(_wo_map.find(win)); + return (it != _wo_map.end()) ? it->second : 0; + } + + //! @brief Searches in PWinObj list if PWinObj wo exists. + //! @param wo PWinObj to search for. + //! @return true if found, else false. + static inline bool windowObjectExists(PWinObj *wo) { + std::vector::iterator it = + std::find(_wo_list.begin(), _wo_list.end(), wo); + if (it != _wo_list.end()) + return true; + return false; + } + + //! @brief Return Window this PWinObj represents. + inline Window getWindow(void) const { return _window; } + //! @brief Sets Window this PWinObj represents. + inline void setWindow(Window window) { _window = window; } + //! @brief Returns parent PWinObj. + inline PWinObj *getParent(void) const { return _parent; } + //! @brief Sets parent PWinObj. + inline void setParent(PWinObj *wo) { _parent = wo; } + //! @brief Returns type of PWinObj. + inline Type getType(void) const { return _type; } + + void addChildWindow(Window win); + void removeChildWindow(Window win); + + //! @brief Returns x coordinate of PWinObj. + inline int getX(void) const { return _gm.x; } + //! @brief Returns y coordinate of PWinObj. + inline int getY(void) const { return _gm.y; } + //! @brief Returns right edge x coordinate of PWinObj. + inline int getRX(void) const { return _gm.x + _gm.width; } + //! @brief Returns bottom edge y coordinate of PWinObj. + inline int getBY(void) const { return _gm.y + _gm.height; } + + //! @brief Returns width of PWinObj. + inline uint getWidth(void) const { return _gm.width; } + //! @brief Returns height of PWinObj: + inline uint getHeight(void) const { return _gm.height; } + + //! @brief Returns workspace PWinObj is on. + inline uint getWorkspace(void) const { return _workspace; } + /** @brief Returns layer PWinObj is in. */ + inline unsigned int getLayer(void) const { return _layer; } + + //! @brief Returns mapped state of PWinObj. + inline bool isMapped(void) const { return _mapped; } + //! @brief Returns iconofied state of PWinObj. + inline bool isIconified(void) const { return _iconified; } + //! @brief Returns hidden state of PWinObj. + inline bool isHidden(void) const { return _hidden; } + //! @brief Returns focused state of PWinObj. + inline bool isFocused(void) const { return _focused; } + //! @brief Returns sticky state of PWinObj. + inline bool isSticky(void) const { return _sticky; } + //! @brief Returns Focusable state of PWinObj. + inline bool isFocusable(void) const { return _focusable; } + +#ifdef OPACITY + //! @brief Returns transparency state of PWinObj + inline bool isOpaque(void) const { return _opaque; } + void setOpacity(uint focused, uint unfocused, bool enabled=true); + inline void setOpacity(uint value) { setOpacity(value, value); } + inline void setOpacity(PWinObj *child) { setOpacity(child->_opacity.focused, child->_opacity.unfocused, !child->_opaque); } + void updateOpacity(void); +#endif // OPACITY + + // interface + virtual void mapWindow(void); + virtual void mapWindowRaised(void); + virtual void unmapWindow(void); + virtual void iconify(void); + virtual void stick(void); + + virtual void move(int x, int y); + virtual void resize(uint width, uint height); + virtual void moveResize(int x, int y, uint width, uint height); + //! @brief Raises PWinObj without respect of layer. + virtual void raise(void) { XRaiseWindow(_dpy, _window); } + //! @brief Lowers PWinObj without respect of layer. + virtual void lower(void) { XLowerWindow(_dpy, _window); } + + virtual void setWorkspace(uint workspace); + virtual void setLayer(uint layer); + virtual void setFocused(bool focused); + virtual void setSticky(bool sticky); +#ifdef OPACITY + void setOpaque(bool opaque); +#endif // OPACITY + /** Set focusable flag. */ + virtual void setFocusable(bool focusable) { _focusable = focusable; } + virtual void setHidden(bool hidden); + + virtual void giveInputFocus(void); + virtual void reparent(PWinObj *parent, int x, int y); + + virtual bool getSizeRequest(Geometry &request); + + // event interface + + //! @brief Handles button press events, always return 0. + virtual ActionEvent *handleButtonPress(XButtonEvent *ev) { return 0; } + //! @brief Handles button release events, always return 0. + virtual ActionEvent *handleButtonRelease(XButtonEvent *ev) { return 0; } + //! @brief Handles key press events, always return 0. + virtual ActionEvent *handleKeyPress(XKeyEvent *ev) { return 0; } + //! @brief Handles key release vents, always return 0. + virtual ActionEvent *handleKeyRelease(XKeyEvent *ev) { return 0; } + //! @brief Handles motion events, always return 0. + virtual ActionEvent *handleMotionEvent(XMotionEvent *ev) { return 0; } + //! @brief Handles enter events, always return 0. + virtual ActionEvent *handleEnterEvent(XCrossingEvent *ev) { return 0; } + //! @brief Handles leave events, always return 0. + virtual ActionEvent *handleLeaveEvent(XCrossingEvent *ev) { return 0; } + //! @brief Handles expose events, always return 0. + virtual ActionEvent *handleExposeEvent(XExposeEvent *ev) { return 0; } + + //! @brief Handles handle map request events, always return 0. + virtual ActionEvent *handleMapRequest(XMapRequestEvent *ev) { return 0; } + //! @brief Handles handle unmap events, always return 0. + virtual ActionEvent *handleUnmapEvent(XUnmapEvent *ev) { return 0; } + + // operators + + //! @brief Operator matching against Window PWinObj represents.. + virtual bool operator == (const Window &window) { + return (_window == window); + } + //! @brief Operator matching against Window PWinObj represents. + virtual bool operator != (const Window &window) { + return (_window != window); + } + + // other window commands + + //! @brief Clears Window causing a redraw. + inline void clear(void) { XClearWindow(_dpy, _window); } + //! @brief Sets Window background colour. + inline void setBackground(long pixel) { + XSetWindowBackground(_dpy, _window, pixel); + } + //! @brief Sets Window background pixmap. + inline void setBackgroundPixmap(Pixmap pm) { + XSetWindowBackgroundPixmap(_dpy, _window, pm); + } + +protected: + static void woListAdd(PWinObj *wo); + static void woListRemove(PWinObj *wo); + +protected: + static Display *_dpy; //!< Display PWinObj is on. + Window _window; //!< Window PWinObj represents. + PWinObj *_parent; //!< Parent PWinObj. + + Type _type; //!< Type of PWinObj. +#ifdef OPACITY + // Opacity information + class Opacity { + public: + Opacity(void) + : current(EWMH_OPAQUE_WINDOW), + focused(EWMH_OPAQUE_WINDOW), + unfocused(EWMH_OPAQUE_WINDOW) { } + uint current, focused, unfocused; + } _opacity; +#endif // OPACITY + + Geometry _gm; //!< Geometry of PWinObj (always in absolute coordinates). + uint _workspace; //!< Workspace PWinObj is on. + unsigned int _layer; //!< Layer PWinObj is in. + bool _mapped; //!< Mapped state of PWinObj. + bool _iconified; //!< Iconified state of PWinObj. + bool _hidden; //!< Hidden state of PWinObj. + bool _focused; //!< Focused state of PWinObj. + bool _sticky; //!< Sticky state of PWinObj. + bool _focusable; //!< Focusable state of PWinObj. +#ifdef OPACITY + bool _opaque; //!< Opaque set state of PWinObj +#endif // OPACITY + static PWinObj *_root_wo; //!< Static root PWinObj pointer. + static PWinObj *_focused_wo; //!< Static focused PWinObj pointer. + static std::vector _wo_list; //!< List of PWinObjs. + static std::map _wo_map; //!< Mapping of Window to PWinObj +}; + +#endif // _PWIN_OBJ_HH_ diff --git a/pekwm/PWinObjReference.cc b/pekwm/PWinObjReference.cc new file mode 100644 index 0000000..1727e67 --- /dev/null +++ b/pekwm/PWinObjReference.cc @@ -0,0 +1,47 @@ +// +// PWinObjReference.cc for pekwm +// Copyright © 2009 Claes Nästen +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "PWinObjReference.hh" + +/** + * Construct new window reference. + */ +PWinObjReference::PWinObjReference(PWinObj *wo_ref) + : _wo_ref(0) +{ + setWORef(wo_ref); +} + +/** + * Destruct refernce, remove observer. + */ +PWinObjReference::~PWinObjReference(void) +{ + setWORef(0); +} + +/** + * Set the window reference and update observer. + */ +void +PWinObjReference::setWORef(PWinObj *wo_ref) +{ + if (_wo_ref != 0) { + _wo_ref->removeObserver(this); + } + + _wo_ref = wo_ref; + + if (_wo_ref != 0) { + _wo_ref->addObserver(this); + } +} diff --git a/pekwm/PWinObjReference.hh b/pekwm/PWinObjReference.hh new file mode 100644 index 0000000..8969a71 --- /dev/null +++ b/pekwm/PWinObjReference.hh @@ -0,0 +1,37 @@ +// +// PWinObjReference.hh for pekwm +// Copyright © 2009 Claes Nästen +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _PWIN_OBJ_REFERENCE_HH_ +#define _PWIN_OBJ_REFERENCE_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "PWinObj.hh" +#include "Observer.hh" + +class PWinObjReference : public Observer { +public: + PWinObjReference(PWinObj *wo_ref=0); + virtual ~PWinObjReference(void); + + /** Notify about reference update, unset the reference. */ + virtual void notify(Observable *observable, Observation *observation) { + _wo_ref = 0; } + + /** Returns the PWinObj reference. */ + PWinObj *getWORef(void) { return _wo_ref; } + /** Sets the PWinObj reference. */ + void setWORef(PWinObj *wo_ref); + +private: + PWinObj *_wo_ref; /**< Window object reference. */ +}; + +#endif // _PWIN_OBJ_REFERENCE_HH_ diff --git a/pekwm/ParseUtil.hh b/pekwm/ParseUtil.hh new file mode 100644 index 0000000..2e618dc --- /dev/null +++ b/pekwm/ParseUtil.hh @@ -0,0 +1,67 @@ +// +// ParseUtil.hh for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _PARSE_UTIL_HH_ +#define _PARSE_UTIL_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include + +extern "C" { +#include +} + +namespace ParseUtil { + +class Entry { +public: + Entry(const char *text) : _text(text) { } + // hacky, but we don't want to loose speed do we? + Entry(const std::string &text) : _text(text.c_str()) { } + ~Entry(void) { } + + /** Get text version of string. */ + inline const char *get_text(void) const { return _text; } + + // operators + inline bool operator==(const std::string &rhs) const { + return (strcasecmp(rhs.c_str(), _text) == 0); + } + inline bool operator!=(const std::string &rhs) const { + return (strcasecmp(rhs.c_str(), _text) != 0); + } + // < > needed for map searching + inline bool operator<(const ParseUtil::Entry &rhs) const { + return (strcasecmp(_text, rhs._text) < 0); + } + inline bool operator>(const ParseUtil::Entry &rhs) const { + return (strcasecmp(_text, rhs._text) > 0); + } + +private: + const char *_text; +}; + +//! @brief Finds item in map, returns "" in map if not found +template +inline T +getValue(const std::string &text, + typename std::map &val) +{ + typename std::map::iterator it = val.find(text); + return (it != val.end()) ? it->second : val[""]; +} + +} + +#endif // _PARSE_UTIL_HH_ diff --git a/pekwm/PixmapHandler.cc b/pekwm/PixmapHandler.cc new file mode 100644 index 0000000..ef9ea38 --- /dev/null +++ b/pekwm/PixmapHandler.cc @@ -0,0 +1,130 @@ +// +// PixmapHandler.cc for pekwm +// Copyright (C) 2003-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "../config.h" + +#include "PixmapHandler.hh" +#include "PScreen.hh" + +#ifdef DEBUG +#include +using std::cerr; +using std::endl; +#endif // DEBUG +#include + +using std::map; + +// PixmapHandler::Entry + +//! @brief Constructor for Entry class +//! @param width Width of entry. +//! @param height Height of entry. +//! @param depth Depth of entry. +//! @param pixmap Pixmap associated with entry. +PixmapHandler::Entry::Entry(uint width, uint height, uint depth, Pixmap pixmap) + : _width(width), _height(height), _depth(depth), _pixmap(pixmap) +{ +} + +//! @brief Destructor for Entry class +PixmapHandler::Entry::~Entry(void) +{ + XFreePixmap(PScreen::instance()->getDpy(), _pixmap); +} + +// PixmapHandler + +//! @brief Constructor for PixmapHandler class +PixmapHandler::PixmapHandler(uint cache_size) + : _cache_size(cache_size) +{ +} + +//! @brief Destructor for PixmapHandler class +PixmapHandler::~PixmapHandler(void) +{ +#ifdef DEBUG + if (_used_pix.size() > 0) { + cerr << __FILE__ << "@" << __LINE__ << ": " + << "Used pixmap list size > 0, size: " + << _used_pix.size() << endl; + } +#endif // DEBUG + + // Free resources + map::iterator it; + for (it = _free_pix.begin(); it != _free_pix.end(); ++it) { + delete it->second; + } + for (it = _used_pix.begin(); it != _used_pix.end(); ++it) { + delete it->second; + } +} + +//! @brief Sets the cache size and trims the cache if needed +void +PixmapHandler::setCacheSize(uint cache) +{ + // Cache size will be trimmed on next return pixmap call. + _cache_size = cache; +} + +//! @brief Searches the Pixmap cache for a sutiable Pixmap or creates a new +//! @param width Width of the pixmap +//! @param height Height of the pixmap +//! @param depth Depth of the pixmap +Pixmap +PixmapHandler::getPixmap(uint width, uint height, uint depth) +{ + Pixmap pixmap = None; + + // search already created pixmaps + map::iterator it(_free_pix.begin()); + for (; it != _free_pix.end(); ++it) { + if (it->second->isMatch(width, height, depth)) { + pixmap = it->first; + + // Move the entry to the used list + _used_pix[it->first] = it->second; + _free_pix.erase(it); + break; + } + } + + // No entry, create one + if (pixmap == None) { + pixmap = XCreatePixmap(PScreen::instance()->getDpy(), + PScreen::instance()->getRoot(), + width, height, depth); + + _used_pix[pixmap] = new Entry(width, height, depth, pixmap); + } + + return pixmap; +} + +//! @brief Returns the pixmap to the Pixmap cache +void +PixmapHandler::returnPixmap(Pixmap pixmap) +{ + // Remove from used list + std::map::iterator it = _used_pix.find(pixmap); + if (it != _used_pix.end()) { + _free_pix[it->first] = it->second; + _used_pix.erase(it); + } + + // Trim the cache + while (_free_pix.size() > _cache_size) { + it = _free_pix.begin(); + + delete it->second; + _free_pix.erase(it); + } +} diff --git a/pekwm/PixmapHandler.hh b/pekwm/PixmapHandler.hh new file mode 100644 index 0000000..60873ba --- /dev/null +++ b/pekwm/PixmapHandler.hh @@ -0,0 +1,53 @@ +// +// PixmapHandler.hh for pekwm +// Copyright (C) 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// +// $Id$ +// + +#include "../config.h" + +#ifndef _PIXMAP_HANDLER_HH_ +#define _PIXMAP_HANDLER_HH_ + +#include "pekwm.hh" + +#include + +class PixmapHandler { +public: + class Entry { + public: + Entry(uint width, uint height, uint depth, Pixmap pixmap); + ~Entry(void); + + inline bool isMatch(uint width, uint height, uint depth) { + return ((depth == _depth) && (_width == width) && (_height == height)); + } + + private: + uint _width; //!< Width of entry. + uint _height; //!< Height of entry. + uint _depth; //!< Depth of entry. + Pixmap _pixmap; //!< Pixmap associated with entry. + }; + + PixmapHandler(uint cache_size); + ~PixmapHandler(void); + + Pixmap getPixmap(uint width, uint height, uint depth); + void returnPixmap(Pixmap pixmap); + + void setCacheSize(uint cache); + +private: + uint _cache_size; + + std::map _free_pix; //!< Set of free pixmaps. + std::map _used_pix; //!< Set of used pixmaps. +}; + +#endif // _PIXMAP_HANDLER_HH_ diff --git a/pekwm/RegexString.cc b/pekwm/RegexString.cc new file mode 100644 index 0000000..63007d9 --- /dev/null +++ b/pekwm/RegexString.cc @@ -0,0 +1,254 @@ +// +// RegexString.cc for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include + +#include "RegexString.hh" +#include "Util.hh" + +using std::cerr; +using std::endl; +using std::list; +using std::string; +using std::wstring; +using std::strtol; + +const char RegexString::SEPARATOR = '/'; + +//! @brief RegexString constructor. +RegexString::RegexString(void) + : _reg_ok(false), _reg_inverted(false), _ref_max(1) +{ +} + +//! @brief RegexString constructor with default search +RegexString::RegexString(const std::wstring &str, bool full) + : _reg_ok(false), _reg_inverted(false), _ref_max(1) +{ + parse_match(str, full); +} + +//! @brief RegexString destructor. +RegexString::~RegexString(void) +{ + free_regex(); +} + +//! @brief Simple ed s command lookalike. +bool +RegexString::ed_s(std::wstring &str) +{ + if (! _reg_ok) { + return false; + } + + string mb_str(Util::to_mb_str(str)); + + const char *c_str = mb_str.c_str(); + regmatch_t *matches = new regmatch_t[_ref_max]; + + // Match + if (regexec(&_regex, mb_str.c_str(), _ref_max, matches, 0)) { + delete [] matches; + return false; + } + + string result; + uint ref, size; + + list::iterator it(_ref_list.begin()); + for (; it != _ref_list.end(); ++it) { + if (it->get_reference() >= 0) { + ref = it->get_reference(); + + if (matches[ref].rm_so != -1) { + size = matches[ref].rm_eo - matches[ref].rm_so; + result.append(string(c_str + matches[ref].rm_so, size)); + } + } else { + result.append(Util::to_mb_str(it->get_string())); + } + } + + // Replace area regexp matched. + size = matches[0].rm_eo - matches[0].rm_so; + mb_str.replace(matches[0].rm_so, size, result); + + str = Util::to_wide_str(mb_str); + + return true; +} + +//! @brief Parses match part of regular expression. +//! @param match Expression. +//! @param full Full expression if true (including flags). Defaults to false. +bool +RegexString::parse_match(const std::wstring &match, bool full) +{ + // Free resources + if (_reg_ok) { + free_regex(); + } + + if (match.size()) { + int flags = REG_EXTENDED; + string expression; + wstring expression_str; + + // Full regular expression syntax, parse out flags etc + string::size_type pos; + if (match[0] == SEPARATOR + && (pos = match.find_last_of(SEPARATOR)) != string::npos) { + // Main expression + expression_str = match.substr(1, pos - 1); + + // Expression flags + for (string::size_type i = pos + 1; i < match.size(); ++i) { + switch (match[i]) { + case 'i': + flags |= REG_ICASE; + break; + case '!': + _reg_inverted = true; + break; + default: + cerr << "Invalid flag \"" << match[i] << "\" for regular expression." << endl; + break; + } + } + + expression = Util::to_mb_str(expression_str); + } else { + if (full) { + cerr << "Invalid format of regular expression, missing separator " << SEPARATOR << endl; + } + expression = Util::to_mb_str(match); + } + + _reg_ok = ! regcomp(&_regex, expression.c_str(), flags); + } else { + _reg_ok = false; + } + + return _reg_ok; +} + +//! @brief Parses replace part of ed_s command. +//! Expects input in the style of /replace/me/. / can be any character +//! except \. References to sub expressions are made with \num. \0 Represents +//! the part of the string that matched. +bool +RegexString::parse_replace(const std::wstring &replace) +{ + _ref_max = 0; + + wstring part; + wstring::size_type begin = 0, end = 0, last = 0; + + // Go through the string and split at \num points + while ((end = replace.find_first_of('\\', begin)) != string::npos) { + // Store string between references. + if (end > last) { + part = replace.substr(last, end - last); + _ref_list.push_back(RegexString::Part(part)); + } + + // Get reference number. + for (begin = ++end; isdigit(replace[end]); end++) + ; + + if (end > begin) { + // Convert number and add item. + part = replace.substr(begin, end - last); + int ref = strtol(Util::to_mb_str(part).c_str(), 0, 10); + if (ref >= 0) { + _ref_list.push_back(RegexString::Part(L"", ref)); + if (ref > _ref_max) { + _ref_max = ref; + } + } + } + + last = end; + begin = last + 1; + } + + if (begin < replace.size()) { + part = replace.substr(begin, replace.size() - begin); + _ref_list.push_back(RegexString::Part(part)); + } + + _ref_max++; + + return true; +} + +//! @brief Parses ed s style command. /from/to/ +bool +RegexString::parse_ed_s(const std::wstring &ed_s) +{ + if (ed_s.size() < 3) { + return false; + } + + wchar_t c_delimeter = ed_s[0]; + string::size_type middle, end; + + // Middle. + for (middle = 1; middle < ed_s.size(); middle++) { + if ((ed_s[middle] == c_delimeter) && (ed_s[middle - 1] != '\\')) { + break; + } + } + + // End. + for (end = middle + 1; end < ed_s.size(); end++) { + if ((ed_s[end] == c_delimeter) && (ed_s[end - 1] != '\\')) { + break; + } + } + + wstring match, replace; + match = ed_s.substr(1, middle - 1); + replace = ed_s.substr(middle + 1, end - middle - 1); + + parse_match(match); + parse_replace(replace); + + return true; +} + +//! @brief Matches RegexString against rhs, needs successfull parse_match. +bool +RegexString::operator==(const std::wstring &rhs) +{ + if (! _reg_ok) { + return false; + } + + string mb_rhs(Util::to_mb_str(rhs)); + bool match = ! regexec(&_regex, mb_rhs.c_str(), 0, 0, 0); + + return _reg_inverted ? ! match : match; +} + +//! @brief Free resources used by RegexString. +void +RegexString::free_regex(void) +{ + if (_reg_ok) { + regfree(&_regex); + _reg_ok = false; + } + _reg_inverted = false; +} diff --git a/pekwm/RegexString.hh b/pekwm/RegexString.hh new file mode 100644 index 0000000..9a48d77 --- /dev/null +++ b/pekwm/RegexString.hh @@ -0,0 +1,79 @@ +// +// RegexString.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _REGEX_STRING_HH_ +#define _REGEX_STRING_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include + +extern "C" { +#include +#include +} + +#include "Types.hh" + +//! @brief POSIX regular expression wrapper. +class RegexString +{ +public: + //! @brief Part of parsed replace data. + class Part + { + public: + //! @brief RegexString::Part constructor. + Part(const std::wstring &str, int ref = -1) : _string(str), _ref(ref) { } + //! @brief RegexString::Part destructor. + ~Part(void) { } + + //! @brief Returns string data. + const std::wstring &get_string(void) { return _string; } + //! @brief Returns reference number. + int get_reference(void) { return _ref; } + + private: + std::wstring _string; //!< String data at item. + int _ref; //!< Reference string should be replaced with. + }; + + RegexString(void); + RegexString(const std::wstring &string, bool full = false); + ~RegexString(void); + + //! @brief Returns parse_match data status. + bool is_match_ok(void) { return _reg_ok; } + + bool ed_s(std::wstring &str); + + bool parse_match(const std::wstring &match, bool full = false); + bool parse_replace(const std::wstring &replace); + bool parse_ed_s(const std::wstring &ed_s); + + bool operator==(const std::wstring &rhs); + +private: + RegexString(const RegexString &); + RegexString &operator=(const RegexString &); + void free_regex (void); + +private: + regex_t _regex; //!< Compiled regular expression holder. + bool _reg_ok; //!< _regex compiled ok flag. + bool _reg_inverted; /**< If true, a non-matching regexp is considered a match. */ + + int _ref_max; //!< Highest reference used. + std::list _ref_list; //!< List of RegexString::Part holding data generated by parse_replace. + static const char SEPARATOR; /**< Regular expression seperator. */ +}; + +#endif // _REGEX_STRING_HH_ diff --git a/pekwm/ScreenResources.cc b/pekwm/ScreenResources.cc new file mode 100644 index 0000000..0506135 --- /dev/null +++ b/pekwm/ScreenResources.cc @@ -0,0 +1,79 @@ +// +// ScreenResources.hh for pekwm +// Copyright (C) 2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "../config.h" + +#include "PWinObj.hh" +#include "ScreenResources.hh" +#include "PScreen.hh" +#include "Config.hh" +#include "PixmapHandler.hh" + +extern "C" { +#include +} + +#ifdef DEBUG +#include +using std::cerr; +using std::endl; +#endif // DEBUG + +using std::map; + +ScreenResources *ScreenResources::_instance = 0; + +//! @brief ScreenResources constructor +ScreenResources::ScreenResources(void) + : _pixmap_handler(0) +{ + if (_instance) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "ScreenResources(" << this << ")::ScreenResources()" << endl + << " *** _instance already set: " << _instance << endl; +#endif // DEBUG + } + _instance = this; + + Display *dpy = PScreen::instance()->getDpy(); + + // create resize cursors + _cursor_map[CURSOR_TOP_LEFT] = XCreateFontCursor(dpy, XC_top_left_corner); + _cursor_map[CURSOR_TOP] = XCreateFontCursor(dpy, XC_top_side); + _cursor_map[CURSOR_TOP_RIGHT] = XCreateFontCursor(dpy, XC_top_right_corner); + _cursor_map[CURSOR_LEFT] = XCreateFontCursor(dpy, XC_left_side); + _cursor_map[CURSOR_RIGHT] = XCreateFontCursor(dpy, XC_right_side); + _cursor_map[CURSOR_BOTTOM_LEFT] = + XCreateFontCursor(dpy, XC_bottom_left_corner); + _cursor_map[CURSOR_BOTTOM] = XCreateFontCursor(dpy, XC_bottom_side); + _cursor_map[CURSOR_BOTTOM_RIGHT] = + XCreateFontCursor(dpy, XC_bottom_right_corner); + // create other cursors + _cursor_map[CURSOR_ARROW] = XCreateFontCursor(dpy, XC_left_ptr); + _cursor_map[CURSOR_MOVE] = XCreateFontCursor(dpy, XC_fleur); + _cursor_map[CURSOR_RESIZE] = XCreateFontCursor(dpy, XC_plus); + + _pixmap_handler = + new PixmapHandler(Config::instance()->getScreenPixmapCacheSize()); +} + +//! @brief ScreenResources destructor +ScreenResources::~ScreenResources(void) +{ + map::iterator c_it(_cursor_map.begin()); + for (; c_it != _cursor_map.end(); ++c_it) { + XFreeCursor(PScreen::instance()->getDpy(), c_it->second); + } + + if (_pixmap_handler) { + delete _pixmap_handler; + } + + _instance = 0; +} diff --git a/pekwm/ScreenResources.hh b/pekwm/ScreenResources.hh new file mode 100644 index 0000000..3c6c2a2 --- /dev/null +++ b/pekwm/ScreenResources.hh @@ -0,0 +1,55 @@ +// +// ScreenResources.hh for pekwm +// Copyright (C) 2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "../config.h" + +#ifndef _SCREEN_RESOURCES_HH_ +#define _SCREEN_RESOURCES_HH_ + +#include "pekwm.hh" + +extern "C" { +#include +} + +#include + +class PixmapHandler; + +class ScreenResources { +public: + enum CursorType { + CURSOR_TOP_LEFT = BORDER_TOP_LEFT, + CURSOR_TOP = BORDER_TOP, + CURSOR_TOP_RIGHT = BORDER_TOP_RIGHT, + CURSOR_LEFT = BORDER_LEFT, + CURSOR_RIGHT = BORDER_RIGHT, + CURSOR_BOTTOM_LEFT = BORDER_BOTTOM_LEFT, + CURSOR_BOTTOM = BORDER_BOTTOM, + CURSOR_BOTTOM_RIGHT = BORDER_BOTTOM_RIGHT, + CURSOR_ARROW = BORDER_NO_POS, + CURSOR_MOVE, + CURSOR_RESIZE + }; + + ScreenResources(void); + ~ScreenResources(void); + + static inline ScreenResources *instance(void) { return _instance; } + + inline Cursor getCursor(ScreenResources::CursorType type) { return _cursor_map[type]; } + inline PixmapHandler *getPixmapHandler(void) { return _pixmap_handler; } + +private: + std::map _cursor_map; + PixmapHandler *_pixmap_handler; + + static ScreenResources *_instance; +}; + +#endif // _SCREEN_RESOURCES_HH_ diff --git a/pekwm/SearchDialog.cc b/pekwm/SearchDialog.cc new file mode 100644 index 0000000..19c9ac0 --- /dev/null +++ b/pekwm/SearchDialog.cc @@ -0,0 +1,185 @@ +// +// SearchDialog.cc for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "SearchDialog.hh" + +#include "Client.hh" +#include "RegexString.hh" + +#include +#include + +using std::cerr; +using std::endl; +using std::list; +using std::wcerr; + +/** + * SearchDialog constructor. + */ +SearchDialog::SearchDialog(Theme *theme) + : InputDialog(theme, L"Search"), + _result_menu(0) +{ + _type = PWinObj::WO_SEARCH_DIALOG; + + // Set up ActionEvent + _ae.action_list.back().setAction(ACTION_GOTO_CLIENT); + + // Set up menu for displaying results + _result_menu = new PMenu(_theme, L"", ""); + _result_menu->reparent(this, borderLeft(), borderTop() + getTitleHeight() + _text_wo->getHeight()); + _result_menu->setLayer(LAYER_DESKTOP); // Ignore when placing + _result_menu->setSticky(STATE_SET); + _result_menu->setBorder(STATE_UNSET); + _result_menu->setTitlebar(STATE_UNSET); + _result_menu->setFocusable(false); + _result_menu->mapWindow(); +} + +/** + * SearchDialog destructor. + */ +SearchDialog::~SearchDialog(void) +{ + delete _result_menu; +} + +/** + * Run when INPUT_EXEC is entered in the dialog giving the selected + * Client focus if any. + */ +ActionEvent* +SearchDialog::exec(void) +{ + // InputDialog::close() may have overwritten our action. + _ae.action_list.back().setAction(ACTION_GOTO_CLIENT); + + PWinObj *wo_ref = 0; + if (_result_menu->getItemCurr()) { + wo_ref = _result_menu->getItemCurr()->getWORef(); + } + setWORef(wo_ref); + + return &_ae; +} + +/** + * Called whenever the buffer has changed. Updates the displayed clients. + */ +void +SearchDialog::bufChanged(void) +{ + InputDialog::bufChanged(); + findClients(_buf); +} + +/** + * Focus next item in result menu. + */ +void +SearchDialog::histNext(void) +{ + _result_menu->selectItemRel(1); +} + +/** + * Focus previous item in result menu. + */ +void +SearchDialog::histPrev(void) +{ + _result_menu->selectItemRel(-1); +} + +/** + * Update size making sure result menu fits. + */ +void +SearchDialog::updateSize(void) +{ + InputDialog::updateSize(); + _result_menu->setMenuWidth(_text_wo->getWidth()); +} + +/** + * Search list of clients for matching titles. + * + * @param search Regexp to search, case insensitive + * @return Number of matches + */ +uint +SearchDialog::findClients(const std::wstring &search) +{ + // Do nothing if search has not changed. + if (_previous_search == search) { + return _result_menu->size(); + } + _previous_search = search; + + _result_menu->removeAll(); + if (search.size() > 0) { + RegexString search_re(L"/" + search + L"/i"); + if (! search_re.is_match_ok()) { + return 0; + } + + list matches; + list::iterator it(Client::client_begin()); + for (; it != Client::client_end(); ++it) { + if ((*it)->isFocusable() && ! (*it)->isSkip(SKIP_FOCUS_TOGGLE) + && search_re == (*it)->getTitle()->getReal()) { + matches.push_back(*it); + } + } + + for (it = matches.begin(); it != matches.end(); ++it) { + _result_menu->insert((*it)->getTitle()->getVisible(), *it, (*it)->getIcon()); + } + } + + // Rebuild menu and make room for it + _result_menu->buildMenu(); + + unsigned int width, height; + getInputSize(width, height); + + if (_result_menu->size()) { + resizeChild(_text_wo->getWidth(), height + _result_menu->getHeight()); + XRaiseWindow(_dpy, _result_menu->getWindow()); + // Render first item as selected, needs to be done after map/raise. + _result_menu->selectItemNum(0); + } else { + resizeChild(_text_wo->getWidth(), height); + XLowerWindow(_dpy, _result_menu->getWindow()); + } + + return 0; +} + +/** + * Unmap window and clear buffer, result menu and window reference. + */ +void +SearchDialog::unmapWindow(void) +{ + if (_mapped) { + InputDialog::unmapWindow(); + setWORef(0); + bufClear(); + + // Clear the menu and hide it. + _result_menu->clear(); + _previous_search.clear(); + XLowerWindow(_dpy, _result_menu->getWindow()); + } +} diff --git a/pekwm/SearchDialog.hh b/pekwm/SearchDialog.hh new file mode 100644 index 0000000..9d7070e --- /dev/null +++ b/pekwm/SearchDialog.hh @@ -0,0 +1,52 @@ +// +// SearchDialog.cc for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _SEARCH_DIALOG_HH_ +#define _SEARCH_DIALOG_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" + +#include "InputDialog.hh" +#include "Theme.hh" +#include "PMenu.hh" + +#include + +/** + * Search dialog providing a dialog for searching clients together + * with a menu that shows clients matching the search. + */ +class SearchDialog : public InputDialog { +public: + SearchDialog(Theme *theme); + virtual ~SearchDialog(void); + + void unmapWindow(void); + +protected: + virtual ActionEvent *exec(void); + + virtual void bufChanged(void); + + virtual void histNext(void); + virtual void histPrev(void); + + virtual void updateSize(void); + +private: + uint findClients(const std::wstring &search); + + PMenu *_result_menu; /**< Menu for displaying results. */ + std::wstring _previous_search; /**< Buffer with previous search string. */ +}; + +#endif // _SEARCH_DIALOG_HH_ diff --git a/pekwm/StatusWindow.cc b/pekwm/StatusWindow.cc new file mode 100644 index 0000000..cef13e3 --- /dev/null +++ b/pekwm/StatusWindow.cc @@ -0,0 +1,157 @@ +// +// StatusWindow.cc for pekwm +// Copyright (C) 2004-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#include "../config.h" + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "StatusWindow.hh" +#include "PScreen.hh" +#include "PTexture.hh" +#include "PixmapHandler.hh" +#include "ScreenResources.hh" +#include "Theme.hh" +#include "Workspaces.hh" + +#ifdef DEBUG +#include +using std::cerr; +using std::endl; +#endif // DEBUG + +StatusWindow *StatusWindow::_instance = 0; + +//! @brief StatusWindow constructor +StatusWindow::StatusWindow(Theme *theme) + : PDecor(theme, "STATUSWINDOW"), + _bg(None) +{ + if (_instance) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "StatusWindow(" << this << ")::StatusWindow()" << endl + << " *** _instance already set: " << _instance << endl; +#endif // DEBUG + } + _instance = this; + + + // PWinObj attributes + _type = PWinObj::WO_STATUS; + setLayer(LAYER_NONE); // hack, goes over LAYER_MENU + _hidden = true; // don't care about it when changing worskpace etc + + XSetWindowAttributes attr; + attr.event_mask = None; + + _status_wo = new PWinObj; + _status_wo->setWindow(XCreateWindow(_dpy, _window, + 0, 0, 1, 1, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWEventMask, &attr)); + + addChild(_status_wo); + activateChild(_status_wo); + _status_wo->mapWindow(); + + setTitlebar(STATE_UNSET); + setFocused(true); // always draw as focused + + Workspaces::instance()->insert(this); +} + +//! @brief StatusWindow destructor +StatusWindow::~StatusWindow(void) +{ + Workspaces::instance()->remove(this); + + // remove ourself from the decor manually, no need to reparent and stuff + _child_list.remove(_status_wo); + + // free resources + XDestroyWindow(_dpy, _status_wo->getWindow()); + delete _status_wo; + + unloadTheme(); + + _instance = 0; +} + +//! @brief Resizes window to fit the text and renders text +//! @param text Text to draw in StatusWindow +//! @param do_center Center the StatusWindow on screen. Defaults to false. +void +StatusWindow::draw(const std::wstring &text, bool do_center, Geometry *gm) +{ + uint width, height; + PFont *font = _theme->getStatusData()->getFont(); // convenience + + width = font->getWidth(text.c_str()) + 10; + width = width - (width % 10); + height = font->getHeight() + + _theme->getStatusData()->getPad(PAD_UP) + + _theme->getStatusData()->getPad(PAD_DOWN); + + if ((width != getChildWidth()) || (height != getChildHeight())) { + resizeChild(width, height); + render(); + } + + if (do_center) { + center(gm); + } + + font->setColor(_theme->getStatusData()->getColor()); + _status_wo->clear(); + font->draw(_status_wo->getWindow(), + (width - font->getWidth(text.c_str())) / 2, + _theme->getStatusData()->getPad(PAD_UP), + text.c_str()); +} + +//! @brief Renders and sets background +void +StatusWindow::loadTheme(void) +{ + render(); +} + +//! @brief Frees theme resources +void +StatusWindow::unloadTheme(void) +{ + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_bg); +} + +//! @brief Renders and sets background +void +StatusWindow::render(void) +{ + PixmapHandler *pm = ScreenResources::instance()->getPixmapHandler(); + pm->returnPixmap(_bg); + _bg = pm->getPixmap(_status_wo->getWidth(), _status_wo->getHeight(), + PScreen::instance()->getDepth()); + + _theme->getStatusData()->getTexture()->render(_bg, 0, 0, _status_wo->getWidth(), _status_wo->getHeight()); + + _status_wo->setBackgroundPixmap(_bg); + _status_wo->clear(); +} + +//! @brief Centers the StatusWindow on the current head/screen +void +StatusWindow::center(Geometry *gm) +{ + Geometry head; + if (! gm) { + PScreen::instance()->getHeadInfo(PScreen::instance()->getCurrHead(), head); + gm = &head; + } + + move(gm->x + (gm->width - _gm.width) / 2, gm->y + (gm->height - _gm.height) / 2); +} diff --git a/pekwm/StatusWindow.hh b/pekwm/StatusWindow.hh new file mode 100644 index 0000000..f4fac1e --- /dev/null +++ b/pekwm/StatusWindow.hh @@ -0,0 +1,48 @@ +// +// StatusWindow.hh for pekwm +// Copyright © 2004-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + + +#ifndef _STATUS_WINDOW_HH_ +#define _STATUS_WINDOW_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" + +class PDecor; + +//! @brief Status display window. +class StatusWindow : public PDecor { +public: + StatusWindow(Theme *theme); + virtual ~StatusWindow(void); + + //! @brief Returns the StatusWindow instance pointer. + static StatusWindow *instance(void) { return _instance; } + + void draw(const std::wstring &text, bool do_center = false, Geometry *gm = 0); + +private: + // BEGIN - PDecor interface + virtual void loadTheme(void); + // END - PDecor interface + void unloadTheme(void); + + void render(void); + void center(Geometry *gm = 0); + +private: + PWinObj *_status_wo; + Pixmap _bg; + + static StatusWindow *_instance; +}; + +#endif // _STATUS_WINDOW_HH_ diff --git a/pekwm/TextureHandler.cc b/pekwm/TextureHandler.cc new file mode 100644 index 0000000..43e3156 --- /dev/null +++ b/pekwm/TextureHandler.cc @@ -0,0 +1,244 @@ +// +// TextureHandler.cc for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "PTexture.hh" +#include "TextureHandler.hh" +#include "PScreen.hh" +#include "PTexturePlain.hh" +#include "Util.hh" + +#include +#include + +using std::list; +using std::vector; +using std::map; +using std::string; +using std::cerr; +using std::endl; + +TextureHandler* TextureHandler::_instance = 0; +map TextureHandler::_parse_map = map(); + +const int TextureHandler::LENGTH_MIN = 5; + +//! @brief TextureHandler constructor +TextureHandler::TextureHandler(void) +{ +#ifdef DEBUG + if (_instance) { + cerr << __FILE__ << "@" << __LINE__ << ": " + << "TextureHandler(" << this << ")::TextureHandler()" + << " *** _instance already set: " << _instance << endl; + } +#endif // DEBUG + _instance = this; + + // fill parse map with values + _parse_map[""] = PTexture::TYPE_NO; + _parse_map["SOLID"] = PTexture::TYPE_SOLID; + _parse_map["SOLIDRAISED"] = PTexture::TYPE_SOLID_RAISED; + _parse_map["IMAGE"] = PTexture::TYPE_IMAGE; + _parse_map["EMPTY"] = PTexture::TYPE_EMPTY; +} + +//! @brief TextureHandler destructor +TextureHandler::~TextureHandler(void) +{ + _instance = 0; +} + +//! @brief Gets or creates a PTexture +PTexture* +TextureHandler::getTexture(const std::string &texture) +{ + // check for already existing entry + list::iterator it(_texture_list.begin()); + + for (; it != _texture_list.end(); ++it) { + if (*(*it) == texture) { + (*it)->incRef(); + return (*it)->getTexture(); + } + } + + // parse texture + PTexture *ptexture = parse(texture); + + if (ptexture) { + // create new entry + TextureHandler::Entry *entry = new TextureHandler::Entry(texture, ptexture); + entry->incRef(); + + _texture_list.push_back(entry); + } + + return ptexture; +} + +//! @brief Add/Increment reference cont for texture. +//! @return Pointer to texture referenced. +PTexture* +TextureHandler::referenceTexture(PTexture *texture) +{ + // Check for already existing entry + list::iterator it(_texture_list.begin()); + + for (; it != _texture_list.end(); ++it) { + if ((*it)->getTexture() == texture) { + (*it)->incRef(); + return texture; + } + } + + // Create new entry + TextureHandler::Entry *entry = new TextureHandler::Entry("", texture); + entry->incRef(); + + _texture_list.push_back(entry); + + return texture; +} + +//! @brief Returns a texture +void +TextureHandler::returnTexture(PTexture *texture) +{ + bool found = false; + + list::iterator it(_texture_list.begin()); + for (; it != _texture_list.end(); ++it) { + if ((*it)->getTexture() == texture) { + found = true; + + (*it)->decRef(); + if ((*it)->getRef() == 0) { + delete *it; + _texture_list.erase(it); + } + break; + } + } + + if (! found) { + delete texture; + } +} + +//! @brief Parses the string, and creates a texture +PTexture* +TextureHandler::parse(const std::string &texture) +{ + PTexture *ptexture = 0; + vector tok; + + PTexture::Type type; + if (Util::splitString(texture, tok, " \t")) { + type = ParseUtil::getValue(tok[0], _parse_map); + } else { + type = ParseUtil::getValue(texture, _parse_map); + } + + // need at least type and parameter, except for EMPTY type + if (tok.size() > 1) { + tok.erase(tok.begin()); // remove type + switch (type) { + case PTexture::TYPE_SOLID: + ptexture = parseSolid(tok); + break; + case PTexture::TYPE_SOLID_RAISED: + ptexture = parseSolidRaised(tok); + break; + case PTexture::TYPE_IMAGE: + ptexture = new PTextureImage(PScreen::instance()->getDpy(), tok[1]); + break; + case PTexture::TYPE_NO: + default: + break; + } + + // If it fails to load, set clean resources and set it to 0. + if (ptexture && ! ptexture->isOk()) { + delete ptexture; + ptexture = 0; + } + + } else if (type == PTexture::TYPE_EMPTY) { + ptexture = new PTexture(PScreen::instance()->getDpy()); + } + + return ptexture; +} + +//! @brief Parse and create PTextureSolid +PTexture* +TextureHandler::parseSolid(std::vector &tok) +{ + if (tok.size() < 1) { + cerr << "*** WARNING: not enough parameters to texture Solid" << endl; + return 0; + } + + PTextureSolid *tex = new PTextureSolid(PScreen::instance()->getDpy(), tok[0]); tok.erase(tok.begin()); + + // check if we have size + if (tok.size() == 1) { + parseSize(tex, tok[0]); + } + + return tex; +} + +//! @brief Parse and create PTextureSolidRaised +PTexture* +TextureHandler::parseSolidRaised(std::vector &tok) +{ + if (tok.size() < 3) { + cerr << "*** WARNING: not enough parameters to texture SolidRaised" << endl; + return 0; + } + + PTextureSolidRaised *tex = new PTextureSolidRaised(PScreen::instance()->getDpy(), + tok[0], tok[1], tok[2]); + tok.erase(tok.begin(), tok.begin() + 3); + + // Check if we have line width and offset. + if (tok.size() > 2) { + tex->setLineWidth(strtol(tok[0].c_str(), 0, 10)); + tex->setLineOff(strtol(tok[1].c_str(), 0, 10)); + tok.erase(tok.begin(), tok.begin() + 2); + } + // Check if have side draw specified. + if (tok.size() > 4) { + tex->setDraw(Util::isTrue(tok[0]), Util::isTrue(tok[1]), + Util::isTrue(tok[2]), Util::isTrue(tok[3])); + tok.erase(tok.begin(), tok.begin() + 4); + } + + // Check if we have size + if (tok.size() == 1) { + parseSize(tex, tok[0]); + } + + return tex; +} + +//! @brief Parses size parameter, i.e. 10x20 +void +TextureHandler::parseSize(PTexture *tex, const std::string &size) +{ + vector tok; + if ((Util::splitString(size, tok, "x", 2, true)) == 2) { + tex->setWidth(strtol(tok[0].c_str(), 0, 10)); + tex->setHeight(strtol(tok[1].c_str(), 0, 10)); + } +} diff --git a/pekwm/TextureHandler.hh b/pekwm/TextureHandler.hh new file mode 100644 index 0000000..283f7ce --- /dev/null +++ b/pekwm/TextureHandler.hh @@ -0,0 +1,78 @@ +// +// TextureHandler.hh for pekwm +// Copyright © 2005 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _TEXTURE_HANDLER_HH_ +#define _TEXTURE_HANDLER_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "ParseUtil.hh" + +#include +#include +#include + +extern "C" { +#include +} + +class PTexture; + +class TextureHandler { +public: + class Entry { + public: + Entry(const std::string &name, PTexture *texture) : _name(name), _texture(texture), _ref(0) { } + ~Entry(void) { delete _texture; } + + PTexture *getTexture(void) { return _texture; } + + inline uint getRef(void) const { return _ref; } + inline void incRef(void) { ++_ref; } + inline void decRef(void) { if (_ref > 0) { --_ref; } + } + + inline bool operator==(const std::string &name) { + return (::strcasecmp(_name.c_str(), name.c_str()) == 0); + } + + private: + std::string _name; + PTexture *_texture; + + uint _ref; + }; + + TextureHandler(void); + ~TextureHandler(void); + + static TextureHandler *instance(void) { return _instance; } + static int getLengthMin(void) { return LENGTH_MIN; } + + PTexture *getTexture(const std::string &texture); + PTexture *referenceTexture(PTexture *texture); + void returnTexture(PTexture *texture); + +private: + PTexture *parse(const std::string &texture); + PTexture *parseSolid(std::vector &tok); + PTexture *parseSolidRaised(std::vector &tok); + + void parseSize(PTexture *tex, const std::string &size); + +private: + static TextureHandler *_instance; + static std::map _parse_map; + static const int LENGTH_MIN; //!< Minimum texture name length. + + std::list _texture_list; +}; + +#endif // _TEXTURE_HANDLER_HH_ diff --git a/pekwm/Theme.cc b/pekwm/Theme.cc new file mode 100644 index 0000000..e3ca7f8 --- /dev/null +++ b/pekwm/Theme.cc @@ -0,0 +1,1059 @@ +// +// Theme.cc for pekwm +// Copyright © 2003-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "ParseUtil.hh" +#include "Theme.hh" + +#include "PScreen.hh" +#include "Config.hh" +#include "PWinObj.hh" +#include "PFont.hh" +#include "PTexture.hh" +#include "ColorHandler.hh" +#include "FontHandler.hh" +#include "ImageHandler.hh" +#include "TextureHandler.hh" +#include "Util.hh" + +#include +#include + +using std::cerr; +using std::endl; +using std::string; +using std::list; +using std::vector; +using std::map; + +// Theme::PDecorButtonData + +//! @brief Theme::PDecorButtonData constructor. +Theme::PDecorButtonData::PDecorButtonData(void) + : ThemeData(), + _left(false), _width(1), _height(1) +{ + for (uint i = 0; i < BUTTON_STATE_NO; ++i) { + _texture[i] = 0; + } +} + +//! @brief Theme::PDecorButtonData destructor. +Theme::PDecorButtonData::~PDecorButtonData(void) +{ + unload(); +} + +//! @brief Parses CfgParser::Entry section, loads and verifies data. +//! @param section CfgParser::Entry with button configuration. +//! @return True if a valid button was parsed. +bool +Theme::PDecorButtonData::load(CfgParser::Entry *section) +{ + if (*section == "LEFT") { + _left = true; + } else if (*section == "RIGHT") { + _left = false; + } else { + return false; + } + + // Get actions. + ActionEvent ae; + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + if (Config::instance()->parseActionEvent(*it, ae, BUTTONCLICK_OK, true)) { + _ae_list.push_back (ae); + } + } + + // Got some actions, consider it to be a valid button. + if (_ae_list.size() > 0) { + TextureHandler *th = TextureHandler::instance(); + CfgParser::Entry *value; + + value = section->find_entry("FOCUSED"); + if (value) { + _texture[BUTTON_STATE_FOCUSED] = th->getTexture(value->get_value()); + } + + value = section->find_entry("UNFOCUSED"); + if (value) { + _texture[BUTTON_STATE_UNFOCUSED] = th->getTexture(value->get_value()); + } + + value = section->find_entry("PRESSED"); + if (value) { + _texture[BUTTON_STATE_PRESSED] = th->getTexture(value->get_value()); + } + + // HOOVER has been kept around due to backwards compatibility. + value = section->find_entry("HOVER"); + if (! value) { + value = section->find_entry("HOOVER"); + } + if (value) { + _texture[BUTTON_STATE_HOVER] = th->getTexture(value->get_value()); + } + + check(); + + return true; + } + + return false; +} + +//! @brief Unloads data. +void +Theme::PDecorButtonData::unload(void) +{ + for (uint i = 0; i < BUTTON_STATE_NO; ++i) { + TextureHandler::instance()->returnTexture(_texture[i]); + _texture[i] = 0; + } +} + +//! @brief Verifies and makes sure no 0 textures exists. +void +Theme::PDecorButtonData::check(void) +{ + for (uint i = 0; i < (BUTTON_STATE_NO - 1); ++i) { + if (! _texture[i]) { + _texture[i] = TextureHandler::instance()->getTexture("EMPTY"); + } + } + + _width = _texture[BUTTON_STATE_FOCUSED]->getWidth(); + _height = _texture[BUTTON_STATE_FOCUSED]->getHeight(); +} + +// Theme::PDecorData + +map Theme::PDecorData::_fs_map = map(); +map Theme::PDecorData::_border_map = map(); + +//! @brief Theme::PDecorData constructor. +Theme::PDecorData::PDecorData(const char *name) + : ThemeData(), + _title_height(0), _title_width_min(0), _title_width_max(100), + _title_width_symetric(true), _title_height_adapt(false) +{ + if (name) { + _name = name; + } + + // init static data + if (! _fs_map.size()) { + _fs_map[FOCUSED_STATE_FOCUSED] = "FOCUSED"; + _fs_map[FOCUSED_STATE_UNFOCUSED] = "UNFOCUSED"; + _fs_map[FOCUSED_STATE_FOCUSED_SELECTED] = "FOCUSEDSELECTED"; + _fs_map[FOCUSED_STATE_UNFOCUSED_SELECTED] = "UNFOCUSEDSELECTED"; + } + if (! _border_map.size()) { + _border_map[BORDER_TOP_LEFT] = "TOPLEFT"; + _border_map[BORDER_TOP] = "TOP"; + _border_map[BORDER_TOP_RIGHT] = "TOPRIGHT"; + _border_map[BORDER_LEFT] = "LEFT"; + _border_map[BORDER_RIGHT] = "RIGHT"; + _border_map[BORDER_BOTTOM_LEFT] = "BOTTOMLEFT"; + _border_map[BORDER_BOTTOM] = "BOTTOM"; + _border_map[BORDER_BOTTOM_RIGHT] = "BOTTOMRIGHT"; + } + + // init arrays + for (uint i = 0; i < PAD_NO; ++i) { + _pad[i] = 0; + } + for (uint i = 0; i < FOCUSED_STATE_NO; ++i) { + _texture_tab[i] = 0; + _font[i] = 0; + _font_color[i] = 0; + } + for (uint i = 0; i < FOCUSED_STATE_FOCUSED_SELECTED; ++i) { + _texture_main[i] = 0; + _texture_separator[i] = 0; + } + + for (uint i = 0; i < FOCUSED_STATE_FOCUSED_SELECTED; ++i) { + for (uint j = 0; j < BORDER_NO_POS; ++j) { + _texture_border[i][j] = 0; + } + } +} + +//! @brief Theme::PDecorData destructor. +Theme::PDecorData::~PDecorData(void) +{ + unload(); +} + +//! @brief Parses CfgParser::Entry section, loads and verifies data. +//! @param section CfgParser::Entry with pdecor configuration. +//! @return True if a valid pdecor was parsed. +bool +Theme::PDecorData::load(CfgParser::Entry *section) +{ + if (! section) { + return false; + } + + CfgParser::Entry *value; + + _name = section->get_value(); + if (! _name.size()) { + cerr << " *** WARNING: no name identifying decor" << endl; + return false; + } + + CfgParser::Entry *title_section = section->find_section("TITLE"); + if (! title_section) { + cerr << " *** WARNING: no title section in decor: " << _name << endl; + return false; + } + + TextureHandler *th = TextureHandler::instance(); // convenience + + vector tok; + list key_list; + string value_pad, value_focused, value_unfocused; + + key_list.push_back(new CfgParserKeyNumeric("HEIGHT", _title_height, 10, 0)); + key_list.push_back(new CfgParserKeyNumeric("WIDTHMIN", _title_width_min, 0)); + key_list.push_back(new CfgParserKeyNumeric("WIDTHMAX", _title_width_max, 100, 0, 100)); + key_list.push_back(new CfgParserKeyBool("WIDTHSYMETRIC", _title_width_symetric)); + key_list.push_back(new CfgParserKeyBool("HEIGHTADAPT", _title_height_adapt)); + key_list.push_back(new CfgParserKeyString("PAD", value_pad, "0 0 0 0", 7)); + key_list.push_back(new CfgParserKeyString("FOCUSED", value_focused, "Empty", th->getLengthMin ())); + key_list.push_back(new CfgParserKeyString("UNFOCUSED", value_unfocused, "Empty", th->getLengthMin ())); + + // Free up resources + title_section->parse_key_values(key_list.begin(), key_list.end()); + + for_each (key_list.begin(), key_list.end(), Util::Free()); + key_list.clear(); + + // Handle parsed data. + _texture_main[FOCUSED_STATE_FOCUSED] = th->getTexture(value_focused); + _texture_main[FOCUSED_STATE_UNFOCUSED] = th->getTexture(value_unfocused); + if (Util::splitString(value_pad, tok, " \t", 4) == 4) { + for (uint i = 0; i < PAD_NO; ++i) + _pad[i] = strtol(tok[i].c_str (), 0, 10); + } + + CfgParser::Entry *tab_section = title_section->find_section("TAB"); + if (tab_section) { + for (uint i = 0; i < FOCUSED_STATE_NO; ++i) { + value = tab_section->find_entry(_fs_map[FocusedState (i)]); + if (value) { + _texture_tab[i] = th->getTexture(value->get_value ()); + } + } + } + + CfgParser::Entry *separator_section = title_section->find_section ("SEPARATOR"); + if (separator_section) { + key_list.push_back(new CfgParserKeyString("FOCUSED", value_focused, "Empty", th->getLengthMin())); + key_list.push_back(new CfgParserKeyString("UNFOCUSED", value_unfocused, "Empty", th->getLengthMin())); + + // Parse data + separator_section->parse_key_values(key_list.begin(), key_list.end()); + + // Free up resources + for_each (key_list.begin(), key_list.end(), Util::Free()); + key_list.clear(); + + // Handle parsed data. + _texture_separator[FOCUSED_STATE_FOCUSED] = th->getTexture(value_focused); + _texture_separator[FOCUSED_STATE_UNFOCUSED] = th->getTexture(value_unfocused); + } + + + CfgParser::Entry *font_section = title_section->find_section ("FONT"); + if (font_section) { + for (uint i = 0; i < FOCUSED_STATE_NO; ++i) { + value = font_section->find_entry(_fs_map[FocusedState(i)]); + if (value) { + _font[i] = FontHandler::instance()->getFont(value->get_value ()); + } + } + } else { + cerr << " *** WARNING: no font section in decor: " << _name << endl; + } + + CfgParser::Entry *fontcolor_section = title_section->find_section ("FONTCOLOR"); + if (fontcolor_section) { + for (uint i = 0; i < FOCUSED_STATE_NO; ++i) { + value = fontcolor_section->find_entry (_fs_map[FocusedState(i)]); + if (value) { + _font_color[i] = FontHandler::instance()->getColor(value->get_value ()); + } + } + } + + loadButtons (title_section->find_section("BUTTONS")); + loadBorder (title_section->find_section("BORDER")); + + check(); + + return true; +} + +//! @brief Unloads data. +void +Theme::PDecorData::unload(void) +{ + TextureHandler *th = TextureHandler::instance(); + + for (uint i = 0; i < FOCUSED_STATE_NO; ++i) { + th->returnTexture(_texture_tab[i]); + FontHandler::instance()->returnFont(_font[i]); + FontHandler::instance()->returnColor(_font_color[i]); + + _texture_tab[i] = 0; + _font[i] = 0; + _font_color[i] = 0; + } + + for (uint i = 0; i < FOCUSED_STATE_FOCUSED_SELECTED; ++i) { + th->returnTexture(_texture_main[i]); + th->returnTexture(_texture_separator[i]); + _texture_main[i] = 0; + _texture_separator[i] = 0; + + for (uint j = 0; j < BORDER_NO_POS; ++j) { + th->returnTexture(_texture_border[i][j]); + _texture_border[i][j] = 0; + } + } + + list::iterator it(_button_list.begin()); + for (; it != _button_list.end(); ++it) { + delete *it; + } + _button_list.clear(); +} + +//! @brief Checks data properties, prints warning and tries to fix. +void +Theme::PDecorData::check(void) +{ + // check values + if (_title_width_max > 100) { + cerr << " *** WARNING: " << _name << " WIDTHMAX > 100" << endl; + _title_width_max = 100; + } + + checkTextures(); + checkFonts(); + checkBorder(); + checkColors(); +} + +//! @brief Loads border data. +void +Theme::PDecorData::loadBorder(CfgParser::Entry *section) +{ + if (! section) { + return; + } + + TextureHandler *th = TextureHandler::instance(); // convenience + + CfgParser::Entry *sub, *value; + + sub = section->find_section ("FOCUSED"); + if (sub) { + for (uint i = 0; i < BORDER_NO_POS; ++i) { + value = sub->find_entry (_border_map[BorderPosition (i)]); + if (value) { + _texture_border[FOCUSED_STATE_FOCUSED][i] = + th->getTexture (value->get_value ()); + } + } + } + + sub = section->find_section ("UNFOCUSED"); + if (sub) { + for (uint i = 0; i < BORDER_NO_POS; ++i) { + value = sub->find_entry (_border_map[BorderPosition (i)]); + if (value) { + _texture_border[FOCUSED_STATE_UNFOCUSED][i] = + th->getTexture (value->get_value ()); + } + } + } +} + +//! @brief Loads button data. +void +Theme::PDecorData::loadButtons(CfgParser::Entry *section) +{ + if (! section) { + return; + } + + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + if (! (*it)->get_section()) { + continue; + } + + Theme::PDecorButtonData *btn = new Theme::PDecorButtonData (); + if (btn->load((*it)->get_section())) { + _button_list.push_back(btn); + } else { + delete btn; + } + } +} + +//! @brief Checks for 0 textures, prints warning and sets empty texture +void +Theme::PDecorData::checkTextures(void) +{ + for (uint i = 0; i < FOCUSED_STATE_NO; ++i) { + if (! _texture_tab[i]) { + cerr << " *** WARNING: " << _name << " missing tab texture state " + << _fs_map[FocusedState(i)] << endl; + _texture_tab[i] = TextureHandler::instance()->getTexture("EMPTY"); + } + } + for (uint i = 0; i < FOCUSED_STATE_FOCUSED_SELECTED; ++i) { + if (! _texture_main[i]) { + cerr << " *** WARNING: " << _name << " missing main texture state " + << _fs_map[FocusedState(i)] << endl; + _texture_main[i] = TextureHandler::instance()->getTexture("EMPTY"); + } + if (! _texture_separator[i]) { + cerr << " *** WARNING: " << _name << " missing tab texture state " + << _fs_map[FocusedState(i)] << endl; + _texture_separator[i] = TextureHandler::instance()->getTexture("EMPTY"); + } + } +} + +//! @brief Checks for 0 fonts, prints warning and sets empty font +void +Theme::PDecorData::checkFonts(void) +{ + // the only font that's "obligatory" is the standard focused font, + // others are only used if availible so we only check the focused font. + if (! _font[FOCUSED_STATE_FOCUSED]) { + cerr << " *** WARNING: " << _name << " missing font state " + << _fs_map[FOCUSED_STATE_FOCUSED] << endl; + _font[FOCUSED_STATE_FOCUSED] = FontHandler::instance()->getFont(""); + } +} + +//! @brief Checks for 0 border PTextures. +void +Theme::PDecorData::checkBorder(void) +{ + for (uint state = FOCUSED_STATE_FOCUSED; state < FOCUSED_STATE_FOCUSED_SELECTED; ++state) { + for (uint i = 0; i < BORDER_NO_POS; ++i) { + if (! _texture_border[state][i]) { + cerr << " *** WARNING: " << _name << " missing border texture " + << _border_map[BorderPosition(i)] << " " + << _fs_map[FocusedState(state)] << endl; + _texture_border[state][i] = + TextureHandler::instance()->getTexture("EMPTY"); + } + } + } +} + +//! @brief Checks for 0 colors, prints warning and sets empty color +void +Theme::PDecorData::checkColors(void) +{ + for (uint i = 0; i < FOCUSED_STATE_NO; ++i) { + if (! _font_color[i]) { + cerr << " *** WARNING: " << _name << " missing font color state " + << _fs_map[FocusedState(i)] << endl; + _font_color[i] = FontHandler::instance()->getColor("#000000"); + } + } +} + +// Theme::PMenuData + +//! @brief PMenuData constructor +Theme::PMenuData::PMenuData(void) + : ThemeData() +{ + for (uint i = 0; i <= OBJECT_STATE_NO; ++i) { + _font[i] = 0; + _color[i] = 0; + _tex_menu[i] = 0; + _tex_item[i] = 0; + _tex_arrow[i] = 0; + } + for (uint i = 0; i < OBJECT_STATE_NO; ++i) { + _tex_sep[i] = 0; + } + for (uint i = 0; i < PAD_NO; ++i) { + _pad[i] = 0; + } +} + +//! @brief PMenuData destructor +Theme::PMenuData::~PMenuData(void) +{ + unload(); +} + +//! @brief Parses CfgParser::Entry section, loads and verifies data. +//! @param section CfgParser::Entry with pmenu configuration. +bool +Theme::PMenuData::load(CfgParser::Entry *section) +{ + CfgParser::Entry *value; + value = section->find_entry ("PAD"); + if (value) { + vector tok; + if (Util::splitString (value->get_value (), tok, " \t", 4) == 4) { + for (int i = 0; i < PAD_NO; ++i) { + _pad[i] = strtol (tok[i].c_str(), 0, 10); + } + } + } + + value = section->find_section ("FOCUSED"); + if (value) { + loadState(value, OBJECT_STATE_FOCUSED); + } + + value = section->find_section ("UNFOCUSED"); + if (value) { + loadState(value, OBJECT_STATE_UNFOCUSED); + } + + value = section->find_section ("SELECTED"); + if (value) { + loadState(value, OBJECT_STATE_SELECTED); + } + + check(); + + return true; +} + +//! @brief Unloads data. +void +Theme::PMenuData::unload(void) +{ + for (uint i = 0; i <= OBJECT_STATE_NO; ++i) { + FontHandler::instance()->returnFont(_font[i]); + FontHandler::instance()->returnColor(_color[i]); + TextureHandler::instance()->returnTexture(_tex_menu[i]); + TextureHandler::instance()->returnTexture(_tex_item[i]); + TextureHandler::instance()->returnTexture(_tex_arrow[i]); + + _font[i] = 0; + _color[i] = 0; + _tex_menu[i] = 0; + _tex_item[i] = 0; + _tex_arrow[i] = 0; + } + + for (uint i = 0; i < OBJECT_STATE_NO; ++i) { + TextureHandler::instance()->returnTexture(_tex_sep[i]); + _tex_sep[i] = 0; + } +} + +//! @brief Check data properties, prints warning and tries to fix. +void +Theme::PMenuData::check(void) +{ + for (uint i = 0; i <= OBJECT_STATE_NO; ++i) { + if (! _font[i]) { + _font[i] = FontHandler::instance()->getFont(""); + } + if (! _color[i]) { + _color[i] = FontHandler::instance()->getColor("#000000"); + } + if (! _tex_menu[i]) { + _tex_menu[i] = TextureHandler::instance()->getTexture("EMPTY"); + } + if (! _tex_item[i]) { + _tex_item[i] = TextureHandler::instance()->getTexture("EMPTY"); + } + if (! _tex_arrow[i]) { + _tex_arrow[i] = TextureHandler::instance()->getTexture("EMPTY"); + } + } + + for (uint i = 0; i < OBJECT_STATE_NO; ++i) { + if (! _tex_sep[i]) { + _tex_sep[i] = TextureHandler::instance()->getTexture("EMPTY"); + } + } +} + +//! @brief +void +Theme::PMenuData::loadState (CfgParser::Entry *section, ObjectState state) +{ + list key_list; + string value_font, value_background, value_item; + string value_text, value_arrow, value_separator; + + key_list.push_back(new CfgParserKeyString ("FONT", value_font)); + key_list.push_back(new CfgParserKeyString ("BACKGROUND", value_background, "Solid #ffffff")); + key_list.push_back(new CfgParserKeyString ("ITEM", value_item, "Solid #ffffff")); + key_list.push_back(new CfgParserKeyString ("TEXT", value_text, "Solid #000000")); + key_list.push_back(new CfgParserKeyString ("ARROW", value_arrow, "Solid #000000")); + if (state < OBJECT_STATE_SELECTED) { + key_list.push_back(new CfgParserKeyString("SEPARATOR", value_separator, "Solid #000000")); + } + + section->parse_key_values(key_list.begin(), key_list.end()); + + for_each(key_list.begin(), key_list.end(), Util::Free()); + + TextureHandler *th = TextureHandler::instance (); + + // Handle parsed data. + _font[state] = FontHandler::instance ()->getFont (value_font); + _tex_menu[state] = th->getTexture (value_background); + _tex_item[state] = th->getTexture (value_item); + _color[state] = FontHandler::instance ()->getColor (value_text); + _tex_arrow[state] = th->getTexture (value_arrow); + if (state < OBJECT_STATE_SELECTED) { + _tex_sep[state] = th->getTexture(value_separator); + } +} + +// Theme::TextDialogData + +//! @brief TextDialogData constructor. +Theme::TextDialogData::TextDialogData(void) + : ThemeData(), + _font(0), _color(0), _tex(0) +{ + for (uint i = 0; i < PAD_NO; ++i) { + _pad[i] = 0; + } +} + +//! @brief TextDialogData destructor. +Theme::TextDialogData::~TextDialogData(void) +{ + unload(); +} + +//! @brief Parses CfgParser::Entry section, loads and verifies data. +//! @param section CfgParser::Entry with textdialog configuration. +bool +Theme::TextDialogData::load(CfgParser::Entry *section) +{ + list key_list; + string value_font, value_text, value_texture, value_pad; + + key_list.push_back(new CfgParserKeyString("FONT", value_font)); + key_list.push_back(new CfgParserKeyString("TEXT", value_text, "#000000")); + key_list.push_back(new CfgParserKeyString("TEXTURE", value_texture, "Solid #ffffff")); + key_list.push_back(new CfgParserKeyString("PAD", value_pad, "0 0 0 0", 7)); + + section->parse_key_values(key_list.begin(), key_list.end()); + + for_each(key_list.begin(), key_list.end(), Util::Free()); + + // Handle parsed data. + _font = FontHandler::instance ()->getFont (value_font); + _color = FontHandler::instance ()->getColor (value_text); + _tex = TextureHandler::instance ()->getTexture (value_texture); + + vector tok; + if (Util::splitString(value_pad, tok, " \t", 4) == 4) { + for (uint i = 0; i < PAD_NO; ++i) { + _pad[i] = strtol(tok[i].c_str(), 0, 10); + } + } + + check(); + + return true; +} + +//! @brief Unloads data. +void +Theme::TextDialogData::unload(void) +{ + FontHandler::instance()->returnFont(_font); + FontHandler::instance()->returnColor(_color); + TextureHandler::instance()->returnTexture(_tex); + + _font = 0; + _tex = 0; + _color = 0; +} + +//! @brief Check data properties, prints warning and tries to fix. +//! @todo print warnings +void +Theme::TextDialogData::check(void) +{ + if (! _font) { + _font = FontHandler::instance()->getFont(""); + } + if (! _color) { + _color = FontHandler::instance()->getColor("#000000"); + } + if (! _tex) { + _tex = TextureHandler::instance()->getTexture("EMPTY"); + } +} + +// WorkspaceIndicatorData + +/** + * WorkspaceIndicatorData constructor + */ +Theme::WorkspaceIndicatorData::WorkspaceIndicatorData(void) + : ThemeData(), + font(0), font_color(0), texture_background(0), + texture_workspace(0), texture_workspace_act(0), + edge_padding(0), workspace_padding(0) +{ +} + +/** + * WorkspaceIndicatorData destructor + */ +Theme::WorkspaceIndicatorData::~WorkspaceIndicatorData(void) +{ + unload(); +} + +/** + * Load theme data and check. + */ +bool +Theme::WorkspaceIndicatorData::load(CfgParser::Entry *section) +{ + list key_list; + + string value_font, value_color, value_tex_bg; + string value_tex_ws, value_tex_ws_act; + + key_list.push_back(new CfgParserKeyString("FONT", value_font)); + key_list.push_back(new CfgParserKeyString("TEXT", value_color)); + key_list.push_back(new CfgParserKeyString("BACKGROUND", value_tex_bg)); + key_list.push_back(new CfgParserKeyString("WORKSPACE", value_tex_ws)); + key_list.push_back(new CfgParserKeyString("WORKSPACEACTIVE", value_tex_ws_act)); + key_list.push_back(new CfgParserKeyNumeric("EDGEPADDING", edge_padding, 5, 0)); + key_list.push_back(new CfgParserKeyNumeric("WORKSPACEPADDING", workspace_padding, 2, 0)); + + section->parse_key_values(key_list.begin(), key_list.end()); + for_each(key_list.begin(), key_list.end(), Util::Free()); + + font = FontHandler::instance()->getFont(value_font); + font_color = FontHandler::instance()->getColor(value_color); + texture_background = TextureHandler::instance()->getTexture(value_tex_bg); + texture_workspace = TextureHandler::instance()->getTexture(value_tex_ws); + texture_workspace_act = TextureHandler::instance()->getTexture(value_tex_ws_act); + + check(); + + return true; +} + +/** + * Unload loaded theme data. + */ +void +Theme::WorkspaceIndicatorData::unload(void) +{ + FontHandler::instance()->returnFont(font); + FontHandler::instance()->returnColor(font_color); + TextureHandler::instance()->returnTexture(texture_background); + TextureHandler::instance()->returnTexture(texture_workspace); + TextureHandler::instance()->returnTexture(texture_workspace_act); + + font = 0; + font_color = 0; + texture_background = 0; + texture_workspace = 0; + texture_workspace_act = 0; + edge_padding = 0; + workspace_padding = 0; +} + +/** + * Validate theme data loading + */ +void +Theme::WorkspaceIndicatorData::check(void) +{ + if (! font) { + font = FontHandler::instance()->getFont("Sans#Center#XFT"); + } + + if (! font_color) { + font_color = FontHandler::instance()->getColor("#000000"); + } + + if (! texture_background) { + texture_background = TextureHandler::instance()->getTexture("Solid #ffffff"); + } + + if (! texture_workspace) { + texture_workspace = TextureHandler::instance()->getTexture("Solid #cccccc"); + } + + if (! texture_workspace_act) { + texture_workspace_act = TextureHandler::instance()->getTexture("Solid #aaaaaa"); + } +} + +/** + * HarbourData constructor. + */ +Theme::HarbourData::HarbourData(void) + : ThemeData(), + _texture(0) +{ +} + +/** + * HarbourData destructor, unload data. + */ +Theme::HarbourData::~HarbourData(void) +{ + unload(); +} + +/** + * Load harbour data and validate state, unloading previously loaded + * data if any. + */ +bool +Theme::HarbourData::load(CfgParser::Entry *section) +{ + CfgParser::Entry *value; + + value = section->find_entry ("TEXTURE"); + if (value) { + _texture = TextureHandler::instance()->getTexture (value->get_value()); + } + + check(); + + return true; +} + + +/** + * Unload harbour data. + */ +void +Theme::HarbourData::unload(void) +{ + if (_texture) { + TextureHandler::instance()->returnTexture(_texture); + _texture = 0; + } +} + + +/** + * Check state of harbour data. + */ +void +Theme::HarbourData::check(void) +{ + if (! _texture) { + _texture = TextureHandler::instance()->getTexture("EMPTY"); + } +} + +// Theme + +//! @brief Theme constructor +Theme::Theme(PScreen *scr) + : _scr(scr), _image_handler(0), + _is_loaded(false), _invert_gc(None) +{ + // image handler + _image_handler = new ImageHandler(); + + // Map between theme sections and ThemeData structures. + _section_data_map["MENU"] = &_menu_data; + _section_data_map["STATUS"] = &_status_data; + _section_data_map["CMDDIALOG"] = &_cmd_d_data; + _section_data_map["WORKSPACEINDICATOR"] = &_workspace_indicator_data; + _section_data_map["HARBOUR"] = &_harbour_data; + + // window gc's + XGCValues gv; + gv.function = GXinvert; + gv.subwindow_mode = IncludeInferiors; + gv.line_width = 1; + _invert_gc = XCreateGC(_scr->getDpy(), _scr->getRoot(), + GCFunction|GCSubwindowMode|GCLineWidth, &gv); + + _scr->grabServer(); + + load(Config::instance()->getThemeFile()); + + _scr->ungrabServer(true); +} + +//! @brief Theme destructor +Theme::~Theme(void) +{ + unload(); // should clean things up + + XFreeGC(_scr->getDpy(), _invert_gc); + + delete _image_handler; +} + +/** + * Re-loads theme if needed, clears up previously used resources. + */ +bool +Theme::load(const std::string &dir) +{ + string norm_dir(dir); + if (dir.size() && dir.at(dir.size() - 1) != '/') { + norm_dir.append("/"); + } + string theme_file(norm_dir + string("theme")); + + if (! Util::requireReload(_cfg_state, theme_file)) { + return false; + } + + if (_is_loaded) { + unload(); + } + + _theme_dir = norm_dir; + if (! _theme_dir.size()) { + cerr << " *** WARNING: empty theme directory name, using default." << endl; + _theme_dir = DATADIR "/pekwm/themes/default/"; + } + + bool theme_ok = true; + CfgParser theme; + + if (! theme.parse(theme_file)) { + _theme_dir = DATADIR "/pekwm/themes/default/"; + theme_file = _theme_dir + string("theme"); + if (! theme.parse(theme_file)) { + cerr << " *** WARNING: couldn't load " << _theme_dir << " or default theme." << endl; + theme_ok = false; + } + } + + // Setup quirks and requirements before parsing. + if (theme_ok) { + if (theme.is_dynamic_content()) { + _cfg_state.clear(); + } else { + _cfg_state = theme.get_file_list(); + } + loadThemeRequire(theme, theme_file); + } + + // Set image basedir. + _image_handler->path_clear(); + _image_handler->path_push_back(_theme_dir); + + // Load decor data. + CfgParser::Entry *section = theme.get_entry_root()->find_section("PDECOR"); + if (section) { + CfgParser::iterator it(section->begin()); + for (; it != section->end(); ++it) { + Theme::PDecorData *data = new Theme::PDecorData(); + if (data->load((*it)->get_section())) { + _pdecordata_map[data->getName()] = data; + } else { + delete data; + } + } + } + + if (! getPDecorData("DEFAULT")) { + // Create DEFAULT decor, let check fill it up with empty but non-null data. + cerr << " *** WARNING: theme doesn't contain any DEFAULT decor." << endl; + Theme::PDecorData *decor_data = new Theme::PDecorData("DEFAULT"); + decor_data->check(); + _pdecordata_map["DEFAULT"] = decor_data; + } + + map::iterator it(_section_data_map.begin()); + for (; it != _section_data_map.end(); ++it) { + section = theme.get_entry_root()->find_section(it->first); + if (section) { + it->second->load(section); + } else { + cerr << " *** WARNING: missing " << it->first << " section!" << endl; + it->second->check(); + } + } + + _is_loaded = true; + + return true; +} + +/** + * Load template quirks. + */ +void +Theme::loadThemeRequire(CfgParser &theme_cfg, std::string &file) +{ + CfgParser::Entry *section; + + // Look for requires section, + section = theme_cfg.get_entry_root()->find_section("REQUIRE"); + if (section) { + list key_list; + bool value_templates; + + key_list.push_back(new CfgParserKeyBool("TEMPLATES", value_templates, false)); + section->parse_key_values(key_list.begin(), key_list.end()); + for_each(key_list.begin(), key_list.end(), Util::Free()); + + // Re-load configuration with templates enabled. + if (value_templates) { + theme_cfg.clear(true); + theme_cfg.parse(file, CfgParserSource::SOURCE_FILE, true); + } + } +} + +/** + * Unload theme data. + */ +void +Theme::unload(void) +{ + // Unload decors + map::iterator p_it(_pdecordata_map.begin()); + for (; p_it != _pdecordata_map.end(); ++p_it) { + delete p_it->second; + } + _pdecordata_map.clear(); + + // Unload theme data + map::iterator d_it(_section_data_map.begin()); + for (; d_it != _section_data_map.end(); ++d_it) { + d_it->second->unload(); + } + + _is_loaded = false; +} diff --git a/pekwm/Theme.hh b/pekwm/Theme.hh new file mode 100644 index 0000000..2a526f8 --- /dev/null +++ b/pekwm/Theme.hh @@ -0,0 +1,396 @@ +// +// Theme.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _THEME_HH_ +#define _THEME_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" + +#include "CfgParser.hh" +#include "Action.hh" // ActionEvent +#include "PFont.hh" // PFont::Color +#include "ParseUtil.hh" + +class PScreen; +class PTexture; +class Button; +class ButtonData; +class ImageHandler; + +#include +#include + +//! @brief Theme data parser and container. +class Theme +{ +public: + /** + * Base class for all theme data objects, provides interface for + * loading and un-loading of decoration data. + */ + class ThemeData { + public: + virtual ~ThemeData() {} + virtual bool load(CfgParser::Entry *section) = 0; + virtual void unload(void) = 0; + virtual void check(void) = 0; + }; + + //! @brief Theme data parser and container for PDecor::Button + class PDecorButtonData : public ThemeData { + public: + PDecorButtonData(void); + virtual ~PDecorButtonData(void); + + //! @brief Returns wheter the button is positioned relative to the left title edge. + inline bool isLeft(void) const { return _left; } + //! @brief Returns width of button. + inline uint getWidth(void) const { return _width; } + //! @brief Returns height of button. + inline uint getHeight(void) const { return _height; } + + //! @brief Returns PTexture used in ButtonState state. + inline PTexture *getTexture(ButtonState state) { + return _texture[(state != BUTTON_STATE_NO) ? state : 0]; + } + //! @brief Returns iterator to the first ActionEvent. + inline std::list::iterator begin(void) { + return _ae_list.begin(); + } + //! @brief Return iterator to the last+1 ActionEvent. + inline std::list::iterator end(void) { + return _ae_list.end(); + } + + virtual bool load(CfgParser::Entry *section); + virtual void unload(void); + virtual void check(void); + + private: + std::list _ae_list; + PTexture *_texture[BUTTON_STATE_NO]; + + bool _left; + uint _width, _height; + }; + + //! @brief PDecor theme data container and parser. + class PDecorData : public ThemeData { + public: + PDecorData(const char *name=0); + virtual ~PDecorData(void); + + //! @brief Returns decor name. + inline const std::string &getName(void) const { return _name; } + //! @brief Sets decor name. + inline void setName(const std::string &name) { _name = name; } + + //! @brief Returns title height. + inline int getTitleHeight(void) const { return _title_height; } + //! @brief Returns title minimum width (0 for full width title). + inline int getTitleWidthMin(void) const { return _title_width_min; } + //! @brief Returns title maximum width in procent. + inline int getTitleWidthMax(void) const { return _title_width_max; } + //! @brief Returns title text pad for dir. + inline int getPad(PadType pad) const { + return _pad[(pad != PAD_NO) ? pad : 0]; + } + + //! @brief Returns wheter all items in the title have same width. + inline bool isTitleWidthSymetric(void) const { + return _title_width_symetric; + } + //! @brief Returns wheter titlebar height should be relative the font height + inline bool isTitleHeightAdapt(void) const { return _title_height_adapt; } + + // Title textures + + //! @brief Returns background PTexture used in FocusedState state. + inline PTexture *getTextureMain(FocusedState state) { + return _texture_main[(state < FOCUSED_STATE_FOCUSED_SELECTED) + ? state : 0]; + } + //! @brief Returns tab PTexture used in FocusedState state. + inline PTexture *getTextureTab(FocusedState state) { + return _texture_tab[(state != FOCUSED_STATE_NO) ? state : 0]; + } + //! @brief Returns separator PTexture used in FocusedState state. + inline PTexture *getTextureSeparator(FocusedState state) { + return _texture_separator[(state < FOCUSED_STATE_FOCUSED_SELECTED) + ? state : 0]; + } + + // font + + //! @brief Returns PFont used in FocusedState state. + inline PFont *getFont(FocusedState state) const { + return _font[((state == FOCUSED_STATE_NO) || ! _font[state]) + ? FOCUSED_STATE_FOCUSED : state]; + } + //! @brief Return PFont::Color used in FocusedState state. + inline PFont::Color *getFontColor(FocusedState state) { + return _font_color[(state != FOCUSED_STATE_NO) ? state : 0]; + } + + // border + + //! @brief Return border PTexture used in FocusedState state for pos. + inline PTexture *getBorderTexture(FocusedState state, BorderPosition pos) { + return _texture_border[(state < FOCUSED_STATE_FOCUSED_SELECTED) + ? state : 0][pos]; + } + + // button + + //! @brief Return iterator to the first Theme::PDecorButtonData. + inline std::list::iterator buttonBegin(void) { + return _button_list.begin(); + } + //! @brief Return iterator to the last+1 Theme::PDecorButtonData. + inline std::list::iterator buttonEnd(void) { + return _button_list.end(); + } + + virtual bool load(CfgParser::Entry *section); + virtual void unload(void); + virtual void check(void); + + private: + void loadBorder(CfgParser::Entry *cs); + void loadButtons(CfgParser::Entry *cs); + + void checkTextures(void); + void checkFonts(void); + void checkBorder(void); + void checkColors(void); + + private: + std::string _name; + + // size, padding etc + int _title_height; + int _title_width_min, _title_width_max; + int _pad[PAD_NO]; + bool _title_width_symetric; + bool _title_height_adapt; + + // title + PTexture *_texture_main[FOCUSED_STATE_FOCUSED_SELECTED]; + PTexture *_texture_tab[FOCUSED_STATE_NO]; + PTexture *_texture_separator[FOCUSED_STATE_FOCUSED_SELECTED]; + + // font + PFont *_font[FOCUSED_STATE_NO]; + PFont::Color *_font_color[FOCUSED_STATE_NO]; + + // border + PTexture *_texture_border[FOCUSED_STATE_FOCUSED_SELECTED][BORDER_NO_POS]; + + // buttons + std::list _button_list; + + static std::map _fs_map; + static std::map _border_map; + }; + + //! @brief PMenu theme data container and parser. + class PMenuData : public ThemeData { + public: + PMenuData(void); + virtual ~PMenuData(void); + + //! @brief Returns PFont used in ObjectState state. + inline PFont *getFont(ObjectState state) { return _font[state]; } + //! @brief Returns PFont::Color used in ObjectState state. + inline PFont::Color *getColor(ObjectState state) { return _color[state]; } + //! @brief Returns menu PTexture used in ObjectState state. + inline PTexture *getTextureMenu(ObjectState state) { + return _tex_menu[state]; + } + //! @brief Returns item PTexture used in ObjectState state. + inline PTexture *getTextureItem(ObjectState state) { + return _tex_item[state]; + } + //! @brief Returns arrow PTexture used in ObjectState state. + inline PTexture *getTextureArrow(ObjectState state) { + return _tex_arrow[state]; + } + //! @brief Returns separator PTexture used in ObjectState state. + inline PTexture *getTextureSeparator(ObjectState state) { + return _tex_sep[(state < OBJECT_STATE_SELECTED) + ? state : OBJECT_STATE_FOCUSED]; + } + //! @brief Returns text pad in PadType dir. + inline uint getPad(PadType dir) const { + return _pad[(dir != PAD_NO) ? dir : 0]; + } + + virtual bool load(CfgParser::Entry *section); + virtual void unload(void); + virtual void check(void); + + private: + void loadState(CfgParser::Entry *cs, ObjectState state); + + private: + PFont *_font[OBJECT_STATE_NO + 1]; + PFont::Color *_color[OBJECT_STATE_NO + 1]; + PTexture *_tex_menu[OBJECT_STATE_NO + 1]; + PTexture *_tex_item[OBJECT_STATE_NO + 1]; + PTexture *_tex_arrow[OBJECT_STATE_NO + 1]; + PTexture *_tex_sep[OBJECT_STATE_NO]; + + uint _pad[PAD_NO]; + }; + + //! @brief CmdDialog/StatusWindow theme data container and parser. + class TextDialogData : public ThemeData { + public: + TextDialogData(void); + virtual ~TextDialogData(void); + + //! @brief Returns PFont. + inline PFont *getFont(void) { return _font; } + //! @brief Returns PFont::Color. + inline PFont::Color *getColor(void) { return _color; } + //! @brief Returns background texture. + inline PTexture *getTexture(void) { return _tex; } + //! @brief Returns text pad in PadType dir. + inline uint getPad(PadType dir) const { + return _pad[(dir != PAD_NO) ? dir : 0]; + } + + virtual bool load(CfgParser::Entry *section); + virtual void unload(void); + virtual void check(void); + + private: + PFont *_font; + PFont::Color *_color; + PTexture *_tex; + + uint _pad[PAD_NO]; + }; + + /** + * Class holding WorkspaceIndicator theme data. + */ + class WorkspaceIndicatorData : public ThemeData { + public: + WorkspaceIndicatorData(void); + virtual ~WorkspaceIndicatorData(void); + + virtual bool load(CfgParser::Entry *section); + virtual void unload(void); + virtual void check(void); + + public: + PFont *font; + PFont::Color *font_color; + PTexture *texture_background; + PTexture *texture_workspace; + PTexture *texture_workspace_act; + + int edge_padding; + int workspace_padding; + }; + + /** + * Class holding harbour theme data. + */ + class HarbourData : public ThemeData { + public: + HarbourData(void); + virtual ~HarbourData(void); + + inline PTexture *getTexture(void) const { return _texture; } + + virtual bool load(CfgParser::Entry *section); + virtual void unload(void); + virtual void check(void); + private: + PTexture *_texture; /**< Texture for rendering dockapps in the harbour. */ + }; + + inline Theme::HarbourData *getHarbourData(void) { return &_harbour_data; } + + Theme(PScreen *scr); + ~Theme(void); + + bool load(const std::string &dir); + void unload(void); + + inline const GC &getInvertGC(void) const { return _invert_gc; } + + inline std::map::const_iterator decor_begin(void) { return _pdecordata_map.begin(); } + inline std::map::const_iterator decor_end(void) { return _pdecordata_map.end(); } + + /** + * Find PDecorData based on name. + */ + Theme::PDecorData *getPDecorData(const std::string &name) { + std::map::iterator it = _pdecordata_map.begin(); + for (; it != _pdecordata_map.end(); ++it) { + if (strcasecmp(it->first.c_str(), name.c_str()) == 0) { + return it->second; + } + } + + // Backwards compatibility, CMDDIALOG was used instead of + // INPUTDIALOG previously. + if (strcasecmp("INPUTDIALOG", name.c_str()) == 0) { + return getPDecorData("CMDDIALOG"); + } + + return 0; + } + + Theme::WorkspaceIndicatorData &getWorkspaceIndicatorData(void) { return _workspace_indicator_data; } + + // menu + inline Theme::PMenuData *getMenuData(void) { return &_menu_data; } + + // status/cmd + inline Theme::TextDialogData *getStatusData(void) { return &_status_data; } + inline Theme::TextDialogData *getCmdDialogData(void) { return &_cmd_d_data; } + +private: + void loadThemeRequire(CfgParser &theme_cfg, std::string &file); + +private: + PScreen *_scr; + ImageHandler *_image_handler; + + std::map _section_data_map; /**< Map between section names and data. */ + + std::string _theme_dir; /**< Path to theme directory. */ + std::map _cfg_state; /**< Map of file mtime for all files touched by a configuration. */ + + bool _is_loaded; + + // gc + GC _invert_gc; + + // frame decors + std::map _pdecordata_map; + + // menu + Theme::PMenuData _menu_data; + + HarbourData _harbour_data; /**< Data for styling harbour. */ + + // status window + TextDialogData _status_data, _cmd_d_data; + WorkspaceIndicatorData _workspace_indicator_data; +}; + +#endif // _THEME_HH_ diff --git a/pekwm/Timer.hh b/pekwm/Timer.hh new file mode 100644 index 0000000..a673ea7 --- /dev/null +++ b/pekwm/Timer.hh @@ -0,0 +1,174 @@ +// +// WorkspaceIndicator.hh for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _TIMER_HH_ +#define _TIMER_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" + +#include +extern "C" { +#include +} + +/** + * Timed event running action when timeout is reached. + */ +template +class TimedEvent +{ +public: + /** + * TimedEvent constructor, set timeval to current time + timeout miliseconds. + */ + TimedEvent(uint timeout, T set_data) + : data(set_data) + { + if (gettimeofday(&timeval, 0) == -1) { + // FIXME: throw sane exception + } + + timeval.tv_sec += timeout / 1000; + timeval.tv_usec += (timeout % 1000) * 1000; + } + + /** + * Compare timeout value + */ + bool operator<(const TimedEvent &rhs) { + return timercmp(&timeval, &rhs.timeval, <); + } + bool operator<(const struct timeval &rhs) { + return timercmp(&timeval, &rhs, <); + } + +public: + T data; //!< Data for timed event + struct timeval timeval; //!< Time when timer expires +}; + +/** + * Handler for timeout events. + */ +template +class Timer +{ +public: + typedef TimedEvent* timed_event_list_entry; + typedef typename std::list timed_event_list; + typedef typename timed_event_list::iterator timed_event_list_it; + + /** + * Timer destructor, removes all timed events. + */ + ~Timer(void) + { + timed_event_list_it it(_events.begin()); + + for (; it != _events.end(); ++it) { + delete *it; + } + } + + /** + * Add timeout to timer. + * + * @param timeout Timeout in miliseconds + * @param data Data for TimedEvent + * @return TimedEvent for timeout + */ + timed_event_list_entry add(uint timeout, T data) + { + TimedEvent *event = new TimedEvent(timeout, data); + + // Find place in event list + timed_event_list_it it(_events.begin()); + for (; it != _events.end() && *event < *(*it); ++it) + ; + + it = _events.insert(it, event); + + // Update timeout if first in the list + if (it == _events.begin()) { + updateTimer(); + } + + return event; + } + + /** + * Remove timeout from timer. + */ + void remove(timed_event_list_entry event) { + timed_event_list_it it(_events.begin()); + for (; it != _events.end(); ++it) { + if (*it == event) { + it = _events.erase(it); + break; + } + } + + if (it == _events.begin()) { + updateTimer(); + } + } + + /** + * Fill events list with TimedEvent that has timed out. + */ + unsigned int getTimedOut(timed_event_list &events) { + struct timeval now; + if (gettimeofday(&now, 0) == -1) { + return 0; // FIXME: raise exception? + } + + timed_event_list_it it(_events.begin()); + for (; it != _events.end(); ++it) { + if (*(*it) < now) { + events.push_back(*it); + it = --_events.erase(it); + } + } + + if (events.size() > 0) { + updateTimer(); + } + + return events.size(); + } + +private: + /** + * Update timer to run next timer. + */ + void updateTimer(void) { + if (! _events.size()) { + return; + } + + TimedEvent *event = *_events.begin(); + struct itimerval value; + + timerclear(&value.it_interval); + if (gettimeofday(&value.it_value, 0) == -1) { + // FIXME: raise exception? + } + timersub(&event->timeval, &value.it_value, &value.it_value); + + setitimer(ITIMER_REAL, &value, 0); + } + +private: + timed_event_list _events; //!< List of events in queue +}; + +#endif // _TIMER_HH_ diff --git a/pekwm/Types.hh b/pekwm/Types.hh new file mode 100644 index 0000000..887843b --- /dev/null +++ b/pekwm/Types.hh @@ -0,0 +1,32 @@ +// +// Types.hh for pekwm +// Copyright (C) 2003-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _TYPES_HH_ +#define _TYPES_HH_ + +extern "C" { +#include +} + +#ifndef ushort +#define ushort unsigned short +#endif // ushort + +#ifndef uint +#define uint unsigned int +#endif // uint + +#ifndef ulong +#define ulong unsigned long +#endif // ulong + +#ifndef uchar +#define uchar unsigned char +#endif // uchar + +#endif // _TYPES_HH_ diff --git a/pekwm/Util.cc b/pekwm/Util.cc new file mode 100644 index 0000000..a15bb32 --- /dev/null +++ b/pekwm/Util.cc @@ -0,0 +1,649 @@ +// +// Util.cc for pekwm +// Copyright © 2002-2009 Claes Nästén +// +// misc.cc for aewm++ +// Copyright (C) 2000 Frank Hale +// http://sapphire.sourceforge.net/ +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +} + +#include "Util.hh" + +using std::cerr; +using std::endl; +using std::ostringstream; +using std::string; +using std::transform; +using std::vector; +using std::wstring; +using std::list; +using std::ifstream; +using std::ofstream; +using std::find; +using std::map; +using std::getenv; +using std::wcstombs; +using std::wmemset; +using std::mbstowcs; +using std::exit; + +namespace Util { + +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 255 +#endif // HOST_NAME_MAX + +static iconv_t do_iconv_open(const char **from_names, const char **to_names); +static size_t do_iconv (iconv_t ic, const char **inp, size_t *in_bytes, + char **outp, size_t *out_bytes); + +// Initializers, members used for shared buffer +unsigned int WIDE_STRING_COUNT = 0; +iconv_t IC_TO_WC = reinterpret_cast(-1); +iconv_t IC_TO_UTF8 = reinterpret_cast(-1); +char *ICONV_BUF = 0; +size_t ICONV_BUF_LEN = 0; + +// Constants, name of iconv internal names +const char *ICONV_WC_NAMES[] = {"WCHAR_T", "UCS-4", 0}; +const char *ICONV_UTF8_NAMES[] = {"UTF-8", "UTF8", 0}; + +const char *ICONV_UTF8_INVALID_STR = ""; +const wchar_t *ICONV_WIDE_INVALID_STR = L""; + +// Constants, maximum number of bytes a single UTF-8 character can use. +const size_t UTF8_MAX_BYTES = 6; + +//! @brief Fork and execute command with /bin/sh and execlp +void +forkExec(std::string command) +{ + if (command.length() == 0) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "Util::forkExec() *** command length == 0" << endl; +#endif // DEBUG + return; + } + + pid_t pid = fork(); + switch (pid) { + case 0: + setsid(); + execlp("/bin/sh", "sh", "-c", command.c_str(), (char *) 0); + cerr << __FILE__ << "@" << __LINE__ << ": " + << "Util::forkExec(" << command << ") execlp failed." << endl; + exit(1); + case -1: + cerr << __FILE__ << "@" << __LINE__ << ": " + << "Util::forkExec(" << command << ") fork failed." << endl; + } +} + +/** + * Wrapper for gethostname returning a string instead of populating + * char buffer. + */ +std::string +getHostname(void) +{ + string hostname; + + // Set WM_CLIENT_MACHINE + char hostname_buf[HOST_NAME_MAX + 1]; + if (! gethostname(hostname_buf, HOST_NAME_MAX)) { + // Make sure it is null terminated + hostname_buf[HOST_NAME_MAX] = '\0'; + hostname = hostname_buf; + } + + return hostname; +} + +//! @brief Determines if the file exists +bool +isFile(const std::string &file) +{ + if (file.size() == 0) { + return false; + } + + struct stat stat_buf; + if (stat(file.c_str(), &stat_buf) == 0) { + return (S_ISREG(stat_buf.st_mode)); + } + + return false; +} + +//! @brief Determines if the file is executable for the current user. +bool +isExecutable(const std::string &file) +{ + if (file.size() == 0) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "Util::isExecutable() *** file length == 0" << endl; +#endif // DEBUG + return false; + } + + struct stat stat_buf; + if (! stat(file.c_str(), &stat_buf)) { + if (stat_buf.st_uid == getuid()) { // user readable and executable + if ((stat_buf.st_mode&S_IRUSR) && (stat_buf.st_mode&S_IXUSR)) { + return true; + } + } + if (getgid() == stat_buf.st_gid) { // group readable and executable + if ((stat_buf.st_mode&S_IRGRP) && (stat_buf.st_mode&S_IXGRP)) { + return true; + } + } + if ((stat_buf.st_mode&S_IROTH) && (stat_buf.st_mode&S_IXOTH)) { + return true; // other readable and executable + } + } + + return false; +} + +/** + * Get file mtime. + */ +time_t +getMtime(const std::string &file) +{ + struct stat stat_buf; + + if (! stat(file.c_str(), &stat_buf)) { + return stat_buf.st_mtime; + } else { + return 0; + } +} + +/** + * Check if file has different mtime than provided mtime. + */ +bool +isFileChanged(const std::string &file, time_t &mtime) +{ + time_t cur_mtime = getMtime(file); + if (cur_mtime != mtime) { + mtime = cur_mtime; + return true; + } + return false; +} + +/** + * Check if old_file needs to be reloaded due to it being different + * from new_file, path or mtime. + */ +bool +requireReload(std::map &state, const std::string &file) +{ + // Check for the file, signal reload if not previously loaded. + map::iterator it(state.find(file)); + if (it == state.end()) { + return true; + } + + // Check state of all files, if one is updated reload. + for (it = state.begin(); it != state.end(); ++it) { + if (isFileChanged(it->first, it->second)) { + return true; + } + } + + return false; +} + +/** + * Copies a single text file. + */ +bool +copyTextFile(const std::string &from, const std::string &to) +{ + if ((from.length() == 0) || (to.length() == 0)) { + return false; + } + + ifstream stream_from(from.c_str()); + if (! stream_from.good()) { + cerr << __FILE__ << "@" << __LINE__ << ": " + << "Can't copy: " << from << " to: " << to << endl; + return false; + } + + ofstream stream_to(to.c_str()); + if (! stream_to.good()) { + cerr << __FILE__ << "@" << __LINE__ << ": " + << "Can't copy: " << from << " to: " << to << endl; + return false; + } + + stream_to << stream_from.rdbuf(); + + return true; +} + +/** + * Get name of the current user. + */ +string +getUserName(void) +{ + // Try to lookup current user with + struct passwd *entry = getpwuid(geteuid()); + + if (entry && entry->pw_name) { + return entry->pw_name; + } else { + if (getenv("USER")) { + return getenv("USER"); + } else { + return "UNKNOWN"; + } + } +} + + +//! @brief Returns .extension of file +std::string +getFileExt(const std::string &file) +{ + string::size_type pos = file.find_last_of('.'); + if ((pos != string::npos) && (pos < file.size())) { + return file.substr(pos + 1, file.size() - pos - 1); + } else { + return string(""); + } +} + +//! @brief Returns dir part of file +std::string +getDir(const std::string &file) +{ + string::size_type pos = file.find_last_of('/'); + if ((pos != string::npos) && (pos < file.size())) { + return file.substr(0, pos); + } else { + return string(""); + } +} + +//! @brief Replaces the ~ with the complete homedir path. +void +expandFileName(std::string &file) +{ + if (file.size() > 0) { + if (file[0] == '~') { + file.replace(0, 1, getenv("HOME")); + } + } +} + +//! @brief Split the string str based on separator sep and put into vals +//! +//! This splits the string str into to max_tokens parts and puts in the vector +//! vals. If max is 0 then it'll split it into as many tokens +//! as possible, max defaults to 0. +//! splitString returns the number of tokens it put into vals. +//! +//! @param str String to split +//! @param vals Vector to put split values into +//! @param sep Separators to use when splitting string +//! @param max Maximum number of elements to put into vals (optional) +//! @param include_empty Include empty elements, defaults to false. +//! @return Number of tokens inserted into vals +uint +splitString(const std::string str, std::vector &toks, const char *sep, uint max, bool include_empty) +{ + if (str.size() < 1) { + return 0; + } + + uint n = toks.size(); + string::size_type s, e; + + s = str.find_first_not_of(" \t\n"); + for (uint i = 0; ((i < max) || (max == 0)) && (s != string::npos); ++i) { + e = str.find_first_of(sep, s); + + if ((e != string::npos) && (i < (max - 1))) { + toks.push_back(str.substr(s, e - s)); + } else if (s < str.size()) { + toks.push_back(str.substr(s, str.size() - s)); + break; + } else { + break; + } + + if (include_empty) { + s = str.find_first_of(sep, e + 1); + if (s != (e + 1)) { + s = str.find_first_not_of(sep, e); + } + } else { + s = str.find_first_not_of(sep, e); + } + } + + return (toks.size() - n); +} + +//! @brief Converts wide-character string to multibyte version +//! @param str String to convert. +//! @return Returns multibyte version of string. +std::string +to_mb_str(const std::wstring &str) +{ + size_t ret, num = str.size() * 6 + 1; + char *buf = new char[num]; + memset(buf, '\0', num); + + ret = wcstombs(buf, str.c_str(), num); + if (ret == static_cast(-1)) { + cerr << " *** WARNING: failed to convert wide string to multibyte string" << endl; + } + string ret_str(buf); + + delete [] buf; + + return ret_str; +} + +//! @brief Converts multibyte string to wide-character version +//! @param str String to convert. +//! @return Returns wide-character version of string. +std::wstring +to_wide_str(const std::string &str) +{ + size_t ret, num = str.size() + 1; + wchar_t *buf = new wchar_t[num]; + wmemset(buf, L'\0', num); + + ret = mbstowcs(buf, str.c_str(), num); + if (ret == static_cast(-1)) { + cerr << " *** WARNING: failed to convert multibyte string to wide string" << endl; + } + wstring ret_str(buf); + + delete [] buf; + + return ret_str; +} + +//! @brief Open iconv handle with to/from names. +//! @param from_names null terminated list of from name alternatives. +//! @param to_names null terminated list of to name alternatives. +//! @return iconv_t handle on success, else -1. +iconv_t +do_iconv_open(const char **from_names, const char **to_names) +{ + iconv_t ic = reinterpret_cast(-1); + + // Try all combinations of from/to name's to get a working + // conversion handle. + for (unsigned int i = 0; from_names[i]; ++i) { + for (unsigned int j = 0; to_names[j]; ++j) { + ic = iconv_open (to_names[j], from_names[i]); + if (ic != reinterpret_cast(-1)) { +#ifdef HAVE_ICONVCTL + int int_value_one = 1; + iconvctl(ic, ICONV_SET_DISCARD_ILSEQ, &int_value_one); +#endif // HAVE_ICONVCTL + return ic; + } + } + } + + return ic; +} + +//! @brief Iconv wrapper to hide different definitions of iconv. +//! @param ic iconv handle. +//! @param inp Input pointer. +//! @param in_bytes Input bytes. +//! @param outp Output pointer. +//! @param out_bytes Output bytes. +//! @return number of bytes converted irreversible or -1 on error. +size_t +do_iconv (iconv_t ic, const char **inp, size_t *in_bytes, + char **outp, size_t *out_bytes) +{ +#ifdef ICONV_CONST + return iconv(ic, inp, in_bytes, outp, out_bytes); +#else // !ICONV_CONST + return iconv(ic, const_cast(inp), in_bytes, outp, out_bytes); +#endif // ICONV_CONST +} + +//! @brief Init iconv conversion. +void +iconv_init (void) +{ + // Cleanup previous init if any, being paranoid. + iconv_deinit (); + + // Raise exception if this fails + IC_TO_WC = do_iconv_open (ICONV_UTF8_NAMES, ICONV_WC_NAMES); + IC_TO_UTF8 = do_iconv_open (ICONV_WC_NAMES, ICONV_UTF8_NAMES); + + // Equal mean + if (IC_TO_WC != reinterpret_cast(-1) + && IC_TO_UTF8 != reinterpret_cast(-1)) { + // Create shared buffer. + ICONV_BUF_LEN = 1024; + ICONV_BUF = new char[ICONV_BUF_LEN]; + } +} + +//! @brief Deinit iconv conversion. +void +iconv_deinit (void) +{ + // Cleanup resources + if (IC_TO_WC != reinterpret_cast(-1)) { + iconv_close (IC_TO_WC); + } + + if (IC_TO_UTF8 != reinterpret_cast(-1)) { + iconv_close (IC_TO_UTF8); + } + + if (ICONV_BUF) { + delete [] ICONV_BUF; + } + + // Set members to safe values + IC_TO_WC = reinterpret_cast(-1); + IC_TO_UTF8 = reinterpret_cast(-1); + ICONV_BUF = 0; + ICONV_BUF_LEN = 0; +} + +//! @brief Validate buffer size, grow if required. +//! @param size Required size. +void +iconv_buf_grow (size_t size) +{ + if (ICONV_BUF_LEN < size) { + // Free resources, if any. + if (ICONV_BUF) { + delete [] ICONV_BUF; + } + + // Calculate new buffer length and allocate new buffer + for (; ICONV_BUF_LEN < size; ICONV_BUF_LEN *= 2) + ; + ICONV_BUF = new char[ICONV_BUF_LEN]; + } +} + +//! @brief Converts wide-character string to UTF-8 +//! @param str String to convert. +//! @return Returns UTF-8 representation of string. +std::string +to_utf8_str(const std::wstring &str) +{ + string utf8_str; + + // Calculate length + size_t in_bytes = str.size() * sizeof (wchar_t); + size_t out_bytes = str.size() * UTF8_MAX_BYTES + 1; + + iconv_buf_grow(out_bytes); + + // Convert + const char *inp = reinterpret_cast(str.c_str()); + char *outp = ICONV_BUF; + size_t len = do_iconv (IC_TO_UTF8, &inp, &in_bytes, &outp, &out_bytes); + if (len != static_cast(-1)) { + // Terminate string and cache result + *outp = '\0'; + + utf8_str = ICONV_BUF; + + } else { + cerr << " *** WARNING: to_utf8_str, failed with error " + << strerror (errno) << endl; + utf8_str = ICONV_UTF8_INVALID_STR; + } + + return utf8_str; +} + +//! @brief Converts to wide string from UTF-8 +//! @param str String to convert. +//! @return Returns wide representation of string. +std::wstring +from_utf8_str(const std::string &str) +{ + wstring wide_str; + + // Calculate length + size_t in_bytes = str.size(); + size_t out_bytes = (in_bytes + 1) * sizeof(wchar_t); + + iconv_buf_grow(out_bytes); + + // Convert + const char *inp = str.c_str(); + char *outp = ICONV_BUF; + size_t len = do_iconv(IC_TO_WC, &inp, &in_bytes, &outp, &out_bytes); + if (len != static_cast(-1)) { + // Terminate string and cache result + *reinterpret_cast(outp) = L'\0'; + + wide_str = reinterpret_cast(ICONV_BUF); + } else { + cerr << " *** WARNING: from_utf8_str, failed on string \"" + << str << "\"." << endl; + wide_str = ICONV_WIDE_INVALID_STR; + } + + return wide_str; +} + +/** + * Add object to list making sure there are no duplicates. + */ +void +file_backed_list::push_back_unique(const std::wstring &entry) +{ + list::iterator it(find(begin(), end(), entry)); + if (it != end()) { + erase(it); + } + + push_back(entry); +} + +/** + * Load list from file, updating _path if set. + * + * @param path Load data from path. + * @return Number of elements loaded. + */ +unsigned int +file_backed_list::load (const std::string &path) +{ + unsigned int loaded = 0; + ifstream ifile; + ifile.open(path.c_str()); + if (ifile.is_open()) { + // Update only path if successfully opened. + _path = path; + + string mb_line; + + while (ifile.good()) { + getline(ifile, mb_line); + if (mb_line.size()) { + push_back(to_wide_str(mb_line)); + ++loaded; + } + } + + ifile.close(); + } + + return loaded; +} + +/** + * Save list from file overwriting previous data. + */ +bool +file_backed_list::save (const std::string &path) +{ + bool status = false; + ofstream ofile(path.c_str()); + if (ofile.is_open()) { + // Update path if successfully opened. + _path = path; + + string line; + + list::iterator it(begin ()); + for (; it != end(); ++it) { + ofile << to_utf8_str(*it) << "\n"; + } + + ofile.close(); + status = true; + } + + return status; +} + +} // end namespace Util. + diff --git a/pekwm/Util.hh b/pekwm/Util.hh new file mode 100644 index 0000000..ed3706c --- /dev/null +++ b/pekwm/Util.hh @@ -0,0 +1,176 @@ +// +// Util.hh for pekwm +// Copyright © 2002-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _UTIL_HH_ +#define _UTIL_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Types.hh" + +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +/** + * String utilities, convenience functions for making life easier + * when working with strings. + */ +namespace String { + /** Get safe version of position */ + inline size_t safe_position(size_t pos, size_t fallback = 0, size_t add = 0) { + return pos == std::wstring::npos ? fallback : (pos + add); + } +} + +//! @brief Namespace Util used for various small file/string tasks. +namespace Util { + void forkExec(std::string command); + std::string getHostname(void); + + bool isFile(const std::string &file); + bool isExecutable(const std::string &file); + time_t getMtime(const std::string &file); + bool isFileChanged(const std::string &file, time_t &mtime); + bool requireReload(std::map &state, const std::string &file); + + bool copyTextFile(const std::string &from, const std::string &to); + + std::string getUserName(void); + + std::string getFileExt(const std::string &file); + std::string getDir(const std::string &file); + void expandFileName(std::string &file); + uint splitString(const std::string str, std::vector &vals, const char *sep, uint max = 0, bool include_empty = false); + + template std::string to_string(T t) { + std::ostringstream oss; + oss << t; + return oss.str(); + } + + /** + * Converts string to uppercase + * + * @param str Reference to the string to convert + */ + inline void to_upper(std::string &str) { + std::transform(str.begin(), str.end(), str.begin(), (int(*)(int)) std::toupper); + } + + /** + * Converts string to lowercase + * + * @param str Reference to the string to convert + */ + inline void to_lower(std::string &str) { + std::transform(str.begin(), str.end(), str.begin(), (int(*)(int)) std::tolower); + } + + /** + * Converts wide string to uppercase + * + * @param str Reference to the string to convert + */ + inline void to_upper(std::wstring &str) { + std::transform(str.begin(), str.end(), str.begin(), (int(*)(int)) std::towupper); + } + + + /** + * Converts wide string to lowercase + * + * @param str Reference to the string to convert + */ + inline void to_lower(std::wstring &str) { + std::transform(str.begin(), str.end(), str.begin(), (int(*)(int)) std::towlower); + } + + std::string to_mb_str(const std::wstring &str); + std::wstring to_wide_str(const std::string &str); + + void iconv_init(void); + void iconv_deinit(void); + + /** + * Return value within bounds of min and max value. + */ + template + T between(T value, T min_val, T max_val) { + if (value < min_val) { + value = min_val; + } else if (value > max_val) { + value = max_val; + } + return value; + } + + std::string to_utf8_str(const std::wstring &str); + std::wstring from_utf8_str(const std::string &str); + + //! @brief Removes leading blanks( \n\t) from string. + inline void trimLeadingBlanks(std::string &trim) { + std::string::size_type first = trim.find_first_not_of(" \n\t"); + if ((first != std::string::npos) && + (first != (std::string::size_type) trim[0])) { + trim = trim.substr(first, trim.size() - first); + } + } + + //! @brief Returns true if value represents true(1 or TRUE). + inline bool isTrue(const std::string &value) { + if (value.size() > 0) { + if ((value[0] == '1') // check for 1 / 0 + || ! ::strncasecmp(value.c_str(), "TRUE", 4)) { + return true; + } + } + return false; + } + + //! @brief for_each delete utility. + template struct Free : public std::unary_function { + void operator ()(T t) { delete t; } + + }; + + /** + * File backed string list used to persist, amongst other things + * command history. + */ + class file_backed_list : public std::list + { + public: + /** Return path list is backed up by. */ + const std::string &get_path (void) const { return _path; } + /** Set path list is backed up by. */ + void set_path (const std::string &path) { _path = path; } + + void push_back_unique(const std::wstring &entry); + + unsigned int load (const std::string &path); + bool save (const std::string &path); + + private: + std::string _path; /**< Path to file backed version. */ + }; + +} + +#endif // _UTIL_HH_ diff --git a/pekwm/WORefMenu.cc b/pekwm/WORefMenu.cc new file mode 100644 index 0000000..6caea8b --- /dev/null +++ b/pekwm/WORefMenu.cc @@ -0,0 +1,69 @@ +// +// WORefMenu.hh for pekwm +// Copyright © 2004-2009 Claes Nästen +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif // HAVE_CONFIG_H + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "PMenu.hh" +#include "WORefMenu.hh" + +#include "PScreen.hh" +#include "Client.hh" + +using std::string; +using std::wstring; + +//! @brief WORefMenu constructor +//! @param scr Pointer to PScreen +//! @param theme Pointer to Theme +//! @param title Title of menu +//! @param name Name of menu +//! @param decor_name Name of decor, defaults to MENU +WORefMenu::WORefMenu(PScreen *scr, Theme *theme, const std::wstring &title, + const std::string &name, const std::string &decor_name) + : PMenu(theme, title, name, decor_name), PWinObjReference(0), + _title_base(title), + _title_pre(L" ["), _title_post(L"]") +{ +} + +//! @brief WORefMenu destructor +WORefMenu::~WORefMenu(void) +{ +} + +/** + * When notified, unmap all windows as window menu refers to object + * being removed. + */ +void +WORefMenu::notify(Observable *observable, Observation *observation) +{ + PWinObjReference::notify(observable, observation); + unmapAll(); +} + +//! @brief Sets the reference and updates the title +void +WORefMenu::setWORef(PWinObj *wo_ref) +{ + PWinObjReference::setWORef(wo_ref); + + wstring title(_title_base); + + // if of client type, add the clients named to the title + if (wo_ref && (wo_ref->getType() == PWinObj::WO_CLIENT)) { + Client *client = static_cast(wo_ref); + title += _title_pre + client->getTitle()->getVisible() + _title_post; + } + + setTitle(title); +} diff --git a/pekwm/WORefMenu.hh b/pekwm/WORefMenu.hh new file mode 100644 index 0000000..c1ca9e4 --- /dev/null +++ b/pekwm/WORefMenu.hh @@ -0,0 +1,42 @@ +// +// WORefMenu.hh for pekwm +// Copyright © 2004-2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifndef _WOREFMENU_HH_ +#define _WOREFMENU_HH_ + +#include + +#include "PWinObjReference.hh" + +class PWinObj; +class PMenu; +class PScreen; +class Theme; + +class WORefMenu : public PMenu, public PWinObjReference +{ +public: + WORefMenu(PScreen *scr, Theme *theme, + const std::wstring &title, const std::string &name, + const std::string &decor_name = "MENU"); + virtual ~WORefMenu(void); + + virtual void notify(Observable *observable, Observation *observation); + + virtual void setWORef(PWinObj *wo_ref); + +protected: + std::wstring _title_base; + std::wstring _title_pre, _title_post; +}; + +#endif // _WOREFMENU_HH_ diff --git a/pekwm/WindowManager.cc b/pekwm/WindowManager.cc new file mode 100644 index 0000000..5d7764e --- /dev/null +++ b/pekwm/WindowManager.cc @@ -0,0 +1,1748 @@ +// +// WindowManager.cc for pekwm +// Copyright © 2002-2009 Claes Nästén +// +// windowmanager.cc for aewm++ +// Copyright (C) 2000 Frank Hale +// http://sapphire.sourceforge.net/ +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "Frame.hh" +#include "Client.hh" +#include "WindowManager.hh" + +#include "PScreen.hh" +#include "ScreenResources.hh" +#include "ActionHandler.hh" +#include "AutoProperties.hh" +#include "Config.hh" +#include "Theme.hh" +#include "PFont.hh" +#include "PTexture.hh" +#include "ColorHandler.hh" +#include "FontHandler.hh" +#include "PixmapHandler.hh" +#include "TextureHandler.hh" +#include "Workspaces.hh" +#include "Util.hh" + +#include "RegexString.hh" + +#include "KeyGrabber.hh" +#include "MenuHandler.hh" +#include "Harbour.hh" +#include "HarbourMenu.hh" +#include "DockApp.hh" +#include "CmdDialog.hh" +#include "SearchDialog.hh" +#include "StatusWindow.hh" +#include "WorkspaceIndicator.hh" + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include + +#include +#include +#ifdef HAVE_XRANDR +#include +#endif // HAVE_XRANDR +} + +using std::cout; +using std::cerr; +using std::endl; +using std::find; +using std::list; +using std::map; +using std::mem_fun; +using std::string; +using std::vector; +using std::wstring; + +// Static initializers +WindowManager *WindowManager::_instance = 0; + +extern "C" { + + static bool is_signal_hup = false; + static bool is_signal_int_term = false; + static bool is_signal_alrm = false; + + /** + * Signal handler setting signal flags. + */ + static void + sigHandler(int signal) + { + switch (signal) { + case SIGHUP: + is_signal_hup = true; + break; + case SIGINT: + case SIGTERM: + is_signal_int_term = true; + break; + case SIGCHLD: + wait(0); + break; + case SIGALRM: + // Do nothing, just used to break out of waiting + is_signal_alrm = true; + break; + } + } + +} // extern "C" + + +// WindowManager + +/** + * Create window manager instance and run main routine. + */ +WindowManager* +WindowManager::start(const std::string &command_line, + const std::string &config_file, bool replace) +{ + if (_instance) { + delete _instance; + } + + // Setup window manager + _instance = new WindowManager(command_line, config_file); + + if (_instance->setupDisplay(replace)) { + _instance->scanWindows(); + Frame::resetFrameIDs(); + static_cast(PWinObj::getRootPWinObj())->setEwmhDesktopNames(); + + // add all frames to the MRU list + _instance->_mru_list.resize(Frame::frame_size()); + copy(Frame::frame_begin(), Frame::frame_end (), + _instance->_mru_list.begin()); + + _instance->execStartFile(); + } else { + delete _instance; + _instance = 0; + } + + return _instance; +} + +/** + * Destroy current running window manager. + */ +void +WindowManager::destroy(void) +{ + delete _instance; + _instance = 0; +} + +//! @brief Constructor for WindowManager class +WindowManager::WindowManager(const std::string &command_line, + const std::string &config_file) + : + _screen(0), _screen_resources(0), + _keygrabber(0), + _config(0), _color_handler(0), + _font_handler(0), _texture_handler(0), + _theme(0), _action_handler(0), + _autoproperties(0), _workspaces(0), + _harbour(0), + _cmd_dialog(0), _search_dialog(0), + _status_window(0), _workspace_indicator(0), + _command_line(command_line), + _startup(false), _shutdown(false), _reload(false), + _allow_grouping(true), _hint_wo(0), _root_wo(0) +{ + struct sigaction act; + + // Set up the signal handlers. + act.sa_handler = sigHandler; + act.sa_mask = sigset_t(); + act.sa_flags = SA_NOCLDSTOP | SA_NODEFER; + + sigaction(SIGTERM, &act, 0); + sigaction(SIGINT, &act, 0); + sigaction(SIGHUP, &act, 0); + sigaction(SIGCHLD, &act, 0); + sigaction(SIGALRM, &act, 0); + + // construct + _config = new Config(); + _config->load(config_file); + _config->loadMouseConfig(_config->getMouseConfigFile()); +} + +//! @brief WindowManager destructor +WindowManager::~WindowManager(void) +{ + cleanup(); + + delete _cmd_dialog; + delete _search_dialog; + delete _status_window; + delete _workspace_indicator; + MenuHandler::destroy(); + delete _harbour; + delete _root_wo; + delete _hint_wo; + delete _action_handler; + delete _autoproperties; + delete _keygrabber; + delete _workspaces; + delete _config; + delete _theme; + delete _color_handler; + delete _font_handler; + delete _texture_handler; + delete _screen_resources; + + if (_screen) { + Display *dpy = _screen->getDpy(); + + delete _screen; + + XCloseDisplay(dpy); + } +} + +//! @brief Checks if the start file is executable then execs it. +void +WindowManager::execStartFile(void) +{ + string start_file(_config->getStartFile()); + + bool exec = Util::isExecutable(start_file); + if (! exec) { + start_file = SYSCONFDIR "/start"; + exec = Util::isExecutable(start_file); + } + + if (exec) { + Util::forkExec(start_file); + } +} + +//! @brief Cleans up, Maps windows etc. +void +WindowManager::cleanup(void) +{ + // update all nonactive clients properties + list::iterator it_f(Frame::frame_begin()); + for (; it_f != Frame::frame_end(); ++it_f) { + (*it_f)->updateInactiveChildInfo(); + } + + _harbour->removeAllDockApps(); + + // To preserve stacking order when destroying the frames, we go through + // the PWinObj list from the Workspaces and put all Frames into our own + // list, then we delete the frames in order. + if (_workspaces) { + list frame_list; + + list::iterator it_w(_workspaces->begin()); + for (; it_w != _workspaces->end(); ++it_w) { + if ((*it_w)->getType() == PWinObj::WO_FRAME) { + frame_list.push_back(static_cast(*it_w)); + } + } + + // Delete all Frames. This reparents the Clients. + for (it_f = frame_list.begin(); it_f != frame_list.end(); ++it_f) { + delete *it_f; + } + } + + // Delete all Clients. + list client_list(Client::client_begin(), Client::client_end()); + list::iterator it_c(client_list.begin()); + for (; it_c != client_list.end(); ++it_c) { + delete *it_c; + } + + if (_keygrabber) { + _keygrabber->ungrabKeys(_screen->getRoot()); + } + + // destroy screen edge + screenEdgeDestroy(); + + XInstallColormap(_screen->getDpy(), _screen->getColormap()); + XSetInputFocus(_screen->getDpy(), PointerRoot, RevertToPointerRoot, CurrentTime); +} + +/** + * Setup display and claim resources. + */ +bool +WindowManager::setupDisplay(bool replace) +{ + Display *dpy = XOpenDisplay(0); + if (! dpy) { + cerr << "Can not open display!" << endl + << "Your DISPLAY variable currently is set to: " + << getenv("DISPLAY") << endl; + exit(1); + } + + // Setup screen, init atoms and claim the display. + _screen = new PScreen(dpy, _config->isHonourRandr()); + + PWinObj::setDisplay(dpy); + + Atoms::init(); + + try { + // Create hint window _before_ root window. + _hint_wo = new HintWO(_screen->getRoot(), replace); + } catch (string &ex) { + return false; + } + + // Create root PWinObj + _root_wo = new RootWO(_screen->getRoot()); + PWinObj::setRootPWinObj(_root_wo); + + _color_handler = new ColorHandler(dpy); + _font_handler = new FontHandler(); + _texture_handler = new TextureHandler(); + _action_handler = new ActionHandler(); + + // Setup the font trimming + PFont::setTrimString(_config->getTrimTitle()); + + // load colors, fonts + _screen_resources = new ScreenResources(); + _theme = new Theme(_screen); + + _autoproperties = new AutoProperties(); + _autoproperties->load(); + + _workspaces = new Workspaces(_config->getWorkspaces(), _config->getWorkspacesPerRow()); + + _harbour = new Harbour(_screen, _theme, _workspaces); + + MenuHandler::init(_theme); + + _cmd_dialog = new CmdDialog(_theme); + _search_dialog = new SearchDialog(_theme); + _status_window = new StatusWindow(_theme); + _workspace_indicator = new WorkspaceIndicator(_theme, _timer_action); + + XDefineCursor(dpy, _screen->getRoot(), + _screen_resources->getCursor(ScreenResources::CURSOR_ARROW)); + +#ifdef HAVE_XRANDR + XRRSelectInput(dpy, _screen->getRoot(), RRScreenChangeNotifyMask|RRCrtcChangeNotifyMask); +#endif // HAVE_XRANDR + + _keygrabber = new KeyGrabber(_screen); + _keygrabber->load(_config->getKeyFile()); + _keygrabber->grabKeys(_screen->getRoot()); + + // Create screen edge windows + screenEdgeCreate(); + screenEdgeMapUnmap(); + + return true; +} + +//! @brief Goes through the window and creates Clients/DockApps. +void +WindowManager::scanWindows(void) +{ + if (_startup) { // only done once when we start + return; + } + + uint num_wins; + Window d_win1, d_win2, *wins; + XWindowAttributes attr; + + // Lets create a list of windows on the display + XQueryTree(_screen->getDpy(), _screen->getRoot(), + &d_win1, &d_win2, &wins, &num_wins); + list win_list(wins, wins + num_wins); + XFree(wins); + + list::iterator it(win_list.begin()); + + // We filter out all windows with the the IconWindowHint + // set not pointing to themselves, making DockApps + // work as they are supposed to. + for (; it != win_list.end(); ++it) { + if (*it == None) { + continue; + } + + XWMHints *wm_hints = XGetWMHints(_screen->getDpy(), *it); + if (wm_hints) { + if ((wm_hints->flags&IconWindowHint) && + (wm_hints->icon_window != *it)) { + list::iterator i_it(find(win_list.begin(), win_list.end(), wm_hints->icon_window)); + if (i_it != win_list.end()) + *i_it = None; + } + XFree(wm_hints); + } + } + + Client *client; + for (it = win_list.begin(); it != win_list.end(); ++it) { + if (*it == None) { + continue; + } + + XGetWindowAttributes(_screen->getDpy(), *it, &attr); + if (! attr.override_redirect && attr.map_state != IsUnmapped) { + XWMHints *wm_hints = XGetWMHints(_screen->getDpy(), *it); + if (wm_hints) { + if ((wm_hints->flags&StateHint) && + (wm_hints->initial_state == WithdrawnState)) { + _harbour->addDockApp(new DockApp(_screen, _theme, *it)); + } else { + client = new Client(*it); + if (! client->isAlive()) { + delete client; + } + + } + XFree(wm_hints); + } else { + client = new Client(*it); + if (! client->isAlive()) + delete client; + } + } + } + + // Try to focus the ontop window, if no window we give root focus + PWinObj *wo = _workspaces->getTopWO(PWinObj::WO_FRAME); + if (wo && wo->isMapped()) { + wo->giveInputFocus(); + } else { + _root_wo->giveInputFocus(); + } + + // Try to find transients for all clients, on restarts ordering might + // not be correct. + list::iterator it_client = Client::client_begin(); + for (; it_client != Client::client_end(); ++it_client) { + if ((*it_client)->isTransient() && ! (*it_client)->getTransientClient()) { + (*it_client)->findAndRaiseIfTransient(); + } + } + + // We won't be needing these anymore until next restart + _autoproperties->removeApplyOnStart(); + + _startup = true; +} + +//! @brief Creates and places screen edge +void +WindowManager::screenEdgeCreate(void) +{ + if (_screen_edge_list.size() != 0) { + return; + } + + bool indent = Config::instance()->getScreenEdgeIndent(); + + _screen_edge_list.push_back(new EdgeWO(_screen->getRoot(), SCREEN_EDGE_LEFT, + indent && (_config->getScreenEdgeSize(SCREEN_EDGE_LEFT) > 0))); + _screen_edge_list.push_back(new EdgeWO(_screen->getRoot(), SCREEN_EDGE_RIGHT, + indent && (_config->getScreenEdgeSize(SCREEN_EDGE_RIGHT) > 0))); + _screen_edge_list.push_back(new EdgeWO(_screen->getRoot(), SCREEN_EDGE_TOP, + indent && (_config->getScreenEdgeSize(SCREEN_EDGE_TOP) > 0))); + _screen_edge_list.push_back(new EdgeWO(_screen->getRoot(), SCREEN_EDGE_BOTTOM, + indent && (_config->getScreenEdgeSize(SCREEN_EDGE_BOTTOM) > 0))); + + // make sure the edge stays ontop + list::iterator it(_screen_edge_list.begin()); + for (; it != _screen_edge_list.end(); ++it) { + _workspaces->insert(*it); + } + + screenEdgeResize(); +} + +//! @brief +void +WindowManager::screenEdgeDestroy(void) +{ + if (_screen_edge_list.size() == 0) { + return; + } + + list::iterator it(_screen_edge_list.begin()); + for (; it != _screen_edge_list.end(); ++it) { + _workspaces->remove(*it); + delete *it; + } +} + +//! @brief +void +WindowManager::screenEdgeResize(void) +{ + assert(_screen_edge_list.size() == 4); + + uint l_size = std::max(_config->getScreenEdgeSize(SCREEN_EDGE_LEFT), 1); + uint r_size = std::max(_config->getScreenEdgeSize(SCREEN_EDGE_RIGHT), 1); + uint t_size = std::max(_config->getScreenEdgeSize(SCREEN_EDGE_TOP), 1); + uint b_size = std::max(_config->getScreenEdgeSize(SCREEN_EDGE_BOTTOM), 1); + + list::iterator it(_screen_edge_list.begin()); + + // Left edge + (*it)->moveResize(0, 0, l_size, _screen->getHeight()); + ++it; + + // Right edge + (*it)->moveResize(_screen->getWidth() - r_size, 0, r_size, _screen->getHeight()); + ++it; + + // Top edge + (*it)->moveResize(l_size, 0, _screen->getWidth() - l_size - r_size, t_size); + ++it; + + // Bottom edge + (*it)->moveResize(l_size, _screen->getHeight() - b_size, _screen->getWidth() - l_size - r_size, b_size); + + for (it = _screen_edge_list.begin(); it != _screen_edge_list.end(); ++it) { + (*it)->configureStrut(_config->getScreenEdgeIndent() + && (_config->getScreenEdgeSize((*it)->getEdge()) > 0)); + } + + _screen->updateStrut(); +} + +void +WindowManager::screenEdgeMapUnmap(void) +{ + assert (_screen_edge_list.size() == 4); + + list::iterator it(_screen_edge_list.begin()); + for (; it != _screen_edge_list.end(); ++it) { + if ((_config->getScreenEdgeSize((*it)->getEdge()) > 0) && + (_config->getEdgeListFromPosition((*it)->getEdge())->size() > 0)) { + (*it)->mapWindow(); + } else { + (*it)->unmapWindow(); + } + } +} + +//! @brief Reloads configuration and updates states. +void +WindowManager::doReload(void) +{ + doReloadConfig(); + doReloadTheme(); + doReloadMouse(); + doReloadKeygrabber(); + doReloadAutoproperties(); + + MenuHandler::instance()->reloadMenus(); + // Special case for HARBOUR menu which is not included in the menu map + _harbour->getHarbourMenu()->reload(static_cast(0)); + doReloadHarbour(); + + _root_wo->setEwmhDesktopNames(); + + _reload = false; +} + +/** + * Reload main config file. + */ +void +WindowManager::doReloadConfig(void) +{ + // If any of these changes, re-fetch of all names is required + bool old_client_unique_name = _config->getClientUniqueName(); + string old_client_unique_name_pre = _config->getClientUniqueNamePre(); + string old_client_unique_name_post = _config->getClientUniqueNamePost(); + + // Reload configuration + if (! _config->load(_config->getConfigFile())) { + return; + } + + // Update what might have changed in the cfg touching the hints + _workspaces->setSize(_config->getWorkspaces()); + _workspaces->setPerRow(_config->getWorkspacesPerRow()); + _workspaces->setNames(); + + // Flush pixmap cache and set size + _screen_resources->getPixmapHandler()->setCacheSize(_config->getScreenPixmapCacheSize()); + + // Set the font title trim before reloading the themes + PFont::setTrimString(_config->getTrimTitle()); + + // Update the ClientUniqueNames if needed + if ((old_client_unique_name != _config->getClientUniqueName()) || + (old_client_unique_name_pre != _config->getClientUniqueNamePre()) || + (old_client_unique_name_post != _config->getClientUniqueNamePost())) { + for_each(Client::client_begin(), Client::client_end(), mem_fun(&Client::readName)); + } + + // Resize the screen edge + screenEdgeResize(); + screenEdgeMapUnmap(); + + _screen->updateStrut(); +} + +/** + * Reload theme file and update decorations. + */ +void +WindowManager::doReloadTheme(void) +{ + // Reload the theme + if (! _theme->load(_config->getThemeFile())) { + return; + } + + // Reload the themes on all decors + for_each(PDecor::pdecor_begin(), PDecor::pdecor_end(), mem_fun(&PDecor::loadDecor)); +} + +/** + * Reload mouse configuration and re-grab buttons on all windows. + */ +void +WindowManager::doReloadMouse(void) +{ + if (! _config->loadMouseConfig(_config->getMouseConfigFile())) { + return; + } + + for_each(Client::client_begin(), Client::client_end(), mem_fun(&Client::grabButtons)); +} + +/** + * Reload keygrabber configuration and re-grab keys on all windows. + */ +void +WindowManager::doReloadKeygrabber(bool force) +{ + // Reload the keygrabber + if (! _keygrabber->load(_config->getKeyFile(), force)) { + return; + } + + _keygrabber->ungrabKeys(_screen->getRoot()); + _keygrabber->grabKeys(_screen->getRoot()); + + // Regrab keys and buttons + list::iterator c_it(Client::client_begin()); + for (; c_it != Client::client_end(); ++c_it) { + (*c_it)->grabButtons(); + _keygrabber->ungrabKeys((*c_it)->getWindow()); + _keygrabber->grabKeys((*c_it)->getWindow()); + } +} + +/** + * Reload autoproperties. + */ +void +WindowManager::doReloadAutoproperties(void) +{ + if (! _autoproperties->load()) { + return; + } + + // NOTE: we need to load autoproperties after decor have been updated + // as otherwise old theme data pointer will be used and sig 11 pekwm. + list::iterator it_c(Client::client_begin()); + for (; it_c != Client::client_end(); ++it_c) { + (*it_c)->readAutoprops(APPLY_ON_RELOAD); + } + + list::iterator it_f(Frame::frame_begin()); + for (; it_f != Frame::frame_end(); ++it_f) { + (*it_f)->readAutoprops(APPLY_ON_RELOAD); + } +} + +/** + * Reload harbour configuration. + */ +void +WindowManager::doReloadHarbour(void) +{ + _harbour->loadTheme(); + _harbour->rearrange(); + _harbour->restack(); + _harbour->updateHarbourSize(); +} + +//! @brief Exit pekwm and restart with the command command +void +WindowManager::restart(std::string command) +{ + if (command.size() == 0) { + command = _command_line; + } + _restart_command = command; + _shutdown = true; +} + +// Event handling routins beneath this ===================================== + +void +WindowManager::doEventLoop(void) +{ + XEvent ev; + Timer::timed_event_list events; + + while (! _shutdown && ! is_signal_int_term) { + // Handle timeouts + if (is_signal_alrm) { + is_signal_alrm = false; + + if (_timer_action.getTimedOut(events)) { + Timer::timed_event_list_it it(events.begin()); + for (; it != events.end(); ++it) { + _action_handler->handleAction((*it)->data); + } + events.clear(); + } + } + + // Reload if requested + if (is_signal_hup || _reload) { + is_signal_hup = false; + doReload(); + } + + // Get next event, drop event handling if none was given + if (_screen->getNextEvent(ev)) { + switch (ev.type) { + case MapRequest: + handleMapRequestEvent(&ev.xmaprequest); + break; + case UnmapNotify: + handleUnmapEvent(&ev.xunmap); + break; + case DestroyNotify: + handleDestroyWindowEvent(&ev.xdestroywindow); + break; + + case ConfigureRequest: + handleConfigureRequestEvent(&ev.xconfigurerequest); + break; + case ClientMessage: + handleClientMessageEvent(&ev.xclient); + break; + case ColormapNotify: + handleColormapEvent(&ev.xcolormap); + break; + case PropertyNotify: + _screen->setLastEventTime(ev.xproperty.time); + handlePropertyEvent(&ev.xproperty); + break; + case MappingNotify: + handleMappingEvent(&ev.xmapping); + break; + case Expose: + handleExposeEvent(&ev.xexpose); + break; + + case KeyPress: + case KeyRelease: + _screen->setLastEventTime(ev.xkey.time); + handleKeyEvent(&ev.xkey); + break; + + case ButtonPress: + _screen->setLastEventTime(ev.xbutton.time); + handleButtonPressEvent(&ev.xbutton); + break; + case ButtonRelease: + _screen->setLastEventTime(ev.xbutton.time); + handleButtonReleaseEvent(&ev.xbutton); + break; + + case MotionNotify: + _screen->setLastEventTime(ev.xmotion.time); + handleMotionEvent(&ev.xmotion); + break; + + case EnterNotify: + _screen->setLastEventTime(ev.xcrossing.time); + handleEnterNotify(&ev.xcrossing); + break; + case LeaveNotify: + _screen->setLastEventTime(ev.xcrossing.time); + handleLeaveNotify(&ev.xcrossing); + break; + case FocusIn: + handleFocusInEvent(&ev.xfocus); + break; + case FocusOut: + handleFocusOutEvent(&ev.xfocus); + break; + + case SelectionClear: + // Another window + cerr << " *** INFO: Being replaced by another WM" << endl; + _shutdown = true; + break; + + default: +#ifdef HAVE_SHAPE + if (_screen->hasExtensionShape() && (ev.type == _screen->getEventShape())) { + handleShapeEvent(&ev.xany); + } +#endif // HAVE_SHAPE +#ifdef HAVE_XRANDR + if (_screen->hasExtensionXRandr() && (ev.type == _screen->getEventXRandr())) { + handleXRandrEvent(reinterpret_cast(&ev)); + } +#endif // HAVE_XRANDR + break; + } + } + } +} + +//! @brief Handle XKeyEvents +void +WindowManager::handleKeyEvent(XKeyEvent *ev) +{ + ActionEvent *ae = 0; + PWinObj *wo, *wo_orig; + wo = wo_orig = PWinObj::getFocusedPWinObj(); + PWinObj::Type type = (wo) ? wo->getType() : PWinObj::WO_SCREEN_ROOT; + + switch (type) { + case PWinObj::WO_CLIENT: + case PWinObj::WO_FRAME: + case PWinObj::WO_SCREEN_ROOT: + case PWinObj::WO_MENU: + if (ev->type == KeyPress) { + ae = _keygrabber->findAction(ev, type); + } + break; + case PWinObj::WO_CMD_DIALOG: + case PWinObj::WO_SEARCH_DIALOG: + if (ev->type == KeyPress) { + ae = wo->handleKeyPress(ev); + } else { + ae = wo->handleKeyRelease(ev); + } + wo = static_cast(wo)->getWORef(); + break; + default: + if (wo) { + if (ev->type == KeyPress) { + ae = wo->handleKeyPress(ev); + } else { + ae = wo->handleKeyRelease(ev); + } + } + break; + } + + if (ae) { + // HACK: Always close CmdDialog and SearchDialog before actions + if (wo_orig + && (wo_orig->getType() == PWinObj::WO_CMD_DIALOG + || wo_orig->getType() == PWinObj::WO_SEARCH_DIALOG)) { + ::Action close_action; + ActionEvent close_ae; + + close_ae.action_list.push_back(close_action); + close_ae.action_list.back().setAction(ACTION_CLOSE); + + ActionPerformed close_ap(wo_orig, close_ae); + _action_handler->handleAction(close_ap); + } + + ActionPerformed ap(wo, *ae); + ap.type = ev->type; + ap.event.key = ev; + _action_handler->handleAction(ap); + } + + // flush Enter events caused by keygrabbing + XEvent e; + while (XCheckTypedEvent(_screen->getDpy(), EnterNotify, &e)) { + if (! e.xcrossing.send_event) { + _screen->setLastEventTime(e.xcrossing.time); + } + } +} + +//! @brief +void +WindowManager::handleButtonPressEvent(XButtonEvent *ev) +{ + // Clear event queue + while (XCheckTypedEvent(_screen->getDpy(), ButtonPress, (XEvent *) ev)) { + if (! ev->send_event) { + _screen->setLastEventTime(ev->time); + } + } + + ActionEvent *ae = 0; + PWinObj *wo = 0; + + wo = PWinObj::findPWinObj(ev->window); + if (wo) { + ae = wo->handleButtonPress(ev); + + if (wo->getType() == PWinObj::WO_FRAME) { + // this is done so that clicking the titlebar executes action on + // the client clicked on, doesn't apply when subwindow is set (meaning + // a titlebar button beeing pressed) + if ((ev->subwindow == None) + && (ev->window == static_cast(wo)->getTitleWindow())) { + wo = static_cast(wo)->getChildFromPos(ev->x); + } else { + wo = static_cast(wo)->getActiveChild(); + } + } + } + + if (ae) { + ActionPerformed ap(wo, *ae); + ap.type = ev->type; + ap.event.button = ev; + + _action_handler->handleAction(ap); + } else { + DockApp *da = _harbour->findDockAppFromFrame(ev->window); + if (da) { + _harbour->handleButtonEvent(ev, da); + } + } +} + +//! @brief +void +WindowManager::handleButtonReleaseEvent(XButtonEvent *ev) +{ + // Flush ButtonReleases + while (XCheckTypedEvent(_screen->getDpy(), ButtonRelease, (XEvent *) ev)) { + if (! ev->send_event) { + _screen->setLastEventTime(ev->time); + } + } + + ActionEvent *ae = 0; + PWinObj *wo = PWinObj::findPWinObj(ev->window); + if (wo == _root_wo && ev->subwindow != None) { + wo = PWinObj::findPWinObj(ev->subwindow); + } + + if (wo) { + // Kludge for the case that wo is freed by handleButtonRelease(.). + PWinObj::Type wotype = wo->getType(); + ae = wo->handleButtonRelease(ev); + + if (wotype == PWinObj::WO_FRAME) { + // this is done so that clicking the titlebar executes action on + // the client clicked on, doesn't apply when subwindow is set (meaning + // a titlebar button beeing pressed) + if ((ev->subwindow == None) + && (ev->window == static_cast(wo)->getTitleWindow())) { + wo = static_cast(wo)->getChildFromPos(ev->x); + } else { + wo = static_cast(wo)->getActiveChild(); + } + } + + if (ae) { + ActionPerformed ap(wo, *ae); + ap.type = ev->type; + ap.event.button = ev; + + _action_handler->handleAction(ap); + } + } else { + DockApp *da = _harbour->findDockAppFromFrame(ev->window); + if (da) { + _harbour->handleButtonEvent(ev, da); + } + } +} + +void +WindowManager::handleConfigureRequestEvent(XConfigureRequestEvent *ev) +{ + Client *client = Client::findClientFromWindow(ev->window); + + if (client) { + ((Frame*) client->getParent())->handleConfigureRequest(ev, client); + + } else { + DockApp *da = _harbour->findDockApp(ev->window); + if (da) { + _harbour->handleConfigureRequestEvent(ev, da); + } else { + // Since this window isn't yet a client lets delegate + // the configure request back to the window so it can use it. + + XWindowChanges wc; + + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + + XConfigureWindow(_screen->getDpy(), ev->window, ev->value_mask, &wc); + } + } +} + +/** + * Handle motion event, match on event window expect when event window + * is root and subwindow is set then also match on menus. + */ +void +WindowManager::handleMotionEvent(XMotionEvent *ev) +{ + ActionEvent *ae = 0; + PWinObj *wo = PWinObj::findPWinObj(ev->window); + if (wo == _root_wo && ev->subwindow != None) { + wo = PWinObj::findPWinObj(ev->subwindow); + } + + if (wo) { + if (wo->getType() == PWinObj::WO_CLIENT) { + ae = wo->getParent()->handleMotionEvent(ev); + + } else if (wo->getType() == PWinObj::WO_FRAME) { + ae = wo->handleMotionEvent(ev); + + // this is done so that clicking the titlebar executes action on + // the client clicked on + if (ev->subwindow != None) { + wo = static_cast(wo)->getActiveChild(); + } else { + wo = static_cast(wo)->getChildFromPos(ev->x); + } + + } else { + ae = wo->handleMotionEvent(ev); + } + + if (ae) { + ActionPerformed ap(wo, *ae); + ap.type = ev->type; + ap.event.motion = ev; + + _action_handler->handleAction(ap); + } + } else { + DockApp *da = _harbour->findDockAppFromFrame(ev->window); + if (da) { + _harbour->handleMotionNotifyEvent(ev, da); + } + } +} + +//! @brief +void +WindowManager::handleMapRequestEvent(XMapRequestEvent *ev) +{ + PWinObj *wo = PWinObj::findPWinObj(ev->window); + + if (wo) { + wo->handleMapRequest(ev); + } else { + XWindowAttributes attr; + XGetWindowAttributes(_screen->getDpy(), ev->window, &attr); + if (! attr.override_redirect) { + // We need to figure out whether or not this is a dockapp. + XWMHints *wm_hints = XGetWMHints(_screen->getDpy(), ev->window); + if (wm_hints) { + if ((wm_hints->flags&StateHint) && + (wm_hints->initial_state == WithdrawnState)) { + _harbour->addDockApp(new DockApp(_screen, _theme, ev->window)); + } else { + Client *client = new Client(ev->window, true); + if (! client->isAlive()) { + delete client; + } + } + XFree(wm_hints); + } else { + Client *client = new Client(ev->window, true); + if (! client->isAlive()) { + delete client; + } + } + } + } +} + +//! @brief +void +WindowManager::handleUnmapEvent(XUnmapEvent *ev) +{ + PWinObj *wo = PWinObj::findPWinObj(ev->window); + PWinObj *wo_search = 0; + + PWinObj::Type wo_type = PWinObj::WO_NO_TYPE; + + if (wo) { + wo_type = wo->getType(); + if (wo_type == PWinObj::WO_CLIENT) { + wo_search = wo->getParent(); + } + + wo->handleUnmapEvent(ev); + + if (wo == PWinObj::getFocusedPWinObj()) { + PWinObj::setFocusedPWinObj(0); + } + } else { + DockApp *da = _harbour->findDockApp(ev->window); + if (da) { + if (ev->window == ev->event) { + _harbour->removeDockApp(da); + } + } + } + + if (wo_type != PWinObj::WO_MENU + && wo_type != PWinObj::WO_CMD_DIALOG + && wo_type != PWinObj::WO_SEARCH_DIALOG + && ! PWinObj::getFocusedPWinObj()) { + findWOAndFocus(wo_search); + } +} + +void +WindowManager::handleDestroyWindowEvent(XDestroyWindowEvent *ev) +{ + Client *client = Client::findClientFromWindow(ev->window); + + if (client) { + PWinObj *wo_search = client->getParent(); + client->handleDestroyEvent(ev); + + if (! PWinObj::getFocusedPWinObj()) { + findWOAndFocus(wo_search); + } + } else { + DockApp *da = _harbour->findDockApp(ev->window); + if (da) { + da->setAlive(false); + _harbour->removeDockApp(da); + } + } +} + +void +WindowManager::handleEnterNotify(XCrossingEvent *ev) +{ + // Clear event queue + while (XCheckTypedEvent(_screen->getDpy(), EnterNotify, (XEvent *) ev)) { + if (! ev->send_event) { + _screen->setLastEventTime(ev->time); + } + } + + if ((ev->mode == NotifyGrab) || (ev->mode == NotifyUngrab)) { + return; + } + + PWinObj *wo = PWinObj::findPWinObj(ev->window); + + if (wo) { + + if (wo->getType() == PWinObj::WO_CLIENT) { + wo = wo->getParent(); + } + + ActionEvent *ae = wo->handleEnterEvent(ev); + + if (ae) { + ActionPerformed ap(wo, *ae); + ap.type = ev->type; + ap.event.crossing = ev; + + _action_handler->handleAction(ap); + } + } +} + +void +WindowManager::handleLeaveNotify(XCrossingEvent *ev) +{ + // Clear event queue + while (XCheckTypedEvent(_screen->getDpy(), LeaveNotify, (XEvent *) ev)) { + if (! ev->send_event) { + _screen->setLastEventTime(ev->time); + } + } + + if ((ev->mode == NotifyGrab) || (ev->mode == NotifyUngrab)) { + return; + } + + PWinObj *wo = PWinObj::findPWinObj(ev->window); + + if (wo) { + ActionEvent *ae = wo->handleLeaveEvent(ev); + + if (ae) { + ActionPerformed ap(wo, *ae); + ap.type = ev->type; + ap.event.crossing = ev; + + _action_handler->handleAction(ap); + } + } +} + +//! @brief Handles FocusIn Events. +void +WindowManager::handleFocusInEvent(XFocusChangeEvent *ev) +{ + if ((ev->mode == NotifyGrab) || (ev->mode == NotifyUngrab)) { + return; + } + + PWinObj *wo = PWinObj::findPWinObj(ev->window); + if (wo) { + // To simplify logic, changing client in frame wouldn't update the + // focused window because wo != focused_wo woule be true. + if (wo->getType() == PWinObj::WO_FRAME) { + wo = static_cast(wo)->getActiveChild(); + } + + if (! wo->isFocusable() || ! wo->isMapped()) { + findWOAndFocus(0); + + } else if (wo != PWinObj::getFocusedPWinObj()) { + // If it's a valid FocusIn event with accepatable target lets flush + // all EnterNotify and LeaveNotify as they can interfere with + // focusing if Sloppy or Follow like focus model is used. + XEvent e_flush; + while (XCheckTypedEvent(_screen->getDpy(), EnterNotify, &e_flush)) { + if (! e_flush.xcrossing.send_event) { + _screen->setLastEventTime(e_flush.xcrossing.time); + } + } + while (XCheckTypedEvent(_screen->getDpy(), LeaveNotify, &e_flush)) { + if (! e_flush.xcrossing.send_event) { + _screen->setLastEventTime(e_flush.xcrossing.time); + } + } + + PWinObj *focused_wo = PWinObj::getFocusedPWinObj(); // convenience + + // unfocus last window + if (focused_wo) { + if (focused_wo->getType() == PWinObj::WO_CLIENT) { + focused_wo->getParent()->setFocused(false); + } else { + focused_wo->setFocused(false); + } + _workspaces->setLastFocused(_workspaces->getActive(), focused_wo); + } + + PWinObj::setFocusedPWinObj(wo); + + if (wo->getType() == PWinObj::WO_CLIENT) { + wo->getParent()->setFocused(true); + _root_wo->setEwmhActiveWindow(wo->getWindow()); + + // update the MRU list + _mru_list.remove(wo->getParent()); + _mru_list.push_back(wo->getParent()); + } else { + wo->setFocused(true); + _root_wo->setEwmhActiveWindow(None); + } + } + } +} + +//! @brief Handles FocusOut Events. +void +WindowManager::handleFocusOutEvent(XFocusChangeEvent *ev) +{ + // Get the last focus in event, no need to go through them all. + while (XCheckTypedEvent(_screen->getDpy(), FocusOut, (XEvent *) ev)) + ; +} + +/** + * Handle XClientMessageEvent events. + * + * @param ev Event to handle. + */ +void +WindowManager::handleClientMessageEvent(XClientMessageEvent *ev) +{ + if (ev->window == _screen->getRoot()) { + // root window messages + + if (ev->format == 32) { + + if (ev->message_type == Atoms::getAtom(NET_CURRENT_DESKTOP)) { + _workspaces->setWorkspace(ev->data.l[0], true); + + } else if (ev->message_type == + Atoms::getAtom(NET_NUMBER_OF_DESKTOPS)) { + if (ev->data.l[0] > 0) { + _workspaces->setSize(ev->data.l[0]); + } + } + } + + } else { + // client messages + + Client *client = Client::findClientFromWindow(ev->window); + if (client) { + static_cast(client->getParent())->handleClientMessage(ev, client); + } + } +} + +void +WindowManager::handleColormapEvent(XColormapEvent *ev) +{ + Client *client = Client::findClient(ev->window); + if (client) { + client = + static_cast(((Frame*) client->getParent())->getActiveChild()); + client->handleColormapChange(ev); + } +} + +//! @brief Handles XPropertyEvents +void +WindowManager::handlePropertyEvent(XPropertyEvent *ev) +{ + if (ev->window == _screen->getRoot()) { + if (ev->atom == Atoms::getAtom(NET_DESKTOP_NAMES)) { + _root_wo->readEwmhDesktopNames(); + _workspaces->setNames(); + } + + return; + } + + Client *client = Client::findClientFromWindow(ev->window); + + if (client) { + ((Frame*) client->getParent())->handlePropertyChange(ev, client); + } +} + +//! @brief Handles XMappingEvent +void +WindowManager::handleMappingEvent(XMappingEvent *ev) +{ + if (ev->request == MappingKeyboard || ev->request == MappingModifier) { + XRefreshKeyboardMapping(ev); + _screen->setLockKeys(); + InputDialog::reloadKeysymMap(); + doReloadKeygrabber(true); + } +} + +void +WindowManager::handleExposeEvent(XExposeEvent *ev) +{ + ActionEvent *ae = 0; + + PWinObj *wo = PWinObj::findPWinObj(ev->window); + if (wo) { + ae = wo->handleExposeEvent(ev); + } + + if (ae) { + ActionPerformed ap(wo, *ae); + ap.type = ev->type; + ap.event.expose = ev; + + _action_handler->handleAction(ap); + } +} + + +//! @brief Handle shape events applying shape to clients +void +WindowManager::handleShapeEvent(XAnyEvent *ev) +{ +#ifdef HAVE_SHAPE + Client *client = Client::findClient(ev->window); + if (client && client->getParent()) { + static_cast(client->getParent())->handleShapeEvent(ev); + } +#endif // HAVE_SHAPE +} + +#ifdef HAVE_XRANDR +//! @brief Handles XRandr events +//! @param ev XRRNotifyEvent to handle. +void +WindowManager::handleXRandrEvent(XRRNotifyEvent *ev) +{ + if (ev->subtype == RRNotify_CrtcChange) { + handleXRandrCrtcChangeEvent(reinterpret_cast(ev)); + } else { + handleXRandrScreenChangeEvent(reinterpret_cast(ev)); + } +} + +/** + * Handle screen change event. + * + * Reads the screen geometry and head information all over, updates + * the screen edge and harbour. + * + * @param ev XRRScreenChangeNotifyEvent event to handle. + */ +void +WindowManager::handleXRandrScreenChangeEvent(XRRScreenChangeNotifyEvent *ev) +{ +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": WindowManager::handleXRandrScreenChangeEvent()" << endl; +#endif // DEBUG + + _screen->updateGeometry(ev->width, ev->height); + _harbour->updateGeometry(); + screenEdgeResize(); + + // Make sure windows are visible after resize + list::iterator it(PDecor::pdecor_begin()); + for (; it != PDecor::pdecor_end(); ++it) { + _workspaces->placeWoInsideScreen(*it); + } +} + +//! @brief Handle crtc change event, does nothing. +//! @param ev XRRCrtcChangeNotifyEvent event to handle. +void +WindowManager::handleXRandrCrtcChangeEvent(XRRCrtcChangeNotifyEvent *ev) +{ +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": WindowManager::handleXRandrCrtcChangeEvent()" << endl; +#endif // DEBUG +} + +#endif // HAVE_XRANDR + +// Event handling routines stop ============================================ + +//! @brief Searches for a PWinObj to focus, and then gives it input focus +void +WindowManager::findWOAndFocus(PWinObj *search) +{ + PWinObj *focus = 0; + + if (PWinObj::windowObjectExists(search) && + (search->isMapped()) && (search->isFocusable())) { + focus = search; + } + + // search window object didn't exist, go through the MRU list + if (! focus) { + list::reverse_iterator f_it = _mru_list.rbegin(); + for (; ! focus && (f_it != _mru_list.rend()); ++f_it) { + if ((*f_it)->isMapped() && (*f_it)->isFocusable()) { + focus = *f_it; + } + } + } + + if (focus) { + focus->giveInputFocus(); + + } else if (! PWinObj::getFocusedPWinObj()) { + _root_wo->giveInputFocus(); + _root_wo->setEwmhActiveWindow(None); + } +} + +//! @brief Raises the client and all window having transient relationship with it +//! @param client Client part of the famliy +//! @param raise If true, raise frames, else lowers them +void +WindowManager::familyRaiseLower(Client *client, bool raise) +{ + Client *parent; + Window win_search; + if (! client->getTransientClient()) { + parent = client; + win_search = client->getWindow(); + } else { + parent = client->getTransientClient(); + win_search = client->getTransientClientWindow(); + } + + list client_list; + Client::findFamilyFromWindow(client_list, win_search); + + if (parent) { // make sure parent gets underneath the children + if (raise) { + client_list.push_front(parent); + } else { + client_list.push_back(parent); + } + } + + Frame *frame; + list::iterator it(client_list.begin()); + for (; it != client_list.end(); ++it) { + frame = dynamic_cast((*it)->getParent()); + if (frame) { + if (raise) { + frame->raise(); + } else { + frame->lower(); + } + } + } +} + +//! @brief Remove from MRU list. +void +WindowManager::removeFromFrameList(Frame *frame) +{ + _mru_list.remove(frame); +} + +/** + * Match Frame against autoproperty. + */ +bool +WindowManager::findGroupMatchProperty(Frame *frame, AutoProperty *property) +{ + if ((property->group_global + || (property->isMask(AP_WORKSPACE) + ? (frame->getWorkspace() == property->workspace + && ! frame->isIconified()) + : frame->isMapped())) + && (property->group_size == 0 + || signed(frame->size()) < property->group_size) + && (((frame->getClassHint()->group.size() > 0) + ? (frame->getClassHint()->group == property->group_name) : false) + || AutoProperties::matchAutoClass(*frame->getClassHint(), + static_cast(property)))) { + return true; + } + return false; +} + +/** + * Tries to find a Frame to autogroup with. This is called recursively + * if workspace specific matching is on to avoid conflicts with the + * global property. + */ +Frame* +WindowManager::findGroup(AutoProperty *property) +{ + if (! _allow_grouping) { + return 0; + } + + Frame *frame = 0; + if (property->group_global && property->isMask(AP_WORKSPACE)) { + bool group_global_orig = property->group_global; + property->group_global= false; + frame = findGroup(property); + property->group_global= group_global_orig; + } + + if (! frame) { + frame = findGroupMatch(property); + } + + return frame; +} + +/** + * Do matching against Frames searching for a suitable Frame for + * grouping. + */ +Frame* +WindowManager::findGroupMatch(AutoProperty *property) +{ + Frame *frame = 0; + + // Matching against the focused window first if requested + if (property->group_focused_first + && PWinObj::isFocusedPWinObj(PWinObj::WO_CLIENT)) { + Frame *fo_frame = + static_cast(PWinObj::getFocusedPWinObj()->getParent()); + if (findGroupMatchProperty(fo_frame, property)) { + frame = fo_frame; + } + } + + // Moving on to the rest of the frames. + if (! frame) { + list::iterator it(Frame::frame_begin()); + for (; it != Frame::frame_end(); ++it) { + if (findGroupMatchProperty(*it, property)) { + frame = *it; + break; + } + } + } + + return frame; +} + +//! @brief Attaches all marked clients to frame +void +WindowManager::attachMarked(Frame *frame) +{ + list::iterator it(Client::client_begin()); + for (; it != Client::client_end(); ++it) { + if ((*it)->isMarked()) { + if ((*it)->getParent() != frame) { + ((Frame*) (*it)->getParent())->removeChild(*it); + (*it)->setWorkspace(frame->getWorkspace()); + frame->addChild(*it); + } + (*it)->setStateMarked(STATE_UNSET); + } + } +} + +//! @brief Attaches the Client/Frame into the Next/Prev Frame +void +WindowManager::attachInNextPrevFrame(Client *client, bool frame, bool next) +{ + if (! client) + return; + + Frame *new_frame; + + if (next) { + new_frame = + getNextFrame((Frame*) client->getParent(), true, SKIP_FOCUS_TOGGLE); + } else { + new_frame = + getPrevFrame((Frame*) client->getParent(), true, SKIP_FOCUS_TOGGLE); + } + + if (new_frame) { // we found a frame + if (frame) { + new_frame->addDecor((Frame*) client->getParent()); + } else { + client->getParent()->setFocused(false); + ((Frame*) client->getParent())->removeChild(client); + new_frame->addChild(client); + new_frame->activateChild(client); + new_frame->giveInputFocus(); + } + } +} + +//! @brief Finds the next frame in the list +//! +//! @param frame Frame to search from +//! @param mapped Match only agains mapped frames +//! @param mask Defaults to 0 +Frame* +WindowManager::getNextFrame(Frame* frame, bool mapped, uint mask) +{ + if (! frame || (Frame::frame_size() < 2)) { + return 0; + } + + Frame *next_frame = 0; + list::iterator f_it(find(Frame::frame_begin(), Frame::frame_end(), frame)); + + if (f_it != Frame::frame_end()) { + list::iterator n_it(f_it); + + if (++n_it == Frame::frame_end()) { + n_it = Frame::frame_begin(); + } + + while (! next_frame && (n_it != f_it)) { + if (! (*n_it)->isSkip(mask) && (! mapped || (*n_it)->isMapped())) + next_frame = (*n_it); + + if (++n_it == Frame::frame_end()) { + n_it = Frame::frame_begin(); + } + } + } + + return next_frame; +} + +//! @brief Finds the previous frame in the list +//! +//! @param frame Frame to search from +//! @param mapped Match only agains mapped frames +//! @param mask Defaults to 0 +Frame* +WindowManager::getPrevFrame(Frame* frame, bool mapped, uint mask) +{ + if (! frame || (Frame::frame_size() < 2)) { + return 0; + } + + Frame *next_frame = 0; + list::iterator f_it(find(Frame::frame_begin(), Frame::frame_end(), frame)); + + if (f_it != Frame::frame_end()) { + list::iterator n_it(f_it); + + if (n_it == Frame::frame_begin()) { + n_it = --Frame::frame_end(); + } else { + --n_it; + } + + while (! next_frame && (n_it != f_it)) { + if (! (*n_it)->isSkip(mask) && (! mapped || (*n_it)->isMapped())) { + next_frame = (*n_it); + } + + if (n_it == Frame::frame_begin()) { + n_it = --Frame::frame_end(); + } else { + --n_it; + } + } + } + + return next_frame; +} diff --git a/pekwm/WindowManager.hh b/pekwm/WindowManager.hh new file mode 100644 index 0000000..d5efe5b --- /dev/null +++ b/pekwm/WindowManager.hh @@ -0,0 +1,247 @@ +// +// WindowManager.hh for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// windowmanager.hh for aewm++ +// Copyright (C) 2000 Frank Hale +// http://sapphire.sourceforge.net/ +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#ifndef _WINDOWMANAGER_HH_ +#define _WINDOWMANAGER_HH_ + +#include "pekwm.hh" +#include "Action.hh" +#include "Atoms.hh" +#include "PScreen.hh" +#include "Timer.hh" +#include "ManagerWindows.hh" + +#include +#include + +#ifdef HAVE_XRANDR +extern "C" { +#include +} +#endif // HAVE_XRANDR + +class ActionHandler; +class AutoProperties; +class Config; +class ColorHandler; +class FontHandler; +class TextureHandler; +class Theme; +class Workspaces; + +class PScreen; +class ScreenResources; +class PWinObj; +class PDecor; +class Frame; +class Client; +class ClassHint; + +class AutoProperty; // for findGroup +class CmdDialog; +class SearchDialog; +class StatusWindow; +class WorkspaceIndicator; + +class KeyGrabber; +class Harbour; + +class WindowManager +{ +public: + static WindowManager *start(const std::string &command_line, + const std::string &config_file, bool replace); + static void destroy(void); + inline static WindowManager *instance(void) { return _instance; } + + void doEventLoop(void); + + //! @brief Sets reload status, will reload from main loop. + void reload(void) { _reload = true; } + void restart(std::string command = ""); + //! @brief Sets shutdown status, will shutdown from main loop. + void shutdown(void) { _shutdown = true; } + /**< Return shutdown flag, set to tru to shutdown window manager. */ + bool *getShutdownFlag(void) { return &_shutdown; } + + // get "base" classes + inline PScreen *getScreen(void) const { return _screen; } + inline Config *getConfig(void) const { return _config; } + inline Theme *getTheme(void) const { return _theme; } + inline ActionHandler *getActionHandler(void) + const { return _action_handler; } + inline AutoProperties* getAutoProperties(void) const { + return _autoproperties; + } + inline Workspaces *getWorkspaces(void) const { return _workspaces; } + inline KeyGrabber *getKeyGrabber(void) const { return _keygrabber; } + inline Harbour *getHarbour(void) const { return _harbour; } + + inline const std::string &getRestartCommand(void) const { return _restart_command; } + inline bool isStartup(void) const { return _startup; } + + // list iterators + inline std::list::iterator mru_begin(void) { return _mru_list.begin(); } + inline std::list::reverse_iterator mru_rbegin(void) { return _mru_list.rbegin(); } + inline std::list::iterator mru_end(void) { return _mru_list.end(); } + inline std::list::reverse_iterator mru_rend(void) { return _mru_list.rend(); } + + // adds + inline void addToFrameList(Frame *frame) { + if (frame) { + _mru_list.remove(frame); + _mru_list.push_front(frame); + } + } + + // removes + void removeFromFrameList(Frame *frame); + + PWinObj *findPWinObj(Window win); + void familyRaiseLower(Client *client, bool raise); + + Frame* findGroup(AutoProperty *property); + + inline bool isAllowGrouping(void) const { return _allow_grouping; } + inline void setStateGlobalGrouping(StateAction sa) { + if (ActionUtil::needToggle(sa, _allow_grouping)) { + _allow_grouping = !_allow_grouping; + } + } + + void attachMarked(Frame *frame); + void attachInNextPrevFrame(Client* client, bool frame, bool next); + + Frame* getNextFrame(Frame* frame, bool mapped, uint mask = 0); + Frame* getPrevFrame(Frame* frame, bool mapped, uint mask = 0); + + void findWOAndFocus(PWinObj *wo); + + inline CmdDialog *getCmdDialog(void) { return _cmd_dialog; } + inline SearchDialog *getSearchDialog(void) { return _search_dialog; } + inline StatusWindow *getStatusWindow(void) { return _status_window; } + WorkspaceIndicator *getWorkspaceIndicator(void) { return _workspace_indicator; } + + // Extended Window Manager hints function prototypes + void setEwmhSupported(void); + void setEwmhActiveWindow(Window w); + void setDesktopNames(void); + + // public event handlers used when doing grabbed actions + void handleKeyEvent(XKeyEvent *ev); + void handleButtonPressEvent(XButtonEvent *ev); + void handleButtonReleaseEvent(XButtonEvent *ev); + +private: + WindowManager(const std::string &command_line, + const std::string &config_file); + WindowManager(const WindowManager &); // not implemented to ensure singleton + ~WindowManager(void); + + bool setupDisplay(bool replace); + void scanWindows(void); + void execStartFile(void); + + void doReload(void); + void doReloadConfig(void); + void doReloadTheme(void); + void doReloadMouse(void); + void doReloadKeygrabber(bool force=false); + void doReloadAutoproperties(void); + void doReloadHarbour(void); + + void cleanup(void); + + // screen edge related + void screenEdgeCreate(void); + void screenEdgeDestroy(void); + void screenEdgeResize(void); + void screenEdgeMapUnmap(void); + + void handleMapRequestEvent(XMapRequestEvent *ev); + void handleUnmapEvent(XUnmapEvent *ev); + void handleDestroyWindowEvent(XDestroyWindowEvent *ev); + + void handleConfigureRequestEvent(XConfigureRequestEvent *ev); + void handleClientMessageEvent(XClientMessageEvent *ev); + + void handleColormapEvent(XColormapEvent *ev); + void handlePropertyEvent(XPropertyEvent *ev); + void handleMappingEvent(XMappingEvent *ev); + void handleExposeEvent(XExposeEvent *ev); + + void handleMotionEvent(XMotionEvent *ev); + + void handleEnterNotify(XCrossingEvent *ev); + void handleLeaveNotify(XCrossingEvent *ev); + void handleFocusInEvent(XFocusChangeEvent *ev); + void handleFocusOutEvent(XFocusChangeEvent *ev); + + void handleShapeEvent(XAnyEvent *ev); + +#ifdef HAVE_XRANDR + void handleXRandrEvent(XRRNotifyEvent *ev); + void handleXRandrScreenChangeEvent(XRRScreenChangeNotifyEvent *ev); + void handleXRandrCrtcChangeEvent(XRRCrtcChangeNotifyEvent *ev); +#endif // HAVE_XRANDR + + void readDesktopNamesHint(void); + + // private methods for the hints + void initHints(void); + + bool findGroupMatchProperty(Frame *frame, AutoProperty *property); + Frame *findGroupMatch(AutoProperty *property); + +private: + PScreen *_screen; + ScreenResources *_screen_resources; + KeyGrabber *_keygrabber; + Config *_config; + ColorHandler *_color_handler; + FontHandler *_font_handler; + TextureHandler *_texture_handler; + Theme *_theme; + ActionHandler *_action_handler; + AutoProperties *_autoproperties; + Workspaces *_workspaces; + Harbour *_harbour; + CmdDialog *_cmd_dialog; + SearchDialog *_search_dialog; + StatusWindow *_status_window; + + WorkspaceIndicator *_workspace_indicator; //!< Window popping up when switching workspace + + Timer _timer_action; + + std::string _command_line, _restart_command; + bool _startup; //!< Indicates startup status. + bool _shutdown; //!< Set to wheter we want to shutdown. + bool _reload; //!< Set to wheter we want to reload. + + std::list _mru_list; + + bool _allow_grouping; // _screen_edge_list; + HintWO *_hint_wo; /**< Hint window object, communicates EWMH hints. */ + RootWO *_root_wo; /**< Root window object, wrapper for root window. */ + + // pointer for singleton pattern + static WindowManager *_instance; +}; + +#endif // _WINDOWMANAGER_HH_ diff --git a/pekwm/WorkspaceIndicator.cc b/pekwm/WorkspaceIndicator.cc new file mode 100644 index 0000000..84b8bdc --- /dev/null +++ b/pekwm/WorkspaceIndicator.cc @@ -0,0 +1,254 @@ +// +// WorkspaceIndicator.hh for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include + +#include "Config.hh" +#include "PixmapHandler.hh" +#include "PScreen.hh" +#include "ScreenResources.hh" +#include "Workspaces.hh" +#include "WorkspaceIndicator.hh" + +using std::cerr; +using std::endl; +using std::vector; + +/** + * Display constructor + */ +WorkspaceIndicator::Display::Display(PWinObj *parent, Theme *theme) + : PWinObj(), + _theme(theme), _pixmap(None) +{ + _parent = parent; + // Do not give the indicator focus, it doesn't handle input + _focusable = false; + + XSetWindowAttributes attr; + attr.override_redirect = false; + attr.event_mask = ButtonPressMask|ButtonReleaseMask|ButtonMotionMask| + FocusChangeMask|KeyPressMask|KeyReleaseMask; + _window = XCreateWindow(_dpy, _parent->getWindow(), 0, 0, 1, 1, 0, + CopyFromParent, InputOutput, CopyFromParent, + CWOverrideRedirect|CWEventMask, &attr); +} + +/** + * Display destructor + */ +WorkspaceIndicator::Display::~Display(void) +{ + XDestroyWindow(_dpy, _window); + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_pixmap); +} + +/** + * Get required size to render workspaces. + */ +bool +WorkspaceIndicator::Display::getSizeRequest(Geometry &gm) +{ + Geometry head; + uint head_nr = PScreen::instance()->getNearestHead(_parent->getX(), _parent->getY()); + PScreen::instance()->getHeadInfo(head_nr, head); + + uint head_size = std::min(head.width, head.height) / Config::instance()->getWorkspaceIndicatorScale(); + gm.x = gm.y = 0; + gm.width = head_size * Workspaces::instance()->getPerRow() + getPaddingHorizontal(); + gm.height = head_size * Workspaces::instance()->getRows() + getPaddingVertical(); + + return true; +} + +/** + * Render workspace view to pixmap + */ +void +WorkspaceIndicator::Display::render(void) +{ + Theme::WorkspaceIndicatorData &data(_theme->getWorkspaceIndicatorData()); + + // Make sure pixmap has correct size + ScreenResources::instance()->getPixmapHandler()->returnPixmap(_pixmap); + _pixmap = ScreenResources::instance()->getPixmapHandler()->getPixmap(_gm.width, _gm.height, + PScreen::instance()->getDepth()); + + // Render background + data.texture_background->render(_pixmap, 0, 0, _gm.width, _gm.height); + + // Render workspace grid, then active workspace fill and end with + // rendering active workspace number and name + renderWorkspaces(data.edge_padding, data.edge_padding, + _gm.width - getPaddingHorizontal(), + _gm.height - getPaddingVertical()); + + data.font->setColor(data.font_color); + data.font->draw(_pixmap, data.edge_padding, _gm.height - data.edge_padding - data.font->getHeight(), + Workspaces::instance()->getActiveWorkspace()->getName(), + 0 /* max_chars */, _gm.width - data.edge_padding * 2 /* max_width */); + + // Refresh + setBackgroundPixmap(_pixmap); + clear(); +} + +/** + * Render workspace part of indication + * + * @param x X offset on drawable + * @param y Y offset on drawable + * @param width Allowed width to use + * @param height Allowed height to use + */ +void +WorkspaceIndicator::Display::renderWorkspaces(int x, int y, uint width, uint height) +{ + Theme::WorkspaceIndicatorData &data(_theme->getWorkspaceIndicatorData()); + + uint per_row = Workspaces::instance()->getPerRow(); + uint rows = Workspaces::instance()->getRows(); + + uint ws_width = width / per_row; + uint ws_height = height / rows; + + // Starting positions of the workspace squares + uint x_pos = x; + uint y_pos = y; + + vector::iterator it(Workspaces::instance()->ws_begin()); + + for (uint row = 0; it != Workspaces::instance()->ws_end(); ++it) { + // Check for next row + if (Workspaces::instance()->getRow((*it)->getNumber()) > row) { + row = Workspaces::instance()->getRow((*it)->getNumber()); + + x_pos = x; + y_pos += ws_height + data.workspace_padding; + } + + if ((*it)->getNumber() == Workspaces::instance()->getActive()) { + data.texture_workspace_act->render(_pixmap, x_pos, y_pos, ws_width, ws_height); + } else { + data.texture_workspace->render(_pixmap, x_pos, y_pos, ws_width, ws_height); + } + + x_pos += ws_width + data.workspace_padding; + } +} + +/** + * Get horizontal padding for window around workspaces. + */ +uint +WorkspaceIndicator::Display::getPaddingHorizontal(void) +{ + Theme::WorkspaceIndicatorData &data(_theme->getWorkspaceIndicatorData()); + + return (data.edge_padding * 2 + data.workspace_padding * (Workspaces::instance()->getPerRow() - 1)); +} + +/** + * Get vertical padding for window around workspaces. + */ +uint +WorkspaceIndicator::Display::getPaddingVertical(void) +{ + Theme::WorkspaceIndicatorData &data(_theme->getWorkspaceIndicatorData()); + + return (data.edge_padding * 3 + data.font->getHeight() + + data.workspace_padding * (Workspaces::instance()->getRows() - 1)); +} + +/** + * WorkspaceIndicator constructor + */ +WorkspaceIndicator::WorkspaceIndicator(Theme *theme, Timer &timer) + : PDecor(theme, "WORKSPACEINDICATOR"), + _timer(timer), _display_wo(this, theme), + _timer_hide(0) +{ + _type = PWinObj::WO_WORKSPACE_INDICATOR; + setLayer(LAYER_NONE); // Make sure this goes on top of everything + _hidden = true; // Do not include in workspace handling etc + + // Add hide action to timer action + _action_hide.action_list.push_back(Action(ACTION_HIDE_WORKSPACE_INDICATOR)); + + // Add title + titleAdd(&_title); + titleSetActive(0); + _title.setReal(L"Workspace"); + + // Add display window + addChild(&_display_wo); + activateChild(&_display_wo); + _display_wo.mapWindow(); + + // Load theme data, horay for pretty colors + loadTheme(); + + // Register ourselves + Workspaces::instance()->insert(this); + woListAdd(this); + _wo_map[_window] = this; + +#ifdef OPACITY + setOpacity(Config::instance()->getWorkspaceIndicatorOpacity()); +#endif // OPACITY +} + +/** + * WorkspaceIndicator destructor + */ +WorkspaceIndicator::~WorkspaceIndicator(void) +{ + removeChild(&_display_wo, false); + + // Un-register ourselves + Workspaces::instance()->remove(this); + _wo_map.erase(_window); + woListRemove(this); +} + +/** + * Resize indicator and render + */ +void +WorkspaceIndicator::render(void) +{ + // Center on head + Geometry head, request; + PScreen::instance()->getHeadInfo(PScreen::instance()->getCurrHead(), head); + + _display_wo.getSizeRequest(request); + resizeChild(request.width, request.height); + + move(head.x + (head.width - _gm.width) / 2, + head.y + (head.height - _gm.height) / 2); + + // Render workspaces + _display_wo.render(); +} + +/** + * Remove previous timer and add new hide timer for timeout seconds in + * the future. + */ +void +WorkspaceIndicator::updateHideTimer(uint timeout) +{ + if (_timer_hide) { + _timer.remove(_timer_hide); + } + _timer_hide = _timer.add(timeout, ActionPerformed(this, _action_hide)); +} diff --git a/pekwm/WorkspaceIndicator.hh b/pekwm/WorkspaceIndicator.hh new file mode 100644 index 0000000..d10954f --- /dev/null +++ b/pekwm/WorkspaceIndicator.hh @@ -0,0 +1,68 @@ +// +// WorkspaceIndicator.hh for pekwm +// Copyright © 2009 Claes Nästén +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _WORKSPACE_INDICATOR_HH_ +#define _WORKSPACE_INDICATOR_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "Theme.hh" +#include "Timer.hh" + +/** + * Workspace indicator rendering a simple window with workspace layout + * showing the number and name of the active workspace. + */ +class WorkspaceIndicator : public PDecor +{ +public: + /** + * Display class rendering workspace layout in WorkspaceIndicator. + */ + class Display : public PWinObj { + public: + Display(PWinObj *parent, Theme *theme); + virtual ~Display(void); + + virtual bool getSizeRequest(Geometry &request); + + void render(void); + + private: + void renderWorkspaces(int x, int y, uint width, uint height); + + uint getPaddingHorizontal(void); + uint getPaddingVertical(void); + + private: + Theme *_theme; + Pixmap _pixmap; //!< Pixmap holding rendered workspace view + }; + + WorkspaceIndicator(Theme *theme, Timer &timer); + virtual ~WorkspaceIndicator(void); + + void render(void); + void updateHideTimer(uint timeout); + +private: + Timer &_timer; //!< Timer used to add unmap events to + + Display _display_wo; //!< Display winobj handling rendering of workspace status + PDecor::TitleItem _title; //!< Title item added to the decor + Timer::timed_event_list_entry _timer_hide; //!< Timeout for hiding workspace indicator + ActionEvent _action_hide; //!< ActionEvent for hiding the workspace indicator used in timer +}; + +#endif // _WORKSPACE_INDICATOR_HH_ diff --git a/pekwm/Workspaces.cc b/pekwm/Workspaces.cc new file mode 100644 index 0000000..e3a6278 --- /dev/null +++ b/pekwm/Workspaces.cc @@ -0,0 +1,1060 @@ +// +// Workspaces.cc for pekwm +// Copyright © 2002-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Workspaces.hh" +#include "PScreen.hh" +#include "Atoms.hh" +#include "ParseUtil.hh" +#include "Config.hh" +#include "PWinObj.hh" +#include "PDecor.hh" +#include "Frame.hh" +#include "Client.hh" // For isSkip() +#include "WindowManager.hh" +#include "WorkspaceIndicator.hh" + +#include +#include +#ifdef HAVE_LIMITS +#include +using std::numeric_limits; +#endif // HAVE_LIMITS + +extern "C" { +#include // for XA_WINDOW +} + +using std::list; +using std::vector; +using std::string; +using std::find; +using std::wostringstream; +using std::wstring; +using std::cerr; +using std::endl; + +// Workspaces::Workspace + +Workspaces::Workspace::Workspace(const std::wstring &name, uint number) + : _name(name), _number(number), _last_focused(0) +{ +} + +Workspaces::Workspace::~Workspace(void) +{ +} + +// Workspaces + +Workspaces *Workspaces::_instance = 0; + + +//! @brief Workspaces constructor +Workspaces::Workspaces(uint number, uint per_row) + : _active(0), _previous(0), _per_row(per_row) +{ +#ifdef DEBUG + if (_instance) { + cerr << __FILE__ << "@" << __LINE__ << ": " + << "Workspaces(" << this << ")::Workspaces(" << number << ")" + << endl << " *** _instance already set: " << _instance << endl; + } +#endif // DEBUG + _instance = this; + + if (number < 1) { +#ifdef DEBUG + cerr << __FILE__ << "@" << __LINE__ << ": " + << "Workspaces(" << this << ")::Workspaces(" << number << ")" + << " *** number < 1" << endl; +#endif // DEBUG + number = 1; + } + + // create new workspaces + for (uint i = 0; i < number; ++i) { + _workspace_list.push_back(new Workspace(getWorkspaceName(i), i)); + } +} + +//! @brief Workspaces destructor +Workspaces::~Workspaces(void) +{ + vector::iterator it(_workspace_list.begin()); + for (; it != _workspace_list.end(); ++it) { + delete *it; + } + + _instance = 0; +} + +//! @brief Sets total amount of workspaces to number +void +Workspaces::setSize(uint number) +{ + if (number < 1) { + number = 1; + } + + if (number == _workspace_list.size()) { + return; // no need to change number of workspaces to the current number + } + + uint before = _workspace_list.size(); + if (_active >= number) { + _active = number - 1; + } + if (_previous >= number) { + _previous = number - 1; + } + + // We have more workspaces than we want, lets remove the last ones + if (before > number) { + list::iterator it(_wo_list.begin()); + for (; it != _wo_list.end(); ++it) { + if ((*it)->getWorkspace() > (number - 1)) + (*it)->setWorkspace(number - 1); + } + + for (uint i = before - 1; i >= number; --i) { + delete _workspace_list[i]; + } + + _workspace_list.resize(number, 0); + + } else { // We need more workspaces, lets create some + for (uint i = before; i < number; ++i) { + _workspace_list.push_back(new Workspace(getWorkspaceName(i), i)); + } + } + + // Tell the rest of the world how many workspaces we have. + XChangeProperty(PScreen::instance()->getDpy(), + PScreen::instance()->getRoot(), + Atoms::getAtom(NET_NUMBER_OF_DESKTOPS), + XA_CARDINAL, 32, PropModeReplace, + (uchar *) &number, 1); + + // make sure we aren't on an non-existent workspace + if (number <= _active) { + setWorkspace(number - 1, true); + } +} + +/** + * Set workspace names. + */ +void +Workspaces::setNames(void) +{ + vector::iterator it(_workspace_list.begin()); + for (; it != _workspace_list.end(); ++it) { + (*it)->setName(Config::instance()->getWorkspaceName((*it)->getNumber())); + } +} + +//! @brief Activates Workspace workspace and sets the right hints +//! @param num Workspace to activate +//! @param focus whether or not to focus a window after switch +void +Workspaces::setWorkspace(uint num, bool focus) +{ + if ((num == _active) || ( num >= _workspace_list.size())) { + return; + } + + PScreen::instance()->grabServer(); + + PWinObj *wo = PWinObj::getFocusedPWinObj(); + // Make sure that sticky windows gets unfocused on workspace change, + // it will be set back after switch is done. + if (wo) { + if (wo->getType() == PWinObj::WO_CLIENT) { + wo->getParent()->setFocused(false); + } else { + wo->setFocused(false); + } + } + + // Save the focused window object + setLastFocused(_active, wo); + PWinObj::setFocusedPWinObj(0); + + // switch workspace + hideAll(_active); + AtomUtil::setLong(PScreen::instance()->getRoot(), + Atoms::getAtom(NET_CURRENT_DESKTOP), + num); + unhideAll(num, focus); + + PScreen::instance()->ungrabServer(true); + + // Show workspace indicator if requested + if (Config::instance()->getShowWorkspaceIndicator() > 0) { + WindowManager::instance()->getWorkspaceIndicator()->render(); + WindowManager::instance()->getWorkspaceIndicator()->mapWindowRaised(); + WindowManager::instance()->getWorkspaceIndicator()->updateHideTimer(Config::instance()->getShowWorkspaceIndicator()); + } +} + +//! @brief +bool +Workspaces::gotoWorkspace(uint direction, bool warp) +{ + uint workspace; + int dir = 0; + // Using a bool flag to detect changes due to special workspaces such + // as PREV + bool switched = true; + uint per_row = Config::instance()->getWorkspacesPerRow(); + + uint cur_row = getRow(), row_min = getRowMin(), row_max = getRowMax(); + switch (direction) { + case WORKSPACE_LEFT: + case WORKSPACE_PREV: + dir = 1; + + if (_active > row_min) { + workspace = _active - 1; + } else if (direction == WORKSPACE_PREV) { + workspace = row_max; + } else { + switched = false; + } + break; + case WORKSPACE_NEXT: + case WORKSPACE_RIGHT: + dir = 2; + + if (_active < row_max) { + workspace = _active + 1; + } else if (direction == WORKSPACE_NEXT) { + workspace = row_min; + } else { + switched = false; + } + break; + case WORKSPACE_PREV_V: + case WORKSPACE_UP: + dir = -1; + + if (_active >= per_row) { + workspace = _active - per_row; + } else if (direction == WORKSPACE_PREV_V) { + // Bottom left + workspace = _workspace_list.size() - per_row; + // Add column + workspace += _active - cur_row * per_row; + } else { + switched = false; + } + break; + case WORKSPACE_NEXT_V: + case WORKSPACE_DOWN: + dir = -2; + + if ((_active + per_row) < _workspace_list.size()) { + workspace = _active + per_row; + } else if (direction == WORKSPACE_NEXT_V) { + workspace = _active - cur_row * per_row; + } else { + switched = false; + } + break; + case WORKSPACE_LAST: + workspace = _previous; + if (_active == workspace) { + switched = false; + } + break; + default: + if (direction == _active) { + switched = false; + } else { + workspace = direction; + } + } + + if (switched) { + if (warp) { + warpToWorkspace(workspace, dir); + } else { + setWorkspace(workspace, true); + } + } + + return switched; +} + +//! @brief +bool +Workspaces::warpToWorkspace(uint num, int dir) +{ + if ((num == _active) || ( num >= _workspace_list.size())) { + return false; + } + + int x, y; + PScreen::instance()->getMousePosition(x, y); + + if (dir != 0) { + switch(dir) { + case 1: + x = PScreen::instance()->getWidth() - std::max(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_LEFT) + 2, 2); + break; + case 2: + x = std::max(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_RIGHT) * 2, 2); + break; + case -1: + y = PScreen::instance()->getHeight() - std::max(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_BOTTOM) + 2, 2); + break; + case -2: + y = std::max(Config::instance()->getScreenEdgeSize(SCREEN_EDGE_TOP) + 2, 2); + break; + } + + // warp pointer + XWarpPointer(PScreen::instance()->getDpy(), None, + PScreen::instance()->getRoot(), 0, 0, 0, 0, x, y); + } + + // set workpsace + setWorkspace(num, true); + + return true; +} + +/** + * Adds a PWinObj to the stacking list. + * @param wo PWinObj to insert + * @param raise Whether to insert at the bottom or top of the layer (defaults to true). + */ +void +Workspaces::insert(PWinObj *wo, bool raise) +{ + list::iterator it(_wo_list.begin()), position(_wo_list.end()); + for (; it != _wo_list.end() && position == _wo_list.end(); ++it) { + if (raise) { + // If raising, make sure the inserted wo gets below the first + // window in the next layer. + if ((*it)->getLayer() > wo->getLayer()) { + position = it; + } + } else { + // If lowering, put the window below the first window with the same level. + if (wo->getLayer() <= (*it)->getLayer()) { + position = it; + } + } + } + + _wo_list.insert(position, wo); + + if (position == _wo_list.end()) { + XRaiseWindow(PScreen::instance()->getDpy(), wo->getWindow()); + } else { + stackWinUnderWin((*position)->getWindow(), wo->getWindow()); + } +} + +//! @brief Removes a PWinObj from the stacking list. +void +Workspaces::remove(PWinObj* wo) +{ + _wo_list.remove(wo); + + // remove from last focused + vector::iterator it(_workspace_list.begin()); + for (; it != _workspace_list.end(); ++it) { + if (wo == (*it)->getLastFocused()) { + (*it)->setLastFocused(0); + } + } +} + +//! @brief Hides all non-sticky Frames on the workspace. +void +Workspaces::hideAll(uint workspace) +{ + list::iterator it(_wo_list.begin()); + for (; it != _wo_list.end(); ++it) { + if (! ((*it)->isSticky()) && ! ((*it)->isHidden()) && + ((*it)->getWorkspace() == workspace)) { + (*it)->unmapWindow(); + } + } +} + +//! @brief Unhides all hidden PWinObjs on the workspace. +void +Workspaces::unhideAll(uint workspace, bool focus) +{ + if (workspace >= _workspace_list.size()) + return; + _previous = _active; + _active = workspace; + + + list::iterator it(_wo_list.begin()); + for (; it != _wo_list.end(); ++it) { + if (! (*it)->isMapped() && ! (*it)->isIconified() && ! (*it)->isHidden() + && ((*it)->getWorkspace() == workspace)) { + (*it)->mapWindow(); // don't restack ontop windows + } + } + + // Try to focus last focused window and if that fails we get the top-most + // Frame if any and give it focus. + if (focus) { + PWinObj *wo = _workspace_list[workspace]->getLastFocused(); + if (! wo || ! PWinObj::windowObjectExists(wo)) { + wo = getTopWO(PWinObj::WO_FRAME); + } + + if (wo && wo->isMapped() && wo->isFocusable()) { + // Render as focused + if (wo->getType() == PWinObj::WO_CLIENT) { + wo->getParent()->setFocused(true); + } else { + wo->setFocused(true); + } + + // Get the active child if a frame, to get correct focus behavior + if (wo->getType() == PWinObj::WO_FRAME) { + wo = static_cast(wo)->getActiveChild(); + } + + // Focus + wo->giveInputFocus(); + PWinObj::setFocusedPWinObj(wo); + } + + // If focusing fails, focus the root window. + if (! PWinObj::getFocusedPWinObj() + || ! PWinObj::getFocusedPWinObj()->isMapped()) { + PWinObj::getRootPWinObj()->giveInputFocus(); + } + } +} + +//! @brief Raises a PWinObj and restacks windows. +void +Workspaces::raise(PWinObj* wo) +{ + list::iterator it(find(_wo_list.begin(), _wo_list.end(), wo)); + + if (it == _wo_list.end()) { // no Frame to raise. + return; + } + _wo_list.erase(it); + + insert(wo, true); // reposition and restack +} + +//! @brief Lower a PWinObj and restacks windows. +void +Workspaces::lower(PWinObj* wo) +{ + list::iterator it(find(_wo_list.begin(), _wo_list.end(), wo)); + + if (it == _wo_list.end()) // no Frame to raise. + return; + _wo_list.erase(it); + + insert(wo, false); // reposition and restack +} + +//! @brief Places the PWinObj above the window win +//! @param wo PWinObj to place. +//! @param win Window to place Frame above. +//! @param restack Restack the X windows, defaults to true. +void +Workspaces::stackAbove(PWinObj *wo, Window win, bool restack) +{ + list::iterator old_pos(find(_wo_list.begin(), _wo_list.end(), wo)); + + if (old_pos != _wo_list.end()) { + list::iterator it(_wo_list.begin()); + for (; it != _wo_list.end(); ++it) { + if (win == (*it)->getWindow()) { + _wo_list.erase(old_pos); + _wo_list.insert(++it, wo); + + // Before restacking make sure we are the active frame + // also that there are two different frames + if (restack) { + stackWinUnderWin(win, wo->getWindow()); + } + break; + } + } + } +} + +//! @brief Places the PWinObj below the window win +//! @param wo PWinObj to place. +//! @param win Window to place Frame under +//! @param restack Restack the X windows, defaults to true +void +Workspaces::stackBelow(PWinObj* wo, Window win, bool restack) +{ + list::iterator old_pos(find(_wo_list.begin(), _wo_list.end(), wo)); + + if (old_pos != _wo_list.end()) { + list::iterator it(_wo_list.begin()); + for (; it != _wo_list.end(); ++it) { + if (win == (*it)->getWindow()) { + _wo_list.erase(old_pos); + _wo_list.insert(it, wo); + + if (restack) { + stackWinUnderWin(wo->getWindow(), win); + } + break; + } + } + } +} + + +//! @brief +PWinObj* +Workspaces::getLastFocused(uint workspace) +{ + if (workspace >= _workspace_list.size()) { + return 0; + } + return _workspace_list[workspace]->getLastFocused(); +} + +//! @brief +void +Workspaces::setLastFocused(uint workspace, PWinObj* wo) +{ + if (workspace >= _workspace_list.size()) { + return; + } + + _workspace_list[workspace]->setLastFocused(wo); +} + +//! @brief Helper function to stack a window below another +//! @param win_over Window to place win_under under +//! @param win_under Window to place under win_over +void +Workspaces::stackWinUnderWin(Window over, Window under) +{ + if (over == under) { + return; + } + + Window windows[2] = { over, under }; + XRestackWindows(PScreen::instance()->getDpy(), windows, 2); +} + +//! @brief Create name for workspace num +wstring +Workspaces::getWorkspaceName(uint num) +{ + wostringstream buf; + buf << num + 1; + buf << L": "; + buf << Config::instance()->getWorkspaceName(num); + + return buf.str(); +} + +// MISC METHODS + +//! @brief Returns the first focusable PWinObj with the highest stacking +PWinObj* +Workspaces::getTopWO(uint type_mask) +{ + list::reverse_iterator r_it = _wo_list.rbegin(); + for (; r_it != _wo_list.rend(); ++r_it) { + if ((*r_it)->isMapped() + && (*r_it)->isFocusable() + && ((*r_it)->getType()&type_mask)) { + return (*r_it); + } + } + return 0; +} + +/** + * Builds a list of all clients in stacking order, clients in the same + * frame come after each other. + */ +Window* +Workspaces::buildClientList(unsigned int &num_windows) +{ + Frame *frame; + Client *client, *client_active; + + list windows_list; + list::iterator it_f, it_c; + for (it_f = _wo_list.begin(); it_f != _wo_list.end(); ++it_f) { + if ((*it_f)->getType() != PWinObj::WO_FRAME) { + continue; + } + + frame = static_cast(*it_f); + client_active = frame->getActiveClient(); + + if (Config::instance()->isReportAllClients()) { + for (it_c = frame->begin(); it_c != frame->end(); ++it_c) { + client = dynamic_cast(*it_c); + if (client + && ! client->isSkip(SKIP_TASKBAR) + && client != client_active) { + windows_list.push_back(client->getWindow()); + } + } + } + + if (client_active && ! client_active->isSkip(SKIP_TASKBAR)) { + windows_list.push_back(client_active->getWindow()); + } + } + + num_windows = windows_list.size(); + Window *windows = new Window[num_windows ? num_windows : 1]; + if (num_windows > 0) { + copy(windows_list.begin(), windows_list.end(), windows); + } + + return windows; +} + +/** + * Updates the Ewmh Client list hint. + */ +void +Workspaces::updateClientList(void) +{ + unsigned int num_windows; + Window *windows = buildClientList(num_windows); + + AtomUtil::setWindows(PScreen::instance()->getRoot(), + Atoms::getAtom(NET_CLIENT_LIST), + windows, num_windows); + + delete [] windows; +} + +/** + * Updates the Ewmh Stacking list hint. + */ +void +Workspaces::updateClientStackingList(void) +{ + unsigned int num_windows; + Window *windows = buildClientList(num_windows); + + AtomUtil::setWindows(PScreen::instance()->getRoot(), + Atoms::getAtom(NET_CLIENT_LIST_STACKING), + windows, num_windows); + + delete [] windows; +} + +// PLACEMENT ROUTINES + +//! @brief Tries to place the Wo. +void +Workspaces::placeWo(PWinObj *wo, Window parent) +{ + bool placed = false; + + list::iterator it(Config::instance()->getPlacementModelBegin()); + for (; (placed != true) && + (it != Config::instance()->getPlacementModelEnd()); ++it) { + switch (*it) { + case PLACE_SMART: + placed = placeSmart(wo); + break; + case PLACE_MOUSE_NOT_UNDER: + placed = placeMouseNotUnder(wo); + break; + case PLACE_MOUSE_CENTERED: + placed = placeMouseCentered(wo); + break; + case PLACE_MOUSE_TOP_LEFT: + placed = placeMouseTopLeft(wo); + break; + case PLACE_CENTERED_ON_PARENT: + placed = placeCenteredOnParent(wo, parent); + break; + default: + // do nothing + break; + } + } +} + +/** + * Make sure window is inside screen boundaries. + */ +void +Workspaces::placeWoInsideScreen(PWinObj *wo) +{ + Geometry gm_before(wo->getX(), wo->getY(), wo->getWidth(), wo->getHeight()); + Geometry gm_after(gm_before); + + Strut *strut = 0; + if (wo->getType() == PWinObj::WO_FRAME) { + Client *client = static_cast(wo)->getActiveClient(); + if (client) { + strut = client->getStrut(); + } + } + + placeInsideScreen(gm_after, strut); + if (gm_before != gm_after) { + wo->move(gm_after.x, gm_after.y); + } +} + +//! @brief Tries to find empty space to place the client in +//! @return true if client got placed, else false +//! @todo What should we do about Xinerama as when we don't have it enabled we care about the struts. +bool +Workspaces::placeSmart(PWinObj* wo) +{ + PWinObj *wo_e; + bool placed = false; + + int step_x = (Config::instance()->getPlacementLtR()) ? 1 : -1; + int step_y = (Config::instance()->getPlacementTtB()) ? 1 : -1; + int offset_x = (Config::instance()->getPlacementLtR()) + ? Config::instance()->getPlacementOffsetX() + : -Config::instance()->getPlacementOffsetX(); + int offset_y = (Config::instance()->getPlacementTtB()) + ? Config::instance()->getPlacementOffsetY() + : -Config::instance()->getPlacementOffsetY(); + int start_x, start_y, test_x = 0, test_y = 0; + + // Wrap these up, to get proper checking of space. + uint wo_width = wo->getWidth() + Config::instance()->getPlacementOffsetX(); + uint wo_height = wo->getHeight() + Config::instance()->getPlacementOffsetY(); + + Geometry head; + PScreen::instance()->getHeadInfoWithEdge(PScreen::instance()->getCurrHead(), + head); + + start_x = (Config::instance()->getPlacementLtR()) + ? (head.x) + : (head.x + head.width - wo_width); + start_y = (Config::instance()->getPlacementTtB()) + ? (head.y) + : (head.y + head.height - wo_height); + + if (Config::instance()->getPlacementRow()) { // row placement + test_y = start_y; + while (! placed && (Config::instance()->getPlacementTtB() + ? ((test_y + wo_height) <= (head.y + head.height)) + : (test_y >= head.y))) { + test_x = start_x; + while (! placed && (Config::instance()->getPlacementLtR() + ? ((test_x + wo_width) <= (head.x + head.width)) + : (test_x >= head.x))) { + // see if we can place the window here + if ((wo_e = isEmptySpace(test_x, test_y, wo))) { + placed = false; + test_x = Config::instance()->getPlacementLtR() + ? (wo_e->getX() + wo_e->getWidth()) : (wo_e->getX() - wo_width); + } else { + placed = true; + wo->move(test_x + offset_x, test_y + offset_y); + } + } + test_y += step_y; + } + } else { // column placement + test_x = start_x; + while (! placed && (Config::instance()->getPlacementLtR() + ? ((test_x + wo_width) <= (head.x + head.width)) + : (test_x >= head.x))) { + test_y = start_y; + while (! placed && (Config::instance()->getPlacementTtB() + ? ((test_y + wo_height) <= (head.y + head.height)) + : (test_y >= head.y))) { + // see if we can place the window here + if ((wo_e = isEmptySpace(test_x, test_y, wo))) { + placed = false; + test_y = Config::instance()->getPlacementTtB() + ? (wo_e->getY() + wo_e->getHeight()) : (wo_e->getY() - wo_height); + } else { + placed = true; + wo->move(test_x + offset_x, test_y + offset_y); + } + } + test_x += step_x; + } + } + + return placed; + +} + +//! @brief Places the wo in a corner of the screen not under the pointer +bool +Workspaces::placeMouseNotUnder(PWinObj *wo) +{ + Geometry head; + PScreen::instance()->getHeadInfoWithEdge(PScreen::instance()->getCurrHead(), + head); + + int mouse_x, mouse_y; + PScreen::instance()->getMousePosition(mouse_x, mouse_y); + + // compensate for head offset + mouse_x -= head.x; + mouse_y -= head.y; + + // divide the screen into four rectangles using the pointer as divider + if ((wo->getWidth() < unsigned(mouse_x)) && (wo->getHeight() < head.height)) { + wo->move(head.x, head.y); + return true; + } + + if ((wo->getWidth() < head.width) && (wo->getHeight() < unsigned(mouse_y))) { + wo->move(head.x + head.width - wo->getWidth(), head.y); + return true; + } + + if ((wo->getWidth() < (head.width - mouse_x)) && (wo->getHeight() < head.height)) { + wo->move(head.x + head.width - wo->getWidth(), head.y + head.height - wo->getHeight()); + return true; + } + + if ((wo->getWidth() < head.width) && (wo->getHeight() < (head.height - mouse_y))) { + wo->move(head.x, head.y + head.height - wo->getHeight()); + return true; + } + + return false; +} + +//! @brief Places the client centered under the mouse +bool +Workspaces::placeMouseCentered(PWinObj *wo) +{ + int mouse_x, mouse_y; + PScreen::instance()->getMousePosition(mouse_x, mouse_y); + + Geometry gm(mouse_x - (wo->getWidth() / 2), mouse_y - (wo->getHeight() / 2), + wo->getWidth(), wo->getHeight()); + + // make sure it's within the screens border + placeInsideScreen(gm); + + wo->move(gm.x, gm.y); + + return true; +} + +//! @brief Places the client like the menu gets placed +bool +Workspaces::placeMouseTopLeft(PWinObj *wo) +{ + int mouse_x, mouse_y; + PScreen::instance()->getMousePosition(mouse_x, mouse_y); + + Geometry gm(mouse_x, mouse_y, wo->getWidth(), wo->getHeight()); + placeInsideScreen(gm); // make sure it's within the screens border + + wo->move(gm.x, gm.y); + + return true; +} + +//! @brief Places centerd on the window parent +bool +Workspaces::placeCenteredOnParent(PWinObj *wo, Window parent) +{ + if (parent == None) { + return false; + } + + PWinObj *wo_s = PWinObj::findPWinObj(parent); + if (wo_s) { + wo->move(wo_s->getX() + wo_s->getWidth() / 2 - wo->getWidth() / 2, + wo_s->getY() + wo_s->getHeight() / 2 - wo->getHeight() / 2); + return true; + } + + return false; +} + +//! @brief Makes sure the window is inside the screen. +void +Workspaces::placeInsideScreen(Geometry &gm, Strut *strut) +{ + // Do not include screen edges when calculating the position if the window + // has a strut as it then is likely to be a panel or the like placed + // along the edge of the screen. + Geometry head; + if (strut) { + PScreen::instance()->getHeadInfo(PScreen::instance()->getCurrHead(), head); + } else { + PScreen::instance()->getHeadInfoWithEdge(PScreen::instance()->getCurrHead(), head); + } + + if (gm.x < head.x) { + gm.x = head.x; + } else if ((gm.x + gm.width) > (head.x + head.width)) { + gm.x = head.x + head.width - gm.width; + } + + if (gm.y < head.y) { + gm.y = head.y; + } else if ((gm.y + gm.height) > (head.y + head.height)) { + gm.y = head.y + head.height - gm.height; + } +} + +//! @brief +PWinObj* +Workspaces::isEmptySpace(int x, int y, const PWinObj* wo) +{ + if (! wo) { + return 0; + } + + // say that it's placed, now check if we are wrong! + list::iterator it(_wo_list.begin()); + for (; it != _wo_list.end(); ++it) { + // Skip ourselves, non-mapped and desktop objects. Iconified means + // skip placement. + if (wo == (*it) || ! (*it)->isMapped() || (*it)->isIconified() + || ((*it)->getLayer() == LAYER_DESKTOP)) { + continue; + } + + // Also skip windows tagged as Maximized as they cause us to + // automatically fail. + if ((*it)->getType() == PWinObj::WO_FRAME) { + Client *client = static_cast((*it))->getActiveClient(); + if (client && + (client->isFullscreen() + || (client->isMaximizedVert() && client->isMaximizedHorz()))) { + continue; + } + } + + // Check if we are "intruding" on some other window's place + if (((*it)->getX() < signed(x + wo->getWidth())) && + (signed((*it)->getX() + (*it)->getWidth()) > x) && + ((*it)->getY() < signed(y + wo->getHeight())) && + (signed((*it)->getY() + (*it)->getHeight()) > y)) { + return (*it); + } + } + + return 0; // we passed the test, no frames in the way +} + +//! @brief +//! @param wo PWinObj to originate from when searching +//! @param dir Direction to search +//! @param skip Bitmask for skipping window objects, defaults to 0 +PWinObj* +Workspaces::findDirectional(PWinObj *wo, DirectionType dir, uint skip) +{ + // search from the frame not client, if client + if (wo->getType() == PWinObj::WO_CLIENT) { + wo = static_cast(wo)->getParent(); + } + + PWinObj *found_wo = 0; + + uint score = 0, score_min; + int wo_main, wo_sec; + int diff_main = 0; + +#ifdef HAVE_LIMITS + score_min = numeric_limits::max(); +#else // !HAVE_LIMITS + score_min = ~0; +#endif // HAVE_LIMITS + + // init wo variables + if ((dir == DIRECTION_UP) || (dir == DIRECTION_DOWN)) { + wo_main = wo->getY() + wo->getHeight() / 2; + wo_sec = wo->getX() + wo->getWidth() / 2; + } else { + wo_main = wo->getX() + wo->getWidth() / 2; + wo_sec = wo->getY() + wo->getHeight() / 2; + } + + list::iterator it(_wo_list.begin()); + for (; it != _wo_list.end(); ++it) { + if ((wo == (*it)) || ! ((*it)->isMapped())) { + continue; // skip ourselves and unmapped wo's + } + if (((*it)->getType() != PWinObj::WO_FRAME) || + static_cast(*it)->isSkip(skip)) { + continue; // only include frames and not having skip set + } + + // check main direction, making sure it's at the right side + // we check against the middle of the window as it gives a saner feeling + // than the edges IMHO + switch (dir) { + case DIRECTION_UP: + diff_main = wo_main - ((*it)->getY() + (*it)->getHeight() / 2); + break; + case DIRECTION_DOWN: + diff_main = ((*it)->getY() + (*it)->getHeight() / 2) - wo_main; + break; + case DIRECTION_LEFT: + diff_main = wo_main - ((*it)->getX() + (*it)->getWidth() / 2); + break; + case DIRECTION_RIGHT: + diff_main = ((*it)->getX() + (*it)->getWidth() / 2) - wo_main; + break; + default: + return 0; // no direction to search + } + + if (diff_main < 0) { + continue; // wrong direction + } + + score = diff_main; + + if ((dir == DIRECTION_UP) || (dir == DIRECTION_DOWN)) { + if ((wo_sec < (*it)->getX()) || (wo_sec > (*it)->getRX())) { + score += PScreen::instance()->getHeight() / 2; + } + score += abs (static_cast (wo_sec - ((*it)->getX () + + (*it)->getWidth () / 2))); + + } else { + if ((wo_sec < (*it)->getY()) || (wo_sec > (*it)->getBY())) { + score += PScreen::instance()->getWidth() / 2; + } + + score += abs (static_cast (wo_sec - ((*it)->getY () + + (*it)->getHeight () / 2))); + } + + if (score < score_min) { + found_wo = *it; + score_min = score; + } + } + + return found_wo; +} + diff --git a/pekwm/Workspaces.hh b/pekwm/Workspaces.hh new file mode 100644 index 0000000..5975587 --- /dev/null +++ b/pekwm/Workspaces.hh @@ -0,0 +1,159 @@ +// +// Workspaces.hh for pekwm +// Copyright © 2002-2009 Claes Nasten +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _WORKSPACES_HH_ +#define _WORKSPACES_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "pekwm.hh" +#include "PScreen.hh" + +#include +#include +#include + +class PScreen; +class EwmhAtoms; +class PWinObj; +class Frame; +class FrameWidget; + +class Workspaces { +public: + class Workspace { + public: + Workspace(const std::wstring &name, uint number); + ~Workspace(void); + + inline std::wstring &getName(void) { return _name; } + void setName(const std::wstring &name) { _name = name; } + inline uint getNumber(void) { return _number; } + inline PWinObj* getLastFocused(void) { return _last_focused; } + + inline void setLastFocused(PWinObj* wo) { _last_focused = wo; } + + private: + std::wstring _name; + uint _number; + + PWinObj *_last_focused; + }; + + Workspaces(uint number, uint per_row); + ~Workspaces(void); + + static inline Workspaces *instance(void) { return _instance; } + + inline uint size(void) const { return _workspace_list.size(); } + inline std::list::iterator begin(void) { return _wo_list.begin(); } + inline std::list::iterator end(void) { return _wo_list.end(); } + inline std::list::reverse_iterator rbegin(void) { return _wo_list.rbegin(); } + inline std::list::reverse_iterator rend(void) { return _wo_list.rend(); } + + std::vector::iterator ws_begin(void) { return _workspace_list.begin(); } + std::vector::iterator ws_end(void) { return _workspace_list.end(); } + + inline uint getActive(void) const { return _active; } + inline uint getPrevious(void) const { return _previous; } + uint getRow(int active = -1) { + if (active < 0) { + active = _active; + } + return _per_row ? (active / _per_row) : 0; + } + uint getRowMin(void) { return _per_row ? (getRow() * _per_row) : 0; } + uint getRowMax(void) { return _per_row ? (getRowMin() + _per_row - 1) : size() - 1; } + uint getRows(void) { return _per_row ? (size() / _per_row + (size() % _per_row ? 1 : 0)) : 1; } + uint getPerRow(void) { return _per_row ? _per_row : size(); } + + void setSize(uint number); + void setPerRow(uint per_row) { _per_row = per_row; } + void setNames(void); + + void setWorkspace(uint num, bool focus); + bool gotoWorkspace(uint direction, bool warp); + + inline const std::list &getWOList(void) const { + return _wo_list; + } + + Workspace *getActiveWorkspace(void) { + return _workspace_list[_active]; + } + Workspace *getWorkspace(uint workspace) { + if (workspace >= _workspace_list.size()) + return 0; + return _workspace_list[workspace]; + }; + + void insert(PWinObj* wo, bool raise = true); + void remove(PWinObj* wo); + + void hideAll(uint workspace); + void unhideAll(uint workspace, bool focus); + + PWinObj* getLastFocused(uint workspace); + void setLastFocused(uint workspace, PWinObj* wo); + + void raise(PWinObj* wo); + void lower(PWinObj* wo); + void stackAbove(PWinObj* wo, Window win, bool restack = true); + void stackBelow(PWinObj *wo, Window win, bool restack = true); + + PWinObj* getTopWO(uint type_mask); + void updateClientList(void); + void updateClientStackingList(void); + void placeWo(PWinObj* wo, Window parent); + void placeWoInsideScreen(PWinObj *wo); + + PWinObj *findDirectional(PWinObj *wo, DirectionType dir, uint skip = 0); + +private: + Window *buildClientList(unsigned int &num_windows); + bool warpToWorkspace(uint num, int dir); + + void stackWinUnderWin(Window over, Window under); + + std::wstring getWorkspaceName(uint num); + + // placement + bool placeSmart(PWinObj* wo); + bool placeMouseNotUnder(PWinObj *wo); + bool placeMouseCentered(PWinObj *wo); + bool placeMouseTopLeft(PWinObj *wo); + bool placeCenteredOnParent(PWinObj *wo, Window parent); + void placeInsideScreen(Geometry &gm, Strut *strut=0); + + // placement helpers + PWinObj* isEmptySpace(int x, int y, const PWinObj *wo); + inline bool isBetween(const int &x1, const int &x2, + const int &t1, const int &t2) { + if (x1 > t1) { + if (x1 < t2) + return true; + } else if (x2 > t1) { + return true; + } + return false; + } + +private: + static Workspaces *_instance; + + uint _active; /**< Current active workspace. */ + uint _previous; /**< Previous workspace. */ + uint _per_row; /**< Workspaces per row in layout. */ + + std::list _wo_list; + std::vector _workspace_list; +}; + +#endif // _WORKSPACES_HH_ diff --git a/pekwm/main.cc b/pekwm/main.cc new file mode 100644 index 0000000..cbfdd1e --- /dev/null +++ b/pekwm/main.cc @@ -0,0 +1,147 @@ +// +// main.cc for pekwm +// Copyright © 2003-2009 Claes Nästén +// +// main.cc for aewm++ +// Copyright (C) 2000 Frank Hale +// http://sapphire.sourceforge.net/ +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "PWinObj.hh" +#include "PDecor.hh" +#include "Client.hh" +#include "Compat.hh" +#include "Frame.hh" +#include "WindowManager.hh" +#include "Util.hh" + +#include +#include +#include + +extern "C" { +#include // execlp +#include +} + +using std::cout; +using std::endl; +using std::string; + +namespace Info { + +//! @brief Prints version +void +printVersion(void) +{ + cout << "pekwm: version " << VERSION << EXTRA_VERSION_INFO << endl; +} + +//! @brief Prints version and availible options +void +printUsage(void) +{ + printVersion(); + cout << " --help show this info." << endl; + cout << " --version show version info" << endl; + cout << " --info extended info. Use for bug reports." << endl; + cout << " --display display to connect to" << endl; + cout << " --config alternative config file" << endl; + cout << " --replace replace running window manager" << endl; +} + +//! @brief Prints version and build-time options +void +printInfo(void) +{ + printVersion(); + cout << "features: " << FEATURES << endl; +} + +} // end namespace Info + +//! @brief Main function of pekwm +int +main(int argc, char **argv) +{ + string config_file; + string command_line; + bool replace = false; + + setlocale(LC_CTYPE, ""); + Util::iconv_init(); + + setenv("PEKWM_ETC_PATH", SYSCONFDIR, 1); + setenv("PEKWM_SCRIPT_PATH", DATADIR "/pekwm/scripts", 1); + setenv("PEKWM_THEME_PATH", DATADIR "/pekwm/themes", 1); + + // build commandline + for (int i = 0; i < argc; ++i) { + command_line = command_line + argv[i] + " "; + } + + // get the args and test for different options + for (int i = 1; i < argc; ++i) { + if ((strcmp("--display", argv[i]) == 0) && ((i + 1) < argc)) { + setenv("DISPLAY", argv[++i], 1); + } else if ((strcmp("--config", argv[i]) == 0) && ((i + 1) < argc)) { + config_file = argv[++i]; + } else if (strcmp("--replace", argv[i]) == 0) { + replace = true; + } else if (strcmp("--version", argv[i]) == 0) { + Info::printVersion(); + exit(0); + } else if (strcmp("--info", argv[i]) == 0) { + Info::printInfo(); + exit(0); + } else if (strcmp("--help", argv[i]) || ! strcmp("-h", argv[i]) == 0) { + Info::printUsage(); + exit(0); + } + } + + // Get configuration file if none was specified as a parameter, + // default to reading environment, if not set get ~/.pekwm/config + if (config_file.size() == 0) { + if (getenv("PEKWM_CONFIG_FILE") && strlen(getenv("PEKWM_CONFIG_FILE"))) { + config_file = getenv("PEKWM_CONFIG_FILE"); + } else { + config_file = string(getenv("HOME")) + string("/.pekwm/config"); + } + } + +#ifdef DEBUG + cout << "Starting pekwm. Use this information in bug reports:" << endl; + Info::printInfo(); +#endif // DEBUG + + WindowManager *wm = WindowManager::start(command_line, config_file, replace); + + if (wm) { + wm->doEventLoop(); + + // see if we wanted to restart + if (WindowManager::instance()->getRestartCommand().size() > 0) { + string command = WindowManager::instance()->getRestartCommand(); + + // cleanup before restarting + WindowManager::destroy(); + Util::iconv_deinit(); + + execlp("/bin/sh", "sh" , "-c", command.c_str(), (char*) 0); + } + WindowManager::destroy(); + } + + // Cleanup + Util::iconv_deinit(); + + return 0; +} diff --git a/pekwm/pekwm.hh b/pekwm/pekwm.hh new file mode 100644 index 0000000..7889a7d --- /dev/null +++ b/pekwm/pekwm.hh @@ -0,0 +1,264 @@ +// +// pekwm.hh for pekwm +// Copyright (C) 2003-2009 Claes Nasten +// +// aewm.hh for aewm++ +// Copyright (C) 2002 Frank Hale +// http://sapphire.sourceforge.net/ +// +// This program is licensed under the GNU GPL. +// See the LICENSE file for more information. +// + +#ifndef _PEKWM_HH_ +#define _PEKWM_HH_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "Compat.hh" +#include "Types.hh" +#include "Exception.hh" + +extern "C" { +#include +#include +} + +// data types + +class Geometry { +public: + Geometry(void) + : x(0), y(0), width(1), height(1) { } + Geometry(int _x, int _y, unsigned int _width, unsigned int _height) : + x(_x), y(_y), width(_width), height(_height) { } + Geometry(const Geometry &gm) + : x(gm.x), y(gm.y), width(gm.width), height(gm.height) { } + ~Geometry(void) { } + + int x, y; + unsigned int width, height; + + inline Geometry& operator = (const Geometry& gm) { + x = gm.x; + y = gm.y; + width = gm.width; + height = gm.height; + return *this; + } + inline bool operator == (const Geometry& gm) { + if ((x == gm.x) && (y == gm.y) && + (width == gm.width) && (height == gm.height)) + return true; + return false; + } + inline bool operator != (const Geometry& gm) { + if ((x != gm.x) || (y != gm.y) || + (width != gm.width) || (height != gm.height)) + return true; + return false; + } +}; + +// Extended Net Hints stuff +class NetWMStates { +public: + NetWMStates(void) + : modal(false), sticky(false), + max_vert(false), max_horz(false), shaded(false), + skip_taskbar(false), skip_pager(false), + hidden(false), fullscreen(false), + above(false), below(false), demands_attention(false) { } + ~NetWMStates(void) { } + + bool modal; + bool sticky; + bool max_vert, max_horz; + bool shaded; + bool skip_taskbar, skip_pager; + bool hidden; + bool fullscreen; + bool above, below; + bool demands_attention; +}; + +#define NET_WM_STICKY_WINDOW 0xffffffff +#define EWMH_OPAQUE_WINDOW 0xffffffff + +// enums +enum Layer { + LAYER_DESKTOP = 0, + LAYER_DESKTOP_TRANSIENT = 1, + LAYER_BELOW = 2, + LAYER_BELOW_TRANSIENT = 3, + LAYER_NORMAL = 4, + LAYER_NORMAL_TRANSIENT = 5, + LAYER_ONTOP = 6, + LAYER_ONTOP_TRANSIENT = 7, + LAYER_DOCK = 8, + LAYER_DOCK_TRANSIENT = 9, + LAYER_ABOVE_DOCK = 10, + LAYER_ABOVE_DOCK_TRANSIENT = 11, + LAYER_MENU = 12, + LAYER_MENU_TRANSIENT = 13, + LAYER_NONE = 14 +}; + +enum ApplyOn { + APPLY_ON_START = (1<<1), + APPLY_ON_NEW = (1<<2), + APPLY_ON_RELOAD = (1<<3), + APPLY_ON_WORKSPACE = (1<<4), + APPLY_ON_TRANSIENT = (1<<5), + APPLY_ON_TRANSIENT_ONLY = (1<<6), + APPLY_ON_NONE = 0 +}; + +enum Skip { + SKIP_MENUS = (1<<1), + SKIP_FOCUS_TOGGLE = (1<<2), + SKIP_SNAP = (1<<3), + SKIP_PAGER = (1<<4), + SKIP_TASKBAR = (1<<5), + SKIP_NONE = 0 +}; + +enum BorderPosition { + BORDER_TOP_LEFT = 0, BORDER_TOP_RIGHT, + BORDER_BOTTOM_LEFT, BORDER_BOTTOM_RIGHT, + BORDER_TOP, BORDER_LEFT, BORDER_RIGHT, BORDER_BOTTOM, + BORDER_NO_POS +}; + +enum ButtonState { + BUTTON_STATE_FOCUSED, BUTTON_STATE_UNFOCUSED, BUTTON_STATE_PRESSED, + BUTTON_STATE_HOVER, BUTTON_STATE_NO +}; +enum FocusedState { + FOCUSED_STATE_FOCUSED, FOCUSED_STATE_UNFOCUSED, + FOCUSED_STATE_FOCUSED_SELECTED, FOCUSED_STATE_UNFOCUSED_SELECTED, + FOCUSED_STATE_NO +}; +enum DecorState { + DECOR_TITLEBAR = (1<<1), + DECOR_BORDER = (1<<2) +}; + +enum ImageType { + IMAGE_TYPE_TILED = 1, IMAGE_TYPE_SCALED, IMAGE_TYPE_FIXED, IMAGE_TYPE_NO = 0 +}; + +enum FontJustify { + FONT_JUSTIFY_LEFT, FONT_JUSTIFY_CENTER, FONT_JUSTIFY_RIGHT, FONT_JUSTIFY_NO +}; +enum PlacementModel { + PLACE_SMART, PLACE_MOUSE_NOT_UNDER, PLACE_MOUSE_CENTERED, + PLACE_MOUSE_TOP_LEFT, PLACE_CENTERED_ON_PARENT, PLACE_NO +}; +enum HarbourPlacement { + TOP, LEFT, RIGHT, BOTTOM, NO_HARBOUR_PLACEMENT +}; +enum EdgeType { + SCREEN_EDGE_TOP, SCREEN_EDGE_BOTTOM, + SCREEN_EDGE_LEFT, SCREEN_EDGE_RIGHT, SCREEN_EDGE_NO +}; +enum PadType { + PAD_UP, PAD_DOWN, PAD_LEFT, PAD_RIGHT, PAD_NO +}; +enum DirectionType { + DIRECTION_UP, DIRECTION_DOWN, + DIRECTION_LEFT, DIRECTION_RIGHT, + DIRECTION_NO +}; +enum WorkspaceChangeType { + WORKSPACE_NO = (uint) ~0, WORKSPACE_LEFT = WORKSPACE_NO - 9, + WORKSPACE_PREV, WORKSPACE_NEXT, WORKSPACE_RIGHT, + WORKSPACE_PREV_V, WORKSPACE_UP, WORKSPACE_NEXT_V, WORKSPACE_DOWN, + WORKSPACE_LAST +}; +enum OrientationType { + TOP_LEFT, TOP_EDGE, TOP_CENTER_EDGE, TOP_RIGHT, + BOTTOM_RIGHT, BOTTOM_EDGE, BOTTOM_CENTER_EDGE, BOTTOM_LEFT, + LEFT_EDGE, LEFT_CENTER_EDGE, RIGHT_EDGE, RIGHT_CENTER_EDGE, + CENTER, NO_EDGE +}; +enum Raise { + ALWAYS_RAISE, END_RAISE, NEVER_RAISE, NO_RAISE +}; +enum Orientation { + TOP_TO_BOTTOM = 1, LEFT_TO_RIGHT = 1, BOTTOM_TO_TOP = 2, RIGHT_TO_LEFT = 2, + NO_ORIENTATION = 0 +}; +enum MouseEventType { + MOUSE_EVENT_PRESS = (1<<1), + MOUSE_EVENT_RELEASE = (1<<2), + MOUSE_EVENT_DOUBLE = (1<<3), + MOUSE_EVENT_MOTION = (1<<4), + MOUSE_EVENT_ENTER = (1<<5), + MOUSE_EVENT_LEAVE = (1<<6), + MOUSE_EVENT_ENTER_MOVING = (1<<7), + MOUSE_EVENT_MOTION_PRESSED = (1<<8), /**< Motion event with button pressed. */ + MOUSE_EVENT_NO = 0 +}; +enum ButtonNum { + BUTTON_ANY = 0, + BUTTON1 = Button1, BUTTON2, BUTTON3, BUTTON4, BUTTON5, + BUTTON6, BUTTON7, BUTTON8, BUTTON9, BUTTON10, BUTTON11, + BUTTON12, BUTTON_NO +}; +enum Mod { + MOD_ANY = (uint) ~0 +}; +enum Focus { + FOCUS_NEW = (1<<1), + FOCUS_ENTER = (1<<2), + FOCUS_LEAVE = (1<<3), + FOCUS_CLICK = (1<<4), + NO_FOCUS = 0 +}; +enum MenuType { + WINDOWMENU_TYPE, ROOTMENU_TYPE, ROOTMENU_STANDALONE_TYPE, + GOTOMENU_TYPE, ICONMENU_TYPE, + ATTACH_CLIENT_TYPE, ATTACH_FRAME_TYPE, + ATTACH_CLIENT_IN_FRAME_TYPE, ATTACH_FRAME_IN_FRAME_TYPE, + DYNAMIC_MENU_TYPE, DECORMENU_TYPE, GOTOCLIENTMENU_TYPE, NO_MENU_TYPE +}; +enum ObjectState { + OBJECT_STATE_FOCUSED = 0, OBJECT_STATE_UNFOCUSED = 1, + OBJECT_STATE_SELECTED = 2, OBJECT_STATE_NO = 2 +}; +enum MouseActionListName { + MOUSE_ACTION_LIST_TITLE_FRAME, MOUSE_ACTION_LIST_TITLE_OTHER, + MOUSE_ACTION_LIST_CHILD_FRAME, MOUSE_ACTION_LIST_CHILD_OTHER, + MOUSE_ACTION_LIST_ROOT, MOUSE_ACTION_LIST_MENU, + MOUSE_ACTION_LIST_OTHER, + + MOUSE_ACTION_LIST_EDGE_T, MOUSE_ACTION_LIST_EDGE_B, + MOUSE_ACTION_LIST_EDGE_L, MOUSE_ACTION_LIST_EDGE_R, + + MOUSE_ACTION_LIST_BORDER_TL, MOUSE_ACTION_LIST_BORDER_T, + MOUSE_ACTION_LIST_BORDER_TR, MOUSE_ACTION_LIST_BORDER_L, + MOUSE_ACTION_LIST_BORDER_R, MOUSE_ACTION_LIST_BORDER_BL, + MOUSE_ACTION_LIST_BORDER_B, MOUSE_ACTION_LIST_BORDER_BR +}; +enum CfgDeny { + CFG_DENY_POSITION = (1L << 0), //!< ConfigureRequest position deny. + CFG_DENY_SIZE = (1L << 1), //!< ConfigureRequest size deny. + CFG_DENY_STACKING = (1L << 2), //!< ConfigureRequest stacking deny. + + CFG_DENY_ACTIVE_WINDOW = (1L << 3), //!< _NET_ACTIVE_WINDOW deny. + + CFG_DENY_STATE_MAXIMIZED_VERT = (1L << 4), //!< EWMH state maximized vert deny. + CFG_DENY_STATE_MAXIMIZED_HORZ = (1L << 5), //!< EWMH state maximized horz deny. + CFG_DENY_STATE_HIDDEN = (1L << 6), //!< EWMH state hidden deny. + CFG_DENY_STATE_FULLSCREEN = (1L << 7), //!< EWMH state fullscreen deny. + CFG_DENY_STATE_ABOVE = (1L << 8), //! EWMH state above deny. + CFG_DENY_STATE_BELOW = (1L << 9), //! EWMH state below deny. + + CFG_DENY_NO = 0 //! No deny. +}; + + +#endif // _PEKWM_HH_