Repository: kolbusa/stalonetray Branch: master Commit: 58d92e26d726 Files: 68 Total size: 393.5 KB Directory structure: gitextract_7_pxnf_6/ ├── .clang-format ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ ├── build.yml │ ├── lint.yml │ └── release.yml ├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── TODO ├── generate-manpage.sh ├── meson.build ├── meson.format ├── meson.options ├── src/ │ ├── common.h │ ├── debug.c │ ├── debug.h │ ├── embed.c │ ├── embed.h │ ├── icons.c │ ├── icons.h │ ├── image.c │ ├── image.h │ ├── kde_tray.c │ ├── kde_tray.h │ ├── layout.c │ ├── layout.h │ ├── list.h │ ├── main.c │ ├── meson.build │ ├── scrollbars.c │ ├── scrollbars.h │ ├── settings.c │ ├── settings.h │ ├── tray.c │ ├── tray.h │ ├── wmh.c │ ├── wmh.h │ ├── xembed.c │ ├── xembed.h │ ├── xinerama.c │ ├── xinerama.h │ ├── xutils.c │ └── xutils.h ├── stalonetray.xml.in ├── stalonetrayrc.sample └── utils/ ├── get_props/ │ └── main.c ├── py-gtk-example/ │ ├── LICENSE │ ├── README.md │ ├── gtk-example.glade │ ├── gtk-example.py │ └── python3.xpm ├── tray-test-fdo/ │ ├── main.c │ ├── run_icons │ ├── seq1 │ ├── seq2 │ ├── xembed.c │ └── xembed.h ├── tray-test-gtk/ │ └── traytest └── tray-xembed-test/ ├── common.h ├── debug.c ├── main.c ├── run ├── xembed.c ├── xembed.h ├── xutils.c └── xutils.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- Language: Cpp AccessModifierOffset: 0 AlignAfterOpenBracket: DontAlign AlignConsecutiveMacros: false AlignConsecutiveAssignments: false AlignConsecutiveBitFields: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: DontAlign AlignOperands: DontAlign AlignTrailingComments: false AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortEnumsOnASingleLine: true AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: false AfterControlStatement: Never AfterEnum: false AfterFunction: true AfterStruct: false AfterUnion: false BeforeElse: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: Linux BreakBeforeTernaryOperators: true BreakStringLiterals: true ColumnLimit: 79 CommentPragmas: '^ IWYU pragma:' ContinuationIndentWidth: 4 DeriveLineEnding: true DerivePointerAlignment: false DisableFormat: false ForEachMacros: - foreach IncludeBlocks: Preserve IncludeCategories: - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 SortPriority: 0 - Regex: '.*' Priority: 1 SortPriority: 0 IncludeIsMainRegex: '(Test)?$' IncludeIsMainSourceRegex: '' IndentCaseLabels: false IndentCaseBlocks: false IndentGotoLabels: false IndentPPDirectives: None IndentWidth: 4 IndentWrappedFunctionNames: true InsertTrailingCommas: None KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false Standard: c++03 TabWidth: 4 UseCRLF: false UseTab: Never WhitespaceSensitiveMacros: - STRINGIZE - PP_STRINGIZE - BOOST_PP_STRINGIZE ... ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the buggy behavior (even if intermittent): 1. Use WM FOO (describe configuration if relevant) 2. Launch app X 3. Launch app Y 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Log files** Attach stalonetray output with `--log-level trace` for good and bad behavior. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: branches: - master pull_request: {} jobs: ubuntu: name: Ubuntu runs-on: ubuntu-latest strategy: fail-fast: false matrix: featureset: ["base", "xinerama", "xpm", "native_kde", "all", "debug"] steps: - name: Checkout source code uses: actions/checkout@v5 - name: Disable man-db auto-update run: | echo "set man-db/auto-update false" | sudo debconf-communicate sudo dpkg-reconfigure man-db sudo rm -f /var/lib/man-db/auto-update - name: Install and cache dependencies uses: awalsh128/cache-apt-pkgs-action@v1 with: packages: > xsltproc docbook-xsl libxpm-dev libx11-dev libxinerama-dev meson - name: Set up build directory run: > if [ "${{ matrix.featureset }}" = "base" ]; then echo "== Basic build ==" meson setup --buildtype release build -D c_std=gnu2x elif [ "${{ matrix.featureset }}" = "all" ]; then echo "== Enabling all features ==" meson setup --buildtype release build -D c_std=gnu2x \ -D xinerama=enabled -D xpm=enabled -D native_kde=enabled elif [ "${{ matrix.featureset }}" = "debug" ]; then echo "== Debug build with all features enabled ==" meson setup --buildtype debug build -D c_std=gnu2x \ -D xinerama=enabled -D xpm=enabled -D native_kde=enabled \ -D trace_events=true -D delay_embedding_confirmation=true \ -D dump_window_information=true -D exit_gracefully=true else echo "== Enabling feature ${{ matrix.featureset }} ==" meson setup --buildtype release build -D c_std=gnu2x \ -D ${{ matrix.featureset }}=enabled fi - name: Build run: meson compile -C build stalonetray - name: Build manpage run: meson compile -C build manpage freebsd: name: FreeBSD runs-on: ubuntu-latest strategy: fail-fast: false matrix: version: ["14.2", "14.3", "15.0"] steps: - name: Checkout source code uses: actions/checkout@v5 - name: Disable man-db auto-update run: | echo "set man-db/auto-update false" | sudo debconf-communicate sudo dpkg-reconfigure man-db sudo rm -f /var/lib/man-db/auto-update - name: Install and cache vbox dependencies uses: awalsh128/cache-apt-pkgs-action@v1 with: execute_install_scripts: true packages: > zstd libvirt-daemon-system virt-manager qemu-kvm libosinfo-bin axel expect screen sshpass - name: Build on FreeBSD VM uses: vmactions/freebsd-vm@v1 with: arch: amd64 release: ${{ matrix.version }} prepare: | mkdir -p /usr/local/etc/pkg/repos repoFile="/usr/local/etc/pkg/repos/FreeBSD.conf" if [ ${{ matrix.version }} = "15.0" ]; then repoURL="http://pkg.freebsd.org/\${ABI}/latest" else repoURL="pkg+http://pkg.freebsd.org/\${ABI}/latest" fi echo "FreeBSD: { url: ${repoURL} }" > "$repoFile" IGNORE_OSVERSION=yes pkg update -f pkg install -y pkgconf meson libX11 libXpm libXinerama docbook-xsl \ libxslt run: | echo "== Basic build ==" meson setup --buildtype release build meson compile -C build stalonetray meson compile -C build manpage for feature in xinerama xpm native_kde; do echo "== Building with $feature enabled ==" meson setup --wipe build -D $feature=enabled meson compile -C build stalonetray meson compile -C build manpage done echo "== Building with all features enabled ==" meson setup --wipe build \ -D xinerama=enabled -D xpm=enabled -D native_kde=enabled meson compile -C build stalonetray meson compile -C build manpage ================================================ FILE: .github/workflows/lint.yml ================================================ name: Lint on: push: branches: - master pull_request: {} jobs: new-version: name: Version Bump Check runs-on: ubuntu-latest if: github.event_name == 'pull_request' outputs: next-version: ${{ steps.next-version.outputs.version }} has-next-version: ${{ steps.next-version.outputs.hasNextVersion }} steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - id: next-version uses: thenativeweb/get-next-version@2.7.1 - name: Install meson run: pip install 'meson>=1.5.0' - name: Show the next version run: | mesonV=$(meson introspect meson.build --projectinfo | jq -r .version) wantedV="${{ steps.next-version.outputs.version }}" shouldBump="${{ steps.next-version.outputs.hasNextVersion }}" echo "Should bump version: $shouldBump" echo "Next version: $wantedV" echo "Current version on meson.build: $mesonV" if "$shouldBump" && [ "$mesonV" != "$wantedV" ]; then echo "Version bump mismatch! Expected $wantedV but found $mesonV" exit 1 elif "$shouldBump"; then echo "Version bump detected and matches meson.build" else echo "No version change detected, nothing to do" fi meson-format: name: Meson Format runs-on: ubuntu-latest steps: - name: Checkout source code uses: actions/checkout@v5 - name: Install meson run: pip install 'meson>=1.5.0' - name: Run meson-format run: meson format --recursive -q ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: branches: - master concurrency: group: merge-to-master cancel-in-progress: false permissions: contents: write jobs: new-version: name: Get New Version runs-on: ubuntu-latest outputs: next-version: ${{ steps.next-version.outputs.version }} has-next-version: ${{ steps.next-version.outputs.hasNextVersion }} steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - id: next-version uses: thenativeweb/get-next-version@2.7.1 - name: Show the next version run: | echo "Should bump version: ${{ steps.next-version.outputs.hasNextVersion }}" echo "Next version: ${{ steps.next-version.outputs.version }}" create-release: name: Create runs-on: ubuntu-latest if: needs.new-version.outputs.has-next-version == 'true' needs: [new-version] steps: - uses: actions/checkout@v5 - name: Build distribution tarball run: | echo "set man-db/auto-update false" | sudo debconf-communicate sudo dpkg-reconfigure man-db sudo rm -f /var/lib/man-db/auto-update sudo apt-get update sudo apt-get install -y \ xsltproc docbook-xsl libxpm-dev libx11-dev libxinerama-dev pip install 'meson>=1.5.0' meson setup --buildtype release _build -D c_std=gnu2x meson dist -C _build - uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.new-version.outputs.next-version }} generate_release_notes: true files: "_build/meson-dist/*" ================================================ FILE: .gitignore ================================================ # Generated by build system /aclocal.m4 /autom4te.cache Makefile.in Makefile /compile /config.log /config.status /configure /depcomp /install-sh /missing .deps/ /src/config.h.in /src/config.h /src/stamp-h1 /build/ /.cache/ # Build artifacts /src/*.o /stalonetray.xml # Output artifacts /src/stalonetray /stalonetray.1 /stalonetray.html ================================================ FILE: AUTHORS ================================================ Roman Dubtsov d3adb5 ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser General Public License instead of this License. ================================================ FILE: README.md ================================================ # STAnd aLONE TRAY [![Build][badge-build]][yaml-build] [![Lint][badge-lint]][yaml-lint] [badge-build]: https://github.com/d3adb5/stalonetray/actions/workflows/build.yml/badge.svg [yaml-build]: https://github.com/d3adb5/stalonetray/actions/workflows/build.yml [badge-lint]: https://github.com/d3adb5/stalonetray/actions/workflows/lint.yml/badge.svg [yaml-lint]: https://github.com/d3adb5/stalonetray/actions/workflows/lint.yml Stalonetray is a STAnd-aLONE system TRAY (notification area) for Unix desktops using the X11 windowing system. It has minimal default build and run-time dependencies: the Xlib and libXinerama, though you could do away with the latter by disabling a feature for even more minimalism. Stalonetray runs under virtually any window manager. To start using stalonetray, just copy `stalonetrayrc.sample` to `~/.stalonetrayrc` or to `$XDG_CONFIG_HOME/stalonetrayrc`. It is well-commented and should suffice for a quick start. Note that some features are disabled by default and may not work out of the box, depending on how stalonetray was built by the package maintainer. See the "Building from source" section below if you want to compile it yourself with the features you need. ## Maintenance status This project was originally developed by [Roman Dubtsov (kolbusa)][gh-kolbusa] and recently changed hands. Roman is still involved with, but no longer actively maintains the project. **To him goes all the credit for creating and maintaining this project for many years. Thank you, Roman!** [gh-kolbusa]: https://github.com/kolbusa ## Installation Package managers are the most convenient way to install stalonetray. It is packaged for several Linux distributions and BSD variants. On Debian and Ubuntu, run: ```sh sudo apt install stalonetray ``` On Fedora run: ```sh sudo dnf install stalonetray ``` ## Building from source Stalonetray uses [Meson](https://mesonbuild.com/). Refer to the `meson.options` file for available build options and their default values. To build stalonetray using Meson, ensure necessary dependencies are installed --- by default only Xlib and libXinerama development packages are required --- and run the standard Meson build commands: ```sh meson setup builddir meson compile -C builddir stalonetray ``` This should build the `stalonetray` binary in the `builddir` directory. To build stalonetray's documentation, you'll need `xsltproc` and DocBook stylesheets installed first. Then build the `manpage` target: ```sh meson compile -C builddir manpage ``` This creates the `stalonetray.1` file in the `builddir` directory. Installation from source can be done with: ```sh meson install -C builddir ``` ================================================ FILE: TODO ================================================ + Each icon must have a newly created parent window, which matches its dimentions (for ease of move and for them to be centered ?). + Anvanced grow + Reorganize settings (and vars) - Make use of resources (WONTDO) + rc file + Need to handle unusual sizes of "icons" (and resolve when to force constraints) + Make satray survive even if window suddenly dissapears (during some operation, when destroy\unmap was not handled) + KDE tray implementation (solves previous problem) + Handle tray icon move/resize + Simplify rc file syntax/re-reorganize settings + main.c cleanup + update transparent/parent background on icon move/resize + review DBG() calls in accordance with doc/DEBUG-LEVELS + reacquire tray selection after loss (if holder exits) (update sample rc(done)) + find and fix typos + fix (almost all) warnings with -Wall + .spec file for building rpms + man page + tray grow with wnd deco for 0.4 + default values in the manpage, MWM/EWMH compliancy must be noted + ontop/onbottom/normal (wnd_layer param) + xorg7 build troubles + more flexible wnd deco specification + make use of ewmh spec (_NET_WM_STATE_SKIP_PAGER, _NET_WM_WINDOW_TYPE_DOCK and friends) + all configure.in needs review (refuse to build if something goes wrong) + update DBG so that it does not require C99 + *wm tests (pwm(2), e17 have some issues): + WMaker (everything works) + Fvwm (for sure) + PWM(1) (no_{title,border} do not work) + E17 (the same =() + something has happened to vertical layout (all works) + tests on sf cf + now stalonetray can be build with Sun ONE 8 C compiler =) (because it's sources conform to C90 specs) + change status to beta =) for 0.5(unreleased, renamed to 0.6) + code cleanup (again :() (REVIEW layout.c very thoroughly!!!) + fix geometry issues + GNUisms in debug.h + icons are not aligned properly inside their mid-parents + basename can be either in libgen.h or in string.h o_O : XPG ver from libgen.h was chosen + update manpages, etc (docbook for documentation ?) + fix default rc file name + backtrace in configure.in is not detected ? + make boolean cfg file opts to have _optional_ argument + make permanent behaviour default and remove it from configuration + default values in documentation + validate stalonetray.xml + collect icons on startup (test, KDE icons issue) needs XEMBED ?!!! + Full XEMBED implementation (?) + issue with composite extension on (seq1,2) + version info + pixmap bg + check rc keyword names for concistency and update stalonetrayrc.sample + test non-debug version + test with LARGE pixmap + PORTING + fix the website for IE (and colors) for 0.7 - DOXYGEN documentation (WONTDO, 0.8?) - one state instead of multiple flags for icons (WONTDO) - split main.c into: signals.c events.c main.c (WONTDO) - support for WINE icons (WONTDO: isn't it WINE problem?) + consistent naming scheme for funcs + shrink back mode + reconsider API + improve X11-using code + params for boolean cmd line parameters (reconsider cmd line params and rc file directives) ??? (--skip-taskbar=true) + indicate parsing errors (make them fatal?) + comments + bg tinting (with fade out) + wmaker mode (SORTOFWORKS) + fall back to legacy KDE icons handling scheme when list of icons is not available + withdrawn mode should be configurable + XEMBED test suite ? + rethink command line params names + merge changes from 0.6 bugfixes branch (x86_64 patches pending) (size hints handling?) + layout _needs_ fixin` (TEST!!!!, seq1) (HOORAY!!!!!) + automatic version string in _all_ documentation + update README/docs + sanitize DBG levels for 0.7.6 + ensure that x11_ok() called at all exit paths from x11-calling functions + startup issues (collect existing icons) - focus issue in Wmaker for 0.8 + slot size vs. icon size + review ignore_icon_resize usage + all dims must be given in slots not in pixels + scrollable tray area? (see http://wnd.katei.fi/weblog/entry-7) + need repeater mode + support for mouse wheel (scroll vertically if mousewheeling over vert sb and horizontally if over horz sb; default to orientation otherwise) + strut WM hints (requires Xinerama?) + fix geometry code + fix wmaker docking mode (--widthrawn=off/simple/wmaker) - proper dockapp behaviour in WM + add --kludge command line param that turns kludges on/off + move respect icon hints under this category + geometry bugs (wrong placement) (CANTREPRODUCE?) + valgrind + color background + xpm background + xpm background + transparency + remote interface (via send event to tray selection manager) + profile the code + powertop; fix event loop to block if there are no events and no scrolling + proper handling of signals required!!! + SIGUSR1 handing is OK (its for debug only anyway) + termination signals wont be handled to avoid hacks... + fix complaint about icon placement in WM dockapp mode + fix complaint about window layer under WM (CANTREPRODUCE, Window Maker 0.92.0) + rework DBG/ERR/DIE + update sample RC + proper formatting of log lines (date + remove location) + fix bug with resize for 0.8.1,2,../0.9 - systray 0.3 spec (VISUAL) - proper dockapp behaviour in WM - profile the code - gprofile; use array in icons.c instead of list - RENDER implementation of tinting/compositing, take implementation from rxvt - sanitize x11 calls: check return values instead of relying on x11_ok() whenever possible - track size changes of the icons that failed to fit into the tray - rework root transparency code (handle cases when root pmap size is less than root window; take implementation from urxvt) - restructure command line params and reduce their count - rework command line interface ================================================ FILE: generate-manpage.sh ================================================ #!/bin/sh # # A simple script to build the manpage for stalonetray using xsltproc. # # The logic in this script would've been placed in the meson.build file, but as # of the time of writing Meson doesn't provide a way to write intermediate # files from the stdout of a command, instead relying on the command creating # the file itself. # # Plenty of the logic was viable within Meson, but was moved here to simplify # the meson.build file. # # Usage: ./generate-manpage.sh template output stalonetray-version # # Requires: sed, xsltproc # # TODO: Accept features as arguments to include/exclude parts of the manpage. template="$1" output="$2" version_str="$3" stylesheet="" intermediate_xml="$(basename "$template" .in)" for cmd in sed xsltproc; do if ! command -v "$cmd" >/dev/null 2>&1; then echo "$cmd is required to build the manpage." >&2 exit 1 fi done for root in \ /usr/share/sgml/docbook/stylesheet/xsl/nwalsh \ /usr/share/xml/docbook/stylesheet/docbook-xs \ /usr/share/xml/docbook/stylesheet/docbook-xsl-nons \ /usr/share/xml/docbook/xsl-stylesheets \ /usr/share/sgml/docbook/xsl-stylesheets \ /usr/share/xml/docbook/xsl-stylesheets-*-nons \ /usr/share/sgml/docbook/xsl-stylesheets \ /usr/share/xsl/docbook \ /usr/local/share/xsl/docbook \ ; do if [ -f "$root/manpages/docbook.xsl" ]; then stylesheet="$root/manpages/docbook.xsl" break fi done if [ -z "$stylesheet" ]; then echo "Could not find docbook manpage stylesheet." >&2 exit 2 fi sed "s/@VERSION_STR@/$version_str/g" "$template" > "$intermediate_xml" xsltproc -o "$output" "$stylesheet" "$intermediate_xml" rm -f "$intermediate_xml" # vim: tabstop=2 shiftwidth=2 expandtab ================================================ FILE: meson.build ================================================ project( 'stalonetray', 'c', version: '1.0.3', default_options: ['warning_level=3', 'c_std=gnu23'], meson_version: '>=1.1.0', ) build_args = [ '-O2', '-Wno-missing-braces', '-DPROGNAME="@0@"'.format(meson.project_name()), '-DVERSION="@0@"'.format(meson.project_version()), ] project_sources = [] # == Dependencies & Optional Features ========================================== dependencies = { 'x11': dependency('x11', required: true), 'xinerama': dependency('xinerama', required: get_option('xinerama')), 'xpm': dependency('xpm', required: get_option('xpm')), } assert(dependencies['x11'].found(), 'Xlib is required to build stalonetray') # == Sources & Headers ========================================================= subdir('src') # == Build Options ============================================================= feature_list = [] if get_option('xinerama').enabled() build_args += ['-D_ST_WITH_XINERAMA'] feature_list += ['xinerama'] endif if get_option('xpm').enabled() build_args += ['-D_ST_WITH_XPM'] feature_list += ['xpm'] endif if get_option('native_kde').enabled() build_args += ['-D_ST_WITH_NATIVE_KDE'] feature_list += ['native_kde'] endif if get_option('trace_events') build_args += ['-D_ST_TRACE_EVENTS'] feature_list += ['trace_events'] endif if get_option('delay_embedding_confirmation') build_args += ['-D_ST_DELAY_EMBEDDING_CONFIRMATION'] feature_list += ['delay_embedding_confirmation'] endif if get_option('dump_window_information') build_args += ['-D_ST_DUMP_WINDOW_INFORMATION'] feature_list += ['dump_window_information'] endif if get_option('exit_gracefully') build_args += ['-D_ST_EXIT_GRACEFULLY'] feature_list += ['exit_gracefully'] endif build_args += ['-DFEATURE_LIST="@0@"'.format(' '.join(feature_list))] # == Targets =================================================================== # TODO: Replace this with dict.values() when meson 1.10 is released. _dependencies = [] foreach _, dep : dependencies _dependencies += [dep] endforeach executable( 'stalonetray', project_sources, c_args: build_args, dependencies: _dependencies, install: true, ) # == Documentation ============================================================= gen_manpage = find_program('generate-manpage.sh') custom_target( 'manpage', input: 'stalonetray.xml.in', output: 'stalonetray.1', command: [gen_manpage, '@INPUT@', '@OUTPUT@', meson.project_version()], install: true, install_dir: join_paths(get_option('prefix'), get_option('mandir'), 'man1'), ) # == Sample Configuration ====================================================== install_data( 'stalonetrayrc.sample', install_dir: get_option('sysconfdir'), rename: ['stalonetrayrc'], ) ================================================ FILE: meson.format ================================================ indent_by = ' ' max_line_length = 80 ================================================ FILE: meson.options ================================================ option('xinerama', type: 'feature', value: 'enabled', description: 'Enable Xinerama support for multiple monitors') option('xpm', type: 'feature', value: 'auto', description: 'Enable XPM background support') option('native_kde', type: 'feature', value: 'auto', description: 'Enable native KDE system tray support') option('trace_events', type: 'boolean', value: false, description: 'Enable X11 event tracing for debugging purposes') option('delay_embedding_confirmation', type: 'boolean', value: false, description: 'Delay sending XEMBED_EMBEDDED_NOTIFY message for debugging') option('dump_window_information', type: 'boolean', value: false, description: 'Use xprop/xwininfo to dump icon window information') option('exit_gracefully', type: 'boolean', value: true, description: 'Use non-portable hack to exit gracefully on signal') ================================================ FILE: src/common.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * common.h * Mon, 01 May 2006 01:45:08 +0700 * ------------------------------- * Common declarations * -------------------------------*/ #ifndef _COMMON_H_ #define _COMMON_H_ #include "debug.h" /* Default icon size */ #define FALLBACK_ICON_SIZE 24 /* Minimal icon size */ #define MIN_ICON_SIZE 16 /* Default KDE icon size */ #define KDE_ICON_SIZE 22 /* Number of time icon is allowed to change its size after which * stalonetray gives up */ #define ICON_SIZE_RESETS_THRESHOLD 2 /* Meaningful names for return values */ #define SUCCESS 1 #define FAILURE 0 /* Simple macro to return status and log it if necessary */ #define RETURN_STATUS(rc) \ do { \ LOG_TRACE( \ ("status = %s\n", ((rc) == SUCCESS ? "SUCCESS" : "FAILURE"))); \ return rc; \ } while (0) /* Meaningful names for return values of icon mass-operations */ #define MATCH 1 #define NO_MATCH 0 /* Simple macro to return mass-op status and log it if necessary */ #define RETURN_MATCH(rc) \ do { \ LOG_TRACE(("status = %s\n", rc == MATCH : "MATCH" : "NO_MATCH")); \ return rc; \ } while (0) /* Meaningful names for return values of icon mass-operations */ #define MATCH 1 /* Portable macro for function name */ #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define __FUNC__ __func__ #elif defined(__GNUC__) && __GNUC__ >= 3 #define __FUNC__ __FUNCTION__ #else #define __FUNC__ "unknown" #endif /* DIE */ #define DIEDIE() exit(-1) /* Print a message and... DIE */ #define DIE(message) \ do { \ LOG_ERROR(message); \ DIEDIE(); \ } while (0) /* Log OOM condition */ #define LOG_ERR_IE(message) \ do { \ LOG_ERROR( \ ("Internal error, please report to maintaner (see AUTHORS)\n")); \ LOG_ERROR(message); \ } while (0); /* DIE on internal error */ #define DIE_IE(message) \ do { \ LOG_ERR_IE(message); \ DIEDIE(); \ } while (0) /* Log OOM condition */ #define LOG_ERR_OOM(message) \ do { \ LOG_ERROR(("Out of memory\n")); \ LOG_ERROR(message); \ } while (0); /* DIE on OOM error */ #define DIE_OOM(message) \ do { \ LOG_ERR_OOM(message); \ DIEDIE(); \ } while (0) /*** WARNING: feed following macros only with side-effects-free expressions * ***/ /* Get a value within target interval */ #define cutoff(tgt, min, max) \ (tgt) < (min) ? (min) : ((tgt) > (max) ? max : tgt) /* Update value to fit into target interval */ #define val_range(tgt, min, max) (tgt) = cutoff(tgt, min, max) #endif ================================================ FILE: src/debug.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * debug.c * Mon, 01 May 2006 01:44:42 +0700 * ------------------------------- * Debugging code/utilities * -------------------------------*/ #include "debug.h" #include "xutils.h" #include #include #include #include #ifdef DBG_PRINT_PID #include #include #endif int debug_output_disabled = 0; /* Disables all output from debugging macros */ void debug_disable_output() { debug_output_disabled = 1; } /* Print the message to STDERR (varadic macros is not used in the sake of * portability) */ void print_message_to_stderr(const char *fmt, ...) { static char msg[PATH_MAX]; va_list va; va_start(va, fmt); vsnprintf(msg, PATH_MAX, fmt, va); va_end(va); fprintf(stderr, "%s", msg); } int trace_mode = False; void print_trace_header( const char *funcname, const char *fname, const int line) { static pid_t pid = 0; #ifdef TRACE_TIMESTAMP { static char timestr[PATH_MAX + 1]; time_t curtime = time(NULL); struct tm *loctime = localtime(&curtime); strftime(timestr, PATH_MAX, "%b %e %H:%M:%S", loctime); fprintf(stderr, "%s ", timestr); } #endif #ifdef TRACE_WM_NAME fprintf(stderr, "%s ", settings.wnd_name); #endif #ifdef TRACE_PID if (!pid) pid = getpid(); fprintf(stderr, "(%d) ", pid); #endif #ifdef TRACE_DPY if (tray_data.dpy != NULL) fprintf(stderr, "(%s) ", DisplayString(tray_data.dpy)); else fprintf(stderr, "(dpy) "); #endif #ifdef TRACE_VERBOSE_LOCATION fprintf(stderr, "(%s:%4d) ", fname, line); #endif fprintf(stderr, "%s(): ", funcname); (void) pid; /* unused */ (void) fname; /* unused */ (void) line; /* unused */ } /* Print the summary of icon data */ int print_icon_data(struct TrayIcon *ti) { #ifdef DEBUG XWindowAttributes xwa; #endif LOG_INFO(("wid = 0x%lx\n", ti->wid)); LOG_TRACE((" self = %p\n", (void *) ti)); LOG_TRACE((" prev = %p\n", (void *) ti->prev)); LOG_TRACE((" next = %p\n", (void *) ti->next)); LOG_TRACE((" invalid = %d\n", ti->is_invalid)); LOG_TRACE((" layed_out = %d\n", ti->is_layed_out)); LOG_TRACE((" update_pos = %d\n", ti->is_updated)); LOG_INFO((" name = %s\n", x11_get_window_name(tray_data.dpy, ti->wid, ""))); LOG_INFO((" visible = %d\n", ti->is_visible)); LOG_INFO((" position (grid) = %dx%d+%d+%d\n", ti->l.grd_rect.w, ti->l.grd_rect.h, ti->l.grd_rect.x, ti->l.grd_rect.y)); LOG_INFO((" position (pixels) = %dx%d+%d+%d\n", ti->l.icn_rect.w, ti->l.icn_rect.h, ti->l.icn_rect.x, ti->l.icn_rect.y)); LOG_INFO((" wnd_sz = %dx%d\n", ti->l.wnd_sz.x, ti->l.wnd_sz.y)); LOG_TRACE((" cmode = %d\n", ti->cmode)); LOG_INFO((" xembed = %d\n", ti->is_xembed_supported)); if (ti->is_xembed_supported) { LOG_TRACE( (" xembed_accepts_focus = %d\n", ti->is_xembed_accepts_focus)); LOG_TRACE( (" xembed_last_timestamp = %ld\n", ti->xembed_last_timestamp)); LOG_TRACE((" xembed_last_msgid = %ld\n", ti->xembed_last_msgid)); } LOG_INFO((" embedded = %d\n", ti->is_embedded)); #ifdef DEBUG if (ti->is_embedded) { LOG_TRACE((" mid-parent = 0x%lx\n", ti->mid_parent)); if (!XGetWindowAttributes(tray_data.dpy, ti->mid_parent, &xwa)) { LOG_TRACE(( " ERR: XGetWindowAttributes() on mid-parent window faied\n")); } else { LOG_TRACE((" mid-parent wid = 0x%lx\n", ti->mid_parent)); LOG_TRACE((" mid-parent state = %s\n", xwa.map_state == IsUnmapped ? "Unmapped" : (xwa.map_state == IsUnviewable ? "Unviewable" : "Viewable"))); LOG_TRACE((" mid-parent geometry = %dx%d+%d+%d\n", xwa.width, xwa.height, xwa.x, xwa.y)); } } if (settings.log_level > LOG_LEVEL_INFO) { Window real_parent, root, *children = NULL; unsigned int nchildren = 0; int rc; rc = XQueryTree(tray_data.dpy, ti->wid, &root, &real_parent, &children, &nchildren); if (rc && children != NULL && nchildren > 0) XFree(children); if (!rc) { LOG_TRACE((" ERR: XQueryTree() failed\n")); } else { LOG_TRACE( (" parent wid from XQueryTree() = 0x%lx\n", real_parent)); } } if (!XGetWindowAttributes(tray_data.dpy, ti->wid, &xwa)) { LOG_TRACE((" ERR: XGetWindowAttributes() on icon window failed\n")); } else { LOG_TRACE((" geometry from XGetWindowAttributes() = %dx%d+%d+%d\n", xwa.width, xwa.height, xwa.x, xwa.y)); LOG_TRACE((" mapstate from XGetWindowAttributes() = %s\n", xwa.map_state == IsUnmapped ? "Unmapped" : (xwa.map_state == IsUnviewable ? "Unviewable" : "Viewable"))); } #endif /* This resets x11 error state (which might have left from X11 calls above) */ x11_ok(); return NO_MATCH; } void dump_icon_list() { icon_list_forall(&print_icon_data); } ================================================ FILE: src/debug.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * debug.h * Tue, 24 Aug 2004 12:05:38 +0700 * ------------------------------- * Debugging code/utilities * -------------------------------*/ #ifndef _DEBUG_H_ #define _DEBUG_H_ #include #include #include "common.h" #include "settings.h" #include "tray.h" #include #include #define LOG_LEVEL_ERR 0 #define LOG_LEVEL_INFO 1 #define LOG_LEVEL_TRACE 2 extern int debug_output_disabled; /* Disables all output from debugging macros */ void debug_disable_output(); /* Print the message to STDERR (in the sake of portability, we do not use * varadic macros) */ void print_message_to_stderr(const char *fmt, ...) #ifdef __GNUC__ __attribute__((__format__(__printf__, 1, 2))) #endif ; /* The following defines control what is printed in each logged line */ #ifdef DEBUG /* Print window name */ #define TRACE_WM_NAME /* Print pid */ #undef TRACE_PID /* Print name of display */ #undef TRACE_DPY /* Print timestamp */ #define TRACE_TIMESTAMP /* Print name of a function, file and line which outputs the message */ /* Othewise, only function name is printed */ #undef TRACE_VERBOSE_LOCATION #endif /* Print the debug header as specified by defines below */ void print_trace_header( const char *funcname, const char *fname, const int line); #define PRINT_TRACE_HEADER() \ do { \ print_trace_header(__FUNC__, __FILE__, __LINE__); \ } while (0) /* Print the debug message of specified level */ #define LOG_TRACE(message) \ do { \ if (!debug_output_disabled \ && settings.log_level >= LOG_LEVEL_TRACE) { \ PRINT_TRACE_HEADER(); \ print_message_to_stderr message; \ }; \ } while (0) #define LOG_ERROR(message) \ do { \ if (!debug_output_disabled) { \ if (settings.log_level >= LOG_LEVEL_TRACE) PRINT_TRACE_HEADER(); \ if (settings.log_level >= LOG_LEVEL_ERR) \ print_message_to_stderr message; \ } \ } while (0) #define LOG_INFO(message) \ do { \ if (!debug_output_disabled) { \ if (settings.log_level >= LOG_LEVEL_TRACE) PRINT_TRACE_HEADER(); \ if (settings.log_level >= LOG_LEVEL_INFO) \ print_message_to_stderr message; \ } \ } while (0) /* Print the summary of icon data */ int print_icon_data(struct TrayIcon *ti); /* Print icon list contents */ void dump_icon_list(); #endif ================================================ FILE: src/embed.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * embed.c * Fri, 03 Sep 2004 20:38:55 +0700 * ------------------------------- * embedding cycle implementation * -------------------------------*/ #include #include #include #include #ifdef DELAY_EMBEDDING_CONFIRMATION #include #endif #include "embed.h" #include "common.h" #include "debug.h" #include "icons.h" #include "settings.h" #include "tray.h" #include "xutils.h" #define CALC_INNER_POS(x_, y_, ti_) \ do { \ x_ = (ti_->l.icn_rect.w - ti_->l.wnd_sz.x) / 2; \ y_ = (ti_->l.icn_rect.h - ti_->l.wnd_sz.y) / 2; \ } while (0); #ifdef DELAY_EMBEDDING_CONFIRMATION void *send_delayed_confirmation(void *dummy) { struct TrayIcon *ti = (struct TrayIcon *)dummy; Display *dpy; if ((dpy = XOpenDisplay(settings.display_str)) != NULL) { LOG_TRACE( ("will now sleep for %d seconds\n", settings.confirmation_delay)); sleep(settings.confirmation_delay); LOG_TRACE(("sending embedding confirmation\n")); x11_send_client_msg32(dpy, tray_data.tray, tray_data.tray, tray_data.xa_tray_opcode, 0, STALONE_TRAY_DOCK_CONFIRMED, ti->wid, 0, 0); XSync(dpy, False); XClose(dpy); } else { DIE_IE(("failed to initialize display\n")); } pthread_exit(NULL); } #endif int embedder_embed(struct TrayIcon *ti) { int x, y, rc; XSetWindowAttributes xswa; /* If the icon is being embedded as hidden, * we just start listening for property changes * to track _XEMBED mapped state */ if (!ti->is_visible) { XSelectInput(tray_data.dpy, ti->wid, PropertyChangeMask); return x11_ok(); } /* 0. Start listening for events on icon window */ XSelectInput( tray_data.dpy, ti->wid, StructureNotifyMask | PropertyChangeMask); if (!x11_ok()) RETURN_STATUS(FAILURE); /* 1. Calculate position of mid-parent window */ CALC_INNER_POS(x, y, ti); LOG_TRACE( ("position of icon 0x%lx inside the tray: (%d, %d)\n", ti->wid, x, y)); /* 2. Create mid-parent window */ ti->mid_parent = XCreateSimpleWindow(tray_data.dpy, tray_data.tray, ti->l.icn_rect.x + x, ti->l.icn_rect.y + y, ti->l.wnd_sz.x, ti->l.wnd_sz.y, 0, 0, 0); /* 2.5. Setup mid-parent window properties */ xswa.win_gravity = settings.bit_gravity; XChangeWindowAttributes( tray_data.dpy, ti->mid_parent, CWWinGravity, &xswa); #ifndef DEBUG_HIGHLIGHT_MIDPARENT XSetWindowBackgroundPixmap(tray_data.dpy, ti->mid_parent, ParentRelative); #else XSetWindowBackgroundPixmap(tray_data.dpy, ti->mid_parent, 0); #endif if (!x11_ok() || ti->mid_parent == None) RETURN_STATUS(FAILURE); LOG_TRACE(("created mid-parent window 0x%lx\n", ti->mid_parent)); /* 3. Embed window into mid-parent */ switch (ti->cmode) { case CM_KDE: case CM_FDO: XReparentWindow(tray_data.dpy, ti->wid, ti->mid_parent, 0, 0); XMapRaised(tray_data.dpy, ti->wid); break; default: break; } /* 4. Show mid-parent */ XMapWindow(tray_data.dpy, ti->mid_parent); /* mid-parent must be lowered so that it does not osbcure * scollbar windows */ XLowerWindow(tray_data.dpy, ti->mid_parent); if (!x11_ok()) RETURN_STATUS(FAILURE); #ifndef DELAY_EMBEDDING_CONFIRMATION /* 5. Send message confirming embedding */ rc = x11_send_client_msg32(tray_data.dpy, tray_data.tray, tray_data.tray, tray_data.xa_tray_opcode, 0, STALONE_TRAY_DOCK_CONFIRMED, ti->wid, 0, 0); RETURN_STATUS(rc != 0); #else /* This is here for debugging purposes */ { pthread_t delayed_thread; pthread_create( &delayed_thread, NULL, send_delayed_confirmation, (void *)ti); LOG_TRACE(("sent delayed confirmation\n")); RETURN_STATUS(SUCCESS); } #endif } int embedder_unembed(struct TrayIcon *ti) { if (!ti->is_embedded) return SUCCESS; switch (ti->cmode) { case CM_KDE: case CM_FDO: /* Unembed icon as described in system tray protocol */ if (ti->is_embedded) { XSelectInput(tray_data.dpy, ti->wid, NoEventMask); XUnmapWindow(tray_data.dpy, ti->wid); XReparentWindow(tray_data.dpy, ti->wid, DefaultRootWindow(tray_data.dpy), ti->l.icn_rect.x, ti->l.icn_rect.y); XMapRaised(tray_data.dpy, ti->wid); if (!x11_ok()) LOG_ERROR(("failed to move icon 0x%lx out of the tray\n", ti->wid)); } /* Destroy mid-parent */ if (ti->mid_parent != None) { XDestroyWindow(tray_data.dpy, ti->mid_parent); if (!x11_ok()) LOG_ERROR(("failed to destroy icon mid-parent 0x%lx\n", ti->mid_parent)); } break; default: LOG_ERR_IE(("Error: the compatibility mode %d is not supported " "(should not happen)\n", ti->cmode)); return FAILURE; } LOG_TRACE(("done unembedding 0x%lx\n", ti->wid)); RETURN_STATUS(x11_ok() == 0); /* This resets error status for the generations to come (XXX) */ } int embedder_hide(struct TrayIcon *ti) { XUnmapWindow(tray_data.dpy, ti->mid_parent); /* We do not wany any StructureNotify events for icon window anymore */ XSelectInput(tray_data.dpy, ti->wid, PropertyChangeMask); if (!x11_ok()) { ti->is_invalid = True; return FAILURE; } else { ti->is_size_set = False; ti->num_size_resets = 0; ti->is_visible = False; return SUCCESS; } } int embedder_show(struct TrayIcon *ti) { unsigned int x, y; /* If the window has never been embedded, * perform real embedding */ if (ti->mid_parent == None) { ti->is_visible = True; return embedder_embed(ti); } /* 0. calculate new position for mid-parent */ CALC_INNER_POS(x, y, ti); /* 1. move mid-parent to new location */ XMoveResizeWindow(tray_data.dpy, ti->mid_parent, ti->l.icn_rect.x + x, ti->l.icn_rect.y + y, ti->l.wnd_sz.x, ti->l.wnd_sz.y); /* 2. adjust icon position inside mid-parent */ XMoveWindow(tray_data.dpy, ti->wid, 0, 0); /* 3. map icon ? */ XMapRaised(tray_data.dpy, ti->wid); /* 4. map mid-parent */ XMapWindow(tray_data.dpy, ti->mid_parent); XSelectInput( tray_data.dpy, ti->wid, StructureNotifyMask | PropertyChangeMask); if (!x11_ok()) { ti->is_invalid = True; return FAILURE; } else { ti->is_visible = True; return SUCCESS; } } static int update_forced = False; static int embedder_update_window_position(struct TrayIcon *ti) { int x, y; /* Ignore hidden icons */ if (!ti->is_visible) return NO_MATCH; /* Update only those icons that do want it (everyone if update was forced) */ if (!update_forced && !ti->is_updated && !ti->is_resized && ti->is_embedded) return NO_MATCH; LOG_TRACE(("Updating position of icon 0x%lx\n", ti->wid)); /* Recalculate icon position */ CALC_INNER_POS(x, y, ti); /* Reset the flags */ ti->is_resized = False; ti->is_updated = False; /* Move mid-parent window */ XMoveResizeWindow(tray_data.dpy, ti->mid_parent, ti->l.icn_rect.x + x, ti->l.icn_rect.y + y, ti->l.wnd_sz.x, ti->l.wnd_sz.y); /* Sanitize icon position inside mid-parent */ XMoveWindow(tray_data.dpy, ti->wid, 0, 0); /* Refresh the icon */ embedder_refresh(ti); if (!x11_ok()) { LOG_TRACE(("failed to update position of icon 0x%lx\n", ti->wid)); ti->is_invalid = True; } return NO_MATCH; } int embedder_update_positions(int forced) { /* I wish C had closures =( */ update_forced = forced; icon_list_forall(&embedder_update_window_position); return SUCCESS; } int embedder_refresh(struct TrayIcon *ti) { if (!ti->is_visible) return NO_MATCH; XClearWindow(tray_data.dpy, ti->mid_parent); x11_refresh_window( tray_data.dpy, ti->wid, ti->l.wnd_sz.x, ti->l.wnd_sz.y, True); /* Check if the icon has survived all these manipulations */ if (!x11_ok()) { LOG_TRACE(("could not refresh 0x%lx\n", ti->wid)); ti->is_invalid = True; } return NO_MATCH; } /* This function defines initial icon size or * is used to reset size of the icon window */ int embedder_reset_size(struct TrayIcon *ti) { struct Point icon_sz = {0, 0}; int rc = FAILURE; /* Do not reset size for non-KDE icons with size set if icon_resizes * are handled */ if (ti->is_size_set && ti->cmode != CM_KDE && !(settings.kludge_flags & KLUDGE_FORCE_ICONS_SIZE)) return SUCCESS; /* Increase counter of size resets for given icon. If this number * exeeds the threshold, do nothing. This should work around the icons * that react badly to size changes */ if (ti->is_size_set) ti->num_size_resets++; if (ti->num_size_resets > ICON_SIZE_RESETS_THRESHOLD) return SUCCESS; if (ti->cmode == CM_KDE) { icon_sz.x = settings.icon_size < KDE_ICON_SIZE ? settings.icon_size : KDE_ICON_SIZE; icon_sz.y = icon_sz.x; } else { /* If icon hints are to be respected, retrive the data */ if (settings.kludge_flags & KLUDGE_USE_ICONS_HINTS) rc = x11_get_window_min_size( tray_data.dpy, ti->wid, &icon_sz.x, &icon_sz.y); /* If this has failed, or icon hinst are not respected, or minimal size * hints are too small, fall back to default values */ if (!rc || !(settings.kludge_flags & KLUDGE_USE_ICONS_HINTS) || (settings.kludge_flags & KLUDGE_FORCE_ICONS_SIZE) || (icon_sz.x < settings.icon_size && icon_sz.y < settings.icon_size)) { icon_sz.x = settings.icon_size; icon_sz.y = settings.icon_size; } } LOG_TRACE(("proposed icon size: %dx%d\n", icon_sz.x, icon_sz.y)); if (x11_set_window_size(tray_data.dpy, ti->wid, icon_sz.x, icon_sz.y)) { ti->l.wnd_sz = icon_sz; ti->is_size_set = True; return SUCCESS; } else { ti->is_invalid = True; return FAILURE; } } ================================================ FILE: src/embed.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * embed.h * Fri, 03 Sep 2004 20:38:55 +0700 * ------------------------------- * embedding cycle implementation * -------------------------------*/ #ifndef _EMBED_H_ #include "icons.h" /* Constants for compatibility modes */ /* KDE */ #define CM_KDE 1 /* Generic, freedesktop.org */ #define CM_FDO 2 /* Embed an icon */ int embedder_embed(struct TrayIcon *ti); /* Unembed an icon */ int embedder_unembed(struct TrayIcon *ti); /* If (forced) * recalculate and update positions of all icons; * else * recalculate and update positions of all icons that have requested an * update; */ int embedder_update_positions(int force); /* Show the icon */ int embedder_show(struct TrayIcon *ti); /* Hide the icon */ int embedder_hide(struct TrayIcon *ti); /* Refresh icon and its parent */ int embedder_refresh(struct TrayIcon *ti); /* (Re)set icon size according to the policy */ int embedder_reset_size(struct TrayIcon *ti); #endif ================================================ FILE: src/icons.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * icons.c * Tue, 24 Aug 2004 12:05:38 +0700 * ------------------------------- * Manipulations with reparented * windows --- tray icons * -------------------------------*/ #include #include #include "icons.h" #include "common.h" #include "debug.h" #include "layout.h" #include "list.h" #include "tray.h" #ifdef DEBUG #include "xutils.h" #endif #include "assert.h" struct TrayIcon *icons_head = NULL; struct TrayIcon *icon_list_new(Window wid, int cmode) { struct TrayIcon *new_icon; /* Do not allocate second structure for the same window */ if (icon_list_find(wid) != NULL) return NULL; if ((new_icon = malloc(sizeof(struct TrayIcon))) == NULL) { LOG_ERR_OOM(("Could not allocate memory for new icon\n")); return NULL; } new_icon->wid = wid; new_icon->l.wnd_sz.x = 0; new_icon->l.wnd_sz.y = 0; new_icon->mid_parent = None; new_icon->cmode = cmode; new_icon->is_embedded = False; new_icon->is_layed_out = False; new_icon->is_updated = False; new_icon->is_resized = True; new_icon->is_visible = False; new_icon->is_invalid = False; new_icon->is_xembed_supported = False; new_icon->is_size_set = False; new_icon->num_size_resets = 0; LIST_ADD_ITEM(icons_head, new_icon); return new_icon; } int icon_list_free(struct TrayIcon *ti) { if (ti != NULL) { LIST_DEL_ITEM(icons_head, ti); free(ti); } return SUCCESS; } struct TrayIcon *icon_list_next(struct TrayIcon *ti) { return (ti != NULL && ti->next != NULL) ? ti->next : icons_head; } struct TrayIcon *icon_list_prev(struct TrayIcon *ti) { struct TrayIcon *tmp; if (ti != NULL && ti->prev != NULL) return ti->prev; else { tmp = icons_head; for (; tmp->next != NULL; tmp = tmp->next) ; return tmp; } } static struct TrayIcon *backup_head = NULL; int icon_list_backup() { struct TrayIcon *tmp, *cur, *cur2; /* Refuse to perform second backup in a row */ if (backup_head != NULL) { DIE_IE(("Only one backup of icon list at a time is supported\n")); } /* For each icon in the list we allocate new temporary structure and add it * to the end of temporary list backup_head */ for (cur = icons_head, cur2 = NULL; cur != NULL; cur = cur->next, cur2 = tmp) { tmp = (struct TrayIcon *)malloc(sizeof(struct TrayIcon)); if (tmp == NULL) { LOG_ERR_OOM(("Could not allocate backup list")); icon_list_backup_purge(); return FAILURE; } memcpy(tmp, cur, sizeof(struct TrayIcon)); LIST_INSERT_AFTER(backup_head, cur2, tmp); cur2 = tmp; } return SUCCESS; } int icon_list_restore() { struct TrayIcon *cur_b, *cur_i, *prev_sv, *next_sv; LOG_TRACE(("restoring the icon list from the backup\n")); /* Restore the list by copying raw data from * backup list. This assumes that sequences have the * same length. */ for (cur_i = icons_head, cur_b = backup_head; cur_i != NULL && cur_b != NULL; cur_i = cur_i->next, cur_b = cur_b->next) { prev_sv = cur_i->prev; next_sv = cur_i->next; memcpy(cur_i, cur_b, sizeof(struct TrayIcon)); cur_i->prev = prev_sv; cur_i->next = next_sv; } /* Some consistency checking: ensures that * both lists had the same length */ assert(cur_i == NULL && cur_b == NULL); /* Clean backup list */ LIST_CLEAN(backup_head, cur_b); backup_head = NULL; return SUCCESS; } int icon_list_backup_purge() { struct TrayIcon *tmp; LOG_TRACE(("purging the backed up icon list\n")); /* Clean backup list */ LIST_CLEAN(backup_head, tmp); backup_head = NULL; return SUCCESS; } struct TrayIcon *icon_list_find(Window wid) { /* Traverse the whole list */ struct TrayIcon *tmp; for (tmp = icons_head; tmp != NULL; tmp = tmp->next) if (tmp->wid == wid) return tmp; return NULL; } struct TrayIcon *icon_list_find_ex(Window wid) { /* Traverse the whole list */ struct TrayIcon *tmp; for (tmp = icons_head; tmp != NULL; tmp = tmp->next) if (tmp->wid == wid || tmp->mid_parent == wid) return tmp; return NULL; } int icon_list_clean() { struct TrayIcon *tmp; LIST_CLEAN(icons_head, tmp); return SUCCESS; } int icon_list_clean_callback(IconCallbackFunc cbk) { struct TrayIcon *tmp; LIST_CLEAN_CBK(icons_head, tmp, cbk); return SUCCESS; } /* TODO: is it necessary always to sort the full list? */ void icon_list_sort(IconCmpFunc cmp) { struct TrayIcon *new_head = NULL, *cur, *tmp; while (icons_head != NULL) { /* Find the least element and move it to temporary list */ cur = icons_head; for (tmp = icons_head; tmp != NULL; tmp = tmp->next) if (cmp(tmp, cur) > 0) cur = tmp; LIST_DEL_ITEM(icons_head, cur); LIST_ADD_ITEM(new_head, cur); } icons_head = new_head; } struct TrayIcon *icon_list_forall(IconCallbackFunc cbk) { return icon_list_forall_from(icons_head, cbk); } struct TrayIcon *icon_list_forall_from( struct TrayIcon *tgt, IconCallbackFunc cbk) { /* Traverse the list starting from tgt*/ struct TrayIcon *tmp; for (tmp = (tgt != NULL ? tgt : icons_head); tmp != NULL; tmp = tmp->next) if (cbk(tmp) == MATCH) { return tmp; } return NULL; } ================================================ FILE: src/icons.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * icons.h * Tue, 24 Aug 2004 12:05:38 +0700 * ------------------------------- * Manipulations with the list of * tray icons * -------------------------------*/ #ifndef _ICONS_H_ #define _ICONS_H_ #include #include /* Simple point & rect data structures */ struct Point { int x, y; }; struct Rect { int x, y, w, h; }; /* Tray icon layout data structure */ struct Layout { struct Rect grd_rect; /* The rect in the grid */ struct Rect icn_rect; /* Real position inside the tray */ struct Point wnd_sz; /* Size of the window of the icon */ }; /* Tray icon data structure */ struct TrayIcon { struct TrayIcon *next; struct TrayIcon *prev; Window wid; /* Window ID */ Window mid_parent; /* Mid-parent ID */ int cmode; /* Compatibility mode: CM_FDO/CM_KDE (see embed.h) */ int is_embedded; /* Flag: is the icon succesfully embedded ? */ int is_invalid; /* Flag: is the icon invalid ? */ int is_visible; /* Flag: is the icon hidden ? */ int is_resized; /* Flag: the icon has recently resized itself */ int is_layed_out; /* Flag: the icon is succesfully layed out */ int is_updated; /* Flag: the position of the icon needs to be updated */ int is_xembed_supported; /* Flag: does the icon support xembed */ unsigned long xembed_data[2]; /* XEMBED data */ int num_size_resets; /* How many times size was reset */ int is_size_set; /* Flag: has the size for the icon been set */ int is_xembed_accepts_focus; /* Flag: does the icon want focus */ long xembed_last_timestamp; /* The timestamp of last processed xembed message */ long xembed_last_msgid; /* ID of the last processed xembed message */ struct Layout l; /* Layout info */ }; /* Typedef for comparison function */ typedef int (*IconCmpFunc)(struct TrayIcon *, struct TrayIcon *); /* Typedef for callback function */ typedef int (*IconCallbackFunc)(struct TrayIcon *); /* Add the new icon to the list */ struct TrayIcon *icon_list_new(Window w, int cmode); /* Delete the icon from the list */ int icon_list_free(struct TrayIcon *ti); /* Return the next/previous icon in the list after the icon specified by ti */ struct TrayIcon *icon_list_next(struct TrayIcon *ti); struct TrayIcon *icon_list_prev(struct TrayIcon *ti); /************************************************* * BIG FAT WARNING: backup/restore routines will * memleak/fail if the number of icons in the * list has changed between backup/restore calls. * (in return, it does not invalidate pointers :P) *************************************************/ /* Back up the list */ int icon_list_backup(); /* Restore the list from the backup */ int icon_list_restore(); /* Free the back-up list */ int icon_list_backup_purge(); /* Apply a callback specified by cbk to all icons. * List is traversed in a natural order. Function stops * and returns current_icon if cbk(current_icon) == MATCH */ struct TrayIcon *icon_list_forall(IconCallbackFunc cbk); /* For readability sake, we sometimes use this function */ #define icon_list_advanced_find icon_list_forall /* Same as above, but start traversal from the icon specified by tgt */ struct TrayIcon *icon_list_forall_from( struct TrayIcon *tgt, IconCallbackFunc cbk); /* Clear the whole list */ int icon_list_clean(); /* Clear the whole list, calling cbk for each icon */ int icon_list_clean_callback(IconCallbackFunc cbk); /* Sort the list using comparison function specified by cmp. * Memo for writing comparison functions: * if a < b => cmp(a,b) < 0 * if a = b => cmp(a,b) = 0 * if a > b => cmp(a,b) > 0 */ void icon_list_sort(IconCmpFunc cmp); /* Find the icon with wid == w */ struct TrayIcon *icon_list_find(Window w); /* Find the icon with wid == w or parent wid == w */ struct TrayIcon *icon_list_find_ex(Window w); #endif ================================================ FILE: src/image.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * image.c * Fri, 22 Jun 2007 23:32:27 +0700 * ------------------------------- * Simple XImage manipulation * interface * -------------------------------*/ #include "image.h" #include "debug.h" /***** Forward declarations *****/ /* Depth-specialized versions of tinting routine */ int image_tint_32(CARD8 *data, size_t len, CARD32 pixel, CARD8 alpha); int image_tint_24(CARD8 *data, size_t len, CARD32 pixel, CARD8 alpha); int image_tint_16(CARD16 *data, size_t len, CARD32 pixel, CARD8 alpha); int image_tint_15(CARD16 *data, size_t len, CARD32 pixel, CARD8 alpha); /* Depth-specialized versions of compose routine */ int image_compose_32(CARD8 *data, CARD8 *bg, CARD8 *mask, size_t len); int image_compose_24(CARD8 *data, CARD8 *bg, CARD8 *mask, size_t len); int image_compose_16(CARD16 *data, CARD16 *bg, CARD8 *mask, size_t len); int image_compose_15(CARD16 *data, CARD16 *bg, CARD8 *mask, size_t len); /***** Interface level *****/ int image_tint(XImage *image, XColor *color, CARD8 alpha) { switch (image->bits_per_pixel) { case 32: return image_tint_32((CARD8 *)image->data, image->width * image->height, color->pixel, alpha); case 24: return image_tint_24((CARD8 *)image->data, image->width * image->height, color->pixel, alpha); case 16: return image_tint_16((CARD16 *)image->data, image->width * image->height, color->pixel, alpha); case 15: return image_tint_15((CARD16 *)image->data, image->width * image->height, color->pixel, alpha); default: DIE_IE(("image_tint() called with unsupported depth %d\n", image->bits_per_pixel)); return FAILURE; } } int image_compose(XImage *image, XImage *bg, CARD8 *mask) { switch (image->bits_per_pixel) { case 32: return image_compose_32((CARD8 *)image->data, (CARD8 *)bg->data, mask, (image->width * image->height)); case 24: return image_compose_24((CARD8 *)image->data, (CARD8 *)bg->data, mask, (image->width * image->height)); case 16: return image_compose_16((CARD16 *)image->data, (CARD16 *)bg->data, mask, (image->width * image->height)); case 15: return image_compose_15((CARD16 *)image->data, (CARD16 *)bg->data, mask, (image->width * image->height)); default: DIE_IE(("image_compose() called with unsupported depth %d\n", image->bits_per_pixel)); return FAILURE; } } CARD8 *image_create_alpha_mask(int ord, int w, int h) { unsigned char *m, *ll, *ul; int x, y, bord; bord = (1 << ord) - 1; m = malloc(w * h); if (m == NULL) return NULL; memset(m, 255, w * h); /* Shade top and bootom of the rectangle */ ul = m; /* top */ ll = m + (w * (h - 1)); /* bottom */ for (y = 0; y < bord; y++) { for (x = 0; x < w; x++) { ul[x] = ((unsigned int)ul[x] * (y + 1)) >> ord; ll[x] = ((unsigned int)ll[x] * (y + 1)) >> ord; } ul += w; ll -= w; } /* Shade left and right of the rectangle */ for (x = 0; x < bord; x++) { ul = m + x; /* left side */ ll = m + w - x - 1; /* right side */ for (y = 0; y < h; y++) { *ul = ((unsigned int)*ul * (x + 1)) >> ord; *ll = ((unsigned int)*ll * (x + 1)) >> ord; ll += w; ul += w; } } return m; } /***** Implementation level *****/ #define sr16(p, a) (((CARD32)((p)&0xf800) * (a))) #define sg16(p, a) (((CARD32)((p)&0x7e0) * (a))) #define sb16(p, a) (((CARD32)((p)&0x1f) * (a))) #define sr15(p, a) (((CARD32)((p)&0x7c00) * (a))) #define sg15(p, a) (((CARD32)((p)&0x3e0) * (a))) #define sb15(p, a) (((CARD32)((p)&0x1f) * (a))) int image_tint_32(CARD8 *data, size_t len, CARD32 pixel, CARD8 alpha) { CARD8 *p, tr, tg, tb, ralpha; ralpha = 255 - alpha; #ifndef BIGENDIAN tr = ((pixel & 0x00ff0000) * alpha) >> 24; tg = ((pixel & 0x0000ff00) * alpha) >> 16; tb = ((pixel & 0x000000ff) * alpha) >> 8; #else tr = ((pixel & 0x000000ff) * alpha) >> 8; tg = ((pixel & 0x0000ff00) * alpha) >> 16; tb = ((pixel & 0x00ff0000) * alpha) >> 24; #endif /* traverse data by 4 bytes starting from the end */ for (p = data + (len - 1) * 4; p >= data; p -= 4) { #ifndef BIGENDIAN p[0] = (((CARD16)p[0] * ralpha) >> 8) + tb; p[1] = (((CARD16)p[1] * ralpha) >> 8) + tg; p[2] = (((CARD16)p[2] * ralpha) >> 8) + tr; #else p[0] = (((CARD16)p[0] * ralpha) >> 8) + tr; p[1] = (((CARD16)p[1] * ralpha) >> 8) + tg; p[2] = (((CARD16)p[2] * ralpha) >> 8) + tb; #endif } return SUCCESS; } int image_tint_24(CARD8 *data, size_t len, CARD32 pixel, CARD8 alpha) { CARD8 *p, tr, tg, tb, ralpha; ralpha = 255 - alpha; #ifndef BIGENDIAN tr = ((pixel & 0x00ff0000) * alpha) >> 24; tg = ((pixel & 0x0000ff00) * alpha) >> 16; tb = ((pixel & 0x000000ff) * alpha) >> 8; #else tr = ((pixel & 0x000000ff) * alpha) >> 8; tg = ((pixel & 0x0000ff00) * alpha) >> 16; tb = ((pixel & 0x00ff0000) * alpha) >> 24; #endif /* traverse data by 3 bytes starting from the end */ for (p = data + (len - 1) * 3; p >= data; p -= 3) { #ifndef BIGENDIAN p[0] = (((CARD16)p[0] * ralpha) >> 8) + tb; p[1] = (((CARD16)p[1] * ralpha) >> 8) + tg; p[2] = (((CARD16)p[2] * ralpha) >> 8) + tr; #else p[0] = (((CARD16)p[0] * ralpha) >> 8) + tr; p[1] = (((CARD16)p[1] * ralpha) >> 8) + tg; p[2] = (((CARD16)p[2] * ralpha) >> 8) + tb; #endif } return SUCCESS; } int image_tint_16(CARD16 *data, size_t len, CARD32 pixel, CARD8 alpha) { CARD32 tr, tg, tb; CARD32 r, g, b; CARD16 *p; CARD8 ralpha; ralpha = 255 - alpha; tr = sr16(pixel, alpha); tg = sg16(pixel, alpha); tb = sb16(pixel, alpha); /* traverse data by 2 bytes starting from the end */ for (p = data + len - 1; p >= data; p--) { r = sr16(*p, ralpha) + tr; g = sg16(*p, ralpha) + tg; b = sb16(*p, ralpha) + tb; *p = ((r >> 8) & 0xf800) | ((g >> 8) & 0x7e0) | ((b >> 8) & 0x1f); } return SUCCESS; } int image_tint_15(CARD16 *data, size_t len, CARD32 pixel, CARD8 alpha) { CARD32 tr, tg, tb; CARD32 r, g, b; CARD16 *p; CARD8 ralpha; ralpha = 255 - alpha; tr = sr15(pixel, alpha); tg = sg15(pixel, alpha); tb = sb15(pixel, alpha); /* traverse data by 2 bytes starting from the end */ for (p = data + len - 1; p >= data; p--) { r = sr15(*p, ralpha) + tr; g = sg15(*p, ralpha) + tg; b = sb15(*p, ralpha) + tb; *p = ((r >> 8) & 0x7c00) | ((g >> 8) & 0x3e0) | ((b >> 8) & 0x1f); } return SUCCESS; } int image_compose_32(CARD8 *data, CARD8 *bg, CARD8 *mask, size_t len) { CARD8 *p, *b, *m; CARD16 a, ra; /* traverse data, bg by 4 bytes and mask by 1 byte starting from the end */ for (p = data + (len - 1) * 4, b = bg + (len - 1) * 4, m = mask + len - 1; p != data - 4; p -= 4, b -= 4, m--) { a = *m; ra = 255 - a; p[0] = (p[0] * a + b[0] * ra) >> 8; p[1] = (p[1] * a + b[1] * ra) >> 8; p[2] = (p[2] * a + b[2] * ra) >> 8; } return SUCCESS; } int image_compose_24(CARD8 *data, CARD8 *bg, CARD8 *mask, size_t len) { CARD8 *p, *b, *m; CARD16 a, ra; /* traverse data, bg by 3 bytes and mask by 1 byte starting from the end */ for (p = data + (len - 1) * 3, b = bg + (len - 1) * 3, m = mask + len - 1; p != data - 3; p -= 3, b -= 3, m--) { a = *m; ra = 255 - a; p[0] = (p[0] * a + b[0] * ra) >> 8; p[1] = (p[1] * a + b[1] * ra) >> 8; p[2] = (p[2] * a + b[2] * ra) >> 8; } return SUCCESS; } int image_compose_16(CARD16 *data, CARD16 *bg, CARD8 *mask, size_t len) { CARD32 r, g, b; CARD16 *p, *pb; CARD8 a, ra, *m; /* traverse data by 2 bytes starting from the end */ for (p = data + len - 1, pb = bg + len - 1, m = mask + len - 1; p != data; p--, pb--, m--) { a = *m; ra = 255 - a; r = sr16(*p, a) + sr16(*pb, ra); g = sg16(*p, a) + sg16(*pb, ra); b = sb16(*p, a) + sb16(*pb, ra); *p = ((r >> 8) & 0xf800) | ((g >> 8) & 0x7e0) | ((b >> 8) & 0x1f); } return SUCCESS; } int image_compose_15(CARD16 *data, CARD16 *bg, CARD8 *mask, size_t len) { CARD32 r, g, b; CARD16 *p, *pb; CARD8 a, ra, *m; /* traverse data by 2 bytes starting from the end */ for (p = data + len - 1, pb = bg + len - 1, m = mask + len - 1; p != data; p--, pb--, m--) { a = *mask; ra = 255 - a; r = sr15(*p, a) + sr15(*pb, ra); g = sg15(*p, a) + sg15(*pb, ra); b = sb15(*p, a) + sb15(*pb, ra); *p = ((r >> 8) & 0x7c00) | ((g >> 8) & 0x3e0) | ((b >> 8) & 0x1f); } return SUCCESS; } ================================================ FILE: src/image.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * image.h * Fri, 22 Jun 2007 23:32:27 +0700 * ------------------------------- * Simple XImage manipulation * interface * -------------------------------*/ #ifndef _IMAGE_H_ #define _IMAGE_H_ #include #include /* outstanding TODO (for 0.8): use Xrender when available */ /* WARNING: works with ZPixmaps only */ /* Creates alpha channel mask with specified fade-out order */ CARD8 *image_create_alpha_mask(int ord, int w, int h); /* Alpha-tint image using color. */ int image_tint(XImage *image, XColor *color, CARD8 alpha); /* Compose image stored in tgt with image stored in bg. * Alpha of each pixel is defined by mask, which should */ int image_compose(XImage *tgt, XImage *bg, CARD8 *mask); #endif ================================================ FILE: src/kde_tray.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * kde_tray.c * Sun, 19 Sep 2004 12:31:10 +0700 * ------------------------------- * kde tray related routines * -------------------------------*/ #include #include #include #include "debug.h" #include "kde_tray.h" #include "xutils.h" /* This list holds "old" KDE icons, e.g. the icons that are (likely) to be * already embedded into some system tray and, therefore, are to be ignored. * The list is empty initially */ Window *old_kde_icons = NULL; unsigned long n_old_kde_icons = -1; int kde_tray_update_fallback_mode(Display *dpy) { /* Get the contents of KDE_NET_SYSTEM_TRAY_WINDOWS root window property. * All windows that are listed there are considered to be "old" KDE icons, * i.e. icons that are to be ignored on the tray startup. * If the property does not exist, fall back to old mode */ if (tray_data.xa_kde_net_system_tray_windows == None || !x11_get_root_winlist_prop(dpy, tray_data.xa_kde_net_system_tray_windows, (unsigned char **)&old_kde_icons, &n_old_kde_icons)) { LOG_INFO(("WM does not support KDE_NET_SYSTEM_TRAY_WINDOWS, will use " "legacy scheme\n")); x11_extend_root_event_mask(tray_data.dpy, SubstructureNotifyMask); tray_data.kde_tray_old_mode = 1; } else { tray_data.kde_tray_old_mode = 0; } return tray_data.kde_tray_old_mode; } void kde_tray_init(Display *dpy) { static int initialized = False; unsigned long n_client_windows, i; Window *client_windows; Atom xa_net_client_list; if (!kde_tray_update_fallback_mode(dpy)) return; /* do nothing if this function was already called */ if (initialized) return; /* 1. If theres no previous tray selection owner, try to embed all * available KDE icons and, therefore, leave the list of old KDE icons * empty */ if (tray_data.old_selection_owner == None) { n_old_kde_icons = 0; initialized = True; return; } /* 2.Next, we are going to remove some entries from old_kde_icons list */ /* 2.a. First, we remove all icons that are listed in _NET_CLIENT_LIST * property, since this means that they are not embedded in any kind of * tray */ xa_net_client_list = XInternAtom(dpy, "_NET_CLIENT_LIST", True); if (x11_get_root_winlist_prop(dpy, xa_net_client_list, (unsigned char **)&client_windows, &n_client_windows)) { for (i = 0; i < n_client_windows; i++) kde_tray_old_icons_remove(client_windows[i]); } /* 2.b. Second, we remove all windows that have root window as their * parent, * since this also means that they are not embedded in any kind of tray */ for (i = 0; i < n_old_kde_icons; i++) { Window root, parent, *children; unsigned int nchildren; int rc; nchildren = 0; children = NULL; if ((rc = XQueryTree(dpy, old_kde_icons[i], &root, &parent, &children, &nchildren))) { if (root == parent) old_kde_icons[i] = None; if (nchildren > 0) XFree(children); } if (!x11_ok() || !rc) old_kde_icons[i] = None; } #ifdef DEBUG /* Some diagnostic output */ for (i = 0; i < n_old_kde_icons; i++) if (old_kde_icons[i] != None) LOG_TRACE( ("0x%lx is marked as an old KDE icon\n", old_kde_icons[i])); #endif initialized = True; } int kde_tray_update_old_icons(Display *dpy) { unsigned int i, rc; XWindowAttributes xwa; /* Remove dead entries from old kde icons list. * We use XGetWindowAttributes to see if the * window is still alive */ for (i = 0; i < n_old_kde_icons; i++) { rc = XGetWindowAttributes(dpy, old_kde_icons[i], &xwa); if (!x11_ok() || !rc) old_kde_icons[i] = None; } return SUCCESS; } int kde_tray_is_old_icon(Window w) { unsigned int i; for (i = 0; i < n_old_kde_icons; i++) if (old_kde_icons[i] == w) return True; return False; } void kde_tray_old_icons_remove(Window w) { unsigned int i; for (i = 0; i < n_old_kde_icons; i++) if (old_kde_icons[i] == w) { LOG_TRACE(("0x%lx unmarked as an old kde icon\n", w)); old_kde_icons[i] = None; } } int kde_tray_check_for_icon(Display *dpy, Window w) { Atom actual_type; int actual_format; unsigned long nitems, bytes_after; static Atom xa_kde_net_wm_system_tray_window_for = None; unsigned char *data = NULL; /* Check if the window has a property named _KDE_NET_WM_SYSTEM_TRAY_WINDOW * FOR */ if (xa_kde_net_wm_system_tray_window_for == None) xa_kde_net_wm_system_tray_window_for = XInternAtom(dpy, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", True); /* If this atom does not exist, we have nothing to check for */ if (xa_kde_net_wm_system_tray_window_for == None) return False; /* TODO: use x11_ call */ XGetWindowProperty(dpy, w, xa_kde_net_wm_system_tray_window_for, 0L, 1L, False, XA_WINDOW, &actual_type, &actual_format, &nitems, &bytes_after, &data); XFree(data); if (x11_ok() && actual_type == XA_WINDOW && nitems == 1) return SUCCESS; else return FAILURE; } Window kde_tray_find_icon(Display *dpy, Window w) { Window root, parent, *children = NULL; unsigned int nchildren, i; Window r = None; if (kde_tray_check_for_icon(dpy, w)) return w; XQueryTree(dpy, w, &root, &parent, &children, &nchildren); if (!x11_ok()) goto bailout; for (i = 0; i < nchildren; i++) if ((r = kde_tray_find_icon(dpy, children[i])) != None) goto bailout; bailout: if (children != NULL && nchildren > 0) XFree(children); return r; } ================================================ FILE: src/kde_tray.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * kde_tray.h * Sun, 19 Sep 2004 12:28:59 +0700 * ------------------------------- * KDE tray related routines * -------------------------------*/ #ifndef _KDE_TRAY_H_ #define _KDE_TRAY_H_ /* Init support for KDE tray icons. */ void kde_tray_init(Display *dpy); /* Check if WM supports KDE tray icons */ int kde_tray_update_fallback_mode(Display *dpy); /* Update the list of "old" KDE icons. Icon is considered "old" * if it was present before the tray was started. */ int kde_tray_update_old_icons(Display *dpy); /* Check if the window w is an "old" KDE tray icon */ int kde_tray_is_old_icon(Window w); /* Remove the window w from the list of "old" KDE tray icons */ void kde_tray_old_icons_remove(Window w); /* Check if the window w is a KDE tray icon */ int kde_tray_check_for_icon(Display *dpy, Window w); /* Find KDE tray icon in subwindows of w */ Window kde_tray_find_icon(Display *dpy, Window w); #endif ================================================ FILE: src/layout.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * layout.c * Tue, 24 Aug 2004 12:19:48 +0700 * ------------------------------- * Icon layout implementation. * (Used to be) the dirtiest place around. * -------------------------------*/ #include #include #include #include "common.h" #include "debug.h" #include "icons.h" #include "layout.h" #include "list.h" #include "tray.h" /* not very nice (and calculates its arguments twice!), but allows doing things * like swap(*a, *b) */ #define swap(a, b) \ do { \ int t; \ t = (a); \ (a) = (b); \ (b) = t; \ } while (0) /***************** * Grid interface * *****************/ struct Point grid_sz = {0, 0}; int grid_add(struct TrayIcon *ti); int grid_add_wrapper(struct TrayIcon *ti); int grid_remove(struct TrayIcon *ti); int grid_update(struct TrayIcon *ti, int sort); int grid_translate_from_window(struct TrayIcon *ti); int layout_translate_to_window(struct TrayIcon *ti); int layout_unset_flag(struct TrayIcon *ti); /************************ * Layout implementation * ************************/ int layout_add(struct TrayIcon *ti) { if (grid_add(ti)) { grid_update(ti, True); return SUCCESS; } else return FAILURE; } int layout_remove(struct TrayIcon *ti) { return ti->is_layed_out ? grid_remove(ti) : SUCCESS; } int layout_handle_icon_resize(struct TrayIcon *ti) { struct Rect old_grd_rect; struct Point old_grid_sz; int rc; #ifdef DEBUG if (settings.log_level >= LOG_LEVEL_TRACE) { LOG_TRACE(("currently managed icons:\n")); icon_list_forall(&print_icon_data); } #endif if (!icon_list_backup()) return FAILURE; old_grid_sz = grid_sz; if (ti->is_layed_out) { /* if the icon is already layed up and * its grid rect did not change we do nothing, * since its position inside grid cell will be * updated by embedder_update_positions */ old_grd_rect = ti->l.grd_rect; grid_translate_from_window(ti); if (ti->l.grd_rect.w == old_grd_rect.w && ti->l.grd_rect.h == old_grd_rect.h) { icon_list_backup_purge(); return SUCCESS; } } /* Here's the place where icon sorting start playing its role. * It is easy to see that resizing ti affects only those icons * that are after ti in the icon list. */ /* 1. Unset layout flags of all icons after ti */ icon_list_forall_from(ti, &layout_unset_flag); /* 2. If shrink mode is on, recalculate the size of the grid * for the remaining icons. This ensures that final grid * will be "minimal" (I did not prove that :) */ if (settings.shrink_back_mode) grid_update(ti, False); #ifdef DEBUG if (settings.log_level >= LOG_LEVEL_TRACE) { LOG_TRACE(("list of icons which are to be added back to the grid\n")); icon_list_forall_from(ti, &print_icon_data); } #endif /* 3. Start adding icons after ti back to the grid one * by one. If this fails for some icon, forall_icons_from() * will return non-NULL value and this will be considered * as a error condition */ if (icon_list_forall_from(ti, &grid_add_wrapper) == NULL) { /* Everything is OK */ icon_list_backup_purge(); grid_update(ti, True); rc = SUCCESS; } else { /* Error has occured */ icon_list_restore(); grid_sz = old_grid_sz; rc = FAILURE; } #ifdef DEBUG if (settings.log_level >= LOG_LEVEL_TRACE) { LOG_TRACE(("currently managed icons:\n")); icon_list_forall(&print_icon_data); } #endif return rc; } void layout_get_size(int *width, int *height) { *width = grid_sz.x * settings.slot_size.x; *height = grid_sz.y * settings.slot_size.y; if (settings.vertical) swap((*width), (*height)); } struct TrayIcon *layout_next(struct TrayIcon *current) { if ((settings.icon_gravity & GRAV_H) == GRAV_W) return icon_list_next(current); else return icon_list_prev(current); } struct TrayIcon *layout_prev(struct TrayIcon *current) { if ((settings.icon_gravity & GRAV_H) == GRAV_W) return icon_list_prev(current); else return icon_list_next(current); } /********************** * Grid implementation * **********************/ /* Structure to hold possible placement for an icon */ struct IconPlacement { struct Point pos; /* Position */ struct Point sz_delta; /* Layout size delta */ int valid; /* Is the placement valid */ }; /* Find best placement for an icon */ struct IconPlacement *grid_find_placement(struct TrayIcon *ti); /* Place tray icon as specified by ip */ int grid_place_icon(struct TrayIcon *ti, struct IconPlacement *ip); /* Comparison function for icon_list_sort */ int trayicon_cmp_func(struct TrayIcon *ti1, struct TrayIcon *ti2); /* Update grid dimentions */ int grid_recalc_size(struct TrayIcon *ti); /* A. Coords translations */ int grid_translate_from_window(struct TrayIcon *ti) { ti->l.grd_rect.w = ti->l.wnd_sz.x / settings.slot_size.x + (ti->l.wnd_sz.x % settings.slot_size.x != 0); ti->l.grd_rect.h = ti->l.wnd_sz.y / settings.slot_size.y + (ti->l.wnd_sz.y % settings.slot_size.y != 0); if (settings.vertical) swap(ti->l.grd_rect.w, ti->l.grd_rect.h); return NO_MATCH; } int layout_translate_to_window(struct TrayIcon *ti) { struct Rect old_icn_rect = ti->l.icn_rect; if (!ti->is_layed_out) return NO_MATCH; /* Swap vert & horz dimentions temporarily */ if (settings.vertical) { swap(ti->l.grd_rect.w, ti->l.grd_rect.h); swap(ti->l.grd_rect.x, ti->l.grd_rect.y); } /* Compute icon position and dimensions */ if (settings.icon_gravity & GRAV_W) ti->l.icn_rect.x = ti->l.grd_rect.x * settings.slot_size.x; else ti->l.icn_rect.x = tray_data.xsh.width - (ti->l.grd_rect.x + ti->l.grd_rect.w) * settings.slot_size.x; if (settings.icon_gravity & GRAV_N) ti->l.icn_rect.y = ti->l.grd_rect.y * settings.slot_size.y; else ti->l.icn_rect.y = tray_data.xsh.height - (ti->l.grd_rect.y + ti->l.grd_rect.h) * settings.slot_size.y; ti->l.icn_rect.w = ti->l.grd_rect.w * settings.slot_size.y; ti->l.icn_rect.h = ti->l.grd_rect.h * settings.slot_size.y; /* Swap vert & horz dimentions back */ if (settings.vertical) { swap(ti->l.grd_rect.w, ti->l.grd_rect.h); swap(ti->l.grd_rect.x, ti->l.grd_rect.y); } /* Shift icon position according to scrollbars positions */ /* TODO: must be in separate function, with a reverse */ ti->l.icn_rect.x += ((settings.icon_gravity & GRAV_W ? 1 : -1) * (tray_data.scrollbars_data.scroll_base.x - tray_data.scrollbars_data.scroll_pos.x)); ti->l.icn_rect.y += ((settings.icon_gravity & GRAV_N ? 1 : -1) * (tray_data.scrollbars_data.scroll_base.y - tray_data.scrollbars_data.scroll_pos.y)); LOG_TRACE(("grid %dx%d+%d+%d -> window %dx%d+%d+%d\n", ti->l.grd_rect.w, ti->l.grd_rect.h, ti->l.grd_rect.x, ti->l.grd_rect.y, ti->l.icn_rect.w, ti->l.icn_rect.h, ti->l.icn_rect.x, ti->l.icn_rect.y)); /* Set flag indicating if new position is different from old */ ti->is_updated = ti->is_updated || !(ti->l.icn_rect.x == old_icn_rect.x && ti->l.icn_rect.y == old_icn_rect.y && ti->l.icn_rect.w == old_icn_rect.w && ti->l.icn_rect.h == old_icn_rect.h); return NO_MATCH; } /* B. Adding/removing icon from the grid */ int grid_add(struct TrayIcon *ti) { struct IconPlacement *pmt; /* Invalidate grid position data */ ti->l.grd_rect.x = -1; ti->l.grd_rect.y = -1; ti->l.grd_rect.w = -1; ti->l.grd_rect.h = -1; /* Invalidate window position data */ ti->l.icn_rect.x = -1; ti->l.icn_rect.y = -1; ti->l.icn_rect.w = -1; ti->l.icn_rect.h = -1; /* Get window dimensions */ grid_translate_from_window(ti); pmt = grid_find_placement(ti); if (pmt == NULL) { LOG_TRACE(("no candidates for placement found\n")); ti->is_layed_out = False; RETURN_STATUS(FAILURE); } ti->is_layed_out = True; grid_place_icon(ti, pmt); RETURN_STATUS(SUCCESS); } int grid_remove(struct TrayIcon *ti) { /* implementation is similar to that of layout_handle_icon_resize(), * see detailed description there */ icon_list_forall_from(ti, &layout_unset_flag); if (settings.shrink_back_mode || settings.scrollbars_mode != SB_MODE_NONE) grid_update(ti, False); /* Since NULL is a special case for icon_list_froall_from, * avoid calling it for the last icon */ if (ti->next != NULL) RETURN_STATUS( icon_list_forall_from(ti->next, &grid_add_wrapper) == NULL); else RETURN_STATUS(SUCCESS); } /* C. Grid manipulations */ int grid_update(struct TrayIcon *ti, int sort) { icon_list_forall_from(ti, &layout_translate_to_window); if (sort) icon_list_sort(&trayicon_cmp_func); /* recalculate minimal size */ grid_sz.x = 0; grid_sz.y = 0; icon_list_forall(&grid_recalc_size); LOG_TRACE(("final grid size: %dx%d\n", grid_sz.x, grid_sz.y)); return SUCCESS; } /* D. Placement functions */ int grid_place_icon(struct TrayIcon *ti, struct IconPlacement *ip) { struct Layout *l = &ti->l; struct Point *p = &ip->pos; /* Set the flag if icon position was really updated */ ti->is_updated = ti->is_updated || (p->x != l->grd_rect.x || p->y != l->grd_rect.y); if (ti->is_updated || !ti->is_layed_out) { LOG_TRACE(("updating position (%d,%d) to (%d,%d)\n", ti->l.grd_rect.x, ti->l.grd_rect.y, p->x, p->y)); l->grd_rect.x = p->x; l->grd_rect.y = p->y; } /* Update grid dimensions */ grid_sz.x += ip->sz_delta.x; grid_sz.y += ip->sz_delta.y; /* Update icon position */ layout_translate_to_window(ti); LOG_TRACE(("current grid size: %dx%d\n", grid_sz.x, grid_sz.y)); return ti->is_updated; } /* Oh, the wonders of languages without closures... */ static struct Rect chk_rect; /* Check if grid rect of ti intersects with chk_rect */ int find_obstacle(struct TrayIcon *ti) { return ti->is_layed_out && (RECTS_ISECT(chk_rect, ti->l.grd_rect) || RECTS_ISECT(ti->l.grd_rect, chk_rect)); } /* Check if grid rect of ti intersects with any other tray icon * grid rect and return its width or height, depending on tray orientation */ int grid_check_rect_free(int x, int y, int w, int h) { struct TrayIcon *obst; chk_rect.x = x; chk_rect.y = y; chk_rect.w = w; chk_rect.h = h; obst = icon_list_advanced_find(&find_obstacle); if (obst == NULL) { return 0; } else { if (settings.vertical) return obst->l.grd_rect.h; else return obst->l.grd_rect.w; } } /* Simple function to fill in fields of IconPlacement struct * x, y: position of the icon * rect: provides the size of the icon * return value: returns true if the placement is valid; returns false * otherwise. */ int icon_placement_create( struct IconPlacement *ip, int x, int y, struct Rect *rect) { /* scrollbar & tray orientation-aware shortcuts for maximal layout dimensions. * if scrollbar is enabled in some direction, layout size in this dimension is * not limited */ #define max_layout_width \ (settings.scrollbars_mode & SB_MODE_HORZ \ ? INT_MAX \ : ((settings.vertical ? settings.max_tray_dims.y \ : settings.max_tray_dims.x) \ / settings.slot_size.x)) #define max_layout_height \ (settings.scrollbars_mode & SB_MODE_VERT \ ? INT_MAX \ : ((settings.vertical ? settings.max_tray_dims.x \ : settings.max_tray_dims.y) \ / settings.slot_size.y)) ip->valid = 0; ip->pos.x = x; ip->pos.y = y; ip->sz_delta.x = x + rect->w - grid_sz.x; ip->sz_delta.y = y + rect->h - grid_sz.y; ip->sz_delta.x = ip->sz_delta.x > 0 ? ip->sz_delta.x : 0; ip->sz_delta.y = ip->sz_delta.y > 0 ? ip->sz_delta.y : 0; ip->valid = (ip->pos.x + rect->w <= max_layout_width) && (ip->pos.y + rect->h <= max_layout_height); #ifdef DEBUG LOG_TRACE(("placement (%d, %d, %d, %d) is %s\n", x, y, ip->sz_delta.x, ip->sz_delta.y, ip->valid ? "valid" : "invalid")); #endif return ip->valid; } /* Compare points in lexographical order */ #define compare_points(a, b) \ (((a).y < (b).y) || ((a).y == (b).y && (a).x < (b).x)) /* Choose best placement --- placement policy is defined here * Placement A is considered to be better than placement B * if either * - min_space_policy is on and (window) size delta for A is * strictly less then size delta for B; * - position for A is less (in lexographical order) * than position for B. * */ void icon_placement_choose_best( struct IconPlacement *old, struct IconPlacement *new) { int old_lout_delta_norm, new_lout_delta_norm; int lout_norm_cmp; struct Point new_wnd_sz_delta, old_wnd_sz_delta; int old_wnd_delta_norm = 0, new_wnd_delta_norm = 0; int wnd_norm_cmp = 0; /* I whish there were nested functions in C standard */ #define calc_wnd_sz_delta(delta, pmt) \ do { \ delta.x = cutoff((grid_sz.x + pmt->sz_delta.x) * settings.slot_size.x \ - tray_data.xsh.width, \ 0, pmt->sz_delta.x * settings.slot_size.x); \ delta.y = cutoff((grid_sz.y + pmt->sz_delta.y) * settings.slot_size.y \ - tray_data.xsh.height, \ 0, pmt->sz_delta.y * settings.slot_size.y); \ } while (0) /* If tray is vertically oriented, swap width <-> height * (just for readability sake) */ if (settings.vertical) { swap(tray_data.xsh.width, tray_data.xsh.height); swap(settings.orig_tray_dims.x, settings.orig_tray_dims.y); } calc_wnd_sz_delta(old_wnd_sz_delta, old); calc_wnd_sz_delta(new_wnd_sz_delta, new); /* Some black magic. This is probably buggy and you are not supposed to * understand this (I definetly don't). The basic idea is that sometimes * layout resize means window resize. Sometimes it does not. */ if (settings.shrink_back_mode) { if (grid_sz.x >= settings.orig_tray_dims.x / settings.slot_size.x) { old_wnd_sz_delta.x = old->sz_delta.x * settings.slot_size.x; new_wnd_sz_delta.x = new->sz_delta.x *settings.slot_size.x; } if (grid_sz.y >= settings.orig_tray_dims.y / settings.slot_size.y) { old_wnd_sz_delta.y = old->sz_delta.y * settings.slot_size.y; new_wnd_sz_delta.y = new->sz_delta.y *settings.slot_size.y; } } /* Restore values */ if (settings.vertical) { swap(tray_data.xsh.width, tray_data.xsh.height); swap(settings.orig_tray_dims.x, settings.orig_tray_dims.y); } LOG_TRACE(("old placement = (%d, %d, %d, %d, %d, %d)\n", old->pos.x, old->pos.y, old->sz_delta.x, old->sz_delta.y, old_wnd_sz_delta.x, old_wnd_sz_delta.y)); LOG_TRACE(("new placement = (%d, %d, %d, %d, %d, %d)\n", new->pos.x, new->pos.y, new->sz_delta.x, new->sz_delta.y, new_wnd_sz_delta.x, new_wnd_sz_delta.y)); /* Compute norms for window deltas */ old_wnd_delta_norm = old_wnd_sz_delta.x + old_wnd_sz_delta.y; new_wnd_delta_norm = new_wnd_sz_delta.x + new_wnd_sz_delta.y; wnd_norm_cmp = new_wnd_delta_norm < old_wnd_delta_norm; /* Compute norms for layout deltas */ old_lout_delta_norm = old->sz_delta.x + old->sz_delta.y; new_lout_delta_norm = new->sz_delta.x + new->sz_delta.y; lout_norm_cmp = new_lout_delta_norm < old_lout_delta_norm; LOG_TRACE(("old wnd delta norm = %d, lout delta norm = %d\n", old_wnd_delta_norm, old_lout_delta_norm)); LOG_TRACE(("new wnd delta norm = %d, lout delta norm = %d\n", new_wnd_delta_norm, new_lout_delta_norm)); LOG_TRACE(("lout norm cmp = %d, wnd norm cmp = %d\n", lout_norm_cmp, wnd_norm_cmp)); LOG_TRACE(("compare points (new, old) = %d\n", compare_points(new->pos, old->pos))); /* CORE of placement logic. In short, * - valid placement is always better than invalid * - with minimal space policy on (off by default), placement with smaller * deltas is better * - placement which does not cause tray window to grow is always better * than one that does * - otherwise, placement that is "closer" to initial point (that depends * on gravity) is better */ if (!old->valid && new->valid) goto replace; if (settings.min_space_policy && (lout_norm_cmp || wnd_norm_cmp)) goto replace; if (old_lout_delta_norm && !new_lout_delta_norm) goto replace; if (old_wnd_delta_norm && !new_wnd_delta_norm) goto replace; if ((!old_lout_delta_norm == !new_lout_delta_norm) && compare_points(new->pos, old->pos)) goto replace; LOG_TRACE(("old placement is better\n")); return; replace: LOG_TRACE(("new placement is better\n")); *old = *new; } /* WARNING: returns ptr to STATIC buffer */ struct IconPlacement *grid_find_placement(struct TrayIcon *ti) { static struct IconPlacement cur_pmt; struct IconPlacement tmp_pmt; int x = 0, y = 0, skip, orig_layed_out; orig_layed_out = ti->is_layed_out; ti->is_layed_out = 0; /* to avoid self-intersections */ /* General idea is to look through all possible placements * while keeping one which is considered to be the "best" and * comparing current placement with "best" one and updating * it accordingly */ if (grid_sz.x == 0 || grid_sz.y == 0) { /* Grid is empty */ /* This is the only possible placement */ icon_placement_create(&cur_pmt, 0, 0, &ti->l.grd_rect); } else { /* Seek and place */ /* Create two obvious placements */ /* Rightmost-top position */ icon_placement_create(&cur_pmt, grid_sz.x, 0, &ti->l.grd_rect); /* Bottommost-left position */ icon_placement_create(&tmp_pmt, 0, grid_sz.y, &ti->l.grd_rect); /* Choose the best one */ icon_placement_choose_best(&cur_pmt, &tmp_pmt); /* Start looking through possible placements. * Current placement is determined by x and y variables. */ do { /* Check if the current rect is free */ skip = grid_check_rect_free(x, y, ti->l.grd_rect.w, ti->l.grd_rect.h); LOG_TRACE(("x=%d, y=%d, skip=%d\n", x, y, skip)); /* If so, create new placement */ if (skip == 0) { if (icon_placement_create(&tmp_pmt, x, y, &ti->l.grd_rect)) { icon_placement_choose_best(&cur_pmt, &tmp_pmt); /* If not settings.full_pmt_search, take the first valid * non-resizing placement and run */ if (!settings.full_pmt_search && !cur_pmt.sz_delta.x && !cur_pmt.sz_delta.y) break; } skip = ti->l.grd_rect.w; } /* Advance to the next possible placement */ x += skip; if (x >= grid_sz.x) { x = 0; y++; } } while (x < grid_sz.x && y < grid_sz.y); } /* Restore layed out state */ ti->is_layed_out = orig_layed_out; if (cur_pmt.valid) { LOG_TRACE(("using placement (%d, %d, %d, %d)\n", cur_pmt.pos.x, cur_pmt.pos.y, cur_pmt.sz_delta.x, cur_pmt.sz_delta.y)); return &cur_pmt; } else { return NULL; } } /* E. Mass-ops helpers */ int trayicon_cmp_func(struct TrayIcon *ti1, struct TrayIcon *ti2) { return (!ti2->is_layed_out && ti1->is_layed_out) || compare_points(ti2->l.grd_rect, ti1->l.grd_rect); } int grid_add_wrapper(struct TrayIcon *ti) { /* Ignore hidden icons */ return !ti->is_visible || grid_add(ti) ? NO_MATCH : MATCH; } int grid_recalc_size(struct TrayIcon *ti) { int x, y; /* Ignore icons that are not layed out */ if (!ti->is_layed_out) return NO_MATCH; /* Calculate coordinates for bottom-right corner of ti */ x = ti->l.grd_rect.x + ti->l.grd_rect.w; y = ti->l.grd_rect.y + ti->l.grd_rect.h; /* Update grid dimensions */ grid_sz.x = x > grid_sz.x ? x : grid_sz.x; grid_sz.y = y > grid_sz.y ? y : grid_sz.y; LOG_TRACE(("new grid size: %dx%d\n", grid_sz.x, grid_sz.y)); return NO_MATCH; } int layout_unset_flag(struct TrayIcon *ti) { ti->is_layed_out = 0; return NO_MATCH; } ================================================ FILE: src/layout.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * layout.h * Tue, 24 Aug 2004 12:19:27 +0700 * ------------------------------- * Icon layout implementation * -------------------------------*/ #ifndef _LAYOUT_H_ #define _LAYOUT_H_ #include "icons.h" /* Gravity constants */ /* East gravity */ #define GRAV_E (1L << 0) /* West gravity */ #define GRAV_W (1L << 1) /* South gravity */ #define GRAV_S (1L << 2) /* North gravity */ #define GRAV_N (1L << 3) /* Shortcut for horisontal gravity */ #define GRAV_H (GRAV_W | GRAV_E) /* Shortcut for vertical gravity */ #define GRAV_V (GRAV_S | GRAV_N) /* Macros to test rect intersection */ /* Helpers */ #define RX1(r) (r.x) #define RY1(r) (r.y) #define RX2(r) (r.x + r.w - 1) #define RY2(r) (r.y + r.h - 1) #define RECTS_ISECT_(r1, r2) \ (((RX1(r1) <= RX1(r2) && RX1(r2) <= RX2(r1)) \ || (RX1(r1) <= RX2(r2) && RX2(r2) <= RX2(r1))) \ && ((RY1(r1) <= RY1(r2) && RY1(r2) <= RY2(r1)) \ || (RY1(r1) <= RY2(r2) && RY2(r2) <= RY2(r1)))) /* Macro itself */ #define RECTS_ISECT(r1, r2) (RECTS_ISECT_(r1, r2) || RECTS_ISECT_(r2, r1)) /* Add icon to layout */ int layout_add(struct TrayIcon *ti); /* Remove icon from layout */ int layout_remove(struct TrayIcon *ti); /* Relayout the icon which has changed its size */ int layout_handle_icon_resize(struct TrayIcon *ti); /* Get current layout dimensions */ void layout_get_size(int *width, int *height); /* Translate grid coordinates into window coordinates */ int layout_translate_to_window(struct TrayIcon *ti); /* Return next icon in tab chain */ struct TrayIcon *layout_next(struct TrayIcon *current); /* Return previous icon in tab chain */ struct TrayIcon *layout_prev(struct TrayIcon *current); #endif ================================================ FILE: src/list.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * list.h * Fri, 10 Sep 2004 23:34:48 +0700 * ------------------------------- * Simple double linked list * -------------------------------*/ #ifndef _LIST_H_ #define _LIST_H_ /* Implement basic functions for doubly-linked list. * Each structure to hold an element of the list must have two * fields pointer fields: prev and next. */ /* Add item i_ to the head of the list h_ (pointer to the head) */ #define LIST_ADD_ITEM(h_, i_) \ do { \ (i_)->prev = NULL; \ if ((h_) != NULL) { \ (i_)->next = (h_); \ (h_)->prev = (i_); \ } else { \ (i_)->next = NULL; \ } \ (h_) = (i_); \ } while (0) /* Insert item i_ after item t_ to the list h_ (pointer to the head) */ #define LIST_INSERT_AFTER(h_, t_, i_) \ do { \ (i_)->prev = (t_); \ if ((t_) != NULL) { \ (i_)->next = (t_)->next; \ (t_)->next = (i_); \ } else { \ if ((h_) != NULL) { \ (i_)->next = (h_); \ (h_)->prev = (i_); \ } else { \ (i_)->next = NULL; \ } \ (h_) = (i_); \ } \ } while (0) /* Delete item i_ from the list h_ (pointer to the head) */ #define LIST_DEL_ITEM(h_, i_) \ do { \ if (i_->prev != NULL) i_->prev->next = i_->next; \ if (i_->next != NULL) i_->next->prev = i_->prev; \ if (i_ == h_) h_ = i_->next; \ } while (0) /* Clean the list h_ (pointer to the head); * tmp_ is a temporary variable */ #define LIST_CLEAN(h_, tmp_) \ do { \ for (tmp_ = h_, h_ = (h_ != NULL) ? h_->next : NULL; tmp_ != NULL; \ tmp_ = h_, h_ = h_ != NULL ? h_->next : NULL) { \ free(tmp_); \ } \ h_ = NULL; \ } while (0) /* Clean the list h_ (pointer to the head) calling cbk_ for every * element of the list; tmp_ is a temporary variable */ #define LIST_CLEAN_CBK(h_, tmp_, cbk_) \ do { \ for (tmp_ = h_, h_ = (h_ != NULL) ? h_->next : NULL; tmp_ != NULL; \ tmp_ = h_, h_ = h_ != NULL ? h_->next : NULL) { \ cbk_(tmp_); \ free(tmp_); \ } \ h_ = NULL; \ } while (0) #endif ================================================ FILE: src/main.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * main.c * Tue, 24 Aug 2004 12:19:48 +0700 * ------------------------------- * main is main * -------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "debug.h" #include "embed.h" #include "icons.h" #include "layout.h" #include "wmh.h" #include "xembed.h" #include "xutils.h" #ifdef _ST_WITH_NATIVE_KDE #include "kde_tray.h" #endif #include "scrollbars.h" #include "settings.h" #include "tray.h" #include "xinerama.h" struct TrayData tray_data; static int tray_status_requested = 0; #ifdef _ST_EXIT_GRACEFULLY static Display *async_dpy; #endif void my_usleep(useconds_t usec) { struct timeval timeout; fd_set rfds; FD_ZERO(&rfds); timeout.tv_sec = 0; timeout.tv_usec = usec; select(1, &rfds, NULL, NULL, &timeout); } /**************************** * Signal handlers, cleanup ***************************/ void request_tray_status_on_signal(int) { tray_status_requested = 1; } #ifdef _ST_EXIT_GRACEFULLY void exit_on_signal(int sig) { if (sig == SIGPIPE) { debug_disable_output(); } else { psignal(sig, ""); /* This is UGLY and is, probably, to be submitted to * Daily WTF, but it is the only way I found not to * use usleep in main event loop. */ LOG_TRACE(("Sending fake WM_DELETE_WINDOW message\n")); } x11_send_client_msg32(async_dpy, tray_data.tray, tray_data.tray, tray_data.xa_wm_protocols, tray_data.xa_wm_delete_window, 0, 0, 0, 0); XSync(async_dpy, False); } #endif void cleanup() { static int clean = 0; static int cleanup_in_progress = 0; if (!clean && cleanup_in_progress) { LOG_ERROR(("forced to die\n")); abort(); } if (clean) return; cleanup_in_progress = 1; if (tray_data.dpy != NULL && x11_connection_status()) { LOG_TRACE(("being nice to the icons\n")); /* Clean the list unembedding icons one by one */ icon_list_clean_callback(&embedder_unembed); /* Give away the selection */ if (tray_data.is_active) XSetSelectionOwner( tray_data.dpy, tray_data.xa_tray_selection, None, CurrentTime); /* Sync in order to wait until all icons finish their reparenting * process */ XSync(tray_data.dpy, False); XCloseDisplay(tray_data.dpy); tray_data.dpy = NULL; } cleanup_in_progress = 0; clean = 1; } /************************************** * Helper functions **************************************/ /* Print tray status */ void dump_tray_status() { int grid_w, grid_h; tray_status_requested = 0; layout_get_size(&grid_w, &grid_h); LOG_INFO(("----------- tray status -----------\n")); LOG_INFO(("active: %s\n", tray_data.is_active ? "yes" : "no")); LOG_INFO(("geometry: %dx%d+%d+%d\n", tray_data.xsh.width, tray_data.xsh.height, tray_data.xsh.x, tray_data.xsh.y)); if (tray_data.xembed_data.current) LOG_INFO(("XEMBED focus: 0x%lx\n", tray_data.xembed_data.current->wid)); else LOG_INFO(("XEMBED focus: none\n")); LOG_INFO(("currently managed icons:\n")); icon_list_forall(&print_icon_data); LOG_INFO(("-----------------------------------\n")); } /* Checks whether a given window class should be ignored */ int is_ignored_class(const char *name) { struct WindowClass *haystack = NULL; for (haystack = settings.ignored_classes; haystack; haystack = haystack->next) { if (!strcmp(name, haystack->name)) return 1; } return 0; } /************************************** * (Un)embedding cycle implementation **************************************/ /* Add icon to the tray */ void add_icon(Window w, int cmode) { struct TrayIcon *ti; const char *classname = NULL; /* Aviod adding duplicates */ if ((ti = icon_list_find(w)) != NULL) { LOG_TRACE(("ignoring second request to embed 0x%lx" " (requested cmode=%d, current cmode=%d)\n", w, cmode, ti->cmode)); return; } /* Dear Edsger W. Dijkstra, I see you behind my back =( */ if ((ti = icon_list_new(w, cmode)) == NULL) goto icon_allocation_failed; LOG_TRACE(("starting embedding for icon 0x%lx, cmode=%d\n", w, cmode)); x11_dump_win_info(tray_data.dpy, w); classname = x11_get_window_class(tray_data.dpy, w); if (classname == NULL) { LOG_TRACE(("Ignoring icon, x11_get_window_class() failed: 0x%lx" " (requested cmode=%d)\n", w, cmode)); goto embedding_failed; } if (is_ignored_class(classname)) { LOG_INFO(("Ignoring icon because its class is ignored: %s\n", classname)); goto done; } /* Start embedding cycle */ if (!xembed_check_support(ti)) goto embedding_failed; if (ti->is_xembed_supported) ti->is_visible = xembed_get_mapped_state(ti); else ti->is_visible = True; if (ti->is_visible) { if (!embedder_reset_size(ti)) goto embedding_failed; if (!layout_add(ti)) goto embedding_failed; } if (!xembed_embed(ti)) goto embedding_failed_after_layout; if (!embedder_embed(ti)) goto embedding_failed_after_layout; embedder_update_positions(False); tray_update_window_props(); /* Report success */ LOG_INFO(("added icon %s (wid 0x%lx) as %s\n", x11_get_window_name(tray_data.dpy, ti->wid, ""), ti->wid, ti->is_visible ? "visible" : "hidden")); goto done; embedding_failed_after_layout: layout_remove(ti); embedding_failed: icon_list_free(ti); icon_allocation_failed: LOG_INFO(("failed to add icon %s (wid 0x%lx)\n", x11_get_window_name(tray_data.dpy, ti->wid, ""), ti->wid)); done: if (classname != NULL) free((void *) classname); if (settings.log_level >= LOG_LEVEL_TRACE) dump_tray_status(); return; } /* Remove icon from the tray */ void remove_icon(Window w) { struct TrayIcon *ti; /* Ignore false alarms */ if ((ti = icon_list_find(w)) == NULL) return; dump_tray_status(); embedder_unembed(ti); xembed_unembed(ti); layout_remove(ti); icon_list_free(ti); LOG_INFO(("removed icon %s (wid 0x%lx)\n", x11_get_window_name(tray_data.dpy, ti->wid, ""), w)); /* no need to call embedde_update_positions(), as * scrollbars_click(SB_WND_MAX) will call it */ /* XXX: maybe we need a different name for this * routine instad of passing cryptinc constant? */ scrollbars_click(SB_WND_MAX); tray_update_window_props(); dump_tray_status(); } /* Track icon visibility state changes */ void icon_track_visibility_changes(Window w) { struct TrayIcon *ti; int mapped; /* Ignore false alarms */ if ((ti = icon_list_find(w)) == NULL || !ti->is_xembed_supported) return; mapped = xembed_get_mapped_state(ti); LOG_TRACE(("xembed_is_mapped(0x%lx) = %u\n", w, mapped)); LOG_TRACE(("is_visible = %u\n", ti->is_visible)); #ifdef DEBUG x11_dump_win_info(tray_data.dpy, ti->wid); #endif /* Nothing has changed */ if (mapped == ti->is_visible) return; ti->is_visible = mapped; LOG_INFO(("%s icon 0x%lx\n", mapped ? "showing" : "hiding", w)); if (mapped) { /* Icon has become mapped and is listed as hidden. Show this icon. */ embedder_reset_size(ti); if (!layout_add(ti)) { xembed_set_mapped_state(ti, False); return; } embedder_show(ti); } else { /* Icon has become unmapped and is listed as visible. Hide this icon. */ layout_remove(ti); embedder_hide(ti); } embedder_update_positions(False); tray_update_window_props(); } /* helper to identify invalid icons */ int find_invalid_icons(struct TrayIcon *ti) { return ti->is_invalid; } #ifdef _ST_WITH_NATIVE_KDE /* Find newly available KDE icons and add them as necessary */ /* TODO: move to kde_tray.c */ void kde_icons_update() { unsigned long list_len, i; Window *kde_tray_icons; if (tray_data.kde_tray_old_mode || !x11_get_root_winlist_prop(tray_data.dpy, tray_data.xa_kde_net_system_tray_windows, (unsigned char **)&kde_tray_icons, &list_len)) { return; } for (i = 0; i < list_len; i++) /* If the icon is not None and is non old, try to add it * (if the icon is already there, nothing is gonna happen). */ if (kde_tray_icons[i] != None && !kde_tray_is_old_icon(kde_tray_icons[i])) { LOG_TRACE(("found (possibly unembedded) KDE icon %s (wid 0x%lx)\n", x11_get_window_name( tray_data.dpy, kde_tray_icons[i], ""), kde_tray_icons[i])); add_icon(kde_tray_icons[i], CM_KDE); } XFree(kde_tray_icons); } #endif void find_unmanaged_chromium_icons() { unsigned int n; Window *topwins, dummy; XQueryTree(tray_data.dpy, DefaultRootWindow(tray_data.dpy), &dummy, &dummy, &topwins, &n); if (topwins == NULL) return; // Find toplevel windows (unmanaged) that have: //_NET_WM_WINDOW_TYPE(ATOM) == _NET_WM_WINDOW_TYPE_NOTIFICATION // CHROMIUM_COMPOSITE_WINDOW(CARDINAL) == 1 Atom win_type = XInternAtom(tray_data.dpy, "_NET_WM_WINDOW_TYPE", False); Atom win_type_notif = XInternAtom(tray_data.dpy, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False); Atom chrom_composite = XInternAtom(tray_data.dpy, "CHROMIUM_COMPOSITE_WINDOW", False); for (unsigned int i = 0; i < n; i++) { Atom *aitem; unsigned int *citem; unsigned long nitems; int rc = False; Bool ok = True; LOG_TRACE(("Chromium hack - checking window 0x%lx\n", topwins[i])); rc = x11_get_window_prop32(tray_data.dpy, topwins[i], win_type, XA_ATOM, (unsigned char **)&aitem, &nitems); LOG_TRACE(("Chromium hack, ret: %x %x=%x %lx\n", x11_ok(), rc, Success, nitems)); if (!(x11_ok() && rc == SUCCESS && nitems == 1)) continue; LOG_TRACE(("Chromium hack, comp: %lx=%lx\n", aitem[0], win_type_notif)); ok = aitem[0] == win_type_notif; XFree(aitem); if (!ok) continue; LOG_TRACE(("Found toplevel notification window 0x%lx\n", topwins[i])); rc = x11_get_window_prop32(tray_data.dpy, topwins[i], chrom_composite, XA_CARDINAL, (unsigned char **)&citem, &nitems); LOG_TRACE(("Chromium hack, ret: %x %x=%x %lx\n", x11_ok(), rc, Success, nitems)); if (!(x11_ok() && rc == SUCCESS && nitems == 1)) continue; LOG_TRACE(("Chromium hack, comp: %lx=%lx\n", aitem[0], win_type_notif)); ok = citem[0] == 1; XFree(citem); if (!ok) continue; LOG_TRACE(("Found chromium composite window 0x%ld\n", topwins[i])); add_icon(topwins[i], CM_FDO); } XFree(topwins); } #define PT_MASK_SB (1L << 0) #define PT_MASK_ALL PT_MASK_SB /* Perform several periodic tasks */ void perform_periodic_tasks(int mask) { struct TrayIcon *ti; /* 1. Remove all invalid icons */ while ((ti = icon_list_forall(&find_invalid_icons)) != NULL) { LOG_TRACE(("icon 0x%lx is invalid; removing\n", ti->wid)); remove_icon(ti->wid); } /* 2. Print tray status if asked to */ if (tray_status_requested) dump_tray_status(); /* 3. KLUDGE to fix window size on (buggy?) WMs */ if (settings.kludge_flags & KLUDGE_FIX_WND_SIZE) { /* KLUDGE TODO: resolve */ XWindowAttributes xwa; XGetWindowAttributes(tray_data.dpy, tray_data.tray, &xwa); if (!tray_data.is_reparented && (xwa.width != tray_data.xsh.width || xwa.height != tray_data.xsh.height)) { LOG_TRACE(("KLUDGE: fixing tray window size (current: %dx%d, " "required: %dx%d)\n", xwa.width, xwa.height, tray_data.xsh.width, tray_data.xsh.height)); tray_update_window_props(); } } /* 4. run scrollbars periodic tasks */ if (mask & PT_MASK_SB) scrollbars_periodic_tasks(); } /********************** * Event handlers **********************/ void expose(XExposeEvent ev) { if (ev.window == tray_data.tray && settings.parent_bg && ev.count == 0) tray_refresh_window(False); } void visibility_notify(XVisibilityEvent) {} void property_notify(XPropertyEvent ev) { #define TRACE_PROPS #if defined(DEBUG) && defined(TRACE_PROPS) char *atom_name; atom_name = XGetAtomName(tray_data.dpy, ev.atom); LOG_TRACE(("atom = %s\n", atom_name)); XFree(atom_name); #endif /* React on wallpaper change */ if (ev.atom == tray_data.xa_xrootpmap_id || ev.atom == tray_data.xa_xsetroot_id) { if (settings.transparent) tray_update_bg(True); if (settings.parent_bg || settings.transparent || settings.fuzzy_edges) tray_refresh_window(True); } #ifdef _ST_WITH_NATIVE_KDE /* React on change of list of KDE icons */ if (ev.atom == tray_data.xa_kde_net_system_tray_windows) { if (tray_data.is_active) kde_icons_update(); else LOG_TRACE(("not updating KDE icons list: tray is not active\n")); kde_tray_update_old_icons(tray_data.dpy); } #endif /* React on WM (re)starts */ if (ev.atom == XInternAtom(tray_data.dpy, _NET_SUPPORTING_WM_CHECK, False)) { #ifdef DEBUG ewmh_list_supported_atoms(tray_data.dpy); #endif tray_set_wm_hints(); #ifdef _ST_WITH_NATIVE_KDE kde_tray_update_fallback_mode(tray_data.dpy); #endif } /* React on _XEMBED_INFO changes of embedded icons * (currently used to track icon visibility status) */ if (ev.atom == tray_data.xembed_data.xa_xembed_info) { icon_track_visibility_changes(ev.window); } if (ev.atom == tray_data.xa_net_client_list) { Window *windows; unsigned long nwindows, rc, i; rc = x11_get_root_winlist_prop(tray_data.dpy, tray_data.xa_net_client_list, (unsigned char **)&windows, &nwindows); if (x11_ok() && rc) { tray_data.is_reparented = True; for (i = 0; i < nwindows; i++) if (windows[i] == tray_data.tray) { tray_data.is_reparented = False; break; } } if (nwindows) XFree(windows); LOG_TRACE(( "tray was %sreparented\n", tray_data.is_reparented ? "" : "not ")); } } void reparent_notify(XReparentEvent ev) { struct TrayIcon *ti; ti = icon_list_find(ev.window); if (ti == NULL) return; /* Reparenting out of the tray is one of non-destructive * ways to end XEMBED protocol (see spec) */ if (ti->is_embedded && ti->mid_parent != ev.parent) { LOG_TRACE(("will now unembed 0x%lx\n", ti->wid)); #ifdef DEBUG print_icon_data(ti); x11_dump_win_info(tray_data.dpy, ev.parent); #endif remove_icon(ev.window); } } void client_message(XClientMessageEvent ev) { int cmode = CM_FDO; struct TrayIcon *ti; #ifdef DEBUG /* Print neat message(s) about this event to aid debugging */ char *msg_type_name; msg_type_name = XGetAtomName(tray_data.dpy, ev.message_type); if (msg_type_name != NULL) { LOG_TRACE(("message \"%s\"\n", msg_type_name)); XFree(msg_type_name); } if (ev.message_type == tray_data.xa_wm_protocols) { msg_type_name = XGetAtomName(tray_data.dpy, ev.data.l[0]); if (msg_type_name != NULL) { LOG_TRACE(("WM_PROTOCOLS message type: %s\n", msg_type_name)); XFree(msg_type_name); } } #endif /* Graceful exit */ if (ev.message_type == tray_data.xa_wm_protocols && (unsigned long) ev.data.l[0] == tray_data.xa_wm_delete_window && ev.window == tray_data.tray) { LOG_TRACE(("got WM_DELETE message, will now exit\n")); exit(0); // atexit will call cleanup() } /* Handle _NET_WM_PING */ if (ev.message_type == tray_data.xa_wm_protocols && (unsigned long) ev.data.l[0] == tray_data.xa_net_wm_ping && ev.window == tray_data.tray) { LOG_TRACE(("got WM_PING message, sending it back\n")); XEvent reply; reply.xclient = ev; reply.xclient.window = DefaultRootWindow(tray_data.dpy); XSendEvent(tray_data.dpy, DefaultRootWindow(tray_data.dpy), False, (SubstructureNotifyMask | SubstructureRedirectMask), &reply); } /* Handle _NET_SYSTEM_TRAY_* messages */ if (ev.message_type == tray_data.xa_tray_opcode && tray_data.is_active) { LOG_TRACE(("this is the _NET_SYSTEM_TRAY_OPCODE(%lu) message\n", ev.data.l[1])); switch (ev.data.l[1]) { /* This is the starting point of NET SYSTEM TRAY protocol */ case SYSTEM_TRAY_REQUEST_DOCK: LOG_TRACE( ("dockin' requested by window 0x%lx, serving in a moment\n", ev.data.l[2])); #ifdef _ST_WITH_NATIVE_KDE if (kde_tray_check_for_icon(tray_data.dpy, ev.data.l[2])) cmode = CM_KDE; if (kde_tray_is_old_icon(ev.data.l[2])) kde_tray_old_icons_remove(ev.data.l[2]); #endif add_icon(ev.data.l[2], cmode); break; /* We ignore these messages, since we do not show * any baloons anyways */ case SYSTEM_TRAY_BEGIN_MESSAGE: case SYSTEM_TRAY_CANCEL_MESSAGE: break; /* Below are special cases added by this implementation */ /* STALONETRAY_TRAY_DOCK_CONFIRMED is sent by stalonetray * to itself. (see embed.c) */ case STALONE_TRAY_DOCK_CONFIRMED: ti = icon_list_find(ev.data.l[2]); if (ti != NULL && !ti->is_embedded) { ti->is_embedded = True; LOG_TRACE(("embedding confirmed for icon 0x%lx\n", ti->wid)); #ifdef DEBUG dump_tray_status(); #endif } tray_update_window_props(); break; /* Dump tray status on request */ case STALONE_TRAY_STATUS_REQUESTED: dump_tray_status(); break; /* Find icon and scroll to it if necessary */ case STALONE_TRAY_REMOTE_CONTROL: ti = icon_list_find(ev.window); if (ti == NULL) break; scrollbars_scroll_to(ti); #if 0 /* Quick hack */ { Window icon = ev.window; int rc; int x = ev.data.l[3], y = ev.data.l[4], depth = 0, idummy, i; int btn = ev.data.l[2]; Window win, root; unsigned int udummy, w, h; XGetGeometry(tray_data.dpy, icon, &root, &idummy, &idummy, &w, &h, &udummy, &udummy); LOG_TRACE(("wid=0x%x w=%d h=%d\n", icon, w, h)); x = (x == REMOTE_CLICK_POS_DEFAULT) ? w / 2 : x; y = (y == REMOTE_CLICK_POS_DEFAULT) ? h / 2 : y; /* 3.2. Find subwindow to execute click on */ win = x11_find_subwindow_at(tray_data.dpy, icon, &x, &y, depth); /* 3.3. Send mouse click(s) to target */ LOG_TRACE(("wid=0x%x btn=%d x=%d y=%d\n", win, btn, x, y)); #define SEND_BTN_EVENT(press, time) \ do { \ x11_send_button(tray_data.dpy, /* dispslay */ \ press, /* event type */ \ win, /* target window */ \ root, /* root window */ \ time, /* time */ \ btn, /* button */ \ Button1Mask << (btn - 1), /* state mask */ \ x, /* x coord (relative) */ \ y); /* y coord (relative) */ \ } while (0) for (i = 0; i < ev.data.l[0]; i++) { SEND_BTN_EVENT(1, x11_get_server_timestamp(tray_data.dpy, tray_data.tray)); my_usleep(250); SEND_BTN_EVENT(0, x11_get_server_timestamp(tray_data.dpy, tray_data.tray)); } #undef SEND_BTN_EVENT } #endif break; default: break; } } #ifdef DEBUG if (ev.message_type == tray_data.xa_tray_opcode && !tray_data.is_active) LOG_TRACE(("ignoring _NET_SYSTEM_TRAY_OPCODE(%d) message because " "tray is not active\n", tray_data.is_active)); #endif } void destroy_notify(XDestroyWindowEvent ev) { if (!tray_data.is_active && ev.window == tray_data.old_selection_owner) { /* Old tray selection owner was destroyed. Take over selection * ownership. */ tray_acquire_selection(); } else if (ev.window != tray_data.tray) { /* Try to remove icon from the tray */ remove_icon(ev.window); #ifdef _ST_WITH_NATIVE_KDE } else if (kde_tray_is_old_icon(ev.window)) { /* Since X Server may reuse window ids, remove ev.window * from the list of old KDE icons */ kde_tray_old_icons_remove(ev.window); #endif } } void configure_notify(XConfigureEvent ev) { struct TrayIcon *ti; struct Point sz; XWindowAttributes xwa; if (ev.window == tray_data.tray) { /* Tray window was resized */ /* TODO: distinguish between synthetic and real configure notifies */ /* TODO: catch rejected configure requests */ /* XXX: Geometry stuff is a mess. Geometry * is specified in slots, but almost always is * stored in pixels... */ LOG_TRACE(("tray window geometry from event: %ux%u+%d+%d\n", ev.width, ev.height, ev.x, ev.y)); /* Sometimes, configure notifies come too late, so we fetch real * geometry ourselves */ XGetWindowAttributes(tray_data.dpy, tray_data.tray, &xwa); x11_get_window_abs_coords( tray_data.dpy, tray_data.tray, &tray_data.xsh.x, &tray_data.xsh.y); LOG_TRACE(("tray window geometry from X11 calls: %dx%d+%d+%d\n", xwa.width, xwa.height, tray_data.xsh.x, tray_data.xsh.y)); tray_data.xsh.width = xwa.width; tray_data.xsh.height = xwa.height; /* Update icons positions */ /* XXX: internal API is bad. example below */ icon_list_forall(&layout_translate_to_window); embedder_update_positions(True); /* Adjust window background if necessary */ tray_update_bg(False); tray_refresh_window(True); tray_update_window_strut(); scrollbars_update(); } else if ((ti = icon_list_find(ev.window)) != NULL) { /* Some icon has resized its window */ /* KDE icons are not allowed to change their size. Reset icon size. */ if (ti->cmode == CM_KDE || settings.kludge_flags & KLUDGE_FORCE_ICONS_SIZE) { embedder_reset_size(ti); return; } if (settings.kludge_flags & KLUDGE_FORCE_ICONS_SIZE) return; /* Get new window size */ if (!x11_get_window_size(tray_data.dpy, ti->wid, &sz.x, &sz.y)) { embedder_unembed(ti); return; } LOG_TRACE(("icon 0x%lx was resized, new size: %ux%u, old size: %ux%u\n", ev.window, sz.x, sz.y, ti->l.wnd_sz.x, ti->l.wnd_sz.y)); /* Check if the size has really changed */ if (sz.x == ti->l.wnd_sz.x && sz.y == ti->l.wnd_sz.y) return; ti->l.wnd_sz = sz; ti->is_resized = True; /* Do the job */ layout_handle_icon_resize(ti); embedder_refresh(ti); #ifdef DEBUG print_icon_data(ti); #endif embedder_update_positions(False); tray_update_window_props(); #ifdef DEBUG dump_tray_status(); #endif } } void selection_clear(XSelectionClearEvent ev) { /* Is it _NET_SYSTEM_TRAY selection? */ if (ev.selection == tray_data.xa_tray_selection) { /* Is it us who has lost the selection */ if (ev.window == tray_data.tray) { LOG_INFO(("another tray detected; deactivating\n")); tray_data.is_active = False; tray_data.old_selection_owner = XGetSelectionOwner(tray_data.dpy, tray_data.xa_tray_selection); if (!x11_ok()) { LOG_INFO(("could not find proper new tray; reactivating\n")); tray_acquire_selection(); }; LOG_TRACE(("new selection owner is 0x%lx\n", tray_data.old_selection_owner)); XSelectInput(tray_data.dpy, tray_data.old_selection_owner, StructureNotifyMask); return; } else if (!tray_data.is_active) { /* Someone else has lost selection and tray is not active --- take * over the selection */ LOG_INFO(("another tray exited; reactivating\n")); tray_acquire_selection(); } else { /* Just in case */ LOG_TRACE(("WEIRD: tray is active and someone else has lost tray " "selection\n")); } } } void map_notify(XMapEvent ev) { #ifdef _ST_WITH_NATIVE_KDE /* Legacy scheme to handle KDE icons */ if (tray_data.kde_tray_old_mode) { struct TrayIcon *ti = icon_list_find_ex(ev.window); if (ti == NULL) { Window w = kde_tray_find_icon(tray_data.dpy, ev.window); if (w != None) { LOG_TRACE(("Legacy scheme for KDE icons: detected KDE icon " "0x%lx. Adding.\n", w)); add_icon(w, CM_KDE); /* TODO: remove some properties to trick ion3 so that it no * longer thinks that w is a toplevel. Candidates for removal: * - WM_STATE */ } } } #else (void) ev; /* unused */ #endif } void unmap_notify(XUnmapEvent ev) { struct TrayIcon *ti; ti = icon_list_find(ev.window); if (ti != NULL && !ti->is_invalid) { /* KLUDGE! sometimes icons occasionally * unmap their windows, but do _not_ destroy * them. We map those windows back */ /* XXX: not root caused */ LOG_TRACE(("Unmap icons KLUDGE executed for 0x%lx\n", ti->wid)); XMapRaised(tray_data.dpy, ti->wid); if (!x11_ok()) ti->is_invalid = True; } } /*********************************************************/ /* main() for usual operation */ int tray_main(int argc, char **argv) { XEvent ev; /* Interpret those settings that need an open display */ interpret_settings(); #ifdef DEBUG ewmh_list_supported_atoms(tray_data.dpy); #endif xinerama_init(tray_data.dpy); xinerama_update_geometry(); /* Create and show tray window */ tray_create_window(argc, argv); tray_acquire_selection(); tray_show_window(); #ifdef _ST_WITH_NATIVE_KDE kde_tray_init(tray_data.dpy); #endif xembed_init(); #ifdef _ST_WITH_NATIVE_KDE kde_icons_update(); #endif find_unmanaged_chromium_icons(); /* Main event loop */ while ("my guitar gently wheeps") { /* This is ugly and extra dependency. But who cares? * Rationale: we want to block unless absolutely needed. * This way we ensure that stalonetray does not show up * in powertop (i.e. does not eat unnecessary power and * CPU cycles) * Drawback: handling of signals is very limited. XNextEvent() * does not if signal occurs. This means that graceful * exit on e.g. Ctrl-C cannot be implemented without hacks. */ while (XPending(tray_data.dpy) || tray_data.scrollbars_data.scrollbar_down == -1) { XNextEvent(tray_data.dpy, &ev); xembed_handle_event(ev); scrollbars_handle_event(ev); switch (ev.type) { case VisibilityNotify: LOG_TRACE(("VisibilityNotify (0x%lx, state=%d)\n", ev.xvisibility.window, ev.xvisibility.state)); visibility_notify(ev.xvisibility); break; case Expose: LOG_TRACE(("Expose (0x%lx)\n", ev.xexpose.window)); expose(ev.xexpose); break; case PropertyNotify: LOG_TRACE(("PropertyNotify(0x%lx)\n", ev.xproperty.window)); property_notify(ev.xproperty); break; case DestroyNotify: LOG_TRACE(("DestroyNotify(0x%lx)\n", ev.xdestroywindow.window)); destroy_notify(ev.xdestroywindow); break; case ClientMessage: LOG_TRACE(("ClientMessage(from 0x%lx?)\n", ev.xclient.window)); client_message(ev.xclient); break; case ConfigureNotify: LOG_TRACE(("ConfigureNotify(0x%lx)\n", ev.xconfigure.window)); configure_notify(ev.xconfigure); break; case MapNotify: LOG_TRACE(("MapNotify(0x%lx)\n", ev.xmap.window)); map_notify(ev.xmap); break; case ReparentNotify: LOG_TRACE(("ReparentNotify(0x%lx to 0x%lx)\n", ev.xreparent.window, ev.xreparent.parent)); reparent_notify(ev.xreparent); break; case SelectionClear: LOG_TRACE(("SelectionClear (0x%lx has lost selection)\n", ev.xselectionclear.window)); selection_clear(ev.xselectionclear); break; case SelectionNotify: LOG_TRACE(("SelectionNotify\n")); break; case SelectionRequest: LOG_TRACE(("SelectionRequest (from 0x%lx to 0x%lx)\n", ev.xselectionrequest.requestor, ev.xselectionrequest.owner)); break; case UnmapNotify: LOG_TRACE(("UnmapNotify(0x%lx)\n", ev.xunmap.window)); unmap_notify(ev.xunmap); break; default: #if defined(DEBUG) && defined(_ST_TRACE_EVENTS) LOG_TRACE(("Unhandled event: %s, serial: %ld, window: 0x%lx\n", x11_event_names[ev.type], ev.xany.serial, ev.xany.window)); #endif break; } if (tray_data.terminated) goto bailout; /* Perform all periodic tasks but for scrollbars */ perform_periodic_tasks(PT_MASK_ALL & (~PT_MASK_SB)); } perform_periodic_tasks(PT_MASK_ALL); my_usleep(500000L); } bailout: LOG_TRACE(("Clean exit\n")); return 0; } /* main() for controlling stalonetray remotely */ int remote_main(int, char **) { Window tray, icon = None; int rc; int x, y, depth = 0, idummy, i; Window win, root; unsigned int udummy, w, h; tray_init_selection_atoms(); tray_create_phony_window(); LOG_TRACE( ("name=\"%s\" btn=%d cnt=%d x=%d y=%d\n", settings.remote_click_name, settings.remote_click_btn, settings.remote_click_cnt, settings.remote_click_pos.x, settings.remote_click_pos.y)); tray = XGetSelectionOwner(tray_data.dpy, tray_data.xa_tray_selection); if (tray == None) return 255; /* 1. find window matching requested name */ icon = x11_find_subwindow_by_name( tray_data.dpy, tray, settings.remote_click_name); if (icon == None) return 127; /* 2. form a message to tray requesting it to show the icon */ rc = x11_send_client_msg32(tray_data.dpy, /* display */ tray, /* destination */ icon, /* window */ tray_data.xa_tray_opcode, /* atom */ settings.remote_click_cnt, /* data0 */ STALONE_TRAY_REMOTE_CONTROL, /* data1 */ settings.remote_click_btn, /* data2 */ settings.remote_click_pos.x, /* data3 */ settings.remote_click_pos.y /* data4 */ ); if (!rc) return 63; /* 3. Execute the click */ /* 3.1. Sort out click position */ XGetGeometry(tray_data.dpy, icon, &root, &idummy, &idummy, &w, &h, &udummy, &udummy); LOG_TRACE(("wid=0x%lx w=%d h=%d\n", icon, w, h)); x = (settings.remote_click_pos.x == REMOTE_CLICK_POS_DEFAULT) ? w / 2 : (unsigned int) settings.remote_click_pos.x; y = (settings.remote_click_pos.y == REMOTE_CLICK_POS_DEFAULT) ? h / 2 : (unsigned int) settings.remote_click_pos.y; /* 3.2. Find subwindow to execute click on */ win = x11_find_subwindow_at(tray_data.dpy, icon, &x, &y, depth); /* 3.3. Send mouse click(s) to target */ LOG_TRACE( ("wid=0x%lx btn=%d x=%d y=%d\n", win, settings.remote_click_btn, x, y)); #define SEND_BTN_EVENT(press, time) \ do { \ x11_send_button(tray_data.dpy, /* dispslay */ \ press, /* event type */ \ win, /* target window */ \ root, /* root window */ \ time, /* time */ \ settings.remote_click_btn, /* button */ \ Button1Mask << (settings.remote_click_btn - 1), /* state mask */ \ x, /* x coord (relative) */ \ y); /* y coord (relative) */ \ } while (0) for (i = 0; i < settings.remote_click_cnt; i++) { SEND_BTN_EVENT( 1, x11_get_server_timestamp(tray_data.dpy, tray_data.tray)); my_usleep(250); SEND_BTN_EVENT( 0, x11_get_server_timestamp(tray_data.dpy, tray_data.tray)); } #undef SEND_BTN_EVENT return 0; } /* main() */ int main(int argc, char **argv) { /* Read settings */ tray_init(); read_settings(argc, argv); /* Register cleanup and signal handlers */ atexit(cleanup); signal(SIGUSR1, &request_tray_status_on_signal); #ifdef _ST_EXIT_GRACEFULLY signal(SIGINT, &exit_on_signal); signal(SIGTERM, &exit_on_signal); signal(SIGPIPE, &exit_on_signal); #endif /* Open display */ if ((tray_data.dpy = XOpenDisplay(settings.display_str)) == NULL) DIE(("could not open display\n")); else LOG_TRACE(("Opened dpy %p\n", (void *) tray_data.dpy)); #ifdef _ST_EXIT_GRACEFULLY if ((async_dpy = XOpenDisplay(settings.display_str)) == NULL) DIE(("could not open display\n")); else LOG_TRACE(("Opened async dpy %p\n", async_dpy)); #endif if (settings.xsync) XSynchronize(tray_data.dpy, True); x11_trap_errors(); /* Execute proper main() function */ if (settings.remote_click_name != NULL) return remote_main(argc, argv); else return tray_main(argc, argv); } ================================================ FILE: src/meson.build ================================================ project_sources += [ 'src/debug.c', 'src/embed.c', 'src/icons.c', 'src/image.c', 'src/layout.c', 'src/main.c', 'src/scrollbars.c', 'src/settings.c', 'src/tray.c', 'src/wmh.c', 'src/xembed.c', 'src/xinerama.c', 'src/xutils.c', ] if get_option('native_kde').enabled() project_sources += ['src/kde_tray.c'] endif ================================================ FILE: src/scrollbars.c ================================================ /* ************************************ * vim:tabstop=4:shiftwidth=4:cindent:fen:fdm=syntax * tray.c * Mon, 09 Mar 2009 12:38:30 -0400 * ************************************ * scrollbars functions * ************************************/ #include #include #include #include #include #include "embed.h" #include "tray.h" #include "xutils.h" void scrollbars_init() { tray_data.scrollbars_data.scroll_pos.x = 0; tray_data.scrollbars_data.scroll_pos.y = 0; tray_data.scrollbars_data.scroll_base.x = 0; tray_data.scrollbars_data.scroll_base.y = 0; tray_data.scrollbars_data.scrollbar_down = -1; } void scrollbars_create() { int i; /*#define DEBUG_SCROLLBAR_POSITIONS*/ if (settings.scrollbars_mode & SB_MODE_VERT) { tray_data.scrollbars_data.scrollbar[SB_WND_TOP] = XCreateSimpleWindow(tray_data.dpy, tray_data.tray, 0, 0, 1, 1, 0, #ifdef DEBUG_SCROLLBAR_POSITIONS 0xff00ff, 0xff00ff); #else settings.bg_color.pixel, settings.bg_color.pixel); #endif tray_data.scrollbars_data.scrollbar[SB_WND_BOT] = XCreateSimpleWindow(tray_data.dpy, tray_data.tray, 0, 0, 1, 1, 0, #ifdef DEBUG_SCROLLBAR_POSITIONS 0xffff00, 0xffff00); #else settings.bg_color.pixel, settings.bg_color.pixel); #endif } else { tray_data.scrollbars_data.scrollbar[SB_WND_TOP] = None; tray_data.scrollbars_data.scrollbar[SB_WND_BOT] = None; } if (settings.scrollbars_mode & SB_MODE_HORZ) { tray_data.scrollbars_data.scrollbar[SB_WND_LFT] = XCreateSimpleWindow(tray_data.dpy, tray_data.tray, 0, 0, 1, 1, 0, #ifdef DEBUG_SCROLLBAR_POSITIONS 0x00ff00, 0x00ff00); #else settings.bg_color.pixel, settings.bg_color.pixel); #endif tray_data.scrollbars_data.scrollbar[SB_WND_RHT] = XCreateSimpleWindow(tray_data.dpy, tray_data.tray, 0, 0, 1, 1, 0, #ifdef DEBUG_SCROLLBAR_POSITIONS 0x00ffff, 0x00ffff); #else settings.bg_color.pixel, settings.bg_color.pixel); #endif } else { tray_data.scrollbars_data.scrollbar[SB_WND_LFT] = None; tray_data.scrollbars_data.scrollbar[SB_WND_RHT] = None; } scrollbars_update(); if (settings.scroll_everywhere && settings.scrollbars_mode != SB_MODE_NONE) { XGrabButton(tray_data.dpy, Button5, AnyModifier, tray_data.tray, False, ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None); XGrabButton(tray_data.dpy, Button4, AnyModifier, tray_data.tray, False, ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None); } for (i = 0; i < SB_WND_MAX; i++) if (tray_data.scrollbars_data.scrollbar[i] != None) { #ifndef DEBUG_SCROLLBAR_POSITIONS XSetWindowBackgroundPixmap(tray_data.dpy, tray_data.scrollbars_data.scrollbar[i], ParentRelative); #endif XMapRaised(tray_data.dpy, tray_data.scrollbars_data.scrollbar[i]); XSelectInput(tray_data.dpy, tray_data.scrollbars_data.scrollbar[i], ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | EnterWindowMask | LeaveWindowMask); } } int scrollbars_refresh(int exposures) { int i; for (i = 0; i < SB_WND_MAX; i++) if (tray_data.scrollbars_data.scrollbar[i] != None) x11_refresh_window(tray_data.dpy, tray_data.scrollbars_data.scrollbar[i], tray_data.scrollbars_data.scrollbar_xsh[i].width, tray_data.scrollbars_data.scrollbar_xsh[i].height, exposures); return SUCCESS; } int scrollbars_update() { int offset, i; static int initialized = 0; XSizeHints scrollbar_xsh_local[SB_WND_MAX]; if (settings.scrollbars_mode & SB_MODE_HORZ) { offset = (settings.vertical && (settings.scrollbars_mode & SB_MODE_VERT)) ? settings.scrollbars_size : 0; scrollbar_xsh_local[SB_WND_LFT].x = 0; scrollbar_xsh_local[SB_WND_LFT].y = offset; scrollbar_xsh_local[SB_WND_LFT].width = settings.scrollbars_size; scrollbar_xsh_local[SB_WND_LFT].height = tray_data.xsh.height - offset * 2; scrollbar_xsh_local[SB_WND_RHT] = scrollbar_xsh_local[SB_WND_LFT]; scrollbar_xsh_local[SB_WND_RHT].x = tray_data.xsh.width - settings.scrollbars_size; } if (settings.scrollbars_mode & SB_MODE_VERT) { offset = (settings.vertical && (settings.scrollbars_mode & SB_MODE_HORZ)) ? settings.scrollbars_size : 0; scrollbar_xsh_local[SB_WND_TOP].x = offset; scrollbar_xsh_local[SB_WND_TOP].y = 0; scrollbar_xsh_local[SB_WND_TOP].width = tray_data.xsh.width - offset * 2; scrollbar_xsh_local[SB_WND_TOP].height = settings.scrollbars_size; scrollbar_xsh_local[SB_WND_BOT] = scrollbar_xsh_local[SB_WND_TOP]; scrollbar_xsh_local[SB_WND_BOT].y = tray_data.xsh.height - settings.scrollbars_size; } for (i = 0; i < SB_WND_MAX; i++) if (tray_data.scrollbars_data.scrollbar[i] != None && (!initialized || (scrollbar_xsh_local[i].x != tray_data.scrollbars_data.scrollbar_xsh[i].x || scrollbar_xsh_local[i].y != tray_data.scrollbars_data.scrollbar_xsh[i].y || scrollbar_xsh_local[i].width != tray_data.scrollbars_data.scrollbar_xsh[i].width || scrollbar_xsh_local[i].height != tray_data.scrollbars_data.scrollbar_xsh[i] .height))) { XMoveResizeWindow(tray_data.dpy, tray_data.scrollbars_data.scrollbar[i], scrollbar_xsh_local[i].x, scrollbar_xsh_local[i].y, scrollbar_xsh_local[i].width, scrollbar_xsh_local[i].height); tray_data.scrollbars_data.scrollbar_xsh[i] = scrollbar_xsh_local[i]; } initialized = 1; return x11_ok(); } int scrollbars_get_id(Window wid, int x, int y) { int i; for (i = 0; i < SB_WND_MAX; i++) if (wid == tray_data.scrollbars_data.scrollbar[i] && 0 <= x && 0 <= y && x < tray_data.scrollbars_data.scrollbar_xsh[i].width && y < tray_data.scrollbars_data.scrollbar_xsh[i].height) { return i; } return -1; } void scrollbars_validate_scroll_pos() { int layout_width, layout_height; int base_width, base_height; struct Point max_scroll_pos; layout_get_size(&layout_width, &layout_height); tray_calc_tray_area_size( tray_data.xsh.width, tray_data.xsh.height, &base_width, &base_height); max_scroll_pos.x = layout_width - base_width; max_scroll_pos.y = layout_height - base_height; val_range(max_scroll_pos.x, 0, INT_MAX); val_range(max_scroll_pos.y, 0, INT_MAX); LOG_TRACE(("computed max scroll position: %dx%d\n", max_scroll_pos.x, max_scroll_pos.y)); val_range(tray_data.scrollbars_data.scroll_pos.x, 0, max_scroll_pos.x); val_range(tray_data.scrollbars_data.scroll_pos.y, 0, max_scroll_pos.y); } int scrollbars_click(int id) { /* TODO: implement scroll gravity (i.e. scroll weel must scroll in * direction that agrees with current tray orientation) */ /* TODO: scrollbars_inc must be settable via cmdline */ /* last entry is for action that just sanitizes current scroll * position after icon removal */ static struct Point scrollbars_deltas[SB_WND_MAX + 1] = { {0, -1}, {0, 1}, {-1, 0}, {1, 0}, {0, 0}}; tray_data.scrollbars_data.scroll_pos.x += (settings.icon_gravity & GRAV_W ? 1 : -1) * scrollbars_deltas[id].x * settings.scrollbars_inc; tray_data.scrollbars_data.scroll_pos.y += (settings.icon_gravity & GRAV_N ? 1 : -1) * scrollbars_deltas[id].y * settings.scrollbars_inc; scrollbars_validate_scroll_pos(); LOG_TRACE(("computed new scroll position: %dx%d\n", tray_data.scrollbars_data.scroll_pos.x, tray_data.scrollbars_data.scroll_pos.y)); icon_list_forall(&layout_translate_to_window); embedder_update_positions(id != SB_WND_MAX); return SUCCESS; } void scrollbars_handle_event(XEvent ev) { int id; switch (ev.type) { case EnterNotify: case LeaveNotify: LOG_TRACE(("%s, wid=0x%lx x=%d y=%d\n", ev.type == EnterNotify ? "EnterNotify" : "LeaveNotify", ev.xcrossing.window, ev.xcrossing.x, ev.xcrossing.y)); if (settings.scrollbars_highlight_color_str != NULL && (id = scrollbars_get_id(ev.xcrossing.window, 0, 0)) != -1) { if (ev.type == EnterNotify) scrollbars_highlight_on(id); else scrollbars_highlight_off(id); } break; case ButtonPress: LOG_TRACE(("ButtonPress, state=0x%x\n", ev.xbutton.state)); if (ev.xbutton.button == Button1 && (id = scrollbars_get_id( ev.xbutton.window, ev.xbutton.x, ev.xbutton.y)) != -1) { tray_data.scrollbars_data.scrollbar_down = id; tray_data.scrollbars_data.scrollbar_repeat_active = 1; tray_data.scrollbars_data.scrollbar_repeat_counter = SCROLLBAR_REPEAT_COUNTDOWN_MAX_1ST; tray_data.scrollbars_data.scrollbar_repeats_done = 0; } break; case MotionNotify: LOG_TRACE(("MotionNotify, state=0x%x\n", ev.xbutton.state)); tray_data.scrollbars_data.scrollbar_repeat_active = (tray_data.scrollbars_data.scrollbar_down != -1 && scrollbars_get_id( ev.xmotion.window, ev.xmotion.x, ev.xmotion.y) == tray_data.scrollbars_data.scrollbar_down); break; case ButtonRelease: LOG_TRACE(("ButtonRelease, state=0x%x\n", ev.xbutton.state)); switch (ev.xbutton.button) { case Button1: if (tray_data.scrollbars_data.scrollbar_down != -1) { #if 0 /* If no repeats were done, advance scroll position */ if ((scrollbars_get_id(ev.xbutton.window, ev.xbutton.x, ev.xbutton.y) != -1) && (tray_data.scrollbars_data.scrollbar_repeats_done == 0)) { scrollbars_click(tray_data.scrollbars_data.scrollbar_down); } #endif tray_data.scrollbars_data.scrollbar_down = -1; tray_data.scrollbars_data.scrollbar_repeat_active = 0; } break; case Button4: if (settings.vertical && settings.scrollbars_mode & SB_MODE_VERT) scrollbars_click(SB_WND_TOP); else if (settings.scrollbars_mode & SB_MODE_HORZ) scrollbars_click(SB_WND_LFT); break; case Button5: if (settings.vertical && settings.scrollbars_mode & SB_MODE_VERT) scrollbars_click(SB_WND_BOT); else if (settings.scrollbars_mode & SB_MODE_HORZ) scrollbars_click(SB_WND_RHT); break; default: break; } break; } } void scrollbars_periodic_tasks() { if (tray_data.scrollbars_data.scrollbar_down != -1 && tray_data.scrollbars_data.scrollbar_repeat_active) { scrollbars_click(tray_data.scrollbars_data.scrollbar_down); } } int scrollbars_scroll_to(struct TrayIcon *ti) { struct Rect tray_viewport_rect; tray_viewport_rect.x = tray_data.scrollbars_data.scroll_base.x, tray_viewport_rect.y = tray_data.scrollbars_data.scroll_base.y, tray_calc_tray_area_size(tray_data.xsh.width, tray_data.xsh.height, &tray_viewport_rect.w, &tray_viewport_rect.h); /* Check if icon is already visible. If so, nothing needs to be done */ if (RECTS_ISECT(tray_viewport_rect, ti->l.icn_rect)) return SUCCESS; /* Update scroll pos so that the icon is visible */ /* TODO: must be in separate function, with a reverse */ tray_data.scrollbars_data.scroll_pos.x = (settings.icon_gravity & GRAV_W ? 1 : -1) * (ti->l.icn_rect.x - tray_data.scrollbars_data.scroll_base.x); tray_data.scrollbars_data.scroll_pos.y = (settings.icon_gravity & GRAV_W ? 1 : -1) * (ti->l.icn_rect.y - tray_data.scrollbars_data.scroll_base.y); scrollbars_validate_scroll_pos(); LOG_TRACE(("computed required scroll position: %dx%d\n", tray_data.scrollbars_data.scroll_pos.x, tray_data.scrollbars_data.scroll_pos.y)); icon_list_forall(&layout_translate_to_window); embedder_update_positions(True); return SUCCESS; } int scrollbars_highlight_on(int id) { Window sb_wid; sb_wid = (0 <= id && id < 4) ? tray_data.scrollbars_data.scrollbar[id] : None; if (sb_wid != None) XSetWindowBackground( tray_data.dpy, sb_wid, settings.scrollbars_highlight_color.pixel); scrollbars_refresh(1); return SUCCESS; } int scrollbars_highlight_off(int id) { Window sb_wid; sb_wid = (0 <= id && id < 4) ? tray_data.scrollbars_data.scrollbar[id] : None; if (sb_wid != None) XSetWindowBackgroundPixmap(tray_data.dpy, sb_wid, ParentRelative); scrollbars_refresh(1); return SUCCESS; } ================================================ FILE: src/scrollbars.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * tray.h * Mon, 09 Mar 2009 12:41:32 -0400 * ------------------------------- * Scrollbars data & interface * -------------------------------*/ #ifndef _SCROLLBAR_H_ #define _SCROLLBAR_H_ #include #include #include #define SB_WND_TOP 0 #define SB_WND_BOT 1 #define SB_WND_LFT 2 #define SB_WND_RHT 3 #define SB_WND_MAX 4 #define SB_MODE_NONE 0 #define SB_MODE_VERT 1 #define SB_MODE_HORZ 2 #define SCROLLBAR_REPEAT_COUNTDOWN_MAX_1ST 10 #define SCROLLBAR_REPEAT_COUNTDOWN_MAX_SUCC 5 /* Scrollbar data */ struct ScrollbarsData { struct Point scroll_base; /* Base scroll position */ struct Point scroll_pos; /* Current scroll position */ Window scrollbar[SB_WND_MAX]; /* Window IDs of scrollbars */ XSizeHints scrollbar_xsh[SB_WND_MAX]; /* Cached window sizes for scrollbars */ int scrollbar_down; /* Click state */ int scrollbar_highlighted; /* Highlight state */ int scrollbar_repeat_active; /* If repeat is active */ int scrollbar_repeat_counter; /* Countown for repeat action */ int scrollbar_repeats_done; /* Numberf of executed repeat actions */ }; /* Initialize data structures */ void scrollbars_init(); /* Create scrollbars windows */ void scrollbars_create(); /* Update positions of scrollbars */ int scrollbars_update(); /* Refresh scrollbars */ int scrollbars_refresh(int exposures); /* Get scrollbar under given coords */ int scrollbars_get_id(Window wid, int x, int y); /* Update tray wrt scrollbar click; * - id == SB_WND_MAX is a special case used to * update scroll positions without chaning it * (i.e. after icon removal) */ int scrollbars_click(int id); /* Event handler for scrollbars */ void scrollbars_handle_event(XEvent ev); /* Perform periodic tasks for scrollbars */ void scrollbars_periodic_tasks(); /* Scroll to icon */ int scrollbars_scroll_to(struct TrayIcon *ti); /* Highlight scrollbar */ int scrollbars_highlight_on(int id); /* Switch hightlight off */ int scrollbars_highlight_off(int id); #endif ================================================ FILE: src/settings.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * settings.c * Sun, 12 Sep 2004 18:55:53 +0700 * ------------------------------- * settings parser\container * -------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "debug.h" #include "layout.h" #include "list.h" #include "settings.h" #include "tray.h" #include "wmh.h" #include "xutils.h" /* Here we keep our filthy settings */ struct Settings settings; /* Initialize data */ void init_default_settings(void) { settings.bg_color_str = "gray"; settings.tint_color_str = "white"; settings.scrollbars_highlight_color_str = "white"; settings.display_str = NULL; #ifdef DEBUG settings.log_level = LOG_LEVEL_ERR; #endif settings.geometry_str = NULL; settings.max_geometry_str = "0x0"; settings.icon_size = FALLBACK_ICON_SIZE; settings.slot_size.x = -1; settings.slot_size.y = -1; settings.deco_flags = DECO_NONE; settings.max_tray_dims.x = 0; settings.max_tray_dims.y = 0; settings.parent_bg = 0; settings.shrink_back_mode = 1; settings.sticky = 1; settings.skip_taskbar = 1; settings.transparent = 0; settings.vertical = 0; settings.grow_gravity = GRAV_N | GRAV_W; settings.icon_gravity = GRAV_N | GRAV_W; settings.wnd_type = _NET_WM_WINDOW_TYPE_DOCK; settings.wnd_layer = NULL; settings.wnd_name = PROGNAME; settings.xsync = 0; settings.need_help = 0; settings.config_fname = NULL; settings.full_pmt_search = 1; settings.min_space_policy = 0; settings.pixmap_bg = 0; settings.bg_pmap_path = NULL; settings.tint_level = 0; settings.fuzzy_edges = 0; settings.dockapp_mode = DOCKAPP_NONE; settings.scrollbars_size = -1; settings.scrollbars_mode = SB_MODE_NONE; settings.scrollbars_inc = -1; settings.wm_strut_mode = WM_STRUT_AUTO; settings.kludge_flags = 0; settings.remote_click_name = NULL; settings.remote_click_btn = REMOTE_CLICK_BTN_DEFAULT; settings.remote_click_cnt = REMOTE_CLICK_CNT_DEFAULT; settings.remote_click_pos.x = REMOTE_CLICK_POS_DEFAULT; settings.remote_click_pos.y = REMOTE_CLICK_POS_DEFAULT; settings.ignored_classes = NULL; settings.scroll_everywhere = 0; #ifdef DELAY_EMBEDDING_CONFIRMATION settings.confirmation_delay = 3; #endif #ifdef _ST_WITH_XINERAMA settings.monitor = 0; #endif } /* ******* general parsing utils ********* */ #define PARSING_ERROR(msg, str) \ if (!silent) LOG_ERROR(("Parsing error: " msg ", \"%s\" found\n", str)); /* Parse highlight color */ int parse_scrollbars_highlight_color(int, const char **argv, void **references, int) { char **highlight_color = (char **) references[0]; if (!strcasecmp(argv[0], "disable")) *highlight_color = NULL; else if ((*highlight_color = strdup(argv[0])) == NULL) DIE_OOM(("Could not copy value from parameter\n")); return SUCCESS; } /* Parse log level */ int parse_log_level(int, const char **argv, void **references, int silent) { int *log_level = (int *) references[0]; if (!strcmp(argv[0], "err")) *log_level = LOG_LEVEL_ERR; else if (!strcmp(argv[0], "info")) *log_level = LOG_LEVEL_INFO; else if (!strcmp(argv[0], "trace")) *log_level = LOG_LEVEL_TRACE; else { PARSING_ERROR("err, info, or trace expected", argv[0]); return FAILURE; } return SUCCESS; } /* Parse list of ignored window classes */ int parse_ignored_classes(int argc, const char **argv, void **references, int) { struct WindowClass **classes = (struct WindowClass **) references[0]; struct WindowClass *newclass = NULL; int i; for (i = 0; i < argc; i++) { newclass = malloc(sizeof(struct WindowClass)); newclass->name = strdup(argv[i]); LIST_ADD_ITEM(*classes, newclass); } return SUCCESS; } /* Parse dockapp mode */ int parse_dockapp_mode(int, const char **argv, void **references, int silent) { int *dockapp_mode = (int *) references[0]; if (!strcmp(argv[0], "none")) *dockapp_mode = DOCKAPP_NONE; else if (!strcmp(argv[0], "simple")) *dockapp_mode = DOCKAPP_SIMPLE; else if (!strcmp(argv[0], "wmaker")) *dockapp_mode = DOCKAPP_WMAKER; else { PARSING_ERROR("none, simple, or wmaker expected", argv[0]); return FAILURE; } return SUCCESS; } /* Parse gravity string ORing resulting value * with current value of tgt */ int parse_gravity(int, const char **argv, void **references, int silent) { int *gravity = (int *) references[0]; const char *gravity_s = argv[0]; int parsed = 0; if (strlen(gravity_s) > 2) goto fail; for (; *gravity_s; gravity_s++) { switch (tolower(*gravity_s)) { case 'n': parsed |= GRAV_N; break; case 's': parsed |= GRAV_S; break; case 'w': parsed |= GRAV_W; break; case 'e': parsed |= GRAV_E; break; default: goto fail; } } if ((parsed & GRAV_N && parsed & GRAV_S) || (parsed & GRAV_E && parsed & GRAV_W)) goto fail; *gravity = parsed; return SUCCESS; fail: PARSING_ERROR("gravity expected", gravity_s); return FAILURE; } /* Parse integer string storing resulting value in tgt */ int parse_int(int, const char **argv, void **references, int silent) { int *parsed = (int *) references[0]; char *invalid; *parsed = strtol(argv[0], &invalid, 0); if (*invalid != '\0') { PARSING_ERROR("integer expected", argv[0]); return FAILURE; } return SUCCESS; } /* Parse kludges mode */ int parse_kludges(int, const char **argv, void **references, int silent) { const char *token = strtok((char *) argv[0], ","); int *kludges = (int *) references[0]; for (; token != NULL; token = strtok(NULL, ",")) { if (!strcasecmp(token, "fix_window_pos")) *kludges |= KLUDGE_FIX_WND_POS; else if (!strcasecmp(token, "force_icons_size")) *kludges |= KLUDGE_FORCE_ICONS_SIZE; else if (!strcasecmp(token, "use_icons_hints")) *kludges |= KLUDGE_USE_ICONS_HINTS; else { PARSING_ERROR("kludge flag expected", token); return FAILURE; } } return SUCCESS; } /* Parse strut mode */ int parse_strut_mode(int, const char **argv, void **references, int silent) { int *strut_mode = (int *) references[0]; if (!strcasecmp(argv[0], "auto")) *strut_mode = WM_STRUT_AUTO; else if (!strcasecmp(argv[0], "top")) *strut_mode = WM_STRUT_TOP; else if (!strcasecmp(argv[0], "bottom")) *strut_mode = WM_STRUT_BOT; else if (!strcasecmp(argv[0], "left")) *strut_mode = WM_STRUT_LFT; else if (!strcasecmp(argv[0], "right")) *strut_mode = WM_STRUT_RHT; else if (!strcasecmp(argv[0], "none")) *strut_mode = WM_STRUT_NONE; else { PARSING_ERROR( "one of top, bottom, left, right, or auto expected", argv[0]); return FAILURE; } return SUCCESS; } /* Parse boolean string storing result in tgt*/ int parse_bool(int, const char **argv, void **references, int silent) { const char *true_str[] = {"yes", "on", "true", "1", NULL}; const char *false_str[] = {"no", "off", "false", "0", NULL}; int *boolean = (int *) references[0]; for (const char **s = true_str; *s; s++) { if (!strcasecmp(argv[0], *s)) { *boolean = True; return SUCCESS; } } for (const char **s = false_str; *s; s++) { if (!strcasecmp(argv[0], *s)) { *boolean = False; return SUCCESS; } } PARSING_ERROR("boolean expected", argv[0]); return FAILURE; } /* Backwards version of the boolean parser */ int parse_bool_rev(int argc, const char **argv, void **references, int silent) { int *boolean = (int *) references[0]; if (parse_bool(argc, argv, references, silent)) { *boolean = ! *boolean; return SUCCESS; } return FAILURE; } /* Parse window layer string storing result in tgt */ int parse_wnd_layer(int, const char **argv, void **references, int silent) { char **window_layer = (char **) references[0]; if (!strcasecmp(argv[0], "top")) *window_layer = _NET_WM_STATE_ABOVE; else if (!strcasecmp(argv[0], "bottom")) *window_layer = _NET_WM_STATE_BELOW; else if (!strcasecmp(argv[0], "normal")) *window_layer = NULL; else { PARSING_ERROR("window layer expected", argv[0]); return FAILURE; } return SUCCESS; } /* Parse window type string storing result in tgt */ int parse_wnd_type(int, const char **argv, void **references, int silent) { const char **window_type = (const char **) references[0]; if (!strcasecmp(argv[0], "dock")) *window_type = _NET_WM_WINDOW_TYPE_DOCK; else if (!strcasecmp(argv[0], "toolbar")) *window_type = _NET_WM_WINDOW_TYPE_TOOLBAR; else if (!strcasecmp(argv[0], "utility")) *window_type = _NET_WM_WINDOW_TYPE_UTILITY; else if (!strcasecmp(argv[0], "normal")) *window_type = _NET_WM_WINDOW_TYPE_NORMAL; else if (!strcasecmp(argv[0], "desktop")) *window_type = _NET_WM_WINDOW_TYPE_DESKTOP; else { PARSING_ERROR("window type expected", argv[0]); return FAILURE; } return SUCCESS; } /* Just copy string from arg to *tgt */ int parse_copystr(int, const char **argv, void **references, int) { const char **stringref = (const char **) references[0]; /* Valgrind note: this memory will never be freed before stalonetray's exit. */ if ((*stringref = strdup(argv[0])) == NULL) DIE_OOM(("Could not copy value from parameter\n")); return SUCCESS; } /* Parses window decoration specification */ int parse_deco(int, const char **argv, void **references, int silent) { int *decorations = (int *) references[0]; const char *arg = argv[0]; if (!strcasecmp(arg, "none")) *decorations = DECO_NONE; else if (!strcasecmp(arg, "all")) *decorations = DECO_ALL; else if (!strcasecmp(arg, "border")) *decorations = DECO_BORDER; else if (!strcasecmp(arg, "title")) *decorations = DECO_TITLE; else { PARSING_ERROR("decoration specification expected", arg); return FAILURE; } return SUCCESS; } /* Parses window decoration specification */ int parse_sb_mode(int, const char **argv, void **references, int silent) { int *sb_mode = (int *) references[0]; if (!strcasecmp(argv[0], "none")) *sb_mode = 0; else if (!strcasecmp(argv[0], "vertical")) *sb_mode = SB_MODE_VERT; else if (!strcasecmp(argv[0], "horizontal")) *sb_mode = SB_MODE_HORZ; else if (!strcasecmp(argv[0], "all")) *sb_mode = SB_MODE_HORZ | SB_MODE_VERT; else { PARSING_ERROR("scrollbars specification expected", argv[0]); return FAILURE; } return SUCCESS; } #if 0 /* Parses remote op specification */ int parse_remote(char *str, void **tgt, int silent) { #define NEXT_TOK(str, rest) \ do { \ (str) = (rest); \ if ((str) != NULL) { \ (rest) = strchr((str), ','); \ if ((rest) != NULL) *((rest)++) = 0; \ } \ } while (0) #define PARSE_INT(tgt, str, tail, def, msg) \ do { \ if (str == NULL || *(str) == '\0') { \ (tgt) = def; \ } else { \ (tgt) = strtol((str), &(tail), 0); \ if (*(tail) != '\0') { \ PARSING_ERROR(msg, (str)); \ return FAILURE; \ } \ } \ } while (0) /* Handy names for parameters */ int *flag = (int *) tgt[0]; char **name = (char **) tgt[1]; int *btn = (int *) tgt[2]; struct Point *pos = (struct Point *) tgt[3]; /* Local variables */ char *rest = str, *tail; if (str == NULL || strlen(str) == 0) return FAILURE; *flag = 1; NEXT_TOK(str, rest); *name = strdup(str); NEXT_TOK(str, rest); PARSE_INT(*btn, str, tail, INT_MIN, "remote click: button number expected"); NEXT_TOK(str, rest); PARSE_INT(pos->x, str, tail, INT_MIN, "remote click: x coordinate expected"); NEXT_TOK(str, rest); PARSE_INT(pos->y, str, tail, INT_MIN, "remote click: y coordinate expected"); return SUCCESS; #undef NEXT_TOK #undef PARSE_INT } #endif int parse_remote_click_type(int, const char **argv, void **references, int silent) { int *remote_click_type = (int *) references[0]; if (!strcasecmp(argv[0], "single")) *remote_click_type = 1; else if (!strcasecmp(argv[0], "double")) *remote_click_type = 2; else { PARSING_ERROR("click type can be single or double", argv[0]); return FAILURE; } return SUCCESS; } int parse_pos(int, const char **argv, void **references, int) { struct Point *pos = (struct Point *) references[0]; unsigned int dummy; XParseGeometry(argv[0], &pos->x, &pos->y, &dummy, &dummy); return SUCCESS; } int parse_size(int, const char **argv, void **references, int) { struct Point *size = (struct Point *) references[0]; unsigned int width, height; int bitmask, dummy; bitmask = XParseGeometry(argv[0], &dummy, &dummy, &width, &height); if (bitmask == 0 || bitmask & ~(WidthValue | HeightValue)) return FAILURE; if ((bitmask & HeightValue) == 0) height = width; size->x = (width > (unsigned int) INT_MAX) ? INT_MAX : (int) width; size->y = (height > (unsigned int) INT_MAX) ? INT_MAX : (int) height; return SUCCESS; } /************ CLI **************/ #define MAX_TARGETS 10 #define MAX_DEFAULT_ARGS 10 /* parameter parser function */ typedef int (*param_parser_t)(int argc, const char **argv, void **references, int silent); struct Param { const char *short_name; /* short command line parameter name */ const char *long_name; /* long command line parameter name */ const char *rc_name; /* parameter name for config file */ void *references[MAX_TARGETS]; /* array of references necessary when parsing */ const int pass; /* 0th pass parameters are parsed before rc file, */ /* 1st pass parameters are parsed after it */ const int min_argc; /* minimum number of expected arguments */ const int max_argc; /* maximum number of expected arguments, 0 for unlimited */ const int default_argc; /* number of default arguments, if present */ const char *default_argv[MAX_DEFAULT_ARGS]; /* default arguments if none are given */ param_parser_t parser; /* pointer to parsing function */ }; struct Param params[] = { { .short_name = "-display", .long_name = NULL, .rc_name = "display", .references = { (void *) &settings.display_str }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_copystr }, { .short_name = NULL, .long_name = "--log-level", .rc_name = "log_level", .references = { (void *) &settings.log_level }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_log_level }, { .short_name = "-bg", .long_name = "--background", .rc_name = "background", .references = { (void *) &settings.bg_color_str }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_copystr }, { .short_name = "-c", .long_name = "--config", .rc_name = NULL, .references = { (void *) &settings.config_fname }, .pass = 0, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_copystr }, { .short_name = "-d", .long_name = "--decorations", .rc_name = "decorations", .references = { (void *) &settings.deco_flags }, .pass = 1, .min_argc = 0, .max_argc = 1, .default_argc = 1, .default_argv = { "all" }, .parser = (param_parser_t) &parse_deco }, { .short_name = NULL, .long_name = "--dockapp-mode", .rc_name = "dockapp_mode", .references = { (void *) &settings.dockapp_mode }, .pass = 1, .min_argc = 0, .max_argc = 1, .default_argc = 1, .default_argv = { "simple" }, .parser = (param_parser_t) &parse_dockapp_mode }, { .short_name = "-f", .long_name = "--fuzzy-edges", .rc_name = "fuzzy_edges", .references = { (void *) &settings.fuzzy_edges }, .pass = 1, .min_argc = 0, .max_argc = 1, .default_argc = 1, .default_argv = { "2" }, .parser = (param_parser_t) &parse_int }, { .short_name = "-geometry", .long_name = "--geometry", .rc_name = "geometry", .references = { (void *) &settings.geometry_str }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_copystr }, { .short_name = NULL, .long_name = "--grow-gravity", .rc_name = "grow_gravity", .references = { (void *) &settings.grow_gravity }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_gravity }, { .short_name = NULL, .long_name = "--icon-gravity", .rc_name = "icon_gravity", .references = { (void *) &settings.icon_gravity }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_gravity }, { .short_name = "-i", .long_name = "--icon-size", .rc_name = "icon_size", .references = { (void *) &settings.icon_size }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_int }, { .short_name = "-h", .long_name = "--help", .rc_name = NULL, .references = { (void *) &settings.need_help }, .pass = 0, .min_argc = 0, .max_argc = 0, .default_argc = 1, .default_argv = {"true"}, .parser = (param_parser_t) &parse_bool }, { .short_name = NULL, .long_name = "--kludges", .rc_name = "kludges", .references = { (void *) &settings.kludge_flags }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_kludges }, { .short_name = NULL, .long_name = "--max-geometry", .rc_name = "max_geometry", .references = { (void *) &settings.max_geometry_str }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_copystr }, #ifdef _ST_WITH_XINERAMA { .short_name = "-m", .long_name = "--monitor", .rc_name = "monitor", .references = { (void *) &settings.monitor }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_int }, #endif { .short_name = NULL, .long_name = "--no-shrink", .rc_name = "no_shrink", .references = { (void *) &settings.shrink_back_mode }, .pass = 1, .min_argc = 0, .max_argc = 1, .default_argc = 1, .default_argv = {"true"}, .parser = (param_parser_t) &parse_bool_rev }, { .short_name = "-p", .long_name = "--parent-bg", .rc_name = "parent_bg", .references = { (void *) &settings.parent_bg }, .pass = 1, .min_argc = 0, .max_argc = 1, .default_argc = 1, .default_argv = {"true"}, .parser = (param_parser_t) &parse_bool }, { .short_name = "-r", .long_name = "--remote-click-icon", .rc_name = NULL, .references = { (void *) &settings.remote_click_name }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_copystr }, { .short_name = NULL, .long_name = "--remote-click-button", .rc_name = NULL, .references = { (void *) &settings.remote_click_btn }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_int }, { .short_name = NULL, .long_name = "--remote-click-position", .rc_name = NULL, .references = { (void *) &settings.remote_click_pos }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_pos }, { .short_name = NULL, .long_name = "--remote-click-type", .rc_name = NULL, .references = { (void *) &settings.remote_click_cnt }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_remote_click_type }, { .short_name = NULL, .long_name = "--scrollbars", .rc_name = "scrollbars", .references = { (void *) &settings.scrollbars_mode }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_sb_mode }, { .short_name = NULL, .long_name = "--scrollbars-highlight", .rc_name = "scrollbars_highlight", .references = { (void *) &settings.scrollbars_highlight_color_str }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_scrollbars_highlight_color }, { .short_name = NULL, .long_name = "--scrollbars-step", .rc_name = "scrollbars_step", .references = { (void *) &settings.scrollbars_inc }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_int }, { .short_name = NULL, .long_name = "--scrollbars-size", .rc_name = "scrollbars_size", .references = { (void *) &settings.scrollbars_size }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_int }, { .short_name = NULL, .long_name = "--scroll-everywhere", .rc_name = "scroll_everywhere", .references = { (void *) &settings.scroll_everywhere }, .pass = 1, .min_argc = 0, .max_argc = 1, .default_argc = 1, .default_argv = {"true"}, .parser = (param_parser_t) &parse_bool }, { .short_name = NULL, .long_name = "--skip-taskbar", .rc_name = "skip_taskbar", .references = { (void *) &settings.skip_taskbar }, .pass = 1, .min_argc = 0, .max_argc = 1, .default_argc = 1, .default_argv = {"true"}, .parser = (param_parser_t) &parse_bool }, { .short_name = "-s", .long_name = "--slot-size", .rc_name = "slot_size", .references = { (void *) &settings.slot_size }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_size }, { .short_name = NULL, .long_name = "--sticky", .rc_name = "sticky", .references = { (void *) &settings.sticky }, .pass = 1, .min_argc = 0, .max_argc = 1, .default_argc = 1, .default_argv = {"true"}, .parser = (param_parser_t) &parse_bool }, { .short_name = NULL, .long_name = "--tint-color", .rc_name = "tint_color", .references = { (void *) &settings.tint_color_str }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_copystr }, { .short_name = NULL, .long_name = "--tint-level", .rc_name = "tint_level", .references = { (void *) &settings.tint_level }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_int }, { .short_name = "-t", .long_name = "--transparent", .rc_name = "transparent", .references = { (void *) &settings.transparent }, .pass = 1, .min_argc = 0, .max_argc = 1, .default_argc = 1, .default_argv = {"true"}, .parser = (param_parser_t) &parse_bool }, { .short_name = "-v", .long_name = "--vertical", .rc_name = "vertical", .references = { (void *) &settings.vertical }, .pass = 1, .min_argc = 0, .max_argc = 1, .default_argc = 1, .default_argv = {"true"}, .parser = (param_parser_t) &parse_bool }, { .short_name = NULL, .long_name = "--window-layer", .rc_name = "window_layer", .references = { (void *) &settings.wnd_layer }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_wnd_layer }, { .short_name = NULL, .long_name = "--window-name", .rc_name = "window_name", .references = { (void *) &settings.wnd_name }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_copystr }, { .short_name = NULL, .long_name = "--window-strut", .rc_name = "window_strut", .references = { (void *) &settings.wm_strut_mode }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_strut_mode }, { .short_name = NULL, .long_name = "--window-type", .rc_name = "window_type", .references = { (void *) &settings.wnd_type }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_wnd_type }, { .short_name = NULL, .long_name = "--xsync", .rc_name = "xsync", .references = { (void *) &settings.xsync }, .pass = 1, .min_argc = 0, .max_argc = 1, .default_argc = 1, .default_argv = {"true"}, .parser = (param_parser_t) &parse_bool }, { .short_name = NULL, .long_name = NULL, .rc_name = "ignore_classes", .references = { (void *) &settings.ignored_classes }, .pass = 1, .min_argc = 1, .max_argc = 0, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_ignored_classes }, #ifdef DELAY_EMBEDDING_CONFIRMATION { .short_name = NULL, .long_name = "--confirmation-delay", .rc_name = "confirmation_delay", .references = { (void *) &settings.confirmation_delay }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_int }, #endif #ifdef _ST_WITH_XPM { .short_name = NULL, .long_name = "--pixmap-bg", .rc_name = "pixmap_bg", .references = { (void *) &settings.bg_pmap_path }, .pass = 1, .min_argc = 1, .max_argc = 1, .default_argc = 0, .default_argv = NULL, .parser = (param_parser_t) &parse_copystr }, #endif }; void usage(char *progname) { printf("\nstalonetray " VERSION " [ " FEATURE_LIST " ]\n"); printf( "\nUsage: %s [options...]\n" "\n" "For short options argument can be specified as -o value or -ovalue.\n" "For long options argument can be specified as --option value or\n" "--option=value. All flag-options have expilicit optional boolean \n" "argument, which can be true (1, yes) or false (0, no).\n" "\n", progname); printf( "Possible options are:\n" " -display use X display \n" " -bg, --background select background color (default: " "#777777)\n" " -c, --config read configuration from \n" " (instead of default " "$HOME/.stalonetrayrc)\n" " -d, --decorations set what part of window decorations " "are\n" " visible; deco can be: none " "(default),\n" " title, border, all\n" " --dockapp-mode [] enable dockapp mode; mode can be " "none (default),\n" " simple (default if no mode " "specified), or wmaker\n" " -f, --fuzzy-edges [] set edges fuzziness level from\n" " 0 (disabled) to 3 (maximum); works " "with\n" " tinting and/or pixmap background;\n" " if not specified, level defaults to " "2\n" " [-]-geometry set initial tray`s geometry (width " "and height\n" " are defined in icon slots; offsets " "are defined\n" " in pixels)\n" " --grow-gravity set tray`s grow gravity,\n" " either to N, S, W, E, NW, NE, SW, or " "SE\n" " --icon-gravity icon positioning gravity (NW, NE, " "SW, SE)\n" " -i, --icon-size set basic icon size to ; default " "is 24\n" " --ignore-classes ignore tray icons in xembed if the " "tray window has one of the given classes, separated by commas\n" " -h, --help show this message\n" ); printf( #ifdef DEBUG " --log-level set the level of output to either " "err\n" " (default), info, or trace\n" #else " --log-level set the level of output to either " "err\n" " (default), or info\n" #endif " --kludges enable specific kludges to work " "around\n" " non-conforming WMs and/or " "stalonetray bugs;\n" " argument is a comma-separated list " "of:\n" " - fix_window_pos (fix window " "position),\n" " - force_icons_size (ignore icon " "resizes),\n" " - use_icons_hints (use icon size " "hints)\n" " --max-geometry set tray maximal width and height; 0 " "indicates\n" " no limit in respective direction\n" " -m, --monitor What monitor to display in\n" " --no-shrink do not shrink window back after icon " "removal\n" " -p, --parent-bg use parent for background\n" " --pixmap-bg use pixmap for tray`s window " "background\n" " -r, --remote-click-icon remote control (assumes an " "instance of\n" " stalonetray is already an active " "tray on this\n" " screen); sends click to icon which " "window's \n" " name is \n" " --remote-click-button defines mouse button for " "--remote-click-icon\n" " --remote-click-position x defines position for " "--remote-click-icon\n" " --remote-click-type defines click type for " "--remote-click-icon;\n" " type can be either single, or " "double\n" " --scrollbars set scrollbar mode either to all, " "horizontal,\n" " vertical, or none\n" " --scrollbars-highlight set scrollbar highlighting mode " "which can\n" " be either color spec (default color " "is red)\n" " or disable\n" " --scrollbars-step set scrollbar step to n pixels\n" " --scrollbars-size set scrollbar size to n pixels\n" " --scroll-everywhere enable scrolling outside of the scrollbars too\n" " --slot-size [x] set icon slot size in pixels\n" " if omitted, hight is set equal to width\n" " --skip-taskbar hide tray`s window from the taskbar\n" " --sticky make tray`s window sticky across " "multiple\n" " desktops/pages\n" " -t, --transparent enable root transparency\n" " --tint-color tint tray background with color (not " "used\n" " with plain color background)\n" " --tint-level set tinting level from 0 to 255\n" " -v, --vertical use vertical layout of icons " "(horizontal\n" " layout is used by default)\n" " --window-layer set tray`s window EWMH layer\n" " either to bottom, normal, or top\n" " --window-strut set window strut mode to either " "auto,\n" " left, right, top, or bottom\n" " --window-type set tray`s window EWMH type to " "either\n" " normal, dock, toolbar, utility, " "desktop\n" " --xsync operate on X server synchronously " "(SLOW)\n" "\n"); } /* Parse command line parameters */ int parse_cmdline(int argc, char **argv, int pass) { struct Param *p, *match; char *progname = argv[0]; const char **p_argv = NULL, *argbuf[MAX_DEFAULT_ARGS]; int p_argc; while (--argc > 0) { argv++; match = NULL; for (p = params; p->parser != NULL; p++) { if (p->max_argc) { if (p->short_name != NULL && strstr(*argv, p->short_name) == *argv) { if ((*argv)[strlen(p->short_name)] != '\0') { /* accept arguments in the form -a5 */ argbuf[0] = *argv + strlen(p->short_name); p_argc = 1; p_argv = argbuf; } else if (argc > 1 && argv[1][0] != '-') { /* accept arguments in the form -a 5, do not accept values starting with '-' */ argbuf[0] = *(++argv); p_argc = 1; p_argv = argbuf; argc--; } else if (p->min_argc > 0) { /* argument is missing */ LOG_ERROR(("%s expects an argument\n", p->short_name)); break; } else { /* argument is optional, use default value */ p_argc = p->default_argc; p_argv = p->default_argv; } } else if (p->long_name != NULL && strstr(*argv, p->long_name) == *argv) { if ((*argv)[strlen(p->long_name)] == '=') {/* accept arguments in the form --abcd=5 */ argbuf[0] = *argv + strlen(p->long_name) + 1; p_argc = 1; p_argv = argbuf; } else if ((*argv)[strlen(p->long_name)] == '\0') { /* accept arguments in the from --abcd 5 */ if (argc > 1 && argv[1][0] != '-') { /* arguments cannot start with the dash */ argbuf[0] = *(++argv); p_argc = 1; p_argv = argbuf; argc--; } else if (p->min_argc > 0) { /* argument is missing */ LOG_ERROR(("%s expects an argument\n", p->long_name)); break; } else { /* argument is optional, use default value */ p_argc = p->default_argc; p_argv = p->default_argv; } } else continue; /* just in case when there can be both --abc and --abcd */ } else continue; match = p; break; } else if (strcmp(*argv, p->short_name) == 0 || strcmp(*argv, p->long_name) == 0) { match = p; p_argc = p->default_argc; p_argv = p->default_argv; break; } } #define USAGE_AND_DIE() \ do { \ usage(progname); \ DIE(("Could not parse command line\n")); \ } while (0) if (match == NULL) USAGE_AND_DIE(); if (match->pass != pass) continue; if (p_argv == NULL) DIE_IE(("Argument cannot be NULL!\n")); LOG_TRACE(("cmdline: pass %d, param \"%s\", args: [", pass, match->long_name != NULL ? match->long_name : match->short_name)); for (int i = 0; i < p_argc - 1; i++) LOG_TRACE(("\"%s\", ", p_argv[i])); LOG_TRACE(("\"%s\"]\n", p_argv[p_argc - 1])); if (!match->parser(p_argc, p_argv, match->references, match->min_argc == 0)) { if (match->min_argc == 0) { assert(p_argv != match->default_argv); match->parser(match->default_argc, match->default_argv, match->references, False); argc++; argv--; } else USAGE_AND_DIE(); } } if (settings.need_help) { usage(progname); exit(0); } return SUCCESS; } /************ .stalonetrayrc ************/ /************************************************************************************** * ::= [] [( )* ] [] * ::= ""| *::= # **************************************************************************************/ /* Does exactly what its name says */ #define SKIP_SPACES(p) \ { \ for (; *p != 0 && isspace((int)*p); p++) \ ; \ } /* Break the line in argc, argv pair */ int get_args(char *line, int *argc, char ***argv) { int q_flag = 0; char *arg_start, *q_pos; *argc = 0; *argv = NULL; /* 1. Strip leading spaces */ SKIP_SPACES(line); if (0 == *line) { /* meaningless line */ return SUCCESS; } arg_start = line; /* 2. Strip comments */ for (; 0 != *line; line++) { q_flag = ('"' == *line) ? !q_flag : q_flag; if ('#' == *line && !q_flag) { *line = 0; break; } } if (q_flag) { /* disbalance of quotes */ LOG_ERROR(("Disbalance of quotes\n")); return FAILURE; } if (arg_start == line) { /* meaningless line */ return SUCCESS; } line--; /* 3. Strip trailing spaces */ for (; line != arg_start && isspace((int)*line); line--) ; if (arg_start == line) { /* meaningless line */ return FAILURE; } *(line + 1) = 0; /* this _is_ really ok since isspace(0) != 0 */ line = arg_start; /* 4. Extract arguments */ do { (*argc)++; /* Add space to store one more argument */ if (NULL == (*argv = realloc(*argv, *argc * sizeof(char *)))) DIE_OOM(("Could not allocate memory to parse parameters\n")); if (*arg_start == '"') { /* 4.1. argument is quoted: find matching quote */ arg_start++; (*argv)[*argc - 1] = arg_start; if (NULL == (q_pos = strchr(arg_start, '"'))) { free(*argv); DIE_IE(("Quotes balance calculation failed\n")); return FAILURE; } arg_start = q_pos; } else { /* 4.2. whitespace-separated argument: find fist whitespace */ (*argv)[*argc - 1] = arg_start; for (; 0 != *arg_start && !isspace((int)*arg_start); arg_start++) ; } if (*arg_start != 0) { *arg_start = 0; arg_start++; SKIP_SPACES(arg_start); } } while (*arg_start != 0); return SUCCESS; } char *find_rc(const char *path_part1, const char *path_part2, const char *rc) { static char full_path[PATH_MAX]; int len; struct stat statbuf; if (path_part1 == NULL) return NULL; if (path_part2 == NULL) { LOG_TRACE(("looking for config file in '%s/%s'\n", path_part1, rc)); len = snprintf(full_path, sizeof(full_path), "%s/%s", path_part1, rc); } else { LOG_TRACE(("looking for config file in '%s/%s/%s'\n", path_part1, path_part2, rc)); len = snprintf(full_path, sizeof(full_path), "%s/%s/%s", path_part1, path_part2, rc); } if (len < 0 || (size_t) len >= sizeof(full_path)) return NULL; if (stat(full_path, &statbuf) != 0) return NULL; return full_path; } #define READ_BUF_SZ 512 /* Parses rc file (path is either taken from settings.config_fname * or ~/.stalonetrayrc is used) */ void parse_rc(void) { FILE *cfg; char buf[READ_BUF_SZ + 1]; int lnum = 0; int argc, p_argc; char **argv; const char **p_argv; struct Param *p, *match; /* 1. Setup file name */ if (settings.config_fname == NULL) settings.config_fname = find_rc(getenv("HOME"), NULL, "." STALONETRAY_RC); if (settings.config_fname == NULL) settings.config_fname = find_rc(getenv("XDG_CONFIG_HOME"), NULL, STALONETRAY_RC); if (settings.config_fname == NULL) settings.config_fname = find_rc(getenv("HOME"), ".config", STALONETRAY_RC); if (settings.config_fname == NULL) { LOG_INFO(("could not find any config files.\n")); return; } LOG_INFO(("using config file \"%s\"\n", settings.config_fname)); /* 2. Open file */ cfg = fopen(settings.config_fname, "r"); if (cfg == NULL) { LOG_ERROR(("could not open %s (%s)\n", settings.config_fname, strerror(errno))); return; } /* 3. Read the file line by line */ buf[READ_BUF_SZ] = 0; while (!feof(cfg)) { lnum++; if (fgets(buf, READ_BUF_SZ, cfg) == NULL) { if (ferror(cfg)) LOG_ERROR(("read error (%s)\n", strerror(errno))); break; } if (!get_args(buf, &argc, &argv)) { DIE(("Configuration file parse error at %s:%d: could not parse " "line\n", settings.config_fname, lnum)); } if (!argc) continue; /* This is empty/comment-only line */ match = NULL; p_argc = argc - 1; p_argv = (const char **) argv + 1; for (p = params; p->parser != NULL; p++) { if (p->rc_name != NULL && strcmp(argv[0], p->rc_name) == 0) { if (argc - 1 < p->min_argc || (p->max_argc && argc - 1 > p->max_argc)) { DIE(("Configuration file parse error at %s:%d: " "invalid number of args for \"%s\" (%d-%d required)\n", settings.config_fname, lnum, p->rc_name, p->min_argc, p->max_argc)); } match = p; if (p_argc == 0) { p_argc = match->default_argc; p_argv = match->default_argv; } break; } } if (!match) { DIE(("Configuration file parse error at %s:%d: unrecognized rc " "file keyword \"%s\".\n", settings.config_fname, lnum, argv[0])); } assert(p_argv != NULL); LOG_TRACE(("rc: param \"%s\", args [\"", match->rc_name)); for (int i = 0; i < p_argc - 1; i++) LOG_TRACE(("\"%s\", ", p_argv[i])); LOG_TRACE(("\"%s\"]\n", p_argv[p_argc - 1])); if (!match->parser(p_argc, p_argv, match->references, False)) { DIE(("Configuration file parse error at %s:%d: could not parse " "argument for \"%s\".\n", settings.config_fname, lnum, argv[0])); } free(argv); } fclose(cfg); } /* Interpret all settings that need an open display or other settings */ void interpret_settings(void) { static int gravity_matrix[11] = {ForgetGravity, EastGravity, WestGravity, ForgetGravity, SouthGravity, SouthEastGravity, SouthWestGravity, ForgetGravity, NorthGravity, NorthEastGravity, NorthWestGravity}; int geom_flags; int dummy; XWindowAttributes root_wa; /* Sanitize icon size */ val_range(settings.icon_size, MIN_ICON_SIZE, INT_MAX); if (settings.slot_size.x < settings.icon_size) settings.slot_size.x = settings.icon_size; if (settings.slot_size.y < settings.icon_size) settings.slot_size.y = settings.icon_size; /* Sanitize scrollbar settings */ if (settings.scrollbars_mode != SB_MODE_NONE) { val_range(settings.scrollbars_inc, settings.slot_size.x / 2, INT_MAX); if (settings.scrollbars_size < 0) settings.scrollbars_size = settings.slot_size.x / 4; } /* Sanitize all gravity strings */ settings.icon_gravity |= ((settings.icon_gravity & GRAV_V) ? 0 : GRAV_N); settings.icon_gravity |= ((settings.icon_gravity & GRAV_H) ? 0 : GRAV_W); settings.win_gravity = gravity_matrix[settings.grow_gravity]; settings.bit_gravity = gravity_matrix[settings.icon_gravity]; /* Parse all background-related settings */ #ifdef _ST_WITH_XPM settings.pixmap_bg = (settings.bg_pmap_path != NULL); #endif if (settings.pixmap_bg) { settings.parent_bg = False; settings.transparent = False; } if (settings.transparent) settings.parent_bg = False; /* Parse color-related settings */ if (!x11_parse_color( tray_data.dpy, settings.bg_color_str, &settings.bg_color)) DIE(("Could not parse background color \"%s\"\n", settings.bg_color_str)); if (settings.scrollbars_highlight_color_str != NULL) { if (!x11_parse_color(tray_data.dpy, settings.scrollbars_highlight_color_str, &settings.scrollbars_highlight_color)) { DIE(("Could not parse scrollbars highlight color \"%s\"\n", settings.bg_color_str)); } } /* Sanitize tint level value */ val_range(settings.tint_level, 0, 255); if (settings.tint_level) { /* Parse tint color */ if (!x11_parse_color( tray_data.dpy, settings.tint_color_str, &settings.tint_color)) DIE(("Could not parse tint color \"%s\"\n", settings.tint_color_str)); } /* Sanitize edges fuzziness */ val_range(settings.fuzzy_edges, 0, 3); /* Get dimensions of root window */ if (!XGetWindowAttributes( tray_data.dpy, DefaultRootWindow(tray_data.dpy), &root_wa)) DIE(("Could not get root window dimensions.\n")); tray_data.root_wnd.x = 0; tray_data.root_wnd.y = 0; tray_data.root_wnd.width = root_wa.width; tray_data.root_wnd.height = root_wa.height; /* Parse geometry */ #define DEFAULT_GEOMETRY "1x1+0+0" tray_data.xsh.flags = PResizeInc | PBaseSize; tray_data.xsh.x = 0; tray_data.xsh.y = 0; tray_data.xsh.width_inc = settings.slot_size.x; tray_data.xsh.height_inc = settings.slot_size.y; tray_data.xsh.base_width = 0; tray_data.xsh.base_height = 0; tray_calc_window_size( 0, 0, &tray_data.xsh.base_width, &tray_data.xsh.base_height); geom_flags = XWMGeometry(tray_data.dpy, DefaultScreen(tray_data.dpy), settings.geometry_str, DEFAULT_GEOMETRY, 0, &tray_data.xsh, &tray_data.xsh.x, &tray_data.xsh.y, &tray_data.xsh.width, &tray_data.xsh.height, &tray_data.xsh.win_gravity); tray_data.xsh.win_gravity = settings.win_gravity; tray_data.xsh.min_width = tray_data.xsh.width; tray_data.xsh.min_height = tray_data.xsh.height; tray_data.xsh.max_width = tray_data.xsh.width; tray_data.xsh.min_height = tray_data.xsh.height; tray_data.xsh.flags = PResizeInc | PBaseSize | PMinSize | PMaxSize | PWinGravity; tray_calc_tray_area_size(tray_data.xsh.width, tray_data.xsh.height, &settings.orig_tray_dims.x, &settings.orig_tray_dims.y); /* Dockapp mode */ if (settings.dockapp_mode == DOCKAPP_WMAKER) tray_data.xsh.flags |= USPosition; else { if (geom_flags & (XValue | YValue)) tray_data.xsh.flags |= USPosition; else tray_data.xsh.flags |= PPosition; if (geom_flags & (WidthValue | HeightValue)) tray_data.xsh.flags |= USSize; else tray_data.xsh.flags |= PSize; } LOG_TRACE(("final geometry: %dx%d at (%d,%d)\n", tray_data.xsh.width, tray_data.xsh.height, tray_data.xsh.x, tray_data.xsh.y)); if ((geom_flags & XNegative) && (geom_flags & YNegative)) settings.geom_gravity = SouthEastGravity; else if (geom_flags & YNegative) settings.geom_gravity = SouthWestGravity; else if (geom_flags & XNegative) settings.geom_gravity = NorthEastGravity; else settings.geom_gravity = NorthWestGravity; /* Set tray maximal width/height */ geom_flags = XParseGeometry(settings.max_geometry_str, &dummy, &dummy, (unsigned int *)&settings.max_tray_dims.x, (unsigned int *)&settings.max_tray_dims.y); LOG_TRACE(("max geometry from max_geometry_str: %dx%d\n", settings.max_tray_dims.x, settings.max_tray_dims.y)); if (!settings.max_tray_dims.x) settings.max_tray_dims.x = root_wa.width; else { settings.max_tray_dims.x *= settings.slot_size.x; val_range( settings.max_tray_dims.x, settings.orig_tray_dims.x, INT_MAX); } if (!settings.max_tray_dims.y) settings.max_tray_dims.y = root_wa.height; else { settings.max_tray_dims.y *= settings.slot_size.y; val_range( settings.max_tray_dims.y, settings.orig_tray_dims.y, INT_MAX); } LOG_TRACE(("max geometry after normalization: %dx%d\n", settings.max_tray_dims.x, settings.max_tray_dims.y)); /* XXX: this assumes certain degree of symmetry and in some point * in the future this may not be the case... */ tray_calc_window_size(0, 0, &tray_data.scrollbars_data.scroll_base.x, &tray_data.scrollbars_data.scroll_base.y); tray_data.scrollbars_data.scroll_base.x /= 2; tray_data.scrollbars_data.scroll_base.y /= 2; } /************** "main" ***********/ int read_settings(int argc, char **argv) { init_default_settings(); /* Parse 0th pass command line args */ parse_cmdline(argc, argv, 0); /* Parse configuration files */ parse_rc(); /* Parse 1st pass command line args */ parse_cmdline(argc, argv, 1); /* Display some settings */ LOG_TRACE(("stalonetray " VERSION " [ " FEATURE_LIST " ]\n")); LOG_TRACE(("bg_color_str = \"%s\"\n", settings.bg_color_str)); LOG_TRACE(("config_fname = \"%s\"\n", settings.config_fname)); LOG_TRACE(("deco_flags = 0x%x\n", settings.deco_flags)); LOG_TRACE(("display_str = \"%s\"\n", settings.display_str)); LOG_TRACE(("dockapp_mode = %d\n", settings.dockapp_mode)); LOG_TRACE(("full_pmt_search = %d\n", settings.full_pmt_search)); LOG_TRACE(("geometry_str = \"%s\"\n", settings.geometry_str)); LOG_TRACE(("grow_gravity = 0x%x\n", settings.grow_gravity)); LOG_TRACE(("icon_gravity = 0x%x\n", settings.icon_gravity)); LOG_TRACE(("icon_size = %d\n", settings.icon_size)); LOG_TRACE(("log_level = %d\n", settings.log_level)); LOG_TRACE(("max_tray_dims.x = %d\n", settings.max_tray_dims.x)); LOG_TRACE(("max_tray_dims.y = %d\n", settings.max_tray_dims.y)); LOG_TRACE(("min_space_policy = %d\n", settings.min_space_policy)); LOG_TRACE(("need_help = %d\n", settings.need_help)); LOG_TRACE(("parent_bg = %d\n", settings.parent_bg)); LOG_TRACE(("scrollbars_highlight_color_str = \"%s\"\n", settings.scrollbars_highlight_color_str)); LOG_TRACE(("scrollbars_highlight_color.pixel = %ld\n", settings.scrollbars_highlight_color.pixel)); LOG_TRACE(("scrollbars_inc = %d\n", settings.scrollbars_inc)); LOG_TRACE(("scrollbars_mode = %d\n", settings.scrollbars_mode)); LOG_TRACE(("scrollbars_size = %d\n", settings.scrollbars_size)); LOG_TRACE(("shrink_back_mode = %d\n", settings.shrink_back_mode)); LOG_TRACE(("slot_size.x = %d\n", settings.slot_size.x)); LOG_TRACE(("slot_size.y = %d\n", settings.slot_size.y)); LOG_TRACE(("vertical = %d\n", settings.vertical)); LOG_TRACE(("xsync = %d\n", settings.xsync)); return SUCCESS; } ================================================ FILE: src/settings.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * settings.h * Sun, 12 Sep 2004 18:06:08 +0700 * ------------------------------- * settings parser\container * -------------------------------*/ #ifndef _SETTINGS_H_ #define _SETTINGS_H_ #include #include #include "layout.h" /* Default name of configuration file */ #define STALONETRAY_RC "stalonetrayrc" struct WindowClass { char *name; struct WindowClass *next; struct WindowClass *prev; }; struct Settings { /* Flags */ int parent_bg; /* Enable pseudo-transparency using parents' background*/ int deco_flags; /* Decoration flags */ int transparent; /* Enable root-transparency */ int skip_taskbar; /* Remove tray from wm`s taskbar */ int sticky; /* Make tray sticky across desktops/pages */ int xsync; /* Operate on X server syncronously */ int pixmap_bg; /* Is pixmap used for background */ int min_space_policy; /* Use placement that cause minimal grow */ int full_pmt_search; /* Use non-first-match search algorithm */ int vertical; /* Use vertical icon layout */ int shrink_back_mode; /* Keep tray's window size minimal */ int dockapp_mode; /* Activate dockapp mode */ int kludge_flags; /* What kludges to activate */ int scroll_everywhere; /* Whether scrolling is limited to the scrollbars only */ int need_help; /* Print usage and exit */ /* Lists of strings */ struct WindowClass *ignored_classes; /* List of window classes to ignore */ /* Strings */ char *display_str; /* Name of the display */ char *bg_color_str; /* Background color name */ char *scrollbars_highlight_color_str; /* Name of color to highlight scrollbars with. NULL means highlighting is disabled */ char *geometry_str; /* Geometry spec */ char *max_geometry_str; /* Geometry spec */ char *config_fname; /* Path to the configuration file */ char *wnd_type; /* Window type */ char *wnd_layer; /* Window layer */ char *wnd_name; /* Window name (WM_NAME) */ char *bg_pmap_path; /* Background pixmap path */ char *tint_color_str; /* Color used for tinting */ char *remote_click_name; /* Icon name to execute remote click on */ /* Values */ int icon_size; /* Icon size */ struct Point slot_size; /* Grid slot size */ int grow_gravity; /* Icon gravity (interpretation of icon_gravity_str) */ int icon_gravity; /* Grow gravity (interpretation of grow_gravity_str) */ int win_gravity; /* Tray window gravity (computed using grow gravity) */ int bit_gravity; /* Tray window bit gravity (computed using icon_gravity) */ int geom_gravity; /* Tray window gravity when mapping the window (computed using geometry_str) */ int fuzzy_edges; /* Level of edges fuzziness (0 = disabled) */ int tint_level; /* Tinting level (0 = disabled) */ int scrollbars_mode; /* SB_MODE_NONE | SB_MODE_VERT | SB_MODE_HORZ */ int scrollbars_size; /* Size of scrollbar windows in pixels */ int scrollbars_inc; /* Step of scrollbar */ int wm_strut_mode; /* WM strut mode */ struct Point max_tray_dims; /* Maximal tray dimensions */ struct Point max_layout_dims; /* Maximal layout dimensions */ struct Point orig_tray_dims; /* Original tray dimensions */ struct Point remote_click_pos; /* Remote click position */ int remote_click_btn; /* Remote click button */ int remote_click_cnt; /* Remote click count */ XColor tint_color; /* Color used for tinting */ #ifdef DELAY_EMBEDDING_CONFIRMATION int confirmation_delay; #endif XColor bg_color; /* Tray background color */ XColor scrollbars_highlight_color; /* Color to highlight scrollbars with */ int log_level; /* Debug level */ #ifdef _ST_WITH_XINERAMA int monitor; /* Which monitor to display tray in */ #endif }; extern struct Settings settings; /* Read settings from cmd line and configuration file */ int read_settings(int argc, char **argv); /* Interpret all settings that either need an * open display or are interpreted from other * settings */ void interpret_settings(); #endif ================================================ FILE: src/tray.c ================================================ /* ************************************ * vim:tabstop=4:shiftwidth=4:cindent:fen:fdm=syntax * tray.c * Tue, 07 Mar 2006 10:36:10 +0600 * ************************************ * tray functions * ************************************/ #include #include #include #include #ifdef _ST_WITH_XPM #include #endif #include #include "common.h" #include "embed.h" #include "icons.h" #include "image.h" #include "settings.h" #include "tray.h" #include "wmh.h" #include "xutils.h" #ifdef _ST_WITH_NATIVE_KDE #endif #include "debug.h" void tray_init() { tray_data.tray = None; tray_data.hint_win = None; tray_data.dpy = NULL; tray_data.terminated = False; tray_data.bg_pmap = None; tray_data.xa_xrootpmap_id = None; tray_data.xa_xsetroot_id = None; tray_data.kde_tray_old_mode = 0; #ifdef _ST_WITH_XINERAMA tray_data.xinerama_active = False; tray_data.n_monitors = 0; tray_data.monitors = NULL; #endif scrollbars_init(); } #ifdef _ST_WITH_XPM int tray_init_pixmap_bg() { XpmAttributes xpma; Pixmap mask = None; int rc; xpma.valuemask = 0; rc = XpmReadFileToPixmap(tray_data.dpy, tray_data.tray, settings.bg_pmap_path, &tray_data.bg_pmap, &mask, &xpma); if (rc != XpmSuccess) { DIE(("Could not read background pixmap from %s.\n", settings.bg_pmap_path)); return FAILURE; } /* Ignore the mask */ if (mask != None) XFreePixmap(tray_data.dpy, mask); tray_data.bg_pmap_dims.x = xpma.height; tray_data.bg_pmap_dims.y = xpma.width; LOG_TRACE( ("created background pixmap (%dx%d)\n", xpma.width, xpma.height)); return SUCCESS; } #endif /* Mostly from FVWM:fvwm/colorset.c:172 */ Pixmap tray_get_root_pixmap(Atom prop) { Atom type; int format; unsigned long length, after; unsigned char *reteval = NULL; int ret; Pixmap pix = None; Window root_window = XRootWindow(tray_data.dpy, DefaultScreen(tray_data.dpy)); ret = XGetWindowProperty(tray_data.dpy, root_window, prop, 0L, 1L, False, XA_PIXMAP, &type, &format, &length, &after, &reteval); if (x11_ok() && ret == Success && type == XA_PIXMAP && format == 32 && length == 1 && after == 0) pix = (Pixmap)(*(long *)reteval); if (reteval) XFree(reteval); return pix; } /* Originally from FVWM:fvwm/colorset.c:195 */ int tray_update_root_bg_pmap(Pixmap *pmap, int *width, int *height) { unsigned int w = 0, h = 0; int rc = 0; XID dummy; Pixmap pix = None; /* Retrive root image pixmap */ /* Try _XROOTPMAP_ID first */ if (tray_data.xa_xrootpmap_id != None) pix = tray_get_root_pixmap(tray_data.xa_xrootpmap_id); /* Else try _XSETROOT_ID */ if (!pix && tray_data.xa_xsetroot_id != None) pix = tray_get_root_pixmap(tray_data.xa_xsetroot_id); /* Get root pixmap size */ if (pix) rc = XGetGeometry(tray_data.dpy, pix, &dummy, (int *)&dummy, (int *)&dummy, &w, &h, (unsigned int *)&dummy, (unsigned int *)&dummy); if (!x11_ok() || !pix || !rc) { LOG_TRACE(("could not update root background pixmap\n")); return FAILURE; } *pmap = pix; if (width != NULL) *width = w; if (height != NULL) *height = h; LOG_TRACE(("got new root pixmap: 0x%lx (%ix%i)\n", pix, w, h)); return SUCCESS; } /* XXX: most of Pixmaps are not really needed. Use XImages instead. * GCs, XImages and Pixmaps stay allocated during cleanup, valgrind * complains. Who cares? * TODO: move pixmaps/GCs into tray_data and free them during the exit. */ int tray_update_bg(int update_pixmap) { static Pixmap root_pmap = None; static Pixmap bg_pmap = None; static Pixmap final_pmap = None; static GC root_gc = None; static GC bg_gc = None; static GC final_gc = None; static int old_width = -1, old_height = -1; struct Rect clr; /* clipping rectangle */ struct Rect clr_loc; /* clipping rectangle in local coords */ XImage *bg_img; int bg_pmap_updated = False; #define make_gc(pmap, gc) \ do { \ XGCValues gcv; \ if (gc != None) XFreeGC(tray_data.dpy, gc); \ gcv.graphics_exposures = False; \ gc = XCreateGC(tray_data.dpy, pmap, GCGraphicsExposures, &gcv); \ } while (0) #define make_pmap(pmap) \ do { \ if (pmap != None) XFreePixmap(tray_data.dpy, pmap); \ pmap = XCreatePixmap(tray_data.dpy, tray_data.tray, \ tray_data.xsh.width, tray_data.xsh.height, \ DefaultDepth(tray_data.dpy, DefaultScreen(tray_data.dpy))); \ } while (0) #define recreate_pixmap(pmap, gc) \ do { \ make_pmap(pmap); \ make_gc(pmap, gc); \ } while (0) /* No need to update background if it is a color one */ if (!settings.transparent && !settings.pixmap_bg) return SUCCESS; /* Calculate clipping rect */ clr.x = cutoff(tray_data.xsh.x, 0, tray_data.root_wnd.width); clr.y = cutoff(tray_data.xsh.y, 0, tray_data.root_wnd.height); clr.w = cutoff(tray_data.xsh.x + tray_data.xsh.width, 0, tray_data.root_wnd.width) - clr.x; clr.h = cutoff(tray_data.xsh.y + tray_data.xsh.height, 0, tray_data.root_wnd.height) - clr.y; /* There's no need to update background if tray is not visible */ /* TODO: visibility is better to be tracked using some events */ if (clr.w == 0 || clr.h == 0) return SUCCESS; /* Calculate local clipping rect */ clr_loc.x = clr.x - tray_data.xsh.x; clr_loc.y = clr.y - tray_data.xsh.y; clr_loc.w = clr.w; clr_loc.h = clr.h; if (old_width != tray_data.xsh.width || old_height != tray_data.xsh.height || final_pmap == None) recreate_pixmap(final_pmap, final_gc); /* Update root pixmap if asked and necessary */ if ((root_pmap == None || update_pixmap) && (settings.transparent || settings.fuzzy_edges)) { if (!tray_update_root_bg_pmap(&root_pmap, NULL, NULL)) { /* More gracefull solution */ LOG_TRACE(("still waiting for root pixmap\n")); return SUCCESS; } else make_gc(root_pmap, root_gc); } /* We don't have to update background if only window position has * changed unless we depend on root pixmap */ if (tray_data.xsh.width == old_width && tray_data.xsh.width == old_height && !settings.transparent && !settings.fuzzy_edges) { return SUCCESS; } /* If pixmap bg is used, bg_pmap holds tinted and tiled background pixmap, * so there's no need to update it unless window size has changed */ if (settings.pixmap_bg && (bg_pmap == None || old_width != tray_data.xsh.width || old_height != tray_data.xsh.height)) { int i, j; recreate_pixmap(bg_pmap, bg_gc); for (i = 0; i < tray_data.xsh.width / tray_data.bg_pmap_dims.x + 1; i++) for (j = 0; j < tray_data.xsh.height / tray_data.bg_pmap_dims.y + 1; j++) { XCopyArea(tray_data.dpy, tray_data.bg_pmap, bg_pmap, bg_gc, 0, 0, tray_data.bg_pmap_dims.x, tray_data.bg_pmap_dims.y, i * tray_data.bg_pmap_dims.x, j * tray_data.bg_pmap_dims.y); } bg_pmap_updated = True; } else /* If root transparency is enabled, it is neccessary to copy portion of * root pixmap under the window (root_pmap) to bg_pmap */ /* XXX: must correctly work around situations when bg pixmap is smaller than root window (but how?) */ if (settings.transparent) { recreate_pixmap(bg_pmap, bg_gc); XCopyArea(tray_data.dpy, root_pmap, bg_pmap, bg_gc, tray_data.xsh.x, tray_data.xsh.y, tray_data.xsh.width, tray_data.xsh.height, 0, 0); } /* Create an XImage from bg_pmap */ bg_img = XGetImage(tray_data.dpy, bg_pmap, 0, 0, tray_data.xsh.width, tray_data.xsh.height, XAllPlanes(), ZPixmap); if (bg_img == NULL) return FAILURE; /* Tint the image if necessary. If bg_pmap was not updated, tinting * is not needed, since it has been already done */ if (settings.tint_level && ((settings.pixmap_bg && bg_pmap_updated) || settings.transparent)) { image_tint(bg_img, &settings.tint_color, settings.tint_level); XPutImage(tray_data.dpy, bg_pmap, bg_gc, bg_img, 0, 0, 0, 0, tray_data.xsh.width, tray_data.xsh.height); bg_pmap_updated = False; } /* Apply fuzzy edges */ /* XXX: THIS IS UGLY */ if (settings.fuzzy_edges) { static CARD8 *alpha_mask = NULL; /* Portion of root pixmap under the tray */ XImage *root_img = XGetImage(tray_data.dpy, root_pmap, clr.x, clr.y, clr.w, clr.h, XAllPlanes(), ZPixmap); /* Proxy structure to work on */ XImage *tmp_img = NULL; static Pixmap tmp_pmap = None; static GC tmp_gc = None; if (root_img == NULL) { LOG_ERROR( ("Failed to get image of root pixmap under tray window\n")); LOG_TRACE(("Clipping rectangle: %dx%d+%d+%d\n", clr.w, clr.h, clr.x, clr.y)); return x11_ok(); } /* Alpha mask needs to be updated only on size changes */ if (old_width != tray_data.xsh.width || old_height != tray_data.xsh.height) { recreate_pixmap(tmp_pmap, tmp_gc); if (alpha_mask != NULL) free(alpha_mask); alpha_mask = image_create_alpha_mask(settings.fuzzy_edges, tray_data.xsh.width, tray_data.xsh.height); } XPutImage(tray_data.dpy, tmp_pmap, tmp_gc, root_img, clr_loc.x, clr_loc.y, 0, 0, clr_loc.w, clr_loc.h); tmp_img = XGetImage(tray_data.dpy, tmp_pmap, 0, 0, tray_data.xsh.width, tray_data.xsh.height, XAllPlanes(), ZPixmap); if (alpha_mask != NULL && tmp_img != NULL) image_compose(bg_img, tmp_img, alpha_mask); XDestroyImage(root_img); if (tmp_img != NULL) XDestroyImage(tmp_img); } XPutImage(tray_data.dpy, final_pmap, final_gc, bg_img, 0, 0, 0, 0, tray_data.xsh.width, tray_data.xsh.height); XSetWindowBackgroundPixmap(tray_data.dpy, tray_data.tray, final_pmap); XDestroyImage(bg_img); old_width = tray_data.xsh.width; old_height = tray_data.xsh.height; RETURN_STATUS(x11_ok()); } void tray_refresh_window(int exposures) { LOG_TRACE(("refreshing tray window\n")); icon_list_forall(&embedder_refresh); x11_refresh_window(tray_data.dpy, tray_data.tray, tray_data.xsh.width, tray_data.xsh.height, exposures); scrollbars_refresh(exposures); } int tray_calc_window_size( int base_width, int base_height, int *wnd_width, int *wnd_height) { *wnd_width = base_width; *wnd_height = base_height; if (settings.scrollbars_mode & SB_MODE_HORZ) *wnd_width += settings.scrollbars_size * 2; if (settings.scrollbars_mode & SB_MODE_VERT) *wnd_height += settings.scrollbars_size * 2; return SUCCESS; } int tray_calc_tray_area_size( int wnd_width, int wnd_height, int *base_width, int *base_height) { *base_width = wnd_width; *base_height = wnd_height; if (settings.scrollbars_mode & SB_MODE_HORZ) *base_width -= settings.scrollbars_size * 2; if (settings.scrollbars_mode & SB_MODE_VERT) *base_height -= settings.scrollbars_size * 2; return SUCCESS; } int tray_update_window_strut() { /* Set window strut */ if (settings.wm_strut_mode != WM_STRUT_NONE) { int strut_mode; if (settings.wm_strut_mode == WM_STRUT_AUTO) { /* Do autodetection: if some edge of tray is adjacent to one * of screen edges, we could set window strut to that */ int h_strut_mode, v_strut_mode; int p_strut_mode, s_strut_mode; h_strut_mode = (tray_data.xsh.x == 0 ? WM_STRUT_LFT : (tray_data.xsh.x + tray_data.xsh.width == tray_data.root_wnd.width ? WM_STRUT_RHT : WM_STRUT_NONE)); v_strut_mode = (tray_data.xsh.y == 0 ? WM_STRUT_TOP : (tray_data.xsh.x + tray_data.xsh.height == tray_data.root_wnd.height ? WM_STRUT_BOT : WM_STRUT_NONE)); /* If tray is vertical, horizontal strut mode has higher priority, * else vertical strut mode has higher priority */ if (settings.vertical) { p_strut_mode = h_strut_mode; s_strut_mode = v_strut_mode; } else { p_strut_mode = v_strut_mode; s_strut_mode = h_strut_mode; } if (p_strut_mode != WM_STRUT_NONE) strut_mode = p_strut_mode; else strut_mode = s_strut_mode; } else strut_mode = settings.wm_strut_mode; LOG_TRACE(("computed final strut mode: %d\n", strut_mode)); /* Update respective window hint */ if (strut_mode != WM_STRUT_NONE) { wm_strut_t wm_strut; int base_idx; memset(wm_strut, 0, sizeof(wm_strut)); LOG_TRACE( ("current tray geometry: %dx%d+%d+%d\n", tray_data.xsh.width, tray_data.xsh.height, tray_data.xsh.x, tray_data.xsh.y)); if (strut_mode == WM_STRUT_TOP || strut_mode == WM_STRUT_BOT) { if (strut_mode == WM_STRUT_TOP) { base_idx = WM_STRUT_IDX_TOP; wm_strut[WM_STRUT_IDX_TOP] = tray_data.xsh.y + tray_data.xsh.height; } else { base_idx = WM_STRUT_IDX_BOT; wm_strut[WM_STRUT_IDX_BOT] = tray_data.root_wnd.height - tray_data.xsh.y; } wm_strut[WM_STRUT_IDX_START(base_idx)] = tray_data.xsh.x; wm_strut[WM_STRUT_IDX_END(base_idx)] = tray_data.xsh.x + tray_data.xsh.width - 1; } else { if (strut_mode == WM_STRUT_LFT) { base_idx = WM_STRUT_IDX_LFT; wm_strut[WM_STRUT_IDX_LFT] = tray_data.xsh.x + tray_data.xsh.width; } else { base_idx = WM_STRUT_IDX_RHT; wm_strut[WM_STRUT_IDX_RHT] = tray_data.root_wnd.width - tray_data.xsh.x; } wm_strut[WM_STRUT_IDX_START(base_idx)] = tray_data.xsh.y; wm_strut[WM_STRUT_IDX_END(base_idx)] = tray_data.xsh.y + tray_data.xsh.height - 1; } { int i; for (i = 0; i < _NET_WM_STRUT_PARTIAL_SZ; i++) LOG_TRACE(("computed hints [%d] = %lu\n", i, wm_strut[i])); } ewmh_set_window_strut(tray_data.dpy, tray_data.tray, wm_strut); } } return SUCCESS; } int tray_update_window_props() { XSizeHints xsh; int cur_base_width, cur_base_height; int new_width, new_height; int layout_width, layout_height; /* Phase 1: calculate new tray window dimensions. * Algorithm summary: * new_dims = * if (layout_dims > max_dims) max_dims; * else if ((shrink_back && layout_dims > orig_dims) || * (layout_dims > current_dims)) layout_dims; * else if (shrink_back) orig_dims; * else current_dims; */ x11_get_window_size(tray_data.dpy, tray_data.tray, &tray_data.xsh.width, &tray_data.xsh.height); layout_get_size(&layout_width, &layout_height); LOG_TRACE(("layout geometry: %dx%d\n", layout_width, layout_height)); tray_calc_tray_area_size(tray_data.xsh.width, tray_data.xsh.height, &cur_base_width, &cur_base_height); LOG_TRACE( ("base tray geometry: %dx%d\n", cur_base_width, cur_base_height)); LOG_TRACE(("orig tray geometry: %dx%d\n", settings.orig_tray_dims.x, settings.orig_tray_dims.y)); LOG_TRACE(("max tray geometry: %dx%d\n", settings.max_tray_dims.x, settings.max_tray_dims.y)); #define CALC_DIM(tgt, cur, layout, max, orig) \ if (layout > max) \ tgt = max; \ else if ((settings.shrink_back_mode && layout > orig) || layout > cur) \ tgt = layout; \ else if (settings.shrink_back_mode) \ tgt = orig; \ else \ tgt = cur; CALC_DIM(new_width, cur_base_width, layout_width, settings.max_tray_dims.x, settings.orig_tray_dims.x); CALC_DIM(new_height, cur_base_height, layout_height, settings.max_tray_dims.y, settings.orig_tray_dims.y); LOG_TRACE(("new base tray geometry: %dx%d\n", new_width, new_height)); tray_calc_window_size(new_width, new_height, &new_width, &new_height); LOG_TRACE(("new tray geometry: %dx%d\n", new_width, new_height)); #if 1 /* Not sure if this is really necessary */ xsh.x = tray_data.xsh.x; xsh.y = tray_data.xsh.y; xsh.width = new_width; xsh.height = new_height; #endif xsh.min_width = new_width; xsh.min_height = new_height; xsh.max_width = new_width; xsh.max_height = new_height; xsh.width_inc = settings.slot_size.x; xsh.height_inc = settings.slot_size.y; tray_calc_window_size(0, 0, &xsh.base_width, &xsh.base_height); xsh.win_gravity = NorthWestGravity; xsh.flags = tray_data.xsh.flags; XSetWMNormalHints(tray_data.dpy, tray_data.tray, &xsh); /* Phase 2: set new window size * This check helps to avod extra (erroneous) moves of the window when * geometry changes are not updated yet, but tray_update_window_props() was * called once again */ if (new_width != tray_data.xsh.width || new_height != tray_data.xsh.height) { /* Apparently, not every WM (hello, WindowMaker!) handles gravity the * way I have expected (i.e. using it to calculate reference point as * described in ICCM/WM specs). Perhaps, I was dreaming. So, prior to * resizing trays window, it is necessary to recalculate window * absolute position and shift it according to grow gravity settings */ x11_get_window_abs_coords( tray_data.dpy, tray_data.tray, &tray_data.xsh.x, &tray_data.xsh.y); LOG_TRACE(("old tray window geometry: %dx%d+%d+%d\n", new_width, new_height, tray_data.xsh.x, tray_data.xsh.y)); if (settings.grow_gravity & GRAV_E) tray_data.xsh.x -= new_width - tray_data.xsh.width; else if (!(settings.grow_gravity & GRAV_H)) tray_data.xsh.x -= (new_width - tray_data.xsh.width) / 2; if (settings.grow_gravity & GRAV_S) tray_data.xsh.y -= new_height - tray_data.xsh.height; else if (!(settings.grow_gravity & GRAV_V)) tray_data.xsh.y -= (new_height - tray_data.xsh.height) / 2; tray_data.xsh.width = new_width; tray_data.xsh.height = new_height; LOG_TRACE(("new tray window geometry: %dx%d+%d+%d\n", new_width, new_height, tray_data.xsh.x, tray_data.xsh.y)); XResizeWindow(tray_data.dpy, tray_data.tray, new_width, new_height); XMoveWindow( tray_data.dpy, tray_data.tray, tray_data.xsh.x, tray_data.xsh.y); if (!x11_ok()) { LOG_TRACE(("could not update tray window geometry\n")); return FAILURE; } } else { /* XXX: Why do we need this again? */ XResizeWindow(tray_data.dpy, tray_data.tray, tray_data.xsh.width, tray_data.xsh.height); } tray_update_window_strut(); scrollbars_update(); return SUCCESS; } void tray_create_window(int argc, char **argv) { XTextProperty wm_name; XSetWindowAttributes xswa; XClassHint xch; XWMHints xwmh; Atom net_system_tray_orientation; Atom orient; Atom protocols_atoms[3]; /* Create some atoms */ tray_data.xa_wm_delete_window = XInternAtom(tray_data.dpy, "WM_DELETE_WINDOW", False); tray_data.xa_net_wm_ping = XInternAtom(tray_data.dpy, "_NET_WM_PING", False); tray_data.xa_wm_take_focus = XInternAtom(tray_data.dpy, "WM_TAKE_FOCUS", False); tray_data.xa_wm_protocols = XInternAtom(tray_data.dpy, "WM_PROTOCOLS", False); tray_data.xa_kde_net_system_tray_windows = XInternAtom(tray_data.dpy, "_KDE_NET_SYSTEM_TRAY_WINDOWS", False); tray_data.xa_net_client_list = XInternAtom(tray_data.dpy, "_NET_CLIENT_LIST", False); /* Create tray window */ tray_data.tray = XCreateSimpleWindow(tray_data.dpy, DefaultRootWindow(tray_data.dpy), tray_data.xsh.x, tray_data.xsh.y, tray_data.xsh.width, tray_data.xsh.height, 0, settings.bg_color.pixel, settings.bg_color.pixel); LOG_TRACE(("created tray window: 0x%lx\n", tray_data.tray)); if (settings.dockapp_mode == DOCKAPP_WMAKER) { tray_data.hint_win = XCreateSimpleWindow(tray_data.dpy, DefaultRootWindow(tray_data.dpy), 0, 0, 1, 1, 0, settings.bg_color.pixel, settings.bg_color.pixel); LOG_TRACE(("created hint_win window: 0x%lx\n", tray_data.hint_win)); } /* Set tray window properties */ xswa.bit_gravity = settings.bit_gravity; xswa.win_gravity = settings.win_gravity; xswa.backing_store = settings.parent_bg ? NotUseful : WhenMapped; XChangeWindowAttributes(tray_data.dpy, tray_data.tray, CWBitGravity | CWWinGravity | CWBackingStore, &xswa); { /* XXX: use XStoreName ?*/ int numtries = 0; /* First, try user-supplied value */ while (1) { if (XmbTextListToTextProperty( tray_data.dpy, &settings.wnd_name, 1, XTextStyle, &wm_name) != Success) /* Retry with default value */ settings.wnd_name = PROGNAME; else break; if (numtries++ > 1) DIE(("Invalid window name \"%s\"\n", settings.wnd_name)); } } XSetWMName(tray_data.dpy, tray_data.tray, &wm_name); XFree(wm_name.value); /* Setup class hints */ xch.res_class = PROGNAME; xch.res_name = PROGNAME; /* Setup window manager hints */ xwmh.flags = StateHint | InputHint; xwmh.input = False; xwmh.initial_state = settings.dockapp_mode != DOCKAPP_NONE ? WithdrawnState : NormalState; /* Apply hints */ XSetWMHints(tray_data.dpy, tray_data.tray, &xwmh); XSetClassHint(tray_data.dpy, tray_data.tray, &xch); XSetWMNormalHints(tray_data.dpy, tray_data.tray, &tray_data.xsh); XSetCommand(tray_data.dpy, tray_data.tray, argv, argc); /* Set properties of hint window if WindowMaker dockapp mode enabled */ if (settings.dockapp_mode == DOCKAPP_WMAKER) { xwmh.flags |= IconWindowHint | IconPositionHint | WindowGroupHint; xwmh.initial_state = WithdrawnState; xwmh.icon_x = tray_data.xsh.x; xwmh.icon_y = tray_data.xsh.y; xwmh.icon_window = tray_data.tray; xwmh.window_group = tray_data.hint_win; XSetClassHint(tray_data.dpy, tray_data.hint_win, &xch); XSetWMHints(tray_data.dpy, tray_data.hint_win, &xwmh); } /* v0.2 tray protocol support */ orient = settings.vertical ? _NET_SYSTEM_TRAY_ORIENTATION_HORZ : _NET_SYSTEM_TRAY_ORIENTATION_VERT; net_system_tray_orientation = XInternAtom(tray_data.dpy, TRAY_ORIENTATION_ATOM, False); XChangeProperty(tray_data.dpy, tray_data.tray, net_system_tray_orientation, net_system_tray_orientation, 32, PropModeReplace, (unsigned char *)&orient, 1); /* Ask X server / WM to report certain events */ protocols_atoms[0] = tray_data.xa_wm_delete_window; protocols_atoms[1] = tray_data.xa_wm_take_focus; protocols_atoms[2] = tray_data.xa_net_wm_ping; XSetWMProtocols(tray_data.dpy, tray_data.tray, protocols_atoms, 3); XSelectInput(tray_data.dpy, tray_data.tray, StructureNotifyMask | FocusChangeMask | PropertyChangeMask | ExposureMask); x11_extend_root_event_mask(tray_data.dpy, PropertyChangeMask); scrollbars_create(); /* Set tray window background if necessary */ #ifdef _ST_WITH_XPM if (settings.pixmap_bg) tray_init_pixmap_bg(); #endif if (settings.parent_bg) XSetWindowBackgroundPixmap( tray_data.dpy, tray_data.tray, ParentRelative); else if (settings.transparent || settings.fuzzy_edges) { tray_data.xa_xrootpmap_id = XInternAtom(tray_data.dpy, "_XROOTPMAP_ID", False); tray_data.xa_xsetroot_id = XInternAtom(tray_data.dpy, "_XSETROOT_ID", False); } tray_update_bg(True); } void tray_create_phony_window() { /* Create fake tray window */ tray_data.tray = XCreateSimpleWindow( tray_data.dpy, DefaultRootWindow(tray_data.dpy), 0, 0, 1, 1, 0, 0, 0); /* Select for PropertyNotify so that x11_get_server_timestamp() works */ XSelectInput(tray_data.dpy, tray_data.tray, PropertyChangeMask); } int tray_set_wm_hints() { int mwm_decor = 0; if (settings.deco_flags & DECO_TITLE) mwm_decor |= MWM_DECOR_TITLE | MWM_DECOR_MENU; if (settings.deco_flags & DECO_BORDER) mwm_decor |= MWM_DECOR_RESIZEH | MWM_DECOR_BORDER; mwm_set_hints(tray_data.dpy, tray_data.tray, mwm_decor, MWM_FUNC_ALL); if (settings.sticky) { ewmh_add_window_state( tray_data.dpy, tray_data.tray, _NET_WM_STATE_STICKY); ewmh_set_window_atom32( tray_data.dpy, tray_data.tray, _NET_WM_DESKTOP, 0xFFFFFFFF); } if (settings.skip_taskbar) ewmh_add_window_state( tray_data.dpy, tray_data.tray, _NET_WM_STATE_SKIP_TASKBAR); if (settings.wnd_layer != NULL) ewmh_add_window_state( tray_data.dpy, tray_data.tray, settings.wnd_layer); if (strcmp(settings.wnd_type, _NET_WM_WINDOW_TYPE_NORMAL) != 0) ewmh_add_window_type(tray_data.dpy, tray_data.tray, settings.wnd_type); /* Alwas add NORMAL window type for WM that do not support (some) special * types */ ewmh_add_window_type( tray_data.dpy, tray_data.tray, _NET_WM_WINDOW_TYPE_NORMAL); return SUCCESS; } void tray_init_selection_atoms() { static char *tray_sel_atom_name = NULL; /* Obtain selection atom name basing on current screen number */ if (tray_sel_atom_name == NULL) { tray_sel_atom_name = (char *)malloc(strlen(TRAY_SEL_ATOM) + 10); if (tray_sel_atom_name == NULL) DIE_OOM(("could not allocate memory for selection atom name\n")); snprintf(tray_sel_atom_name, strlen(TRAY_SEL_ATOM) + 10, "%s%u", TRAY_SEL_ATOM, DefaultScreen(tray_data.dpy)); } LOG_TRACE(("tray_sel_atom_name=%s\n", tray_sel_atom_name)); /* Initialize atom values */ tray_data.xa_tray_selection = XInternAtom(tray_data.dpy, tray_sel_atom_name, False); tray_data.xa_tray_opcode = XInternAtom(tray_data.dpy, "_NET_SYSTEM_TRAY_OPCODE", False); tray_data.xa_tray_data = XInternAtom(tray_data.dpy, "_NET_SYSTEM_TRAY_MESSAGE_DATA", False); } void tray_acquire_selection() { Time timestamp = x11_get_server_timestamp(tray_data.dpy, tray_data.tray); tray_init_selection_atoms(); /* Save old selection owner */ tray_data.old_selection_owner = XGetSelectionOwner(tray_data.dpy, tray_data.xa_tray_selection); LOG_TRACE(("old selection owner: 0x%lx\n", tray_data.old_selection_owner)); /* Acquire selection */ XSetSelectionOwner( tray_data.dpy, tray_data.xa_tray_selection, tray_data.tray, timestamp); /* Check if we have really got the selection */ if (XGetSelectionOwner(tray_data.dpy, tray_data.xa_tray_selection) != tray_data.tray) { DIE(("could not set selection owner.\nMay be another (greedy) tray " "running?\n")); } else { tray_data.is_active = True; LOG_TRACE(("ok, got _NET_SYSTEM_TRAY selection\n")); } /* Send the message notifying about new MANAGER */ x11_send_client_msg32(tray_data.dpy, DefaultRootWindow(tray_data.dpy), DefaultRootWindow(tray_data.dpy), XInternAtom(tray_data.dpy, "MANAGER", False), timestamp, tray_data.xa_tray_selection, tray_data.tray, 0, 0); } void tray_show_window() { tray_set_wm_hints(); tray_update_window_props(); XMapRaised(tray_data.dpy, tray_data.tray); if (settings.dockapp_mode == DOCKAPP_NONE) XMoveWindow( tray_data.dpy, tray_data.tray, tray_data.xsh.x, tray_data.xsh.y); if (settings.dockapp_mode == DOCKAPP_WMAKER) XMapWindow(tray_data.dpy, tray_data.hint_win); /* XXX: I do not why, but for some WM it is * required to set hints / window properties * after and before window creation */ /* TODO: check if this is really necessary */ tray_set_wm_hints(); tray_update_window_props(); } ================================================ FILE: src/tray.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * tray.h * Wed, 29 Sep 2004 23:10:02 +0700 * ------------------------------- * Common tray routines * -------------------------------*/ #ifndef _TRAY_H_ #define _TRAY_H_ #include #include #include #include #include "common.h" #include "icons.h" #include "scrollbars.h" #include "xembed.h" #ifdef _ST_WITH_XINERAMA #include #endif /* Tray opcode messages from System Tray Protocol Specification * http:freedesktop.org/Standards/systemtray-spec/systemtray-spec-0.2.html */ #define SYSTEM_TRAY_REQUEST_DOCK 0 /* These two are unused */ #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 /* Custom message: remote control */ #define STALONE_TRAY_REMOTE_CONTROL 0xFFFD /* Custom message: request for status */ #define STALONE_TRAY_STATUS_REQUESTED 0xFFFE /* Custom message: confirmation of embedding */ #define STALONE_TRAY_DOCK_CONFIRMED 0xFFFF /* Name of tray selection atom */ #define TRAY_SEL_ATOM "_NET_SYSTEM_TRAY_S" /* Name of tray orientation atom*/ #define TRAY_ORIENTATION_ATOM "_NET_SYSTEM_TRAY_ORIENTATION" /* Name of tray orientation atom*/ #define STALONETRAY_REMOTE_ATOM "STALONETRAY_REMOTE" /* Values of tray orientation property */ #define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0 #define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1 /* Window decoration flags */ #define DECO_BORDER (1 << 0) #define DECO_TITLE (1 << 1) #define DECO_NONE 0 #define DECO_ALL (DECO_BORDER | DECO_TITLE) /* WM struts flags */ #define WM_STRUT_NONE 0 #define WM_STRUT_LFT 1 #define WM_STRUT_RHT 2 #define WM_STRUT_TOP 3 #define WM_STRUT_BOT 4 #define WM_STRUT_AUTO 5 /* Dockapp modes */ #define DOCKAPP_NONE 0 #define DOCKAPP_SIMPLE 1 #define DOCKAPP_WMAKER 2 /* Kludge flags */ #define KLUDGE_FIX_WND_SIZE (1L << 1) #define KLUDGE_FIX_WND_POS (1L << 2) #define KLUDGE_USE_ICONS_HINTS (1L << 3) #define KLUDGE_FORCE_ICONS_SIZE (1L << 4) /* Remote click constants */ #define REMOTE_CLICK_BTN_DEFAULT Button1 #define REMOTE_CLICK_POS_DEFAULT INT_MAX #define REMOTE_CLICK_CNT_DEFAULT 1 /* Structure to hold all tray data */ struct TrayData { /* General */ Window tray; /* ID of tray window */ Window hint_win; /* ID of icon window */ Display *dpy; /* Display pointer */ XSizeHints xsh; /* Size & position of the tray window */ XSizeHints root_wnd; /* Size & position :) of the root window */ Window old_selection_owner; /* Old owner of tray selection */ int terminated; /* Exit flag */ int is_active; /* Is the tray active? */ int is_reparented; /* Was the tray reparented in smth like FvwmButtons ? */ int kde_tray_old_mode; /* Use legacy scheme to handle KDE icons via MapNotify */ /* Atoms */ Atom xa_tray_selection; /* Atom: _NET_SYSTEM_TRAY_SELECTION_S */ Atom xa_tray_opcode; /* Atom: _NET_SYSTEM_TRAY_MESSAGE_OPCODE */ Atom xa_tray_data; /* Atom: _NET_SYSTEM_TRAY_MESSAGE_DATA */ Atom xa_wm_protocols; /* Atom: WM_PROTOCOLS */ Atom xa_wm_delete_window; /* Atom: WM_DELETE_WINDOW */ Atom xa_net_wm_ping; /* Atom: WM_PING */ Atom xa_wm_take_focus; /* Atom: WM_TAKE_FOCUS */ Atom xa_kde_net_system_tray_windows; /* Atom: _KDE_NET_SYSTEM_TRAY_WINDOWS */ Atom xa_net_client_list; /* Atom: _NET_CLIENT_LIST */ /* Background pixmap */ Atom xa_xrootpmap_id; /* Atom: _XROOTPMAP_ID */ Atom xa_xsetroot_id; /* Atom: _XSETROOT_ID */ Pixmap bg_pmap; /* Pixmap for tray background */ struct Point bg_pmap_dims; /* Background pixmap dimensions */ #ifdef _ST_WITH_XINERAMA /* Xinerama data */ int xinerama_active; /* Is Xinerama active? */ int n_monitors; /* Number of Xinerama screens */ XineramaScreenInfo *monitors; /* Xinerama screens info */ #endif /* XEMBED data */ struct XEMBEDData xembed_data; /* XEMBED data */ /* Scrollbar data */ struct ScrollbarsData scrollbars_data; }; extern struct TrayData tray_data; /* Initialize all tray data structures */ void tray_init(); /* Create tray window */ void tray_create_window(int argc, char **argv); /* Create phony tray window so that certain x11_ calls work */ void tray_create_phony_window(); /* Initialize tray selection atoms */ void tray_init_selection_atoms(); /* Acquire tray selection */ void tray_acquire_selection(); /* Show tray window */ void tray_show_window(); /* Refresh tray window */ void tray_refresh_window(int exposures); /* Update tray background (and pixmap, if update_pixmap is true) */ int tray_update_bg(int update_pixmap); /* Calculate tray window size given the size of icon area in pixels. */ int tray_calc_window_size( int base_width, int base_height, int *new_width, int *new_height); /* Calculate size of icon area given the tray window size in pixels. */ int tray_calc_tray_area_size( int wnd_width, int wnd_height, int *base_width, int *base_height); /* Update window struts (if enabled) */ int tray_update_window_strut(); /* Update tray window size and hints */ int tray_update_window_props(); /* Set tray window WM hints */ int tray_set_wm_hints(); #endif ================================================ FILE: src/wmh.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * ewmh.c * Thu, 30 Mar 2006 23:15:37 +0700 * ------------------------------- * EWMH/MWM support * ------------------------------- */ #include #include #include "common.h" #include "debug.h" #include "wmh.h" #include "xutils.h" /* Structure for Motif WM hints */ typedef struct { unsigned long flags; unsigned long functions; unsigned long decorations; long inputMode; unsigned long status; } PropMotifWmHints; /* Bit flags for fields of MWM hints data structure */ #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) /* Number of CARD32 entries in MWM hints data structure */ #define PROP_MOTIF_WM_HINTS_ELEMENTS 5 /* Check if WM that supports EWMH hints is present on given display */ int ewmh_wm_present(Display *dpy) { Window *check_win, *check_win_self_ref; unsigned long cw_len = 0, cwsr_len = 0; int rc = False; /* see _NET_SUPPORTING_WM_CHECK in the WM spec. */ rc = x11_get_root_winlist_prop(dpy, XInternAtom(dpy, _NET_SUPPORTING_WM_CHECK, False), (unsigned char **)&check_win, &cw_len); if (x11_ok() && rc && cw_len == 1) { LOG_TRACE(("_NET_SUPPORTING_WM_CHECK (root) = 0x%lx\n", check_win[0])); x11_get_window_prop32(dpy, check_win[0], XInternAtom(dpy, _NET_SUPPORTING_WM_CHECK, False), XA_WINDOW, (unsigned char **)&check_win_self_ref, &cwsr_len); rc = (x11_ok() && rc && cwsr_len == 1 && check_win[0] == check_win_self_ref[0]); LOG_TRACE(("_NET_SUPPORTING_WM_CHECK (self reference) = 0x%lx\n", check_win[0])); } if (cw_len != 0) XFree(check_win); if (cwsr_len != 0) XFree(check_win_self_ref); LOG_TRACE(("EWMH WM %sdetected\n", rc ? "" : "not ")); return rc; } /* Add EWMH window state for the given window */ int ewmh_add_window_state(Display *dpy, Window wnd, char *state) { Atom prop; Atom atom; XWindowAttributes xwa; int rc; prop = XInternAtom(dpy, "_NET_WM_STATE", False); atom = XInternAtom(dpy, state, False); LOG_TRACE(("adding state %s to window 0x%lx\n", state, atom)); /* Ping the window and get its state */ rc = XGetWindowAttributes(dpy, wnd, &xwa); if (!x11_ok() || !rc) return FAILURE; if (xwa.map_state != IsUnmapped && ewmh_wm_present(dpy)) { /* For mapped windows, ask WM (if it is here) to add the window state */ rc = x11_send_client_msg32( dpy, xwa.root, wnd, prop, _NET_WM_STATE_ADD, atom, 0, 0, 0); } else { /* Else, alter the window state atom value ourselves */ rc = XChangeProperty(dpy, wnd, prop, XA_ATOM, 32, PropModeAppend, (unsigned char *)&atom, 1); rc = x11_ok() && rc; } return rc; } /* Add EWMH window type for the given window */ int ewmh_add_window_type(Display *dpy, Window wnd, char *type) { Atom prop; Atom atom; prop = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); atom = XInternAtom(dpy, type, False); LOG_TRACE(("adding type %s to window 0x%lx\n", type, atom)); /* Update property value (append) */ XChangeProperty(dpy, wnd, prop, XA_ATOM, 32, PropModeAppend, (unsigned char *)&atom, 1); return x11_ok(); } /* Set data for _NET_WM_STRUT{,_PARTIAL} hints */ int ewmh_set_window_strut(Display *dpy, Window wnd, wm_strut_t wm_strut) { Atom prop_strut; Atom prop_strut_partial; prop_strut = XInternAtom(dpy, _NET_WM_STRUT, False); prop_strut_partial = XInternAtom(dpy, _NET_WM_STRUT_PARTIAL, False); XChangeProperty(dpy, wnd, prop_strut, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)wm_strut, _NET_WM_STRUT_SZ); XChangeProperty(dpy, wnd, prop_strut_partial, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)wm_strut, _NET_WM_STRUT_PARTIAL_SZ); return x11_ok(); } /* Set CARD32 value of EWMH atom for a given window */ int ewmh_set_window_atom32( Display *dpy, Window wnd, char *prop_name, CARD32 value) { Atom prop; XWindowAttributes xwa; int rc; prop = XInternAtom(dpy, prop_name, False); LOG_TRACE(("0x%lx: setting atom %s to 0x%x\n", wnd, prop_name, value)); /* Ping the window and get its state */ rc = XGetWindowAttributes(dpy, wnd, &xwa); if (!x11_ok() || !rc) return FAILURE; if (xwa.map_state != IsUnmapped && ewmh_wm_present(dpy)) { /* For mapped windows, ask WM (if it is here) to add the window state */ return x11_send_client_msg32(dpy, DefaultRootWindow(dpy), wnd, prop, value, 2 /* source indication */, 0, 0, 0); } else { Atom atom = value; /* Else, alter the window state atom value ourselves */ XChangeProperty(dpy, wnd, prop, XA_ATOM, 32, PropModeAppend, (unsigned char *)&atom, 1); return x11_ok(); } } /* Set MWM hints */ int mwm_set_hints(Display *dpy, Window wnd, unsigned long decorations, unsigned long functions) { PropMotifWmHints *prop = NULL, new_prop; int act_fmt; unsigned long nitems, bytes_after; static Atom atom = None, act_type; /* Check if WM supports Motif WM hints */ if (atom == None) atom = XInternAtom(dpy, "_MOTIF_WM_HINTS", False); if (atom == None) return FAILURE; /* Get current hints */ XGetWindowProperty(dpy, wnd, atom, 0, 5, False, atom, &act_type, &act_fmt, &nitems, &bytes_after, (unsigned char **)&prop); if ((act_type == None && act_fmt == 0 && bytes_after == 0) || nitems == 0) { /* Hints are either not set or have some other type. * Reset all values. */ memset(&new_prop, 0, sizeof(PropMotifWmHints)); if (prop != NULL) XFree(prop); prop = &new_prop; } else if (prop != NULL) { /* Copy value */ new_prop = *prop; XFree(prop); prop = &new_prop; } else { /* Something is broken */ x11_ok(); /* Reset x11 error status */ return FAILURE; } /* Update value */ prop->flags |= MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS; prop->decorations = decorations; prop->functions = functions; XChangeProperty(dpy, wnd, atom, atom, 32, PropModeReplace, (unsigned char *)prop, PROP_MOTIF_WM_HINTS_ELEMENTS); return x11_ok(); } #ifdef DEBUG /* Dumps EWMH atoms supported by WM */ int ewmh_list_supported_atoms(Display *dpy) { Atom *atom_list; unsigned long atom_list_len, i; char *atom_name; if (ewmh_wm_present(dpy)) { if (x11_get_window_prop32(dpy, DefaultRootWindow(dpy), XInternAtom(dpy, _NET_SUPPORTED, False), XA_ATOM, (unsigned char **)&atom_list, &atom_list_len)) if (atom_list_len) { for (i = 0; i < atom_list_len; i++) { atom_name = XGetAtomName(dpy, atom_list[i]); if (atom_name != NULL) { LOG_TRACE(("_NET_SUPPORTED[%ld]: %s (0x%lx)\n", i, atom_name, atom_list[i])); } else LOG_TRACE(("_NET_SUPPORTED[%ld]: bogus value (0x%lx)\n", i, atom_list[i])); XFree(atom_name); x11_ok(); } free(atom_list); return SUCCESS; } } return FAILURE; } /* List EWMH states that are set for the given window */ int ewmh_dump_window_states(Display *dpy, Window wnd) { Atom prop, *data; unsigned long prop_len; int j; char *tmp; /* Check if WM supports _NET_WM_STATE */ prop = XInternAtom(tray_data.dpy, "_NET_WM_STATE", True); if (prop == None) return FAILURE; /* Retrive the list of states */ if (x11_get_window_prop32( dpy, wnd, prop, XA_ATOM, (unsigned char **)&data, &prop_len)) { for (j = 0; j < prop_len; j++) { tmp = XGetAtomName(tray_data.dpy, data[j]); if (x11_ok() && tmp != NULL) { LOG_TRACE(("0x%lx:_NET_WM_STATE[%d] = %s\n", wnd, j, tmp)); XFree(tmp); } } return SUCCESS; } return FAILURE; } #endif ================================================ FILE: src/wmh.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * ewmh.h * Thu, 30 Mar 2006 23:12:43 +0700 * ------------------------------- * EWMH/MWM support * ------------------------------- */ #ifndef _EWMH_H_ #define _EWMH_H_ #include #include /* Defines for Motif WM functions */ #define MWM_FUNC_ALL (1L << 0) #define MWM_FUNC_RESIZE (1L << 1) #define MWM_FUNC_MOVE (1L << 2) #define MWM_FUNC_MINIMIZE (1L << 3) #define MWM_FUNC_MAXIMIZE (1L << 4) #define MWM_FUNC_CLOSE (1L << 5) /* Defines for Motif WM decorations */ #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) /* EWMH atoms */ #define _NET_SUPPORTED "_NET_SUPPORTED" #define _NET_SUPPORTING_WM_CHECK "_NET_SUPPORTING_WM_CHECK" #define _NET_WM_DESKTOP "_NET_WM_DESKTOP" #define _NET_ACTIVE_WINDOW "_NET_ACTIVE_WINDOW" #define _NET_CLIENT_LIST "_NET_CLIENT_LIST" #define _NET_WM_PING "_NET_WM_PING" #define _NET_WM_STRUT_PARTIAL "_NET_WM_STRUT_PARTIAL" #define _NET_WM_STRUT "_NET_WM_STRUT" /* Defines for EWMH window types */ #define _NET_WM_WINDOW_TYPE_DESKTOP "_NET_WM_WINDOW_TYPE_DESKTOP" #define _NET_WM_WINDOW_TYPE_DOCK "_NET_WM_WINDOW_TYPE_DOCK" #define _NET_WM_WINDOW_TYPE_TOOLBAR "_NET_WM_WINDOW_TYPE_TOOLBAR" #define _NET_WM_WINDOW_TYPE_MENU "_NET_WM_WINDOW_TYPE_MENU" #define _NET_WM_WINDOW_TYPE_UTILITY "_NET_WM_WINDOW_TYPE_UTILITY" #define _NET_WM_WINDOW_TYPE_SPLASH "_NET_WM_WINDOW_TYPE_SPLASH" #define _NET_WM_WINDOW_TYPE_DIALOG "_NET_WM_WINDOW_TYPE_DIALOG" #define _NET_WM_WINDOW_TYPE_NORMAL "_NET_WM_WINDOW_TYPE_NORMAL" /* Defined for EWMH window states */ #define _NET_WM_STATE_MODAL "_NET_WM_STATE_MODAL" #define _NET_WM_STATE_STICKY "_NET_WM_STATE_STICKY" #define _NET_WM_STATE_MAXIMIZED_VERT "_NET_WM_STATE_MAXIMIZED_VERT" #define _NET_WM_STATE_MAXIMIZED_HORZ "_NET_WM_STATE_MAXIMIZED_HORZ" #define _NET_WM_STATE_SHADED "_NET_WM_STATE_SHADED" #define _NET_WM_STATE_SKIP_TASKBAR "_NET_WM_STATE_SKIP_TASKBAR" #define _NET_WM_STATE_SKIP_PAGER "_NET_WM_STATE_SKIP_PAGER" #define _NET_WM_STATE_HIDDEN "_NET_WM_STATE_HIDDEN" #define _NET_WM_STATE_FULLSCREEN "_NET_WM_STATE_FULLSCREEN" #define _NET_WM_STATE_ABOVE "_NET_WM_STATE_ABOVE" #define _NET_WM_STATE_BELOW "_NET_WM_STATE_BELOW" #define _NET_WM_STATE_DEMANDS_ATTENTION "_NET_WM_STATE_DEMANDS_ATTENTION" /* Flags for window state manipulations */ #define _NET_WM_STATE_REMOVE 0 /* remove/unset property */ #define _NET_WM_STATE_ADD 1 /* add/set property */ #define _NET_WM_STATE_TOGGLE 2 /* toggle property */ #define _NET_WM_STRUT_PARTIAL_SZ 12 #define _NET_WM_STRUT_SZ 4 typedef unsigned long wm_strut_t[_NET_WM_STRUT_PARTIAL_SZ]; #define WM_STRUT_IDX_LFT 0 #define WM_STRUT_IDX_RHT 1 #define WM_STRUT_IDX_TOP 2 #define WM_STRUT_IDX_BOT 3 #define WM_STRUT_IDX_START(B) B * 2 + 4 #define WM_STRUT_IDX_END(B) B * 2 + 5 /* Check if WM that supports EWMH hints is present on given display */ int ewmh_wm_present(Display *dpy); /* Add window type for the window wnd */ int ewmh_add_window_state(Display *dpy, Window wnd, char *state); /* Add window type for the window wnd */ int ewmh_add_window_type(Display *dpy, Window wnd, char *type); /* Set data for _NET_WM_STRUT_PARTIAL hint */ int ewmh_set_window_strut(Display *dpy, Window wnd, wm_strut_t wm_strut); /* Set CARD32 value of EWMH atom for a given window */ int ewmh_set_window_atom32( Display *dpy, Window wnd, char *prop_name, CARD32 value); /* Set Motif WM hints for window wnd; read MWM spec for more info */ int mwm_set_hints(Display *dpy, Window wnd, unsigned long decorations, unsigned long functions); #ifdef DEBUG /* Dumps EWMH atoms supported by WM */ int ewmh_list_supported_atoms(Display *dpy); /* Dump all EWMH states that have been set for the window wnd */ int ewmh_dump_window_states(Display *dpy, Window wnd); #endif #endif ================================================ FILE: src/xembed.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * xembed.c * Tue, 24 Aug 2004 12:05:38 +0700 * ------------------------------- * XEMBED protocol implementation * -------------------------------*/ /* Currently broken: * - XEMBED_{ACTIVATE,REGISTER,UNREGISTER}_ACCELERATOR * - XEMBED_REQUEST_FOCUS */ #include #include #include #include #include "common.h" #include "debug.h" #include "wmh.h" #include "xembed.h" #include "xutils.h" #include "list.h" /* Internal return codes */ #define XEMBED_RESULT_OK 0 #define XEMBED_RESULT_UNSUPPORTED 1 #define XEMBED_RESULT_X11ERROR 2 /* XEMBED messages */ #define XEMBED_EMBEDDED_NOTIFY 0 #define XEMBED_WINDOW_ACTIVATE 1 #define XEMBED_WINDOW_DEACTIVATE 2 #define XEMBED_REQUEST_FOCUS 3 #define XEMBED_FOCUS_IN 4 #define XEMBED_FOCUS_OUT 5 #define XEMBED_FOCUS_NEXT 6 #define XEMBED_FOCUS_PREV 7 /* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */ #define XEMBED_MODALITY_ON 10 #define XEMBED_MODALITY_OFF 11 #define XEMBED_REGISTER_ACCELERATOR 12 #define XEMBED_UNREGISTER_ACCELERATOR 13 #define XEMBED_ACTIVATE_ACCELERATOR 14 /* Details for XEMBED_FOCUS_IN */ #define XEMBED_FOCUS_CURRENT 0 #define XEMBED_FOCUS_FIRST 1 #define XEMBED_FOCUS_LAST 2 /* Modifiers field for XEMBED_REGISTER_ACCELERATOR */ #define XEMBED_MODIFIER_SHIFT (1 << 0) #define XEMBED_MODIFIER_CONTROL (1 << 1) #define XEMBED_MODIFIER_ALT (1 << 2) #define XEMBED_MODIFIER_SUPER (1 << 3) #define XEMBED_MODIFIER_HYPER (1 << 4) /* Flags for XEMBED_ACTIVATE_ACCELERATOR */ #define XEMBED_ACCELERATOR_OVERLOADED (1 << 0) /* Directions for focusing */ #define XEMBED_DIRECTION_DEFAULT 0 #define XEMBED_DIRECTION_UP_DOWN 1 #define XEMBED_DIRECTION_LEFT_RIGHT 2 /* Flags for _XEMBED_INFO */ #define XEMBED_MAPPED (1 << 0) /* Structure to hold XEMBED accelerator data */ struct XEMBEDAccel { struct XEMBEDAccel *next, *prev; int overloaded; /* Is this accelerator overloaded? */ long id; /* Accelerator Id */ long symb; /* Symbol */ long mods; /* Modifiers */ }; /* Shortcuts for sending XEMBED messages */ #define xembed_send_msg(dpy, dst, timestamp, msg, detail, data1, data2) \ x11_send_client_msg32(dpy, dst, dst, tray_data.xembed_data.xa_xembed, \ timestamp, msg, detail, data1, data2) #define xembed_send_embedded_notify(dpy, src, dst, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_EMBEDDED_NOTIFY, 0, src, 0) #define xembed_send_window_activate(dpy, dst, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_WINDOW_ACTIVATE, 0, 0, 0) #define xembed_send_window_deactivate(dpy, dst, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_WINDOW_DEACTIVATE, 0, 0, 0) #define xembed_send_focus_in(dpy, dst, focus, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_FOCUS_IN, focus, 0, 0) #define xembed_send_focus_out(dpy, dst, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_FOCUS_OUT, 0, 0, 0) #define xembed_send_activate_accelerator(dpy, dst, timestamp, id, overloaded) \ xembed_send_msg( \ dpy, dst, timestamp, XEMBED_ACTIVATE_ACCELERATOR, id, overloaded, 0) /* Retrive XEMBED data for the given icon */ int xembed_retrive_data(struct TrayIcon *ti); /* Post icon XEMBED data to window property */ int xembed_post_data(struct TrayIcon *ti); /* Returns the next icon in tab chain */ struct TrayIcon *xembed_next(); /* Returns the previous icon in tab chain */ struct TrayIcon *xembed_prev(); /* XEMBED event handler */ int xembed_process_kbd_event(XKeyEvent xkey); /* Register new XEMBED accelerator */ void xembed_add_accel(long id, long symb, long mods); /* Delete previously registered XEMBED accelerator */ void xembed_del_accel(long id); /* Activate previously registered XEMBED accelerator */ void xembed_act_accel(struct XEMBEDAccel *accel); /* Switch XEMBED focus to the specified icon */ void xembed_switch_focus_to(struct TrayIcon *tgt, long focus); /* Broadcast the focus change to all icons */ void xembed_track_focus_change(int activate); /* Process XEMBED message */ void xembed_message(XClientMessageEvent ev); /* Tries to request focus from WM */ void xembed_request_focus_from_wm(); void xembed_init() { /* 1. Initialize data structures */ tray_data.xembed_data.window_has_focus = False; tray_data.xembed_data.focus_requested = False; tray_data.xembed_data.current = NULL; tray_data.xembed_data.accels = NULL; tray_data.xembed_data.timestamp = CurrentTime; tray_data.xembed_data.xa_xembed = XInternAtom(tray_data.dpy, "_XEMBED", False); tray_data.xembed_data.xa_xembed_info = XInternAtom(tray_data.dpy, "_XEMBED_INFO", False); /* 2. Create focus proxy (see XEMBED spec) */ tray_data.xembed_data.focus_proxy = XCreateSimpleWindow( tray_data.dpy, tray_data.tray, -1, -1, 1, 1, 0, 0, 0); XSelectInput(tray_data.dpy, tray_data.xembed_data.focus_proxy, FocusChangeMask | KeyPressMask | KeyReleaseMask); XMapRaised(tray_data.dpy, tray_data.xembed_data.focus_proxy); if (!x11_ok()) DIE(("could not create focus proxy\n")); LOG_TRACE(( "created focus proxy, wid=0x%lx\n", tray_data.xembed_data.focus_proxy)); } void xembed_handle_event(XEvent ev) { switch (ev.type) { case FocusOut: /* Broadcast that the focus has left tray window */ LOG_TRACE(("FocusOut 0x%lx\n", ev.xfocus.window)); if (ev.xfocus.window == tray_data.xembed_data.focus_proxy) xembed_track_focus_change(False); break; case ClientMessage: /* Handle XEMBED-related messages */ if (ev.xclient.message_type == tray_data.xembed_data.xa_xembed) { xembed_message(ev.xclient); } else if (ev.xclient.message_type == tray_data.xa_tray_opcode) { /* we peek at _NET_SYSTEM_TRAY_OPCODE messages * to obtain proper timestamp for embedding */ tray_data.xembed_data.timestamp = ev.xclient.data.l[0]; if (tray_data.xembed_data.timestamp == CurrentTime) tray_data.xembed_data.timestamp = x11_get_server_timestamp(tray_data.dpy, tray_data.tray); } else if (ev.xclient.message_type == tray_data.xa_wm_protocols && (unsigned long) ev.xclient.data.l[0] == tray_data.xa_wm_take_focus && tray_data.xembed_data.focus_requested) { XSetInputFocus(tray_data.dpy, tray_data.xembed_data.focus_proxy, RevertToParent, ev.xclient.data.l[1]); if (!x11_ok()) DIE_IE(("Could not set focus to XEMBED focus proxy\n")); LOG_TRACE(("focus set to focus proxy\n")); xembed_track_focus_change(True); tray_data.xembed_data.focus_requested = False; } break; case KeyRelease: case KeyPress: /* Propagate key events to currently focused icon */ tray_data.xembed_data.timestamp = ev.xkey.time; if (ev.type == KeyRelease && xembed_process_kbd_event(ev.xkey)) break; if (tray_data.xembed_data.current != NULL) { int rc; LOG_TRACE(("current icon accepts_focus: %d\n", tray_data.xembed_data.current->is_xembed_accepts_focus)); rc = XSendEvent(tray_data.dpy, tray_data.xembed_data.current->wid, False, NoEventMask, &ev); if (!x11_ok() || rc == 0) { tray_data.xembed_data.current->is_invalid = True; return; } LOG_TRACE(("sent key event to 0x%lx\n", tray_data.xembed_data.current->wid)); } break; } } int xembed_check_support(struct TrayIcon *ti) { int rc = xembed_retrive_data(ti); ti->is_xembed_supported = (rc == XEMBED_RESULT_OK); return rc != XEMBED_RESULT_X11ERROR; } int xembed_get_mapped_state(struct TrayIcon *ti) { /* It is OK to retrive data each time this function * is called, since there is some overhead only during * initialization, when xembed_retrive_data is called 2 * times in a row(). */ int rc = xembed_retrive_data(ti); if (ti->is_xembed_supported && rc == XEMBED_RESULT_OK) return ((ti->xembed_data[1] & XEMBED_MAPPED) != 0); else { ti->is_xembed_supported = False; ti->is_invalid = (rc == XEMBED_RESULT_X11ERROR); return False; } } int xembed_set_mapped_state(struct TrayIcon *ti, int state) { if (!ti->is_xembed_supported) return FAILURE; if (state) ti->xembed_data[1] |= XEMBED_MAPPED; else ti->xembed_data[1] &= ~XEMBED_MAPPED; return xembed_post_data(ti); } int xembed_embed(struct TrayIcon *ti) { /* if XEMBED is not supported, do nothing */ if (!ti->is_xembed_supported) return SUCCESS; /* By default, consider that all icons accept focus */ ti->is_xembed_accepts_focus = True; /* Send notification */ if (!xembed_send_embedded_notify(tray_data.dpy, tray_data.tray, ti->wid, tray_data.xembed_data.timestamp)) return FAILURE; ti->xembed_last_timestamp = tray_data.xembed_data.timestamp; ti->xembed_last_msgid = XEMBED_EMBEDDED_NOTIFY; if (tray_data.xembed_data.current == NULL) { /* No icon has focus. Set focus to this one */ if (!xembed_send_focus_in(tray_data.dpy, ti->wid, XEMBED_FOCUS_FIRST, tray_data.xembed_data.timestamp)) return FAILURE; tray_data.xembed_data.current = ti; } /* Send activation message if tray window has focus */ if (tray_data.xembed_data.window_has_focus) return xembed_send_window_activate( tray_data.dpy, ti->wid, tray_data.xembed_data.timestamp); return SUCCESS; } int xembed_unembed(struct TrayIcon *ti) { struct TrayIcon *tmp; tray_data.xembed_data.timestamp = x11_get_server_timestamp(tray_data.dpy, tray_data.tray); if (ti == tray_data.xembed_data.current) { /* Currently focused icon is being unembedded, * move focus to the next icon. */ tmp = xembed_next(); if (tmp == ti || tmp->is_xembed_accepts_focus == False) { xembed_switch_focus_to(NULL, 0); } else { xembed_switch_focus_to(tmp, XEMBED_FOCUS_FIRST); } } return SUCCESS; } /*********** implementation level ***************/ void xembed_switch_focus_to(struct TrayIcon *tgt, long focus) { /* 1. Send "focus out" message to the currently focused icon */ if (tray_data.xembed_data.current != NULL) { LOG_TRACE(("XEMBED focus was removed from icon 0x%lx (pointer %p)\n", tray_data.xembed_data.current->wid, (void *) tray_data.xembed_data.current)); xembed_send_focus_out(tray_data.dpy, tray_data.xembed_data.current->wid, tray_data.xembed_data.timestamp); } /* 2. Send "focus in" message to the icon to be focused */ if (tgt != NULL) { xembed_send_focus_in( tray_data.dpy, tgt->wid, focus, tray_data.xembed_data.timestamp); LOG_TRACE(("XEMBED focus was set to icon 0x%lx (pointer %p)\n", tgt->wid, (void *) tgt)); } else { LOG_TRACE(("XEMBED focus was unset\n")); } tray_data.xembed_data.current = tgt; } static int activate = 0; int broadcast_activate_msg(struct TrayIcon *ti) { if (activate) xembed_send_window_activate( tray_data.dpy, ti->wid, tray_data.xembed_data.timestamp); else xembed_send_window_deactivate( tray_data.dpy, ti->wid, tray_data.xembed_data.timestamp); return NO_MATCH; } void xembed_track_focus_change(int in) { if (tray_data.xembed_data.window_has_focus == in) return; tray_data.xembed_data.window_has_focus = in; activate = in; icon_list_forall(&broadcast_activate_msg); LOG_TRACE(("XEMBED focus is %s\n", in ? "ON" : "OFF")); } void xembed_message(XClientMessageEvent ev) { long msgid; LOG_TRACE(("this is an _XEMBED message, window: 0x%lx, timestamp: %lu, " "opcode: %lu, \ndetail: 0x%lx, data1 = 0x%lx, data2 = 0x%lx\n", ev.window, ev.data.l[0], ev.data.l[1], ev.data.l[2], ev.data.l[3], ev.data.l[4])); #if DEBUG if (tray_data.xembed_data.current != NULL) LOG_TRACE(("XEMBED focus is in window 0x%lx (pointer %p)\n", tray_data.xembed_data.current->wid, tray_data.xembed_data.current)); else LOG_TRACE(("XEMBED focus is unset\n")); #endif if (ev.window != tray_data.tray) { LOG_TRACE(("inoring _XEMBED message to some other window\n")); return; } /* Update timestamp if necessary */ if (ev.data.l[0] == CurrentTime) ev.data.l[0] = x11_get_server_timestamp(tray_data.dpy, tray_data.tray); tray_data.xembed_data.timestamp = ev.data.l[0]; msgid = ev.data.l[1]; LOG_TRACE(("_XEMBED message %lu\n", msgid)); switch (msgid) { case XEMBED_REQUEST_FOCUS: xembed_request_focus_from_wm(); break; case XEMBED_FOCUS_NEXT: case XEMBED_FOCUS_PREV: if (tray_data.xembed_data.current != NULL) { struct TrayIcon *old_focus, *new_focus; old_focus = tray_data.xembed_data.current; new_focus = (msgid == XEMBED_FOCUS_NEXT) ? xembed_next() : xembed_prev(); if (new_focus->is_xembed_accepts_focus) { /* If the last message for the new focus target was * focus_{next,prev} and it has the same timestamp as the * current message, it is likely that the corresponding icon * does not want to be focused at all. So mark it as not * accepting focus. */ if (new_focus->xembed_last_timestamp == tray_data.xembed_data.timestamp && (new_focus->xembed_last_msgid == XEMBED_FOCUS_NEXT || new_focus->xembed_last_msgid == XEMBED_FOCUS_PREV)) { new_focus->is_xembed_accepts_focus = False; new_focus = False; } old_focus->xembed_last_timestamp = tray_data.xembed_data.timestamp; old_focus->xembed_last_msgid = msgid; } else new_focus = NULL; xembed_switch_focus_to(new_focus, (msgid == XEMBED_FOCUS_NEXT) ? XEMBED_FOCUS_FIRST : XEMBED_FOCUS_LAST); } break; case XEMBED_REGISTER_ACCELERATOR: xembed_add_accel(ev.data.l[2], ev.data.l[3], ev.data.l[4]); break; case XEMBED_UNREGISTER_ACCELERATOR: xembed_del_accel(ev.data.l[2]); break; default: LOG_TRACE(("Unhandled _XEMBED message, id = %ld\n", ev.data.l[1])); break; } } void xembed_add_accel(long id, long symb, long mods) { struct XEMBEDAccel *xaccel, *tmp; xaccel = (struct XEMBEDAccel *)malloc(sizeof(struct XEMBEDAccel)); if (xaccel == NULL) { LOG_ERR_OOM(("Could not register new XEMBED accelerator\n")); return; } xaccel->id = id; xaccel->symb = symb; xaccel->mods = mods; xaccel->overloaded = 0; /* Check if there are already registered accelerators that are overloaded * by this one */ for (tmp = tray_data.xembed_data.accels; tmp != NULL; tmp = tmp->next) if (tmp->symb == symb && tmp->mods == mods) { xaccel->overloaded++; tmp->overloaded++; } LIST_ADD_ITEM(tray_data.xembed_data.accels, xaccel); LOG_TRACE(("added new XEMBED accelerator: id=0x%lx, sym=0x%lx, mods=0x%lx, " "overloaded=%d\n", id, symb, mods, xaccel->overloaded)); } void xembed_del_accel(long id) { struct XEMBEDAccel *tmp, *tgt = NULL; for (tmp = tray_data.xembed_data.accels; tmp != NULL; tmp = tmp->next) if (tmp->id == id) { tgt = tmp; return; } if (tgt == NULL) { LOG_TRACE(("refusing to remove unregistered XEMBED accelerator\n")); return; } /* Update overloaded status of the remaining accelerators */ for (tmp = tray_data.xembed_data.accels; tmp != NULL; tmp = tmp->next) if (tmp->symb == tgt->symb && tmp->mods == tgt->mods) tmp->overloaded--; LIST_DEL_ITEM(tray_data.xembed_data.accels, tgt); LOG_TRACE(("removed XEMBED accelator id=0x%lx", tgt->id)); free(tgt); } static struct XEMBEDAccel *cur_accel; int xembed_act_accel_helper(struct TrayIcon *ti) { xembed_send_activate_accelerator(tray_data.dpy, ti->wid, tray_data.xembed_data.timestamp, cur_accel->id, cur_accel->overloaded ? 1 : 0); return NO_MATCH; } void xembed_act_accel(struct XEMBEDAccel *accel) { LOG_TRACE( ("activating XEMBED accelerator: id=0x%lx (symb=0x%lx, mods=0x%lx)\n", accel->id, accel->symb, accel->mods)); cur_accel = accel; icon_list_forall(&xembed_act_accel_helper); } int xembed_process_kbd_event(XKeyEvent xkey) { struct XEMBEDAccel *tmp; int hits = 0; KeySym keysym; static char buf[20]; XLookupString(&xkey, buf, 20, &keysym, NULL); LOG_TRACE( ("Key event (type=%d) with keycode=0x%x, symb=0x%lx, state=0x%x\n", xkey.type, xkey.keycode, keysym, xkey.state)); for (tmp = tray_data.xembed_data.accels; tmp != NULL; tmp = tmp->next) if ((unsigned long) tmp->symb == keysym && tmp->mods == xkey.state) { xembed_act_accel(tmp); hits = 1; } return hits; } struct TrayIcon *xembed_next() { struct TrayIcon *tmp, *blocker; tmp = tray_data.xembed_data.current != NULL ? tray_data.xembed_data.current : NULL; blocker = tmp != NULL ? tmp : icon_list_next(NULL); do { tmp = layout_next(tmp); } while ((!tmp->is_xembed_supported || !tmp->is_xembed_accepts_focus) && tmp != blocker); return tmp; } struct TrayIcon *xembed_prev() { struct TrayIcon *tmp, *blocker; tmp = tray_data.xembed_data.current != NULL ? tray_data.xembed_data.current : NULL; blocker = tmp != NULL ? tmp : icon_list_prev(tmp); do { tmp = layout_prev(tmp); } while ((!tmp->is_xembed_supported || !tmp->is_xembed_accepts_focus) && tmp != blocker); return tmp; } int xembed_retrive_data(struct TrayIcon *ti) { Atom act_type; int act_fmt; unsigned long nitems, bytesafter, *data; unsigned char *tmpdata; int rc; /* NOTE: x11_get_win_prop32 is not used since we need to distinguish * between X11 errors and absence of the property */ rc = XGetWindowProperty(tray_data.dpy, ti->wid, tray_data.xembed_data.xa_xembed_info, 0, 2, False, tray_data.xembed_data.xa_xembed_info, &act_type, &act_fmt, &nitems, &bytesafter, &tmpdata); if (!x11_ok() || rc != Success) return XEMBED_RESULT_X11ERROR; rc = (act_type == tray_data.xembed_data.xa_xembed_info && nitems == 2); if (rc) { data = (unsigned long *)tmpdata; ti->xembed_data[0] = data[0]; ti->xembed_data[1] = data[1]; } if (nitems && tmpdata != NULL) XFree(tmpdata); return rc ? XEMBED_RESULT_OK : XEMBED_RESULT_UNSUPPORTED; } int xembed_post_data(struct TrayIcon *ti) { if (!ti->is_xembed_supported) return XEMBED_RESULT_UNSUPPORTED; XChangeProperty(tray_data.dpy, ti->wid, tray_data.xembed_data.xa_xembed_info, tray_data.xembed_data.xa_xembed_info, 32, PropModeReplace, (unsigned char *)ti->xembed_data, 2); return x11_ok() ? XEMBED_RESULT_OK : XEMBED_RESULT_X11ERROR; } void xembed_request_focus_from_wm() { if (!tray_data.is_reparented) { x11_send_client_msg32(tray_data.dpy, DefaultRootWindow(tray_data.dpy), tray_data.tray, XInternAtom(tray_data.dpy, "_NET_ACTIVE_WINDOW", True), 1, /* Request is from application */ x11_get_server_timestamp( tray_data.dpy, tray_data.tray), /* Timestamp */ 0, /* None window is focused current (?) */ 0, /* Unused */ 0); /* Unused */ tray_data.xembed_data.focus_requested = True; } } ================================================ FILE: src/xembed.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * icons.h * Tue, 24 Aug 2004 12:05:38 +0700 * ------------------------------- * XEMBED protocol implementation * -------------------------------*/ #ifndef _XEMBED_H_ #define _XEMBED_H_ #include #include "icons.h" /* Data structure for all XEMBED-related data for the tray */ struct XEMBEDData { struct TrayIcon *current; /* Pointer to the currently focused icon */ struct XEMBEDAccel *accels; /* List of registered XEMBED accelerators */ int window_has_focus; /* Flag: does tray's window have focus */ int focus_requested; /* Flag: if there is not completed focus request */ Window focus_proxy; /* Window ID of XEMBED focus proxy */ long timestamp; /* Timestamp of current processed message */ Atom xa_xembed_info; /* Atom: XEMBED_INFO */ Atom xa_xembed; /* Atom: XEMBED */ }; /* Initialize XEMBED data structures */ void xembed_init(); /* Event handling routine for XEMBED support */ void xembed_handle_event(XEvent ev); /* Check if icon ti supports XEMBED */ int xembed_check_support(struct TrayIcon *ti); /* Send XEMBED embedding acknowledgement to icon ti */ int xembed_embed(struct TrayIcon *ti); /* Same as above for unembedding */ int xembed_unembed(struct TrayIcon *ti); /* Get XEMBED mapped state from XEMBED info */ int xembed_get_mapped_state(struct TrayIcon *ti); /* Set XEMBED mapped state in XEMBED info */ int xembed_set_mapped_state(struct TrayIcon *ti, int state); #endif ================================================ FILE: src/xinerama.c ================================================ #include #include "debug.h" #include "xinerama.h" #ifdef _ST_WITH_XINERAMA #include #include "tray.h" #include "settings.h" #endif void xinerama_init(Display *dpy) { #ifdef _ST_WITH_XINERAMA if (!XineramaIsActive(dpy)) { LOG_TRACE(("Xinerama is not active, returning\n")); return; } LOG_TRACE(("Xinerama is active\n")); tray_data.xinerama_active = True; tray_data.monitors = XineramaQueryScreens(dpy, &tray_data.n_monitors); LOG_TRACE(("Xinerama reports %d monitors\n", tray_data.n_monitors)); #else (void) dpy; /* unused */ #endif } void xinerama_update_geometry(void) { #ifdef _ST_WITH_XINERAMA XineramaScreenInfo chosen_monitor; unsigned int dummy; int x = 0, y = 0, flags; if (!tray_data.xinerama_active) return; LOG_TRACE(("Updating geometry based on chosen Xinerama monitor\n")); flags = XParseGeometry(settings.geometry_str, &x, &y, &dummy, &dummy); chosen_monitor = tray_data.monitors[settings.monitor]; LOG_TRACE(("Chosen monitor %d: %dx%d+%d+%d\n", settings.monitor, chosen_monitor.width, chosen_monitor.height, chosen_monitor.x_org, chosen_monitor.y_org)); if (flags & XValue && flags & XNegative) x += chosen_monitor.width - tray_data.xsh.width; if (flags & YValue && flags & YNegative) y += chosen_monitor.height - tray_data.xsh.height; tray_data.xsh.x = chosen_monitor.x_org + x; tray_data.xsh.y = chosen_monitor.y_org + y; LOG_TRACE(("New tray position (x,y): %d,%d\n", tray_data.xsh.x, tray_data.xsh.y)); #else return; #endif } ================================================ FILE: src/xinerama.h ================================================ #ifndef _STALONETRAY_XINERAMA_H_ #define _STALONETRAY_XINERAMA_H_ #include void xinerama_init(Display *dpy); void xinerama_update_geometry(void); #endif ================================================ FILE: src/xutils.c ================================================ /* ************************************ * vim:tabstop=4:shiftwidth=4 * xutils.c * Sun, 05 Mar 2006 17:56:56 +0600 * ************************************ * misc X11 utilities * ************************************/ #include #include #include #include #include #include #include #include "xutils.h" #include "common.h" #include "debug.h" static int trapped_x11_error_code = 0; static int (*old_x11_error_handler)(Display *, XErrorEvent *) = NULL; static int current_x11_connection_status = 1; static int (*old_x11_io_error_handler)(Display *) = NULL; int x11_io_error_handler(Display *dpy) { current_x11_connection_status = 0; old_x11_io_error_handler(dpy); DIE(("Connection to X11 server lost. Dying.\n")); } int x11_connection_status() { return current_x11_connection_status; } int x11_error_handler(Display *dpy, XErrorEvent *err) { static char msg[PATH_MAX], req_num_str[32], req_str[PATH_MAX]; trapped_x11_error_code = err->error_code; XGetErrorText(dpy, trapped_x11_error_code, msg, sizeof(msg) - 1); snprintf(req_num_str, 32, "%d", err->request_code); XGetErrorDatabaseText( dpy, "XRequest", req_num_str, "Unknown", req_str, PATH_MAX); LOG_ERROR(("X11 error: %s (request: %s, resource 0x%x)\n", msg, req_str, err->request_code)); return 0; } int x11_ok_helper(const char *file, const int line, const char *func) { if (trapped_x11_error_code) { LOG_ERROR(("X11 error %d detected at %s:%d:%s\n", trapped_x11_error_code, file, line, func)); trapped_x11_error_code = 0; return FAILURE; } else return SUCCESS; } int x11_current_error() { return trapped_x11_error_code; } void x11_trap_errors() { old_x11_io_error_handler = XSetIOErrorHandler(x11_io_error_handler); old_x11_error_handler = XSetErrorHandler(x11_error_handler); trapped_x11_error_code = 0; } int x11_untrap_errors() { XSetErrorHandler(old_x11_error_handler); return trapped_x11_error_code; } static Window timestamp_wnd; static Atom timestamp_atom = None; Bool x11_wait_for_timestamp(Display *, XEvent *xevent, XPointer data) { return ((xevent->type == PropertyNotify && xevent->xproperty.window == *((Window *)data) && xevent->xproperty.atom == timestamp_atom) || (xevent->type == DestroyNotify && xevent->xdestroywindow.window == *((Window *)data))); } Time x11_get_server_timestamp(Display *dpy, Window wnd) { unsigned char c = 's'; XEvent xevent; if (timestamp_atom == None) timestamp_atom = XInternAtom(dpy, "STALONETRAY_TIMESTAMP", False); x11_ok(); /* Just reset the status (XXX) */ /* Trigger PropertyNotify event which has a timestamp field */ XChangeProperty( dpy, wnd, timestamp_atom, timestamp_atom, 8, PropModeReplace, &c, 1); if (!x11_ok()) return CurrentTime; /* Wait for the event * XXX: this may block... */ timestamp_wnd = wnd; XIfEvent(dpy, &xevent, x11_wait_for_timestamp, (XPointer)×tamp_wnd); return x11_ok() ? xevent.xproperty.time : CurrentTime; } int x11_get_window_prop32(Display *dpy, Window dst, Atom atom, Atom type, unsigned char **data, unsigned long *len) { Atom act_type; int act_fmt, rc; unsigned long bytes_after, prop_len, buf_len; unsigned char *buf = NULL; *data = NULL; *len = 0; /* Get the property size */ rc = XGetWindowProperty(dpy, dst, atom, 0L, 1L, False, type, &act_type, &act_fmt, &buf_len, &bytes_after, &buf); /* The requested property does not exist */ if (!x11_ok() || rc != Success || act_type != type || act_fmt != 32) return FAILURE; if (buf != NULL) XFree(buf); /* Now go get the property */ prop_len = bytes_after / 4 + 1; XGetWindowProperty(dpy, dst, atom, 0L, prop_len, False, type, &act_type, &act_fmt, &buf_len, &bytes_after, &buf); if (x11_ok()) { *len = buf_len; *data = buf; return SUCCESS; } else { return FAILURE; } } int x11_send_client_msg32(Display *dpy, Window dst, Window wnd, Atom type, long data0, long data1, long data2, long data3, long data4) { XEvent ev; Status rc; ev.xclient.type = ClientMessage; ev.xclient.serial = 0; ev.xclient.send_event = True; ev.xclient.message_type = type; ev.xclient.window = wnd; ev.xclient.format = 32; ev.xclient.data.l[0] = data0; ev.xclient.data.l[1] = data1; ev.xclient.data.l[2] = data2; ev.xclient.data.l[3] = data3; ev.xclient.data.l[4] = data4; /* XXX: Replace 0xFFFFFF for better portability? */ /* XXX: This should actually read NoEventMask... * seems like extra parameter is necessary */ rc = XSendEvent(dpy, dst, False, 0xFFFFFF, &ev); return x11_ok() && rc != 0; } int x11_send_visibility(Display *, Window dst, long state) { XEvent xe; int rc; xe.type = VisibilityNotify; xe.xvisibility.window = dst; xe.xvisibility.state = state; rc = XSendEvent(tray_data.dpy, dst, True, NoEventMask, &xe); return x11_ok() && rc != 0; } int x11_send_button(Display *dpy, int press, Window dst, Window root, Time time, unsigned int button, unsigned int state, int x, int y) { XEvent ev; int rx, ry, rc, idummy; unsigned int udummy; Window dst_root; if (!x11_get_window_abs_coords(dpy, dst, &rx, &ry)) return FAILURE; XGetGeometry(dpy, dst, &dst_root, &idummy, &idummy, &udummy, &udummy, &udummy, &udummy); if (!x11_ok()) return FAILURE; ev.type = press ? ButtonPress : ButtonRelease; ev.xbutton.display = dpy; ev.xbutton.window = dst; ev.xbutton.subwindow = None; ev.xbutton.root = root; ev.xbutton.time = time; ev.xbutton.x = x; ev.xbutton.y = y; ev.xbutton.x_root = rx + x; ev.xbutton.y_root = ry + y; ev.xbutton.button = button; ev.xbutton.state = state; ev.xbutton.same_screen = (root == dst_root); rc = XSendEvent(dpy, dst, True, SubstructureNotifyMask | (press ? ButtonPressMask : ButtonReleaseMask), &ev); return rc && x11_ok(); } int x11_send_expose( Display *, Window dst, int x, int y, int width, int height) { XEvent xe; int rc; xe.type = Expose; xe.xexpose.window = dst; xe.xexpose.x = x; xe.xexpose.y = y; xe.xexpose.width = width; xe.xexpose.height = height; xe.xexpose.count = 0; rc = XSendEvent(tray_data.dpy, dst, True, NoEventMask, &xe); return x11_ok() && rc != 0; } int x11_refresh_window( Display *, Window dst, int width, int height, int exposures) { x11_send_visibility(tray_data.dpy, dst, VisibilityFullyObscured); x11_send_visibility(tray_data.dpy, dst, VisibilityUnobscured); XClearArea(tray_data.dpy, dst, 0, 0, width, height, exposures); return x11_ok(); } int x11_set_window_size(Display *dpy, Window w, int x, int y) { XSizeHints xsh; xsh.flags = PSize; xsh.width = x; xsh.height = y; XSetWMNormalHints(dpy, w, &xsh); XResizeWindow(dpy, w, x, y); if (!x11_ok()) return FAILURE; return SUCCESS; } int x11_get_window_size(Display *dpy, Window w, int *x, int *y) { XWindowAttributes xwa; XGetWindowAttributes(dpy, w, &xwa); if (!x11_ok()) return FAILURE; *x = xwa.width; *y = xwa.height; return SUCCESS; } int x11_get_window_min_size(Display *dpy, Window w, int *x, int *y) { XSizeHints xsh; long flags = 0; int rc = FAILURE; if (XGetWMNormalHints(dpy, w, &xsh, &flags)) { flags = xsh.flags & flags; if (flags & PMinSize) { *x = xsh.min_width; *y = xsh.min_height; rc = SUCCESS; } } return x11_ok() && rc; } int x11_get_window_abs_coords(Display *dpy, Window dst, int *x, int *y) { XWindowAttributes xwa; Window child; #if 0 x11_ok(); /* XXX: this should go away, shouldn't it? */ #endif XGetWindowAttributes(dpy, dst, &xwa); XTranslateCoordinates(dpy, dst, xwa.root, 0, 0, x, y, &child); return x11_ok(); } char *x11_get_window_name(Display *dpy, Window dst, char *def) { static char *name; if (name != NULL) XFree(name); if (!XFetchName(dpy, dst, &name)) name = NULL; return (name != NULL ? name : def); } Window x11_find_subwindow_by_name(Display *dpy, Window tgt, char *name) { char *tgt_name = NULL; Window ret = None, *children, dummy; unsigned int i, nchildren; if (XFetchName(dpy, tgt, &tgt_name)) { LOG_TRACE(("tgt_name=\"%s\", name=\"%s\"\n", tgt_name, name)); if (!strcmp(tgt_name, name)) ret = tgt; } if (!x11_ok()) return None; if (tgt_name != NULL) XFree(tgt_name); if (ret != None) return ret; XQueryTree(dpy, tgt, &dummy, &dummy, &children, &nchildren); if (!x11_ok()) return None; for (i = 0; i < nchildren; i++) { ret = x11_find_subwindow_by_name(dpy, children[i], name); if (ret != None) break; } if (children != NULL) XFree(children); return ret; } /* x and y are relative to top at input, and relative to found window on return */ Window x11_find_subwindow_at( Display *dpy, Window top, int *x, int *y, int depth) { int d, c, bx = 0, by = 0; Window dummy, *children; unsigned int nchildren; Window cur = top, old = None; for (d = 1; d != depth && cur != old && old != None; d++) { old = cur; /* Query children of current window and traverse them starting * from the top in stacking order (i.e. from the end of the list * returned by XQueryTree) */ XQueryTree(dpy, cur, &dummy, &dummy, &children, &nchildren); LOG_TRACE(("cur=0x%lx nchildren=%d\n", cur, nchildren)); if (!x11_ok()) goto fail; /* Exit the loop if window has no children */ if (nchildren == 0) break; /* Loop over children starting from topmost */ for (c = nchildren - 1; c >= 0; c--) { XWindowAttributes xwa; XGetWindowAttributes(dpy, children[c], &xwa); if (!x11_ok()) goto fail; /* Check if this child contains the (x,y) point */ if (xwa.x + bx <= *x && *x < xwa.x + xwa.width + bx && xwa.y + by <= *y && *y < xwa.y + xwa.height + by) { cur = children[c]; bx += xwa.x; by += xwa.y; break; } } if (children != NULL) XFree(children); } *x -= bx; *y -= by; return cur; fail: if (children != NULL) XFree(children); return None; } void x11_extend_root_event_mask(Display *dpy, long mask) { static long old_mask = 0; old_mask |= mask; XSelectInput(dpy, DefaultRootWindow(dpy), old_mask); } int x11_parse_color(Display *dpy, char *str, XColor *color) { int rc; rc = XParseColor( dpy, XDefaultColormap(dpy, DefaultScreen(dpy)), str, color); if (rc) XAllocColor(dpy, XDefaultColormap(dpy, DefaultScreen(dpy)), color); return x11_ok() && rc; } char *x11_get_window_class(Display *dpy, Window w) { XClassHint hint; Status res = 0; char *classname = NULL; res = XGetClassHint(dpy, w, &hint); if (res == 0) { LOG_TRACE(("XGetClassHint(dpy, 0x%lx, &hint) failed: %x", w, res)); return NULL; } classname = strdup(hint.res_class); XFree(hint.res_name); XFree(hint.res_class); return classname; } #ifdef DEBUG const char *x11_event_names[LASTEvent] = {"unknown0", "unknown1", "KeyPress", "KeyRelease", "ButtonPress", "ButtonRelease", "MotionNotify", "EnterNotify", "LeaveNotify", "FocusIn", "FocusOut", "KeymapNotify", "Expose", "GraphicsExpose", "NoExpose", "VisibilityNotify", "CreateNotify", "DestroyNotify", "UnmapNotify", "MapNotify", "MapRequest", "ReparentNotify", "ConfigureNotify", "ConfigureRequest", "GravityNotify", "ResizeRequest", "CirculateNotify", "CirculateRequest", "PropertyNotify", "SelectionClear", "SelectionRequest", "SelectionNotify", "ColormapNotify", "ClientMessage", "MappingNotify"}; void x11_dump_win_info(Display *dpy, Window wid) { #if defined(DEBUG) && defined(_ST_WITH_DUMP_WIN_INFO) if (settings.log_level >= LOG_LEVEL_TRACE) { char cmd[PATH_MAX]; int rc; snprintf(cmd, PATH_MAX, "xwininfo -tree -size -bits -stats -id 0x%x 1>&2\n", (unsigned int)wid); rc = system(cmd); if (rc != 0) LOG_ERROR(("command failed: '%s'\n", cmd)); snprintf(cmd, PATH_MAX, "xprop -id 0x%x 1>&2\n", (unsigned int)wid); rc = system(cmd); if (rc != 0) LOG_ERROR(("command failed: '%s'\n", cmd)); } #endif } #endif ================================================ FILE: src/xutils.h ================================================ /* ************************************ * vim:tabstop=4:shiftwidth=4 * xutils.h * Sun, 05 Mar 2006 17:16:44 +0600 * ************************************ * misc X11 utilities * ************************************/ #ifndef _XUTILS_H_ #define _XUTILS_H_ #include #include #include "common.h" #include "icons.h" /* Returns 1 if connection is active, 0 otherwise */ int x11_connection_status(); /* Return current server timestamp */ Time x11_get_server_timestamp(Display *dpy, Window wnd); /* Convinient way to send a client message event */ int x11_send_client_msg32(Display *dpy, Window dst, Window wnd, Atom type, long data0, long data1, long data2, long data3, long data4); /* Same for visibility event */ int x11_send_visibility(Display *dpy, Window dst, long state); /* Same for expose event */ int x11_send_expose( Display *dpy, Window dst, int x, int y, int width, int height); /* Same for button event */ int x11_send_button(Display *dpy, int press, Window dst, Window root, Time time, unsigned int button, unsigned int state, int x, int y); /* Refresh window */ int x11_refresh_window( Display *dpy, Window dst, int width, int height, int exposures); /* Set window size updating its size hints */ int x11_set_window_size(Display *dpy, Window w, int x, int y); /* Get window size (uses XGetWindowAttributes) */ int x11_get_window_size(Display *dpy, Window w, int *x, int *y); /* Get window minimal size hints if they are available */ int x11_get_window_min_size(Display *dpy, Window w, int *x, int *y); /* Retrive 32-bit property from the target window */ int x11_get_window_prop32(Display *dpy, Window dst, Atom atom, Atom type, unsigned char **data, unsigned long *len); /* Retrive window-list property from the specified window */ #define x11_get_winlist_prop(dpy, dst, atom, data, len) \ x11_get_window_prop32(dpy, dst, atom, XA_WINDOW, data, len) /* Shortcut for the root window case */ #define x11_get_root_winlist_prop(dpy, atom, data, len) \ x11_get_winlist_prop(dpy, DefaultRootWindow(dpy), atom, data, len) /* Returns window absolute position (relative to the root window) */ int x11_get_window_abs_coords(Display *dpy, Window dst, int *x, int *y); /* Get window name. NOT THREAD SAFE. Returns pointer to static buffer */ char *x11_get_window_name(Display *dpy, Window dst, char *def); /* Get window class name. Returns pointer to string created with strdup() */ char *x11_get_window_class(Display *dpy, Window w); /* Find subwindow by name */ Window x11_find_subwindow_by_name(Display *dpy, Window tgt, char *name); /* Find subwindow by at coords */ Window x11_find_subwindow_at( Display *dpy, Window top, int *x, int *y, int depth); /* Extends event mask of the root window */ void x11_extend_root_event_mask(Display *dpy, long mask); /* Parse text representation of a color */ int x11_parse_color(Display *dpy, char *str, XColor *color); /* Checks if any X11 errors have occured so far. */ /* ACHTUNG!!! after any sequence X operations any of which * that might have failed, you _must_ call x11_ok(), since * it resets the internal error flag. If you dont, it will show * up as a error elsewhere. JFYI, always check for x11_ok() first, * since * if (!rc || x11_ok()) { fail; } * is likely to leave error condition on: x11_ok() wont be called * if rc != 0. */ #define x11_ok() x11_ok_helper(__FILE__, __LINE__, __FUNC__) int x11_ok_helper(const char *file, const int line, const char *func); /* WARNING: following functions do not support nested calls */ /* Installs custom X11 error handler */ void x11_trap_errors(); /* Removes custom X11 error handler */ int x11_untrap_errors(); #ifdef DEBUG /* Array that maps event_number -> event_name */ const extern char *x11_event_names[LASTEvent]; /* Dumps window info. Does nothing unless _ST_WITH_DUMP_WIN_INFO is defined, * launches xwininfo and xwinprop otherwise */ void x11_dump_win_info(Display *dpy, Window w); #else /* Dummy delcaration */ #define x11_dump_win_info(dpy, w) \ do { \ } while (0); #endif #endif ================================================ FILE: stalonetray.xml.in ================================================ Roman Dubtsov stalonetray 1 stalonetray stalonetray @VERSION_STR@ User Commands stalonetray stand-alone system tray (notification area) implementation. This document covers @VERSION_STR@ version of stalonetray. stalonetray option DESCRIPTION Stalonetray is a stand-alone system tray (notification area) for X Window System/X11 (e.g. XOrg or XFree86). It has minimal build and run-time dependencies: an X11 lib only. Complete XEMBED support is under development. Stalonetray works with virtually any EWMH-compliant window manager. The behaviour and the look of stalonetray can be configured either via command line options or via configuration file. As usual, command-line options have precedence over options that are specified in the configuration file. Names of command line parameter may have two variants: short () and long (). Write value or value to pass a value using the short name of a parameter; to pass a value using a long name, write value or =value. All flag-like parameters have optional boolean value that when ommited is assumed to be "true". Write "true", "yes", "1", for positive boolean values, and "false", "no", "0" for negative ones. Default configuration file is $HOME/.stalonetrayrc. If it is not found, then $XDG_CONFIG_HOME/stalonetrayrc is checked for. A configuration file contains case-insensetive keyword-argument pairs, one per line. Lines starting with '#' and empty lines are ignored. Alternatively, confiuration file can specified via or command-line options. Stalonetray can be configured to write diagnostic log messages to the standard error stream. Below is the list of possible command line/configuration file options. Options starting with hyphens are command-line parameters others are configuration file keywords. Options that are new in @VERSION_STR@ version are marked with "NEW in @VERSION_STR@". OPTIONS color color color Use color for tray`s background. color can be specified as an HTML hex triplet or as a name from rgb.txt (note that '#' must be quoted). Default value: #777777. filename filename Read configuration from filename instead of default $HOME/.stalonetrayrc. decspec decspec decspec Specify visiblie tray window decorations. Possible values for decspec are: all, title, border, none (default). display display Use X display display. mode mode Set dockapp mode, which can be either simple for e.g. OpenBox, wmaker for WindowMaker, or none (default). level level level Enable fuzzy edges of tray window and set fuzziness level which can range from 0 (disabled, default) to 3. When ommited, the value of level defaults to 2. geometry_spec geometry_spec Set tray`s initial geometry to geometry_spec, specified in standard X notation: widthxheight[+x[+y]], where width and height are specified in icon slot multiples. Default value: 1x1+0-0. gravity gravity Specify icon positioning gravity (eigher N, S, W, E, NW, NE, SW, SE). Grow gravity specifies directions in which the tray's window may grow. For instance, if you specify NW the tray's window will grow down vertically and to the right horizontally (these are sides that are opposite to upper-left or North-West corner of the window); with W the tray's window will grow horizontally to the left side only, and it will vertically grow both upwards and downwards maintaining position of its center. Please note that the latter behaviour is new in 0.8. Default value: NW. gravity gravity Specify icon positioning gravity (either NW, NE, SW, SE). If you specify, e.g SW, then icons will appear starting from the lower-left corner of the tray's window. Default value: NW. n n n Set default icon size to n. Default value: 24. Minimum: 16. Show help message. classes Set X11 window class names to be ignored by stalonetray. Class names are separate arguments: class1 class2 .... level level Set the amount of info to be output by stalonetray to the standard output. Possible values for level: err (default), info, and trace. For the trace option to be available, stalonetray must be configured with at build-time. kludge,kludge Enable specific kludges to work around non-conforming WMs and/or stalonetray bugs. Argument is a comma-separated list of: fix_window_pos — fix tray window position on erroneous moves by WM force_icons_size — ignore resize events on all icons; force their size to be equal to use_icons_hints — use icon window hints to determine icon size geometry_spec geometry_spec Set tray`s maximal geometry to geometry_spec Default value: 0x0, no limit. n n n Set target monitor to n. Default value: 0. bool Do not shrink tray window back after icon removal. Useful when tray is swallowed by another window like FvwmButtons. Default value: false. bool Use the parent's window as a background of the tray's window. Default value: false. path_to_xpm Use the pixmap from an XPM file specified by path_to_xpm for the tray`s window background (pixmap will be tiled to fill the entire window). name name Remote control/click. When this option is specified, stalonetray sends a fake click to the icon with a window named name and exits. The icon is searched for in the currently active tray for the current screen. By default, stalonetray sends a single click with the 1st mouse button to the center of the icon. See the options below for additional information on how to alter the defaults. n Sets the remote click's button number to n (in the X11 numbering order). xxy Sets the remote click's position. type Sets the remote click's type. Possible values: single and double. n wxw wxw Set grid slot width to w and height to b, both of which cannot be less then . By default, the slot size is the same as the icon size. If omitted, height is set to be same as width. mode mode Set scrollbar mode. Possible values: vertical, horizontal, all, or none (default). Scrollbars appear as additional space at tray borders that can be clicked to scroll icon area. Mouse wheel also works. mode mode Set scrollbars highlight mode. Possible values: a color spec, or disable. n n Set scrollbar size to n pixels. By default, the size is 1/4 of . n n Set scrollbar step to n pixels. Default is 1/2 of . bool Enable scrolling outside of the scrollbars too. Default value: false. bool Hide tray`s window from the taskbar. Default value: false. bool Make tray`s window sticky across multiple desktops/pages. Default value: false. bool Set tinting color. Default value: white. level Set tinting level. Default value: 0 (tinting disabled). bool Enable root transparency. Default value: false. bool Use vertical layout of icons (horizontal is used by default). layer layer Sets the EWMH-compliant layer of tray`s window. Possible values for layer: bottom, normal, top. Default value: normal. mode mode Enable window struts for tray window (to avoid covering of tray window by maximized windows). Mode defines to which screen border tray window will be attached. It can be either top, bottom, left, right, none, or auto (default). type type Sets the EWMH-compliant type of tray`s window. Possible values for type: desktop, dock, normal, toolbar, utility. Default value: dock. bool Operate on X server synchronously (SLOW, turned off by default). DEPRECATIONS As of stalonetray 0.8, the following command line and configuration file parameters are deprecated: Please use instead. Please use instead. Please use instead. Please use with use_icon_hints parameter instead. FILES $HOME/.stalonetrayrc — default configuration file. BUGS There are some, definetly. If you need support, the best way to get it is open an issue at the stalonetray github page or to e-mail me directly at busa_ru@users.sourceforge.net. If you have found a bug, please try to reproduce it with the log level set to trace and redirect standard error stream to a file. Then attach the file to a github issue or to an e-mail. If the issue is intermittent, attach two log files -- one with the good and one with the bad behavior. If you have installed stalonetray from a package repository, you can also file a bug in the respective bug tracking system. ================================================ FILE: stalonetrayrc.sample ================================================ # vim: filetype=config textwidth=80 expandtab # # This is sample ~/.stalonetrayrc, resembling default configuration. # Remember: command line parameters take precedence. # #################################################################### # # stalonetray understands following directives # #################################################################### # background # color can be specified as an HTML hex triplet or # as a name from rgb.txt, note that '#' must be # quoted background "#777777" # decorations # set trays window decorations; possible values for # decspec are: all, title, border, none decorations none # display # as usual # dockapp_mode # set dockapp mode, which can be either simple (for # e.g. OpenBox, wmaker for WindowMaker, or none # (default). dockapp_mode none # fuzzy_edges [] # enable fuzzy edges and set fuzziness level. level # can be from 0 (disabled) to 3; this setting works # with tinting and/or transparent and/or pixmap # backgrounds fuzzy_edges 0 # geometry # tray's geometry in standard X notation; width and # height are specified in slot_size multiples geometry 1x1+0+0 # grow_gravity # one of N, S, E, W, NW, NE, SW, SE; tray will grow # in the direction opposite to one specified by # grow_gravity; if horizontal or vertical # direction is not specified, tray will not grow in # that direction grow_gravity NW # icon_gravity # icon placement gravity, one of NW, NE, SW, SE icon_gravity NW # icon_size # specifies dimensions of an icon icon_size 24 # slot_size [x] # specifies width and hight of an icon slot that # contains icon; must be larger than icon size; # height is set to width if it is omitted; # defaults to icon size slot_size 32x64 # log_level # controls the amount of logging output, level can # be err (default), info, or trace (enabled only # when stalonetray configured with --enable-debug) log_level err # kludges kludge[,kludge] # enable specific kludges to work around # non-conforming WMs and/or stalonetray bugs. # Argument is a comma-separated list of # * fix_window_pos - fix tray window position on # erroneous moves by WM # * force_icons_size - ignore resize events on all # icons; force their size to be equal to # icon_size # * use_icon_hints - use icon window hints to # dtermine icon size # max_geometry # maximal tray dimensions; 0 in width/height means # no limit max_geometry 0x0 # monitor # specifies which monitor to draw the tray in # monitor 0 # no_shrink [] # disables shrink-back mode no_shrink false # parent_bg [] # whether to use pseudo-transparency # (looks better when reparented into smth like # FvwmButtons) parent_bg false # pixmap_bg # use pixmap from specified xpm file for (tiled) # background # pixmap_bg /home/user/.stalonetraybg.xpm # scrollbars # enable/disable scrollbars; mode is either # vertical, horizontal, all or none (default) scrollbars none # scrollbars-size # scrollbars step in pixels; default is slot_size/4 # scrollbars-step 8 # scrollbars-step # scrollbars step in pixels; default is slot_size/2 # scrollbars-step 32 # slot_size # specifies size of icon slot, defaults to # icon_size. # skip_taskbar [] # hide tray`s window from the taskbar skip_taskbar true # sticky [] # make a tray`s window sticky across the # desktops/pages sticky true # tint_color # set tinting color tint_color white # tint_level # set tinting level; level ranges from 0 (disabled) # to 255 tint_level 0 # transparent [] # whether to use root-transparency (background # image must be set with Esetroot or compatible # utility) transparent false # vertical [] # whether to use vertical layout (horisontal layout # is used by default) vertical false # window_layer # set the EWMH-compatible window layer; one of: # bottom, normal, top window_layer normal # window_strut # enable/disable window struts for tray window (to # avoid converting of tray window by maximized # windows); mode defines to which screen border # tray will be attached; it can be either top, # bottom, left, right, none or auto (default) window_strut auto # window_type # set the EWMH-compatible window type; one of: # desktop, dock, normal, toolbar, utility window_type dock # xsync [] # whether to operate on X server synchronously # (SLOOOOW) xsync false ================================================ FILE: utils/get_props/main.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * main.c * Tue, 24 Aug 2004 12:00:24 +0700 * ------------------------------- * main is main * ------------------------------- */ /* TODO: XEMBED Implementation wanted */ #include #include #include #include #include int trapped_error_code = 0; static int (*old_error_handler)(Display *, XErrorEvent *); static int error_handler(Display *display, XErrorEvent *error) { trapped_error_code = error->error_code; return 0; } void trap_errors() { trapped_error_code = 0; old_error_handler = XSetErrorHandler(error_handler); } int untrap_errors() { XSetErrorHandler(old_error_handler); return trapped_error_code; } void die(char *msg) { fprintf(stderr, "%s", msg); exit(1); } int main(int argc, char **argv) { Display *dpy; Window wid; XClassHint xch; unsigned long flags; char *tmp; Atom *atoms; int nprops, i; wid = strtol(argv[1], &tmp, 0); if ((dpy = XOpenDisplay(NULL)) == NULL) { die("Error: could not open display\n"); } XSynchronize(dpy, True); atoms = XListProperties(dpy, wid, &nprops); fprintf(stdout, "ATOMS ----------------------------\n"); for (i = 0; i < nprops; i++) { Atom act_type, *atom_list; int act_fmt, j; short *sints; long *lints; unsigned long prop_len, bytes_after; unsigned char *prop, *aname; tmp = XGetAtomName(dpy, atoms[i]); fprintf(stdout, "Atom #%d\n name: '%s'\n", i, tmp); XGetWindowProperty(dpy, wid, atoms[i], 0L, 0L, False, AnyPropertyType, &act_type, &act_fmt, &prop_len, &bytes_after, &prop); prop_len = bytes_after / (act_fmt == 8 ? 1 : (act_fmt == 16 ? 2 : 4)); XGetWindowProperty(dpy, wid, atoms[i], 0L, prop_len, False, act_type, &act_type, &act_fmt, &prop_len, &bytes_after, &prop); fprintf(stdout, " size: %lu\n", prop_len); switch (act_fmt) { case 8: fprintf(stdout, " possible content type is string: %s\n", prop); break; case 16: fprintf( stdout, " possible content type is list of short ints:\n"); sints = (short *)prop; for (j = 0; j < prop_len; j++) fprintf(stdout, " %s[%i] = %d\n", tmp, j, sints[j]); break; case 32: fprintf(stdout, " possible content type is list of long ints:\n"); lints = (long *)prop; for (j = 0; j < prop_len; j++) fprintf(stdout, " %s[%i] = %ld\n", tmp, j, lints[j]); fprintf(stdout, " possible content type is list atoms:\n"); atom_list = (Atom *)prop; for (j = 0; j < prop_len; j++) { trap_errors(); aname = (unsigned char *)XGetAtomName(dpy, atom_list[j]); if (!untrap_errors() && aname != NULL) { fprintf(stdout, " %s[%i] = %s\n", tmp, j, aname); XFree(aname); } } break; default: fprintf(stdout, " WOW: unknown format: %u\n", act_fmt); break; } XFree(prop); XFree(tmp); } fprintf(stdout, "NAMES ----------------------------\n"); XGetClassHint(dpy, wid, &xch); fprintf( stdout, "res_class: %s\nres_name: %s\n", xch.res_class, xch.res_name); fprintf(stdout, "HINTS ----------------------------\n"); { XSizeHints xsh; XGetWMNormalHints(dpy, wid, &xsh, &flags); } return 0; } ================================================ FILE: utils/py-gtk-example/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 Aleksander Alekseev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: utils/py-gtk-example/README.md ================================================ # py-gtk-example ================================================ FILE: utils/py-gtk-example/gtk-example.glade ================================================ True False True False Show pop-up True True False Show/hide window True True False Quit True False GTK Test False True True True False vertical True False True False Text: False True 0 True True True True 1 Show True True True half 0.50999999046325684 0.49000000953674316 False True 2 False False 0 False True False Tab1 False True False vertical True False Nothing here yet False True 0 1 True False Tab2 1 False ================================================ FILE: utils/py-gtk-example/gtk-example.py ================================================ #!/usr/bin/env python3 # gtk-example.py # (c) Aleksander Alekseev 2016 # http://eax.me/ import signal import os import gi gi.require_version('Gtk', '3.0') gi.require_version('Notify', '0.7') from gi.repository import Gtk from gi.repository import Notify APPID = "GTK Test" CURRDIR = os.path.dirname(os.path.abspath(__file__)) # could be PNG or SVG as well ICON = os.path.join(CURRDIR, 'python3.xpm') # Cross-platform tray icon implementation # See: # * http://ubuntuforums.org/showthread.php?t=1923373#post11902222 # * https://github.com/syncthing/syncthing-gtk/blob/master/syncthing_gtk/statusicon.py class TrayIcon: def __init__(self, appid, icon, menu): self.menu = menu APPIND_SUPPORT = 1 try: from gi.repository import AyatanaAppIndicator3 as AppIndicator3 except: APPIND_SUPPORT = 0 if APPIND_SUPPORT == 1: print("Using app indicator") self.ind = AppIndicator3.Indicator.new( appid, icon, AppIndicator3.IndicatorCategory.APPLICATION_STATUS) self.ind.set_status(AppIndicator3.IndicatorStatus.ACTIVE) self.ind.set_menu(self.menu) else: self.ind = Gtk.StatusIcon() self.ind.set_from_file(icon) self.ind.connect('popup-menu', self.onPopupMenu) def onPopupMenu(self, icon, button, time): self.menu.popup(None, None, Gtk.StatusIcon.position_menu, icon, button, time) class Handler: def __init__(self): self.window_is_hidden = False def onShowButtonClicked(self, button): msg = "Clicked: " + entry.get_text() dialog = Gtk.MessageDialog(window, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, msg) dialog.run() dialog.destroy() def onNotify(self, *args): Notify.Notification.new("Notification", "Hello!", ICON).show() def onShowOrHide(self, *args): if self.window_is_hidden: window.show() else: window.hide() self.window_is_hidden = not self.window_is_hidden def onQuit(self, *args): Notify.uninit() Gtk.main_quit() # Handle pressing Ctr+C properly, ignored by default signal.signal(signal.SIGINT, signal.SIG_DFL) builder = Gtk.Builder() builder.add_from_file('gtk-example.glade') builder.connect_signals(Handler()) window = builder.get_object('window1') window.set_icon_from_file(ICON) window.show_all() entry = builder.get_object('entry1') menu = builder.get_object('menu1') icon = TrayIcon(APPID, ICON, menu) Notify.init(APPID) Gtk.main() ================================================ FILE: utils/py-gtk-example/python3.xpm ================================================ /* XPM */ static char * pylogo_xpm[] = { "32 32 316 2", " c None", ". c #8DB0CE", "+ c #6396BF", "@ c #4985B7", "# c #4181B5", "$ c #417EB2", "% c #417EB1", "& c #4D83B0", "* c #6290B6", "= c #94B2CA", "- c #70A1C8", "; c #3D83BC", "> c #3881BD", ", c #387DB6", "' c #387CB5", ") c #387BB3", "! c #3779B0", "~ c #3778AE", "{ c #3776AB", "] c #3776AA", "^ c #3775A9", "/ c #4A7FAC", "( c #709FC5", "_ c #3A83BE", ": c #5795C7", "< c #94B9DB", "[ c #73A4CE", "} c #3D80B7", "| c #387CB4", "1 c #377AB2", "2 c #377AB0", "3 c #3777AC", "4 c #3774A7", "5 c #3773A5", "6 c #3C73A5", "7 c #4586BB", "8 c #4489C1", "9 c #A7C7E1", "0 c #F7F9FD", "a c #E1E9F1", "b c #4C89BC", "c c #3779AF", "d c #3778AD", "e c #3873A5", "f c #4B7CA4", "g c #3982BE", "h c #4389C1", "i c #A6C6E1", "j c #F6F9FC", "k c #D6E4F0", "l c #4A88BB", "m c #3773A6", "n c #366F9F", "o c #366E9D", "p c #376E9C", "q c #4A8BC0", "r c #79A7CD", "s c #548EBD", "t c #387AB0", "u c #3773A4", "v c #366D9C", "w c #387FBA", "x c #387DB7", "y c #387BB4", "z c #3775A8", "A c #366FA0", "B c #4981AF", "C c #427BAA", "D c #3772A4", "E c #376B97", "F c #77A3C8", "G c #4586BC", "H c #3882BE", "I c #3B76A7", "J c #3B76A6", "K c #366E9E", "L c #376B98", "M c #376B96", "N c #5681A3", "O c #F5EEB8", "P c #FFED60", "Q c #FFE85B", "R c #FFE659", "S c #FDE55F", "T c #5592C4", "U c #3A83BF", "V c #3882BD", "W c #387FB9", "X c #3779AE", "Y c #366F9E", "Z c #366C98", "` c #376A94", " . c #5D85A7", ".. c #F5EDB7", "+. c #FFEA5D", "@. c #FFE75A", "#. c #FFE354", "$. c #FDDD56", "%. c #669DC8", "&. c #3885C3", "*. c #3884C2", "=. c #387EB8", "-. c #387CB6", ";. c #377AB1", ">. c #3772A3", ",. c #366D9B", "'. c #F5EBB5", "). c #FFE557", "!. c #FFE455", "~. c #FFDF50", "{. c #FFDB4C", "]. c #FAD862", "^. c #8EB4D2", "/. c #3C86C1", "(. c #3883C0", "_. c #3882BF", ":. c #3881BC", "<. c #3880BB", "[. c #3775AA", "}. c #F5EAB3", "|. c #FFE051", "1. c #FFDE4F", "2. c #FFDA4A", "3. c #FED446", "4. c #F5DF9D", "5. c #77A5CA", "6. c #3885C2", "7. c #387BB2", "8. c #6B8EA8", "9. c #F8E7A1", "0. c #FFE153", "a. c #FFDD4E", "b. c #FFDB4B", "c. c #FFD746", "d. c #FFD645", "e. c #FFD342", "f. c #F6DB8D", "g. c #508DBE", "h. c #3771A3", "i. c #376A95", "j. c #3D6F97", "k. c #C3CBC2", "l. c #FBD964", "m. c #FFDC4D", "n. c #FFD544", "o. c #FFD040", "p. c #F9CF58", "q. c #3F83BB", "r. c #376B95", "s. c #3A6C95", "t. c #4E7BA0", "u. c #91AABC", "v. c #F6E4A3", "w. c #FFDA4B", "x. c #FFD646", "y. c #FFD443", "z. c #FFD241", "A. c #FFCE3D", "B. c #FFCC3B", "C. c #FCC83E", "D. c #3880BC", "E. c #3C79AC", "F. c #5F8DB4", "G. c #7AA0C0", "H. c #82A6C3", "I. c #82A3BF", "J. c #82A2BE", "K. c #82A1BB", "L. c #82A1B9", "M. c #8BA4B5", "N. c #C1C5AE", "O. c #F2E19F", "P. c #FDD74C", "Q. c #FFD94A", "R. c #FFD343", "S. c #FFCE3E", "T. c #FFCB39", "U. c #FFC937", "V. c #FEC636", "W. c #3D79AB", "X. c #9DB6C6", "Y. c #D0CFA2", "Z. c #EFE598", "`. c #F8EE9B", " + c #F8EB97", ".+ c #F8E996", "++ c #F8E894", "@+ c #FAE489", "#+ c #FCDB64", "$+ c #FFDA4D", "%+ c #FFCF3E", "&+ c #FFCB3A", "*+ c #FFC734", "=+ c #FFC532", "-+ c #3F82B7", ";+ c #387EB9", ">+ c #9EB9D0", ",+ c #F2E287", "'+ c #FDEB69", ")+ c #FEEC60", "!+ c #FFEB5E", "~+ c #FFE254", "{+ c #FFE152", "]+ c #FFD747", "^+ c #FFC633", "/+ c #FCC235", "(+ c #578FBE", "_+ c #6996BC", ":+ c #DED9A8", "<+ c #FEEC62", "[+ c #FFE658", "}+ c #FFDF51", "|+ c #FFDE50", "1+ c #FFD03F", "2+ c #FFCD3C", "3+ c #FFC431", "4+ c #FFBF2C", "5+ c #FAC244", "6+ c #85AACA", "7+ c #A1BBD2", "8+ c #F7E47C", "9+ c #FFE456", "0+ c #FFC735", "a+ c #FFBC29", "b+ c #F7D280", "c+ c #9DBAD2", "d+ c #3B7CB2", "e+ c #ABC2D6", "f+ c #FDEB7B", "g+ c #FFC12E", "h+ c #FDBD30", "i+ c #F4DEA8", "j+ c #5F91BA", "k+ c #ABC1D4", "l+ c #FDEE7E", "m+ c #FFE253", "n+ c #FFCC3C", "o+ c #FFBA27", "p+ c #FAC75B", "q+ c #4A82B0", "r+ c #3877AB", "s+ c #3774A6", "t+ c #AAC0D4", "u+ c #FDEE7D", "v+ c #FFEC5F", "w+ c #FFE255", "x+ c #FFD848", "y+ c #FFD444", "z+ c #FFCF3F", "A+ c #FFBC2A", "B+ c #FFBB28", "C+ c #FDBA32", "D+ c #447AA8", "E+ c #4379A7", "F+ c #FFE95C", "G+ c #FFE558", "H+ c #FFE355", "I+ c #FED84B", "J+ c #FCD149", "K+ c #FBCE47", "L+ c #FBCD46", "M+ c #FBC840", "N+ c #FBC63E", "O+ c #FBC037", "P+ c #FAC448", "Q+ c #FDD44C", "R+ c #FCD14E", "S+ c #FFC836", "T+ c #FFC22F", "U+ c #FFC02D", "V+ c #FFE052", "W+ c #FFC636", "X+ c #FFCF5C", "Y+ c #FFD573", "Z+ c #FFC33E", "`+ c #FEBD2D", " @ c #FFDB4D", ".@ c #FFD949", "+@ c #FFD545", "@@ c #FFD140", "#@ c #FFCB48", "$@ c #FFF7E4", "%@ c #FFFCF6", "&@ c #FFE09D", "*@ c #FFBA2E", "=@ c #FDBE2F", "-@ c #FFD748", ";@ c #FFCA38", ">@ c #FFC844", ",@ c #FFF2D7", "'@ c #FFF9EC", ")@ c #FFDB94", "!@ c #FFB92D", "~@ c #FAC54D", "{@ c #FDD54E", "]@ c #FFBD2D", "^@ c #FFC858", "/@ c #FFD174", "(@ c #FFBF3E", "_@ c #FCBD3C", ":@ c #FAD66A", "<@ c #FECD3F", "[@ c #FFC330", "}@ c #FFBD2A", "|@ c #FFB724", "1@ c #FFB521", "2@ c #FFB526", "3@ c #FBC457", "4@ c #F7E09E", "5@ c #F8D781", "6@ c #FAC349", "7@ c #FCC134", "8@ c #FEBE2C", "9@ c #FBBE3F", "0@ c #F7CF79", "a@ c #F5D795", " . + @ # $ % % & * = ", " - ; > > , ' ) ! ~ { ] ^ / ", " ( _ : < [ } | 1 2 ~ 3 4 5 5 6 ", " 7 8 9 0 a b 2 c d 3 { 5 5 5 e f ", " g h i j k l c ~ { { m 5 5 n o p ", " > > q r s t c c d 4 5 u n v v v ", " w x ' y 2 c d d z 5 u A v v v v ", " B C 5 D v v v v E ", " F G H H H x ' ) c c c d I J 5 K v v L M N O P Q R S ", " T U H V V W ' ) c c X ~ 5 5 5 Y v v Z ` ` ...+.@.#.#.$. ", " %.&.*.> w W =.-.;.c 3 { ^ 5 5 >.o v ,.E ` ` .'.).!.#.~.{.]. ", "^./.(._.:.<., ' ) ;.X d [.5 5 >.K v ,.E ` ` ` .}.#.|.1.{.2.3.4.", "5.6.(.H H x ' 7.c c 3 3 4 5 D K v v ,.` ` ` ` 8.9.0.a.b.c.d.e.f.", "g._.> <.w ' ' | 2 3 { z 5 5 h.v v v i.` ` ` j.k.l.m.{.d.n.e.o.p.", "q.> > :.-.' 1 c c c ] 5 5 >.v v ,.r.` ` s.t.u.v.{.w.x.y.z.A.B.C.", "D.D.w -.' 1 c c c E.F.G.H.I.J.J.K.L.L.L.M.N.O.P.Q.c.R.S.B.T.U.V.", "D.D.=.' ' 1 c c W.X.Y.Z.`.`.`.`.`. +.+++@+#+$+Q.d.R.%+B.&+*+=+=+", "-+;+-.' ;.2 c c >+,+'+)+P P P !+Q R ~+{+1.{.]+d.y.%+B.&+^+=+=+/+", "(+' ' ;.c X X _+:+<+P P P P !+R [+~+}+|+{.]+n.R.1+2+&+^+=+3+4+5+", "6+' ) ! ~ { { 7+8+P P P P !+R 9+#.{+{.w.]+y.z.S.&+0+=+=+3+4+a+b+", "c+d+7.! d 3 z e+f+P P P !+R 9+#.{+m.{.]+y.1+B.&+0+=+=+g+4+a+h+i+", " j+c d 3 { 4 k+l+P P !+@.9+m+1.m.{.]+y.1+n+B.*+=+=+g+a+a+o+p+ ", " q+r+{ s+m t+u+v+@.R w+{+}+{.x+d.y+z+n+B.0+=+=+g+A+a+B+C+ ", " * D+E+E+ +.F+G+H+}+}+{.I+J+K+L+M+M+M+M+N+O+O+O+O+P+ ", " ).).#.{+a.{.x+Q+R+ ", " #.m+1.a.{.x+y.o.2+B.S+=+=+T+U+O+ ", " 0.V+{.{.x+n.o.2+B.B.W+X+Y+Z+a+`+ ", " @{..@+@n.@@B.B.S+^+#@$@%@&@*@=@ ", " ].-@x.y.o.%+;@S+=+=+>@,@'@)@!@~@ ", " {@z.z+2+U.=+=+=+T+]@^@/@(@_@ ", " :@<@U.=+=+[@4+}@|@1@2@3@ ", " 4@5@6@7@8@a+a+9@0@a@ "}; ================================================ FILE: utils/tray-test-fdo/main.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * main.c * Tue, 24 Aug 2004 12:00:24 +0700 * ------------------------------- * main is main * ------------------------------- */ /* TODO: XEMBED Implementation wanted */ #include #include #include #include #include #include #include /* from System Tray Protocol Specification * http://freedesktop.org/Standards/systemtray-spec/systemtray-spec-0.1.html */ #define SYSTEM_TRAY_REQUEST_DOCK 0 #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 #define TRAY_SEL_ATOM "_NET_SYSTEM_TRAY_S" #include "xembed.h" #define GROW_PERIOD 50 /* just globals */ Display *dpy; Window wnd; Window wnd1; Window tray; int reparent_out = 0; int grow = 0; int grow_cd = GROW_PERIOD; int maintain_size = 1; XSizeHints xsh = {flags : (PSize | PPosition), x : 100, y : 100, width : 20, height : 20}; Atom xa_xembed_info; Atom xa_tray_selection; Atom xa_tray_opcode; Atom xa_tray_data; Atom xa_wm_delete_window; Atom xa_wm_protocols; Atom xa_wm_take_focus; Atom xa_xembed; void die(char *msg) { fprintf(stderr, "%s", msg); exit(1); } int main(int argc, char **argv) { XClassHint xch; XWMHints xwmh = { flags : (InputHint | StateHint), input : True, initial_state : NormalState, icon_pixmap : None, icon_window : None, icon_x : 0, icon_y : 0, icon_mask : None, window_group : 0 }; char *wnd_name = "test_tray_icon"; XTextProperty wm_name; XWindowAttributes xwa; XEvent ev; XColor xc_black = {flags : 0, pad : 0, pixel : 0x000000, red : 0, green : 0, blue : 0}; XColor xc_bg = {flags : 0, pad : 0, pixel : 0x777777, red : 0, green : 0, blue : 0}; char *tray_sel_atom_name; char *tmp; long mwm_hints[5] = {2, 0, 0, 0, 0}; Atom mwm_wm_hints; char *dpy_name = NULL; char *bg_color_name = NULL; while (--argc > 0) { ++argv; if (!strcmp(argv[0], "-w")) { argv++; argc--; xsh.width = strtol(argv[0], &tmp, 0); } if (!strcmp(argv[0], "-h")) { argv++; argc--; xsh.height = strtol(argv[0], &tmp, 0); } if (!strcmp(argv[0], "-r")) { argv++; argc--; reparent_out = 1; } if (!strcmp(argv[0], "-c")) { argv++; argc--; bg_color_name = argv[0]; } if (!strcmp(argv[0], "-g")) { grow = 1; } if (!strcmp(argv[0], "-gg")) { grow = 2; } if (!strcmp(argv[0], "-n")) { maintain_size = 0; } } if ((dpy = XOpenDisplay(dpy_name)) == NULL) { die("Error: could not open display\n"); } if (bg_color_name != NULL) { XParseColor(dpy, XDefaultColormap(dpy, DefaultScreen(dpy)), bg_color_name, &xc_bg); XAllocColor(dpy, XDefaultColormap(dpy, DefaultScreen(dpy)), &xc_bg); } XSynchronize(dpy, True); if ((tray_sel_atom_name = (char *)malloc(strlen(TRAY_SEL_ATOM) + 2)) == NULL) { die("Error: low mem\n"); } snprintf(tray_sel_atom_name, strlen(TRAY_SEL_ATOM) + 2, "%s%u", TRAY_SEL_ATOM, DefaultScreen(dpy)); xa_tray_selection = XInternAtom(dpy, tray_sel_atom_name, False); free(tray_sel_atom_name); xa_tray_opcode = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False); xa_tray_data = XInternAtom(dpy, "_NET_SYSTEM_TRAY_MESSAGE_DATA", False); xa_xembed_info = XInternAtom(dpy, "_XEMBED_INFO", False); printf("_XEMBED_INFO = 0x%lx\n", xa_xembed_info); xa_xembed = XInternAtom(dpy, "_XEMBED", False); xch.res_class = wnd_name; xch.res_name = wnd_name; trap_errors(); wnd = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), xsh.x, xsh.y, xsh.width, xsh.height, 0, 0, xc_black.pixel); wnd1 = XCreateSimpleWindow( dpy, wnd, 1, 1, xsh.width - 2, xsh.height - 2, 0, 0, xc_bg.pixel); if (untrap_errors()) { die("Error: could not create simple window\n"); } else { printf("wid=0x%lx\n", wnd); } XmbTextListToTextProperty(dpy, &wnd_name, 1, XTextStyle, &wm_name); XSetWMProperties(dpy, wnd, &wm_name, NULL, argv, argc, &xsh, &xwmh, &xch); if ((tray = XGetSelectionOwner(dpy, xa_tray_selection)) == None) { printf("Error: no tray found\n"); } else { xclient_msg32(dpy, tray, xa_tray_opcode, CurrentTime, SYSTEM_TRAY_REQUEST_DOCK, wnd, 0, 0); } XSelectInput(dpy, wnd, SubstructureNotifyMask | StructureNotifyMask | PropertyChangeMask | ButtonPressMask | ButtonReleaseMask); XMapRaised(dpy, wnd); XMapRaised(dpy, wnd1); XFlush(dpy); printf("::::: wid: 0x%lx, color: %s\n", wnd, bg_color_name != NULL ? bg_color_name : "gray"); if (reparent_out) { usleep(5000000L); fflush(stdout); printf("reparenting out\n"); fflush(stdout); XReparentWindow(dpy, wnd, DefaultRootWindow(dpy), 100, 100); } for (;;) { while (XPending(dpy)) { XNextEvent(dpy, &ev); switch (ev.type) { case DestroyNotify: return 0; case ConfigureNotify: if (maintain_size && (ev.xconfigure.width != xsh.width || ev.xconfigure.height != xsh.height)) { printf("0x%lx: configured: %dx%d\n", wnd, ev.xconfigure.width, ev.xconfigure.height); printf("0x%lx: maintaining size\n", wnd); XResizeWindow(dpy, wnd, xsh.width, xsh.height); } break; case ButtonPress: case ButtonRelease: printf("%s: btn=%d, state=0x%x, x=%d, y=%d, send_event=%d\n", ev.type == ButtonPress ? "ButtonPress" : "ButtonRelease", ev.xbutton.button, ev.xbutton.state, ev.xbutton.x, ev.xbutton.y, ev.xbutton.send_event); break; default: break; } } if (grow == 1) { grow_cd--; if (!grow_cd) { printf("0x%lx: size increased 2x\n", wnd); XResizeWindow(dpy, wnd, xsh.width * 2, xsh.height * 2); XResizeWindow( dpy, wnd1, xsh.width * 2 - 2, xsh.height * 2 - 2); xsh.width *= 2; xsh.height *= 2; grow = 0; } } else if (grow == 2) { grow_cd--; if (grow_cd == 0) { printf("0x%lx: size increased 2x\n", wnd); XResizeWindow(dpy, wnd, xsh.width * 2, xsh.height * 2); XResizeWindow( dpy, wnd1, xsh.width * 2 - 2, xsh.height * 2 - 2); xsh.width *= 2; xsh.height *= 2; } else if (grow_cd == -GROW_PERIOD) { printf("0x%lx: size shrinked 2x\n", wnd); xsh.width /= 2; xsh.height /= 2; XResizeWindow(dpy, wnd, xsh.width, xsh.height); XResizeWindow(dpy, wnd1, xsh.width - 2, xsh.height - 2); grow = 0; } } usleep(50000L); } return 0; } ================================================ FILE: utils/tray-test-fdo/run_icons ================================================ #!/bin/bash [[ $# -gt 0 ]] && num=$1 [[ $# -eq 0 ]] && num=2 for ((ts=0; $ts < $num; ts=ts+1)); do ./tray-test-fdo & done echo echo ================================================ FILE: utils/tray-test-fdo/seq1 ================================================ #!/bin/sh ./tray-test-fdo -w 22 -h 22 -c blue 2>&1 & ./tray-test-fdo -w 22 -h 22 -c green 2>&1 & ./tray-test-fdo -w 22 -h 22 -c magenta -gg 2>&1 & sleep 1 ./tray-test-fdo -w 22 -h 22 -c yellow 2>&1 & ./tray-test-fdo -w 22 -h 22 -c black 2>&1 & ================================================ FILE: utils/tray-test-fdo/seq2 ================================================ #!/bin/sh ./tray-test-fdo -w 22 -h 22 -c blue 2>&1 & ./tray-test-fdo -w 22 -h 22 -c green 2>&1 & ./tray-test-fdo -w 22 -h 22 -c red 2>&1 & ./tray-test-fdo -w 22 -h 22 -c yellow 2>&1 & ================================================ FILE: utils/tray-test-fdo/xembed.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * icons.h * Tue, 24 Aug 2004 12:05:38 +0700 * ------------------------------- * XEMBED protocol implementation * -------------------------------*/ #include "xembed.h" /* error handling code taken from xembed spec */ int trapped_error_code = 0; static int (*old_error_handler)(Display *, XErrorEvent *); static int error_handler(Display *display, XErrorEvent *error) { trapped_error_code = error->error_code; return 0; } void trap_errors() { trapped_error_code = 0; old_error_handler = XSetErrorHandler(error_handler); } int untrap_errors() { XSetErrorHandler(old_error_handler); return trapped_error_code; } int xclient_msg32(Display *dpy, Window dst, Atom type, long data0, long data1, long data2, long data3, long data4) { XEvent ev; ev.type = ClientMessage; ev.xclient.window = dst; ev.xclient.message_type = type; ev.xclient.format = 32; ev.xclient.data.l[0] = data0; ev.xclient.data.l[1] = data1; ev.xclient.data.l[2] = data2; ev.xclient.data.l[3] = data3; ev.xclient.data.l[4] = data4; return XSendEvent(dpy, dst, False, NoEventMask, &ev); } int xembed_msg( Display *dpy, Window dst, long msg, long detail, long data1, long data2) { int status; static Atom xa_xembed = None; trap_errors(); if (xa_xembed == None) xa_xembed = XInternAtom(dpy, "_XEMBED", False); status = xclient_msg32( dpy, dst, xa_xembed, CurrentTime, msg, detail, data1, data2); if (untrap_errors()) return trapped_error_code; return status; } unsigned long xembed_get_info(Display *dpy, Window src, long *version) { Atom xa_xembed_info, act_type; int act_fmt; unsigned long nitems, bytesafter; unsigned char *data; xa_xembed_info = XInternAtom(dpy, "_XEMBED_INFO", False); trap_errors(); XGetWindowProperty(dpy, src, xa_xembed_info, 0, 2, False, xa_xembed_info, &act_type, &act_fmt, &nitems, &bytesafter, &data); if (untrap_errors()) /* FIXME: must somehow indicate a error */ return 0; if (act_type != xa_xembed_info) { if (version != NULL) *version = 0; return 0; } if (nitems < 2) /* FIXME: the same */ return 0; if (version != NULL) *version = ((unsigned long *)data)[0]; XFree(data); return ((unsigned long *)data)[1]; } int xembed_embeded_notify(Display *dpy, Window src, Window dst) { return xembed_msg(dpy, dst, XEMBED_EMBEDDED_NOTIFY, 0, src, 0); } int xembed_window_activate(Display *dpy, Window dst) { return xembed_msg(dpy, dst, XEMBED_WINDOW_ACTIVATE, 0, 0, 0); } int xembed_window_deactivate(Display *dpy, Window dst) { return xembed_msg(dpy, dst, XEMBED_WINDOW_DEACTIVATE, 0, 0, 0); } int xembed_focus_in(Display *dpy, Window dst, int focus) { return xembed_msg(dpy, dst, XEMBED_FOCUS_IN, focus, 0, 0); } int xembed_focus_out(Display *dpy, Window dst) { return xembed_msg(dpy, dst, XEMBED_FOCUS_OUT, 0, 0, 0); } ================================================ FILE: utils/tray-test-fdo/xembed.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * icons.h * Tue, 24 Aug 2004 12:05:38 +0700 * ------------------------------- * XEMBED protocol implementation * -------------------------------*/ #ifndef _XEMBED_H_ #define _XEMBED_H_ #include #include /* XEMBED messages */ #define XEMBED_EMBEDDED_NOTIFY 0 #define XEMBED_WINDOW_ACTIVATE 1 #define XEMBED_WINDOW_DEACTIVATE 2 #define XEMBED_REQUEST_FOCUS 3 #define XEMBED_FOCUS_IN 4 #define XEMBED_FOCUS_OUT 5 #define XEMBED_FOCUS_NEXT 6 #define XEMBED_FOCUS_PREV 7 /* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */ #define XEMBED_MODALITY_ON 10 #define XEMBED_MODALITY_OFF 11 #define XEMBED_REGISTER_ACCELERATOR 12 #define XEMBED_UNREGISTER_ACCELERATOR 13 #define XEMBED_ACTIVATE_ACCELERATOR 14 /* Details for XEMBED_FOCUS_IN: */ #define XEMBED_FOCUS_CURRENT 0 #define XEMBED_FOCUS_FIRST 1 #define XEMBED_FOCUS_LAST 2 /* Modifiers field for XEMBED_REGISTER_ACCELERATOR */ #define XEMBED_MODIFIER_SHIFT (1 << 0) #define XEMBED_MODIFIER_CONTROL (1 << 1) #define XEMBED_MODIFIER_ALT (1 << 2) #define XEMBED_MODIFIER_SUPER (1 << 3) #define XEMBED_MODIFIER_HYPER (1 << 4) /* Flags for XEMBED_ACTIVATE_ACCELERATOR */ #define XEMBED_ACCELERATOR_OVERLOADED (1 << 0) /* Directions for focusing */ #define XEMBED_DIRECTION_DEFAULT 0 #define XEMBED_DIRECTION_UP_DOWN 1 #define XEMBED_DIRECTION_LEFT_RIGHT 2 /* Flags for _XEMBED_INFO */ #define XEMBED_MAPPED (1 << 0) /* error handling */ extern int trapped_error_code; void trap_errors(); int untrap_errors(); /* utils */ int xclient_msg32(Display *dpy, Window dst, Atom type, long data0, long data1, long data2, long data3, long data4); int xembed_msg( Display *dpy, Window dst, long msg, long detail, long data1, long data2); /* xembed funcs */ unsigned long xembed_get_info(Display *dpy, Window dst, long *version); int xembed_embeded_notify(Display *dpy, Window src, Window dst); int xembed_window_activate(Display *dpy, Window dst); int xembed_window_deactivate(Display *dpy, Window dst); int xembed_focus_in(Display *dpy, Window dst, int focus); int xembed_focus_out(Display *dpy, Window dst); #endif ================================================ FILE: utils/tray-test-gtk/traytest ================================================ #!/usr/bin/env python # vim:et import gtk, pygtk, gobject import os def send_signal(sig = 1): pass # os.system('pkill -USR%s stalonetray' % sig) def show_menu(status_icon, button, activate_time, menu): menu.popup(None, None, gtk.status_icon_position_menu, button, activate_time, status_icon) def hide_icon(widget): print "hide" send_signal(2) tray_icon.set_visible(False) gobject.timeout_add(500, show_icon, None) def show_icon(widget): print "show" send_signal(2) tray_icon.set_visible(True) # gobject.timeout_add(500, hide_icon, None) return False def notify_about_resize(icon, size, **args): print "notify_about_resize:", size return False send_signal(2) tray_menu = gtk.Menu() menu_hide = gtk.ImageMenuItem(gtk.STOCK_REMOVE) menu_hide.connect("activate", hide_icon) tray_menu.append(menu_hide) menu_quit = gtk.ImageMenuItem(gtk.STOCK_CLOSE) menu_quit.connect("activate", lambda w: gtk.main_quit()) tray_menu.append(menu_quit) tray_menu.show_all() send_signal(2) tray_icon = gtk.status_icon_new_from_stock(gtk.STOCK_DIALOG_ERROR) tray_icon.connect('popup-menu', show_menu, tray_menu) tray_icon.connect('size-changed', notify_about_resize); tray_icon.set_tooltip("Right click me!") #gobject.timeout_add(500, hide_icon, None) send_signal() gtk.main() ================================================ FILE: utils/tray-xembed-test/common.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * common.h * Mon, 01 May 2006 01:45:08 +0700 * ------------------------------- * Common declarations * -------------------------------*/ #ifndef _COMMON_H_ #define _COMMON_H_ #define DBG(level, message) print_message_to_stderr message /* Meaningful names for return values */ #define SUCCESS 1 #define FAILURE 0 /* Meaningful names for return values of icon mass-operations */ #define MATCH 1 #define NO_MATCH 0 /* Portable macro for function name */ #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define __FUNC__ __func__ #elif defined(__GNUC__) && __GNUC__ >= 3 #define __FUNC__ __FUNCTION__ #else #define __FUNC__ "unknown" #endif /* Print a error message */ #ifdef DEBUG #define ERR(message) DBG(0, message) #else #define ERR(message) print_message_to_stderr message #endif /* Print a message and... DIE */ #define DIE(message) do { ERR(message); exit(-1); } while(0) /* WARNING: feed following macro only with * side-effects-free expressions */ /* Get a value within target interval */ #define cutoff(tgt,min,max) (tgt) < (min) ? (min) : ((tgt) > (max) ? max : tgt) /* Update value to fit into target interval */ #define val_range(tgt,min,max) (tgt) = cutoff(tgt,min,max) #endif ================================================ FILE: utils/tray-xembed-test/debug.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * debug.c * Mon, 01 May 2006 01:44:42 +0700 * ------------------------------- * Debugging code/utilities * -------------------------------*/ #include #include #include #include /* Print the message to STDERR (varadic macros is not used in the sake of portability) */ void print_message_to_stderr(const char *fmt,...) { static char msg[PATH_MAX]; va_list va; va_start(va, fmt); vsnprintf(msg, PATH_MAX, fmt, va); va_end(va); fprintf(stderr, msg); } ================================================ FILE: utils/tray-xembed-test/main.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * main.c * Tue, 24 Aug 2004 12:00:24 +0700 * ------------------------------- * main is main * ------------------------------- */ #include #include #include #include #include #include #include /* from System Tray Protocol Specification * http://freedesktop.org/Standards/systemtray-spec/systemtray-spec-0.1.html */ #define SYSTEM_TRAY_REQUEST_DOCK 0 #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 #define TRAY_SEL_ATOM "_NET_SYSTEM_TRAY_S" #include "common.h" #include "xutils.h" #include "xembed.h" /* just globals */ Display *dpy; Window wnd; Window buttons[4]; CARD32 xembed_data[2]; int has_focus = 0; int sel_idx = 0; int embedded = 0; int do_not_accept_focus = 0; int request_focus = 0; int register_accel = 0; CARD32 accel_id = 0x11ff11ff; CARD32 accel_sym = XK_Page_Down; CARD32 accel_mods = 0x14; /* Ctrl */ Window tray = None; CARD32 current_xembed_timestamp; XColor wnd_bg_embedded = {.pixel = 0x000000}; XColor wnd_bg_unembedded = {.pixel = 0x770000}; XColor buttons_focused = {.pixel = 0x999999}; XColor buttons_unfocused = {.pixel = 0x666666}; XColor buttons_selected = {.pixel = 0x0000ff}; XSizeHints xsh = { flags: (PSize | PPosition), x: 100, y: 100, width: 24, height: 24 }; Atom xa_xembed_info; Atom xa_tray_selection; Atom xa_tray_opcode; Atom xa_tray_data; Atom xa_wm_delete_window; Atom xa_wm_protocols; Atom xa_wm_take_focus; Atom xa_xembed; void create_window(int argc, char **argv) { #if 0 XWMHints xwmh = { flags: (InputHint | StateHint ), input: True, initial_state: NormalState, icon_pixmap: None, icon_window: None, icon_x: 0, icon_y: 0, icon_mask: None, window_group: 0 }; #else XWMHints *xwmh; #endif char *wnd_name = "test_tray_icon"; XTextProperty wm_name; XClassHint *xch; XWindowAttributes xwa; int x, y; xch = XAllocClassHint(); xch->res_class = wnd_name; xch->res_name = wnd_name; wnd = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), xsh.x, xsh.y, xsh.width, xsh.height, 0, 0, wnd_bg_unembedded.pixel); printf("Created window: 0x%x\n", wnd); for (x = 0; x < 2; x++) for (y = 0; y < 2; y++) { buttons[x + y * 2] = XCreateSimpleWindow(dpy, wnd, 2 + x * 12, 2 + y * 12, 8, 8, 0, 0, buttons_unfocused.pixel); XMapRaised(dpy, buttons[x + y * 2]); } if (!x11_ok()) { DIE(("Error: could not create simple window\n")); } xembed_data[0] = 0; xembed_data[1] = 1; xembed_post_data(dpy, wnd, xembed_data); XmbTextListToTextProperty(dpy, &wnd_name, 1, XTextStyle, &wm_name); xwmh = XAllocWMHints(); xwmh->flags = InputHint | StateHint | WindowGroupHint | IconWindowHint; xwmh->flags = True; xwmh->initial_state = NormalState; xwmh->window_group = 0; xwmh->icon_window = None; XSetWMProperties(dpy, wnd, &wm_name, NULL, argv, argc, &xsh, xwmh, xch); if ((tray = XGetSelectionOwner(dpy, xa_tray_selection)) == None) { printf("Error: no tray found\n"); } else { x11_send_client_msg32(dpy, tray, wnd, xa_tray_opcode, CurrentTime, SYSTEM_TRAY_REQUEST_DOCK, wnd, 0, 0); } XSelectInput(dpy, wnd, SubstructureNotifyMask | StructureNotifyMask | PropertyChangeMask | KeyReleaseMask | ButtonReleaseMask ); XMapRaised(dpy, wnd); } void redraw_buttons() { int i; unsigned long pixel = 0; for (i = 0; i < 4; i++) { if (has_focus) { if (i == sel_idx) pixel = buttons_selected.pixel; else pixel = buttons_focused.pixel; } else pixel = buttons_unfocused.pixel; XSetWindowBackground(dpy, buttons[i], pixel); XClearWindow(dpy, buttons[i]); } } void focus_in(int how) { DBG(0, ("got focus\n")); if (do_not_accept_focus) { if (how == XEMBED_FOCUS_FIRST) xembed_send_focus_next(dpy, tray, current_xembed_timestamp); else xembed_send_focus_prev(dpy, tray, current_xembed_timestamp); } else { has_focus = 1; if (how == XEMBED_FOCUS_FIRST) sel_idx = 0; else if (how == XEMBED_FOCUS_LAST) sel_idx = 3; redraw_buttons(); } } void focus_out() { DBG(0, ("lost focus\n")); has_focus = 0; redraw_buttons(); } void focus_next() { if (!embedded) { sel_idx = (sel_idx + 1) % 4; redraw_buttons(); } else { if (sel_idx < 3) { sel_idx++; redraw_buttons(); } else xembed_send_focus_next(dpy, tray, x11_get_server_timestamp(dpy, wnd)); } } void focus_prev() { DBG(0, ("focus_prev\n")); if (!embedded) { sel_idx = (4 + (sel_idx - 1)) % 4; redraw_buttons(); } else { if (sel_idx > 0) { sel_idx--; redraw_buttons(); } else xembed_send_focus_prev(dpy, tray, x11_get_server_timestamp(dpy, wnd)); }; DBG(0, ("sel_idx=%d\n", sel_idx)); } void client_event(XClientMessageEvent ev) { char *msg_type_name; msg_type_name = XGetAtomName(dpy, ev.message_type); if (msg_type_name != NULL) { DBG(3, ("message name: \"%s\"\n", msg_type_name)); XFree(msg_type_name); } if (ev.message_type == xa_xembed) { DBG(3, ("XEMBED message opcode: %d\n", ev.data.l[1])); current_xembed_timestamp = ev.data.l[0]; if (current_xembed_timestamp == CurrentTime) current_xembed_timestamp = x11_get_server_timestamp(dpy, wnd); switch (ev.data.l[1]) { case XEMBED_EMBEDDED_NOTIFY: embedded = 1; XSetWindowBackground(dpy, wnd, wnd_bg_embedded.pixel); XClearWindow(dpy, wnd); redraw_buttons(); if (request_focus) xembed_send_request_focus(dpy, tray, x11_get_server_timestamp(dpy, wnd)); break; case XEMBED_FOCUS_OUT: focus_out(); break; case XEMBED_FOCUS_IN: focus_in(ev.data.l[2]); break; case XEMBED_ACTIVATE_ACCELERATOR: DBG(3, ("Got ACTIVATE_ACCELERATOR message, id=0x%x, overloaded=0x%x", ev.data.l[2], ev.data.l[3])); break; } } } void key_release(XKeyReleasedEvent ev) { char buf[20]; KeySym keysym; XLookupString(&ev, buf, 20, &keysym, NULL); DBG(0, ("key_release: code=%d, mask=0x%x, sym=0x%x\n", ev.keycode, ev.state, keysym)); switch (keysym) { case XK_Tab: focus_next(); break; case XK_ISO_Left_Tab: focus_prev(); break; default: break; } } int main(int argc, char** argv) { XEvent ev; char *tray_sel_atom_name; char *dpy_name = NULL; int i; for (i = 1; i < argc; i++) if (!strcmp(argv[i], "-n")) do_not_accept_focus = 1; else if (!strcmp(argv[i], "-r")) request_focus = 1; else if (!strcmp(argv[i], "-a")) register_accel = 1; if ((dpy = XOpenDisplay(dpy_name)) == NULL) { DIE(("Error: could not open display\n")); } x11_trap_errors(); if ((tray_sel_atom_name = (char *)malloc(strlen(TRAY_SEL_ATOM) + 2)) == NULL) { DIE(("OOM\n")); } snprintf(tray_sel_atom_name, strlen(TRAY_SEL_ATOM) + 2, "%s%u", TRAY_SEL_ATOM, DefaultScreen(dpy)); xa_tray_selection = XInternAtom(dpy, tray_sel_atom_name, False); free(tray_sel_atom_name); xa_tray_opcode = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False); xa_tray_data = XInternAtom(dpy, "_NET_SYSTEM_TRAY_MESSAGE_DATA", False); xa_xembed_info = XInternAtom(dpy, "_XEMBED_INFO", False); xa_xembed = XInternAtom(dpy, "_XEMBED", False); create_window(argc, argv); if (register_accel) xembed_send_register_accelerator(dpy, tray, x11_get_server_timestamp(dpy, wnd), accel_id, accel_sym, accel_mods); XFlush(dpy); redraw_buttons(); for(;;) { XNextEvent(dpy, &ev); switch(ev.type) { case ClientMessage: client_event(ev.xclient); break; /* case DestroyNotify:*/ /* return 0;*/ case ConfigureNotify: /* Maintain size */ if (ev.xconfigure.width != xsh.width || ev.xconfigure.height != xsh.height) XResizeWindow(dpy, wnd, xsh.width, xsh.height); break; case KeyRelease: key_release(ev.xkey); break; case ButtonRelease: xembed_send_request_focus(dpy, tray, x11_get_server_timestamp(dpy, wnd)); break; case UnmapNotify: if (ev.xunmap.window == wnd) XMapRaised(dpy, wnd); default: break; } } return 0; } ================================================ FILE: utils/tray-xembed-test/run ================================================ #!/bin/sh ./tray-xembed-test & ./tray-xembed-test -n & ./tray-xembed-test & ./tray-xembed-test -n & ================================================ FILE: utils/tray-xembed-test/xembed.c ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * xembed.c * Tue, 24 Aug 2004 12:05:38 +0700 * ------------------------------- * XEMBED protocol implementation * -------------------------------*/ #include #include #include #include #include "common.h" #include "xembed.h" #include "xutils.h" extern Atom xa_xembed; extern Atom xa_xembed_info; int xembed_retrive_data(Display *dpy, Window w, CARD32 *data) { Atom act_type; int act_fmt; unsigned long nitems, bytesafter; unsigned char *tmpdata; int rc; XGetWindowProperty(dpy, w, xa_xembed_info, 0, 2, False, xa_xembed_info, &act_type, &act_fmt, &nitems, &bytesafter, &tmpdata); if (!x11_ok()) return XEMBED_RESULT_X11ERROR; rc = (x11_ok() && act_type == xa_xembed_info && nitems == 2); if (rc) { data[0] = ((CARD32 *) tmpdata)[0]; data[1] = ((CARD32 *) tmpdata)[1]; } if (nitems && tmpdata != NULL) XFree(tmpdata); return rc ? XEMBED_RESULT_OK : XEMBED_RESULT_UNSUPPORTED; } int xembed_post_data(Display *dpy, Window w, CARD32 *data) { XChangeProperty(dpy, w, xa_xembed_info, xa_xembed_info, 32, PropModeReplace, (unsigned char *) data, 2); return x11_ok() ? XEMBED_RESULT_OK : XEMBED_RESULT_X11ERROR; } ================================================ FILE: utils/tray-xembed-test/xembed.h ================================================ /* ------------------------------- * vim:tabstop=4:shiftwidth=4 * icons.h * Tue, 24 Aug 2004 12:05:38 +0700 * ------------------------------- * XEMBED protocol implementation * -------------------------------*/ #ifndef _XEMBED_H_ #define _XEMBED_H_ #include /* Internal return codes */ #define XEMBED_RESULT_OK 0 #define XEMBED_RESULT_UNSUPPORTED 1 #define XEMBED_RESULT_X11ERROR 2 /* XEMBED messages */ #define XEMBED_EMBEDDED_NOTIFY 0 #define XEMBED_WINDOW_ACTIVATE 1 #define XEMBED_WINDOW_DEACTIVATE 2 #define XEMBED_REQUEST_FOCUS 3 #define XEMBED_FOCUS_IN 4 #define XEMBED_FOCUS_OUT 5 #define XEMBED_FOCUS_NEXT 6 #define XEMBED_FOCUS_PREV 7 /* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */ #define XEMBED_MODALITY_ON 10 #define XEMBED_MODALITY_OFF 11 #define XEMBED_REGISTER_ACCELERATOR 12 #define XEMBED_UNREGISTER_ACCELERATOR 13 #define XEMBED_ACTIVATE_ACCELERATOR 14 /* Details for XEMBED_FOCUS_IN */ #define XEMBED_FOCUS_CURRENT 0 #define XEMBED_FOCUS_FIRST 1 #define XEMBED_FOCUS_LAST 2 /* Modifiers field for XEMBED_REGISTER_ACCELERATOR */ #define XEMBED_MODIFIER_SHIFT (1 << 0) #define XEMBED_MODIFIER_CONTROL (1 << 1) #define XEMBED_MODIFIER_ALT (1 << 2) #define XEMBED_MODIFIER_SUPER (1 << 3) #define XEMBED_MODIFIER_HYPER (1 << 4) /* Flags for XEMBED_ACTIVATE_ACCELERATOR */ #define XEMBED_ACCELERATOR_OVERLOADED (1 << 0) /* Directions for focusing */ #define XEMBED_DIRECTION_DEFAULT 0 #define XEMBED_DIRECTION_UP_DOWN 1 #define XEMBED_DIRECTION_LEFT_RIGHT 2 /* Flags for _XEMBED_INFO */ #define XEMBED_MAPPED (1 << 0) #define xembed_send_msg(dpy, dst, timestamp, msg, detail, data1, data2) \ x11_send_client_msg32(dpy, dst, dst, xa_xembed, timestamp, msg, detail, data1, data2) #define xembed_send_embedded_notify(dpy, src, dst, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_EMBEDDED_NOTIFY, 0, src, 0) #define xembed_send_window_activate(dpy, dst, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_WINDOW_ACTIVATE, 0, 0, 0) #define xembed_send_window_deactivate(dpy, dst, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_WINDOW_DEACTIVATE, 0, 0, 0) #define xembed_send_request_focus(dpy, dst, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_REQUEST_FOCUS, 0, 0, 0) #define xembed_send_focus_in(dpy, dst, focus, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_FOCUS_IN, focus, 0, 0) #define xembed_send_focus_out(dpy, dst, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_FOCUS_OUT, 0, 0, 0) #define xembed_send_focus_next(dpy, dst, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_FOCUS_NEXT, 0, 0, 0) #define xembed_send_focus_prev(dpy, dst, timestamp) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_FOCUS_PREV, 0, 0, 0) #define xembed_send_register_accelerator(dpy, dst, timestamp, id, sym, mods) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_REGISTER_ACCELERATOR, id, sym, mods) #define xembed_send_activate_accelerator(dpy, dst, timestamp, id, overloaded) \ xembed_send_msg(dpy, dst, timestamp, XEMBED_ACTIVATE_ACCELERATOR, id, overloaded, 0) int xembed_retrive_data(Display *dpy, Window w, CARD32 *data); int xembed_post_data(Display *dpy, Window w, CARD32 *data); #endif ================================================ FILE: utils/tray-xembed-test/xutils.c ================================================ /* ************************************ * vim:tabstop=4:shiftwidth=4 * xutils.c * Sun, 05 Mar 2006 17:56:56 +0600 * ************************************ * misc X11 utilities * ************************************/ #include #include #include #include #include #include #include "common.h" #include "xutils.h" static int trapped_x11_error_code = 0; static int (*old_x11_error_handler) (Display *, XErrorEvent *); int x11_error_handler(Display *dpy, XErrorEvent *err) { static char msg[PATH_MAX]; trapped_x11_error_code = err->error_code; XGetErrorText(dpy, trapped_x11_error_code, msg, sizeof(msg)-1); DBG(0, ("X11 error: %s (request: %u/%u, resource 0x%x)\n", msg, err->request_code, err->minor_code, err->resourceid)); return 0; } int x11_ok_helper(const char *file, const int line, const char *func) { if (trapped_x11_error_code) { DBG(0, ("X11 error %d detected at %s:%d:%s\n", trapped_x11_error_code, file, line, func)); trapped_x11_error_code = 0; return FAILURE; } else return SUCCESS; } int x11_current_error() { return trapped_x11_error_code; } void x11_trap_errors() { old_x11_error_handler = XSetErrorHandler(x11_error_handler); trapped_x11_error_code = 0; } int x11_untrap_errors() { XSetErrorHandler(old_x11_error_handler); return trapped_x11_error_code; } static Window timestamp_wnd; static Atom timestamp_atom = None; Bool x11_wait_for_timestamp(Display *dpy, XEvent *xevent, XPointer data) { return (xevent->type == PropertyNotify && xevent->xproperty.window == *((Window *)data) && xevent->xproperty.atom == timestamp_atom); } Time x11_get_server_timestamp(Display *dpy, Window wnd) { unsigned char c = 's'; XEvent xevent; if (timestamp_atom == None) timestamp_atom = XInternAtom(dpy, "STALONETRAY_TIMESTAMP", False); /* Trigger PropertyNotify event which has a timestamp field */ XChangeProperty(dpy, wnd, timestamp_atom, timestamp_atom, 8, PropModeReplace, &c, 1); if (!x11_ok()) return CurrentTime; /* Wait for the event */ timestamp_wnd = wnd; XIfEvent(dpy, &xevent, x11_wait_for_timestamp, (XPointer)×tamp_wnd); return x11_ok() ? xevent.xproperty.time : CurrentTime; } int x11_get_win_prop32(Display *dpy, Window dst, Atom atom, Atom type, unsigned char **data, unsigned long *len) { Atom act_type; int act_fmt, rc; unsigned long bytes_after, prop_len, buf_len; unsigned char *buf = NULL; *data = NULL; *len = 0; /* Get the property size */ rc = XGetWindowProperty(dpy, dst, atom, 0L, 0L, False, type, &act_type, &act_fmt, &buf_len, &bytes_after, &buf); /* The requested property does not exist */ if (rc != Success || act_type != type || act_fmt != 32) return FAILURE; XFree(buf); /* Now go get the property */ prop_len = bytes_after / 4; XGetWindowProperty(dpy, dst, atom, 0L, prop_len, False, type, &act_type, &act_fmt, &buf_len, &bytes_after, &buf); if (x11_ok()) { *len = buf_len; *data = buf; return SUCCESS; } else return FAILURE; } int x11_send_client_msg32(Display *dpy, Window dst, Window wnd, Atom type, long data0, long data1, long data2, long data3, long data4) { XEvent ev; ev.xclient.type = ClientMessage; ev.xclient.serial = 0; ev.xclient.send_event = True; ev.xclient.message_type = type; ev.xclient.window = wnd; ev.xclient.format = 32; ev.xclient.data.l[0] = data0; ev.xclient.data.l[1] = data1; ev.xclient.data.l[2] = data2; ev.xclient.data.l[3] = data3; ev.xclient.data.l[4] = data4; /* XXX: Replace 0xFFFFFF for better portability? */ return XSendEvent(dpy, dst, False, 0xFFFFFF, &ev); } int x11_set_window_size(Display *dpy, Window w, int x, int y) { XSizeHints xsh; xsh.flags = PSize; xsh.width = x; xsh.height = y; XSetWMNormalHints(dpy, w, &xsh); XResizeWindow(dpy, w, x, y); if (!x11_ok()) { DBG(0, ("failed to force 0x%x size to %dx%d\n", w, x, y)); return FAILURE; } return SUCCESS; } int x11_get_window_size(Display *dpy, Window w, int *x, int *y) { XWindowAttributes xwa; XGetWindowAttributes(dpy, w, &xwa); if (!x11_ok()) { DBG(0, ("failed to get 0x%x attributes\n", w)); return FAILURE; } *x = xwa.width; *y = xwa.height; return SUCCESS; } int x11_get_window_min_size(Display *dpy, Window w, int *x, int *y) { XSizeHints xsh; long flags = 0; int rc = FAILURE; if (XGetWMNormalHints(dpy, w, &xsh, &flags)) { flags = xsh.flags & flags; DBG(4, ("flags = 0x%x\n", flags)); if (flags & PMinSize) { DBG(4, ("min_width = %d, min_height = %d\n", xsh.min_width, xsh.min_height)); *x = xsh.min_width; *y = xsh.min_height; rc = SUCCESS; } } return rc; } int x11_get_window_abs_coords(Display *dpy, Window dst, int *x, int *y) { Window root, parent, *wjunk = NULL; int x11_, y_, x11__, y__; unsigned int junk; XGetGeometry(dpy, dst, &root, &x11_, &y_, &junk, &junk, &junk, &junk); XQueryTree(dpy, dst, &root, &parent, &wjunk, &junk); if (junk != 0) XFree(wjunk); if (!x11_ok()) return FAILURE; if (parent == root) { *x = x11_; *y = y_; } else { if (x11_get_window_abs_coords(dpy, parent, &x11__, &y__)) { *x = x11_ + x11__; *y = y_ + y__; } else return FAILURE; } return SUCCESS; } void x11_extend_root_event_mask(Display *dpy, long mask) { static long old_mask = 0; old_mask |= mask; XSelectInput(dpy, RootWindow(dpy, DefaultScreen(dpy)), old_mask); } #ifdef DEBUG const char *x11_event_names[LASTEvent] = { "unknown0", "unknown1", "KeyPress", "KeyRelease", "ButtonPress", "ButtonRelease", "MotionNotify", "EnterNotify", "LeaveNotify", "FocusIn", "FocusOut", "KeymapNotify", "Expose", "GraphicsExpose", "NoExpose", "VisibilityNotify", "CreateNotify", "DestroyNotify", "UnmapNotify", "MapNotify", "MapRequest", "ReparentNotify", "ConfigureNotify", "ConfigureRequest", "GravityNotify", "ResizeRequest", "CirculateNotify", "CirculateRequest", "PropertyNotify", "SelectionClear", "SelectionRequest", "SelectionNotify", "ColormapNotify", "ClientMessage", "MappingNotify" }; void x11_dump_win_info(Display *dpy, Window wid) { #ifdef _ST_WITH_DUMP_WIN_INFO char cmd[PATH_MAX]; DBG(4, ("Dumping info for 0x%x\n", wid)); snprintf(cmd, PATH_MAX, "xwininfo -size -bits -stats -id 0x%x\n", (unsigned int) wid); system(cmd); snprintf(cmd, PATH_MAX, "xprop -id 0x%x\n", (unsigned int) wid); system(cmd); #endif } #endif ================================================ FILE: utils/tray-xembed-test/xutils.h ================================================ /* ************************************ * vim:tabstop=4:shiftwidth=4 * xutils.h * Sun, 05 Mar 2006 17:16:44 +0600 * ************************************ * misc X11 utilities * ************************************/ #ifndef _XUTILS_H_ #define _XUTILS_H_ #include /* Return current server timestamp */ Time x11_get_server_timestamp(Display *dpy, Window wnd); /* Convinient way to send a message */ int x11_send_client_msg32(Display *dpy, Window dst, Window wnd, Atom type, long data0, long data1, long data2, long data3, long data4); /* Set window size updating its size hints */ int x11_set_window_size(Display *dpy, Window w, int x, int y); /* Get window size (uses XGetWindowAttributes) */ int x11_get_window_size(Display *dpy, Window w, int *x, int *y); /* Get window minimal size hints if they are available */ int x11_get_window_min_size(Display *dpy, Window w, int *x, int *y); /* Retrive 32-bit property from the target window */ int x11_get_win_prop32(Display *dpy, Window dst, Atom atom, Atom type, unsigned char **data, unsigned long *len); /* Retrive window-list property from the specified window */ #define x11_get_winlist_prop(dpy, dst, atom, data, len) x11_get_win_prop32(dpy, dst, atom, XA_WINDOW, data, len) /* Shortcut for the root window case */ #define x11_get_root_winlist_prop(dpy, atom, data, len) x11_get_winlist_prop(dpy, DefaultRootWindow(dpy), atom, data, len) /* Returns window absolute position (relative to the root window) */ int x11_get_window_abs_coords(Display *dpy, Window dst, int *x, int *y); /* Extends event mask of the root window */ void x11_extend_root_event_mask(Display *dpy, long mask); /* Checks if any X11 errors have occured so far. */ #define x11_ok() x11_ok_helper(__FILE__, __LINE__, __FUNC__) int x11_ok_helper(const char * file, const int line, const char *func); /* WARNING: following functions do not support nested calls */ /* Installs custom X11 error handler */ void x11_trap_errors(); /* Removes custom X11 error handler */ int x11_untrap_errors(); #ifdef DEBUG /* Array that maps event_number -> event_name */ const extern char *x11_event_names[LASTEvent]; /* Dumps window info. Does nothing unless _ST_WITH_DUMP_WIN_INFO is defined, * launches xwininfo and xwinprop otherwise */ void x11_dump_win_info(Display *dpy, Window w); #else /* Dummy delcaration */ #define x11_dump_win_info(dpy,w) do {} while (0); #endif #endif