From 3ddaa5c1500d0986700592ed2f10d19e81c34868 Mon Sep 17 00:00:00 2001 From: "Michael D. Lowis" Date: Sun, 5 May 2019 22:49:01 -0400 Subject: [PATCH 1/1] initial commit --- AUTHORS | 2 + BUGS | 33 ++ COPYING | 340 ++++++++++++++++++ ChangeLog | 486 +++++++++++++++++++++++++ INSTALL | 11 + Imakefile | 29 ++ README | 4 + README.md | 7 + TODO | 2 + client.c | 783 ++++++++++++++++++++++++++++++++++++++++ cursor.c | 77 ++++ disp.c | 884 ++++++++++++++++++++++++++++++++++++++++++++++ error.c | 64 ++++ ewmh.c | 643 +++++++++++++++++++++++++++++++++ ewmh.h | 94 +++++ lwm | Bin 0 -> 60576 bytes lwm.c | 495 ++++++++++++++++++++++++++ lwm.h | 347 ++++++++++++++++++ lwm.man | 96 +++++ manage.c | 609 ++++++++++++++++++++++++++++++++ no_xmkmf_makefile | 48 +++ shape.c | 56 +++ 22 files changed, 5110 insertions(+) create mode 100644 AUTHORS create mode 100644 BUGS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100755 INSTALL create mode 100644 Imakefile create mode 100644 README create mode 100644 README.md create mode 100644 TODO create mode 100644 client.c create mode 100644 cursor.c create mode 100644 disp.c create mode 100644 error.c create mode 100644 ewmh.c create mode 100644 ewmh.h create mode 100755 lwm create mode 100644 lwm.c create mode 100644 lwm.h create mode 100644 lwm.man create mode 100644 manage.c create mode 100644 no_xmkmf_makefile create mode 100644 shape.c 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 0000000000000000000000000000000000000000..12e9c0380f915c58d5402f2e2527a42ed46ad6ea GIT binary patch literal 60576 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�Ibh;OS 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 literal 0 HcmV?d00001 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; +} -- 2.52.0