From: Michael D. Lowis Date: Mon, 6 May 2019 02:49:01 +0000 (-0400) Subject: initial commit X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=3ddaa5c1500d0986700592ed2f10d19e81c34868;p=proto%2Flwm.git initial commit --- 3ddaa5c1500d0986700592ed2f10d19e81c34868 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..b26d002 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +enh Elliott Hughes ehughes@bluearc.com +jfc James Carter james@jfc.org.uk diff --git a/BUGS b/BUGS new file mode 100644 index 0000000..1e31dd9 --- /dev/null +++ b/BUGS @@ -0,0 +1,33 @@ +we're possibly being a bit overzealous about maintaining the window stack, +with lots of flickering windows as a result. it's particularly noticeable +when you're running a desktop - but then that's a stupid thing to do anyway. +not sure how to fix it without maintaining a lot more data about the window +stack. + +reported by corey holcomb-hockin: lwm producues messages like this when popups appear: +lwm: protocol request X_ConfigureWindow on resource 0xa0000e failed: +BadWindow (invalid Window parameter) +lwm: protocol request X_SendEvent on resource 0xa0000e failed: BadWindow +(invalid Window parameter) +lwm: protocol request X_ConfigureWindow on resource 0xa0000e failed: +BadWindow (invalid Window parameter) +lwm: protocol request X_SendEvent on resource 0xa0000e failed: BadWindow +(invalid Window parameter) +lwm: protocol request X_SetInputFocus on resource 0xa0000e failed: +BadWindow (invalid Window parameter) +lwm: protocol request X_SetInputFocus on resource 0x8000de failed: +BadMatch (invalid parameter attributes) + +martin and elliott still have window problems with edit, although it's +better than it was. apparently the window is now placed correctly, +but gets larger between successive runs. i can't reproduce this, +unfortunately. + +chris reports that positioning a window on the bottom or right edge of a screen +and then resizing the edge opposite to that touching the edge of the screen +causes the window to jump in 1 unit (pixel or character depending on the +application). also, and possibly related, holding button 1 to resize any edge +apart from the right, and then moving the mouse along that edge (ie. +perpendicular to the normal movement for resizing) causes the window to shrink +one unit. + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..dcfa4c2 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..0bc30a0 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,486 @@ +Change Log for "lwm" + +2016-02-12 jfc York + Patches from Greg Kenneky and other fixes for fullscreen windows. + + Released lvm-1.2.4. + +2013-07-09 jfc York + Applied a couple of minor patches suggested by Jari Aalto, the + Debian package maintainer. + + Released lwm-1.2.3. + + +2009-11-24 jfc York + Released lwm-1.2.2. + +2009-11-20 jfc York + Improved performance by only checking for pending X events when the + socket it ready for reading. + + Fixed applyGravity() bug that caused frameless windows to be + mis-positioned. + + Applied a workaround in destroy() to avoid error reports when closing + windows. + +2005-01-28 jfc York + + Applied a patch from Chris Reece that ensures that the popup + menu does not disappear off the bottom of the screen. + +2004-09-30 jfc York + + Fixed an issue with IRIX 6.5 and lwm, where the root menu could + not be used because motion events had coordinates with respect to + the popup, not the root. Fixed by explicitly using the root window + coordinates. + + Released lwm-1.2.1. + +2004-09-28 jfc York + + Added missing -lSM to no_xmkmf_makefile. + +2003-12-09 jfc York + + Fixed bug (reported by Matthew Wilcox) where windows with extremely + long names could cause the pop menu to be unusable. Fixed by + maintaining a separate, shortened name for the menu, if + necessary ("this is a very very [...] ry long window name"). This + takes no account of UTF-8 names as yet. + +2003-12-08 jfc York + + Fixed bug (reported by Eugene Wong) where resizing the top of + a window would cause it to jump up several pixels. The height of + the titlebar was not being considered when calculating mouse + motion in reshaping_motionnotify(). + + Modified manage() to avoid autoplacing windows during + initialisation. + + Released lwm-1.2.0. + +2003-12-03 jfc York + + Applied patch from Elliott that gives focus to new windows in + click-to-focus mode. + + Changed the buttonpress code in disp.c to ignore scroll wheel + "clicks". + + Modified Client_Remove so that, in click-to-focus mode, it + refocuses on the most sensible window (either the top window, + or the window that the closing window was a transient for). + + Attempted to fix the edit placement bug by adding titleHeight() + to the supplied X coordinate during a ConfigureRequest event, + and not attempting to fix clients that don't supply a border + width during a configure request. + + Fixed fullscreen-mode bug where galeon windows appeared to jump + up and to the left after the first click. + + Released lwm-1.1.7. + +2003-11-28 jfc York + + Changed the behaviour when unhiding a window in click-to-focus mode. + An unhidden window now automatically gets focus in this mode. + + Fixed a small bug in the session management code that could + cause a crash when lwm quit. + + Moved a call to ewmh_set_client_list make before ewmh was + initialised. + + Changed lwm's behaviour when minimising windows. Button three must + now be pressed and released before the window is hidden (or + moved to the bottom of the stack). This ensures that lwm swallows + all the events generated during the operation, and allows the user + to back out of the operation by moving the mouse out of the window + before releasing the button. + + Removed include of Xm/MwmUtil.h in manage.c, and the HAVE_MOTIF + kludge from the Imakefile, in favour of copying the few lines + that are required from Xm/MwmUtil.h (LessTif, so hopefully no + licensing issues). + + Fixed bug that caused the last cursor displayed in a frame + to be incorrectly used when moving into the frame when the + root menu was on screen. This is done by brute force - + see Client_ResetAllCursors(). + + Released lwm-1.1.6. + +2003-11-26 jfc York + + Fixed bug in Client_MakeSane that caused occasional crashes + during window moves/resizing. Should investigate why it occasionally + gets called with a NULL client. + +2003-11-03 jfc York + + Added an entry for LeaveNotify in the dispatch table (disp.c). + +2003-08-13 jfc York + + Removed "error" message when lwm fails to connect to a session + manager. This isn't actually an error and the message is confusing. + +2003-08-01 jfc York + + Fixed bug that allowed clients to grab the focus and confuse lwm. + + Cleaned up the code for raising and lowering clients, and added + code to prevent a client from being raised above its transients. + + Retired disp.old, and CLOSE_PATCH.txt. + + Added an edge resistance to the workarea, so that window may + be moved to the edge of the workarea without precise mousing, + as requested by MAD. EDGE_RESIST in lwm.h defines the number of + pixels of resistance and may be safely set to zero. + + Released lwm-1.1.5. + +2003-07-31 jfc York + + In click-to-focus mode, always draw the box in the frame. + +2003-07-29 jfc York + + Added a click-to-focus mode. The default remains (sloppy) + enter-to-focus. + + Released lwm-1.1.4. + + +2003-07-28 jfc York + + Updated no_xmkmf_makefile to reflect the changes made since 1.01. + +2003-07-10 jfc York + + Fixed a bug in manage.c than prevented lwm compiling on systems + with no variety of Motif installed. If this means you, remove + _DHAVE_MOTIF from Imakefile. + + Released lwm-1.1.3. + +2003-07-08 jfc York + + Added support for NET_MOVERESIZE, but I cannot find any + applications the want to use it, apart from the keyboard + variants. I don't know what to do about the keyboard move/resize. + +2003-07-03 jfc York + + Fixed a few buglets thrown up by running lwm through the compiler + with all warnings on. + +2003-07-02 jfc York + + In Client_MakeSane(), added a check to prevent windows being + moved into a position where they might be completely obscured + by panels/docks. + + Changed ewmh_set_strut() to run Client_MakeSane() across all + clients when the work area changes. This avoids clients getting + lost behind panels/docks. + + Added support for _NET_WM_STATE_ABOVE and + _NET_WM_STATE_BELOW. Added fix_stack() to maintain the window + stack as dictated by the EWMH spec. + +2003-07-01 jfc York + + Added support for _NET_WM_STRUT. lwm now maintains _NET_WORKAREA + correctly, and takes the reserved space into account in its + window placing algorithm. + + Released lwm-1.1.2. + +2003-06-30 jfc York + + Fixed bug that caused tk menus to be badly placed placed by + sending a configure notify where appropriate in setactive(). + + Removed compile time option of prepending window title's with + the client machines's name (PREPEND_CLIENT_MACHINE). + + Added i18n support for window titles, using UTF8 names from + _NET_WM_NAME where available and supported (ie XFree86). + + Added code in disp.c to change the pointer in some areas of the + frame to indicate the action taken by button1. I didn't allow the + "move" pointer in the titlebar because it looked nasty. Added + the xkill pointer for the the box. This was a TODO item. + +2003-06-28 jfc York + + Added GPL headers to all the source files. + + Released lwm-1.1.1. + +2003-06-27 jfc York + + Fixed the bug where each GTK window generated an extra + window when lwm shut down by unmapping all the clients in + Client_FreeAll(). Elliott thinks this is bad magic, and that + the X server should lose the windows, but this doesn't happen + with XFree86. + + Fixed bug, reported by Ed Porter, that caused moving the mouse + wheel to generate xterms. Wheel mice generate button press events + on buttons 4 and 5 and shell() wasn't taking this into account. + + Fixed silly bug in motifWouldDecorate(): windows should have a + frame is MWM_DECOR_ALL is set. + +2003-06-26 jfc York + + Shaped windows now work again. I'm not sure what I changed + to break it, but the fix was to process shaped windows in + scanWindowTree (they were previously ignored). They had to be + clients anyway, if they were to appear in _NET_CLIENT_LIST. + +2003-06-25 jfc York + + Fixed bug that caused frameless windows to be immoveable. + + In manage.c, allowed lwm to fall back on Motif hints when + deciding if a window should have a frame, if _NET_WM_WINDOW_TYPE + is not set. This breaks the EWMH spec, in that a window + without _NET_WM_WINDOW_TYPE should be assumed to have + _NET_WM_WINDOW_TYPE_NORMAL, but it's the only way for older + apps to indicate that they don't want decorating, and in the + absence of Motif hints the default state is + _NET_WM_WINDOW_TYPE_NORMAL. + +2003-06-24 jfc York + + Fixed the following TODO item: + allow users to back out of closing a window if + they leave the box before letting go of the button. + Implemented by adding an extra wm_closing_window mode rather + than adding to the Client structure, as per AMidthune's + patch. Not sure which is the better solution, though. + + Added initial support for _NET_WM_STATE, but only for + _NET_WM_STATE_SKIP_TASKBAR, _NET_WM_STATE_SKIP_PAGER and + _NET_WM_HIDDEN. + + Added simple hardwired _NET_WM_ALLOWED_ACTIONS support, and + support for the _NET_CLOSE_WINDOW client message. + + First attempt an _WM_STATE_FULLSCREEN and a full-screen mode. + It's not quite right yet, but useable. + + +2003-06-23 jfc York + + Fixed some silly bugs in the session management code. + + Added initial EWMH code using the 1.2 spec: + http://www.freedesktop.org/standards/wm-spec/1.2/html/ + Initial support covers the mechanisms for announcing support + for EWMH (_NET_SUPPORTED, _NET_SUPPORTING_WM_CHECK), the + client list and active client (_NET_CLIENT_LIST and + _NET_ACTIVE_WINDOW), and the window type (_NET_WM_WINDOW_TYPE). + Windows may now be frameless if their window type indicates. + +2003-06-21 jfc York + + Added session management so that GNOME2's gnome-session does + not wait a long timeout when starting the window manager. + +2000-02-08 enh Basel + + Tried out a patch from Robert Bauer so that it's possible to move + windows with button 1, if you're in the ``titlebar'' (i.e. not touching + the top border). This makes it easier for Windows users to cope + with lwm, and easier for those with two-button mice (or laptops) + too. At the moment, "mv disp.old disp.c" will give back the old + behaviour. + +1999-11-11 enh Basel + + Fixed a cut-and-paste bug in client.c that made the check for + a window being too large or too small wrong. This bug was found + by Mike Meyer. + +1999-09-22 enh Basel + + Altered the button-press code so that it's now easier for unhappy + users to alter which button performs which function. Simply edit + lwm.h and modify the three relevant #define statements. + +1999-07-19 enh Basel + + Added a handler for circulation events so that other programs + can offer "Alt-Tab" functionality. + +1999-07-08 enh Basel + + Fixed the cosmetic problem with titlebars of dialogue boxes. If + this looks to be OK, I can think about another lwm release. + +1999-06-10 enh Basel + + Incorporated bug fix by Adrian Colley regarding the attempt in + manage.c to call XSetWindowBorderWidth on an InputOnly window, + and moved the #include of after so that + lwm can compile on Solaris 2.6. Cosmetic change to move the close + box to line up with the client window. The effect is spoilt if the + child insists on drawing a black border around itself, though. + +1999-02-07 enh Basel + + Title-bars no longer pop up and down. An inactive window has a + grey title instead. This means less load on the server, no annoying + "I want to type the information from one window's title-bar into + the current window but can't" syndrome, and a final solution to + the race condition that's been with us since the very beginning. + + The size feedback no longer pops up as soon as you grab a window, + because that made it almost impossible to grab a window without + resizing it. + +1998-11-03 enh Basel + + The size feedback now pops up as soon as you grab a window, + rather than waiting for you to actually move. + +1998-10-06 enh Basel + + Al pointed out that my Sun actually has two framebuffers. One + monitor-lugging later, and I suddenly have a need for a window + manager that can cope with multiple screens. And here it is! + +1998-05-29 enh Basel + + Fixed window minimum/maximum height code so that it no longer + includes the title decoration. Menu now pops down if a window + disappears while the menu is up. + +1998-03-23 enh Basel + + Removed unused constant. A little tidying up, renaming. Some + debugging code removed. The width of the size-feedback window + is now calculated at run-time depending on the size of the screen. + +1998-02-05 enh Basel + + Fixed bug found by Marty Olevitch: lwm's automatic window + placement heuristics broke down when either the right or bottom + of the display were reached. + Changed menu placement to ensure that the menu is fully + on-screen. + +1998-01-06 enh Basel + + Fixed bug found by J. Han whereby lwm dumped core if a window + disappeared while being reshaped. + +1997-09-01 enh Basel + + "Push to back" functionality moved from button 3 click in box + to button 3 click anywhere in frame with Shift held down. + +1997-08-29 enh Basel + + Simple version numbering introduced. + +1997-08-25 enh Basel + + Fixed stupid mistake introduced with the last change, with regard + to setting the input focus. + +1997-08-22 enh Basel + + Xt applications (strictly, applications whose window title is + the same as their class hint resource name) no longer have a + title bar. This means it's more awkward to kill them, but that + they don't have pointless decoration. + +1997-08-07 enh Basel + + Bug related to hiding windows fixed. + +1997-08-06 enh Basel + + The size indictor now has the correct GC settings. Whoops! + Improved handling of WM_NORMAL_HINTS. Amongst other things, + this means that size reporting of xterm et al is more reliable. + +1997-07-31 enh Basel + + Reshaping now uses the popup to display the current width and + height of the window being reshaped (in whatever units it uses). + +1997-07-04 enh Swanwick + + Clicking button 3 on the "box" pushes the window to the bottom. + Changing image in xv no longer causes the window to gravitate to + the southeast. There's an ICCCM convention that clients should + set the border width with each ConfigureWindow request. As usual, + many clients fail to follow this convention. I get the distinct + impression that the very reason for the existance of the Xt + library is because the X11 protocol and ICCCM are so messy and + involved that the only way to make X11 bearable was to write + this code once and for all. The menu code has been rewritten, + changing as a side-effect the order in which hidden windows + appear on the menu. The rewrite now means that the order is very + easy to change for experiments like alphabetical ordering etc. I + like it as it is: a stack. + +1997-06-24 enh York + + Now handles NoExpose events. Better protocol error reporting. + Default minimum size calculation improved. + +1997-06-23 enh York + + Both button 1 and 2 can now have commands associated with them. + See the documentation for details. Windows whose minimum and + maximum sizes are identical can no longer be resized. The + oscillation race condition is now less likely to occur. Some + dead code removed. + +1997-05-25 enh York + + lwm now does the right thing with respect to hidden windows on + exit and startup. a hidden window is now re-hidden if lwm exits + and is then restarted. + +1997-05-21 enh York + + Fixed a bug that meant a client could confuse lwm by remapping + a hidden window: the menu of hidden windows wasn't being updated. + +1997-05-16 enh York + + A bug relating to ConfigureRequests on the current window caused + the title-bar to be redrawn incorrectly. Once again, this came + to light with xv. + + The "New Shell" command has gone from the button 3 menu, and + button 2 now performs this function. + +1997-05-09 enh York + + This version fixes a bug relating to ConfigureRequests. Client + windows that were resized under program control were resized, + but the client was misinformed as to what change had actually + taken place. xv's optimised redraw, for example, missed out on + part of the window because of this. + The behaviour with regard to hidden windows on exit has also + changed. They're now remapped, but lowered in the window stack. + This means that you don't lose them, but that they don't + obliterate the more important windows on your screen if you kill + the window manager. + +- Initial announcement on comp.windows.x.announce - diff --git a/INSTALL b/INSTALL new file mode 100755 index 0000000..32c8967 --- /dev/null +++ b/INSTALL @@ -0,0 +1,11 @@ +#!/bin/cat + +Installing lwm + +1. Compile lwm: + + xmkmf ; make ; strip lwm + +2. Copy the binary to where you want it. + +3. Edit your Xsession or whatever to call lwm. diff --git a/Imakefile b/Imakefile new file mode 100644 index 0000000..da76f1c --- /dev/null +++ b/Imakefile @@ -0,0 +1,29 @@ +XCOMM lwm, a window manager for X11 +XCOMM Copyright (C) 1997-2016 Elliott Hughes, James Carter +XCOMM +XCOMM This program is free software; you can redistribute it and/or +XCOMM modify it under the terms of the GNU General Public License +XCOMM as published by the Free Software Foundation; either version 2 +XCOMM of the License, or (at your option) any later version. +XCOMM +XCOMM This program is distributed in the hope that it will be useful, +XCOMM but WITHOUT ANY WARRANTY; without even the implied warranty of +XCOMM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +XCOMM GNU General Public License for more details. +XCOMM +XCOMM You should have received a copy of the GNU General Public License +XCOMM along with this program; if not, write to the Free Software +XCOMM Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +INCLUDES = -I$(TOP) +DEPLIBS = $(DEPXLIB) $(DEPSMLIB) +LOCAL_LIBRARIES = $(XLIB) $(SMLIB) -lICE +DEFINES = -DSHAPE + +HEADERS = lwm.h ewmh.h +SRCS = lwm.c manage.c mouse.c client.c cursor.c error.c disp.c shape.c resource.c session.c ewmh.c +OBJS = ${SRCS:.c=.o} + +ComplexProgramTarget(lwm) + +${OBJS}: ${HEADERS} diff --git a/README b/README new file mode 100644 index 0000000..6904297 --- /dev/null +++ b/README @@ -0,0 +1,4 @@ +This is lwm, a window manager for X. + +See INSTALL for install instructions and COPYRIGHT for license details. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..d7d3cae --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +lwm +=== + +*lwm* is a window manager for X that tries to keep out of your face. There are no icons, no button bars, no icon docks, no root menus, no nothing: if you want all that, then other programs can provide it. There's no configurability either: if you want that, you want a different window manager; one that helps your operating system in its evil conquest of your disc space and its annexation of your physical memory. + +[http://www.jfc.org.uk/software/lwm.html](http://www.jfc.org.uk/software/lwm.html) + diff --git a/TODO b/TODO new file mode 100644 index 0000000..ef7bb86 --- /dev/null +++ b/TODO @@ -0,0 +1,2 @@ +use XFT extensions where available? + diff --git a/client.c b/client.c new file mode 100644 index 0000000..e7bf09e --- /dev/null +++ b/client.c @@ -0,0 +1,783 @@ +/* + * lwm, a window manager for X11 + * Copyright (C) 1997-2016 Elliott Hughes, James Carter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "lwm.h" +#include "ewmh.h" + +Client *current; +Client *last_focus = NULL; +static Client *clients; + +static int popup_width; /* The width of the size-feedback window. */ + +Edge interacting_edge; + +static void sendClientMessage(Window, Atom, long, long); + +Client * +client_head(void) { + return clients; +} + +void +setactive(Client *c, int on, long timestamp) { + int inhibit; + + if (c == 0 || hidden(c)) + return; + + inhibit = !c->framed; + + if (!inhibit) { + XMoveResizeWindow(dpy, c->parent, + c->size.x, c->size.y - titleHeight(), + c->size.width, c->size.height + titleHeight()); + XMoveWindow(dpy, c->window, border, border + titleHeight()); + sendConfigureNotify(c); + } + + if (on && c->accepts_focus) { + XSetInputFocus(dpy, c->window, RevertToPointerRoot, CurrentTime); + if (c->proto & Ptakefocus) + sendClientMessage(c->window, wm_protocols, + wm_take_focus, timestamp); + if (focus_mode == focus_click) { + XUngrabButton(dpy, AnyButton, AnyModifier, c->window); + } + cmapfocus(c); + } + + /* FIXME: is this sensible? */ + if (on && !c->accepts_focus) { + XSetInputFocus(dpy, None, RevertToPointerRoot, CurrentTime); + } + + if (!on && focus_mode == focus_click) + XGrabButton(dpy, AnyButton, AnyModifier, c->window, False, + ButtonPressMask | ButtonReleaseMask, GrabModeAsync, + GrabModeSync, None, None); + + if (!inhibit) + Client_DrawBorder(c, on); +} + + +void +Client_DrawBorder(Client *c, int active) { + int quarter = (border + titleHeight()) / 4; + + if (c->parent == c->screen->root || c->parent == 0 || + c->framed == False || c->wstate.fullscreen == True) + return; + + XSetWindowBackground(dpy, c->parent, + active ? c->screen->black : c->screen->gray); + XClearWindow(dpy, c->parent); + + /* Draw the ``box''. */ + if (active || focus_mode == focus_click) { + XDrawRectangle(dpy, c->parent, c->screen->gc, + quarter + 2, quarter, 2 * quarter, 2 * quarter); + } + + /* Draw window title. */ + if (c->name != 0) { +#ifdef X_HAVE_UTF8_STRING + if (c->name_utf8 == True) + Xutf8DrawString(dpy, c->parent, font_set, + c->screen->gc, border + 2 + (3 * quarter), + 2 + ascent(font_set_ext), + c->name, c->namelen); + else +#endif + XmbDrawString(dpy, c->parent, font_set, + c->screen->gc, border + 2 + (3 * quarter), + 2 + ascent(font_set_ext), + c->name, c->namelen); + } +} + + +Client * +Client_Get(Window w) { + Client * c; + + if (w == 0 || (getScreenFromRoot(w) != 0)) + return 0; + + /* Search for the client corresponding to this window. */ + for (c = clients; c; c = c->next) + if (c->window == w || c->parent == w) + return c; + + /* Not found. */ + return 0; +} + + +Client * +Client_Add(Window w, Window root) { + Client * c; + + if (w == 0 || w == root) + return 0; + + /* Search for the client corresponding to this window. */ + for (c = clients; c != 0; c = c->next) + if (c->window == w || c->parent == w) + return c; + + c = calloc(1, sizeof *c); + c->window = w; + c->parent = root; + c->framed = False; + c->hidden = False; + c->state = WithdrawnState; + c->internal_state = INormal; + c->cmap = None; + c->name = 0; + c->menu_name = 0; + c->cursor = ENone; + c->wtype = WTypeNone; + c->wstate.skip_taskbar = False; + c->wstate.skip_pager = False; + c->wstate.fullscreen = False; + c->wstate.above = False; + c->wstate.below = False; + c->strut.left = 0; + c->strut.right = 0; + c->strut.top = 0; + c->strut.bottom = 0; + c->ncmapwins = 0; + c->cmapwins = 0; + c->wmcmaps = 0; + c->accepts_focus = 1; + c->next = clients; + + /* Add to head of list of clients. */ + clients = c; + return clients; +} + + +void +Client_Remove(Client *c) { + Client * cc; + ScreenInfo *screen = c->screen; + + if (c == 0) + return; + + /* Remove the client from our client list. */ + if (c == clients) + clients = c->next; + for (cc = clients; cc && cc->next; cc = cc->next) { + if (cc->next == c) + cc->next = cc->next->next; + } + + /* Remove it from the hidden list if it's hidden. */ + if (hidden(c)) { + /* Al Smith points out that you also want to get rid of the menu + * so you can be sure that if you let go on an item, you always + * get the corresponding window. */ + if (mode == wm_menu_up) { + XUnmapWindow(dpy, current_screen->popup); + mode = wm_idle; + } + } + + /* A deleted window can no longer be the current window. */ + if (c == current || (current == NULL && c == last_focus)) { + Client *focus = NULL; + + /* As pointed out by J. Han, if a window disappears while it's + * being reshaped you need to get rid of the size indicator. */ + if (c == current && mode == wm_reshaping) { + XUnmapWindow(dpy, current_screen->popup); + mode = wm_idle; + } + if (focus_mode == focus_click) { + /* Try and find the window that this was a transient + * for, else focus on the top client. */ + if (c->trans != None) { + focus = Client_Get(c->trans); + } + if (!focus) { + Window dw1; + Window dw2; + Window *wins; + unsigned int nwins; + + XQueryTree(dpy, c->screen->root, &dw1, + &dw2, &wins, &nwins); + while (nwins) { + focus = Client_Get(wins[nwins -1]); + if (focus) break; + nwins--; + } + if (wins) XFree(wins); + } + } + Client_Focus(focus, CurrentTime); + } + + if (getScreenFromRoot(c->parent) == 0) + XDestroyWindow(dpy, c->parent); + + if (c->ncmapwins != 0) { + XFree(c->cmapwins); + free(c->wmcmaps); + } + + if (c->name != 0) + free(c->name); + if (c->menu_name != 0) + free(c->name); + + free(c); + + ewmh_set_client_list(screen); + ewmh_set_strut(screen); +} + + +void +Client_MakeSane(Client *c, Edge edge, int *x, int *y, int *dx, int *dy) { + Bool horizontal_ok = True; + Bool vertical_ok = True; + + if (edge != ENone) { + /* + * Make sure we're not making the window too small. + */ + if (*dx < c->size.min_width) + horizontal_ok = False; + if (*dy < c->size.min_height) + vertical_ok = False; + + /* + * Make sure we're not making the window too large. + */ + if (c->size.flags & PMaxSize) { + if (*dx > c->size.max_width) + horizontal_ok = False; + if (*dy > c->size.max_height) + vertical_ok = False; + } + + /* + * Make sure the window's width & height are multiples of + * the width & height increments (not including the base size). + */ + + if (c->size.width_inc > 1) { + int apparent_dx = *dx - 2 * border - c->size.base_width; + int x_fix = apparent_dx % c->size.width_inc; + + switch (edge) { + case ELeft: + case ETopLeft: + case EBottomLeft: + *x += x_fix; + /*FALLTHROUGH*/ + case ERight: + case ETopRight: + case EBottomRight: + *dx -= x_fix; + break; + default: break; + } + } + + if (c->size.height_inc > 1) { + int apparent_dy = *dy - 2 * border - c->size.base_height; + int y_fix = apparent_dy % c->size.height_inc; + + switch (edge) { + case ETop: + case ETopLeft: + case ETopRight: + *y += y_fix; + /*FALLTHROUGH*/ + case EBottom: + case EBottomLeft: + case EBottomRight: + *dy -= y_fix; + break; + default: break; + } + } + + /* + * Check that we may change the client horizontally and vertically. + */ + + if (c->size.width_inc == 0) + horizontal_ok = False; + if (c->size.height_inc == 0) + vertical_ok = False; + } + + /* Ensure that at least one border is not entirely within the + * reserved areas. Keeping clients completely within the + * the workarea is too restrictive, but this measure means they + * should always be accessible. + * Of course all of this is only applicable if the client doesn't + * set a strut itself. jfc + */ + if (c->strut.left == 0 && c->strut.right == 0 && + c->strut.top == 0 && c->strut.bottom == 0) { + if ((int)(*y + border) >= + (int)(c->screen->display_height - + c->screen->strut.bottom)) { + *y = c->screen->display_height - + c->screen->strut.bottom -border; + } + if ((int)(*y + c->size.height - border) <= + (int)c->screen->strut.top) { + *y = c->screen->strut.top + border - c->size.height; + } + if ((int)(*x + border) >= + (int)(c->screen->display_width - + c->screen->strut.right)) { + *x = c->screen->display_width - + c->screen->strut.right -border; + } + if ((int)(*x + c->size.width - border) <= + (int)c->screen->strut.left) { + *x = c->screen->strut.left + border - c->size.width; + } + } + + /* + * Introduce a resistance to the workarea edge, so that windows may + * be "thrown" to the edge of the workarea without precise mousing, + * as requested by MAD. + */ + if (*x < (int)c->screen->strut.left && + *x > ((int)c->screen->strut.left - EDGE_RESIST)) { + *x = (int)c->screen->strut.left; + } + if ((*x + c->size.width) > + (int)(c->screen->display_width - c->screen->strut.right) && + (*x + c->size.width) < + (int)(c->screen->display_width - c->screen->strut.right + EDGE_RESIST)) { + *x = (int)(c->screen->display_width - c->screen->strut.right - + c->size.width); + } + if ((*y - titleHeight()) < (int)c->screen->strut.top && + (*y - titleHeight()) > + ((int)c->screen->strut.top - EDGE_RESIST)) { + *y = (int)c->screen->strut.top + titleHeight(); + } + if ((*y + c->size.height) > + (int)(c->screen->display_height - c->screen->strut.bottom) && + (*y + c->size.height) < + (int)(c->screen->display_height - c->screen->strut.bottom + EDGE_RESIST)) { + *y = (int)(c->screen->display_height - c->screen->strut.bottom - + c->size.height); + } + + /* + * Update that part of the client information that we're happy with. + */ + if (interacting_edge != ENone) { + if (horizontal_ok) { + c->size.x = *x; + c->size.width = *dx; + } + if (vertical_ok) { + c->size.y = *y; + c->size.height = *dy; + } + } else { + if (horizontal_ok) + c->size.x = *x; + if (vertical_ok) + c->size.y = *y; + } +} + +void +size_expose(void) { + int width, height; + char buf[4*2 + 3 + 1]; + + width = current->size.width - 2*border; + height = current->size.height - 2*border; + + /* This dance ensures that we report 80x24 for an xterm even when + * it has a scrollbar. */ + if (current->size.flags & (PMinSize|PBaseSize) && current->size.flags & PResizeInc) { + if (current->size.flags & PBaseSize) { + width -= current->size.base_width; + height -= current->size.base_height; + } else { + width -= current->size.min_width; + height -= current->size.min_height; + } + } + + if (current->size.width_inc != 0) + width /= current->size.width_inc; + if (current->size.height_inc != 0) + height /= current->size.height_inc; + + snprintf(buf, sizeof(buf), "%i x %i", width, height); + XmbDrawString(dpy, current_screen->popup, popup_font_set, + current_screen->size_gc, + (popup_width - popupWidth(buf, strlen(buf))) / 2, + ascent(popup_font_set_ext) + 1, buf, strlen(buf)); +} + +static void +Client_OpaquePrimitive(Client *c, Edge edge) { + Cursor cursor; + int ox, oy; + + if (c == 0 /*|| c != current*/) + return; + + /* Find out where we've got hold of the window. */ + getMousePosition(&ox, &oy); + ox = c->size.x - ox; + oy = c->size.y - oy; + + cursor = getEdgeCursor(edge); + XChangeActivePointerGrab(dpy, ButtonMask | PointerMotionHintMask | + ButtonMotionMask | OwnerGrabButtonMask, cursor, CurrentTime); + + /* + * Store some state so that we can get back into the main event + * dispatching thing. + */ + interacting_edge = edge; + start_x = ox; + start_y = oy; + mode = wm_reshaping; + ewmh_set_client_list(c->screen); +} + +void +Client_Lower(Client *c) +{ + if (c == 0) return; + + XLowerWindow(dpy, c->window); + if (c->framed) XLowerWindow(dpy, c->parent); + ewmh_set_client_list(c->screen); +} + +void +Client_Raise(Client *c) +{ + Client * trans; + + if (c == 0) return; + + if (c->framed) XRaiseWindow(dpy, c->parent); + XRaiseWindow(dpy, c->window); + + for (trans = clients; trans != NULL; trans = trans->next) { + if (trans->trans != c->window && + !(c->framed == True && trans->trans == c->parent)) + continue; + if (trans->framed) XRaiseWindow(dpy, trans->parent); + XRaiseWindow(dpy, trans->window); + } + + ewmh_set_client_list(c->screen); +} + +void +Client_Close(Client *c) { + if (c == 0) + return; + + /* + * Terminate the client nicely if possible. Be brutal otherwise. + */ + if (c->proto & Pdelete) { + sendClientMessage(c->window, wm_protocols, wm_delete, CurrentTime); + } else { + XKillClient(dpy, c->window); + } +} + +void +Client_SetState(Client *c, int state) { + long data[2]; + + data[0] = (long) state; + data[1] = (long) None; + + c->state = state; + XChangeProperty(dpy, c->window, wm_state, wm_state, 32, + PropModeReplace, (unsigned char *) data, 2); + ewmh_set_state(c); +} + +static void +sendClientMessage(Window w, Atom a, long data0, long data1) { + XEvent ev; + long mask; + + memset(&ev, 0, sizeof(ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = w; + ev.xclient.message_type = a; + ev.xclient.format = 32; + ev.xclient.data.l[0] = data0; + ev.xclient.data.l[1] = data1; + mask = (getScreenFromRoot(w) != 0) ? SubstructureRedirectMask : 0L; + + XSendEvent(dpy, w, False, mask, &ev); +} + +extern void +Client_ResetAllCursors(void) { + Client *c; + XSetWindowAttributes attr; + + for (c = clients; c; c = c->next) { + if (c->framed != True) continue; + attr.cursor = c->screen->root_cursor; + XChangeWindowAttributes(dpy, c->parent, + CWCursor, &attr); + c->cursor = ENone; + } +} + +extern void +Client_FreeAll(void) { + Client *c; + XWindowChanges wc; + + for (c = clients; c; c = c->next) { + int not_mapped = !normal(c); + + /* elliott thinks leaving window unmapped causes the x server + * to lose them when the window manager quits. it doesn't + * happen to me with XFree86's Xnest, but unmapping the + * windows stops gtk window generating an extra window when + * the window manager quits. + * who is right? only time will tell.... + */ + XUnmapWindow(dpy, c->parent); + XUnmapWindow(dpy, c->window); + /* Remap the window if it's hidden. + if (not_mapped) { + XMapWindow(dpy, c->parent); + XMapWindow(dpy, c->window); + } */ + + /* Reparent it, and then push it to the bottom if it was hidden. */ + XReparentWindow(dpy, c->window, c->screen->root, c->size.x, c->size.y); + if (not_mapped) + XLowerWindow(dpy, c->window); + + /* Give it back its initial border width. */ + wc.border_width = c->border; + XConfigureWindow(dpy, c->window, CWBorderWidth, &wc); + } +} + +extern void +Client_ColourMap(XEvent *e) { + int i; + Client * c; + + for (c = clients; c; c = c->next) { + for (i = 0; i < c->ncmapwins; i++) { + if (c->cmapwins[i] == e->xcolormap.window) { + c->wmcmaps[i] = e->xcolormap.colormap; + if (c == current) + cmapfocus(c); + return; + } + } + } +} + +extern void +Client_ReshapeEdge(Client *c, Edge e) { + Client_OpaquePrimitive(c, e); +} + +extern void +Client_Move(Client *c) { + Client_OpaquePrimitive(c, ENone); +} + +extern int +hidden(Client *c) { + return c->state == IconicState; +} + +extern int +withdrawn(Client *c) { + return c->state == WithdrawnState; +} + +extern int +normal(Client *c) { + return c->state == NormalState; +} + +extern void +Client_EnterFullScreen(Client *c) { + XWindowChanges fs; + + memcpy(&c->return_size, &c->size, sizeof(XSizeHints)); + if (c->framed) { + c->size.x = fs.x = -border; + c->size.y = fs.y = -border; + c->size.width = fs.width = + c->screen->display_width + 2 * border; + c->size.height = fs.height = + c->screen->display_height + 2 * border; + XConfigureWindow(dpy, c->parent, + CWX | CWY | CWWidth | CWHeight, &fs); + + fs.x = border; + fs.y = border; + fs.width = c->screen->display_width; + fs.height = c->screen->display_height; + XConfigureWindow(dpy, c->window, + CWX | CWY | CWWidth | CWHeight, &fs); + XRaiseWindow(dpy,c->parent); + } else { + c->size.x = c->size.y = fs.x = fs.y = 0; + c->size.width = fs.width = c->screen->display_width; + c->size.height = fs.height = c->screen->display_height; + XConfigureWindow(dpy, c->window, + CWX | CWY | CWWidth | CWHeight, &fs); + XRaiseWindow(dpy,c->window); + } + sendConfigureNotify(c); +} + +extern void +Client_ExitFullScreen(Client *c) { + XWindowChanges fs; + + memcpy(&c->size, &c->return_size, sizeof(XSizeHints)); + if (c->framed == True) { + fs.x = c->size.x; + fs.y = c->size.y - titleHeight(); + fs.width = c->size.width; + fs.height = c->size.height + titleHeight(); + XConfigureWindow(dpy, c->parent, + CWX | CWY | CWWidth | CWHeight, &fs); + + fs.x = border; + fs.y = border + titleHeight(); + fs.width = c->size.width -(2 * border); + fs.height = c->size.height -(2 * border); + XConfigureWindow(dpy, c->window, + CWX | CWY | CWWidth | CWHeight, &fs); + } else { + fs.x = c->size.x; + fs.y = c->size.y; + fs.width = c->size.width; + fs.height = c->size.height; + XConfigureWindow(dpy, c->window, + CWX | CWY | CWWidth | CWHeight, &fs); + } + sendConfigureNotify(c); +} + +extern void +Client_Focus(Client *c, Time time) { + if (current) { + setactive(current, 0, 0L); + XDeleteProperty(dpy, current->screen->root, + ewmh_atom[_NET_ACTIVE_WINDOW]); + } + + if (!c && current) { + last_focus = current; + } else { + last_focus = NULL; + } + current = c; + if (c) { + setactive(current, 1, time); + XChangeProperty(dpy, current->screen->root, + ewmh_atom[_NET_ACTIVE_WINDOW], + XA_WINDOW, 32, PropModeReplace, + (unsigned char *)¤t->window, 1); + } + + if (focus_mode == focus_click) + Client_Raise(c); +} + +extern void +Client_Name(Client *c, const char *name, Bool is_utf8) { + int tx; + static const char dots[] = " [...] "; + int cut; + + if (c->name) free(c->name); + c->name = strdup((char *) name); + c->namelen = strlen(c->name); + c->name_utf8 = is_utf8; + + if (c->menu_name) free(c->menu_name); + c->menu_name = 0; + tx = titleWidth(popup_font_set, c); + if (tx <= (c->screen->display_width - (c->screen->display_width / 10))) + return; + + /* the menu entry for this client will not fit on the display + * (minus 10% for saftey), so produced a truncated version... + */ + cut = 5; + do { + if (c->menu_name) { + free(c->menu_name); + c->menu_name = 0; + } + if (cut >= (strlen(c->name) / 2)) break; + c->menu_name = strdup(c->name); + /* FIXME: this is not UTF-8 safe! */ + sprintf(&c->menu_name[(strlen(c->name) / 2) - cut], dots); + strcat(c->menu_name, + &c->name[(strlen(c->name) / 2) + cut]); + c->menu_namelen = strlen(c->menu_name); + cut++; + tx = titleWidth(popup_font_set, c); + if (!tx) break; + } while (tx > + (c->screen->display_width - (c->screen->display_width / 10))); +} diff --git a/cursor.c b/cursor.c new file mode 100644 index 0000000..f528095 --- /dev/null +++ b/cursor.c @@ -0,0 +1,77 @@ +/* + * lwm, a window manager for X11 + * Copyright (C) 1997-2016 Elliott Hughes, James Carter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include + +#include +#include +#include +#include + +#include "lwm.h" + +typedef struct CursorMapping CursorMapping; +struct CursorMapping { + Edge edge; + int font_char; +}; + +static CursorMapping cursor_map[] = { + {ETopLeft, XC_top_left_corner}, + {ETop, XC_top_side}, + {ETopRight, XC_top_right_corner}, + {ERight, XC_right_side}, + {ENone, XC_fleur}, + {ELeft, XC_left_side}, + {EBottomLeft, XC_bottom_left_corner}, + {EBottom, XC_bottom_side}, + {EBottomRight, XC_bottom_right_corner}, + {ENone, 0}, +}; + +extern void +initialiseCursors(int screen) { + XColor red, white, exact; + Colormap cmp; + int i; + + cmp = DefaultColormap(dpy, screen); + + XAllocNamedColor(dpy, cmp, "red", &red, &exact); + XAllocNamedColor(dpy, cmp, "white", &white, &exact); + + screens[screen].root_cursor = XCreateFontCursor(dpy, XC_left_ptr); + XRecolorCursor(dpy, screens[screen].root_cursor, &red, &white); + + screens[screen].box_cursor = XCreateFontCursor(dpy, XC_draped_box); + XRecolorCursor(dpy, screens[screen].box_cursor, &red, &white); + + for (i = 0; cursor_map[i].font_char != 0; i++) { + Edge e = cursor_map[i].edge; + screens[screen].cursor_map[e] = + XCreateFontCursor(dpy, cursor_map[i].font_char); + XRecolorCursor(dpy, screens[screen].cursor_map[e], + &red, &white); + } +} + +extern Cursor +getEdgeCursor(Edge edge) { + return screens[0].cursor_map[edge]; +} diff --git a/disp.c b/disp.c new file mode 100644 index 0000000..bdf1d9d --- /dev/null +++ b/disp.c @@ -0,0 +1,884 @@ +/* + * lwm, a window manager for X11 + * Copyright (C) 1997-2016 Elliott Hughes, James Carter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lwm.h" +#include "ewmh.h" + +/* + * Dispatcher for main event loop. + */ +typedef struct Disp Disp; +struct Disp { + int type; + void (*handler)(XEvent *); +}; + +static void expose(XEvent *); +static void buttonpress(XEvent *); +static void buttonrelease(XEvent *); +static void focuschange(XEvent *); +static void maprequest(XEvent *); +static void configurereq(XEvent *); +static void unmap(XEvent *); +static void destroy(XEvent *); +static void clientmessage(XEvent *); +static void colormap(XEvent *); +static void property(XEvent *); +static void reparent(XEvent *); +static void enter(XEvent *); +static void circulaterequest(XEvent *); +static void motionnotify(XEvent *); + +void reshaping_motionnotify(XEvent *); + +static Disp disps[] = +{ + {Expose, expose}, + {MotionNotify, motionnotify}, + {ButtonPress, buttonpress}, + {ButtonRelease, buttonrelease}, + {FocusIn, focuschange}, + {FocusOut, focuschange}, + {MapRequest, maprequest}, + {ConfigureRequest, configurereq}, + {UnmapNotify, unmap}, + {DestroyNotify, destroy}, + {ClientMessage, clientmessage}, + {ColormapNotify, colormap}, + {PropertyNotify, property}, + {ReparentNotify, reparent}, + {EnterNotify, enter}, + {CirculateRequest, circulaterequest}, + {LeaveNotify, 0}, + {ConfigureNotify, 0}, + {CreateNotify, 0}, + {GravityNotify, 0}, + {MapNotify, 0}, + {MappingNotify, 0}, + {SelectionClear, 0}, + {SelectionNotify, 0}, + {SelectionRequest, 0}, + {NoExpose, 0}, +}; + +/** + * pending it the client in which an action has been started by a mouse press + * and we are waiting for the button to be released before performing the action + */ +static Client *pending=NULL; + +extern void +dispatch(XEvent * ev) { + Disp * p; + + for (p = disps; p < disps + sizeof(disps)/sizeof(disps[0]); p++) { + if (p->type == ev->type) { + if (p->handler != 0) + p->handler(ev); + return; + } + } + + if (!shapeEvent(ev)) + fprintf(stderr, "%s: unknown event %d\n", argv0, ev->type); +} + +static void +expose(XEvent * ev) { + Client * c; + Window w; /* Window the expose event is for. */ + + /* Only handle the last in a group of Expose events. */ + if (ev->xexpose.count != 0) return; + + w = ev->xexpose.window; + + /* + * We don't draw on the root window so that people can have + * their favourite Spice Girls backdrop... + */ + if (getScreenFromRoot(w) != 0) + return; + + /* Decide what needs redrawing: window frame or menu? */ + if (current_screen && w == current_screen->popup) { + if (mode == wm_reshaping && current != 0) + size_expose(); + } else { + c = Client_Get(w); + if (c != 0) { + Client_DrawBorder(c, c == current); + } + } +} + +static void +buttonpress(XEvent *ev) { + Client *c; + XButtonEvent *e = &ev->xbutton; + int quarter; + + /* If we're getting it already, we're not in the market for more. */ + if (mode != wm_idle) { + /* but allow a button press to cancel a move/resize, + * to satify the EWMH advisory to allow a second mechanism + * of completing move/resize operations, due to a race. + * (section 4.3) sucky! + */ + if (mode == wm_reshaping) { + mode = wm_idle; + } + return; + } + + c = Client_Get(e->window); + + if (c && c != current && focus_mode == focus_click) { + /* Click is not on current window, + * and in click-to-focus mode, so change focus + */ + Client_Focus(c, e->time); + } + + /*move this test up to disable scroll to focus*/ + if (e->button >= 4 && e->button <= 7) { + return; + } + + if (c && c == current && (e->window == c->parent)) { + /* Click went to our frame around a client. */ + + /* The ``box''. */ + quarter = (border + titleHeight()) / 4; + if (e->x > (quarter + 2) && e->x < (3 + 3*quarter) && e->y > quarter && e->y <= 3*quarter) { + /*Client_Close(c);*/ + pending = c; + mode = wm_closing_window; + return; + } + + /* Somewhere in the rest of the frame. */ + if (e->button == HIDE_BUTTON) { + pending = c; + mode = wm_hiding_window; + return; + } + if (e->button == MOVE_BUTTON) { + Client_Move(c); + return; + } + if (e->button == RESHAPE_BUTTON) { + XMapWindow(dpy, c->parent); + Client_Raise(c); + + /* Lasciate ogni speranza voi ch'entrate... */ + + if (e->x <= border && e->y <= border) { + Client_ReshapeEdge(c, ETopLeft); + } else if (e->x >= (c->size.width - border) && e->y <= border) { + Client_ReshapeEdge(c, ETopRight); + } else if (e->x >= (c->size.width - border) && e->y >= (c->size.height + titleHeight() - border)) { + Client_ReshapeEdge(c, EBottomRight); + } else if (e->x <= border && e->y >= (c->size.height + titleHeight() - border)) { + Client_ReshapeEdge(c, EBottomLeft); + } else if (e->x > border && e->x < (c->size.width - border) && e->y < border) { + Client_ReshapeEdge(c, ETop); + } else if (e->x > border && e->x < (c->size.width - border) && e->y >= border && e->y < (titleHeight() + border)) { + Client_Move(c); + } else if (e->x > (c->size.width - border) && e->y > border && e->y < (c->size.height + titleHeight() - border)) { + Client_ReshapeEdge(c, ERight); + } else if (e->x > border && e->x < (c->size.width - border) && e->y > (c->size.height - border)) { + Client_ReshapeEdge(c, EBottom); + } else if (e->x < border && e->y > border && e->y < (c->size.height + titleHeight() - border)) { + Client_ReshapeEdge(c, ELeft); + } + return; + } + return; + } + + /* Deal with root window button presses. */ + if (e->window == e->root) { + if (e->button == Button3) { + cmapfocus(0); + } else { + shell(getScreenFromRoot(e->root), e->button, e->x, e->y); + } + } +} + +static void +buttonrelease(XEvent *ev) { + XButtonEvent *e = &ev->xbutton; + int quarter; + + if (mode == wm_reshaping) + XUnmapWindow(dpy, current_screen->popup); + else if (mode == wm_closing_window) { + /* was the button released within the window's box?*/ + quarter = (border + titleHeight()) / 4; + if (pending != NULL && + (e->window == pending->parent) && + (e->x > (quarter + 2) && + e->x < (3 + 3*quarter) && + e->y > quarter && e->y <= 3*quarter)) + Client_Close(pending); + pending = NULL; + } else if (mode == wm_hiding_window) { + /* was the button release within the window's frame? */ + if (pending != NULL && + (e->window == pending->parent) && + (e->x >= 0) && (e->y >= 0) && + (e->x <= pending->size.width) && + (e->y <= (pending->size.height + titleHeight()))) { + if (e->state & ShiftMask) { + Client_Lower(pending); + } + } + pending = NULL; + } + + mode = wm_idle; +} + +static void circulaterequest(XEvent *ev) { + XCirculateRequestEvent * e = &ev->xcirculaterequest; + Client * c; + + c = Client_Get(e->window); + + if (c == 0) { + if (e->place == PlaceOnTop) { + XRaiseWindow(e->display, e->window); + } else { + XLowerWindow(e->display, e->window); + } + } else { + if (e->place == PlaceOnTop) { + Client_Raise(c); + } else { + Client_Lower(c); + } + } +} + +static void +maprequest(XEvent *ev) { + Client * c; + XMapRequestEvent * e = &ev->xmaprequest; + + c = Client_Get(e->window); + + if (c == 0 || c->window != e->window) { + int screen; + for (screen = 0; screen < screen_count; screen++) + scanWindowTree(screen); + c = Client_Get(e->window); + if (c == 0 || c->window != e->window) { + fprintf(stderr, "MapRequest for non-existent window!\n"); + return; + } + } + + switch (c->state) { + case WithdrawnState: + if (getScreenFromRoot(c->parent) != 0) { + manage(c, 0); + break; + } + if (c->framed == True) { + XReparentWindow(dpy, c->window, c->parent, border, + border + titleHeight()); + } else { + XReparentWindow(dpy, c->window, c->parent, + c->size.x, c->size.y); + } + XAddToSaveSet(dpy, c->window); + /*FALLTHROUGH*/ + case NormalState: + XMapWindow(dpy, c->parent); + XMapWindow(dpy, c->window); + Client_Raise(c); + Client_SetState(c, NormalState); + break; + } + ewmh_set_client_list(c->screen); +} + +static void +unmap(XEvent *ev) { + Client *c; + XUnmapEvent *e = &ev->xunmap; + + c = Client_Get(e->window); + if (c == 0) return; + + /* + * In the description of the ReparentWindow request we read: "If the window + * is mapped, an UnmapWindow request is performed automatically first". This + * might seem stupid, but it's the way it is. While a reparenting is pending + * we ignore UnmapWindow requests. + */ + if (c->internal_state == IPendingReparenting) { + c->internal_state = INormal; + return; + } + + /* "This time it's the real thing." */ + + if (c->state == IconicState) { + /* + * Is this a hidden window disappearing? If not, then we + * aren't interested because it's an unmap request caused + * by our hiding a window. + */ + } else { + /* This is a plain unmap, so withdraw the window. */ + withdraw(c); + } + + c->internal_state = INormal; +} + +static void +configurereq(XEvent *ev) { + XWindowChanges wc; + Client *c; + XConfigureRequestEvent *e = &ev->xconfigurerequest; + + c = Client_Get(e->window); + + + if (c && c->window == e->window) { + /* + * ICCCM section 4.1.5 says that the x and y coordinates here + * will have been "adjusted for the border width". + * NOTE: this may not be the only place to bear this in mind. + */ + if (e->value_mask & CWBorderWidth) { + e->x -= e->border_width; + e->y -= e->border_width; + } else { + /* + * The ICCCM also says that clients should always set the + * border width in a configure request. As usual, many don't. + */ + /* adding one seems a bit arbitrary and makes edit + drift by one pixel*/ + /*e->x--;*/ + /*e->y--;*/ + } + + if (e->value_mask & CWX) { + c->size.x = e->x; + } + if (e->value_mask & CWY) { + c->size.y = e->y; + if (c->framed == True) + c->size.y += titleHeight(); + } + if (e->value_mask & CWWidth) { + c->size.width = e->width; + if (c->framed == True) + c->size.width += 2 * border; + } + if (e->value_mask & CWHeight) { + c->size.height = e->height; + if (c->framed == True) + c->size.height += 2 * border; + } + if (e->value_mask & CWBorderWidth) + c->border = e->border_width; + + if (getScreenFromRoot(c->parent) == 0) { + wc.x = c->size.x; + wc.y = c->size.y; + if (c->framed == True) + wc.y -= titleHeight(); + wc.width = c->size.width; + wc.height = c->size.height; + if (c->framed == True) + wc.height += titleHeight(); + wc.border_width = 1; + wc.sibling = e->above; + wc.stack_mode = e->detail; + + XConfigureWindow(dpy, e->parent, e->value_mask, &wc); + sendConfigureNotify(c); + } + } + if (c && (c->internal_state == INormal) && (c->framed == True)) { + wc.x = border; + wc.y = border; + } else { + wc.x = e->x; + wc.y = e->y; + } + + wc.width = e->width; + wc.height = e->height; + wc.border_width = 0; + wc.sibling = e->above; + wc.stack_mode = e->detail; + e->value_mask |= CWBorderWidth; + + XConfigureWindow(dpy, e->window, e->value_mask, &wc); + + if (c) { + if (c->framed == True) { + XMoveResizeWindow(dpy, c->parent, + c->size.x, c->size.y - titleHeight(), + c->size.width, c->size.height + titleHeight()); + XMoveWindow(dpy, c->window, + border, border + titleHeight()); + } else { + XMoveResizeWindow(dpy, c->window, + c->size.x, c->size.y, + c->size.width, c->size.height); + } + } +} + +static void +destroy(XEvent *ev) { + Client * c; + Window w = ev->xdestroywindow.window; + + c = Client_Get(w); + if (c == 0) + return; + + ignore_badwindow = 1; + Client_Remove(c); + ignore_badwindow = 0; +} + +static void +clientmessage(XEvent *ev) { + Client * c; + XClientMessageEvent * e = &ev->xclient; + + c = Client_Get(e->window); + if (c == 0) return; + if (e->message_type == wm_change_state) { + return; + } + if (e->message_type == ewmh_atom[_NET_WM_STATE] && + e->format == 32) { + ewmh_change_state(c, e->data.l[0], e->data.l[1]); + ewmh_change_state(c, e->data.l[0], e->data.l[2]); + return; + } + if (e->message_type == ewmh_atom[_NET_ACTIVE_WINDOW] && + e->format == 32) { + /* An EWMH enabled application has asked for this client + * to be made the active window. The window is raised, and + * focus given if the focus mode is click (focusing on a + * window other than the one the pointer is in makes no + * sense when the focus mode is enter). + */ + XMapWindow(dpy, c->parent); + Client_Raise(c); + if (c != current && focus_mode == focus_click) + Client_Focus(c, CurrentTime); + return; + } + if (e->message_type == ewmh_atom[_NET_CLOSE_WINDOW] && + e->format == 32) { + Client_Close(c); + return; + } + if (e->message_type == ewmh_atom[_NET_MOVERESIZE_WINDOW] && + e->format == 32) { + XEvent ev; + + /* FIXME: ok, so this is a bit of a hack */ + ev.xconfigurerequest.window = e->window; + ev.xconfigurerequest.x = e->data.l[1]; + ev.xconfigurerequest.y = e->data.l[2]; + ev.xconfigurerequest.width = e->data.l[3]; + ev.xconfigurerequest.height = e->data.l[4]; + ev.xconfigurerequest.value_mask = 0; + if (e->data.l[0] & (1 << 8)) + ev.xconfigurerequest.value_mask |= CWX; + if (e->data.l[0] & (1 << 9)) + ev.xconfigurerequest.value_mask |= CWY; + if (e->data.l[0] & (1 << 10)) + ev.xconfigurerequest.value_mask |= CWWidth; + if (e->data.l[0] & (1 << 11)) + ev.xconfigurerequest.value_mask |= CWHeight; + configurereq(&ev); + return; + } + if (e->message_type == ewmh_atom[_NET_WM_MOVERESIZE] && + e->format == 32) { + Edge edge = E_LAST; + EWMHDirection direction = e->data.l[2]; +/* + int x_root = e->data.l[0]; + int y_root = e->data.l[1]; +*/ + + + /* before we can do any resizing, make the window visible */ + XMapWindow(dpy, c->parent); + Client_Raise(c); + /* FIXME: we're ignorning x_root, y_root and button! */ + switch (direction) { + case DSizeTopLeft: + edge = ETopLeft; + break; + case DSizeTop: + edge = ETop; + break; + case DSizeTopRight: + edge = ETopRight; + break; + case DSizeRight: + edge = ERight; + break; + case DSizeBottomRight: + edge = EBottomRight; + break; + case DSizeBottom: + edge = EBottom; + break; + case DSizeBottomLeft: + edge = EBottomLeft; + break; + case DSizeLeft: + edge = ELeft; + break; + case DMove: + edge = ENone; + break; + case DSizeKeyboard: + /* FIXME: don't know how to deal with this */ + edge = E_LAST; + break; + case DMoveKeyboard: +#if 0 +/* need to do a release and this is too broken for that */ + /* don't believe i'm doing this. mouse warping + * sucks! + */ + XWarpPointer(dpy, c->screen->root, c->window, + x_root, y_root, + c->screen->display_width, + c->screen->display_height, + c->size.width / 2, c->size.height / 2); + edge = ENone; +#endif + edge = E_LAST; + break; + default: + edge = E_LAST; + fprintf(stderr, "%s: received _NET_WM_MOVERESIZE" + " with bad direction", argv0); + break; + } + switch (edge) { + case E_LAST: + break; + case ENone: + Client_Move(c); + break; + default: + Client_ReshapeEdge(c, edge); + break; + } + } +} + +static void +colormap(XEvent *ev) { + Client * c; + XColormapEvent * e = &ev->xcolormap; + + if (e->new) { + c = Client_Get(e->window); + if (c) { + c->cmap = e->colormap; + if (c == current) + cmapfocus(c); + } else { + Client_ColourMap(ev); + } + } +} + +static void +property(XEvent * ev) { + Client * c; + XPropertyEvent * e = &ev->xproperty; + + c = Client_Get(e->window); + if (c == 0) + return; + + if (e->atom == _mozilla_url || e->atom == XA_WM_NAME) { + getWindowName(c); + setactive(c, c == current, 0L); + } else if (e->atom == XA_WM_TRANSIENT_FOR) { + getTransientFor(c); + } else if (e->atom == XA_WM_NORMAL_HINTS) { + getNormalHints(c); + } else if (e->atom == wm_colormaps) { + getColourmaps(c); + if (c == current) + cmapfocus(c); + } else if (e->atom == ewmh_atom[_NET_WM_STRUT]) { + ewmh_get_strut(c); + } else if (e->atom == ewmh_atom[_NET_WM_STATE]) { + // Received notice that client wants to change its state + // update internal wstate tracking + Bool wasFullscreen = c->wstate.fullscreen; + ewmh_get_state(c); + // make any changes requested + if (c->wstate.fullscreen == True && wasFullscreen == False) Client_EnterFullScreen(c); + else if (c->wstate.fullscreen == False && wasFullscreen == True) Client_ExitFullScreen(c); + } +} + +static void +reparent(XEvent *ev) { + Client * c; + XReparentEvent * e = &ev->xreparent; + + if (getScreenFromRoot(e->event) == 0 || e->override_redirect || getScreenFromRoot(e->parent) != 0) + return; + + c = Client_Get(e->window); + if (c != 0 && (getScreenFromRoot(c->parent) != 0 || withdrawn(c))) + Client_Remove(c); +} + +static void +focuschange(XEvent *ev) { + Client *c; + Window focus_window; + int revert_to; + + if (ev->type == FocusOut) return; + + XGetInputFocus(dpy, &focus_window, &revert_to); + if (focus_window == PointerRoot || focus_window == None) { + if (current) Client_Focus(NULL, CurrentTime); + return; + } + c = Client_Get(focus_window); + if (c && c != current) { + Client_Focus(c, CurrentTime); + } + return; +} + +static void +enter(XEvent *ev) { + Client *c; + + c = Client_Get(ev->xcrossing.window); + if (c == 0 || mode != wm_idle) + return; + + if (c->framed == True) { + XSetWindowAttributes attr; + + attr.cursor = c->screen->root_cursor; + XChangeWindowAttributes(dpy, c->parent, + CWCursor, &attr); + c->cursor = ENone; + } + if (c != current && !c->hidden && focus_mode == focus_enter) { + /* Entering a new window in enter focus mode, so take focus */ + Client_Focus(c, ev->xcrossing.time); + } +} + +static void +motionnotify(XEvent *ev) { + if (mode == wm_reshaping) + reshaping_motionnotify(ev); + else if (mode == wm_idle) { + XMotionEvent *e = &ev->xmotion; + Client *c = Client_Get(e->window); + Edge edge = ENone; + int quarter = (border + titleHeight()) / 4; + + if (c && (e->window == c->parent) && + (e->subwindow != c->window) && + mode == wm_idle) { + /* mouse moved in a frame we manage - check cursor */ + if (e->x > (quarter + 2) + && e->x < (3 + 3*quarter) + && e->y > quarter && e->y <= 3*quarter) { + edge = E_LAST; + } else if (e->x <= border && e->y <= border) { + edge = ETopLeft; + } else if (e->x >= (c->size.width - border) + && e->y <= border) { + edge = ETopRight; + } else if (e->x >= (c->size.width - border) + && e->y >= + (c->size.height + titleHeight() - border)) { + edge = EBottomRight; + } else if (e->x <= border && + e->y >= + (c->size.height + titleHeight() - border)) { + edge = EBottomLeft; + } else if (e->x > border && + e->x < (c->size.width - border) + && e->y < border) { + edge = ETop; + } else if (e->x > border && + e->x < (c->size.width - border) + && e->y >= border + && e->y < (titleHeight() + border)) { + edge = ENone; + } else if (e->x > (c->size.width - border) + && e->y > border + && e->y < + (c->size.height + titleHeight() - border)) { + edge = ERight; + } else if (e->x > border + && e->x < (c->size.width - border) + && e->y > (c->size.height - border)) { + edge = EBottom; + } else if (e->x < border + && e->y > border + && e->y < + (c->size.height + titleHeight() - border)) { + edge = ELeft; + } + if (c->cursor != edge) { + XSetWindowAttributes attr; + + if (edge == ENone) { + attr.cursor = + c->screen->root_cursor; + } else if (edge == E_LAST) { + attr.cursor = + c->screen->box_cursor; + } else { + attr.cursor = + c->screen->cursor_map[edge]; + } + XChangeWindowAttributes(dpy, c->parent, + CWCursor, &attr); + c->cursor = edge; + } + } + } +} + +/*ARGSUSED*/ +void +reshaping_motionnotify(XEvent* ev) { + int nx; /* New x. */ + int ny; /* New y. */ + int ox; /* Original x. */ + int oy; /* Original y. */ + int ndx; /* New width. */ + int ndy; /* New height. */ + int odx; /* Original width. */ + int ody; /* Original height. */ + int pointer_x; + int pointer_y; + + if (mode != wm_reshaping || !current) return; + + getMousePosition(&pointer_x, &pointer_y); + + if (interacting_edge != ENone) { + nx = ox = current->size.x; + ny = oy = current->size.y; + ndx = odx = current->size.width; + ndy = ody = current->size.height; + + /* Vertical. */ + switch (interacting_edge) { + case ETop: + case ETopLeft: + case ETopRight: + pointer_y += titleHeight(); + ndy += (current->size.y - pointer_y); + ny = pointer_y; + break; + case EBottom: + case EBottomLeft: + case EBottomRight: + ndy = pointer_y - current->size.y; + break; + default: break; + } + + /* Horizontal. */ + switch (interacting_edge) { + case ERight: + case ETopRight: + case EBottomRight: + ndx = pointer_x - current->size.x; + break; + case ELeft: + case ETopLeft: + case EBottomLeft: + ndx += (current->size.x - pointer_x); + nx = pointer_x; + break; + default: break; + } + + Client_MakeSane(current, interacting_edge, &nx, &ny, &ndx, &ndy); + XMoveResizeWindow(dpy, current->parent, + current->size.x, current->size.y - titleHeight(), + current->size.width, current->size.height + titleHeight()); + if (current->size.width == odx && current->size.height == ody) { + if (current->size.x != ox || current->size.y != oy) + sendConfigureNotify(current); + } else + XMoveResizeWindow(dpy, current->window, + border, border + titleHeight(), + current->size.width - 2 * border, + current->size.height - 2 * border); + } else { + nx = pointer_x + start_x; + ny = pointer_y + start_y; + + Client_MakeSane(current, interacting_edge, &nx, &ny, 0, 0); + if (current->framed == True) { + XMoveWindow(dpy, current->parent, + current->size.x, + current->size.y - titleHeight()); + } else { + XMoveWindow(dpy, current->parent, + current->size.x, current->size.y); + } + sendConfigureNotify(current); + } +} diff --git a/error.c b/error.c new file mode 100644 index 0000000..9c7e4cd --- /dev/null +++ b/error.c @@ -0,0 +1,64 @@ +/* + * lwm, a window manager for X11 + * Copyright (C) 1997-2016 Elliott Hughes, James Carter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include +#include +#include +#include + +#include "lwm.h" + +int ignore_badwindow; + +void +panic(char *s) { + fprintf(stderr, "%s: %s\n", argv0, s); + exit(EXIT_FAILURE); +} + +int +errorHandler(Display *d, XErrorEvent *e) { + char msg[80]; + char req[80]; + char number[80]; + + if (mode == wm_initialising && + e->request_code == X_ChangeWindowAttributes && + e->error_code == BadAccess) + panic("another window manager is already running."); + + if (ignore_badwindow && + (e->error_code == BadWindow || e->error_code == BadColor)) + return 0; + + XGetErrorText(d, e->error_code, msg, sizeof(msg)); + sprintf(number, "%d", e->request_code); + XGetErrorDatabaseText(d, "XRequest", number, number, req, sizeof(req)); + + fprintf(stderr, "%s: protocol request %s on resource %#x failed: %s\n", + argv0, req, (unsigned int) e->resourceid, msg); + + if (mode == wm_initialising) + panic("can't initialise."); + + return 0; +} diff --git a/ewmh.c b/ewmh.c new file mode 100644 index 0000000..e6bb1b3 --- /dev/null +++ b/ewmh.c @@ -0,0 +1,643 @@ +/* + * lwm, a window manager for X11 + * Copyright (C) 1997-2016 Elliott Hughes, James Carter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lwm.h" +#include "ewmh.h" + +Atom ewmh_atom[EWMH_ATOM_LAST]; +Atom utf8_string; + +void +ewmh_init(void) { + /* build half a million EWMH atoms */ + ewmh_atom[_NET_SUPPORTED] = + XInternAtom(dpy, "_NET_SUPPORTED", False); + ewmh_atom[_NET_CLIENT_LIST] = + XInternAtom(dpy, "_NET_CLIENT_LIST", False); + ewmh_atom[_NET_CLIENT_LIST_STACKING] = + XInternAtom(dpy, "_NET_CLIENT_LIST_STACKING", False); + ewmh_atom[_NET_NUMBER_OF_DESKTOPS] = + XInternAtom(dpy, "_NET_NUMBER_OF_DESKTOPS", False); + ewmh_atom[_NET_DESKTOP_GEOMETRY] = + XInternAtom(dpy, "_NET_DESKTOP_GEOMETRY", False); + ewmh_atom[_NET_DESKTOP_VIEWPORT] = + XInternAtom(dpy, "_NET_DESKTOP_VIEWPORT", False); + ewmh_atom[_NET_CURRENT_DESKTOP] = + XInternAtom(dpy, "_NET_CURRENT_DESKTOP", False); + ewmh_atom[_NET_ACTIVE_WINDOW] = + XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + ewmh_atom[_NET_WORKAREA] = + XInternAtom(dpy, "_NET_WORKAREA", False); + ewmh_atom[_NET_SUPPORTING_WM_CHECK] = + XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + ewmh_atom[_NET_CLOSE_WINDOW] = + XInternAtom(dpy, "_NET_CLOSE_WINDOW", False); + ewmh_atom[_NET_MOVERESIZE_WINDOW] = + XInternAtom(dpy, "_NET_MOVERESIZE_WINDOW", False); + ewmh_atom[_NET_WM_MOVERESIZE] = + XInternAtom(dpy, "_NET_WM_MOVERESIZE", False); + ewmh_atom[_NET_WM_NAME] = + XInternAtom(dpy, "_NET_WM_NAME", False); + ewmh_atom[_NET_WM_WINDOW_TYPE] = + XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + ewmh_atom[_NET_WM_STATE] = + XInternAtom(dpy, "_NET_WM_STATE", False); + ewmh_atom[_NET_WM_ALLOWED_ACTIONS] = + XInternAtom(dpy, "_NET_WM_ALLOWED_ACTIONS", False); + ewmh_atom[_NET_WM_STRUT] = + XInternAtom(dpy, "_NET_WM_STRUT", False); + ewmh_atom[_NET_WM_WINDOW_TYPE_DESKTOP] = + XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DESKTOP", False); + ewmh_atom[_NET_WM_WINDOW_TYPE_DOCK] = + XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False); + ewmh_atom[_NET_WM_WINDOW_TYPE_TOOLBAR] = + XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_TOOLBAR", False); + ewmh_atom[_NET_WM_WINDOW_TYPE_MENU] = + XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_MENU", False); + ewmh_atom[_NET_WM_WINDOW_TYPE_UTILITY] = + XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_UTILITY", False); + ewmh_atom[_NET_WM_WINDOW_TYPE_SPLASH] = + XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_SPLASH", False); + ewmh_atom[_NET_WM_WINDOW_TYPE_DIALOG] = + XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + ewmh_atom[_NET_WM_WINDOW_TYPE_NORMAL] = + XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_NORMAL", False); + ewmh_atom[_NET_WM_STATE_SKIP_TASKBAR] = + XInternAtom(dpy, "_NET_WM_STATE_SKIP_TASKBAR", False); + ewmh_atom[_NET_WM_STATE_SKIP_PAGER] = + XInternAtom(dpy, "_NET_WM_STATE_SKIP_PAGER", False); + ewmh_atom[_NET_WM_STATE_HIDDEN] = + XInternAtom(dpy, "_NET_WM_STATE_HIDDEN", False); + ewmh_atom[_NET_WM_STATE_FULLSCREEN] = + XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + ewmh_atom[_NET_WM_ACTION_MOVE] = + XInternAtom(dpy, "_NET_WM_ACTION_MOVE", False); + ewmh_atom[_NET_WM_ACTION_RESIZE] = + XInternAtom(dpy, "_NET_WM_ACTION_RESIZE", False); + ewmh_atom[_NET_WM_ACTION_FULLSCREEN] = + XInternAtom(dpy, "_NET_WM_ACTION_FULLSCREEN", False); + ewmh_atom[_NET_WM_ACTION_CLOSE] = + XInternAtom(dpy, "_NET_WM_ACTION_CLOSE", False); + utf8_string = XInternAtom(dpy, "UTF8_STRING", False); +} + +void +ewmh_init_screens(void) { + int i; + unsigned long data[4]; + + /* announce EWMH compatibility on all acreens */ + for (i = 0; i < screen_count; i++) { + screens[i].ewmh_set_client_list = False; + screens[i].ewmh_compat = XCreateSimpleWindow(dpy, + screens[i].root, + -200, -200, 1, 1, + 0, 0, 0); + XChangeProperty(dpy, screens[i].ewmh_compat, + ewmh_atom[_NET_WM_NAME], + utf8_string, 8, PropModeReplace, + "lwm", 3); + + /* set root window properties */ + XChangeProperty(dpy, screens[i].root, + ewmh_atom[_NET_SUPPORTED], + XA_ATOM, 32, PropModeReplace, + (unsigned char *)ewmh_atom, EWMH_ATOM_LAST); + + XChangeProperty(dpy, screens[i].root, + ewmh_atom[_NET_SUPPORTING_WM_CHECK], + XA_WINDOW, 32, PropModeReplace, + (unsigned char *)&screens[i].ewmh_compat, 1); + + data[0] = 1; + XChangeProperty(dpy, screens[i].root, + ewmh_atom[_NET_NUMBER_OF_DESKTOPS], + XA_CARDINAL, 32, PropModeReplace, + (unsigned char*)data, 1); + + data[0] = screens[i].display_width; + data[1] = screens[i].display_height; + XChangeProperty(dpy, screens[i].root, + ewmh_atom[_NET_DESKTOP_GEOMETRY], + XA_CARDINAL, 32, PropModeReplace, + (unsigned char*)data, 2); + + data[0] = 0; + data[1] = 0; + XChangeProperty(dpy, screens[i].root, + ewmh_atom[_NET_DESKTOP_VIEWPORT], + XA_CARDINAL, 32, PropModeReplace, + (unsigned char*)data, 2); + + data[0] = 0; + XChangeProperty(dpy, screens[i].root, + ewmh_atom[_NET_CURRENT_DESKTOP], + XA_CARDINAL, 32, PropModeReplace, + (unsigned char*)data, 1); + + ewmh_set_strut(&screens[i]); + ewmh_set_client_list(&screens[i]); + } +} + +EWMHWindowType +ewmh_get_window_type(Window w) { + Atom rt; + Atom *type; + int fmt; + unsigned long n; + unsigned long extra; + int i; + EWMHWindowType ret; + + i = XGetWindowProperty(dpy, w, + ewmh_atom[_NET_WM_WINDOW_TYPE], + 0, 100, False, XA_ATOM, &rt, &fmt, &n, &extra, + (unsigned char **)&type); + if (i != Success || type == NULL) + return WTypeNone; + ret = WTypeNone; + for (; n; n--) { + if (type[n - 1] == + ewmh_atom[_NET_WM_WINDOW_TYPE_DESKTOP]) { + ret = WTypeDesktop; + break; + } + if (type[n - 1] == + ewmh_atom[_NET_WM_WINDOW_TYPE_DOCK]) { + ret = WTypeDock; + break; + } + if (type[n - 1] == + ewmh_atom[_NET_WM_WINDOW_TYPE_TOOLBAR]) { + ret = WTypeToolbar; + break; + } + if (type[n - 1] == + ewmh_atom[_NET_WM_WINDOW_TYPE_MENU]) { + ret = WTypeMenu; + break; + } + if (type[n - 1] == + ewmh_atom[_NET_WM_WINDOW_TYPE_UTILITY]) { + ret = WTypeUtility; + break; + } + if (type[n - 1] == + ewmh_atom[_NET_WM_WINDOW_TYPE_SPLASH]) { + ret = WTypeSplash; + break; + } + if (type[n - 1] == + ewmh_atom[_NET_WM_WINDOW_TYPE_DIALOG]) { + ret = WTypeDialog; + break; + } + if (type[n - 1] == + ewmh_atom[_NET_WM_WINDOW_TYPE_NORMAL]) { + ret = WTypeNormal; + break; + } + } + XFree(type); + return ret; +} + + +Bool ewmh_get_window_name(Client *c) { +#ifdef X_HAVE_UTF8_STRING + Atom rt; + char *name; + int fmt; + unsigned long n; + unsigned long extra; + int i; + + i = XGetWindowProperty(dpy, c->window, + ewmh_atom[_NET_WM_NAME], + 0, 100, False, utf8_string, &rt, &fmt, &n, &extra, + (unsigned char **)&name); + if (i != Success || name == NULL) + return False; + Client_Name(c, name, True); + XFree(name); + return True; +#else + return False; +#endif +} + +Bool +ewmh_hasframe(Client *c) { + switch (c->wtype) { + case WTypeDesktop: + case WTypeDock: + case WTypeMenu: + case WTypeSplash: + return False; + default: + return True; + } +} + +void +ewmh_get_state(Client *c) { + Atom rt; + Atom *state; + int fmt; + unsigned long n; + unsigned long extra; + int i; + + if (c == NULL) return; + i = XGetWindowProperty(dpy, c->window, + ewmh_atom[_NET_WM_STATE], + 0, 100, False, XA_ATOM, &rt, &fmt, &n, &extra, + (unsigned char **)&state); + if (i != Success || state == NULL) return; + c->wstate.skip_taskbar = False; + c->wstate.skip_pager = False; + c->wstate.fullscreen = False; + c->wstate.above = False; + c->wstate.below = False; + for (; n; n--) { + if (state[n - 1] == + ewmh_atom[_NET_WM_STATE_SKIP_TASKBAR]) + c->wstate.skip_taskbar = True; + if (state[n - 1] == + ewmh_atom[_NET_WM_STATE_SKIP_PAGER]) + c->wstate.skip_pager = True; + if (state[n - 1] == + ewmh_atom[_NET_WM_STATE_FULLSCREEN]) + c->wstate.fullscreen = True; + if (state[n - 1] == + ewmh_atom[_NET_WM_STATE_ABOVE]) + c->wstate.above = True; + if (state[n - 1] == + ewmh_atom[_NET_WM_STATE_BELOW]) + c->wstate.below = True; + } + XFree(state); +} + +static Bool +new_state(unsigned long action, Bool current) +{ + enum Action {remove, add, toggle}; + + switch (action) { + case remove: + return False; + case add: + return True; + case toggle: + if (current == True) return False; else return True; + } + fprintf(stderr,"%s: bad action in _NET_WM_STATE (%d)\n", + argv0, (int) action); + return current; +} + +void +ewmh_change_state(Client *c, unsigned long action, + unsigned long atom) { + Atom *a = (Atom *)&atom; + + if (atom == 0) return; + if (*a == ewmh_atom[_NET_WM_STATE_SKIP_TASKBAR]) + c->wstate.skip_taskbar = + new_state(action, c->wstate.skip_taskbar); + if (*a == ewmh_atom[_NET_WM_STATE_SKIP_PAGER]) + c->wstate.skip_pager = + new_state(action, c->wstate.skip_pager); + if (*a == ewmh_atom[_NET_WM_STATE_FULLSCREEN]) { + Bool was_fullscreen = c->wstate.fullscreen; + + c->wstate.fullscreen = + new_state(action, c->wstate.fullscreen); + if (was_fullscreen == False && + c->wstate.fullscreen == True) Client_EnterFullScreen(c); + if (was_fullscreen == True && + c->wstate.fullscreen == False) Client_ExitFullScreen(c); + } + if (*a == ewmh_atom[_NET_WM_STATE_ABOVE]) + c->wstate.above = + new_state(action, c->wstate.above); + if (*a == ewmh_atom[_NET_WM_STATE_BELOW]) + c->wstate.below = + new_state(action, c->wstate.below); + ewmh_set_state(c); + + /* may have to shuffle windows in the stack after a change of state */ + ewmh_set_client_list(c->screen); +} + +void +ewmh_set_state(Client *c) { + int atoms = 0; + Atom *a = NULL; + int i = 0; + + if (c == NULL) return; + + if (c->state != WithdrawnState) { + if (c->hidden == True) atoms++; + if (c->wstate.skip_taskbar == True) atoms++; + if (c->wstate.skip_pager == True) atoms++; + if (c->wstate.fullscreen == True) atoms++; + if (c->wstate.above == True) atoms++; + if (c->wstate.below == True) atoms++; + if (atoms > 0) a = malloc(sizeof(Atom) * atoms); + + if (c->hidden == True) { + a[i] = ewmh_atom[_NET_WM_STATE_HIDDEN]; + i++; + } + if (c->wstate.skip_taskbar == True) { + a[i] = ewmh_atom[_NET_WM_STATE_SKIP_TASKBAR]; + i++; + } + if (c->wstate.skip_pager == True) { + a[i] = ewmh_atom[_NET_WM_STATE_SKIP_PAGER]; + i++; + } + if (c->wstate.fullscreen == True) { + a[i] = ewmh_atom[_NET_WM_STATE_FULLSCREEN]; + i++; + } + if (c->wstate.above == True) { + a[i] = ewmh_atom[_NET_WM_STATE_ABOVE]; + i++; + } + if (c->wstate.below == True) { + a[i] = ewmh_atom[_NET_WM_STATE_BELOW]; + i++; + } + } + + XChangeProperty(dpy, c->window, ewmh_atom[_NET_WM_STATE], + XA_ATOM, 32, PropModeReplace, (unsigned char *)a, atoms); + if (a != NULL) free(a); + +} + +void +ewmh_set_allowed(Client *c) +{ +/* FIXME: this is dumb - the allowed actions should be calculuated + * but for now, anything goes. + */ + Atom action[4]; + + action[0] = ewmh_atom[_NET_WM_ACTION_MOVE]; + action[1] = ewmh_atom[_NET_WM_ACTION_RESIZE]; + action[2] = ewmh_atom[_NET_WM_ACTION_FULLSCREEN]; + action[3] = ewmh_atom[_NET_WM_ACTION_CLOSE]; + XChangeProperty(dpy, c->window, ewmh_atom[_NET_WM_ALLOWED_ACTIONS], + XA_ATOM, 32, PropModeReplace, (unsigned char *)action, 4); +} + +void +ewmh_set_strut(ScreenInfo *screen) { + Client *c; + EWMHStrut strut; + unsigned long data[4]; + /* FIXME: add parameter to MakeSane rather than this hack */ + Edge backup; + + /* find largest reserved areas */ + strut.left = 0; + strut.right = 0; + strut.top = 0; + strut.bottom = 0; + for (c = client_head(); c; c = c->next) { + if (c->screen != screen) continue; + if (c->strut.left > strut.left) strut.left = c->strut.left; + if (c->strut.right > strut.right) strut.right = c->strut.right; + if (c->strut.top > strut.top) strut.top = c->strut.top; + if (c->strut.bottom > strut.bottom) + strut.bottom = c->strut.bottom; + } + + /* if the reservered aread have not changed then we're done */ + if ( screen->strut.left == strut.left && + screen->strut.right == strut.right && + screen->strut.top == strut.top && + screen->strut.bottom == strut.bottom) return; + + /* apply the new strut */ + screen->strut.left = strut.left; + screen->strut.right = strut.right; + screen->strut.top = strut.top; + screen->strut.bottom = strut.bottom; + + /* set the new workarea */ + data[0] = strut.left; + data[1] = strut.top; + data[2] = screen->display_width - (strut.left + strut.right); + data[3] = screen->display_height - (strut.top + strut.bottom); + XChangeProperty(dpy, screen->root, + ewmh_atom[_NET_WORKAREA], + XA_CARDINAL, 32, PropModeReplace, + (unsigned char*)data, 4); + + /* ensure no window fully occupy reserved areas */ + for (c = client_head(); c; c = c->next) { + int x = c->size.x; + int y = c->size.y; + + if (c->wstate.fullscreen == True) continue; + backup = interacting_edge; + interacting_edge = ENone; + Client_MakeSane(c, ENone, &x, &y, 0, 0); + interacting_edge = backup; + if (c->framed == True) { + XMoveWindow(dpy, c->parent, + c->size.x, + c->size.y - titleHeight()); + } else { + XMoveWindow(dpy, c->parent, + c->size.x, c->size.y); + } + sendConfigureNotify(c); + } + +} + +/* + * get _NET_WM_STRUT and if it is available recalulate the screens + * reserved areas. the EWMH spec isn't clear about what we should do + * about hidden windows. It seems silly to reserve space for an invisible + * window, but the spec allows it. Ho Hum... jfc + */ +void +ewmh_get_strut(Client *c) { + Atom rt; + unsigned long *strut; + int fmt; + unsigned long n; + unsigned long extra; + int i; + + if (c == NULL) return; + i = XGetWindowProperty(dpy, c->window, + ewmh_atom[_NET_WM_STRUT], + 0, 5, False, XA_CARDINAL, &rt, &fmt, &n, &extra, + (unsigned char **)&strut); + if (i != Success || strut == NULL || n < 4) return; + c->strut.left = (unsigned int) strut[0]; + c->strut.right = (unsigned int) strut[1]; + c->strut.top = (unsigned int) strut[2]; + c->strut.bottom = (unsigned int) strut[3]; + ewmh_set_strut(c->screen); +} + +/* fix stack forces each window on the screen to be in the right place in + * the window stack as indicated in the EWMH spec version 1.2 (section 7.10). + */ +static void +fix_stack(ScreenInfo *screen) { + Client *c; + + /* this is pretty dumb. we should query the tree and only move + * those windows that require it. doing it regardless liek this + * causes the desktop to flicker + */ + + /* first lower clients with _NET_WM_STATE_BELOW */ + for (c = client_head(); c; c = c->next) { + if (c->wstate.below == False) continue; + Client_Lower(c); + } + + /* lower desktops - they are always the lowest */ + for (c = client_head(); c; c = c->next) { + if (c->wtype != WTypeDesktop) continue; + Client_Lower(c); + break; /* only one desktop, surely */ + } + + /* raise clients with _NET_WM_STATE_ABOVE and docks + * (unless marked with _NET_WM_STATE_BELOW) + */ + for (c = client_head(); c; c = c->next) { + if (!(c->wstate.above == True || + (c->wtype == WTypeDock && + c->wstate.below == False))) + continue; + Client_Raise(c); + } + + /* raise fullscreens - they're always on top */ + /* Misam Saki reports problems with this and believes fullscreens + * should not be automatically raised. + * + * However if the code below is removed then the panel is raised above + * fullscreens, which is not desirable. + */ + for (c = client_head(); c; c = c->next) { + if (c->wstate.fullscreen == False) continue; + Client_Raise(c); + } +} + + +static Bool +valid_for_client_list(ScreenInfo *screen, Client *c) { + if (c->screen != screen) return False; + if (c->state == WithdrawnState) return False; + return True; +} + +/* +* update_client_list updates the properties on the root window used by +* task lists and pagers. +* +* it should be called whenever the window stack is modified, or when clients +* are hidden or unhidden. +*/ +void +ewmh_set_client_list(ScreenInfo *screen) { + int no_clients=0; + Window *client_list=NULL; + Window *stacked_client_list=NULL; + Client *c; + + if (screen == NULL || screen->ewmh_set_client_list == True) return; + screen->ewmh_set_client_list = True; + fix_stack(screen); + for (c = client_head(); c; c = c->next) { + if (valid_for_client_list(screen, c) == True) no_clients++; + } + if (no_clients > 0) { + int i; + Window dw1; + Window dw2; + Window *wins; + unsigned int win; + unsigned int nwins; + + client_list = malloc(sizeof(Window) * no_clients); + i = no_clients - 1; /* array starts with oldest */ + for (c = client_head(); c; c = c->next) { + if (valid_for_client_list(screen, c) == True) { + client_list[i] = c->window; + i--; + if (i < 0) break; + } + } + + stacked_client_list = malloc(sizeof(Window) * no_clients); + i = 0; + XQueryTree(dpy, screen->root, &dw1, &dw2, &wins, &nwins); + for (win = 0; win < nwins; win++) { + c = Client_Get(wins[win]); + if (!c) continue; + if (valid_for_client_list(screen, c) == True) { + stacked_client_list[i] = c->window; + i++; + if (i >= no_clients) break; + } + } + if ( nwins > 0 ) XFree(wins); + + } + XChangeProperty(dpy, screen->root, + ewmh_atom[_NET_CLIENT_LIST], + XA_WINDOW, 32, PropModeReplace, + (unsigned char*)client_list, no_clients); + XChangeProperty(dpy, screen->root, + ewmh_atom[_NET_CLIENT_LIST_STACKING], + XA_WINDOW, 32, PropModeReplace, + (unsigned char*)stacked_client_list, no_clients); + if (no_clients > 0 ) { + free(client_list); + free(stacked_client_list); + } + screen->ewmh_set_client_list = False; +} diff --git a/ewmh.h b/ewmh.h new file mode 100644 index 0000000..a9fced1 --- /dev/null +++ b/ewmh.h @@ -0,0 +1,94 @@ +/* + * lwm, a window manager for X11 + * Copyright (C) 1997-2016 Elliott Hughes, James Carter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/** + * These are indexes into the ewmh_atom array. Only atoms actually supported + * by lwm should be included here because _NET_SUPPORTED is built from the + * ewmh_atom array. + */ + +typedef enum { +/* root window properties */ + _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_SUPPORTING_WM_CHECK, + /*_NET_VIRTUAL_ROOTS,*/ + /*_NET_DESKTOP_LAYOUT,*/ + /*_NET_SHOWING_DESKTOP,*/ +/* other root window messages */ + _NET_CLOSE_WINDOW, + _NET_MOVERESIZE_WINDOW, + _NET_WM_MOVERESIZE, +/* application window properties */ + _NET_WM_NAME, + /*_NET_WM_VISIBLE_NAME,*/ + /*_NET_WM_ICON_NAME,*/ + /*_NET_WM_VISIBLE_ICON_NAME,*/ + /*_NET_WM_DESKTOP,*/ + _NET_WM_WINDOW_TYPE, + _NET_WM_STATE, + _NET_WM_ALLOWED_ACTIONS, + _NET_WM_STRUT, + /*_NET_WM_ICON_GEOMETRY,*/ + /*_NET_WM_ICON,*/ + /*_NET_WM_PID,*/ + /*_NET_WM_HANDLED_ICONS,*/ +/* window types for _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, +/* window states for _NET_WM_STATE */ + /*_NET_WM_STATE_MODAL,*/ + /*_NET_WM_STATE_STICKY,*/ + /*_NET_WM_STATE_MAXIMISED_VERT,*/ + /*_NET_WM_STATE_MAXIMISED_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, +/* allowed actions for _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_HORIZ,*/ + /*_NET_WM_ACTION_MAXIMIZE_VERT,*/ + _NET_WM_ACTION_FULLSCREEN, + /*_NET_WM_ACTION_CHANGE_DESKTOP,*/ + _NET_WM_ACTION_CLOSE, + EWMH_ATOM_LAST +} EWMHAtom; + diff --git a/lwm b/lwm new file mode 100755 index 0000000..12e9c03 Binary files /dev/null and b/lwm differ diff --git a/lwm.c b/lwm.c new file mode 100644 index 0000000..662dfcd --- /dev/null +++ b/lwm.c @@ -0,0 +1,495 @@ +/* + * lwm, a window manager for X11 + * Copyright (C) 1997-2016 Elliott Hughes, James Carter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "lwm.h" + +Mode mode; /* The window manager's mode. (See "lwm.h".) */ +int start_x; /* The X position where the mode changed. */ +int start_y; /* The Y position where the mode changed. */ + +Display * dpy; /* The connection to the X server. */ +int screen_count; /* The number of screens. */ +ScreenInfo * screens; /* Information about these screens. */ +ScreenInfo * current_screen; + +XFontSet font_set = NULL; /* Font set for title var */ +XFontSetExtents *font_set_ext = NULL; +XFontSet popup_font_set = NULL; /* Font set for popups */ +XFontSetExtents *popup_font_set_ext = NULL; + +Bool shape; /* Does server have Shape Window extension? */ +int shape_event; /* ShapeEvent event type. */ + +/* Atoms we're interested in. See the ICCCM for more information. */ +Atom wm_state; +Atom wm_change_state; +Atom wm_protocols; +Atom wm_delete; +Atom wm_take_focus; +Atom wm_colormaps; +Atom compound_text; + +/** Netscape uses this to give information about the URL it's displaying. */ +Atom _mozilla_url; + +/* + * if we're really short of a clue we might look at motif hints, and + * we're not going to link with motif, so we'll have to do it by hand + */ +Atom motif_wm_hints; + +char *argv0; + +static void initScreens(void); +static void initScreen(int); + +char *font_name; /* User's choice of titlebar font. */ +char *popup_font_name; /* User's choice of menu font. */ +char *btn1_command; /* User's choice of button 1 command. */ +char *btn2_command; /* User's choice of button 2 command. */ +int border; /* User's choice of border size. */ +FocusMode focus_mode; /* User's choice of focus mode (default enter) */ + +void getMousePosition(int * x, int * y) { + Window root, child; + int t1, t2; + unsigned int b; + + /* It doesn't matter which root window we give this call. */ + XQueryPointer(dpy, screens[0].root, &root, &child, x, y, &t1, &t2, &b); + current_screen = getScreenFromRoot(root); +} + +void parseResources(void) { + /* Set our fall-back defaults. */ + font_name = DEFAULT_TITLE_FONT; + popup_font_name = DEFAULT_POPUP_FONT; + border = DEFAULT_BORDER; + btn1_command = 0; + btn2_command = DEFAULT_TERMINAL; + focus_mode = focus_enter; +} + +int main(int argc, char *argv[]) { + XEvent ev; + struct sigaction sa; + int dpy_fd, max_fd; + + argv0 = argv[0]; + + mode = wm_initialising; + + /* Open a connection to the X server. */ + dpy = XOpenDisplay(NULL); + if (dpy == 0) + panic("can't open display."); + + parseResources(); + + /* Set up an error handler. */ + XSetErrorHandler(errorHandler); + + /* Set up signal handlers. */ + signal(SIGTERM, Terminate); + signal(SIGINT, Terminate); + signal(SIGHUP, Terminate); + + /* Ignore SIGCHLD. */ + sa.sa_handler = SIG_IGN; +#ifdef SA_NOCLDWAIT + sa.sa_flags = SA_NOCLDWAIT; +#else + sa.sa_flags = 0; +#endif + sigemptyset(&sa.sa_mask); + sigaction(SIGCHLD, &sa, 0); + + /* Internalize useful atoms. */ + wm_state = XInternAtom(dpy, "WM_STATE", False); + wm_change_state = XInternAtom(dpy, "WM_CHANGE_STATE", False); + wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False); + wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wm_take_focus = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + wm_colormaps = XInternAtom(dpy, "WM_COLORMAP_WINDOWS", False); + compound_text = XInternAtom(dpy, "COMPOUND_TEXT", False); + + _mozilla_url = XInternAtom(dpy, "_MOZILLA_URL", False); + + motif_wm_hints = XInternAtom(dpy, "_MOTIF_WM_HINTS", False); + + ewmh_init(); + + /* + * Get fonts for our titlebar and our popup window. We try to + * get Lucida, but if we can't we make do with fixed because everyone + * has that. + */ + { + /* FIXME: do these need to be freed? */ + char **missing; + char *def; + int missing_count; + + font_set = XCreateFontSet(dpy, font_name, + &missing, &missing_count, &def); + if (font_set == NULL) + font_set = XCreateFontSet(dpy, "fixed", + &missing, &missing_count, &def); + if (font_set == NULL) + panic("unable to create font set for title font"); + if (missing_count > 0) + fprintf(stderr,"%s: warning: missing %d charset" + "%s for title font\n", argv0, missing_count, + (missing_count == 1)?"":"s"); + font_set_ext = XExtentsOfFontSet(font_set); + + popup_font_set = XCreateFontSet(dpy, popup_font_name, + &missing, &missing_count, &def); + if (popup_font_set == NULL) + popup_font_set = XCreateFontSet(dpy, "fixed", + &missing, &missing_count, &def); + if (popup_font_set == NULL) + panic("unable to create font set for popup font"); + if (missing_count > 0) + fprintf(stderr,"%s: warning: missing %d charset" + "%s for popup font\n", argv0, missing_count, + (missing_count == 1)?"":"s"); + popup_font_set_ext = XExtentsOfFontSet(popup_font_set); + } + + initScreens(); + ewmh_init_screens(); + + /* See if the server has the Shape Window extension. */ + shape = serverSupportsShapes(); + + /* + * Initialisation is finished, but we start off not interacting with the + * user. + */ + mode = wm_idle; + + /* + * The main event loop. + */ + dpy_fd = ConnectionNumber(dpy); + max_fd = dpy_fd + 1; + for (;;) { + fd_set readfds; + + FD_ZERO(&readfds); + FD_SET(dpy_fd, &readfds); + if (select(max_fd, &readfds, NULL, NULL, NULL) > -1) { + if (FD_ISSET(dpy_fd, &readfds)) { + while (XPending(dpy)) { + XNextEvent(dpy, &ev); + dispatch(&ev); + } + } + } + } +} + +void +sendConfigureNotify(Client *c) { + XConfigureEvent ce; + + ce.type = ConfigureNotify; + ce.event = c->window; + ce.window = c->window; + if (c->framed == True) { + ce.x = c->size.x + border; + ce.y = c->size.y + border; + ce.width = c->size.width - 2 * border; + ce.height = c->size.height - 2 * border; + ce.border_width = c->border; + } else { + ce.x = c->size.x; + ce.y = c->size.y; + ce.width = c->size.width; + ce.height = c->size.height; + ce.border_width = c->border; + } + ce.above = None; + ce.override_redirect = 0; + XSendEvent(dpy, c->window, False, StructureNotifyMask, (XEvent *) &ce); +} + +extern void +scanWindowTree(int screen) { + unsigned int i; + unsigned int nwins; + Client * c; + Window dw1; + Window dw2; + Window * wins; + XWindowAttributes attr; + + XQueryTree(dpy, screens[screen].root, &dw1, &dw2, &wins, &nwins); + for (i = 0; i < nwins; i++) { + XGetWindowAttributes(dpy, wins[i], &attr); + if (attr.override_redirect /*|| isShaped(wins[i])*/ || wins[i] == screens[screen].popup) + continue; + c = Client_Add(wins[i], screens[screen].root); + if (c != 0 && c->window == wins[i]) { + c->screen = &screens[screen]; + c->size.x = attr.x; + c->size.y = attr.y; +/* we'll leave it until it's managed + if (c->framed == True) { + c->size.x -= border; + c->size.y -= border; + } +*/ + c->size.width = attr.width; + c->size.height = attr.height; +/* we'll leave it until it's managed + if (c->framed == True) { + c->size.width += 2 * border; + c->size.height += 2 * border; + } +*/ + c->border = attr.border_width; + if (attr.map_state == IsViewable) { + c->internal_state = IPendingReparenting; + manage(c, 1); + } + } + } + XFree(wins); +} + +/*ARGSUSED*/ +extern void +shell(ScreenInfo * screen, int button, int x, int y) { + char * command = NULL; + char * sh; + + /* Get the command we're to execute. Give up if there isn't one. */ + if (button == Button1) + command = btn1_command; + if (button == Button2) + command = btn2_command; + if (command == NULL) + return; + + sh = getenv("SHELL"); + if (sh == 0) + sh = "/bin/sh"; + + switch (fork()) { + case 0: /* Child. */ + close(ConnectionNumber(dpy)); + if (screen && screen->display_spec != 0) + putenv(screen->display_spec); + execl(sh, sh, "-c", command, NULL); + fprintf(stderr, "%s: can't exec \"%s -c %s\"\n", argv0, sh, + command); + execlp("xterm", "xterm", NULL); + exit(EXIT_FAILURE); + case -1: /* Error. */ + fprintf(stderr, "%s: couldn't fork\n", argv0); + break; + } +} + +extern int +titleHeight(void) { + return font_set_ext->max_logical_extent.height; +} + +extern int +ascent(XFontSetExtents *font_set_ext) { + return abs(font_set_ext->max_logical_extent.y); +} + +extern int +popupHeight(void) { + return popup_font_set_ext->max_logical_extent.height; +} + +extern int +titleWidth(XFontSet font_set, Client *c) { + XRectangle ink; + XRectangle logical; + char *name; + int namelen; + + if (c == NULL) return 0; + if (c->menu_name == NULL) { + name = c->name; + namelen = c->namelen; + } else { + name = c->menu_name; + namelen = c->menu_namelen; + } + if (name == NULL) return 0; +#ifdef X_HAVE_UTF8_STRING + if (c->name_utf8 == True) + Xutf8TextExtents(font_set, name, namelen, + &ink, &logical); + else +#endif + XmbTextExtents(font_set, name, namelen, + &ink, &logical); + + return logical.width; +} + +extern int +popupWidth(char *string, int string_length) { + XRectangle ink; + XRectangle logical; + + XmbTextExtents(popup_font_set, string, string_length, + &ink, &logical); + + return logical.width; +} + +static void +initScreens(void) { + int screen; + + /* Find out how many screens we've got, and allocate space for their info. */ + screen_count = ScreenCount(dpy); + screens = (ScreenInfo *) malloc(screen_count * sizeof(ScreenInfo)); + + /* Go through the screens one-by-one, initialising them. */ + for (screen = 0; screen < screen_count; screen++) { + initialiseCursors(screen); + initScreen(screen); + scanWindowTree(screen); + } +} + +static void +initScreen(int screen) { + XGCValues gv; + XSetWindowAttributes attr; + XColor colour, exact; + int len; + char * display_string = DisplayString(dpy); + char * colon = strrchr(display_string, ':'); + char * dot = NULL; + + /* Set the DISPLAY specification. */ + if (colon) { + dot = strrchr(colon, '.'); + len = 9 + strlen(display_string) + ((dot == 0) ? 2 : 0) + 10; + screens[screen].display_spec = (char *) malloc(len); + sprintf(screens[screen].display_spec, "DISPLAY=%s", display_string); + if (dot == 0) dot = screens[screen].display_spec + len - 3; + else dot = strrchr(screens[screen].display_spec, '.'); + sprintf(dot, ".%i", screen); + } else { + screens[screen].display_spec = 0; + } + + /* Find the root window. */ + screens[screen].root = RootWindow(dpy, screen); + screens[screen].display_width = DisplayWidth(dpy, screen); + screens[screen].display_height = DisplayHeight(dpy, screen); + screens[screen].strut.left = 0; + screens[screen].strut.right = 0; + screens[screen].strut.top = 0; + screens[screen].strut.bottom = 0; + + /* Get the pixel values of the only two colours we use. */ + screens[screen].black = BlackPixel(dpy, screen); + screens[screen].white = WhitePixel(dpy, screen); + XAllocNamedColor(dpy, DefaultColormap(dpy, screen), "DimGray", &colour, &exact); + screens[screen].gray = colour.pixel; + + /* Set up root (frame) GC's. */ + gv.foreground = screens[screen].black ^ screens[screen].white; + gv.background = screens[screen].white; + gv.function = GXxor; + gv.line_width = 1; + gv.subwindow_mode = IncludeInferiors; + screens[screen].gc_thin = XCreateGC(dpy, screens[screen].root, + GCForeground | GCBackground | GCFunction | + GCLineWidth | GCSubwindowMode, &gv); + + gv.line_width = 2; + screens[screen].gc = XCreateGC(dpy, screens[screen].root, + GCForeground | GCBackground | GCFunction | + GCLineWidth | GCSubwindowMode, &gv); + + /* Create a window for our popup. */ + screens[screen].popup = XCreateSimpleWindow(dpy, screens[screen].root, + 0, 0, 1, 1, 1, screens[screen].black, screens[screen].white); + attr.event_mask = ButtonMask | ButtonMotionMask | ExposureMask; + XChangeWindowAttributes(dpy, screens[screen].popup, CWEventMask, &attr); + + /* Create menu GC. */ + gv.line_width = 1; + screens[screen].menu_gc = XCreateGC(dpy, screens[screen].popup, + GCForeground | GCBackground | GCFunction | + GCLineWidth | GCSubwindowMode, &gv); + + /* Create size indicator GC. */ + gv.foreground = screens[screen].black; + gv.function = GXcopy; + screens[screen].size_gc = XCreateGC(dpy, screens[screen].popup, + GCForeground | GCBackground | GCFunction | + GCLineWidth | GCSubwindowMode, &gv); + + /* Announce our interest in the root window. */ + attr.cursor = screens[screen].root_cursor; + attr.event_mask = SubstructureRedirectMask | SubstructureNotifyMask | + ColormapChangeMask | ButtonPressMask | PropertyChangeMask | + EnterWindowMask; + XChangeWindowAttributes(dpy, screens[screen].root, CWCursor | + CWEventMask, &attr); + + /* Make sure all our communication to the server got through. */ + XSync(dpy, False); +} + +/** +Find the screen for which root is the root window. +*/ +ScreenInfo * +getScreenFromRoot(Window root) { + int screen; + + for (screen = 0; screen < screen_count; screen++) + if (screens[screen].root == root) + return &screens[screen]; + + return 0; +} diff --git a/lwm.h b/lwm.h new file mode 100644 index 0000000..136350b --- /dev/null +++ b/lwm.h @@ -0,0 +1,347 @@ +/* + * lwm, a window manager for X11 + * Copyright (C) 1997-2016 Elliott Hughes, James Carter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* --- Administrator-configurable defaults. --- */ + +#define DEFAULT_TITLE_FONT "-*-lucida-bold-r-normal-sans-14-*-*-*-p-*-iso10646-1" +#define DEFAULT_POPUP_FONT "-*-lucida-medium-r-normal-sans-12-*-*-*-p-*-iso10646-1" +#define DEFAULT_TERMINAL "xterm" +#define DEFAULT_BORDER 6 + +#define HIDE_BUTTON Button3 +#define MOVE_BUTTON Button2 +#define RESHAPE_BUTTON Button1 + +#define EDGE_RESIST 32 + +/* --- End of administrator-configurable defaults. --- */ + +/* + * Window manager mode. wm is in one of five modes: it's either getting + * user input to move/reshape a window, getting user input to make a + * selection from the menu, waiting for user input to confirm a window close + * (by releasing a mouse button after prssing it in a window's box), + * waiting for user input to confirm a window hide (by releasing a mouse + * button after prssing it in a window's frame), + * or it's `idle' --- responding to events arriving + * from the server, but not directly interacting with the user. + * OK, so I lied: there's a sixth mode so that we can tell when wm's still + * initialising. + */ +typedef enum { + wm_initialising, + wm_idle, + wm_reshaping, + wm_menu_up, + wm_closing_window, + wm_hiding_window +} Mode; + +/** Window internal state. Yuck. */ +typedef enum { + IPendingReparenting, INormal +} IState ; + + +/** +* Focus mode, may me (and defaults to) enter where entering a window gives +* that window input focus, or click where a window must be explicitly clicked +* to give it the focus. +*/ +typedef enum { + focus_enter, focus_click +} FocusMode; + +/** +* Window edge, used in resizing. The `edge' ENone is used to signify a +* window move rather than a resize. The code is sufficiently similar that +* this isn't a special case to be treated separately. +*/ +typedef enum { + ETopLeft, ETop, ETopRight, + ERight, ENone, ELeft, + EBottomLeft, EBottom, EBottomRight, + E_LAST +} Edge ; + +/** +* EWMH direction for _NET_WM_MOVERESIZE +*/ +typedef enum { + DSizeTopLeft, DSizeTop, DSizeTopRight, + DSizeRight, DSizeBottomRight, DSizeBottom, + DSizeBottomLeft, DSizeLeft, DMove, + DSizeKeyboard, DMoveKeyboard +} EWMHDirection; + +/** +* EWMH window type. See section 5.6 of the EWMH specification (1.2). +* WTypeNone indicates that no EWMH window type as been set and MOTIF +* hints should be used instead. +*/ +typedef enum { + WTypeDesktop, WTypeDock, WTypeToolbar, + WTypeMenu, WTypeUtility, WTypeSplash, + WTypeDialog, WTypeNormal, WTypeNone +} EWMHWindowType; + +/** +* EWMH window state, See section 5.7 of the EWMH specification (1.2). +* lwm does not support all states. _NET_WM_STATE_HIDDEN is taken from +* Client.hidden. +*/ +typedef struct { + Bool skip_taskbar; + Bool skip_pager; + Bool fullscreen; + Bool above; + Bool below; +} EWMHWindowState; + +/** +* EWMH "strut", or area on each edge of the screen reserved for docking +* bars/panels. +*/ +typedef struct { + unsigned int left; + unsigned int right; + unsigned int top; + unsigned int bottom; +} EWMHStrut; + +/** +* Screen information. +*/ +typedef struct ScreenInfo ScreenInfo; +struct ScreenInfo { + Window root; + Window popup; + Window ewmh_compat; + + int display_width; /* The width of the screen. */ + int display_height; /* The height of the screen. */ + EWMHStrut strut; /* reserved areas */ + + GC gc; /* The default GC. */ + GC gc_thin; /* The default GC but with thinner lines. */ + GC menu_gc; /* The GC for the popup window (menu). */ + GC size_gc; /* The GC for the popup window (sizing). */ + + unsigned long black; /* Black pixel value. */ + unsigned long white; /* White pixel value. */ + unsigned long gray; /* Gray pixel value. */ + + Cursor root_cursor; + Cursor box_cursor; + + Cursor cursor_map[E_LAST]; + + Bool ewmh_set_client_list; /* hack to prevent recursion */ + + char * display_spec; +}; + +typedef struct Client Client; +struct Client { + Window window; /* Client's window. */ + Window parent; /* Window manager frame. */ + Window trans; /* Window that client is a transient for. */ + + Bool framed; /* True is lwm is maintaining a frame */ + + Client * next; /* Next window in client list. */ + + int border; /* Client's original border width. */ + + XSizeHints size; /* Client's current geometry information. */ + XSizeHints return_size; /* Client's old geometry information. */ + int state; /* Window state. See ICCCM and */ + + Bool hidden; /* True if this client is hidden. */ + IState internal_state; + int proto; + + int accepts_focus; /* Does this window want keyboard events? */ + + char * name; /* Name used for title in frame. */ + int namelen; + char * menu_name; /* Name used in root popup */ + int menu_namelen; + Bool name_utf8; + + ScreenInfo * screen; + + Edge cursor; /* indicates which cursor is being used for parent window */ + + EWMHWindowType wtype; + EWMHWindowState wstate; + EWMHStrut strut; /* reserved areas */ + + /* Colourmap scum. */ + Colormap cmap; + int ncmapwins; + Window * cmapwins; + Colormap * wmcmaps; +}; + + +/* + * c->proto is a bitarray of these + */ +enum { + Pdelete = 1, + Ptakefocus = 2 +}; + +/* + * This should really have been in X.h --- if you select both ButtonPress + * and ButtonRelease events, the server makes an automatic grab on the + * pressed button for you. This is almost always exactly what you want. + */ +#define ButtonMask (ButtonPressMask | ButtonReleaseMask) + +/* lwm.c */ +extern Mode mode; +extern int start_x; +extern int start_y; +extern Display * dpy; +extern int screen_count; +extern ScreenInfo * screens; +extern ScreenInfo * current_screen; +extern XFontSet font_set; +extern XFontSetExtents *font_set_ext; +extern XFontSet popup_font_set; +extern XFontSetExtents *popup_font_set_ext; +extern Atom _mozilla_url; +extern Atom motif_wm_hints; +extern Atom wm_state; +extern Atom wm_change_state; +extern Atom wm_protocols; +extern Atom wm_delete; +extern Atom wm_take_focus; +extern Atom wm_colormaps; +extern Atom compound_text; +extern Bool shape; +extern int shape_event; +extern char *argv0; +extern void shell(ScreenInfo *, int, int, int); +extern void sendConfigureNotify(Client *); +extern int titleHeight(void); +extern int titleWidth(XFontSet font_set, Client *c); +extern int popupHeight(void); +extern int popupWidth(char *string, int string_length); +extern int ascent(XFontSetExtents *font_set_ext); +extern ScreenInfo * getScreenFromRoot(Window); +extern void scanWindowTree(int); + +/* client.c */ +extern Client *client_head(void); +extern Edge interacting_edge; +extern Client *Client_Get(Window); +extern Client *Client_Add(Window, Window); +extern void Client_MakeSane(Client *, Edge, int *, int *, int *, int *); +extern void Client_DrawBorder(Client *, int); +extern void setactive(Client *, int, long); +extern void size_expose(void); +extern void Client_ReshapeEdge(Client *, Edge); +extern void Client_Move(Client*); +extern void Client_SetState(Client *, int); +extern void Client_Raise(Client *); +extern void Client_Lower(Client *); +extern void Client_Close(Client *); +extern void Client_Remove(Client *); +extern void Client_FreeAll(void); +extern void Client_ColourMap(XEvent*); +extern void Client_EnterFullScreen(Client *c); +extern void Client_ExitFullScreen(Client *c); +extern void Client_Focus(Client *c, Time time); +extern void Client_ResetAllCursors(void); +extern void Client_Name(Client *c, const char *name, Bool is_utf8); +extern int hidden(Client *); +extern int withdrawn(Client *); +extern int normal(Client *); +extern void update_client_list(ScreenInfo *screen); +extern Client *current; + +/* cursor.c */ +extern Cursor getEdgeCursor(Edge edge); +extern void initialiseCursors(int); + +/* disp.c */ +extern void dispatch(XEvent *); +extern void reshaping_motionnotify(XEvent *); + +/* error.c */ +extern int ignore_badwindow; +extern int errorHandler(Display *, XErrorEvent *); +extern void panic(char*); + +/* manage.c */ +extern void getWindowName(Client *); +extern void getNormalHints(Client *); +extern Bool motifWouldDecorate(Client *); +extern void manage(Client *, int); +extern void withdraw(Client *); +extern void cmapfocus(Client *); +extern void getColourmaps(Client *); +extern void getTransientFor(Client *); +extern void Terminate(int); + +/* mouse.c */ +extern void getMousePosition(int *, int *); +extern void hide(Client *); +extern void unhidec(Client *, int); +extern int menu_whichitem(int, int); +extern void menuhit(XButtonEvent *); +extern void unhide(int, int); +extern void menu_expose(void); +extern void menu_motionnotify(XEvent *); +extern void menu_buttonrelease(XEvent *); + +/* shape.c */ +extern int shapeEvent(XEvent *); +extern int serverSupportsShapes(void); +extern int isShaped(Window); +extern void setShape(Client *); + +/* resource.c */ +extern char *font_name; +extern char *popup_font_name; +extern char *btn1_command; +extern char *btn2_command; +extern int border; +extern FocusMode focus_mode; +extern char * sdup(char *); +extern void parseResources(void); + +/* ewmh.c */ +extern Atom ewmh_atom[]; +extern void ewmh_init(void); +extern void ewmh_init_screens(void); +extern EWMHWindowType ewmh_get_window_type(Window w); +extern Bool ewmh_get_window_name(Client *c); +extern Bool ewmh_hasframe(Client *c); +extern void ewmh_set_state(Client *c); +extern void ewmh_get_state(Client *c); +extern void ewmh_change_state(Client *c, unsigned long action, + unsigned long atom); +extern void ewmh_set_allowed(Client *c); +extern void ewmh_set_client_list(ScreenInfo *screen); +extern void ewmh_get_strut(Client *c); +extern void ewmh_set_strut(ScreenInfo *screen); diff --git a/lwm.man b/lwm.man new file mode 100644 index 0000000..a527498 --- /dev/null +++ b/lwm.man @@ -0,0 +1,96 @@ +.\" lwm, a window manager for X11 +.\" Copyright (C) 1997-2016 Elliott Hughes, James Carter +.\" +.\" This program is free software; you can redistribute it and/or +.\" modify it under the terms of the GNU General Public License +.\" as published by the Free Software Foundation; either version 2 +.\" of the License, or (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\" +.TH LWM 1 +.SH NAME +lwm \- Lightweight Window Manager for the X Window System +.SH SYNTAX +\fBlwm \fP[ \fB\-s\fP \fIsession-id\fP ] +.SH DESCRIPTION +\fILwm\fP is a window manager for the X Window System. It provides enough +features to allow the user to manage their windows, and no more. +.PP +Windows are surrounded by a frame with a +titlebar at the top next to a small box. The frame is a grey colour for +all windows except that which has the input focus, where it is black. +.PP +In the default configuration, \fIlwm\fP uses the enter-to-focus scheme, where +moving the pointer into a window gives that window the input focus. +\fILwm\fP may also be configured to use the click-to-focus scheme, where a +window must be clicked on (with any button) to receive the input focus. Clicking +on a window in this mode causes the window to be raised. Note that a click +used to focus a window is always swallowed by \fIlwm\fP, so clicking a +button in a new window requires two clicks. +.PP +A button 1 click on a window frame brings that window to the top. Dragging +button 1 on the frame of a resizable window repositions that edge of +the window. If a corner rather than an edge is dragged, then both edges +forming the corner are repositioned. While you're reshaping a window, +a little window pops up to show you the window's current size. +.PP +In the default configuration, button 1 on the root window does nothing. +.PP +Button 2 is used to drag a window by its frame, repositioning the window +but maintaining its position in the window stack. +.PP +In the default configuration, button 2 on the root window brings up a +new shell. +.PP +A button 3 click on a window frame hides that window. Pressing +button 3 on the root window brings up a menu of all the hidden windows. +Releasing the button while over an item will unhide the named window. +.PP +A button 3 click in the frame while Shift is held down pushes the window +to the back, under any other windows. (Users with 4-button mice are +encouraged to use their fourth button for this function.) +.PP +A click with any button inside the little white box in a window's frame +can be used to close the window. +.SH OPTIONS +\fILwm\fP accepts the following command line options: +.PP +.TP 8 +.B \-s +specifies a client ID for the X Session Management system, and is used +exclusively by session managers. +.SH RESOURCES +\fILwm\fP understands the following X resources: +.TP 12 +.B titlefont +font used in window titles +.TP 12 +.B popupFont +font used in popup window (menu/size indicator) +.TP 12 +.B border +width in pixels of window borders +.TP 12 +.B button1 +program spawned when button 1 is clicked on the root window +.TP 12 +.B button2 +program spawned when button 2 is clicked on the root window +.TP 12 +.B focus +focus mode, one of "enter" for enter-to-focus (or sloppy focus), or +"click" for click-to-focus +.SH "SEE ALSO" +.PP +X(7) +.SH AUTHORS +Elliott Hughes , +James Carter diff --git a/manage.c b/manage.c new file mode 100644 index 0000000..9f8e4f9 --- /dev/null +++ b/manage.c @@ -0,0 +1,609 @@ +/* + * lwm, a window manager for X11 + * Copyright (C) 1997-2016 Elliott Hughes, James Carter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +#include +#include +#include +#include + +/* These are Motif definitions from Xm/MwmUtil.h, but Motif isn't available + everywhere. */ +#define MWM_HINTS_FUNCTIONS (1L << 0) +#define MWM_HINTS_DECORATIONS (1L << 1) +#define MWM_HINTS_INPUT_MODE (1L << 2) +#define MWM_HINTS_STATUS (1L << 3) +#define MWM_DECOR_ALL (1L << 0) +#define MWM_DECOR_BORDER (1L << 1) +#define MWM_DECOR_RESIZEH (1L << 2) +#define MWM_DECOR_TITLE (1L << 3) +#define MWM_DECOR_MENU (1L << 4) +#define MWM_DECOR_MINIMIZE (1L << 5) +#define MWM_DECOR_MAXIMIZE (1L << 6) + +#include "lwm.h" + +static int getProperty(Window, Atom, Atom, long, unsigned char **); +static int getWindowState(Window, int *); +static void applyGravity(Client *); + +/*ARGSUSED*/ +void +manage(Client * c, int mapped) +{ + int state; + XWMHints * hints; + XWindowAttributes current_attr; + XSetWindowAttributes attr; + + /* For WM_PROTOCOLS handling. */ + Atom * protocols; + int n; + int p; + + /* Where auto-placement is going to put the next window. */ + static int auto_x = 100; + static int auto_y = 100; + + /* get the EWMH window type, as this might overrule some hints */ + c->wtype = ewmh_get_window_type(c->window); + /* get in the initial EWMH state */ + ewmh_get_state(c); + /* set EWMH allowable actions, now we intend to manage this window */ + ewmh_set_allowed(c); + /* is this window to have a frame? */ + if (c->wtype == WTypeNone) { + /* this breaks the ewmh spec (section 5.6) because in the + * absence of a _NET_WM_WINDOW_TYPE, _WM_WINDOW_TYPE_NORMAL + * must be taken. bummer. + */ + c->framed = motifWouldDecorate(c); + } else { + c->framed = ewmh_hasframe(c); + } + if (isShaped(c->window)) c->framed = False; + + /* get the EWMH strut - if there is one */ + ewmh_get_strut(c); + + /* + * Get the hints, window name, and normal hints (see ICCCM + * section 4.1.2.3). + */ + hints = XGetWMHints(dpy, c->window); + + getWindowName(c); + getNormalHints(c); + + /* + * Get the colourmaps associated with this window. Get the window + * attribute colourmap first, then look to see if the + * WM_COLORMAP_WINDOWS property has been used to specify + * windows needing colourmaps that differ from the top-level + * colourmap. (See ICCCM section 4.1.8.) + */ + XGetWindowAttributes(dpy, c->window, ¤t_attr); + c->cmap = current_attr.colormap; + + getColourmaps(c); + + /* + * Scan the list of atoms on WM_PROTOCOLS to see which of the + * protocols that we understand the client is prepared to + * participate in. (See ICCCM section 4.1.2.7.) + */ + if (XGetWMProtocols(dpy, c->window, &protocols, &n) != 0) { + for (p = 0; p < n; p++) { + if (protocols[p] == wm_delete) { + c->proto |= Pdelete; + } else if (protocols[p] == wm_take_focus) { + c->proto |= Ptakefocus; + } + } + + XFree(protocols); + } + + /* Get the WM_TRANSIENT_FOR property (see ICCCM section 4.1.2.6). */ + getTransientFor(c); + + /* Work out details for the Client structure from the hints. */ + if (hints && (hints->flags & InputHint)) + c->accepts_focus = hints->input; + if (c->proto | Ptakefocus) + /* WM_TAKE_FOCUS overrides normal hints */ + c->accepts_focus = True; + + if (!getWindowState(c->window, &state)) + state = hints ? hints->initial_state : NormalState; + + /* + * Sort out the window's position. + */ + { + Window root_window; + int x, y; + unsigned int w, h; + unsigned int border_width, depth; + + XGetGeometry(dpy, c->window, &root_window, &x, &y, &w, &h, + &border_width, &depth); + + /* + * Do the size first. + * + * "The size specifiers refer to the width and height of the + * client excluding borders" -- ICCCM 4.1.2.3. + */ + c->size.width = w; + c->size.height = h; + if (c->framed == True) { + c->size.width += 2 * border; + c->size.height += 2 * border; + } + + /* + * THIS IS A HACK! + * + * OpenGL programs have a habit of appearing smaller than their + * minimum sizes, which they don't like. + */ + if (c->size.width < c->size.min_width) + c->size.width = c->size.min_width; + if (c->size.height < c->size.min_height) + c->size.height = c->size.min_height; + + /* Do the position next. */ + + /* + * If we have a user-specified position for a top-level window, + * or a program-specified position for a dialogue box, we'll + * take it. We'll also just take it during initialisation, + * since the previous manage probably placed its windows + * sensibly. + */ + if (c->trans != None && c->size.flags & PPosition) { + /* It's a "dialogue box". Trust it. */ + c->size.x = x; + c->size.y = y; + } else if ((c->size.flags & USPosition) || + c->framed == False || mode == wm_initialising ) { + /* Use the specified window position. */ + c->size.x = x; + c->size.y = y; + + /* + * We need to be careful of the right-hand edge and + * bottom. We can use the window gravity (if specified) + * to handle this. (See section 4.1.2.3 of the ICCCM.) + */ + applyGravity(c); + } else { + /* No position was specified: use the auto-placement + * heuristics. */ + + /* firstly, make sure auto_x and auto_y are outside + * strut */ + if (auto_x < c->screen->strut.left) + auto_x = c->screen->strut.left; + if (auto_y < c->screen->strut.top) + auto_y = c->screen->strut.top; + + if ((auto_x + c->size.width) > + (c->screen->display_width - + c->screen->strut.right) && + (c->size.width <= + (c->screen->display_width - + c->screen->strut.left - + c->screen->strut.right))) { + /* + * If the window wouldn't fit using normal + * auto-placement but is small enough to fit + * horizontally, then centre the window + * horizontally. + */ + c->size.x = (c->screen->display_width + - c->size.width) / 2; + auto_x = c->screen->strut.left + 20; + } else { + c->size.x = auto_x; + auto_x += 10; + if (auto_x > (c->screen->display_width / 2)) + auto_x = c->screen->strut.left + 20; + } + + if (((auto_y + c->size.height) > + (c->screen->display_height - + c->screen->strut.bottom)) && + (c->size.height <= + (c->screen->display_height - + c->screen->strut.top - + c->screen->strut.bottom))) { + /* + * If the window wouldn't fit using normal + * auto-placement but is small enough to fit + * vertically, then centre the window + * vertically. + */ + c->size.y = (c->screen->display_height + - c->size.height) / 2; + auto_y = c->screen->strut.top + 20; + } else { + c->size.y = auto_y; + auto_y += 10; + if (auto_y > (c->screen->display_height / 2)) + auto_y = c->screen->strut.top + 20; + } + } + } + + if (hints) + XFree(hints); + + /* + * Do all the reparenting and stuff. + */ + + if (c->framed == True) { + c->parent = XCreateSimpleWindow(dpy, c->screen->root, + c->size.x, c->size.y - titleHeight(), + c->size.width, c->size.height + titleHeight(), + 1, c->screen->black, c->screen->white); + + attr.event_mask = ExposureMask | EnterWindowMask | ButtonMask | + SubstructureRedirectMask | SubstructureNotifyMask | + PointerMotionMask; + XChangeWindowAttributes(dpy, c->parent, CWEventMask, &attr); + + XResizeWindow(dpy, c->window, c->size.width - 2 * border, + c->size.height - 2 * border); + } + + /* + * Stupid X11 doesn't let us change border width in the above + * call. It's a window attribute, but it's somehow second-class. + * + * As pointed out by Adrian Colley, we can't change the window + * border width at all for InputOnly windows. + */ + if (current_attr.class != InputOnly) + XSetWindowBorderWidth(dpy, c->window, 0); + + attr.event_mask = ColormapChangeMask | EnterWindowMask | + PropertyChangeMask | FocusChangeMask; + attr.win_gravity = StaticGravity; + attr.do_not_propagate_mask = ButtonMask; + XChangeWindowAttributes(dpy, c->window, + CWEventMask | CWWinGravity | CWDontPropagate, &attr); + + if (c->framed == True) { + XReparentWindow(dpy, c->window, c->parent, + border, border + titleHeight()); + } else { + XReparentWindow(dpy, c->window, c->parent, + c->size.x, c->size.y); + } + + setShape(c); + + XAddToSaveSet(dpy, c->window); + if (state == IconicState) { + } else { + /* Map the new window in the relevant state. */ + c->hidden = False; + XMapWindow(dpy, c->parent); + XMapWindow(dpy, c->window); + setactive(c, (focus_mode == focus_click) ? 1 : 0, 0L); + Client_SetState(c, NormalState); + } + + if (c->wstate.fullscreen == True) Client_EnterFullScreen(c); + + if (current != c) + cmapfocus(current); +} + +static void +applyGravity(Client *c) { + if (c->framed == False) return; /* only required for framed windows*/ + if (c->size.flags & PWinGravity) { + switch (c->size.win_gravity) { + case NorthEastGravity: + c->size.x -= 2 * border; + break; + case SouthWestGravity: + c->size.y -= 2 * border; + break; + case SouthEastGravity: + c->size.x -= 2 * border; + c->size.y -= 2 * border; + break; + } + } +} + +void +getTransientFor(Client *c) { + Window trans = None; + + XGetTransientForHint(dpy, c->window, &trans); + c->trans = trans; +} + +void +withdraw(Client *c) { + if (c->parent != c->screen->root) { + XUnmapWindow(dpy, c->parent); + XReparentWindow(dpy, c->parent, c->screen->root, c->size.x, c->size.y); + } + + XRemoveFromSaveSet(dpy, c->window); + Client_SetState(c, WithdrawnState); + + /* + * Flush and ignore any errors. X11 sends us an UnmapNotify before it + * sends us a DestroyNotify. That means we can get here without knowing + * whether the relevant window still exists. + */ + ignore_badwindow = 1; + XSync(dpy, False); + ignore_badwindow = 0; +} + +static void +installColourmap(Colormap cmap) { + if (cmap == None) + cmap = DefaultColormap(dpy, DefaultScreen(dpy)); + XInstallColormap(dpy, cmap); +} + +void +cmapfocus(Client * c) { + int i; + int found; + Client *cc; + + if (c == 0) + installColourmap(None); + else if (c->ncmapwins != 0) { + found = 0; + for (i = c->ncmapwins - 1; i >= 0; i--) { + installColourmap(c->wmcmaps[i]); + if (c->cmapwins[i] == c->window) + found++; + } + if (!found) + installColourmap(c->cmap); + } else if (c->trans != None && (cc = Client_Get(c->trans)) != 0 && + cc->ncmapwins != 0) + cmapfocus(cc); + else + installColourmap(c->cmap); +} + +void +getColourmaps(Client *c) { + int n; + int i; + Window *cw; + XWindowAttributes attr; + + if (c == 0) + return; + + n = getProperty(c->window, wm_colormaps, XA_WINDOW, 100L, (unsigned char **) &cw); + if (c->ncmapwins != 0) { + XFree(c->cmapwins); + free(c->wmcmaps); + } + if (n <= 0) { + c->ncmapwins = 0; + return; + } + c->ncmapwins = n; + c->cmapwins = cw; + + c->wmcmaps = (Colormap *) malloc(n * sizeof(Colormap)); + for (i = 0; i < n; i++) { + if (cw[i] == c->window) { + c->wmcmaps[i] = c->cmap; + } else { + XSelectInput(dpy, cw[i], ColormapChangeMask); + XGetWindowAttributes(dpy, cw[i], &attr); + c->wmcmaps[i] = attr.colormap; + } + } +} + +/*ARGSUSED*/ +void +Terminate(int signal) { + /* Set all clients free. */ + Client_FreeAll(); + + /* Give up the input focus and the colourmap. */ + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + installColourmap(None); + + XCloseDisplay(dpy); + exit(signal ? EXIT_FAILURE : EXIT_SUCCESS); +} + + +static int +getProperty(Window w, Atom a, Atom type, long len, unsigned char **p) { + Atom real_type; + int format; + unsigned long n; + unsigned long extra; + int status; + + /* + * len is in 32-bit multiples. + */ + status = XGetWindowProperty(dpy, w, a, 0L, len, False, type, &real_type, &format, &n, &extra, p); + if (status != Success || *p == 0) + return -1; + if (n == 0) + XFree(*p); + /* + * could check real_type, format, extra here... + */ + return n; +} + +void +getWindowName(Client *c) { + char * name; + Atom actual_type; + int format; + unsigned long n; + unsigned long extra; + int was_nameless; + + if (c == 0) + return; + + was_nameless = (c->name == 0); + + if (ewmh_get_window_name(c) == False && + XGetWindowProperty(dpy, c->window, _mozilla_url, 0L, 100L, False, AnyPropertyType, &actual_type, &format, &n, &extra, (unsigned char **) &name) == Success && name && *name != '\0' && n != 0) { + Client_Name(c, name, False); + XFree(name); + } else if (XGetWindowProperty(dpy, c->window, XA_WM_NAME, 0L, 100L, False, AnyPropertyType, &actual_type, &format, &n, &extra, (unsigned char **) &name) == Success && name && *name != '\0' && n != 0) { + /* That rather unpleasant condition is necessary because xwsh uses + * COMPOUND_TEXT rather than STRING for its WM_NAME property, + * and anonymous xwsh windows are annoying. + */ + if (actual_type == compound_text && memcmp(name, "\x1b\x28\x42", 3) == 0) { + Client_Name(c, name + 3, False); + } else { + Client_Name(c, name, False); + } + XFree(name); + } + + if (!was_nameless) + Client_DrawBorder(c, c == current); +} + +void +getNormalHints(Client *c) { + int x, y, w, h; + long msize; + + /* We have to be a little careful here. The ICCCM says that the x, y + * and width, height components aren't used. So we use them. That means + * that we need to save and restore them whenever we fill the size + * struct. */ + x = c->size.x; + y = c->size.y; + w = c->size.width; + h = c->size.height; + + /* Do the get. */ + if (XGetWMNormalHints(dpy, c->window, &c->size, &msize) == 0) + c->size.flags = 0; + + if (c->framed == True) { + /* + * Correct the minimum allowable size of this client to take + * account of the window border. + */ + if (c->size.flags & PMinSize) { + c->size.min_width += 2 * border; + c->size.min_height += 2 * border; + } else { + c->size.flags |= PMinSize; + c->size.min_width = 2 * (2 * border); + if (c->accepts_focus) + c->size.min_height = 2 * (2*border); + else + c->size.min_height = 2 * (2*border); + } + + /* + * Correct the maximum allowable size of this client to take + * account of the window border. + */ + if (c->size.flags & PMaxSize) { + c->size.max_width += 2 * border; + c->size.max_height += 2 * border; + } + } + + /* + * Ensure that the base width & height and the width & height increments + * are set correctly so that we don't have to do this in MakeSane. + */ + if (!(c->size.flags & PBaseSize)) + c->size.base_width = c->size.base_height = 0; + + if (!(c->size.flags & PResizeInc)) + c->size.width_inc = c->size.height_inc = 1; + + /* + * If the client gives identical minimum and maximum sizes, we don't + * want the user to resize in that direction. + */ + if (c->size.min_width == c->size.max_width) + c->size.width_inc = 0; + + if (c->size.min_height == c->size.max_height) + c->size.height_inc = 0; + + /* Restore the window-manager bits. */ + c->size.x = x; + c->size.y = y; + c->size.width = w; + c->size.height = h; +} + +static int +getWindowState(Window w, int *state) { + long *p = 0; + + if (getProperty(w, wm_state, wm_state, 2L, (unsigned char **) &p) <= 0) + return 0; + + *state = (int) *p; + XFree(p); + return 1; +} + + +extern Bool +motifWouldDecorate(Client *c) { + unsigned long *p = 0; + Bool ret = True; /* if all else fails - decorate */ + + if (getProperty(c->window, motif_wm_hints, motif_wm_hints, + 5L, (unsigned char **) &p) <= 0) + return ret; + + if ((p[0] & MWM_HINTS_DECORATIONS) && + !(p[2] & (MWM_DECOR_BORDER | MWM_DECOR_ALL))) + ret = False; + + XFree(p); + return ret; +} diff --git a/no_xmkmf_makefile b/no_xmkmf_makefile new file mode 100644 index 0000000..d184e5c --- /dev/null +++ b/no_xmkmf_makefile @@ -0,0 +1,48 @@ +# Sample Makefile for lwm. + +# You ought to be using the Imakefile (xmkmf;make) but +# if Imake isn't set up properly on your system, this might +# help you out. I used to use it on an SGI. + +# Uncomment these lines to use gcc. +#CC = gcc +#CFLAGS = -ansi -pedantic -Wall -DSHAPE + +# Uncomment these lines to use SGI cc. +#CC = cc +#CFLAGS = -fullwarn -g -DSHAPE + +# Uncomment these for Solaris Sun Studio, choose your architecture. +#CC = cc +#CFLAGS = -Xa -fast -xarch=v8a +#CFLAGS = -Xa -fast -xarch=386 + +DEFINES = + +# Bennett Todd (bet@lehman.com) says this helped him compile on +# Solaris 2.5.1, avoiding a problem with . +#DEFINES = -D_POSIX_C_SOURCE=2 + +# Add any strange libraries your system needs here. +LDFLAGS = -lXext -lX11 -lICE -lSM + +# ----------------------------------------------------------------------------- + +OFILES = client.o cursor.o disp.o error.o ewmh.o lwm.o manage.o mouse.o \ + resource.o session.o shape.o +HFILES = lwm.h ewmh.h + +# ----------------------------------------------------------------------------- + +all: lwm + +lwm: $(OFILES) + $(CC) $(CFLAGS) $(DEFINES) -o lwm $(OFILES) $(LDFLAGS) + +install: lwm + cp lwm /usr/local/bin + +$(OFILES): $(HFILES) + +clean: + rm -f lwm *.o core diff --git a/shape.c b/shape.c new file mode 100644 index 0000000..2b06189 --- /dev/null +++ b/shape.c @@ -0,0 +1,56 @@ +/* + * lwm, a window manager for X11 + * Copyright (C) 1997-2016 Elliott Hughes, James Carter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#ifdef SHAPE +#include +#endif + +#include "lwm.h" + +/*ARGSUSED*/ +extern void +setShape(Client *c) { +} + +/*ARGSUSED*/ +extern int +shapeEvent(XEvent *ev) { + return 0; +} + +/*ARGSUSED*/ +extern int +isShaped(Window w) { + return 0; +} + +extern int +serverSupportsShapes(void) { + return 0; +}