├── .gitignore ├── Makefile ├── README.md ├── classic-wm.xcodeproj └── project.pbxproj ├── decorations.c ├── decorations.h ├── eventnames.h ├── main.c ├── pool.c └── pool.h /.gitignore: -------------------------------------------------------------------------------- 1 | classic-wm 2 | *.o 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | COLLAPSE_BUTTON_ENABLED?=0 2 | 3 | CFLAGS?= -Os -std=c89 #-g 4 | CFLAGS+= -Wall -Werror -DCOLLAPSE_BUTTON_ENABLED=$(COLLAPSE_BUTTON_ENABLED) 5 | 6 | CFLAGS+= -I/opt/X11/include 7 | LDFLAGS+= -L/opt/X11/lib 8 | LDLIBS+= -lX11 -lXext 9 | 10 | PROG= classic-wm 11 | SRCS= main.c decorations.c pool.c 12 | OBJS= $(SRCS:.c=.o) 13 | 14 | $(PROG): $(OBJS) 15 | $(CC) $(LDFLAGS) $(LDLIBS) -o $@ $(OBJS) 16 | 17 | clean: 18 | rm -f $(PROG) $(OBJS) 19 | 20 | .PHONY: clean 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # classic-wm 2 | classic-wm gives you Mac OS [System 6](https://en.wikipedia.org/wiki/System_6) (and earlier) style monochromatic window management, in the X window system. 3 | 4 | ![Screenshot](https://gist.github.com/RyuKojiro/909e3e29d83470d073a6/raw/541421d1b8e08bb4f62220e26a650275feb0e551/classic-wm.png) 5 | 6 | # Installation 7 | 1. `make` 8 | 2. Install [Chicago](https://en.wikipedia.org/wiki/Chicago_\(typeface\)) bitmap font. 9 | * You can convert the original font to a BDF via fontforge, and should end up with a font identifying as: 10 | 11 | `-FontForge-Chicago-Medium-R-Normal--12-120-75-75-P-78-MacRoman-0` 12 | 13 | classic-wm will accept any font matching: 14 | 15 | `-*-Chicago-*-*-*--12-*-*-*-*-*-*-*` 16 | 3. Run `classic-wm` as your new window manager 17 | 4. For a richer experience, you can also set your background to alternating B&W pixels using `xsetroot -grey`. 18 | 19 | # Collapsing 20 | You can enable the single-click collapse button shown below by building with `make COLLAPSE_BUTTON_ENABLED=1`. 21 | 22 | ![Collapse Button](https://gist.githubusercontent.com/RyuKojiro/c24128fe6e30e6d0eb83/raw/fbe93d6c777107506aa1babba382b1dd42c02151/collapse-button.png) 23 | 24 | Even without the collapse button enabled, all windows will support collapsing by double clicking on any portion of the titlebar that isn't a button. 25 | Collapsing a window does not change its width, but reduces it to consume only as much space as its title bar. 26 | 27 | ![Collapsed Window](https://gist.githubusercontent.com/RyuKojiro/c24128fe6e30e6d0eb83/raw/fbe93d6c777107506aa1babba382b1dd42c02151/collapsed.png) 28 | -------------------------------------------------------------------------------- /classic-wm.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E60614521BFDBF0D0030BCB5 /* pool.c in Sources */ = {isa = PBXBuildFile; fileRef = E621A1B51602B8AB00CBEA9B /* pool.c */; }; 11 | E60614531BFDBF0D0030BCB5 /* decorations.c in Sources */ = {isa = PBXBuildFile; fileRef = E621A19F1602B0FF00CBEA9B /* decorations.c */; }; 12 | E60614541BFDBF0D0030BCB5 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = E621A1A21602B0FF00CBEA9B /* main.c */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXCopyFilesBuildPhase section */ 16 | E60614481BFDBEF60030BCB5 /* CopyFiles */ = { 17 | isa = PBXCopyFilesBuildPhase; 18 | buildActionMask = 2147483647; 19 | dstPath = /usr/share/man/man1/; 20 | dstSubfolderSpec = 0; 21 | files = ( 22 | ); 23 | runOnlyForDeploymentPostprocessing = 1; 24 | }; 25 | /* End PBXCopyFilesBuildPhase section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | E606144A1BFDBEF60030BCB5 /* cwm-xcode */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "cwm-xcode"; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | E621A19F1602B0FF00CBEA9B /* decorations.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = decorations.c; sourceTree = ""; }; 30 | E621A1A01602B0FF00CBEA9B /* decorations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = decorations.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 31 | E621A1A11602B0FF00CBEA9B /* eventnames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = eventnames.h; sourceTree = ""; }; 32 | E621A1A21602B0FF00CBEA9B /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; lineEnding = 0; path = main.c; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.c; }; 33 | E621A1A31602B0FF00CBEA9B /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; 34 | E621A1B51602B8AB00CBEA9B /* pool.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; lineEnding = 0; path = pool.c; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.c; }; 35 | E621A1B61602B8AB00CBEA9B /* pool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = pool.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | E60614471BFDBEF60030BCB5 /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | E606144B1BFDBEF60030BCB5 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | E606144A1BFDBEF60030BCB5 /* cwm-xcode */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | E621A18F1602B0E500CBEA9B = { 58 | isa = PBXGroup; 59 | children = ( 60 | E621A1B51602B8AB00CBEA9B /* pool.c */, 61 | E621A1B61602B8AB00CBEA9B /* pool.h */, 62 | E621A19F1602B0FF00CBEA9B /* decorations.c */, 63 | E621A1A01602B0FF00CBEA9B /* decorations.h */, 64 | E621A1A11602B0FF00CBEA9B /* eventnames.h */, 65 | E621A1A21602B0FF00CBEA9B /* main.c */, 66 | E621A1A31602B0FF00CBEA9B /* Makefile */, 67 | E606144B1BFDBEF60030BCB5 /* Products */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | /* End PBXGroup section */ 72 | 73 | /* Begin PBXLegacyTarget section */ 74 | E621A1961602B0E500CBEA9B /* classic-wm */ = { 75 | isa = PBXLegacyTarget; 76 | buildArgumentsString = "$(ACTION)"; 77 | buildConfigurationList = E621A1991602B0E500CBEA9B /* Build configuration list for PBXLegacyTarget "classic-wm" */; 78 | buildPhases = ( 79 | ); 80 | buildToolPath = /usr/bin/make; 81 | buildWorkingDirectory = ""; 82 | dependencies = ( 83 | ); 84 | name = "classic-wm"; 85 | passBuildSettingsInEnvironment = 1; 86 | productName = "classic-wm"; 87 | }; 88 | /* End PBXLegacyTarget section */ 89 | 90 | /* Begin PBXNativeTarget section */ 91 | E60614491BFDBEF60030BCB5 /* cwm-xcode */ = { 92 | isa = PBXNativeTarget; 93 | buildConfigurationList = E60614511BFDBEF60030BCB5 /* Build configuration list for PBXNativeTarget "cwm-xcode" */; 94 | buildPhases = ( 95 | E60614461BFDBEF60030BCB5 /* Sources */, 96 | E60614471BFDBEF60030BCB5 /* Frameworks */, 97 | E60614481BFDBEF60030BCB5 /* CopyFiles */, 98 | ); 99 | buildRules = ( 100 | ); 101 | dependencies = ( 102 | ); 103 | name = "cwm-xcode"; 104 | productName = "cwm-xcode"; 105 | productReference = E606144A1BFDBEF60030BCB5 /* cwm-xcode */; 106 | productType = "com.apple.product-type.tool"; 107 | }; 108 | /* End PBXNativeTarget section */ 109 | 110 | /* Begin PBXProject section */ 111 | E621A1911602B0E500CBEA9B /* Project object */ = { 112 | isa = PBXProject; 113 | attributes = { 114 | LastUpgradeCheck = 0710; 115 | TargetAttributes = { 116 | E60614491BFDBEF60030BCB5 = { 117 | CreatedOnToolsVersion = 7.1.1; 118 | }; 119 | }; 120 | }; 121 | buildConfigurationList = E621A1941602B0E500CBEA9B /* Build configuration list for PBXProject "classic-wm" */; 122 | compatibilityVersion = "Xcode 3.2"; 123 | developmentRegion = English; 124 | hasScannedForEncodings = 0; 125 | knownRegions = ( 126 | en, 127 | ); 128 | mainGroup = E621A18F1602B0E500CBEA9B; 129 | productRefGroup = E606144B1BFDBEF60030BCB5 /* Products */; 130 | projectDirPath = ""; 131 | projectRoot = ""; 132 | targets = ( 133 | E621A1961602B0E500CBEA9B /* classic-wm */, 134 | E60614491BFDBEF60030BCB5 /* cwm-xcode */, 135 | ); 136 | }; 137 | /* End PBXProject section */ 138 | 139 | /* Begin PBXSourcesBuildPhase section */ 140 | E60614461BFDBEF60030BCB5 /* Sources */ = { 141 | isa = PBXSourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | E60614521BFDBF0D0030BCB5 /* pool.c in Sources */, 145 | E60614531BFDBF0D0030BCB5 /* decorations.c in Sources */, 146 | E60614541BFDBF0D0030BCB5 /* main.c in Sources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXSourcesBuildPhase section */ 151 | 152 | /* Begin XCBuildConfiguration section */ 153 | E606144F1BFDBEF60030BCB5 /* Debug */ = { 154 | isa = XCBuildConfiguration; 155 | buildSettings = { 156 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 157 | CLANG_CXX_LIBRARY = "libc++"; 158 | CLANG_ENABLE_MODULES = YES; 159 | CLANG_ENABLE_OBJC_ARC = YES; 160 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 161 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 162 | CODE_SIGN_IDENTITY = "-"; 163 | DEBUG_INFORMATION_FORMAT = dwarf; 164 | ENABLE_TESTABILITY = YES; 165 | GCC_NO_COMMON_BLOCKS = YES; 166 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 167 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 168 | HEADER_SEARCH_PATHS = /opt/X11/include; 169 | LIBRARY_SEARCH_PATHS = ( 170 | "-I/opt/X11/include\n/opt/X11/lib", 171 | ); 172 | MACOSX_DEPLOYMENT_TARGET = 10.11; 173 | MTL_ENABLE_DEBUG_INFO = YES; 174 | OTHER_LDFLAGS = ( 175 | "-lX11", 176 | "-lXext", 177 | ); 178 | PRODUCT_NAME = "$(TARGET_NAME)"; 179 | }; 180 | name = Debug; 181 | }; 182 | E60614501BFDBEF60030BCB5 /* Release */ = { 183 | isa = XCBuildConfiguration; 184 | buildSettings = { 185 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 186 | CLANG_CXX_LIBRARY = "libc++"; 187 | CLANG_ENABLE_MODULES = YES; 188 | CLANG_ENABLE_OBJC_ARC = YES; 189 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 190 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 191 | CODE_SIGN_IDENTITY = "-"; 192 | COPY_PHASE_STRIP = NO; 193 | ENABLE_NS_ASSERTIONS = NO; 194 | GCC_NO_COMMON_BLOCKS = YES; 195 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 196 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 197 | HEADER_SEARCH_PATHS = /opt/X11/include; 198 | LIBRARY_SEARCH_PATHS = ( 199 | "-I/opt/X11/include\n/opt/X11/lib", 200 | ); 201 | MACOSX_DEPLOYMENT_TARGET = 10.11; 202 | MTL_ENABLE_DEBUG_INFO = NO; 203 | OTHER_LDFLAGS = ( 204 | "-lX11", 205 | "-lXext", 206 | ); 207 | PRODUCT_NAME = "$(TARGET_NAME)"; 208 | }; 209 | name = Release; 210 | }; 211 | E621A1971602B0E500CBEA9B /* Debug */ = { 212 | isa = XCBuildConfiguration; 213 | buildSettings = { 214 | ALWAYS_SEARCH_USER_PATHS = NO; 215 | CLANG_WARN_BOOL_CONVERSION = YES; 216 | CLANG_WARN_CONSTANT_CONVERSION = YES; 217 | CLANG_WARN_EMPTY_BODY = YES; 218 | CLANG_WARN_ENUM_CONVERSION = YES; 219 | CLANG_WARN_INT_CONVERSION = YES; 220 | CLANG_WARN_UNREACHABLE_CODE = YES; 221 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 222 | COPY_PHASE_STRIP = NO; 223 | ENABLE_STRICT_OBJC_MSGSEND = YES; 224 | ENABLE_TESTABILITY = YES; 225 | GCC_C_LANGUAGE_STANDARD = gnu99; 226 | GCC_DYNAMIC_NO_PIC = NO; 227 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 228 | GCC_NO_COMMON_BLOCKS = YES; 229 | GCC_OPTIMIZATION_LEVEL = 0; 230 | GCC_PREPROCESSOR_DEFINITIONS = ( 231 | "DEBUG=1", 232 | "$(inherited)", 233 | ); 234 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 235 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 236 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 237 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 238 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 239 | GCC_WARN_UNDECLARED_SELECTOR = YES; 240 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 241 | GCC_WARN_UNUSED_FUNCTION = YES; 242 | GCC_WARN_UNUSED_VARIABLE = YES; 243 | MACOSX_DEPLOYMENT_TARGET = 10.6; 244 | ONLY_ACTIVE_ARCH = YES; 245 | SDKROOT = macosx; 246 | }; 247 | name = Debug; 248 | }; 249 | E621A1981602B0E500CBEA9B /* Release */ = { 250 | isa = XCBuildConfiguration; 251 | buildSettings = { 252 | ALWAYS_SEARCH_USER_PATHS = NO; 253 | CLANG_WARN_BOOL_CONVERSION = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_EMPTY_BODY = YES; 256 | CLANG_WARN_ENUM_CONVERSION = YES; 257 | CLANG_WARN_INT_CONVERSION = YES; 258 | CLANG_WARN_UNREACHABLE_CODE = YES; 259 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 260 | COPY_PHASE_STRIP = YES; 261 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 262 | ENABLE_STRICT_OBJC_MSGSEND = YES; 263 | GCC_C_LANGUAGE_STANDARD = gnu99; 264 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 265 | GCC_NO_COMMON_BLOCKS = YES; 266 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 267 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 268 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 269 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 270 | GCC_WARN_UNDECLARED_SELECTOR = YES; 271 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 272 | GCC_WARN_UNUSED_FUNCTION = YES; 273 | GCC_WARN_UNUSED_VARIABLE = YES; 274 | MACOSX_DEPLOYMENT_TARGET = 10.6; 275 | SDKROOT = macosx; 276 | }; 277 | name = Release; 278 | }; 279 | E621A19A1602B0E500CBEA9B /* Debug */ = { 280 | isa = XCBuildConfiguration; 281 | buildSettings = { 282 | DEBUGGING_SYMBOLS = YES; 283 | GCC_GENERATE_DEBUGGING_SYMBOLS = YES; 284 | GCC_OPTIMIZATION_LEVEL = 0; 285 | OTHER_CFLAGS = ""; 286 | OTHER_LDFLAGS = ""; 287 | PRODUCT_NAME = "$(TARGET_NAME)"; 288 | }; 289 | name = Debug; 290 | }; 291 | E621A19B1602B0E500CBEA9B /* Release */ = { 292 | isa = XCBuildConfiguration; 293 | buildSettings = { 294 | OTHER_CFLAGS = ""; 295 | OTHER_LDFLAGS = ""; 296 | PRODUCT_NAME = "$(TARGET_NAME)"; 297 | }; 298 | name = Release; 299 | }; 300 | /* End XCBuildConfiguration section */ 301 | 302 | /* Begin XCConfigurationList section */ 303 | E60614511BFDBEF60030BCB5 /* Build configuration list for PBXNativeTarget "cwm-xcode" */ = { 304 | isa = XCConfigurationList; 305 | buildConfigurations = ( 306 | E606144F1BFDBEF60030BCB5 /* Debug */, 307 | E60614501BFDBEF60030BCB5 /* Release */, 308 | ); 309 | defaultConfigurationIsVisible = 0; 310 | }; 311 | E621A1941602B0E500CBEA9B /* Build configuration list for PBXProject "classic-wm" */ = { 312 | isa = XCConfigurationList; 313 | buildConfigurations = ( 314 | E621A1971602B0E500CBEA9B /* Debug */, 315 | E621A1981602B0E500CBEA9B /* Release */, 316 | ); 317 | defaultConfigurationIsVisible = 0; 318 | defaultConfigurationName = Release; 319 | }; 320 | E621A1991602B0E500CBEA9B /* Build configuration list for PBXLegacyTarget "classic-wm" */ = { 321 | isa = XCConfigurationList; 322 | buildConfigurations = ( 323 | E621A19A1602B0E500CBEA9B /* Debug */, 324 | E621A19B1602B0E500CBEA9B /* Release */, 325 | ); 326 | defaultConfigurationIsVisible = 0; 327 | defaultConfigurationName = Release; 328 | }; 329 | /* End XCConfigurationList section */ 330 | }; 331 | rootObject = E621A1911602B0E500CBEA9B /* Project object */; 332 | } 333 | -------------------------------------------------------------------------------- /decorations.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Daniel Loffgren 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include "decorations.h" 27 | 28 | static unsigned long white; 29 | static unsigned long black; 30 | static XFontStruct *font; 31 | 32 | int pointIsInRect(const int px, const int py, const int rx, const int ry, int rw, int rh) { 33 | rw++; 34 | rh++; 35 | return ((px >= rx && px <= (rx + rw)) && 36 | (py >= ry && py <= (ry + rh))); 37 | } 38 | 39 | Window decorateWindow(Display *display, Drawable window, Window root, GC gc, const int x, const int y, const int width, const int height, Window *resizer) { 40 | Window newParent; 41 | XSetWindowAttributes attrib; 42 | XWindowAttributes attr; 43 | XWindowAttributes incomingAttribs; 44 | char *title; 45 | 46 | /* This is entirely for window border compensation */ 47 | /* FIXME: This _works_, but looks like crap for anything with more than a 1px border, in the future this should do up to one pixel and start adjusting the container window for the remainder */ 48 | XGetWindowAttributes(display, window, &incomingAttribs); 49 | 50 | attr.width = width; 51 | attr.height = height + TITLEBAR_THICKNESS; 52 | 53 | /* Flag as override_redirect, so that we don't decorate decorations */ 54 | attrib.override_redirect = 1; 55 | 56 | /* Create New Parent */ 57 | newParent = XCreateWindow(display, root, x, y, width + FRAME_HORIZONTAL_THICKNESS, height + FRAME_VERTICAL_THICKNESS, 0, CopyFromParent, InputOutput, CopyFromParent, CWOverrideRedirect, &attrib); 58 | XReparentWindow(display, window, newParent, FRAME_LEFT_THICKNESS - incomingAttribs.border_width, TITLEBAR_THICKNESS - incomingAttribs.border_width); 59 | 60 | /* Create Resize Button Window */ 61 | *resizer = XCreateWindow(display, newParent, RECT_RESIZE_BTN, 0, CopyFromParent, CopyFromParent, CopyFromParent, 0, 0); 62 | XMapRaised(display, *resizer); 63 | 64 | /* Set Cursor */ 65 | Cursor cur = XCreateFontCursor(display, XC_left_ptr); 66 | XDefineCursor(display, newParent, cur); 67 | 68 | /* Readjust attributes to now refer to the decoration window */ 69 | attr.width += FRAME_HORIZONTAL_THICKNESS; 70 | attr.height += FRAME_BOTTOM_THICKNESS; 71 | 72 | /* Draw Time! */ 73 | XMapWindow(display, newParent); 74 | XFetchName(display, window, &title); 75 | drawDecorations(display, newParent, gc, title, attr, 1); 76 | drawResizeButton(display, *resizer, gc, RECT_RESIZE_DRAW); 77 | 78 | if (title) { 79 | XFree(title); 80 | } 81 | 82 | return newParent; 83 | } 84 | 85 | void undecorateWindow(Display *display, Window decorationWindow, Window resizer) { 86 | XUnmapWindow(display, resizer); 87 | XDestroyWindow(display, resizer); 88 | XUnmapWindow(display, decorationWindow); 89 | XDestroyWindow(display, decorationWindow); 90 | } 91 | 92 | static void whiteOutTitleBar(Display *display, Drawable window, GC gc, XWindowAttributes attr){ 93 | XSetForeground(display, gc, white); 94 | XFillRectangle(display, window, gc, 1, 1, attr.width - FRAME_HORIZONTAL_THICKNESS, TITLEBAR_THICKNESS - 2); 95 | } 96 | 97 | void drawDecorations(Display *display, Drawable window, GC gc, const char *title, XWindowAttributes attr, const int focused) { 98 | if (!white || !black) { 99 | white = XWhitePixel(display, DefaultScreen(display)); 100 | black = XBlackPixel(display, DefaultScreen(display)); 101 | } 102 | 103 | /* Draw bounding box */ 104 | whiteOutTitleBar(display, window, gc, attr); 105 | XSetForeground(display, gc, black); 106 | XDrawRectangle(display, window, gc, RECT_TITLEBAR); 107 | 108 | if (focused) { 109 | /* Draw texture */ 110 | int y; 111 | for (y = TITLEBAR_TEXTURE_START; y < TITLEBAR_TEXTURE_START + TITLEBAR_CONTROL_SIZE; y += TITLEBAR_TEXTURE_SPACE) { 112 | XDrawLine(display, window, gc, 2, y, attr.width - 4, y); 113 | } 114 | } 115 | 116 | /* White out areas for buttons and title */ 117 | XSetForeground(display, gc, white); 118 | /* Subwindow box */ 119 | XFillRectangle(display, window, gc, 120 | FRAME_RIGHT_THICKNESS, 121 | TITLEBAR_THICKNESS, 122 | attr.width - FRAME_HORIZONTAL_THICKNESS, 123 | attr.height - FRAME_VERTICAL_THICKNESS - 1); /* FIXME: Is this extra 1 necessary? */ 124 | 125 | /* Draw buttons and title */ 126 | XSetForeground(display, gc, black); 127 | /* Subwindow box with each edge tucked in */ 128 | XDrawRectangle(display, window, gc, 129 | FRAME_LEFT_THICKNESS - FRAME_TUCK_INSET, 130 | TITLEBAR_THICKNESS - FRAME_TUCK_INSET, 131 | attr.width - FRAME_LEFT_THICKNESS - FRAME_TUCK_INSET, 132 | attr.height - TITLEBAR_THICKNESS - FRAME_TUCK_INSET); 133 | /* Shadow */ 134 | XDrawLine(display, window, gc, 1, attr.height - 1, attr.width, attr.height - 1); /* bottom */ 135 | XDrawLine(display, window, gc, attr.width - 1, attr.height - 1, attr.width - 1, 1); /* left */ 136 | 137 | /* White out the shadow ends */ 138 | XSetForeground(display, gc, white); 139 | XDrawPoint(display, window, gc, 0, attr.height - 1); /* bottom left */ 140 | XDrawPoint(display, window, gc, attr.width - 1, 0); /* top right */ 141 | XSetForeground(display, gc, black); 142 | 143 | /* Draw Title */ 144 | drawTitle(display, window, gc, title, attr); 145 | 146 | if (focused) { 147 | /* Draw Close Button */ 148 | drawCloseButton(display, window, gc, RECT_CLOSE_BTN); 149 | 150 | /* Draw Maximize Button */ 151 | drawMaximizeButton(display, window, gc, RECT_MAX_BTN); 152 | 153 | #if COLLAPSE_BUTTON_ENABLED 154 | /* Draw Collapse Button */ 155 | drawCollapseButton(display, window, gc, RECT_COLLAPSE_BTN); 156 | #endif 157 | } 158 | } 159 | 160 | void drawTitle(Display *display, Drawable window, GC gc, const char *title, XWindowAttributes attr){ 161 | int twidth; 162 | 163 | if (title) { 164 | /* Set up text */ 165 | if (!font) { 166 | font = XLoadQueryFont(display, TITLEBAR_FONTNAME); 167 | if (!font) { 168 | warnx("unable to load preferred font: " TITLEBAR_FONTNAME " using fixed"); 169 | font = XLoadQueryFont(display, "fixed"); 170 | assert(font); 171 | } 172 | } 173 | XSetFont(display, gc, font->fid); 174 | twidth = XTextWidth(font, title, (int)strlen(title)); 175 | 176 | /* If the title wont fit, don't bother drawing it, just bail */ 177 | if (attr.width < (twidth + 42 + (2 * TITLEBAR_TEXT_MARGIN))) { 178 | return; 179 | } 180 | 181 | /* White out under Title */ 182 | XSetForeground(display, gc, white); 183 | XFillRectangle(display, window, gc, 184 | ((attr.width - twidth)/ 2) - TITLEBAR_TEXT_MARGIN, 185 | 4, 186 | twidth + (2 * TITLEBAR_TEXT_MARGIN), 187 | TITLEBAR_CONTROL_SIZE); 188 | 189 | /* Draw title */ 190 | XSetForeground(display, gc, black); 191 | XSetBackground(display, gc, white); 192 | XDrawString(display, window, gc, ((attr.width - twidth)/ 2), TITLEBAR_TEXT_OFFSET, title, (int)strlen(title)); 193 | } 194 | } 195 | 196 | void whiteOutUnderButton(Display *display, Drawable window, GC gc, const int x, const int y, const int w, const int h){ 197 | /* White out bg */ 198 | XSetForeground(display, gc, white); 199 | XFillRectangle(display, window, gc, x - 1, y, w + 3, h + 1); 200 | } 201 | 202 | void drawResizeButton(Display *display, Drawable window, GC gc, const int x, const int y, const int w, const int h) { 203 | whiteOutUnderButton(display, window, gc, x, y, w, h); 204 | 205 | /* Draw Border */ 206 | XSetForeground(display, gc, black); 207 | XDrawRectangle(display, window, gc, x, y, w, h); 208 | 209 | /* Bottom box */ 210 | XDrawRectangle(display, window, gc, x + 5, y + 5, 8, 8); 211 | 212 | /* Top box */ 213 | XDrawRectangle(display, window, gc, x + 3, y + 3, 6, 6); 214 | 215 | /* White out overlap */ 216 | XSetForeground(display, gc, white); 217 | XFillRectangle(display, window, gc, x + 4, y + 4, 5, 5); 218 | } 219 | 220 | void drawMaximizeButton(Display *display, Drawable window, GC gc, const int x, const int y, const int w, const int h) { 221 | whiteOutUnderButton(display, window, gc, x, y, w, h); 222 | 223 | /* Draw Border */ 224 | XSetForeground(display, gc, black); 225 | XDrawRectangle(display, window, gc, x, y, w, h); 226 | 227 | /* Draw Inside */ 228 | XDrawRectangle(display, window, gc, x, y, w / 2, h / 2); 229 | } 230 | 231 | void drawCloseButton(Display *display, Drawable window, GC gc, const int x, const int y, const int w, const int h) { 232 | whiteOutUnderButton(display, window, gc, x, y, w, h); 233 | 234 | /* Draw Border */ 235 | XSetForeground(display, gc, black); 236 | XDrawRectangle(display, window, gc, x, y, w, h); 237 | } 238 | 239 | void drawCollapseButton(Display *display, Drawable window, GC gc, const int x, const int y, const int w, const int h) { 240 | whiteOutUnderButton(display, window, gc, x, y, w, h); 241 | 242 | /* Draw Border */ 243 | XSetForeground(display, gc, black); 244 | XDrawRectangle(display, window, gc, x, y, w, h); 245 | 246 | XDrawRectangle(display, window, gc, x, y + w / 2 - 1, w, 2); 247 | } 248 | 249 | void drawCloseButtonDown(Display *display, Drawable window, GC gc, const int x, const int y, const int w, const int h) { 250 | drawCloseButton(display, window, gc, x, y, w, h); 251 | 252 | /* Draw first diag */ 253 | XDrawLine(display, window, gc, x + 2, y + 2, x + w - 2, y + h - 2); 254 | 255 | /* Draw | */ 256 | XDrawLine(display, window, gc, x + w / 2, y, x + w / 2, y + h); 257 | 258 | /* Draw - */ 259 | XDrawLine(display, window, gc, x, y + h / 2, x + w, y + h / 2); 260 | 261 | /* Draw / */ 262 | XDrawLine(display, window, gc, x + w - 2, y + 2, x + 2, y + h - 2); 263 | 264 | /* Remove Center */ 265 | XSetForeground(display, gc, white); 266 | XFillRectangle(display, window, gc, x + w / 2 - 1, y + h / 2 - 1, 3, 3); 267 | } 268 | -------------------------------------------------------------------------------- /decorations.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Daniel Loffgren 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #ifndef _decorations_h 24 | #define _decorations_h 25 | 26 | #include 27 | #include 28 | 29 | /* Titlebar Font */ 30 | #define TITLEBAR_FONTNAME "-*-Chicago-*-*-*--12-*-*-*-*-*-*-*" 31 | 32 | /* Hard dimensions */ 33 | #define TITLEBAR_THICKNESS (19) /* px tall (This should scale everything else) */ 34 | #define TITLEBAR_TEXT_OFFSET (14) /* ptSize or pxSize */ 35 | #define TITLEBAR_TEXTURE_START (4) /* px from top to start texture */ 36 | #define TITLEBAR_TEXTURE_SPACE (TITLEBAR_THICKNESS / 10 + 1) /* px space between each line */ 37 | #define TITLEBAR_CONTROL_SIZE (TITLEBAR_THICKNESS - 8) /* px^2 */ 38 | #define TITLEBAR_TEXT_MARGIN (7) /* px on either side */ 39 | #define RESIZE_CONTROL_SIZE (15) /* px^2 */ 40 | #define FRAME_LEFT_THICKNESS (1) /* px wide */ 41 | #define FRAME_RIGHT_THICKNESS (2) /* px wide */ 42 | #define FRAME_BOTTOM_THICKNESS (2) /* px tall */ 43 | #define COLLAPSED_THICKNESS (TITLEBAR_THICKNESS - 1 + FRAME_BOTTOM_THICKNESS) 44 | #define FRAME_VERTICAL_THICKNESS (TITLEBAR_THICKNESS + FRAME_BOTTOM_THICKNESS) 45 | #define FRAME_HORIZONTAL_THICKNESS (FRAME_LEFT_THICKNESS + FRAME_RIGHT_THICKNESS) 46 | #define FRAME_TUCK_INSET (1) /* X windows tend to bring their own 1px border */ 47 | 48 | /* Rects */ 49 | #define RECT_TITLEBAR 0, 0, attr.width - 2, TITLEBAR_THICKNESS - 1 50 | #define RECT_CLOSE_BTN 9, 4, TITLEBAR_CONTROL_SIZE - 1, TITLEBAR_CONTROL_SIZE - 1 51 | #define RECT_RESIZE_BTN attr.width - RESIZE_CONTROL_SIZE + 1, attr.height - RESIZE_CONTROL_SIZE, RESIZE_CONTROL_SIZE, RESIZE_CONTROL_SIZE 52 | #define RECT_RESIZE_DRAW 0, 0, RESIZE_CONTROL_SIZE, RESIZE_CONTROL_SIZE 53 | 54 | /* Right hand titlebar controls */ 55 | #define FIRST_RIGHTMOST_BTN attr.width - (10 + TITLEBAR_CONTROL_SIZE), 4, TITLEBAR_CONTROL_SIZE - 1, TITLEBAR_CONTROL_SIZE - 1 56 | #define SECOND_RIGHTMOST_BTN attr.width - (7 + TITLEBAR_CONTROL_SIZE) * 2 - 2, 4, TITLEBAR_CONTROL_SIZE - 1, TITLEBAR_CONTROL_SIZE - 1 57 | 58 | #if COLLAPSE_BUTTON_ENABLED 59 | #define RECT_COLLAPSE_BTN FIRST_RIGHTMOST_BTN 60 | #define RECT_MAX_BTN SECOND_RIGHTMOST_BTN 61 | #else 62 | #define RECT_MAX_BTN FIRST_RIGHTMOST_BTN 63 | #endif 64 | 65 | /* Cursors */ 66 | #define XC_left_ptr (68) 67 | 68 | /* Double Buffering */ 69 | #define DRAW_ACTION(display, window, action) { \ 70 | XdbeSwapInfo swap_info; \ 71 | swap_info.swap_window = window; \ 72 | swap_info.swap_action = XdbeCopied; \ 73 | XdbeBeginIdiom(display); \ 74 | action \ 75 | XdbeSwapBuffers(display, &swap_info, 1); \ 76 | XdbeEndIdiom(display); \ 77 | } 78 | 79 | 80 | /* Functions */ 81 | Window decorateWindow(Display *display, Drawable window, Window root, GC gc, int x, int y, int width, int height, Window *resizer); 82 | void undecorateWindow(Display *display, Window decorationWindow, Window resizer); 83 | void drawDecorations(Display *display, Drawable window, GC gc, const char *title, XWindowAttributes attr, int focused); 84 | void drawTitle(Display *display, Drawable window, GC gc, const char *title, XWindowAttributes attr); 85 | int pointIsInRect(int px, int py, int rx, int ry, int rw, int rh); 86 | 87 | typedef void (decorationFunction)(Display *display, Drawable window, GC gc, int x, int y, int w, int h); 88 | 89 | /* Individual Decorations */ 90 | void whiteOutUnderButton(Display *display, Drawable window, GC gc, int x, int y, int w, int h); 91 | void drawCloseButton(Display *display, Drawable window, GC gc, int x, int y, int w, int h); 92 | void drawCloseButtonDown(Display *display, Drawable window, GC gc, int x, int y, int w, int h); 93 | void drawMaximizeButton(Display *display, Drawable window, GC gc, int x, int y, int w, int h); 94 | void drawResizeButton(Display *display, Drawable window, GC gc, int x, int y, int w, int h); 95 | void drawCollapseButton(Display *display, Drawable window, GC gc, int x, int y, int w, int h); 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /eventnames.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This array was borrowed from the nwm project: 3 | * https://github.com/mixu/nwm/blob/master/src/nwm/x11_misc.c 4 | * The nwm license follows. 5 | * 6 | * The MIT License (MIT) 7 | * 8 | * Copyright (c) 2011-2013 Mikito Takada 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to 12 | * deal in the Software without restriction, including without limitation the 13 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 14 | * sell copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 26 | * IN THE SOFTWARE. 27 | */ 28 | 29 | static const char *event_names[] = { 30 | "", 31 | "", 32 | "KeyPress", 33 | "KeyRelease", 34 | "ButtonPress", 35 | "ButtonRelease", 36 | "MotionNotify", 37 | "EnterNotify", 38 | "LeaveNotify", 39 | "FocusIn", 40 | "FocusOut", 41 | "KeymapNotify", 42 | "Expose", 43 | "GraphicsExpose", 44 | "NoExpose", 45 | "VisibilityNotify", 46 | "CreateNotify", 47 | "DestroyNotify", 48 | "UnmapNotify", 49 | "MapNotify", 50 | "MapRequest", 51 | "ReparentNotify", 52 | "ConfigureNotify", 53 | "ConfigureRequest", 54 | "GravityNotify", 55 | "ResizeRequest", 56 | "CirculateNotify", 57 | "CirculateRequest", 58 | "PropertyNotify", 59 | "SelectionClear", 60 | "SelectionRequest", 61 | "SelectionNotify", 62 | "ColormapNotify", 63 | "ClientMessage", 64 | "MappingNotify" 65 | }; 66 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Daniel Loffgren 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include /* warnx */ 25 | #include /* va_list */ 26 | #include /* getenv */ 27 | #include /* EX_UNAVAILABLE */ 28 | #include /* time() */ 29 | #include 30 | #include /* XSizeHints */ 31 | 32 | #include "eventnames.h" 33 | #include "decorations.h" 34 | #include "pool.h" 35 | 36 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 37 | #define NEW_WINDOW_OFFSET 0 /*((XDisplayWidth(display, DefaultScreen(display)) > 2560) ? 0 : 22) */ 38 | 39 | typedef enum { 40 | MouseDownStateUnknown = 0, 41 | MouseDownStateMove, 42 | MouseDownStateClose, 43 | MouseDownStateMaximize, 44 | #if COLLAPSE_BUTTON_ENABLED 45 | MouseDownStateCollapse, 46 | #endif 47 | MouseDownStateResize 48 | } MouseDownState; 49 | 50 | /* HACK: Find a better way to eat the extraneous Expose events on destruction of decorations */ 51 | static Window decorationWindowDestroyed; 52 | static Window resizerDestroyed; 53 | 54 | static void resizeWindow(Display *display, ManagedWindow *mw, int w, int h) { 55 | /* Set some absolute minimums */ 56 | w = MAX(w, ((TITLEBAR_CONTROL_SIZE) * 5)); 57 | h = MAX(h, ((TITLEBAR_THICKNESS) * 2) + RESIZE_CONTROL_SIZE); 58 | 59 | /* Respect the window minimums, if they exist */ 60 | w = MAX(w, mw->min_w); 61 | h = MAX(h, mw->min_h); 62 | 63 | /* Size the decorations as requested, and inset the actual window */ 64 | XResizeWindow(display, mw->decorationWindow, w, h); 65 | XResizeWindow(display, mw->actualWindow, w - FRAME_HORIZONTAL_THICKNESS, h - FRAME_VERTICAL_THICKNESS); 66 | XMoveWindow(display, mw->resizer, w - RESIZE_CONTROL_SIZE - FRAME_RIGHT_THICKNESS, h - RESIZE_CONTROL_SIZE - FRAME_BOTTOM_THICKNESS); 67 | } 68 | 69 | static void lowerAllWindowsInPool(Display *display, ManagedWindowPool *pool, GC gc) { 70 | ManagedWindow *this; 71 | SLIST_FOREACH(this, &pool->windows, entries) { 72 | if (this != pool->active) { 73 | XWindowAttributes attr; 74 | XGetWindowAttributes(display, this->decorationWindow, &attr); 75 | DRAW_ACTION(display, this->decorationWindow, { 76 | drawDecorations(display, this->decorationBuffer, gc, this->title, attr, 0); 77 | }); 78 | XGrabButton(display, 0, AnyModifier, this->actualWindow, 0, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None); 79 | } 80 | } 81 | } 82 | 83 | static void focusWindow(Display *display, ManagedWindow *mw, GC gc, ManagedWindowPool *pool) { 84 | pool->active = mw; 85 | lowerAllWindowsInPool(display, pool, gc); 86 | XRaiseWindow(display, mw->decorationWindow); 87 | XUngrabButton(display, 0, AnyModifier, mw->actualWindow); 88 | 89 | /* If the window is collapsed, move input focus to the decoration window */ 90 | Window windowToFocus = mw->collapsed ? mw->decorationWindow : mw->actualWindow; 91 | XSetInputFocus(display, windowToFocus, RevertToNone, CurrentTime); 92 | } 93 | 94 | static void collapseWindow(Display *display, ManagedWindowPool *pool, ManagedWindow *mw, GC gc) { 95 | XWindowAttributes attr; 96 | 97 | XGetWindowAttributes(display, mw->decorationWindow, &attr); 98 | 99 | if (mw->collapsed) { 100 | /* collapsed, uncollapse it */ 101 | XResizeWindow(display, mw->decorationWindow, mw->last_w, mw->last_h); 102 | attr.height = mw->last_h; 103 | XMapWindow(display, mw->actualWindow); 104 | 105 | /* Redraw Resizer */ 106 | drawResizeButton(display, mw->resizer, gc, RECT_RESIZE_DRAW); 107 | XRaiseWindow(display, mw->resizer); 108 | 109 | mw->collapsed = 0; 110 | } 111 | else { 112 | /* normal, collapse it */ 113 | mw->last_w = attr.width; 114 | mw->last_h = attr.height; 115 | XResizeWindow(display, mw->decorationWindow, attr.width, COLLAPSED_THICKNESS); 116 | attr.height = COLLAPSED_THICKNESS; 117 | XUnmapWindow(display, mw->actualWindow); 118 | 119 | mw->collapsed = 1; 120 | } 121 | 122 | drawDecorations(display, mw->decorationBuffer, gc, mw->title, attr, 1); 123 | focusWindow(display, mw, gc, pool); 124 | } 125 | 126 | static void maximizeWindow(Display *display, ManagedWindow *mw, GC gc) { 127 | XSizeHints attr; 128 | XSizeHints container; 129 | long supplied_return; 130 | long supplied_return_container; 131 | 132 | XGetWMNormalHints(display, mw->actualWindow, &attr, &supplied_return); 133 | XGetWMNormalHints(display, mw->decorationWindow, &container, &supplied_return_container); 134 | 135 | int max_w = (supplied_return | PMaxSize && attr.max_width) ? attr.max_width : XDisplayWidth(display, DefaultScreen(display)); 136 | int max_h = (supplied_return | PMaxSize && attr.max_height) ? attr.max_height : XDisplayHeight(display, DefaultScreen(display)); 137 | 138 | if (mw->last_h || mw->last_w || mw->last_x || mw->last_y) { 139 | XMoveWindow(display, mw->decorationWindow, mw->last_x, mw->last_y); 140 | resizeWindow(display, mw, mw->last_w, mw->last_h); 141 | 142 | mw->last_h = 0; 143 | mw->last_w = 0; 144 | mw->last_x = 0; 145 | mw->last_y = 0; 146 | } 147 | else { /* if we aren't at max size, and there is one, go to it */ 148 | mw->last_h = attr.height + FRAME_VERTICAL_THICKNESS; 149 | mw->last_w = attr.width + FRAME_HORIZONTAL_THICKNESS; 150 | mw->last_x = container.x; 151 | mw->last_y = container.y; 152 | 153 | XMoveWindow(display, mw->decorationWindow, 0, NEW_WINDOW_OFFSET); 154 | resizeWindow(display, mw, max_w, max_h - NEW_WINDOW_OFFSET); 155 | } 156 | 157 | /* Get dimensions */ 158 | Window w2; /* unused */ 159 | XWindowAttributes geometry; 160 | XGetGeometry(display, mw->actualWindow, &w2, 161 | (int *)&geometry.x, (int *)&geometry.y, 162 | (unsigned int *)&geometry.width, (unsigned int *)&geometry.height, 163 | (unsigned int *)&geometry.border_width, (unsigned int *)&geometry.depth); 164 | 165 | 166 | DRAW_ACTION(display, mw->decorationWindow, { 167 | drawDecorations(display, mw->decorationBuffer, gc, mw->title, geometry, 1); 168 | }); 169 | } 170 | 171 | static void claimWindow(Display *display, Window window, Window root, GC gc, ManagedWindowPool *pool) { 172 | XSizeHints attr; 173 | long supplied_return = PPosition | PSize | PMinSize; 174 | Window resizer; 175 | 176 | XGetWMNormalHints(display, window, &attr, &supplied_return); 177 | 178 | /* 179 | XMoveWindow(display, window, attr.x, attr.y); 180 | XResizeWindow(display, window, attr.width, attr.height); 181 | 182 | warnx("Trying to reparent %d at {%d, %d, %d, %d} with flags %d\n", window, attr.x, attr.y, attr.width, attr.height, attr.flags); 183 | */ 184 | 185 | Window deco = decorateWindow(display, window, root, gc, attr.x, attr.y, attr.width, attr.height, &resizer); 186 | 187 | /* 188 | XMoveWindow(display, deco, XDisplayWidth(display, DefaultScreen(display)) - attr.width - 3, NEW_WINDOW_OFFSET); 189 | */ 190 | 191 | /* Start listening for events on the window */ 192 | /* FIXME: is this where focus events should be listened to? */ 193 | XSelectInput(display, window, SubstructureNotifyMask | ExposureMask); 194 | XSelectInput(display, deco, ExposureMask); 195 | XSelectInput(display, resizer, ExposureMask); 196 | 197 | pool->active = addWindowToPool(display, deco, window, resizer, pool); 198 | focusWindow(display, pool->active, gc, pool); 199 | 200 | if (supplied_return & PMinSize) { 201 | pool->active->min_w = attr.min_width; 202 | pool->active->min_h = attr.min_height; 203 | } 204 | } 205 | 206 | static void unclaimWindow(Display *display, Window window, ManagedWindowPool *pool) { 207 | ManagedWindow *mw = managedWindowForWindow(display, window, pool); 208 | if (mw) { 209 | undecorateWindow(display, mw->decorationWindow, mw->resizer); 210 | decorationWindowDestroyed = mw->decorationWindow; 211 | resizerDestroyed = mw->resizer; 212 | removeWindowFromPool(display, mw, pool); 213 | } 214 | } 215 | 216 | static void redrawButtonState(int *stateToken, decorationFunction buttonFunction, Display *display, Drawable window, GC gc, int px, int py, int bx, int by, int bw, int bh) { 217 | /* 218 | * Zero is up state (default). So, a zero initialized stateToken shold always 219 | * reflect the correct state for a button that has never seen this function. 220 | */ 221 | int newState = pointIsInRect(px, py, bx, by, bw, bh); 222 | 223 | if (newState != *stateToken) { 224 | if (newState) { 225 | drawCloseButtonDown(display, window, gc, bx, by, bw, bh); 226 | } 227 | else { 228 | /* This ensures the the button is redrawn unclicked */ 229 | buttonFunction(display, window, gc, bx, by, bw, bh); 230 | } 231 | 232 | *stateToken = newState; 233 | } 234 | } 235 | 236 | static void claimAllWindows(Display *display, Window root, ManagedWindowPool *pool) { 237 | /* This should only be called once, and only on startup */ 238 | static int once; 239 | assert(!once++); 240 | 241 | Window parent; 242 | Window *children; 243 | unsigned int nchildren; 244 | 245 | XQueryTree(display, root, &root, &parent, &children, &nchildren); 246 | 247 | unsigned int i; 248 | for (i = 0; i < nchildren; i++) { 249 | if (children[i] && children[i] != root) { 250 | GC gc = XCreateGC(display, children[i], 0, 0); 251 | claimWindow(display, children[i], root, gc, pool); 252 | XFlush(display); 253 | XFreeGC(display, gc); 254 | } 255 | else { 256 | warnx("Could not find window with XID:%ld\n", children[i]); 257 | } 258 | } 259 | 260 | XFree(children); 261 | } 262 | 263 | int main (int argc, const char * argv[]) { 264 | (void)argc; 265 | (void)argv; 266 | 267 | Display *display; 268 | XEvent ev; 269 | int screen; 270 | XWindowAttributes attr; 271 | XButtonEvent start = {0}; 272 | MouseDownState downState = MouseDownStateUnknown; 273 | time_t lastClickTime = 0; 274 | Window lastClickWindow = 0; 275 | 276 | ManagedWindowPool *pool = createPool(); 277 | 278 | /* Set up */ 279 | display = XOpenDisplay(getenv("DISPLAY")); 280 | if (!display) { 281 | errx(EX_UNAVAILABLE, "Failed to open display, is X running?\n"); 282 | } 283 | 284 | screen = DefaultScreen(display); 285 | 286 | /* Find the window */ 287 | Window root = RootWindow(display, screen); 288 | 289 | /* Initial capture of all windows on startup */ 290 | claimAllWindows(display, root, pool); 291 | 292 | XSelectInput(display, root, StructureNotifyMask | SubstructureNotifyMask /* CreateNotify */ | ButtonPressMask); 293 | 294 | while(XNextEvent(display, &ev) == 0) { 295 | /* 296 | warnx("Got event \"%s\"\n", event_names[ev.type]); 297 | */ 298 | if (ev.xany.window == decorationWindowDestroyed || ev.xany.window == resizerDestroyed) { 299 | continue; 300 | } 301 | 302 | /* This is a collection of everything that should short-circuit */ 303 | switch(ev.type) { 304 | case DestroyNotify: 305 | /* 306 | * NOTE: The XDestroyWindowEvent structure is tricky. 307 | * ev.xany.window lines up with ev.xdestroywindow.event, 308 | * because xdestroywindow.event is the window being 309 | * destroyed, while xdestroywindow.window is used for some 310 | * other toolkit nonsense. 311 | */ 312 | unclaimWindow(display, ev.xdestroywindow.event, pool); 313 | case UnmapNotify: 314 | case ReparentNotify: 315 | case CreateNotify: 316 | case ConfigureNotify: 317 | case PropertyNotify: 318 | /* 319 | * These are intentionally unhandled notifications that are 320 | * caught in the structure notification masks. So, don't 321 | * let the default case log them. 322 | */ 323 | continue; 324 | 325 | case ButtonPress: { 326 | if (ev.xkey.subwindow == None) { 327 | continue; 328 | } 329 | } break; 330 | case Expose: { 331 | if(downState == MouseDownStateResize) { 332 | continue; 333 | } 334 | } break; 335 | case MapNotify: { 336 | if (managedWindowForWindow(display, ev.xmap.window, pool) || ev.xmap.override_redirect) { 337 | continue; 338 | } 339 | } break; 340 | } 341 | 342 | GC gc = XCreateGC(display, ev.xany.window, 0, 0); 343 | switch (ev.type) { 344 | case ButtonPress: { 345 | ManagedWindow *mw = managedWindowForWindow(display, ev.xkey.subwindow, pool); 346 | if (!mw) { 347 | break; 348 | } 349 | 350 | /* Raise and activate the window, while lowering all others */ 351 | focusWindow(display, mw, gc, pool); 352 | 353 | /* Redraw the decorations, just in case the focus changed */ 354 | XGetWindowAttributes(display, mw->decorationWindow, &attr); 355 | drawDecorations(display, mw->decorationWindow, gc, mw->title, attr, 1); 356 | 357 | /* 358 | * These x,y assignments cannot be consolidated with the other 359 | * ones, since these don't recycle the previous attr, but the 360 | * other ones do. 361 | */ 362 | const int x = ev.xbutton.x_root - attr.x; 363 | const int y = ev.xbutton.y_root - attr.y; 364 | 365 | /* Check what was downed */ 366 | downState = MouseDownStateUnknown; 367 | if (pointIsInRect(x, y, RECT_TITLEBAR)) { 368 | downState = MouseDownStateMove; 369 | /* Grab the pointer */ 370 | XGrabPointer(display, ev.xbutton.subwindow, True, 371 | PointerMotionMask|ButtonReleaseMask, GrabModeAsync, 372 | GrabModeAsync, None, None, CurrentTime); 373 | start = ev.xbutton; 374 | } 375 | if (pointIsInRect(x, y, RECT_CLOSE_BTN)) { 376 | drawCloseButtonDown(display, mw->decorationWindow, gc, RECT_CLOSE_BTN); 377 | downState = MouseDownStateClose; 378 | } 379 | if (pointIsInRect(x, y, RECT_MAX_BTN)) { 380 | drawCloseButtonDown(display, mw->decorationWindow, gc, RECT_MAX_BTN); 381 | downState = MouseDownStateMaximize; 382 | lastClickTime = 0; 383 | } 384 | #if COLLAPSE_BUTTON_ENABLED 385 | if (pointIsInRect(x, y, RECT_COLLAPSE_BTN)) { 386 | drawCloseButtonDown(display, mw->decorationWindow, gc, RECT_COLLAPSE_BTN); 387 | downState = MouseDownStateCollapse; 388 | lastClickTime = 0; 389 | } 390 | #endif 391 | if (!mw->collapsed && 392 | (ev.xbutton.subwindow == mw->resizer || pointIsInRect(x, y, RECT_RESIZE_BTN))) { 393 | /* Grab the pointer */ 394 | XGrabPointer(display, ev.xbutton.subwindow, True, 395 | PointerMotionMask|ButtonReleaseMask, GrabModeAsync, 396 | GrabModeAsync, None, None, CurrentTime); 397 | start = ev.xbutton; 398 | lastClickTime = 0; 399 | downState = MouseDownStateResize; 400 | } 401 | } break; 402 | case Expose: { 403 | ManagedWindow *mw = managedWindowForWindow(display, ev.xexpose.window, pool); 404 | 405 | if (mw) { 406 | /* TODO: This probably performs terribly, replace me with some caching */ 407 | XWindowAttributes geometry; 408 | Window w2; /* unused */ 409 | XGetGeometry(display, mw->decorationWindow, &w2, 410 | (int *)&geometry.x, (int *)&geometry.y, 411 | (unsigned int *)&geometry.width, (unsigned int *)&geometry.height, 412 | (unsigned int *)&geometry.border_width, (unsigned int *)&geometry.depth); 413 | 414 | /* Redraw titlebar based on active or not */ 415 | DRAW_ACTION(display, mw->decorationWindow, { 416 | drawDecorations(display, mw->decorationBuffer, gc, mw->title, geometry, (mw == pool->active)); 417 | }); 418 | 419 | /* Redraw Resizer */ 420 | drawResizeButton(display, mw->resizer, gc, RECT_RESIZE_DRAW); 421 | } 422 | } break; 423 | case MotionNotify: { 424 | /* Invalidate double clicks */ 425 | lastClickTime = 0; 426 | 427 | /* If we have a bunch of MotionNotify events queued up, */ 428 | /* drop all but the last one, since all math is relative */ 429 | while(XCheckTypedEvent(display, MotionNotify, &ev)); 430 | 431 | const int x = ev.xbutton.x_root - attr.x; 432 | const int y = ev.xbutton.y_root - attr.y; 433 | 434 | const int dx = ev.xbutton.x_root - start.x_root; 435 | const int dy = ev.xbutton.y_root - start.y_root; 436 | 437 | switch (downState) { 438 | case MouseDownStateResize: { 439 | ManagedWindow *mw = managedWindowForWindow(display, start.subwindow, pool); 440 | 441 | /* Resize */ 442 | resizeWindow(display, mw, attr.width + dx, attr.height + dy); 443 | start.x_root = ev.xbutton.x_root; 444 | start.y_root = ev.xbutton.y_root; 445 | 446 | /* Persist that info for next iteration */ 447 | attr.width += dx; 448 | attr.height += dy; 449 | 450 | /* Redraw Titlebar */ 451 | DRAW_ACTION(display, mw->decorationWindow, { 452 | drawDecorations(display, mw->decorationBuffer, gc, mw->title, attr, 1); 453 | }); 454 | 455 | /* Redraw Resizer */ 456 | drawResizeButton(display, mw->resizer, gc, RECT_RESIZE_DRAW); 457 | } break; 458 | case MouseDownStateMove: { 459 | XMoveWindow(display, ev.xmotion.window, attr.x + dx, attr.y + dy); 460 | } break; 461 | case MouseDownStateClose: { 462 | static int closeButtonStateToken; 463 | redrawButtonState(&closeButtonStateToken, drawCloseButton, display, ev.xmotion.window, gc, x, y, RECT_CLOSE_BTN); 464 | } break; 465 | case MouseDownStateMaximize: { 466 | static int maximizeButtonStateToken; 467 | redrawButtonState(&maximizeButtonStateToken, drawMaximizeButton, display, ev.xmotion.window, gc, x, y, RECT_MAX_BTN); 468 | } break; 469 | #if COLLAPSE_BUTTON_ENABLED 470 | case MouseDownStateCollapse: { 471 | static int collapseButtonStateToken; 472 | redrawButtonState(&collapseButtonStateToken, drawCollapseButton, display, ev.xmotion.window, gc, x, y, RECT_COLLAPSE_BTN); 473 | } break; 474 | #endif 475 | default: 476 | break; 477 | } 478 | 479 | /* Syncing is required to properly consolidate MotionEvents */ 480 | XSync(display, False); 481 | } break; 482 | case ButtonRelease: { 483 | XUngrabPointer(display, CurrentTime); 484 | 485 | const int x = ev.xbutton.x_root - attr.x; 486 | const int y = ev.xbutton.y_root - attr.y; 487 | 488 | switch (downState) { 489 | case MouseDownStateClose: { 490 | drawCloseButton(display, ev.xmotion.window, gc, RECT_CLOSE_BTN); 491 | 492 | if (pointIsInRect(x, y, RECT_CLOSE_BTN)) { 493 | unclaimWindow(display, ev.xmotion.window, pool); 494 | } 495 | } break; 496 | #if COLLAPSE_BUTTON_ENABLED 497 | case MouseDownStateCollapse: { 498 | drawCollapseButton(display, ev.xmotion.window, gc, RECT_COLLAPSE_BTN); 499 | 500 | if (pointIsInRect(x, y, RECT_COLLAPSE_BTN)) { 501 | ManagedWindow *mw = managedWindowForWindow(ev.xmotion.window, pool); 502 | collapseWindow(display, pool, mw, gc); 503 | lastClickTime = 0; 504 | } 505 | } break; 506 | #endif 507 | case MouseDownStateMaximize: { 508 | drawMaximizeButton(display, ev.xmotion.window, gc, RECT_MAX_BTN); 509 | 510 | if (pointIsInRect(x, y, RECT_MAX_BTN)) { 511 | ManagedWindow *mw = managedWindowForWindow(display, ev.xmotion.window, pool); 512 | maximizeWindow(display, mw, gc); 513 | } 514 | } break; 515 | default: { /* Anywhere else on the titlebar */ 516 | if (ev.xkey.window != None) { 517 | ManagedWindow *mw = managedWindowForWindow(display, ev.xkey.window, pool); 518 | 519 | if (lastClickTime >= (time(NULL) - 1) && lastClickWindow == mw->decorationWindow) { 520 | collapseWindow(display, pool, mw, gc); 521 | lastClickTime = 0; 522 | } 523 | else { 524 | lastClickWindow = mw->decorationWindow; 525 | time(&lastClickTime); 526 | } 527 | } 528 | } break; 529 | } 530 | } break; 531 | case MapNotify: { 532 | if (!ev.xmap.window) { 533 | warnx("Recieved invalid window for event \"%s\"\n", event_names[ev.type]); 534 | } 535 | claimWindow(display, ev.xmap.window, root, gc, pool); 536 | } break; 537 | default: { 538 | warnx("Recieved unhandled event \"%s\"\n", event_names[ev.type]); 539 | } break; 540 | } 541 | 542 | XFreeGC(display, gc); 543 | } 544 | 545 | XCloseDisplay(display); 546 | destroyPool(pool); 547 | 548 | return 0; 549 | } 550 | -------------------------------------------------------------------------------- /pool.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Daniel Loffgren 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | 26 | #include "pool.h" 27 | 28 | ManagedWindowPool *createPool(void) { 29 | ManagedWindowPool *pool = calloc(1, sizeof(ManagedWindowPool)); 30 | assert(pool); 31 | return pool; 32 | } 33 | 34 | void updateWindowTitle(Display *display, ManagedWindow *mw) { 35 | if (mw->title) { 36 | XFree(mw->title); 37 | mw->title = NULL; 38 | } 39 | XFetchName(display, mw->actualWindow, &(mw->title)); 40 | } 41 | 42 | ManagedWindow *addWindowToPool(Display *display, Window decorationWindow, Window actualWindow, Window resizer, ManagedWindowPool *pool) { 43 | ManagedWindow *mw = calloc(1, sizeof(ManagedWindow)); 44 | assert(mw); 45 | 46 | mw->resizer = resizer; 47 | mw->actualWindow = actualWindow; 48 | mw->decorationWindow = decorationWindow; 49 | mw->decorationBuffer = XdbeAllocateBackBufferName(display, decorationWindow, XdbeCopied); 50 | updateWindowTitle(display, mw); 51 | 52 | SLIST_INSERT_HEAD(&pool->windows, mw, entries); 53 | 54 | return mw; 55 | } 56 | 57 | void removeWindowFromPool(Display *display, ManagedWindow *managedWindow, ManagedWindowPool *pool) { 58 | (void)display; 59 | SLIST_REMOVE(&pool->windows, managedWindow, ManagedWindow_t, entries); 60 | 61 | /* FIXME: I need to be dealloced before the decoration window */ 62 | /* 63 | XdbeDeallocateBackBufferName(display, this->decorationBuffer); 64 | */ 65 | XFree(managedWindow->title); 66 | free(managedWindow); 67 | } 68 | 69 | ManagedWindow *managedWindowForWindow(Display *display, Window window, ManagedWindowPool *pool) { 70 | ManagedWindow *this; 71 | SLIST_FOREACH(this, &pool->windows, entries) { 72 | if (this->decorationWindow == window || this->actualWindow == window) { 73 | return this; 74 | } 75 | 76 | unsigned int nchildren; 77 | Window *children; 78 | Window parent; 79 | Window root; 80 | XQueryTree(display, this->actualWindow, &root, &parent, &children, &nchildren); 81 | for (int i = 0; i < nchildren; i++) { 82 | if (children[i] == window) { 83 | return this; 84 | } 85 | } 86 | } 87 | return NULL; 88 | } 89 | 90 | void destroyPool(ManagedWindowPool *pool) { 91 | ManagedWindow *this; 92 | while (!SLIST_EMPTY(&pool->windows)) { 93 | this = SLIST_FIRST(&pool->windows); 94 | SLIST_REMOVE_HEAD(&pool->windows, entries); 95 | free(this); 96 | } 97 | free(pool); 98 | } 99 | 100 | #ifdef DEBUG 101 | #include 102 | 103 | void printPool(ManagedWindowPool *pool) { 104 | fprintf(stderr, "ManagedWindowPool %p {\n", pool); 105 | ManagedWindow *this; 106 | SLIST_FOREACH(this, &pool->windows, entries) { 107 | fprintf(stderr, "\tManagedWindow %p {\n", this); 108 | fprintf(stderr, "\t\tdecorationWindow = %lu,\n", this->decorationWindow); 109 | fprintf(stderr, "\t\tactualWindow = %lu,\n", this->actualWindow); 110 | fprintf(stderr, "\t\tresizer = %lu,\n", this->resizer); 111 | fprintf(stderr, "\t}\n"); 112 | } 113 | fprintf(stderr, "}\n"); 114 | } 115 | #endif 116 | -------------------------------------------------------------------------------- /pool.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Daniel Loffgren 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #ifndef classic_wm_pool_h 24 | #define classic_wm_pool_h 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | struct ManagedWindow_t { 31 | Window decorationWindow; 32 | XdbeBackBuffer decorationBuffer; 33 | Window actualWindow; 34 | Window resizer; 35 | 36 | /* The last_ members are for collapsing and maximizing only */ 37 | unsigned int last_w; 38 | unsigned int last_h; 39 | unsigned int last_x; 40 | unsigned int last_y; 41 | 42 | /* The minimum size hints are cached for performance */ 43 | unsigned int min_w; 44 | unsigned int min_h; 45 | 46 | SLIST_ENTRY(ManagedWindow_t) entries; 47 | char *title; 48 | 49 | int collapsed; 50 | }; 51 | 52 | typedef struct ManagedWindow_t ManagedWindow; 53 | 54 | struct ManagedWindowPool_t { 55 | SLIST_HEAD(windowlist, ManagedWindow_t) windows; 56 | ManagedWindow *active; 57 | }; 58 | 59 | typedef struct ManagedWindowPool_t ManagedWindowPool; 60 | 61 | ManagedWindowPool *createPool(void); 62 | ManagedWindow *addWindowToPool(Display *display, Window decorationWindow, Window actualWindow, Window resizer, ManagedWindowPool *pool); 63 | void removeWindowFromPool(Display *display, ManagedWindow *managedWindow, ManagedWindowPool *pool); 64 | void destroyPool(ManagedWindowPool *pool); 65 | ManagedWindow *managedWindowForWindow(Display *display, Window window, ManagedWindowPool *pool); 66 | void printPool(ManagedWindowPool *pool); 67 | void updateWindowTitle(Display *display, ManagedWindow *mw); 68 | 69 | #endif 70 | --------------------------------------------------------------------------------