Repository: FelixKratz/SketchyBar Branch: master Commit: 856ee4afef9c Files: 75 Total size: 462.3 KB Directory structure: gitextract_dn_wj_wt/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── makefile ├── plugins/ │ ├── battery.sh │ ├── clock.sh │ ├── front_app.sh │ ├── space.sh │ └── volume.sh ├── sketchybarrc └── src/ ├── alias.c ├── alias.h ├── animation.c ├── animation.h ├── app_windows.c ├── app_windows.h ├── background.c ├── background.h ├── bar.c ├── bar.h ├── bar_item.c ├── bar_item.h ├── bar_manager.c ├── bar_manager.h ├── color.c ├── color.h ├── custom_events.c ├── custom_events.h ├── display.c ├── display.h ├── event.c ├── event.h ├── font.c ├── font.h ├── graph.c ├── graph.h ├── group.c ├── group.h ├── hotload.c ├── hotload.h ├── image.c ├── image.h ├── mach.c ├── mach.h ├── media.h ├── media.m ├── message.c ├── message.h ├── misc/ │ ├── defines.h │ ├── env_vars.h │ ├── extern.h │ ├── help.h │ └── helpers.h ├── mouse.c ├── mouse.h ├── popup.c ├── popup.h ├── power.c ├── power.h ├── shadow.c ├── shadow.h ├── sketchybar.c ├── slider.c ├── slider.h ├── text.c ├── text.h ├── volume.c ├── volume.h ├── wifi.h ├── wifi.m ├── window.c ├── window.h ├── workspace.h └── workspace.m ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ ko_fi: felixkratz ================================================ FILE: .gitignore ================================================ test bin todo.md notifications.md .ccls-cache .DS_Store .ccls .cache .cmake CMakeCache.txt CMakeFiles/ cmake_install.cmake compile_commands.json ================================================ FILE: LICENSE.md ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. 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. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey 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; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. 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. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. 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 state 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 3 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program 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, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU 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. But first, please read . ================================================ FILE: README.md ================================================

Install Documentation Setups Plugins

This bar project aims to create a highly flexible, customizable, fast and powerful status bar replacement for people that like playing with shell scripts.

![](images/example.png)

More Setups

## Features * Full *configurability* at any time * Dynamic *animation* system * Powerful *scripting* and *event* system * Optimized to be *fast* and *efficient* * Interactive *mouse* support * Support for displaying macOS menu bar apps (*aliases*) * Can draw arbitrary *graphs* * On-demand *popup* menus The main design principle of this project is that *all* elements of the bar can be added, removed and freely changed at any point in time. Thus, the configuration of the bar is not *static*, rather it is possible to adapt the appearance of the bar completely dynamically with the help of a powerful event-driven scripting system at any point in time using the highly configurable basic building blocks SketchyBar offers. ## Getting Started Refer to the installation guide in the [documentation](https://felixkratz.github.io/SketchyBar/setup) to get the program set up. Once this is sorted you can start to become familiar with the syntax of sketchybar by going through the default [*sketchybarrc*](https://github.com/FelixKratz/SketchyBar/blob/master/sketchybarrc) file and the default [*plugin scripts*](https://github.com/FelixKratz/SketchyBar/blob/master/plugins), which are located in `~/.config/sketchybar/` and look like this: ![](images/default.png) All commands and options are explained in detail in the relevant sections of the configuration [documentation](https://felixkratz.github.io/SketchyBar/config/bar). You can try the commands directly from the commandline to see which affect they have and how they alter the bar. Once you have become familiar with the syntax you can look for a config to start from [here](https://github.com/FelixKratz/SketchyBar/discussions/47?sort=top) or start from scratch and customize everything to your liking. You might also enjoy looking at the [Tips & Tricks](https://felixkratz.github.io/SketchyBar/config/tricks) section for some further tips on your journey. If you are searching for functional items you might want to check the [plugins](https://github.com/FelixKratz/SketchyBar/discussions/12?sort=top) section if someone has already created what you are looking for. Should you encounter things not working as you expect them to, please *do not* hesitate to open an [issue](https://github.com/FelixKratz/SketchyBar/issues), as this is either a bug or a documentation problem and relevant in any case. ## Documentation For the full documentation of all commands and properties please refer to the [website](https://felixkratz.github.io/SketchyBar/config/bar). If questions remain, feel free to consult the [Q&A](https://github.com/FelixKratz/SketchyBar/discussions/categories/q-a) section. ## Supporting *You* can support this project is many ways: - By *creating* issues and pull-requests if you encounter problems - By *sharing* your [plugins](https://github.com/FelixKratz/SketchyBar/discussions/12) and [setups](https://github.com/FelixKratz/SketchyBar/discussions/47) - By *starring* the project on GitHub - If this project has value to you, consider quantifying it and *donating* to a charity of your choice. If you want to let me know about your donation, you can contact me via [email](mailto:felix.kratz@tu-dortmund.de?Subject=Donation). - If you want to support me directly, you can do so via [ko-fi](https://ko-fi.com/felixkratz) ## Credits This project was forked from *[spacebar](https://github.com/cmacrae/spacebar)* and completely reimagined and rewritten.
The original idea is based on the status bar that was included in *[yabai](https://github.com/koekeishiya/yabai)* before getting removed. ## Related Projects - [SbarLua](https://github.com/FelixKratz/SbarLua): A Lua API for SketchyBar - [sketchybar-app-font](https://github.com/kvndrsslr/sketchybar-app-font): A symbol font for SketchyBar - [SketchyBarHelper](https://github.com/FelixKratz/SketchyBarHelper): A header for C/C++ to directly communicate with SketchyBar ## Some animation examples https://user-images.githubusercontent.com/22680421/211198711-45318f04-e96f-4aa1-a0ba-c7f30f050902.mp4 ================================================ FILE: makefile ================================================ CFLAGS = -std=c99 -Wall -O3 -ffast-math -fvisibility=hidden -fno-common LIBS = -framework Carbon \ -framework AppKit \ -framework CoreAudio \ -framework CoreWLAN \ -framework CoreVideo \ -framework IOKit \ -F/System/Library/PrivateFrameworks \ -framework SkyLight \ -framework DisplayServices \ -framework MediaRemote ODIR = bin SRC = src _OBJ = alias.o background.o bar_item.o custom_events.o event.o graph.o \ image.o mouse.o shadow.o font.o text.o message.o mouse.o bar.o color.o \ window.o bar_manager.o display.o group.o mach.o popup.o \ animation.o workspace.om volume.o slider.o power.o wifi.om media.om \ hotload.o app_windows.o OBJ = $(patsubst %, $(ODIR)/%, $(_OBJ)) .PHONY: all clean arm x86 profile leak universal all: clean universal leak: CFLAGS=-std=c99 -Wall -g leak: clean arm64 leak: /usr/libexec/PlistBuddy -c "Add :com.apple.security.get-task-allow bool true" bin/tmp.entitlements codesign -s - --entitlements bin/tmp.entitlements -f ./bin/sketchybar leaks -atExit -- ./bin/sketchybar x86: CFLAGS+=-target x86_64-apple-macos10.13 x86: $(ODIR)/sketchybar arm64: CFLAGS+=-target arm64-apple-macos11 arm64: $(ODIR)/sketchybar universal: $(MAKE) x86 mv $(ODIR)/sketchybar $(ODIR)/sketchybar_x86 rm -rf $(ODIR)/*.o* $(MAKE) arm64 mv $(ODIR)/sketchybar $(ODIR)/sketchybar_arm64 lipo -create -output $(ODIR)/sketchybar $(ODIR)/sketchybar_x86 $(ODIR)/sketchybar_arm64 debug: CFLAGS=-std=c99 -Wall -g debug: arm64 asan: CFLAGS=-std=c99 -Wall -g -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer asan: clean arm64 ./bin/sketchybar $(ODIR)/sketchybar: $(SRC)/sketchybar.c $(OBJ) | $(ODIR) $(CC) $(CFLAGS) $^ -o $@ $(LIBS) $(ODIR)/%.o: $(SRC)/%.c $(SRC)/%.h | $(ODIR) $(CC) -c -o $@ $< $(CFLAGS) $(ODIR)/%.om: $(SRC)/%.m $(SRC)/%.h | $(ODIR) $(CC) -c -o $@ $< $(CFLAGS) $(ODIR): mkdir $(ODIR) clean: rm -rf $(ODIR) ================================================ FILE: plugins/battery.sh ================================================ #!/bin/sh PERCENTAGE="$(pmset -g batt | grep -Eo "\d+%" | cut -d% -f1)" CHARGING="$(pmset -g batt | grep 'AC Power')" if [ "$PERCENTAGE" = "" ]; then exit 0 fi case "${PERCENTAGE}" in 9[0-9]|100) ICON="" ;; [6-8][0-9]) ICON="" ;; [3-5][0-9]) ICON="" ;; [1-2][0-9]) ICON="" ;; *) ICON="" esac if [[ "$CHARGING" != "" ]]; then ICON="" fi # The item invoking this script (name $NAME) will get its icon and label # updated with the current battery status sketchybar --set "$NAME" icon="$ICON" label="${PERCENTAGE}%" ================================================ FILE: plugins/clock.sh ================================================ #!/bin/sh # The $NAME variable is passed from sketchybar and holds the name of # the item invoking this script: # https://felixkratz.github.io/SketchyBar/config/events#events-and-scripting sketchybar --set "$NAME" label="$(date '+%d/%m %H:%M')" ================================================ FILE: plugins/front_app.sh ================================================ #!/bin/sh # Some events send additional information specific to the event in the $INFO # variable. E.g. the front_app_switched event sends the name of the newly # focused application in the $INFO variable: # https://felixkratz.github.io/SketchyBar/config/events#events-and-scripting if [ "$SENDER" = "front_app_switched" ]; then sketchybar --set "$NAME" label="$INFO" fi ================================================ FILE: plugins/space.sh ================================================ #!/bin/sh # The $SELECTED variable is available for space components and indicates if # the space invoking this script (with name: $NAME) is currently selected: # https://felixkratz.github.io/SketchyBar/config/components#space----associate-mission-control-spaces-with-an-item sketchybar --set "$NAME" background.drawing="$SELECTED" ================================================ FILE: plugins/volume.sh ================================================ #!/bin/sh # The volume_change event supplies a $INFO variable in which the current volume # percentage is passed to the script. if [ "$SENDER" = "volume_change" ]; then VOLUME="$INFO" case "$VOLUME" in [6-9][0-9]|100) ICON="󰕾" ;; [3-5][0-9]) ICON="󰖀" ;; [1-9]|[1-2][0-9]) ICON="󰕿" ;; *) ICON="󰖁" esac sketchybar --set "$NAME" icon="$ICON" label="$VOLUME%" fi ================================================ FILE: sketchybarrc ================================================ # This is a demo config to showcase some of the most important commands. # It is meant to be changed and configured, as it is intentionally kept sparse. # For a (much) more advanced configuration example see my dotfiles: # https://github.com/FelixKratz/dotfiles PLUGIN_DIR="$CONFIG_DIR/plugins" ##### Bar Appearance ##### # Configuring the general appearance of the bar. # These are only some of the options available. For all options see: # https://felixkratz.github.io/SketchyBar/config/bar # If you are looking for other colors, see the color picker: # https://felixkratz.github.io/SketchyBar/config/tricks#color-picker sketchybar --bar position=top height=40 blur_radius=30 color=0x40000000 ##### Changing Defaults ##### # We now change some default values, which are applied to all further items. # For a full list of all available item properties see: # https://felixkratz.github.io/SketchyBar/config/items default=( padding_left=5 padding_right=5 icon.font="Hack Nerd Font:Bold:17.0" label.font="Hack Nerd Font:Bold:14.0" icon.color=0xffffffff label.color=0xffffffff icon.padding_left=4 icon.padding_right=4 label.padding_left=4 label.padding_right=4 ) sketchybar --default "${default[@]}" ##### Adding Mission Control Space Indicators ##### # Let's add some mission control spaces: # https://felixkratz.github.io/SketchyBar/config/components#space----associate-mission-control-spaces-with-an-item # to indicate active and available mission control spaces. SPACE_ICONS=("1" "2" "3" "4" "5" "6" "7" "8" "9" "10") for i in "${!SPACE_ICONS[@]}" do sid="$(($i+1))" space=( space="$sid" icon="${SPACE_ICONS[i]}" icon.padding_left=7 icon.padding_right=7 background.color=0x40ffffff background.corner_radius=5 background.height=25 label.drawing=off script="$PLUGIN_DIR/space.sh" click_script="yabai -m space --focus $sid" ) sketchybar --add space space."$sid" left --set space."$sid" "${space[@]}" done ##### Adding Left Items ##### # We add some regular items to the left side of the bar, where # only the properties deviating from the current defaults need to be set sketchybar --add item chevron left \ --set chevron icon= label.drawing=off \ --add item front_app left \ --set front_app icon.drawing=off script="$PLUGIN_DIR/front_app.sh" \ --subscribe front_app front_app_switched ##### Adding Right Items ##### # In the same way as the left items we can add items to the right side. # Additional position (e.g. center) are available, see: # https://felixkratz.github.io/SketchyBar/config/items#adding-items-to-sketchybar # Some items refresh on a fixed cycle, e.g. the clock runs its script once # every 10s. Other items respond to events they subscribe to, e.g. the # volume.sh script is only executed once an actual change in system audio # volume is registered. More info about the event system can be found here: # https://felixkratz.github.io/SketchyBar/config/events sketchybar --add item clock right \ --set clock update_freq=10 icon= script="$PLUGIN_DIR/clock.sh" \ --add item volume right \ --set volume script="$PLUGIN_DIR/volume.sh" \ --subscribe volume volume_change \ --add item battery right \ --set battery update_freq=120 script="$PLUGIN_DIR/battery.sh" \ --subscribe battery system_woke power_source_change ##### Force all scripts to run the first time (never do this in a script) ##### sketchybar --update ================================================ FILE: src/alias.c ================================================ #include "alias.h" #include "misc/helpers.h" #include #include void print_all_menu_items(FILE* rsp) { #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 if (__builtin_available(macOS 11.0, *)) { if (!CGRequestScreenCaptureAccess()) { respond(rsp, "[!] Query (default_menu_items): Screen Recording " "Permissions not given. Restart SketchyBar after granting " "permissions.\n"); return; } } #endif CFArrayRef window_list = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID ); int window_count = CFArrayGetCount(window_list); float x_pos[window_count]; char* owner[window_count]; char* name[window_count]; memset(owner, 0, sizeof(owner)); memset(name, 0, sizeof(name)); int item_count = 0; for (int i = 0; i < window_count; ++i) { x_pos[i] = -9999.f; CFDictionaryRef dictionary = CFArrayGetValueAtIndex(window_list, i); if (!dictionary) continue; CFStringRef owner_ref = CFDictionaryGetValue(dictionary, kCGWindowOwnerName); CFNumberRef owner_pid_ref = CFDictionaryGetValue(dictionary, kCGWindowOwnerPID); CFStringRef name_ref = CFDictionaryGetValue(dictionary, kCGWindowName); CFNumberRef layer_ref = CFDictionaryGetValue(dictionary, kCGWindowLayer); CFDictionaryRef bounds_ref = CFDictionaryGetValue(dictionary, kCGWindowBounds); if (!name_ref || !owner_ref || !owner_pid_ref || !layer_ref || !bounds_ref) continue; long long int layer = 0; CFNumberGetValue(layer_ref, CFNumberGetType(layer_ref), &layer); uint64_t owner_pid = 0; CFNumberGetValue(owner_pid_ref, CFNumberGetType(owner_pid_ref), &owner_pid ); if (layer != MENUBAR_LAYER) continue; CGRect bounds = CGRectNull; if (!CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds)) continue; char* owner_copy = cfstring_copy(owner_ref); if (string_equals(owner_copy, "Window Server")) { free(owner_copy); continue; } owner[item_count] = owner_copy; name[item_count] = cfstring_copy(name_ref); x_pos[item_count++] = bounds.origin.x; } if (item_count > 0) { fprintf(rsp, "[\n"); int counter = 0; for (int i = 0; i < item_count; i++) { float current_pos = x_pos[0]; uint32_t current_pos_id = 0; for (int j = 0; j < window_count; j++) { if (!name[j] || !owner[j]) continue; if (x_pos[j] > current_pos) { current_pos = x_pos[j]; current_pos_id = j; } } if (!name[current_pos_id] || !owner[current_pos_id]) continue; if (strcmp(name[current_pos_id], "") != 0) { if (counter++ > 0) { fprintf(rsp, ", \n"); } fprintf(rsp, "\t\"%s,%s\"", owner[current_pos_id], name[current_pos_id] ); } x_pos[current_pos_id] = -9999.f; } fprintf(rsp, "\n]\n"); for (int i = 0; i < window_count; i++) { if (owner[i]) free(owner[i]); if (name[i]) free(name[i]); } } CFRelease(window_list); } void alias_get_permission(struct alias* alias) { #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 if (__builtin_available(macOS 11.0, *)) { alias->permission = CGRequestScreenCaptureAccess(); } #else if(__builtin_available(macos 10.15, *)) { CGImageRef img = CGWindowListCreateImage(CGRectMake(0, 0, 1, 1), kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault ); CFRelease(img); } #endif } void alias_init(struct alias* alias) { alias->name = NULL; alias->owner = NULL; alias->color_override = false; color_init(&alias->color, 0xffff0000); alias->update_frequency = 1; alias->counter = 0; window_init(&alias->window); image_init(&alias->image); } static void alias_find_window(struct alias* alias) { CFArrayRef window_list = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID ); int window_count = CFArrayGetCount(window_list); for (int i = 0; i < window_count; ++i) { CFDictionaryRef dictionary = CFArrayGetValueAtIndex(window_list, i); if (!dictionary) continue; CFStringRef owner_ref = CFDictionaryGetValue(dictionary, kCGWindowOwnerName); CFNumberRef owner_pid_ref = CFDictionaryGetValue(dictionary, kCGWindowOwnerPID); CFStringRef name_ref = CFDictionaryGetValue(dictionary, kCGWindowName); if (!name_ref) continue; if (!owner_ref) continue; char* owner = cfstring_copy(owner_ref); char* name = cfstring_copy(name_ref); if (!(alias->owner && strcmp(alias->owner, owner) == 0 && ((alias->name && strcmp(alias->name, name) == 0) || (!alias->name && strcmp(name, "") != 0) ))) { free(owner); free(name); continue; } free(owner); free(name); CFNumberRef layer_ref = CFDictionaryGetValue(dictionary, kCGWindowLayer); if (!layer_ref) continue; uint64_t layer = 0; CFNumberGetValue(layer_ref, CFNumberGetType(layer_ref), &layer); if (layer != MENUBAR_LAYER) continue; CFNumberGetValue(owner_pid_ref, CFNumberGetType(owner_pid_ref), &alias->pid ); CFNumberRef window_id_ref = CFDictionaryGetValue(dictionary, kCGWindowNumber); if (!window_id_ref) continue; CFDictionaryRef bounds_ref = CFDictionaryGetValue(dictionary, kCGWindowBounds); if (!bounds_ref) continue; CGRect bounds; CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds); uint64_t wid; CFNumberGetValue(window_id_ref, CFNumberGetType(window_id_ref), &wid ); alias->window.id = (uint32_t)wid; alias->window.frame.size = bounds.size; alias->window.origin = bounds.origin; CFRelease(window_list); return; } alias->window.id = 0; CFRelease(window_list); } static bool alias_update_image(struct alias* alias, bool forced) { if (alias->window.id == 0) alias_find_window(alias); if (alias->window.id == 0) return false; bool disabled = false; CGImageRef image_ref = window_capture(&alias->window, &disabled); if (!image_ref) { if (!disabled) { alias->window.id = 0; image_destroy(&alias->image); } return false; } return image_set_image(&alias->image, image_ref, alias->window.frame, forced ); } void alias_setup(struct alias* alias, char* owner, char* name) { alias->name = name; alias->owner = owner; alias_get_permission(alias); alias_update_image(alias, true); } uint32_t alias_get_length(struct alias* alias) { if (alias->image.image_ref) return alias->image.bounds.size.width; return 0; } uint32_t alias_get_height(struct alias* alias) { if (alias->image.image_ref) return alias->image.bounds.size.height; return 0; } bool alias_update(struct alias* alias, bool forced) { if (alias->update_frequency == 0) return false; alias->counter++; if (forced || alias->counter >= alias->update_frequency) { alias->counter = 0; if (alias_update_image(alias, forced)) { return true; } } return false; } void alias_draw(struct alias* alias, CGContextRef context) { if (alias->color_override) { CGContextSaveGState(context); image_draw(&alias->image, context); CGContextClipToMask(context, alias->image.bounds, alias->image.image_ref); CGContextSetRGBFillColor(context, alias->color.r, alias->color.g, alias->color.b, alias->color.a ); CGContextFillRect(context, alias->image.bounds); CGContextRestoreGState(context); } else { image_draw(&alias->image, context); } } void alias_destroy(struct alias* alias) { image_destroy(&alias->image); if (alias->name) free(alias->name); if (alias->owner) free(alias->owner); alias->name = NULL; alias->owner = NULL; } void alias_calculate_bounds(struct alias* alias, uint32_t x, uint32_t y) { image_calculate_bounds(&alias->image, x, y); } bool alias_parse_sub_domain(struct alias* alias, FILE* rsp, struct token property, char* message) { struct key_value_pair key_value_pair = get_key_value_pair(property.text,'.'); if (key_value_pair.key && key_value_pair.value) { struct token subdom = { key_value_pair.key, strlen(key_value_pair.key) }; struct token entry = { key_value_pair.value, strlen(key_value_pair.value)}; if (token_equals(subdom, SUB_DOMAIN_SHADOW)) return shadow_parse_sub_domain(&alias->image.shadow, rsp, entry, message ); else if (token_equals(subdom, SUB_DOMAIN_COLOR)) { bool changed = !alias->color_override; alias->color_override = true; return color_parse_sub_domain(&alias->color, rsp, entry, message) || changed; } else { respond(rsp, "[!] Alias: Invalid subdomain '%s'\n", subdom.text); } } else if (token_equals(property, PROPERTY_COLOR)) { color_set_hex(&alias->color, token_to_uint32t(get_token(&message))); alias->color_override = true; return true; } else if (token_equals(property, PROPERTY_SCALE)) { return image_set_scale(&alias->image, token_to_float(get_token(&message))); } else if (token_equals(property, PROPERTY_UPDATE_FREQ)) { alias->update_frequency = token_to_uint32t(get_token(&message)); return false; } else { respond(rsp, "[!] Alias: Invalid property '%s' \n", property.text); } return false; } ================================================ FILE: src/alias.h ================================================ #pragma once #include #include "misc/helpers.h" #include "window.h" #include "image.h" #define MENUBAR_LAYER 0x19 struct alias { bool permission; uint32_t update_frequency; uint32_t counter; char* name; char* owner; pid_t pid; struct window window; bool color_override; struct color color; struct image image; }; void alias_init(struct alias* alias); void alias_setup(struct alias* alias, char* owner, char* name); uint32_t alias_get_length(struct alias* alias); uint32_t alias_get_height(struct alias* alias); void alias_calculate_bounds(struct alias* alias, uint32_t x, uint32_t y); void alias_draw(struct alias* alias, CGContextRef context); bool alias_update(struct alias* alias, bool forced); void alias_destroy(struct alias* alias); void print_all_menu_items(FILE* rsp); bool alias_parse_sub_domain(struct alias* alias, FILE* rsp, struct token property, char* message); ================================================ FILE: src/animation.c ================================================ #include "animation.h" #include "event.h" static CVReturn animation_frame_callback(CVDisplayLinkRef display_link, const CVTimeStamp* now, const CVTimeStamp* output_time, CVOptionFlags flags, CVOptionFlags* flags_out, void* context) { uint64_t hostTime = output_time->hostTime; dispatch_async(dispatch_get_main_queue(), ^{ struct event event = { (void*)hostTime, ANIMATOR_REFRESH }; event_post(&event); }); return kCVReturnSuccess; } struct animation* animation_create() { struct animation* animation = malloc(sizeof(struct animation)); memset(animation, 0, sizeof(struct animation)); return animation; } static void animation_destroy(struct animation* animation) { if (animation) free(animation); } static void animation_lock(struct animation* animation) { animation->locked = true; } void animation_setup(struct animation* animation, void* target, animator_function* update_function, int initial_value, int final_value, uint32_t duration, char interp_function) { // The animation duration is represented as a frame count equivalent on a // 60Hz display. E.g. 120frames = 2 seconds animation->duration = (double)duration / 60.0; animation->initial_value = initial_value; animation->final_value = final_value; animation->update_function = update_function; animation->target = target; animation->separate_bytes = false; animation->as_float = false; if (interp_function == INTERP_FUNCTION_TANH) { animation->interp_function = &function_tanh; } else if (interp_function == INTERP_FUNCTION_SIN) { animation->interp_function = &function_sin; } else if (interp_function == INTERP_FUNCTION_QUADRATIC) { animation->interp_function = &function_square; } else if (interp_function == INTERP_FUNCTION_EXP) { animation->interp_function = &function_exp; } else if (interp_function == INTERP_FUNCTION_CIRC) { animation->interp_function = &function_circ; } else { animation->interp_function = &function_linear; } } static bool animation_update(struct animation* animation, uint64_t time, uint64_t clock) { if (!animation->target || !animation->update_function || animation->waiting ) { return false; } if (!animation->initial_time) animation->initial_time = time; double t = animation->duration > 0 ? ((double)(time - animation->initial_time) / (double)(animation->duration * clock)) : 1.0; bool final_frame = t >= 1.0; if (t < 0.0) t = 0.0; if (t > 1.0) t = 1.0; double slider = final_frame ? 1.0 : animation->interp_function(t); int value; if (animation->separate_bytes) { for (int i = 0; i < 4; i++) { unsigned char byte_i = *((unsigned char*)&animation->initial_value + i); unsigned char byte_f = *((unsigned char*)&animation->final_value + i); unsigned char byte_val = (1. - slider) * byte_i + slider * byte_f; *((unsigned char*)&value + i) = byte_val; } } else if (animation->as_float) { *((float*)&value) = (1. - slider) * *(float*)&animation->initial_value + slider * *(float*)&animation->final_value; } else { value = (1. - slider) * animation->initial_value + slider * animation->final_value + 0.5; if (final_frame) value = animation->final_value; } bool needs_update; if (animation->as_float) { needs_update = ((bool (*)(void*, float))animation->update_function)(animation->target, *((float*)&value) ); } else { needs_update = animation->update_function(animation->target, value); } bool found_item = false; for (int i = 0; i < g_bar_manager.bar_item_count; i++) { if (needs_update && (animation->target >= (void*)g_bar_manager.bar_items[i]) && (animation->target < ((void*)g_bar_manager.bar_items[i] + sizeof(struct bar_item) ))) { bar_item_needs_update(g_bar_manager.bar_items[i]); found_item = true; } } if (!found_item && needs_update) g_bar_manager.bar_needs_update = true; animation->finished = final_frame; if (animation->finished && animation->next) { animation->next->previous = NULL; animation->next->waiting = false; animation->next = NULL; } return needs_update; } void animator_init(struct animator* animator) { animator->animations = NULL; animator->animation_count = 0; animator->interp_function = 0; animator->duration = 0; animator->display_link = NULL; animator_renew_display_link(animator); } void animator_renew_display_link(struct animator* animator) { animator_destroy_display_link(animator); CVDisplayLinkCreateWithActiveCGDisplays(&animator->display_link); CVDisplayLinkSetOutputCallback(animator->display_link, animation_frame_callback, animator ); animator->clock = CVGetHostClockFrequency(); CVDisplayLinkStart(animator->display_link); } void animator_destroy_display_link(struct animator* animator) { if (animator->display_link) { CVDisplayLinkStop(animator->display_link); CVDisplayLinkRelease(animator->display_link); animator->display_link = NULL; } } void animator_lock(struct animator* animator) { for (int i = 0; i < animator->animation_count; i++) { animation_lock(animator->animations[i]); } } static void animator_calculate_offset_for_animation(struct animator* animator, struct animation* animation) { if (animator->animation_count < 1) return; struct animation* previous = NULL; for (int i = animator->animation_count - 1; i >= 0; i--) { struct animation* current = animator->animations[i]; if (current->target == animation->target && current->update_function == animation->update_function) { previous = current; break; } } if (previous) { animation->initial_value = previous->final_value; previous->next = animation; animation->previous = previous; animation->waiting = true; } } void animator_add(struct animator* animator, struct animation* animation) { animator_calculate_offset_for_animation(animator, animation); animator->animations = realloc(animator->animations, sizeof(struct animation*) * ++animator->animation_count); animator->animations[animator->animation_count - 1] = animation; if (!animator->display_link) animator_renew_display_link(animator); } static void animator_remove(struct animator* animator, struct animation* animation) { if (animator->animation_count == 1) { free(animator->animations); animator->animations = NULL; animator->animation_count = 0; } else { struct animation* tmp[animator->animation_count - 1]; int count = 0; for (int i = 0; i < animator->animation_count; i++) { if (animator->animations[i] == animation) continue; tmp[count++] = animator->animations[i]; } animator->animation_count--; animator->animations = realloc(animator->animations, sizeof(struct animation*) *animator->animation_count); memcpy(animator->animations, tmp, sizeof(struct animation*)*animator->animation_count); } if (animation->previous) animation->previous->next = NULL; if (animation->next) animation->next->previous = NULL; animation_destroy(animation); } void animator_cancel_locked(struct animator* animator, void* target, animator_function* function) { struct animation* remove[animator->animation_count]; memset(remove, 0, animator->animation_count); uint32_t remove_count = 0; for (int i = 0; i < animator->animation_count; i++) { struct animation* animation = animator->animations[i]; if (animation->locked && animation->target == target && animation->update_function == function) { remove[remove_count++] = animation; } } for (uint32_t i = 0; i < remove_count; i++) { animator_remove(animator, remove[i]); } } bool animator_cancel(struct animator* animator, void* target, animator_function* function) { bool needs_update = false; struct animation* remove[animator->animation_count]; memset(remove, 0, animator->animation_count); uint32_t remove_count = 0; for (int i = 0; i < animator->animation_count; i++) { struct animation* animation = animator->animations[i]; if (animation->target == target && animation->update_function == function) { needs_update |= function(animation->target, animation->final_value); remove[remove_count++] = animation; } } for (uint32_t i = 0; i < remove_count; i++) { animator_remove(animator, remove[i]); } return needs_update; } bool animator_update(struct animator* animator, uint64_t time) { bool needs_refresh = false; struct animation* remove[animator->animation_count]; memset(remove, 0, animator->animation_count); uint32_t remove_count = 0; for (uint32_t i = 0; i < animator->animation_count; i++) { needs_refresh |= animation_update(animator->animations[i], time, animator->clock ); if (animator->animations[i]->finished) { remove[remove_count++] = animator->animations[i]; } } for (uint32_t i = 0; i < remove_count; i++) { animator_remove(animator, remove[i]); } if (animator->animation_count == 0) animator_destroy_display_link(animator); return needs_refresh; } void animator_destroy(struct animator* animator) { if (animator->animation_count > 0) { if (animator->display_link) CVDisplayLinkStop(animator->display_link); CVDisplayLinkRelease(animator->display_link); animator->display_link = NULL; for (int i = 0; i < animator->animation_count; i++) { animation_destroy(animator->animations[i]); } } if (animator->animations) free(animator->animations); } ================================================ FILE: src/animation.h ================================================ #pragma once #include #include "misc/helpers.h" extern struct bar_manager g_bar_manager; #define ANIMATE(f, o, p, t) \ {\ if (g_bar_manager.animator.duration > 0) { \ animator_cancel_locked(&g_bar_manager.animator, (void*)o, (bool (*)(void*, int))&f); \ struct animation* animation = animation_create(); \ animation_setup(animation, \ (void*)o, \ (bool (*)(void*, int))&f, \ p, \ t, \ g_bar_manager.animator.duration, \ g_bar_manager.animator.interp_function ); \ animator_add(&g_bar_manager.animator, animation); \ } else { \ needs_refresh = animator_cancel(&g_bar_manager.animator, (void*)o, (bool (*)(void*, int))&f); \ needs_refresh |= f(o, t); \ } \ } #define ANIMATE_FLOAT(f, o, p, t) \ {\ if (g_bar_manager.animator.duration > 0) { \ animator_cancel_locked(&g_bar_manager.animator, (void*)o, (bool (*)(void*, int))&f); \ struct animation* animation = animation_create(); \ float initial_value = p; \ float final_value = t; \ animation_setup(animation, \ (void*)o, \ (bool (*)(void*, int))&f, \ *(int*)&initial_value, \ *(int*)&final_value, \ g_bar_manager.animator.duration, \ g_bar_manager.animator.interp_function ); \ animation->as_float = true; \ animator_add(&g_bar_manager.animator, animation); \ } else { \ needs_refresh = animator_cancel(&g_bar_manager.animator, (void*)o, (bool (*)(void*, int))&f); \ needs_refresh |= f(o, t); \ } \ } #define ANIMATE_BYTES(f, o, p, t) \ {\ if (g_bar_manager.animator.duration > 0) { \ animator_cancel_locked(&g_bar_manager.animator, (void*)o, (bool (*)(void*, int))&f); \ struct animation* animation = animation_create(); \ animation_setup(animation, \ (void*)o, \ (bool (*)(void*, int))&f, \ p, \ t, \ g_bar_manager.animator.duration, \ g_bar_manager.animator.interp_function ); \ animation->separate_bytes = true; \ animator_add(&g_bar_manager.animator, animation); \ } else { \ needs_refresh = animator_cancel(&g_bar_manager.animator, (void*)o, (bool (*)(void*, int))&f); \ needs_refresh |= f(o, t); \ } \ } #define ANIMATOR_FUNCTION(name) bool name(void* target, int value); typedef ANIMATOR_FUNCTION(animator_function); #define ANIMATION_FUNCTION(name) double name(double x); typedef ANIMATION_FUNCTION(animation_function); #define INTERP_FUNCTION_LINEAR 'l' #define INTERP_FUNCTION_QUADRATIC 'q' #define INTERP_FUNCTION_SIN 's' #define INTERP_FUNCTION_TANH 't' #define INTERP_FUNCTION_CIRC 'c' #define INTERP_FUNCTION_BOUNCE 'b' #define INTERP_FUNCTION_EXP 'e' #define INTERP_FUNCTION_OVERSHOOT 'o' struct animation { bool separate_bytes; bool as_float; bool locked; bool finished; bool waiting; uint64_t initial_time; double duration; int initial_value; int final_value; animation_function* interp_function; void* target; animator_function* update_function; struct animation* next; struct animation* previous; }; struct animation* animation_create(); void animation_setup(struct animation* animation, void* target, animator_function* update_function, int initial_value, int final_value, uint32_t duration, char interp_function); struct animator { CVDisplayLinkRef display_link; double clock; uint32_t interp_function; uint32_t duration; struct animation** animations; uint32_t animation_count; }; void animator_init(struct animator* animator); void animator_add(struct animator* animator, struct animation* animation); bool animator_cancel(struct animator* animator, void* target, animator_function* function); void animator_cancel_locked(struct animator* animator, void* target, animator_function* function); bool animator_update(struct animator* animator, uint64_t time); void animator_lock(struct animator* animator); void animator_destroy(struct animator* animator); void animator_renew_display_link(struct animator* animator); void animator_destroy_display_link(struct animator* animator); ================================================ FILE: src/app_windows.c ================================================ #include "app_windows.h" #include "workspace.h" #include "misc/helpers.h" #include "event.h" extern pid_t g_pid; struct app_windows g_windows = { 0 }; struct app_windows g_hidden_windows = { 0 }; bool g_space_window_events = false; static bool iterator_window_suitable(CFTypeRef iterator) { uint64_t tags = SLSWindowIteratorGetTags(iterator); uint64_t attributes = SLSWindowIteratorGetAttributes(iterator); uint32_t parent_wid = SLSWindowIteratorGetParentID(iterator); if (((parent_wid == 0) && ((attributes & 0x2) || (tags & 0x400000000000000)) && (((tags & 0x1)) || ((tags & 0x2) && (tags & 0x80000000))))) { return true; } return false; } void app_window_clear(struct app_window* window) { memset(window, 0, sizeof(struct app_window)); } void app_windows_add(struct app_windows* windows, struct app_window* window) { for (int i = 0; i < windows->num_windows; i++) { if (!windows->windows[i].wid) { windows->windows[i] = *window; return; } } windows->windows = realloc(windows->windows, sizeof(struct app_window) * ++windows->num_windows); windows->windows[windows->num_windows - 1] = *window; } void app_windows_clear_space(struct app_windows* windows, uint64_t sid) { for (int i = 0; i < windows->num_windows; i++) { if (windows->windows[i].sid == sid) app_window_clear(windows->windows + i); } } void app_windows_register_notifications() { uint32_t window_count = 0; uint32_t wid_list[g_windows.num_windows + g_hidden_windows.num_windows]; for (int i = 0; i < g_windows.num_windows; i++) { if (g_windows.windows[i].wid) wid_list[window_count++] = g_windows.windows[i].wid; } for (int i = 0; i < g_hidden_windows.num_windows; i++) { if (g_hidden_windows.windows[i].wid) wid_list[window_count++] = g_hidden_windows.windows[i].wid; } SLSRequestNotificationsForWindows(g_connection, wid_list, window_count); } bool app_windows_find(struct app_windows* windows, struct app_window* window) { for (int i = 0; i < windows->num_windows; i++) { if (windows->windows[i].wid == window->wid && windows->windows[i].sid == window->sid) { return true; } } return false; } struct app_window* app_windows_find_by_wid(struct app_windows* windows, uint32_t wid) { for (int i = 0; i < windows->num_windows; i++) { if (windows->windows[i].wid == wid) return &windows->windows[i]; } return NULL; } static bool app_window_suitable(struct app_window* window) { CFArrayRef target_ref = cfarray_of_cfnumbers(&window->wid, sizeof(uint32_t), 1, kCFNumberSInt32Type); if (!target_ref) return false; bool suitable = false; CFTypeRef query = SLSWindowQueryWindows(g_connection, target_ref, 0x0); if (query) { CFTypeRef iterator = SLSWindowQueryResultCopyWindows(query); if (iterator && SLSWindowIteratorGetCount(iterator) > 0) { if (SLSWindowIteratorAdvance(iterator)) { if (iterator_window_suitable(iterator)) suitable = true; } } if (iterator) CFRelease(iterator); CFRelease(query); } CFRelease(target_ref); return suitable; } static void app_windows_post_event_for_space(struct app_windows* windows, uint64_t sid) { int index = mission_control_index(sid); pid_t pid_list[windows->num_windows]; uint32_t pid_count[windows->num_windows]; char* pid_name[windows->num_windows]; memset(&pid_list, 0, sizeof(pid_t)*windows->num_windows); memset(&pid_count, 0, sizeof(uint32_t)*windows->num_windows); memset(&pid_name, 0, sizeof(char*)*windows->num_windows); uint32_t length = 64; for (int i = 0; i < windows->num_windows; i++) { for (int j = 0; j < windows->num_windows; j++) { if ((!pid_list[j] || pid_list[j] == windows->windows[i].pid) && windows->windows[i].sid == sid) { pid_list[j] = windows->windows[i].pid; pid_count[j]++; if (!pid_name[j]) { pid_name[j] = workspace_copy_app_name_for_pid(pid_list[j]); length += pid_name[j] ? (strlen(pid_name[j]) + 16) : 0; } break; } } } for (int i = 0; i < windows->num_windows; i++) { for (int j = i + 1; j < windows->num_windows; j++) { if (pid_name[i] && pid_name[j] && strcmp(pid_name[i], pid_name[j]) == 0) { free(pid_name[j]); pid_name[j] = NULL; pid_count[i] += pid_count[j]; } } } char payload[length]; memset(payload, 0, length); snprintf(payload, length, "{\n" "\t\"space\": %d,\n" "\t\"apps\": {\n", index ); char* cursor = payload + strlen(payload); bool first = true; for (int i = 0; i < windows->num_windows; i++) { if (!pid_list[i]) break; if (!pid_name[i]) continue; if (!first) { snprintf(cursor, length - (cursor - payload), ",\n"); cursor = payload + strlen(payload); } else first = false; snprintf(cursor, length - (cursor - payload), "\t\t\"%s\": %d", pid_name[i], pid_count[i] ); free(pid_name[i]); cursor = payload + strlen(payload); } snprintf(cursor, length - (cursor - payload), "\n\t}\n}\n"); struct event event = { payload, SPACE_WINDOWS_CHANGED }; event_post(&event); } static void app_windows_update_space(struct app_windows* windows, uint64_t sid, bool silent) { app_windows_clear_space(windows, sid); CFArrayRef space_list_ref = cfarray_of_cfnumbers(&sid, sizeof(uint64_t), 1, kCFNumberSInt64Type); uint64_t set_tags = 1; uint64_t clear_tags = 0; CFArrayRef window_list = SLSCopyWindowsWithOptionsAndTags(g_connection, 0, space_list_ref, 0x2, &set_tags, &clear_tags ); if (window_list) { uint32_t window_count = CFArrayGetCount(window_list); if (window_count > 0) { CFTypeRef query = SLSWindowQueryWindows(g_connection, window_list, 0x0); if (query) { CFTypeRef iterator = SLSWindowQueryResultCopyWindows(query); if (iterator) { while(SLSWindowIteratorAdvance(iterator)) { if (iterator_window_suitable(iterator)) { uint32_t wid = SLSWindowIteratorGetWindowID(iterator); int wid_cid = 0; SLSGetWindowOwner(g_connection, wid, &wid_cid); pid_t pid = 0; SLSConnectionGetPID(wid_cid, &pid); struct app_window window = {.wid = wid, .sid = sid, .pid = pid}; app_windows_add(windows, &window); } } CFRelease(iterator); } CFRelease(query); } } CFRelease(window_list); } CFRelease(space_list_ref); if (!silent) app_windows_post_event_for_space(windows, sid); app_windows_register_notifications(); } struct window_spawn_data { uint64_t sid; uint32_t wid; }; static void window_spawn_handler(uint32_t event, struct window_spawn_data* data, size_t _, int cid) { uint32_t wid = data->wid; uint64_t sid = data->sid; if (!wid || !sid) return; struct app_window window = { .wid = wid, .sid = sid, .pid = 0 }; if (event == 1325 && app_window_suitable(&window)) { app_windows_update_space(&g_windows, sid, false); } else if (event == 1326 && app_windows_find(&g_windows, &window)) { app_windows_update_space(&g_windows, sid, false); struct app_window* window = app_windows_find_by_wid(&g_hidden_windows, wid ); if (window) app_window_clear(window); } } static void window_hide_handler(uint32_t event, uint32_t* window_id, size_t _, int cid) { uint32_t wid = *window_id; if (event == 816) { struct app_window* window = app_windows_find_by_wid(&g_windows, wid); if (window) { if (!app_windows_find(&g_hidden_windows, window)) { app_windows_add(&g_hidden_windows, window); } app_windows_update_space(&g_windows, window->sid, false); } } else if (event == 815) { struct app_window* window = app_windows_find_by_wid(&g_hidden_windows, wid); if (window) { app_windows_update_space(&g_windows, window->sid, false); app_window_clear(window); app_windows_register_notifications(); } } } static void update_all_spaces(struct app_windows* windows, bool silent) { uint32_t display_count = 0; uint32_t* displays = display_active_display_list(&display_count); for (int i = 0; i < display_count; i++) { int space_count = 0; uint64_t* spaces = display_space_list(displays[i], &space_count); for (int j = 0; j < space_count; j++) { app_windows_update_space(windows, spaces[j], silent); } if (spaces) free(spaces); } if (displays) free(displays); } static void space_handler(uint32_t event, void* data, size_t data_length, void* context) { update_all_spaces(&g_windows, event == 1401); } void forced_space_windows_event() { if (g_space_window_events) update_all_spaces(&g_windows, false); } void begin_receiving_space_window_events() { if (!g_space_window_events) { SLSRegisterNotifyProc(window_spawn_handler, 1325, (void*)(intptr_t)g_connection); SLSRegisterNotifyProc(window_spawn_handler, 1326, (void*)(intptr_t)g_connection); SLSRegisterNotifyProc(window_hide_handler, 815, (void*)(intptr_t)g_connection); SLSRegisterNotifyProc(window_hide_handler, 816, (void*)(intptr_t)g_connection); SLSRegisterNotifyProc((void*)space_handler, 1401, NULL); if (__builtin_available(macOS 13.0, *)) { SLSRegisterNotifyProc((void*)space_handler, 1327, NULL); SLSRegisterNotifyProc((void*)space_handler, 1328, NULL); } g_space_window_events = true; update_all_spaces(&g_windows, true); } } ================================================ FILE: src/app_windows.h ================================================ #pragma once #include "event.h" struct app_window { uint32_t wid; uint64_t sid; pid_t pid; }; struct app_windows { struct app_window* windows; uint32_t num_windows; }; void begin_receiving_space_window_events(); void forced_space_windows_event(); ================================================ FILE: src/background.c ================================================ #include "background.h" #include "image.h" #include "misc/helpers.h" #include "shadow.h" #include "animation.h" #include "bar_manager.h" void background_init(struct background* background) { background->enabled = false; background->clip = 0.f; background->clips = NULL; background->num_clips = 0; background->overrides_height = false; background->bounds.size.height = 0; background->bounds.size.width = 0; background->border_width = 0; background->padding_left = 0; background->padding_right = 0; background->corner_radius = 0; background->x_offset = 0; background->y_offset = 0; color_init(&background->color, 0x00000000); color_init(&background->border_color, 0x00000000); shadow_init(&background->shadow); image_init(&background->image); } bool background_set_height(struct background* background, uint32_t height) { if (background->bounds.size.height == height) return false; background->bounds.size.height = height; background->overrides_height = height != 0; return true; } static void background_reset_clip(struct background* background) { for (uint32_t i = 0; i < background->num_clips; i++) free(background->clips[i]); if (background->clips) free(background->clips); background->clips = NULL; background->num_clips = 0; } bool background_set_enabled(struct background* background, bool enabled) { if (background->enabled == enabled) return false; if (background_clips_bar(background)) { background_reset_clip(background); g_bar_manager.bar_needs_update = true; } background->enabled = enabled; return true; } bool background_set_color(struct background* background, uint32_t color) { bool changed = background_set_enabled(background, true); return color_set_hex(&background->color, color) || changed; } static bool background_set_clip(struct background* background, float clip) { if (background->clip == clip) return false; background->clip = clip; g_bar_manager.bar_needs_update = true; g_bar_manager.might_need_clipping = true; if (clip > 0.f) background_set_enabled(background, true); return true; } static bool background_set_border_color(struct background* background, uint32_t color) { return color_set_hex(&background->border_color, color); } static bool background_set_border_width(struct background* background, uint32_t border_width) { if (background->border_width == border_width) return false; background->border_width = border_width; return true; } static bool background_set_corner_radius(struct background* background, uint32_t corner_radius) { if (background->corner_radius == corner_radius) return false; background->corner_radius = corner_radius; return true; } static bool background_set_xoffset(struct background* background, int offset) { if (background->x_offset == offset) return false; background->x_offset = offset; return true; } static bool background_set_yoffset(struct background* background, int offset) { if (background->y_offset == offset) return false; background->y_offset = offset; return true; } bool background_set_padding_left(struct background* background, uint32_t pad) { if (background->padding_left == pad) return false; background->padding_left = pad; return true; } bool background_set_padding_right(struct background* background, uint32_t pad) { if (background->padding_right == pad) return false; background->padding_right = pad; return true; } bool background_clip_needs_update(struct background* background, struct bar* bar) { if (background->clip == 0.f || !background->enabled) return false; struct background* clip = background_get_clip(background, bar->adid); if (!CGRectEqualToRect(background->bounds, clip->bounds)) return true; if (background->corner_radius != clip->corner_radius) return true; if (background->x_offset != clip->x_offset) return true; if (background->y_offset != clip->y_offset) return true; return false; } static void background_update_clip(struct background* background, struct background* clip) { memcpy(clip, background, sizeof(struct background)); background_clear_pointers(clip); } struct background* background_get_clip(struct background* background, uint32_t adid) { if (adid < 1) return NULL; if (background->num_clips < adid) { background->clips = (struct background**) realloc(background->clips, sizeof(struct background*)*adid); memset(background->clips + background->num_clips, 0, sizeof(struct background*) * (adid - background->num_clips)); background->num_clips = adid; } if (!background->clips[adid - 1]) { struct background* clip = malloc(sizeof(struct background)); background->clips[adid - 1] = clip; background_init(clip); background_update_clip(background, clip); clip->bounds.origin = g_nirvana; } return background->clips[adid - 1]; } bool background_clips_bar(struct background* background) { return background->enabled && (background->clip > 0.f); } void background_clip_bar(struct background* background, int offset, struct bar* bar) { if (!background_clips_bar(background)) return; struct background* clip = background_get_clip(background, bar->adid); background_update_clip(background, clip); CGRect background_bounds = background->bounds; background_bounds.origin.x += offset + background->x_offset; background_bounds.origin.y += background->y_offset; clip_rect(bar->window.context, background_bounds, background->clip, background->corner_radius); } void background_calculate_bounds(struct background* background, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { background->bounds.origin.x = x; background->bounds.origin.y = y - height / 2; background->bounds.size.width = width; background->bounds.size.height = height; if (background->image.enabled) image_calculate_bounds(&background->image, x, y); } static void draw_rect(CGContextRef context, CGRect region, struct color* fill_color, uint32_t corner_radius, uint32_t line_width, struct color* stroke_color) { CGContextSetLineWidth(context, line_width); if (stroke_color) CGContextSetRGBStrokeColor(context, stroke_color->r, stroke_color->g, stroke_color->b, stroke_color->a); CGContextSetRGBFillColor(context, fill_color->r, fill_color->g, fill_color->b, fill_color->a); CGMutablePathRef path = CGPathCreateMutable(); CGRect inset_region = CGRectInset(region, (float)(line_width) / 2.f, (float)(line_width) / 2.f); if (corner_radius > inset_region.size.height / 2.f || corner_radius > inset_region.size.width / 2.f) corner_radius = inset_region.size.height > inset_region.size.width ? inset_region.size.width / 2.f : inset_region.size.height / 2.f; CGPathAddRoundedRect(path, NULL, inset_region, corner_radius, corner_radius); CGContextAddPath(context, path); CGContextDrawPath(context, kCGPathFillStroke); CFRelease(path); } void background_draw(struct background* background, CGContextRef context) { if (!background->enabled) return; if ((background->border_color.a == 0 || background->border_width == 0) && (background->color.a == 0) && !background->shadow.enabled && !background->image.enabled ) { // The background is enabled but has no content. return; } CGRect background_bounds = background->bounds; background_bounds.origin.x += background->x_offset; background_bounds.origin.y += background->y_offset; if (background->shadow.enabled) { CGRect bounds = shadow_get_bounds(&background->shadow, background_bounds); draw_rect(context, bounds, &background->shadow.color, background->corner_radius, background->border_width, &background->shadow.color); } draw_rect(context, background_bounds, &background->color, background->corner_radius, background->border_width, &background->border_color); if (background->image.enabled) image_draw(&background->image, context); } void background_clear_pointers(struct background* background) { background->clips = NULL; background->num_clips = 0; image_clear_pointers(&background->image); } void background_destroy(struct background* background) { for (uint32_t i = 0; i < background->num_clips; i++) free(background->clips[i]); if (background->clips) free(background->clips); image_destroy(&background->image); background_clear_pointers(background); } void background_serialize(struct background* background, char* indent, FILE* rsp, bool detailed) { fprintf(rsp, "%s\"drawing\": \"%s\",\n" "%s\"color\": \"0x%x\",\n" "%s\"border_color\": \"0x%x\",\n" "%s\"border_width\": %u,\n" "%s\"height\": %u,\n" "%s\"corner_radius\": %u,\n" "%s\"padding_left\": %d,\n" "%s\"padding_right\": %d,\n" "%s\"x_offset\": %d,\n" "%s\"y_offset\": %d,\n" "%s\"clip\": %f,\n", indent, format_bool(background->enabled), indent, background->color.hex, indent, background->border_color.hex, indent, background->border_width, indent, background->overrides_height ? (int)background->bounds.size.height : 0, indent, background->corner_radius, indent, background->padding_left, indent, background->padding_right, indent, background->x_offset, indent, background->y_offset, indent, background->clip ); char deeper_indent[strlen(indent) + 2]; snprintf(deeper_indent, strlen(indent) + 2, "%s\t", indent); fprintf(rsp, "%s\"image\": {\n", indent); image_serialize(&background->image, deeper_indent, rsp); fprintf(rsp, "\n%s}", indent); if (!detailed) return; fprintf(rsp, ",\n%s\"shadow\": {\n", indent); shadow_serialize(&background->shadow, deeper_indent, rsp); fprintf(rsp, "\n%s}", indent); } bool background_parse_sub_domain(struct background* background, FILE* rsp, struct token property, char* message) { bool needs_refresh = false; if (token_equals(property, PROPERTY_DRAWING)) return background_set_enabled(background, evaluate_boolean_state(get_token(&message), background->enabled)); else if (token_equals(property, PROPERTY_CLIP)) { struct token token = get_token(&message); ANIMATE_FLOAT(background_set_clip, background, background->clip, token_to_float(token)); } else if (token_equals(property, PROPERTY_HEIGHT)) { struct token token = get_token(&message); ANIMATE(background_set_height, background, background->bounds.size.height, token_to_int(token) ); } else if (token_equals(property, PROPERTY_CORNER_RADIUS)) { struct token token = get_token(&message); ANIMATE(background_set_corner_radius, background, background->corner_radius, token_to_int(token) ); } else if (token_equals(property, PROPERTY_BORDER_WIDTH)) { struct token token = get_token(&message); ANIMATE(background_set_border_width, background, background->border_width, token_to_int(token) ); } else if (token_equals(property, PROPERTY_COLOR)) { struct token token = get_token(&message); ANIMATE_BYTES(background_set_color, background, background->color.hex, token_to_int(token) ); } else if (token_equals(property, PROPERTY_BORDER_COLOR)) { struct token token = get_token(&message); ANIMATE_BYTES(background_set_border_color, background, background->border_color.hex, token_to_int(token) ); } else if (token_equals(property, PROPERTY_PADDING_LEFT)) { struct token token = get_token(&message); ANIMATE(background_set_padding_left, background, background->padding_left, token_to_int(token) ); } else if (token_equals(property, PROPERTY_PADDING_RIGHT)) { struct token token = get_token(&message); ANIMATE(background_set_padding_right, background, background->padding_right, token_to_int(token) ); } else if (token_equals(property, PROPERTY_XOFFSET)) { struct token token = get_token(&message); ANIMATE(background_set_xoffset, background, background->x_offset, token_to_int(token) ); } else if (token_equals(property, PROPERTY_YOFFSET)) { struct token token = get_token(&message); ANIMATE(background_set_yoffset, background, background->y_offset, token_to_int(token) ); } else if (token_equals(property, SUB_DOMAIN_IMAGE)) { return image_load(&background->image, token_to_string(get_token(&message)), rsp ); } else { struct key_value_pair key_value_pair = get_key_value_pair(property.text, '.' ); if (key_value_pair.key && key_value_pair.value) { struct token subdom = {key_value_pair.key,strlen(key_value_pair.key)}; struct token entry = {key_value_pair.value,strlen(key_value_pair.value)}; if (token_equals(subdom, SUB_DOMAIN_SHADOW)) return shadow_parse_sub_domain(&background->shadow, rsp, entry, message ); else if (token_equals(subdom, SUB_DOMAIN_IMAGE)) { return image_parse_sub_domain(&background->image, rsp, entry, message); } else if (token_equals(subdom, SUB_DOMAIN_COLOR)) { return color_parse_sub_domain(&background->color, rsp, entry, message); } else if (token_equals(subdom, SUB_DOMAIN_BORDER_COLOR)) { return color_parse_sub_domain(&background->border_color, rsp, entry, message ); } else { respond(rsp, "[!] Background: Invalid subdomain '%s'\n", subdom.text); } } else { respond(rsp, "[!] Background: Invalid property '%s'\n", property.text); } } return needs_refresh; } ================================================ FILE: src/background.h ================================================ #pragma once #include "image.h" struct background { bool enabled; float clip; bool overrides_height; int padding_left; int padding_right; int x_offset; int y_offset; uint32_t border_width; uint32_t corner_radius; CGRect bounds; struct image image; struct shadow shadow; struct color color; struct color border_color; struct background** clips; uint32_t num_clips; }; struct bar; void background_init(struct background* background); void background_calculate_bounds(struct background* background, uint32_t x, uint32_t y, uint32_t width, uint32_t height); bool background_set_enabled(struct background* background, bool enabled); bool background_set_color(struct background* background, uint32_t color); bool background_set_height(struct background* background, uint32_t height); bool background_set_padding_left(struct background* background, uint32_t pad); bool background_set_padding_right(struct background* background, uint32_t pad); void background_draw(struct background* background, CGContextRef context); struct background* background_get_clip(struct background* background, uint32_t adid); void background_clip_bar(struct background* background, int offset, struct bar* bar); bool background_clip_needs_update(struct background* background, struct bar* bar); bool background_clips_bar(struct background* background); void background_clear_pointers(struct background* background); void background_destroy(struct background* background); void background_serialize(struct background* background, char* indent, FILE* rsp, bool detailed); bool background_parse_sub_domain(struct background* background, FILE* rsp, struct token property, char* message); ================================================ FILE: src/bar.c ================================================ #include "bar.h" #include "bar_manager.h" #include "event.h" #include "display.h" #include "misc/helpers.h" #include "window.h" #define MAX_RENDER_THREADS 10 static pthread_t g_render_threads[MAX_RENDER_THREADS]; static uint32_t g_used_threads = 0; void join_render_threads() { for (int i = 0; i < g_used_threads; i++) pthread_join(g_render_threads[i], NULL); g_used_threads = 0; } bool bar_draws_item(struct bar* bar, struct bar_item* bar_item) { if (!bar_item->drawing || !bar->shown || bar->hidden) return false; if (((bar_item->associated_display > 0 && (!(bar_item->associated_display & (1 << bar->adid)))) || (bar_item->associated_to_active_display && (bar->adid != g_bar_manager.active_adid))) && !bar_item->ignore_association) return false; if (bar_item->associated_space > 0 && (!(bar_item->associated_space & (1 << bar->sid)) && !bar_item->ignore_association) && (bar_item->type != BAR_COMPONENT_SPACE) ) return false; if (bar_item->position == POSITION_POPUP && (!bar_item->parent || !bar_item->parent->popup.drawing || (bar->adid != g_bar_manager.active_adid))) return false; return true; } static void bar_calculate_popup_anchor_for_bar_item(struct bar* bar, struct bar_item* bar_item) { if (bar->adid != g_bar_manager.active_adid) return; struct window* window = bar_item_get_window(bar_item, bar->adid); if (!bar_item->popup.overrides_cell_size) { if (g_bar_manager.position == POSITION_LEFT || g_bar_manager.position == POSITION_RIGHT) { bar_item->popup.cell_size = window->frame.size.width; } else { bar_item->popup.cell_size = window->frame.size.height; } } popup_calculate_bounds(&bar_item->popup, bar); CGPoint anchor = window->origin; if (g_bar_manager.position == POSITION_LEFT || g_bar_manager.position == POSITION_RIGHT) { if (bar_item->popup.align == POSITION_CENTER) { anchor.y += (window->frame.size.height - bar_item->popup.background.bounds.size.height) / 2; } else if (bar_item->popup.align == POSITION_LEFT) { anchor.y -= bar_item->background.padding_left; } else { anchor.y += window->frame.size.height - bar_item->popup.background.bounds.size.height; } anchor.x += (g_bar_manager.position == POSITION_RIGHT ? (- bar_item->popup.background.bounds.size.width) : window->frame.size.width); } else { if (bar_item->popup.align == POSITION_CENTER) { anchor.x += (window->frame.size.width - bar_item->popup.background.bounds.size.width) / 2; } else if (bar_item->popup.align == POSITION_LEFT) { anchor.x -= bar_item->background.padding_left; } else { anchor.x += window->frame.size.width - bar_item->popup.background.bounds.size.width; } anchor.y += (g_bar_manager.position == POSITION_BOTTOM ? (- bar_item->popup.background.bounds.size.height) : window->frame.size.height); } popup_set_anchor(&bar_item->popup, anchor, bar->adid); popup_calculate_bounds(&bar_item->popup, bar); } void bar_order_item_windows(struct bar* bar) { if (bar->sid < 1 || bar->adid < 1 || !bar->shown) return; window_set_level(&bar->window, g_bar_manager.window_level); window_order(&bar->window, NULL, W_ABOVE); struct window* previous_window = NULL; struct window* first_window = NULL; for (int i = 0; i < g_bar_manager.bar_item_count; i++) { struct bar_item* bar_item = g_bar_manager.bar_items[i]; if (bar_item->position == POSITION_POPUP) continue; struct window* window = bar_item_get_window(bar_item, bar->adid); window_set_level(window, g_bar_manager.window_level); if (!first_window) first_window = window; if (bar_item->type == BAR_COMPONENT_GROUP) { if (first_window) window_order(window, first_window, W_BELOW); else window_order(window, &bar->window, W_ABOVE); continue; } if (previous_window) window_order(window, previous_window, W_ABOVE); else window_order(window, &bar->window, W_ABOVE); previous_window = window; } } static void bar_check_for_clip_updates(struct bar* bar) { if (!g_bar_manager.bar_needs_update) { for (int i = 0; i < g_bar_manager.bar_item_count; i++) { struct bar_item* bar_item = g_bar_manager.bar_items[i]; struct window* window = bar_item_get_window(bar_item, bar->adid); bool clips_bar = bar_item_clips_bar(bar_item); if (!clips_bar || (!bar_draws_item(bar, bar_item))) { if (clips_bar && !CGPointEqualToPoint(window->origin, g_nirvana)) { g_bar_manager.bar_needs_update = true; break; } continue; } g_bar_manager.bar_needs_update |= window->needs_move || window->needs_resize || bar_item_clip_needs_update_for_bar(bar_item, bar); if (g_bar_manager.bar_needs_update) break; } } } void bar_draw(struct bar* bar, bool forced, bool threaded) { if (bar->sid < 1 || bar->adid < 1) return; if (g_bar_manager.might_need_clipping) bar_check_for_clip_updates(bar); if (g_bar_manager.bar_needs_update) { struct background background = g_bar_manager.background; background.bounds = bar->window.frame; background.bounds.origin.y -= background.y_offset; background.shadow.enabled = false; background.enabled = true; windows_freeze(); CGContextClearRect(bar->window.context, bar->window.frame); background_draw(&background, bar->window.context); } for (int i = 0; i < g_bar_manager.bar_item_count; i++) { struct bar_item* bar_item = g_bar_manager.bar_items[i]; struct window* window = bar_item_get_window(bar_item, bar->adid); if (!bar_draws_item(bar, bar_item)){ if (!CGPointEqualToPoint(window->origin, g_nirvana)) { window_move(window, g_nirvana); } bar_item_remove_associated_bar(bar_item, bar->adid); continue; } bar_item_append_associated_bar(bar_item, bar->adid); if (bar_item->popup.drawing && bar->adid == g_bar_manager.active_adid) popup_draw(&bar_item->popup); if (g_bar_manager.bar_needs_update) { bar_item_clip_bar(bar_item, window->origin.x - bar->window.origin.x, bar ); } if (!window_apply_frame(window, forced) && !bar_item->needs_update) continue; if (bar_item->update_mask & UPDATE_MOUSE_ENTERED || bar_item->update_mask & UPDATE_MOUSE_EXITED) { window_assign_mouse_tracking_area(window, window->frame); } windows_freeze(); if (threaded && g_used_threads < MAX_RENDER_THREADS) { uint32_t thread_id = g_used_threads++; struct draw_item_payload { struct window* window; struct bar_item bar_item; }* context = malloc(sizeof(struct draw_item_payload)); // We need to perform a shallow copy of the item here because // the cached bounds will be invalidated when drawing the next bar. context->bar_item = *bar_item; context->window = window; pthread_create(&g_render_threads[thread_id], NULL, draw_item_proc, context ); } else { CGContextClearRect(window->context, window->frame); bar_item_draw(bar_item, window->context); CGContextFlush(window->context); window_flush(window); } } if (g_bar_manager.bar_needs_update) { CGContextFlush(bar->window.context); window_flush(&bar->window); } } static void bar_calculate_bounds_top_bottom(struct bar* bar) { bool is_builtin = CGDisplayIsBuiltin(bar->did); uint32_t notch_width = is_builtin ? g_bar_manager.notch_width : 0; uint32_t center_length = bar_manager_length_for_bar_side(&g_bar_manager, bar, POSITION_CENTER); uint32_t bar_left_first_item_x = max(g_bar_manager.background.padding_left, 0 ); uint32_t bar_right_first_item_x = bar->window.frame.size.width -max(g_bar_manager.background.padding_right, 0 ); uint32_t bar_center_first_item_x = (bar->window.frame.size.width - center_length) / 2; uint32_t bar_center_right_first_item_x = (bar->window.frame.size.width + notch_width) / 2; uint32_t bar_center_left_first_item_x = (bar->window.frame.size.width - notch_width) / 2; uint32_t* next_position = NULL; uint32_t y = bar->window.frame.size.height / 2; for (int i = 0; i < g_bar_manager.bar_item_count; i++) { struct bar_item* bar_item = g_bar_manager.bar_items[i]; if (!bar_draws_item(bar, bar_item) || bar_item->type == BAR_COMPONENT_GROUP || bar_item->position == POSITION_POPUP ) { continue; } uint32_t bar_item_display_length = bar_item_get_length(bar_item, true); bool rtl = false; if (bar_item->position == POSITION_LEFT) next_position = &bar_left_first_item_x; else if (bar_item->position == POSITION_CENTER) next_position = &bar_center_first_item_x; else if (bar_item->position == POSITION_RIGHT) next_position = &bar_right_first_item_x, rtl = true; else if (bar_item->position == POSITION_CENTER_RIGHT) next_position = &bar_center_right_first_item_x; else if (bar_item->position == POSITION_CENTER_LEFT) next_position = &bar_center_left_first_item_x, rtl = true; else continue; if (bar_item->position == POSITION_RIGHT || bar_item->position == POSITION_CENTER_LEFT) { *next_position = min(*next_position - bar_item_display_length - bar_item->background.padding_right, bar->window.frame.size.width - bar_item_display_length ); } else { *next_position += max((int)-*next_position, bar_item->background.padding_left); } bar_item->graph.rtl = rtl; CGPoint shadow_offsets = bar_item_calculate_shadow_offsets(bar_item); uint32_t bar_item_length = bar_item_calculate_bounds(bar_item, bar->window.frame.size.height - (g_bar_manager.background.border_width + 1), max(shadow_offsets.x, 0), y ); CGRect frame = {{bar->window.origin.x + *next_position - max(shadow_offsets.x, 0), bar->window.origin.y }, {bar_item_display_length + shadow_offsets.x + shadow_offsets.y, bar->window.frame.size.height} }; window_set_frame(bar_item_get_window(bar_item, bar->adid), frame); if (bar_item->popup.drawing) bar_calculate_popup_anchor_for_bar_item(bar, bar_item); if (bar_item->position == POSITION_RIGHT || bar_item->position == POSITION_CENTER_LEFT) { *next_position += bar_item->has_const_width ? bar_item_display_length + bar_item->background.padding_right - bar_item->custom_width : (- bar_item->background.padding_left); } else { *next_position += bar_item->has_const_width ? bar_item->custom_width - bar_item->background.padding_left : (bar_item_length + bar_item->background.padding_right); } } for (int i = 0; i < g_bar_manager.bar_item_count; i++) { struct bar_item* bar_item = g_bar_manager.bar_items[i]; if (bar_item->type != BAR_COMPONENT_GROUP || bar_item->position == POSITION_POPUP || !bar_draws_item(bar, bar_item)) { continue; } group_calculate_bounds(bar_item->group, bar, y); window_set_frame(bar_item_get_window(bar_item->group->members[0], bar->adid ), bar_item->group->bounds ); if (bar_item->popup.drawing) bar_calculate_popup_anchor_for_bar_item(bar, bar_item); } } static void bar_calculate_bounds_left_right(struct bar* bar) { uint32_t notch_width = 0; uint32_t center_length = bar_manager_length_for_bar_side(&g_bar_manager, bar, POSITION_CENTER); uint32_t bar_left_first_item_y = max(g_bar_manager.background.padding_left, 0 ); uint32_t bar_right_first_item_y = bar->window.frame.size.height -max(g_bar_manager.background.padding_right, 0 ); uint32_t bar_center_first_item_y = (bar->window.frame.size.height - 2*g_bar_manager.margin - center_length) / 2 - 1; uint32_t bar_center_right_first_item_y = (bar->window.frame.size.height + notch_width) / 2; uint32_t bar_center_left_first_item_y = (bar->window.frame.size.height - notch_width) / 2 ; uint32_t* next_position = NULL; for (int i = 0; i < g_bar_manager.bar_item_count; i++) { struct bar_item* bar_item = g_bar_manager.bar_items[i]; if (!bar_draws_item(bar, bar_item) || bar_item->type == BAR_COMPONENT_GROUP || bar_item->position == POSITION_POPUP ) { continue; } uint32_t bar_item_display_height = bar_item_get_height(bar_item); uint32_t bar_item_display_length = bar_item_get_length(bar_item, true); uint32_t x = 0; bool rtl = false; if (bar_item->position == POSITION_LEFT) next_position = &bar_left_first_item_y; else if (bar_item->position == POSITION_CENTER) next_position = &bar_center_first_item_y; else if (bar_item->position == POSITION_RIGHT) next_position = &bar_right_first_item_y, rtl = true; else if (bar_item->position == POSITION_CENTER_RIGHT) next_position = &bar_center_right_first_item_y; else if (bar_item->position == POSITION_CENTER_LEFT) next_position = &bar_center_left_first_item_y, rtl = true; else continue; if (bar_item->position == POSITION_RIGHT || bar_item->position == POSITION_CENTER_LEFT) { *next_position = min(*next_position - bar_item_display_height - bar_item->background.padding_right, bar->window.frame.size.height - bar_item_display_height ); } else { *next_position += max((int)-*next_position, bar_item->background.padding_left); } bar_item->graph.rtl = rtl; CGPoint shadow_offsets = bar_item_calculate_shadow_offsets(bar_item); bar_item_calculate_bounds(bar_item, bar_item_display_height, (g_bar_manager.background.bounds.size.height - bar_item_display_length) / 2. + max(shadow_offsets.x, 0), bar_item_display_height / 2.); CGRect frame = {{bar->window.origin.x + x - max(shadow_offsets.x, 0), bar->window.origin.y + *next_position + -max(-bar_item->y_offset, 0)}, {g_bar_manager.background.bounds.size.height, bar_item_display_height + abs(bar_item->y_offset)}}; window_set_frame(bar_item_get_window(bar_item, bar->adid), frame); if (bar_item->popup.drawing) bar_calculate_popup_anchor_for_bar_item(bar, bar_item); if (bar_item->position == POSITION_RIGHT || bar_item->position == POSITION_CENTER_LEFT) { *next_position += bar_item->has_const_width ? bar_item_display_height + bar_item->background.padding_right - bar_item->custom_width : - bar_item->background.padding_left; } else { *next_position += bar_item->has_const_width ? bar_item->custom_width - bar_item->background.padding_left : (bar_item_display_height + bar_item->background.padding_right); } } } void bar_calculate_bounds(struct bar* bar) { if (bar->sid < 1 || bar->adid < 1) return; if (g_bar_manager.position == POSITION_LEFT || g_bar_manager.position == POSITION_RIGHT) { bar_calculate_bounds_left_right(bar); } else { bar_calculate_bounds_top_bottom(bar); } } static CGRect bar_get_frame(struct bar *bar) { bool is_builtin = CGDisplayIsBuiltin(bar->did); int notch_offset = is_builtin ? g_bar_manager.notch_offset : 0; int notch_display_height = is_builtin ? g_bar_manager.notch_display_height : 0; CGRect bounds = display_bounds(bar->did); CGPoint origin = bounds.origin; if (g_bar_manager.position == POSITION_LEFT || g_bar_manager.position == POSITION_RIGHT) { bounds.size.height -= 2*g_bar_manager.background.y_offset; origin.x += (g_bar_manager.position == POSITION_RIGHT ? (bounds.size.width - g_bar_manager.background.bounds.size.height - g_bar_manager.margin) : g_bar_manager.margin); origin.y += g_bar_manager.background.y_offset; if (display_menu_bar_visible() && !g_bar_manager.topmost) { CGRect menu = display_menu_bar_rect(bar->did); origin.y += menu.size.height; bounds.size.height -= menu.size.height; } return (CGRect) {{origin.x, origin.y}, {g_bar_manager.background.bounds.size.height, bounds.size.height }}; } else { bounds.size.width -= 2*g_bar_manager.margin; CGPoint origin = bounds.origin; origin.x += g_bar_manager.margin; origin.y += g_bar_manager.background.y_offset + notch_offset; if (g_bar_manager.position == POSITION_BOTTOM) { origin.y = CGRectGetMaxY(bounds) - g_bar_manager.background.bounds.size.height - 2*(g_bar_manager.background.y_offset) - notch_offset; } else if (display_menu_bar_visible() && !g_bar_manager.topmost) { CGRect menu = display_menu_bar_rect(bar->did); origin.y += menu.size.height; } if (notch_display_height > 0) { return (CGRect) {{origin.x, origin.y}, {bounds.size.width, g_bar_manager.notch_display_height}}; } return (CGRect) {{origin.x, origin.y}, {bounds.size.width, g_bar_manager.background.bounds.size.height}}; } } void bar_resize(struct bar* bar) { if (bar->hidden || !bar->shown) { window_move(&bar->window, g_nirvana); return; }; window_set_frame(&bar->window, bar_get_frame(bar)); if (window_apply_frame(&bar->window, false)) { window_assign_mouse_tracking_area(&bar->window, bar->window.frame); g_bar_manager.bar_needs_update = true; } } void bar_set_hidden(struct bar* bar, bool hidden) { if (bar->hidden == hidden) return; bar->hidden = hidden; if (hidden) window_move(&bar->window, g_nirvana); else bar_resize(bar); } static void bar_create_window(struct bar* bar) { window_init(&bar->window); window_create(&bar->window, bar_get_frame(bar)); window_assign_mouse_tracking_area(&bar->window, bar->window.frame); window_set_blur_radius(&bar->window, g_bar_manager.blur_radius); if (!g_bar_manager.shadow) window_disable_shadow(&bar->window); context_set_font_smoothing(bar->window.context, g_bar_manager.font_smoothing); } void bar_change_space(struct bar* bar, uint64_t dsid) { if (bar->adid < 1) return; for (uint32_t i = 0; i < g_bar_manager.bar_item_count; i++) { struct bar_item* bar_item = g_bar_manager.bar_items[i]; bar_item_change_space(bar_item, dsid, bar->adid); } window_send_to_space(&bar->window, dsid); } struct bar *bar_create(uint32_t did) { struct bar *bar = malloc(sizeof(struct bar)); memset(bar, 0, sizeof(struct bar)); bar->hidden = false; bar->mouse_over = false; bar->did = did; bar->dsid = display_space_id(did); bar->sid = mission_control_index(bar->dsid); bar->shown = SLSSpaceGetType(g_connection, bar->dsid) != 4; g_bar_manager.bar_needs_update = true; bar_create_window(bar); return bar; } void bar_destroy(struct bar *bar) { window_close(&bar->window); free(bar); } ================================================ FILE: src/bar.h ================================================ #pragma once #include "bar_item.h" #include "misc/helpers.h" #include "window.h" struct bar { bool shown; bool hidden; bool mouse_over; uint32_t sid; uint32_t dsid; uint32_t did; uint32_t adid; struct window window; }; struct bar *bar_create(uint32_t did); void bar_close_window(struct bar* bar); void bar_destroy(struct bar* bar); void bar_set_hidden(struct bar* bar, bool hidden); void bar_calculate_bounds(struct bar* bar); void bar_resize(struct bar* bar); void bar_draw(struct bar* bar, bool forced, bool threaded); void bar_order_item_windows(struct bar* bar); bool bar_draws_item(struct bar* bar, struct bar_item* bar_item); void bar_change_space(struct bar* bar, uint64_t dsid); void context_set_font_smoothing(CGContextRef context, bool smoothing); void join_render_threads(); ================================================ FILE: src/bar_item.c ================================================ #include "bar_item.h" #include "bar_manager.h" #include "event.h" #include "volume.h" #include "power.h" #include "media.h" #include "app_windows.h" struct bar_item* bar_item_create() { struct bar_item* bar_item = malloc(sizeof(struct bar_item)); memset(bar_item, 0, sizeof(struct bar_item)); return bar_item; } void bar_item_init(struct bar_item* bar_item, struct bar_item* default_item) { bar_item->needs_update = true; bar_item->lazy = true; bar_item->drawing = true; bar_item->updates = true; bar_item->updates_only_when_shown = false; bar_item->selected = false; bar_item->ignore_association = false; bar_item->overrides_association = false; bar_item->counter = 0; bar_item->type = BAR_ITEM; bar_item->update_frequency = 0; bar_item->position = POSITION_LEFT; bar_item->align = POSITION_LEFT; bar_item->associated_to_active_display = false; bar_item->associated_display = 0; bar_item->associated_space = 0; bar_item->associated_bar = 0; bar_item->blur_radius = 0; bar_item->event_port = 0; bar_item->shadow = false; bar_item->scroll_texts = false; bar_item->mouse_over = false; bar_item->has_const_width = false; bar_item->custom_width = 0; bar_item->y_offset = 0; bar_item->has_alias = false; bar_item->has_graph = false; bar_item->name = NULL; bar_item->script = NULL; bar_item->click_script = NULL; bar_item->group = NULL; bar_item->parent = NULL; text_init(&bar_item->icon); text_init(&bar_item->label); background_init(&bar_item->background); env_vars_init(&bar_item->signal_args.env_vars); popup_init(&bar_item->popup, bar_item); graph_init(&bar_item->graph); alias_init(&bar_item->alias); slider_init(&bar_item->slider); if (default_item) bar_item_inherit_from_item(bar_item, default_item); } void bar_item_append_associated_space(struct bar_item* bar_item, uint32_t bit) { if (bar_item->associated_space & bit) return; bar_item->associated_space |= bit; if (bar_item->type == BAR_COMPONENT_SPACE) { bar_item->associated_space = bit; char sid_str[32]; snprintf(sid_str, 32, "%u", get_set_bit_position(bit)); env_vars_set(&bar_item->signal_args.env_vars, string_copy("SID"), string_copy(sid_str) ); } } void bar_item_append_associated_display(struct bar_item* bar_item, uint32_t bit) { if (bar_item->associated_display & bit) return; bar_item->associated_display |= bit; if (bar_item->type == BAR_COMPONENT_SPACE) { bar_item->overrides_association = true; bar_item->associated_display = bit; char did_str[32]; snprintf(did_str, 32, "%u", get_set_bit_position(bit)); env_vars_set(&bar_item->signal_args.env_vars, string_copy("DID"), string_copy(did_str) ); } } bool bar_item_is_shown(struct bar_item* bar_item) { if (bar_item->associated_bar & UINT32_MAX) return true; else return false; } void bar_item_append_associated_bar(struct bar_item* bar_item, uint32_t adid) { bar_item->associated_bar |= (1 << (adid - 1)); } void bar_item_remove_associated_bar(struct bar_item* bar_item, uint32_t adid) { bar_item->associated_bar &= ~(1 << (adid - 1)); } void bar_item_reset_associated_bar(struct bar_item* bar_item) { bar_item->associated_bar = 0; } bool bar_item_update(struct bar_item* bar_item, char* sender, bool forced, struct env_vars* env_vars) { bool is_shown = bar_item_is_shown(bar_item); if (is_shown && bar_item->scroll_texts && (bar_item->counter % 15 == 0)) { text_animate_scroll(&bar_item->icon); text_animate_scroll(&bar_item->label); if (bar_item->type == BAR_COMPONENT_SLIDER) text_animate_scroll(&bar_item->slider.knob); } bar_item->counter++; if ((!bar_item->updates || (bar_item->update_frequency == 0 && !sender)) && !forced ) { return false; } bool scheduled_update_needed = bar_item->update_frequency <= bar_item->counter; bool should_update = bar_item->updates_only_when_shown ? is_shown : true; if (((scheduled_update_needed || sender) && should_update) || forced) { bar_item->counter = 0; if ((bar_item->script && strlen(bar_item->script) > 0) || bar_item->event_port ) { if (!env_vars) env_vars = &bar_item->signal_args.env_vars; else { for (int i = 0; i < bar_item->signal_args.env_vars.count; i++) { env_vars_set(env_vars, string_copy(bar_item->signal_args.env_vars.vars[i]->key), string_copy(bar_item->signal_args.env_vars.vars[i]->value)); } env_vars_set(env_vars, string_copy("NAME"), string_copy(bar_item->name)); } if (sender) env_vars_set(env_vars, string_copy("SENDER"), string_copy(sender)); else env_vars_set(env_vars, string_copy("SENDER"), string_copy(forced ? "forced" : "routine")); } // Script Update if (bar_item->script && strlen(bar_item->script) > 0) { fork_exec(bar_item->script, env_vars); } // Mach events if (bar_item->event_port) { uint32_t len = 0; char* message = env_vars_copy_serialized_representation(env_vars, &len); mach_send_message(bar_item->event_port, message, len, false); free(message); } } return false; } void bar_item_needs_update(struct bar_item* bar_item) { bar_item->needs_update = true; } void bar_item_cancel_drag(struct bar_item* bar_item) { if (bar_item->has_slider) { char perc_str[8]; snprintf(perc_str, 8, "%d", bar_item->slider.percentage); env_vars_set(&bar_item->signal_args.env_vars, string_copy("PERCENTAGE"), string_copy(perc_str) ); slider_cancel_drag(&bar_item->slider); } } void bar_item_on_drag(struct bar_item* bar_item, CGPoint point) { if (bar_item->has_slider) { if (slider_handle_drag(&bar_item->slider, point)) { bar_item_needs_update(bar_item); } } } void bar_item_on_click(struct bar_item* bar_item, uint32_t type, uint32_t mouse_button_code, uint32_t modifier, CGPoint point) { if (!bar_item) return; struct env_vars env_vars; env_vars_init(&env_vars); char info_str[256]; snprintf(info_str, 256, "{\n" "\t\"button\": \"%s\",\n" "\t\"button_code\": %u,\n" "\t\"modifier\": \"%s\",\n" "\t\"modfier_code\": %u\n" "}\n", get_type_description(type), mouse_button_code, get_modifier_description(modifier), modifier ); env_vars_set(&env_vars, string_copy("INFO"), string_copy(info_str)); env_vars_set(&env_vars, string_copy("BUTTON"), string_copy(get_type_description(type))); env_vars_set(&env_vars, string_copy("MODIFIER"), string_copy(get_modifier_description(modifier))); if (bar_item->has_slider) { if (bar_item->slider.is_dragged || CGRectContainsPoint(bar_item->slider.background.bounds, point)) { if (slider_handle_drag(&bar_item->slider, point)) { bar_item_needs_update(bar_item); } bar_item_cancel_drag(bar_item); } else { env_vars_destroy(&env_vars); return; } } if (bar_item->click_script && strlen(bar_item->click_script) > 0) { for (int i = 0; i < bar_item->signal_args.env_vars.count; i++) { env_vars_set(&env_vars, string_copy(bar_item->signal_args.env_vars.vars[i]->key), string_copy(bar_item->signal_args.env_vars.vars[i]->value)); } fork_exec(bar_item->click_script, &env_vars); } if (bar_item->update_mask & UPDATE_MOUSE_CLICKED) bar_item_update(bar_item, COMMAND_SUBSCRIBE_MOUSE_CLICKED, true, &env_vars ); env_vars_destroy(&env_vars); } void bar_item_on_scroll(struct bar_item* bar_item, int scroll_delta, uint32_t modifier) { if (!bar_item) return; struct env_vars env_vars; env_vars_init(&env_vars); char info_str[256]; snprintf(info_str, 256, "{\n" "\t\"delta\": %d,\n" "\t\"modifier\": \"%s\",\n" "\t\"modfier_code\": %u\n" "}\n", scroll_delta, get_modifier_description(modifier), modifier ); env_vars_set(&env_vars, string_copy("INFO"), string_copy(info_str)); char delta_ver_str[32]; snprintf(delta_ver_str, 32, "%d", scroll_delta); env_vars_set(&env_vars, string_copy("SCROLL_DELTA"), string_copy(delta_ver_str)); env_vars_set(&env_vars, string_copy("MODIFIER"), string_copy(get_modifier_description(modifier))); if (bar_item->update_mask & UPDATE_MOUSE_SCROLLED) bar_item_update(bar_item, COMMAND_SUBSCRIBE_MOUSE_SCROLLED, true, &env_vars ); env_vars_destroy(&env_vars); } void bar_item_mouse_entered(struct bar_item* bar_item) { if (bar_item->update_mask & UPDATE_MOUSE_ENTERED && !bar_item->mouse_over) { bar_item_update(bar_item, COMMAND_SUBSCRIBE_MOUSE_ENTERED, true, NULL); } bar_item->mouse_over = true; } void bar_item_mouse_exited(struct bar_item* bar_item) { if (bar_item->update_mask & UPDATE_MOUSE_EXITED) { bar_item_update(bar_item, COMMAND_SUBSCRIBE_MOUSE_EXITED, true, NULL); } bar_item->mouse_over = false; } static bool bar_item_set_drawing(struct bar_item* bar_item, bool state) { if (bar_item->drawing == state) return false; bar_item->drawing = state; return true; } static void bar_item_set_script(struct bar_item* bar_item, char* script) { if (!script) return; if (bar_item->script && strcmp(bar_item->script, script) == 0) { free(script); return; } if (script != bar_item->script && bar_item->script) free(bar_item->script); char* path = resolve_path(script); if (path) bar_item->script = path; } static void bar_item_set_click_script(struct bar_item* bar_item, char* script) { if (!script) return; if (bar_item->click_script && strcmp(bar_item->click_script, script) == 0) { free(script); return; } if (script != bar_item->click_script && bar_item->click_script) free(bar_item->click_script); char* path = resolve_path(script); if (path) bar_item->click_script = path; } static bool bar_item_set_yoffset(struct bar_item* bar_item, int offset) { if (bar_item->y_offset == offset) return false; bar_item->y_offset = offset; return true; } static bool bar_item_set_blur_radius(struct bar_item* bar_item, uint32_t radius) { if (bar_item->blur_radius == radius) return false; bar_item->blur_radius = radius; for (int i = 0; i < bar_item->num_windows; i++) { if (!bar_item->windows[i]) continue; window_set_blur_radius(bar_item->windows[i], radius); } return true; } static bool bar_item_set_width(struct bar_item* bar_item, int width) { if (width < 0) { bool prev = bar_item->has_const_width; bar_item->has_const_width = false; return prev != bar_item->has_const_width; } if (bar_item->custom_width == width && bar_item->has_const_width) return false; bar_item->custom_width = width; bar_item->has_const_width = true; return true; } static void bar_item_set_event_port(struct bar_item* bar_item, char* bs_name) { mach_port_t port = mach_get_bs_port(bs_name); bar_item->event_port = port; } bool bar_item_set_name(struct bar_item* bar_item, char* name) { if (!name) return false; if (bar_item->name && strcmp(bar_item->name, name) == 0) { free(name); return true; } if (strlen(name) == 0) return false; if (name != bar_item->name && bar_item->name) free(bar_item->name); bar_item->name = name; env_vars_set(&bar_item->signal_args.env_vars, string_copy("NAME"), string_copy(name) ); return true; } bool bar_item_set_type(struct bar_item* bar_item, char* type) { bool success = true; if (string_equals(type, TYPE_SPACE)) { bar_item->type = BAR_COMPONENT_SPACE; } else if (string_equals(type, TYPE_ALIAS)) { bar_item->type = BAR_COMPONENT_ALIAS; } else if (string_equals(type, TYPE_GROUP)) { bar_item->type = BAR_COMPONENT_GROUP; } else if (string_equals(type, TYPE_GRAPH)) { bar_item->type = BAR_COMPONENT_GRAPH; } else if (string_equals(type, TYPE_SLIDER)) { bar_item->type = BAR_COMPONENT_SLIDER; } else { bar_item->type = BAR_ITEM; success = string_equals(type, TYPE_ITEM); } if (bar_item->type == BAR_COMPONENT_SPACE) { if (!bar_item->script) { bar_item_set_script(bar_item, string_copy("sketchybar -m --set $NAME icon.highlight=$SELECTED")); } bar_item->update_mask |= UPDATE_SPACE_CHANGE; bar_item->updates = false; bar_item->updates_only_when_shown = false; env_vars_set(&bar_item->signal_args.env_vars, string_copy("SELECTED"), string_copy("false") ); env_vars_set(&bar_item->signal_args.env_vars, string_copy("SID"), string_copy("0") ); env_vars_set(&bar_item->signal_args.env_vars, string_copy("DID"), string_copy("0") ); } else if (bar_item->type == BAR_COMPONENT_ALIAS) { bar_item->has_alias = true; } else if (bar_item->type == BAR_COMPONENT_GRAPH) { bar_item->has_graph = true; } else if (bar_item->type == BAR_COMPONENT_SLIDER) { bar_item->has_slider = true; } else if (bar_item->type == BAR_COMPONENT_GROUP) { bar_item->group = group_create(); group_init(bar_item->group); group_add_member(bar_item->group, bar_item); } return success; } bool bar_item_set_position(struct bar_item* bar_item, char* position) { if (!position || strlen(position) == 0) return false; switch (position[0]) { case POSITION_LEFT: break; case POSITION_CENTER_LEFT: break; case POSITION_CENTER: break; case POSITION_CENTER_RIGHT: break; case POSITION_RIGHT: break; case POSITION_POPUP: break; default: return false; } if (bar_item->parent != NULL){ popup_remove_item(&bar_item->parent->popup,bar_item); } bar_item->position = position[0]; if (position[0] != POSITION_POPUP) bar_item->align = position[0]; return true; } bool bar_item_set_media_cover(struct bar_item* bar_item, struct image* image) { bool linked = (bar_item->background.image.link == image ||bar_item->icon.background.image.link == image ||bar_item->label.background.image.link == image); if (linked) bar_item_needs_update(bar_item); return linked && bar_item_is_shown(bar_item); } static uint32_t bar_item_get_content_length(struct bar_item* bar_item) { int length = text_get_length(&bar_item->icon, false) + text_get_length(&bar_item->label, false) + (bar_item->has_graph ? graph_get_length(&bar_item->graph) : 0) + (bar_item->has_slider ? slider_get_length(&bar_item->slider) : 0) + (bar_item->has_alias ? alias_get_length(&bar_item->alias) : 0); return max(length, 0); } uint32_t bar_item_get_length(struct bar_item* bar_item, bool ignore_override) { uint32_t content_length = bar_item_get_content_length(bar_item); if (bar_item->background.enabled && bar_item->background.image.enabled) { CGSize image_size = image_get_size(&bar_item->background.image); if (image_size.width > content_length) content_length = image_size.width; } if (bar_item->has_const_width && (!ignore_override || bar_item->custom_width > content_length )) { return bar_item->custom_width; } return content_length; } uint32_t bar_item_get_height(struct bar_item* bar_item) { uint32_t label_height = text_get_height(&bar_item->label); uint32_t icon_height = text_get_height(&bar_item->icon); uint32_t alias_height = alias_get_height(&bar_item->alias); uint32_t text_height = max(label_height, icon_height); uint32_t item_height = max(text_height, alias_height); uint32_t background_height = 0; if (bar_item->background.enabled) { uint32_t image_height = bar_item->background.image.enabled ? image_get_size(&bar_item->background.image).height : 0; background_height = max(image_height, bar_item->background.bounds.size.height); } return max(item_height, background_height); } struct window* bar_item_get_window(struct bar_item* bar_item, uint32_t adid) { if (adid < 1 || !bar_item) return NULL; if (bar_item->num_windows < adid) { bar_item->windows = (struct window**) realloc(bar_item->windows, sizeof(struct window*)*adid); memset(bar_item->windows + bar_item->num_windows, 0, sizeof(struct window*) * (adid - bar_item->num_windows)); bar_item->num_windows = adid; } if (!bar_item->windows[adid - 1]) { bar_item->windows[adid - 1] = malloc(sizeof(struct window)); window_init(bar_item->windows[adid - 1]); window_create(bar_item->windows[adid - 1], (CGRect){{g_nirvana.x,g_nirvana.y}, {1, 1}}); if (!bar_item->shadow) window_disable_shadow(bar_item->windows[adid - 1]); window_set_blur_radius(bar_item->windows[adid - 1], bar_item->blur_radius); context_set_font_smoothing(bar_item->windows[adid - 1]->context, g_bar_manager.font_smoothing ); if (bar_item->parent) bar_item->parent->popup.needs_ordering = true; else g_bar_manager.needs_ordering = true; } return bar_item->windows[adid - 1]; } void bar_item_remove_window(struct bar_item* bar_item, uint32_t adid) { if (bar_item->num_windows >= adid && bar_item->windows[adid - 1]) { window_close(bar_item->windows[adid - 1]); free(bar_item->windows[adid - 1]); bar_item->windows[adid - 1] = NULL; } } CGPoint bar_item_calculate_shadow_offsets(struct bar_item* bar_item) { CGPoint offset; offset.x = (int)((bar_item->background.shadow.enabled ? max(-bar_item->background.shadow.offset.x, 0) : 0) + (bar_item->icon.shadow.enabled ? max(-bar_item->icon.shadow.offset.x, 0) : 0) + (bar_item->icon.background.shadow.enabled ? max(-bar_item->icon.background.shadow.offset.x, 0) : 0) + (bar_item->label.background.shadow.enabled ? max(-bar_item->label.background.shadow.offset.x, 0) : 0) + (bar_item->label.shadow.enabled ? max(-bar_item->label.shadow.offset.x, 0) : 0) + (bar_item->background.enabled ? max(-bar_item->background.x_offset, 0) : 0)); offset.y = (int)((bar_item->background.shadow.enabled ? max(bar_item->background.shadow.offset.x,0) : 0) + (bar_item->icon.shadow.enabled ? max(bar_item->icon.shadow.offset.x, 0) : 0) + (bar_item->icon.background.shadow.enabled ? max(bar_item->icon.background.shadow.offset.x, 0) : 0) + (bar_item->label.background.shadow.enabled ? max(bar_item->label.background.shadow.offset.x, 0) : 0) + (bar_item->label.shadow.enabled ? max(bar_item->label.shadow.offset.x, 0) : 0) + (bar_item->background.enabled ? max(bar_item->background.x_offset, 0) : 0)); return offset; } uint32_t bar_item_calculate_bounds(struct bar_item* bar_item, uint32_t bar_height, uint32_t x, uint32_t y) { uint32_t content_x = x; uint32_t content_y = y; uint32_t bar_item_length = bar_item_get_length(bar_item, false); uint32_t bar_item_content_length = bar_item_get_content_length(bar_item); if (bar_item_length > bar_item_content_length) { if (bar_item->align == POSITION_CENTER) content_x += (bar_item_length - bar_item_content_length) / 2; else if (bar_item->align == POSITION_RIGHT) content_x += bar_item_length - bar_item_content_length; } uint32_t icon_position = content_x; uint32_t label_position = icon_position + text_get_length(&bar_item->icon, false ); uint32_t sandwich_position = label_position; if (bar_item->has_graph) { label_position += graph_get_length(&bar_item->graph); } else if (bar_item->has_alias) { label_position += alias_get_length(&bar_item->alias); } else if (bar_item->has_slider) { label_position += slider_get_length(&bar_item->slider); } text_calculate_bounds(&bar_item->icon, icon_position, content_y + bar_item->y_offset); text_calculate_bounds(&bar_item->label, label_position, content_y + bar_item->y_offset); if (bar_item->has_alias) alias_calculate_bounds(&bar_item->alias, sandwich_position, content_y + bar_item->y_offset); if (bar_item->has_slider) slider_calculate_bounds(&bar_item->slider, sandwich_position, content_y + bar_item->y_offset); if (bar_item->has_graph) { uint32_t height = bar_item->background.enabled ? (bar_item->background.bounds.size.height - bar_item->background.border_width - 1) : (bar_height - (g_bar_manager.background.border_width + 1)); graph_calculate_bounds(&bar_item->graph, sandwich_position, content_y + bar_item->y_offset, height ); } if (bar_item->background.enabled) { uint32_t height = bar_item->background.overrides_height ? bar_item->background.bounds.size.height : (bar_height - (g_bar_manager.background.border_width + 1)); background_calculate_bounds(&bar_item->background, x, content_y + bar_item->y_offset, bar_item_length, height ); } return bar_item_length; } bool bar_item_clip_needs_update_for_bar(struct bar_item* bar_item, struct bar* bar) { bool needs_update = false; needs_update |= background_clip_needs_update(&bar_item->background, bar) || background_clip_needs_update(&bar_item->icon.background, bar) || background_clip_needs_update(&bar_item->label.background, bar); return needs_update; } bool bar_item_clips_bar(struct bar_item* bar_item) { return background_clips_bar(&bar_item->background) || background_clips_bar(&bar_item->icon.background) || background_clips_bar(&bar_item->label.background); } void bar_item_clip_bar(struct bar_item* bar_item, int offset, struct bar* bar) { background_clip_bar(&bar_item->background, offset, bar); background_clip_bar(&bar_item->icon.background, offset, bar); background_clip_bar(&bar_item->label.background, offset, bar); } void* draw_item_proc(void* context) { struct { struct window* window; struct bar_item bar_item; }* info = context; CGContextClearRect(info->window->context, info->window->frame); bar_item_draw(&info->bar_item, info->window->context); CGContextFlush(info->window->context); window_flush(info->window); free(context); return NULL; } void bar_item_draw(struct bar_item* bar_item, CGContextRef context) { background_draw(&bar_item->background, context); if (bar_item->type == BAR_COMPONENT_GROUP) return; text_draw(&bar_item->icon, context); text_draw(&bar_item->label, context); if (bar_item->has_alias) alias_draw(&bar_item->alias, context); if (bar_item->has_graph) graph_draw(&bar_item->graph, context); if (bar_item->has_slider) slider_draw(&bar_item->slider, context); } void bar_item_change_space(struct bar_item* bar_item, uint64_t dsid, uint32_t adid) { if (bar_item->num_windows >= adid && bar_item->windows[adid - 1]) { window_send_to_space(bar_item->windows[adid - 1], dsid); } popup_change_space(&bar_item->popup, dsid, adid); } static void bar_item_clear_pointers(struct bar_item* bar_item) { bar_item->name = NULL; bar_item->script = NULL; bar_item->click_script = NULL; bar_item->group = NULL; bar_item->signal_args.env_vars.vars = NULL; bar_item->signal_args.env_vars.count = 0; bar_item->windows = NULL; bar_item->num_windows = 0; text_clear_pointers(&bar_item->icon); text_clear_pointers(&bar_item->label); background_clear_pointers(&bar_item->background); slider_clear_pointers(&bar_item->slider); popup_clear_pointers(&bar_item->popup); bar_item->popup.host = bar_item; } void bar_item_inherit_from_item(struct bar_item* bar_item, struct bar_item* ancestor) { text_destroy(&bar_item->icon); text_destroy(&bar_item->label); text_destroy(&bar_item->slider.knob); char* name = bar_item->name; char* script = bar_item->script; char* click_script = bar_item->click_script; memcpy(bar_item, ancestor, sizeof(struct bar_item)); bar_item_clear_pointers(bar_item); bar_item->name = name; bar_item->script = script; bar_item->click_script = click_script; text_copy(&bar_item->icon, &ancestor->icon); text_copy(&bar_item->label, &ancestor->label); text_copy(&bar_item->slider.knob, &ancestor->slider.knob); if (ancestor->script) bar_item_set_script(bar_item, string_copy(ancestor->script)); if (ancestor->click_script) bar_item_set_click_script(bar_item, string_copy(ancestor->click_script)); image_copy(&bar_item->background.image, ancestor->background.image.image_ref); image_copy(&bar_item->icon.background.image, ancestor->icon.background.image.image_ref); image_copy(&bar_item->label.background.image, ancestor->label.background.image.image_ref); if (bar_item->type == BAR_COMPONENT_SPACE) { env_vars_set(&bar_item->signal_args.env_vars, string_copy("SELECTED"), string_copy("false") ); env_vars_set(&bar_item->signal_args.env_vars, string_copy("SID"), string_copy(env_vars_get_value_for_key(&ancestor->signal_args.env_vars, "DID") )); env_vars_set(&bar_item->signal_args.env_vars, string_copy("DID"), string_copy(env_vars_get_value_for_key(&ancestor->signal_args.env_vars, "DID") )); } } void bar_item_destroy(struct bar_item* bar_item, bool free_memory) { if (bar_item->name) free(bar_item->name); if (bar_item->script) free(bar_item->script); if (bar_item->click_script) free(bar_item->click_script); text_destroy(&bar_item->icon); text_destroy(&bar_item->label); graph_destroy(&bar_item->graph); alias_destroy(&bar_item->alias); slider_destroy(&bar_item->slider); if (bar_item->group && bar_item->type == BAR_COMPONENT_GROUP) group_destroy(bar_item->group); else if (bar_item->group) group_remove_member(bar_item->group, bar_item); env_vars_destroy(&bar_item->signal_args.env_vars); popup_destroy(&bar_item->popup); background_destroy(&bar_item->background); for (int j = 1; j <= bar_item->num_windows; j++) { bar_item_remove_window(bar_item, j); } if (bar_item->windows) free(bar_item->windows); if (free_memory) free(bar_item); } void bar_item_serialize(struct bar_item* bar_item, FILE* rsp) { char type[32] = { 0 }; switch (bar_item->type) { case BAR_ITEM: snprintf(type, 32, "item"); break; case BAR_COMPONENT_ALIAS: snprintf(type, 32, "alias"); break; case BAR_COMPONENT_GROUP: snprintf(type, 32, "bracket"); break; case BAR_COMPONENT_SLIDER: snprintf(type, 32, "slider"); break; case BAR_COMPONENT_GRAPH: snprintf(type, 32, "graph"); break; case BAR_COMPONENT_SPACE: snprintf(type, 32, "space"); break; default: snprintf(type, 32, "invalid"); break; } char position[32] = { 0 }; switch (bar_item->position) { case POSITION_LEFT: snprintf(position, 32, "left"); break; case POSITION_RIGHT: snprintf(position, 32, "right"); break; case POSITION_CENTER: snprintf(position, 32, "center"); break; case POSITION_CENTER_LEFT: snprintf(position, 32, "q"); break; case POSITION_CENTER_RIGHT: snprintf(position, 32, "e"); break; case POSITION_POPUP: snprintf(position, 32, "popup"); break; default: snprintf(position, 32, "invalid"); break; } fprintf(rsp, "{\n" "\t\"name\": \"%s\",\n" "\t\"type\": \"%s\",\n" "\t\"geometry\": {\n" "\t\t\"drawing\": \"%s\",\n" "\t\t\"position\": \"%s\",\n" "\t\t\"associated_space_mask\": %u,\n" "\t\t\"associated_display_mask\": %u,\n" "\t\t\"ignore_association\": \"%s\",\n" "\t\t\"y_offset\": %d,\n" "\t\t\"padding_left\": %d,\n" "\t\t\"padding_right\": %d,\n" "\t\t\"scroll_texts\": \"%s\",\n" "\t\t\"width\": %d,\n" "\t\t\"background\": {\n", bar_item->name, type, format_bool(bar_item->drawing), position, bar_item->associated_space, bar_item->associated_display, format_bool(bar_item->ignore_association), bar_item->y_offset, bar_item->background.padding_left, bar_item->background.padding_right, format_bool(bar_item->scroll_texts), bar_item->has_const_width ? bar_item->custom_width : -1); background_serialize(&bar_item->background, "\t\t\t", rsp, true); fprintf(rsp, "\n\t\t}\n\t},\n"); fprintf(rsp, "\t\"icon\": {\n"); text_serialize(&bar_item->icon, "\t\t", rsp); fprintf(rsp, "\n\t},\n"); fprintf(rsp, "\t\"label\": {\n"); text_serialize(&bar_item->label, "\t\t", rsp); fprintf(rsp, "\n\t},\n"); char* escaped_script = escape_string(bar_item->script); char* escaped_click_script = escape_string(bar_item->click_script); fprintf(rsp, "\t\"scripting\": {\n" "\t\t\"script\": \"%s\",\n" "\t\t\"click_script\": \"%s\",\n" "\t\t\"update_freq\": %u,\n" "\t\t\"update_mask\": %llu,\n" "\t\t\"updates\": \"%s\"\n\t},\n", escaped_script, escaped_click_script, bar_item->update_frequency, bar_item->update_mask, bar_item->updates_only_when_shown ? "when_shown" : format_bool(bar_item->updates) ); if (escaped_script) free(escaped_script); if (escaped_click_script) free(escaped_click_script); fprintf(rsp, "\t\"bounding_rects\": {\n"); int counter = 0; for (int i = 0; i < bar_item->num_windows; i++) { if (!bar_item->windows[i]) continue; if (counter++ > 0) fprintf(rsp, ",\n"); fprintf(rsp, "\t\t\"display-%d\": {\n" "\t\t\t\"origin\": [ %f, %f ],\n" "\t\t\t\"size\": [ %f, %f ]\n\t\t}", i + 1, bar_item->windows[i]->origin.x, bar_item->windows[i]->origin.y, bar_item->windows[i]->frame.size.width, bar_item->windows[i]->frame.size.height); } fprintf(rsp, "\n\t}"); if (bar_item->popup.num_items > 0) { fprintf(rsp, ",\n\t\"popup\": {\n"); popup_serialize(&bar_item->popup, "\t\t", rsp); fprintf(rsp, "\n\t}"); } if (bar_item->type == BAR_COMPONENT_GROUP && bar_item->group) { fprintf(rsp, ",\n\t\"bracket\": [\n"); group_serialize(bar_item->group, "\t\t", rsp); fprintf(rsp, "\n\t]"); } else if (bar_item->type == BAR_COMPONENT_GRAPH) { fprintf(rsp, ",\n\t\"graph\": {\n"); graph_serialize(&bar_item->graph, "\t\t", rsp); fprintf(rsp, "\n\t}"); } else if (bar_item->type == BAR_COMPONENT_SLIDER) { fprintf(rsp, ",\n\t\"slider\": {\n"); slider_serialize(&bar_item->slider, "\t\t", rsp); fprintf(rsp, "\n\t}"); } fprintf(rsp, "\n}\n"); } void bar_item_parse_set_message(struct bar_item* bar_item, char* message, FILE* rsp) { bool needs_refresh = false; struct token property = get_token(&message); struct key_value_pair key_value_pair = get_key_value_pair(property.text,'.'); if (key_value_pair.key && key_value_pair.value) { struct token subdom = { key_value_pair.key, strlen(key_value_pair.key) }; struct token entry = { key_value_pair.value, strlen(key_value_pair.value)}; if (token_equals(subdom, SUB_DOMAIN_ICON)) { needs_refresh = text_parse_sub_domain(&bar_item->icon, rsp, entry, message ); } else if (token_equals(subdom, SUB_DOMAIN_LABEL)) { needs_refresh = text_parse_sub_domain(&bar_item->label, rsp, entry, message ); } else if (token_equals(subdom, SUB_DOMAIN_BACKGROUND)) { needs_refresh = background_parse_sub_domain(&bar_item->background, rsp, entry, message ); } else if (token_equals(subdom, SUB_DOMAIN_POPUP)) { needs_refresh = popup_parse_sub_domain(&bar_item->popup, rsp, entry, message ); } else if (token_equals(subdom, SUB_DOMAIN_GRAPH)) { if (bar_item->has_graph || bar_item == &g_bar_manager.default_item) { needs_refresh = graph_parse_sub_domain(&bar_item->graph, rsp, entry, message ); } else { respond(rsp, "[!] Item (%s): Trying to set a graph property on a non-graph item\n", bar_item->name); } } else if (token_equals(subdom, SUB_DOMAIN_ALIAS)) { if (bar_item->has_alias || bar_item == &g_bar_manager.default_item) { needs_refresh = alias_parse_sub_domain(&bar_item->alias, rsp, entry, message ); } else { respond(rsp, "[!] Item (%s): Trying to set an alias property on a non-alias item\n", bar_item->name); } } else if (token_equals(subdom, SUB_DOMAIN_SLIDER)) { if (bar_item->has_slider || bar_item == &g_bar_manager.default_item) { needs_refresh = slider_parse_sub_domain(&bar_item->slider, rsp, entry, message ); } else { respond(rsp, "[!] Item (%s): Trying to set a slider property on a non-slider item\n", bar_item->name); } } else { respond(rsp, "[!] Item (%s): Invalid subdomain '%s'\n", bar_item->name, subdom.text); } } else if (token_equals(property, PROPERTY_ICON)) { struct token dummy = { PROPERTY_STRING, strlen(PROPERTY_STRING)}; needs_refresh = text_parse_sub_domain(&bar_item->icon, rsp, dummy, message ); } else if (token_equals(property, PROPERTY_LABEL)) { struct token dummy = { PROPERTY_STRING, strlen(PROPERTY_STRING)}; needs_refresh = text_parse_sub_domain(&bar_item->label, rsp, dummy, message ); } else if (token_equals(property, PROPERTY_UPDATES)) { struct token token = get_token(&message); if (token_equals(token, ARGUMENT_UPDATES_WHEN_SHOWN)) { bar_item->updates = true; bar_item->updates_only_when_shown = true; } else { bar_item->updates = evaluate_boolean_state(token, bar_item->updates); bar_item->updates_only_when_shown = false; } } else if (token_equals(property, PROPERTY_DRAWING)) { needs_refresh = bar_item_set_drawing(bar_item, evaluate_boolean_state(get_token(&message), bar_item->drawing )); } else if (token_equals(property, PROPERTY_SCROLL_TEXTS)) { bar_item->scroll_texts = evaluate_boolean_state(get_token(&message), bar_item->scroll_texts); } else if (token_equals(property, PROPERTY_WIDTH)) { struct token token = get_token(&message); if (token_equals(token, ARGUMENT_DYNAMIC)) { ANIMATE(bar_item_set_width, bar_item, bar_item->custom_width, bar_item_get_length(bar_item, true) + bar_item->background.padding_left + bar_item->background.padding_right); struct animation* animation = animation_create(); animation_setup(animation, bar_item, (bool (*)(void*, int))&bar_item_set_width, bar_item->custom_width, -1, 0, INTERP_FUNCTION_LINEAR ); animator_add(&g_bar_manager.animator, animation); } else { ANIMATE(bar_item_set_width, bar_item, bar_item_get_length(bar_item, false) + (bar_item->has_const_width ? 0 : (bar_item->background.padding_left + bar_item->background.padding_right)), token_to_int(token) ); } } else if (token_equals(property, PROPERTY_SCRIPT)) { bar_item_set_script(bar_item, token_to_string(get_token(&message))); } else if (token_equals(property, PROPERTY_CLICK_SCRIPT)) { bar_item_set_click_script(bar_item, token_to_string(get_token(&message))); } else if (token_equals(property, PROPERTY_UPDATE_FREQ)) { bar_item->update_frequency = token_to_uint32t(get_token(&message)); } else if (token_equals(property, PROPERTY_POSITION)) { struct token position = get_token(&message); bar_item_set_position(bar_item, position.text); struct key_value_pair key_value_pair = get_key_value_pair(position.text, '.' ); if (key_value_pair.key && key_value_pair.value) { if (key_value_pair.key[0] == POSITION_POPUP) { int item_index_for_name = bar_manager_get_item_index_for_name(&g_bar_manager, key_value_pair.value); if (item_index_for_name < 0) { respond(rsp, "[!] Item Position (%s): Item '%s' is not a valid popup host\n", bar_item->name, key_value_pair.value); return; } struct bar_item* target_item = g_bar_manager.bar_items[item_index_for_name]; popup_add_item(&target_item->popup, bar_item); } else { bar_item->parent = NULL; } } needs_refresh = true; } else if (token_equals(property, PROPERTY_ALIGN)) { struct token position = get_token(&message); if (bar_item->align != position.text[0]) { bar_item->align = position.text[0]; needs_refresh = true; } } else if (token_equals(property, PROPERTY_ASSOCIATED_SPACE) || token_equals(property, PROPERTY_SPACE) ) { struct token token = get_token(&message); uint32_t prev = bar_item->associated_space; bar_item->associated_space = 0; uint32_t count; char** list = token_split(token, ',', &count); if (list && count > 0) { for (int i = 0; i < count; i++) { bar_item_append_associated_space(bar_item, 1 << strtoul(list[i], NULL, 0 )); } free(list); } needs_refresh = (prev != bar_item->associated_space); } else if (token_equals(property, PROPERTY_ASSOCIATED_DISPLAY) || token_equals(property, PROPERTY_DISPLAY) ) { struct token token = get_token(&message); uint32_t prev = bar_item->associated_display; bar_item->associated_display = 0; bar_item->associated_to_active_display = false; uint32_t count; char** list = token_split(token, ',', &count); if (list && count > 0) { for (int i = 0; i < count; i++) { if (strcmp(list[i], "active") == 0) { bar_item->associated_to_active_display = true; } else { bar_item_append_associated_display(bar_item, 1 << strtoul(list[i], NULL, 0 )); } } free(list); } needs_refresh = (prev != bar_item->associated_display); } else if (token_equals(property, PROPERTY_YOFFSET)) { struct token token = get_token(&message); ANIMATE(bar_item_set_yoffset, bar_item, bar_item->y_offset, token_to_int(token) ); } else if (token_equals(property, PROPERTY_PADDING_LEFT)) { struct token token = get_token(&message); ANIMATE(background_set_padding_left, &bar_item->background, bar_item->background.padding_left, token_to_int(token) ); } else if (token_equals(property, PROPERTY_PADDING_RIGHT)) { struct token token = get_token(&message); ANIMATE(background_set_padding_right, &bar_item->background, bar_item->background.padding_right, token_to_int(token) ); } else if (token_equals(property, PROPERTY_BLUR_RADIUS)) { struct token token = get_token(&message); ANIMATE(bar_item_set_blur_radius, bar_item, bar_item->blur_radius, token_to_int(token) ); } else if (token_equals(property, PROPERTY_SHADOW)) { bool prev = bar_item->shadow; bar_item->shadow = evaluate_boolean_state(get_token(&message), bar_item->shadow ); if (prev != bar_item->shadow) { for (int i = 1; i <= bar_item->num_windows; i++) { bar_item_remove_window(bar_item, i); } needs_refresh = true; } } else if (token_equals(property, PROPERTY_IGNORE_ASSOCIATION)) { bar_item->ignore_association = evaluate_boolean_state(get_token(&message), bar_item->ignore_association); needs_refresh = true; } else if (token_equals(property, COMMAND_DEFAULT_RESET)) { bar_item_init(&g_bar_manager.default_item, NULL); } else if (token_equals(property, PROPERTY_EVENT_PORT)) { struct token token = get_token(&message); if (token.text && token.length > 0) bar_item_set_event_port(bar_item, token.text); } else { respond(rsp, "[!] Item (%s): Invalid property '%s' \n", bar_item->name, property.text); } if (needs_refresh) bar_item_needs_update(bar_item); } void bar_item_parse_subscribe_message(struct bar_item* bar_item, char* message, FILE* rsp) { struct token event = get_token(&message); while (event.text && event.length > 0) { uint64_t event_flag = custom_events_get_flag_for_name(&g_bar_manager.custom_events, event.text ); if (event_flag & UPDATE_VOLUME_CHANGE) { begin_receiving_volume_events(); } if (event_flag & UPDATE_BRIGHTNESS_CHANGE) { begin_receiving_brightness_events(); } if (event_flag & UPDATE_MEDIA_CHANGE) { begin_receiving_media_events(); } if (event_flag & UPDATE_SPACE_WINDOWS_CHANGE) { begin_receiving_space_window_events(); } bar_item->update_mask |= event_flag; if (!event_flag) { respond(rsp, "[?] Event: '%s' not found\n", event.text); } event = get_token(&message); } } ================================================ FILE: src/bar_item.h ================================================ #pragma once #include "alias.h" #include "custom_events.h" #include "graph.h" #include "group.h" #include "misc/env_vars.h" #include "misc/helpers.h" #include "popup.h" #include "text.h" #include "slider.h" #define BAR_ITEM 'i' #define BAR_COMPONENT_GRAPH 'g' #define BAR_COMPONENT_SPACE 's' #define BAR_COMPONENT_ALIAS 'a' #define BAR_COMPONENT_GROUP 'b' #define BAR_COMPONENT_SLIDER 't' #define BAR_PLUGIN 'p' struct bar_item { char type; char* name; // Update Modifiers uint32_t counter; bool needs_update; bool updates; bool updates_only_when_shown; bool lazy; bool selected; bool mouse_over; bool ignore_association; bool overrides_association; // Drawing Modifiers bool drawing; bool shadow; bool has_const_width; bool scroll_texts; char align; uint32_t custom_width; uint32_t blur_radius; // These are 32bit masks where the ith bit represents the ith screen/display/bar association bool associated_to_active_display; uint32_t associated_bar; uint32_t associated_display; uint32_t associated_space; uint32_t update_frequency; char* script; char* click_script; struct signal_args signal_args; // The position in the bar: l,r,c char position; int y_offset; // Background struct background background; // Icon properties struct text icon; // Label properties struct text label; // Graph Data bool has_graph; struct graph graph; // Alias Data bool has_alias; struct alias alias; // Slider Properties bool has_slider; struct slider slider; // Group Properties struct group* group; // Update Events uint64_t update_mask; // Windows uint32_t num_windows; struct window** windows; // Popup struct popup popup; struct bar_item* parent; // Mach mach_port_t event_port; }; struct bar_item* bar_item_create(); void bar_item_inherit_from_item(struct bar_item* bar_item, struct bar_item* ancestor); void bar_item_init(struct bar_item* bar_item, struct bar_item* default_item); void bar_item_serialize(struct bar_item* bar_item, FILE* rsp); void bar_item_destroy(struct bar_item* bar_item, bool free_memory); bool bar_item_is_shown(struct bar_item* bar_item); void bar_item_needs_update(struct bar_item* bar_item); bool bar_item_update(struct bar_item* bar_item, char* sender, bool forced, struct env_vars* env_vars); void bar_item_on_click(struct bar_item* bar_item, uint32_t type, uint32_t mouse_button_code, uint32_t modifier, CGPoint point); void bar_item_on_scroll(struct bar_item* bar_item, int scroll_delta, uint32_t modifier); void bar_item_on_drag(struct bar_item* bar_item, CGPoint point); void bar_item_mouse_entered(struct bar_item* bar_item); void bar_item_mouse_exited(struct bar_item* bar_item); void bar_item_cancel_drag(struct bar_item* bar_item); void bar_item_append_associated_space(struct bar_item* bar_item, uint32_t bit); void bar_item_append_associated_display(struct bar_item* bar_item, uint32_t bit); void bar_item_append_associated_bar(struct bar_item* bar_item, uint32_t adid); void bar_item_remove_associated_bar(struct bar_item* bar_item, uint32_t adid); void bar_item_reset_associated_bar(struct bar_item* bar_item); bool bar_item_set_name(struct bar_item* bar_item, char* name); bool bar_item_set_type(struct bar_item* bar_item, char* type); bool bar_item_set_position(struct bar_item* bar_item, char* position); bool bar_item_set_media_cover(struct bar_item* bar_item, struct image* image); uint32_t bar_item_get_length(struct bar_item* bar_item, bool ignore_override); uint32_t bar_item_get_height(struct bar_item* bar_item); struct window* bar_item_get_window(struct bar_item* bar_item, uint32_t adid); void bar_item_remove_window(struct bar_item* bar_item, uint32_t adid); CGPoint bar_item_calculate_shadow_offsets(struct bar_item* bar_item); uint32_t bar_item_calculate_bounds(struct bar_item* bar_item, uint32_t bar_height, uint32_t x, uint32_t y); void* draw_item_proc(void* context); void bar_item_draw(struct bar_item* bar_item, CGContextRef context); bool bar_item_clip_needs_update_for_bar(struct bar_item* bar_item, struct bar* bar); void bar_item_clip_bar(struct bar_item* bar_item, int offset, struct bar* bar); bool bar_item_clips_bar(struct bar_item* bar_item); void bar_item_change_space(struct bar_item* bar_item, uint64_t dsid, uint32_t adid); void bar_item_parse_set_message(struct bar_item* bar_item, char* message, FILE* rsp); void bar_item_parse_subscribe_message(struct bar_item* bar_item, char* message, FILE* rsp); ================================================ FILE: src/bar_manager.c ================================================ #include "bar_manager.h" #include "bar.h" #include "bar_item.h" #include "event.h" #include "misc/env_vars.h" #include "misc/helpers.h" #include "wifi.h" #include "volume.h" #include "power.h" #include "mouse.h" #include "media.h" #include "app_windows.h" extern void forced_front_app_event(); static CLOCK_CALLBACK(clock_handler) { struct event event = { NULL, SHELL_REFRESH }; event_post(&event); } void bar_manager_init(struct bar_manager* bar_manager) { bar_manager->font_smoothing = false; bar_manager->any_bar_hidden = false; bar_manager->needs_ordering = false; bar_manager->bar_needs_update = false; bar_manager->bars = NULL; bar_manager->bar_count = 0; bar_manager->bar_items = NULL; bar_manager->bar_item_count = 0; bar_manager->displays = DISPLAY_ALL_PATTERN; bar_manager->position = POSITION_TOP; bar_manager->shadow = false; bar_manager->blur_radius = 0; bar_manager->margin = 0; bar_manager->frozen = false; bar_manager->sleeps = false; bar_manager->window_level = kCGBackstopMenuLevel; bar_manager->topmost = false; bar_manager->notch_width = 200; bar_manager->notch_offset = 0; bar_manager->notch_display_height = 0; bar_manager->active_adid = display_active_display_adid(); bar_manager->might_need_clipping = false; bar_manager->sticky = true; bar_manager->show_in_fullscreen = false; image_init(&bar_manager->current_artwork); background_init(&bar_manager->background); bar_manager->background.bounds.size.height = 25; bar_manager->background.overrides_height = true; bar_manager->background.padding_left = 20; bar_manager->background.padding_right = 20; color_set_hex(&bar_manager->background.border_color, 0xffff0000); color_set_hex(&bar_manager->background.color, 0x44000000); bar_item_init(&bar_manager->default_item, NULL); bar_item_set_name(&bar_manager->default_item, string_copy("defaults")); custom_events_init(&bar_manager->custom_events); animator_init(&bar_manager->animator); int shell_refresh_frequency = 1; bar_manager->clock = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + shell_refresh_frequency, shell_refresh_frequency, 0, 0, clock_handler, NULL ); CFRunLoopAddTimer(CFRunLoopGetMain(), bar_manager->clock, kCFRunLoopCommonModes); } void bar_manager_sort(struct bar_manager* bar_manager, struct bar_item** ordering, uint32_t count) { int index = 0; for (int i = 0; i < bar_manager->bar_item_count; i++) { for (int j = 0; j < count; j++) { if (bar_manager->bar_items[i] == ordering[j] && bar_manager->bar_items[i] != ordering[index]) { bar_manager->bar_items[i] = ordering[index]; index++; bar_item_needs_update(bar_manager->bar_items[i]); break; } else if (bar_manager->bar_items[i] == ordering[j] && bar_manager->bar_items[i] == ordering[index]) { index++; break; } } } bar_manager->needs_ordering = true; } int bar_manager_get_item_index_for_name(struct bar_manager* bar_manager, char* name) { for (int i = 0; i < bar_manager->bar_item_count; i++) { if (strcmp(bar_manager->bar_items[i]->name, name) == 0) { return i; } } return -1; } int bar_manager_get_item_index_by_address(struct bar_manager* bar_manager, struct bar_item* bar_item) { for (int i = 0; i < bar_manager->bar_item_count; i++) { if (bar_manager->bar_items[i] == bar_item) { return i; } } return -1; } void bar_manager_move_item(struct bar_manager* bar_manager, struct bar_item* item, struct bar_item* reference, bool before) { if (bar_manager->bar_item_count <= 0) return; struct bar_item* tmp[bar_manager->bar_item_count]; int count = 0; for (int i = 0; i < bar_manager->bar_item_count; i++) { if (bar_manager->bar_items[i] == item) continue; if (bar_manager->bar_items[i] == reference && before) { tmp[count++] = item; tmp[count++] = bar_manager->bar_items[i]; continue; } else if (bar_manager->bar_items[i] == reference && !before) { tmp[count++] = bar_manager->bar_items[i]; tmp[count++] = item; continue; } tmp[count++] = bar_manager->bar_items[i]; } bar_manager->bar_items = realloc( bar_manager->bar_items, sizeof(struct bar_item*)*bar_manager->bar_item_count); memcpy(bar_manager->bar_items, tmp, sizeof(struct bar_item*)*bar_manager->bar_item_count); bar_manager->needs_ordering = true; } void bar_manager_remove_item(struct bar_manager* bar_manager, struct bar_item* bar_item) { if (bar_manager->bar_item_count <= 0 || !bar_item || bar_manager_get_item_index_by_address(bar_manager, bar_item) < 0) { return; } if (bar_item->position == POSITION_POPUP) { for (int i = 0; i < bar_manager->bar_item_count; i++) { popup_remove_item(&bar_manager->bar_items[i]->popup, bar_item); } } if (bar_manager->bar_item_count == 1) { free(bar_manager->bar_items); bar_manager->bar_items = NULL; bar_manager->bar_item_count = 0; } else { struct bar_item* tmp[bar_manager->bar_item_count - 1]; int count = 0; for (int i = 0; i < bar_manager->bar_item_count; i++) { if (bar_manager->bar_items[i] == bar_item) continue; tmp[count++] = bar_manager->bar_items[i]; } bar_manager->bar_item_count--; bar_manager->bar_items = realloc( bar_manager->bar_items, sizeof(struct bar_item*)*bar_manager->bar_item_count); memcpy(bar_manager->bar_items, tmp, sizeof(struct bar_item*)*bar_manager->bar_item_count); } bar_item_destroy(bar_item, true); } bool bar_manager_set_margin(struct bar_manager* bar_manager, int margin) { if (bar_manager->margin == margin) return false; bar_manager->margin = margin; bar_manager->bar_needs_resize = true; return true; } bool bar_manager_set_y_offset(struct bar_manager* bar_manager, int y_offset) { if (bar_manager->background.y_offset == y_offset) return false; bar_manager->background.y_offset = y_offset; bar_manager->bar_needs_resize = true; return true; } bool bar_manager_set_bar_height(struct bar_manager* bar_manager, int height) { bar_manager->bar_needs_resize |= background_set_height(&bar_manager->background, height); return bar_manager->bar_needs_resize; } bool bar_manager_set_background_blur(struct bar_manager* bar_manager, uint32_t radius) { if (bar_manager->blur_radius == radius) return false; bar_manager->blur_radius = radius; for (int i = 0; i < bar_manager->bar_count; i++) { window_set_blur_radius(&bar_manager->bars[i]->window, radius); } return false; } bool bar_manager_set_position(struct bar_manager* bar_manager, char pos) { if (bar_manager->position == pos) return false; bar_manager->position = pos; bar_manager->bar_needs_resize = true; return true; } bool bar_manager_set_displays(struct bar_manager* bar_manager, uint32_t displays) { if (bar_manager->displays == displays) return false; bar_manager->displays = displays; bar_manager_reset(bar_manager); return true; } bool bar_manager_set_shadow(struct bar_manager* bar_manager, bool shadow) { if (bar_manager->shadow == shadow) return false; bar_manager->shadow = shadow; bar_manager_reset(bar_manager); return true; } bool bar_manager_set_notch_width(struct bar_manager* bar_manager, uint32_t width) { if (bar_manager->notch_width == width) return false; bar_manager->notch_width = width; return true; } bool bar_manager_set_notch_offset(struct bar_manager* bar_manager, uint32_t offset) { if (bar_manager->notch_offset == offset) return false; bar_manager->notch_offset = offset; bar_manager->bar_needs_resize = true; return true; } bool bar_manager_set_notch_display_height(struct bar_manager* bar_manager, uint32_t offset) { if (bar_manager->notch_display_height == offset) return false; bar_manager->notch_display_height = offset; bar_manager->bar_needs_resize = true; return true; } bool bar_manager_set_font_smoothing(struct bar_manager* bar_manager, bool smoothing) { if (bar_manager->font_smoothing == smoothing) return false; bar_manager->font_smoothing = smoothing; for (int i = 0; i < bar_manager->bar_count; i++) context_set_font_smoothing(bar_manager->bars[i]->window.context, smoothing); return true; } bool bar_manager_set_show_in_fullscreen(struct bar_manager* bar_manager, bool show_in_fullscreen) { if (bar_manager->show_in_fullscreen == show_in_fullscreen) return false; bar_manager->show_in_fullscreen = show_in_fullscreen; return true; } bool bar_manager_set_hidden(struct bar_manager *bar_manager, uint32_t adid, bool hidden) { bar_manager->any_bar_hidden = false; if (adid > 0) { bar_set_hidden(bar_manager->bars[adid - 1], hidden); bar_manager->any_bar_hidden |= hidden; } else { for (int i = 0; i < bar_manager->bar_count; i++) { bar_set_hidden(bar_manager->bars[i], hidden); bar_manager->any_bar_hidden |= hidden; } } if (hidden) { for (int i = 0; i < bar_manager->bar_item_count; i++) { popup_set_drawing(&bar_manager->bar_items[i]->popup, false); } } bar_manager->bar_needs_update = true; return true; } bool bar_manager_set_topmost(struct bar_manager *bar_manager, char level, bool topmost) { if (topmost) { if (level == TOPMOST_LEVEL_WINDOW) { bar_manager->window_level = kCGFloatingWindowLevel; } else if (level == TOPMOST_LEVEL_ALL) { bar_manager->window_level = kCGStatusWindowLevel; } } else { bar_manager->window_level = kCGBackstopMenuLevel; } bar_manager_reset(bar_manager); bar_manager->topmost = topmost; return true; } bool bar_manager_set_sticky(struct bar_manager *bar_manager, bool sticky) { if (sticky == bar_manager->sticky) return false; bar_manager->sticky = sticky; bar_manager_reset(bar_manager); return true; } void bar_manager_freeze(struct bar_manager *bar_manager) { bar_manager->frozen = true; } void bar_manager_unfreeze(struct bar_manager *bar_manager) { bar_manager->frozen = false; } uint32_t bar_manager_length_for_bar_side(struct bar_manager* bar_manager, struct bar* bar, char side) { uint32_t total_length = 0; for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; if (bar_item->position == side && bar_item->type != BAR_COMPONENT_GROUP && bar_draws_item(bar, bar_item) ) { int item_length = (bar_manager->position == POSITION_LEFT || bar_manager->position == POSITION_RIGHT) ? bar_item_get_height(bar_item) : bar_item_get_length(bar_item, false); total_length += item_length + (bar_item->has_const_width ? 0 : bar_item->background.padding_left + bar_item->background.padding_right); } } return total_length; } bool bar_manager_bar_needs_redraw(struct bar_manager* bar_manager, struct bar* bar) { if (bar_manager->bar_needs_update) return true; uint32_t bar_mask = 1 << bar->adid; for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; bool draws_item = bar_draws_item(bar, bar_item); bool regular_update = bar_item->needs_update && draws_item; if (regular_update) return true; bool disabled_item_drawn_on_bar = !bar_item->drawing && (bar_item->associated_bar != 0); if (disabled_item_drawn_on_bar) return true; if (bar_item->ignore_association) continue; bool not_drawn_on_associated_display = (draws_item && (bar_item->associated_display > 0) && (bar_item->associated_display & bar_mask) && !(((bar_item->associated_bar << 1) & bar_mask))) || (draws_item && bar_item->associated_to_active_display && bar_manager->active_adid == bar->adid && !((bar_item->associated_bar << 1) & bar_mask)); if (not_drawn_on_associated_display) return true; bool drawn_on_non_associated_display = (!bar_item->associated_to_active_display && (bar_item->associated_display > 0) && !(bar_item->associated_display & bar_mask) && (((bar_item->associated_bar << 1) & bar_mask))) || (bar_item->drawing && bar_item->associated_to_active_display && (((bar_item->associated_bar << 1) & bar_mask)) && (bar->adid != bar_manager->active_adid)); if (drawn_on_non_associated_display) return true; if (bar_item->type == BAR_COMPONENT_SPACE) continue; bool drawn_on_non_associated_space = bar_item->associated_space > 0 && !(bar_item->associated_space & (1 << bar->sid)) && ((bar_item->associated_bar << 1) & bar_mask); if (drawn_on_non_associated_space) return true; bool not_drawn_on_associated_space = draws_item && bar_item->associated_space > 0 && (bar_item->associated_space & (1 << bar->sid)) && !((bar_item->associated_bar << 1) & bar_mask); if (not_drawn_on_associated_space) return true; } return false; } void bar_manager_clear_needs_update(struct bar_manager* bar_manager) { for (int i = 0; i < bar_manager->bar_item_count; i++) bar_manager->bar_items[i]->needs_update = false; bar_manager->needs_ordering = false; bar_manager->bar_needs_update = false; } void bar_manager_reset_bar_association(struct bar_manager* bar_manager) { for (int i = 0; i < bar_manager->bar_item_count; i++) bar_item_reset_associated_bar(bar_manager->bar_items[i]); } void bar_manager_refresh(struct bar_manager* bar_manager, bool forced, bool threaded) { if (bar_manager->frozen) return; if (forced) { bar_manager_reset_bar_association(bar_manager); for (int j = 0; j < bar_manager->bar_item_count; j++) { bar_item_needs_update(bar_manager->bar_items[j]); } } if (forced || bar_manager->bar_needs_resize) bar_manager_resize(bar_manager); for (int i = 0; i < bar_manager->bar_count; ++i) { if (forced || bar_manager_bar_needs_redraw(bar_manager, bar_manager->bars[i])) { bar_calculate_bounds(bar_manager->bars[i]); bar_draw(bar_manager->bars[i], false, threaded); if (bar_manager->needs_ordering) { bar_order_item_windows(bar_manager->bars[i]); } } } bar_manager_clear_needs_update(bar_manager); if (threaded) join_render_threads(); } void bar_manager_resize(struct bar_manager* bar_manager) { for (int i = 0; i < bar_manager->bar_count; ++i) bar_resize(bar_manager->bars[i]); bar_manager->bar_needs_resize = false; } struct bar_item* bar_manager_create_item(struct bar_manager* bar_manager) { bar_manager->bar_items = (struct bar_item**) realloc( bar_manager->bar_items, sizeof(struct bar_item*) * (bar_manager->bar_item_count + 1)); bar_manager->bar_item_count += 1; struct bar_item* bar_item = bar_item_create(); bar_item_init(bar_item, &bar_manager->default_item); bar_manager->bar_items[bar_manager->bar_item_count - 1] = bar_item; bar_manager->needs_ordering = true; return bar_item; } void bar_manager_update_alias_components(struct bar_manager* bar_manager, bool forced) { for (int i = 0; i < bar_manager->bar_item_count; i++) { if ((!bar_item_is_shown(bar_manager->bar_items[i]) && !forced) || bar_manager->bar_items[i]->type != BAR_COMPONENT_ALIAS ) { continue; } alias_update(&bar_manager->bar_items[i]->alias, forced); } } void bar_manager_update_space_components(struct bar_manager* bar_manager, bool forced) { for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; if (bar_item->type != BAR_COMPONENT_SPACE) continue; if (!bar_item->overrides_association) { uint32_t space = get_set_bit_position(bar_item->associated_space); uint32_t space_did = display_id_for_space(space); if (space_did) { bar_item->associated_display = 1 << (display_arrangement(space_did)); } else { bar_item->associated_display = 1 << 30; } } for (int j = 0; j < bar_manager->bar_count; j++) { struct bar* bar = bar_manager->bars[j]; uint32_t did = bar->adid; if ((1 << did) & bar_item->associated_display) { uint32_t sid = bar->sid; if (sid == 0) continue; if ((!bar_item->selected || forced) && bar_item->associated_space & (1 << sid)) { bar_item->selected = true; bar_item->updates = true; env_vars_set(&bar_item->signal_args.env_vars, string_copy("SELECTED"), string_copy("true") ); } else if ((bar_item->selected || forced) && !(bar_item->associated_space & (1 << sid))) { bar_item->selected = false; bar_item->updates = true; env_vars_set(&bar_item->signal_args.env_vars, string_copy("SELECTED"), string_copy("false") ); } else { bar_item->updates = false; } } } } } void bar_manager_animator_refresh(struct bar_manager* bar_manager, uint64_t time) { bar_manager_freeze(bar_manager); if (animator_update(&bar_manager->animator, time)) { bar_manager_unfreeze(bar_manager); if (bar_manager->bar_needs_resize) bar_manager_resize(bar_manager); bar_manager_refresh(bar_manager, false, false); } bar_manager_unfreeze(bar_manager); } void bar_manager_update(struct bar_manager* bar_manager, bool forced) { if ((bar_manager->frozen && !forced) || bar_manager->sleeps) return; if (forced) { bar_manager_handle_space_change(bar_manager, true); forced_network_event(); forced_volume_event(); forced_brightness_event(); forced_power_event(); forced_front_app_event(); forced_media_change_event(); forced_space_windows_event(); } bool needs_refresh = false; for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; needs_refresh |= bar_item_update(bar_item, NULL, forced, NULL); if (bar_item->has_alias && bar_item_is_shown(bar_item) && alias_update(&bar_item->alias, false)) { bar_item_needs_update(bar_item); needs_refresh = true; } } if (needs_refresh || forced) bar_manager_refresh(bar_manager, forced, false); } void bar_manager_reset(struct bar_manager* bar_manager) { bar_manager_reset_bar_association(bar_manager); for (int i = 0; i < bar_manager->bar_count; i++) { for (int j = 0; j < bar_manager->bar_item_count; j++) { struct bar_item* bar_item = bar_manager->bar_items[j]; bar_item_remove_window(bar_item, bar_manager->bars[i]->adid); } bar_destroy(bar_manager->bars[i]); bar_manager->bars[i] = NULL; } bar_manager->bar_count = 0; bar_manager_begin(bar_manager); } void bar_manager_begin(struct bar_manager* bar_manager) { if (bar_manager->displays == DISPLAY_MAIN_PATTERN) { uint32_t did = display_main_display_id(); bar_manager->bar_count = 1; bar_manager->bars = (struct bar **) realloc( bar_manager->bars, sizeof(struct bar *) * bar_manager->bar_count); memset(bar_manager->bars, 0, sizeof(struct bar*) * bar_manager->bar_count); bar_manager->bars[0] = bar_create(did); bar_manager->bars[0]->adid = display_arrangement(did); } else { uint32_t display_count = display_active_display_count(); uint32_t bar_count = 0; for (uint32_t index = 1; index <= display_count; index++) { if (!(bar_manager->displays & 1 << (index - 1))) continue; bar_count++; } bar_manager->bar_count = bar_count; bar_manager->bars = (struct bar **) realloc( bar_manager->bars, sizeof(struct bar *) * bar_manager->bar_count); memset(bar_manager->bars, 0, sizeof(struct bar*) * bar_manager->bar_count); uint32_t bar_index = 0; for (uint32_t index = 1; index <= display_count; index++) { if (!(bar_manager->displays & 1 << (index - 1))) continue; uint32_t did = display_arrangement_display_id(index); bar_manager->bars[bar_index] = bar_create(did); bar_manager->bars[bar_index]->adid = index; if (bar_manager->any_bar_hidden) bar_set_hidden(bar_manager->bars[bar_index], true); bar_index++; } } bar_manager->active_displays = 0; for (int i = 0; i < bar_manager->bar_count; i++) { bar_manager->active_displays |= 1 << bar_manager->bars[i]->adid; } bar_manager->active_adid = display_active_display_adid(); bar_manager->needs_ordering = true; } struct bar_item* bar_manager_get_item_by_point(struct bar_manager* bar_manager, CGPoint point, struct window** window_out) { for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; if (!bar_item->drawing) continue; for (int adid = 1; adid <= bar_item->num_windows; adid++) { struct window* window = bar_item_get_window(bar_item, adid); if (!window) continue; CGRect frame = window->frame; frame.origin = window->origin; if (cgrect_contains_point(&frame, &point)) { if (window_out) *window_out = window; return bar_item; } } } return NULL; } struct bar_item* bar_manager_get_item_by_wid(struct bar_manager* bar_manager, uint32_t wid, struct window** window_out) { for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; if (!bar_item->drawing) continue; for (int adid = 1; adid <= bar_item->num_windows; adid++) { struct window* window = bar_item_get_window(bar_item, adid); if (!window) continue; if (window->id == wid) { if (window_out) *window_out = window; return bar_item; } } } return NULL; } struct bar* bar_manager_get_bar_by_wid(struct bar_manager* bar_manager, uint32_t wid) { for (int i = 0; i < bar_manager->bar_count; i++) { if (bar_manager->bars[i]->window.id == wid) { return bar_manager->bars[i]; } } return NULL; } bool bar_manager_mouse_over_any_bar(struct bar_manager* bar_manager) { for (int i = 0; i < bar_manager->bar_count; i++) { if (bar_manager->bars[i]->mouse_over) return true; } return false; } struct popup* bar_manager_get_popup_by_wid(struct bar_manager* bar_manager, uint32_t wid) { for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; if (!bar_item->drawing || !bar_item->popup.drawing) { continue; } struct window* window = &bar_item->popup.window; if (window->id == wid) { return &bar_item->popup; } } return NULL; } struct popup* bar_manager_get_popup_by_point(struct bar_manager* bar_manager, CGPoint point) { for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; if (!bar_item->drawing || !bar_item->popup.drawing) { continue; } struct window* window = &bar_item->popup.window; CGRect frame = window->frame; frame.origin = window->origin; if (CGRectContainsPoint(frame, point)) return &bar_item->popup; } return NULL; } struct bar* bar_manager_get_bar_by_point(struct bar_manager* bar_manager, CGPoint point) { for (int i = 0; i < bar_manager->bar_count; i++) { struct window* window = &bar_manager->bars[i]->window; CGRect frame = window->frame; frame.origin = window->origin; if (CGRectContainsPoint(frame, point)) return bar_manager->bars[i]; } return NULL; } bool bar_manager_mouse_over_any_popup(struct bar_manager* bar_manager) { for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; if (!bar_item->drawing || !bar_item->popup.drawing) { continue; } if (bar_item->popup.mouse_over) return true; } return false; } void bar_manager_custom_events_trigger(struct bar_manager* bar_manager, char* name, struct env_vars* env_vars) { uint64_t flag = custom_events_get_flag_for_name(&bar_manager->custom_events, name ); for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; if (bar_item->update_mask & flag) bar_item_update(bar_item, name, false, env_vars); } } void bar_manager_display_resized(struct bar_manager* bar_manager, uint32_t did) { bar_manager_display_changed(bar_manager); } void bar_manager_display_moved(struct bar_manager* bar_manager, uint32_t did) { bar_manager_display_changed(bar_manager); } void bar_manager_display_removed(struct bar_manager* bar_manager, uint32_t did) { bar_manager_display_changed(bar_manager); } void bar_manager_display_added(struct bar_manager* bar_manager, uint32_t did) { bar_manager_display_changed(bar_manager); } void bar_manager_display_changed(struct bar_manager* bar_manager) { bar_manager->active_adid = display_active_display_adid(); bar_manager_freeze(bar_manager); bar_manager_reset(bar_manager); bar_manager_unfreeze(bar_manager); bar_manager_refresh(bar_manager, true, false); bar_manager_handle_display_change(bar_manager); bar_manager_handle_space_change(bar_manager, true); animator_renew_display_link(&bar_manager->animator); } void bar_manager_cancel_drag(struct bar_manager* bar_manager) { for (uint32_t i = 0; i < bar_manager->bar_item_count; i++) { bar_item_cancel_drag(bar_manager->bar_items[i]); } } void bar_manager_handle_mouse_entered_global(struct bar_manager* bar_manager) { bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_MOUSE_ENTERED_GLOBAL, NULL ); } void bar_manager_handle_mouse_exited_global(struct bar_manager* bar_manager) { bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_MOUSE_EXITED_GLOBAL, NULL ); bar_manager_handle_mouse_exited(bar_manager, NULL); } void bar_manager_handle_mouse_scrolled_global(struct bar_manager* bar_manager, int scroll_delta, uint32_t adid, uint32_t modifier) { struct env_vars env_vars; env_vars_init(&env_vars); char delta_ver_str[32]; snprintf(delta_ver_str, 32, "%d", scroll_delta); env_vars_set(&env_vars, string_copy("SCROLL_DELTA"), string_copy(delta_ver_str)); char info_str[256]; snprintf(info_str, 256, "{\n" "\t\"delta\": %d,\n" "\t\"modifier\": \"%s\",\n" "\t\"modfier_code\": %u\n" "}\n", scroll_delta, get_modifier_description(modifier), modifier ); env_vars_set(&env_vars, string_copy("INFO"), string_copy(info_str)); char adid_str[32]; snprintf(adid_str, 32, "%u", adid); env_vars_set(&env_vars, string_copy("DID"), string_copy(adid_str)); env_vars_set(&env_vars, string_copy("MODIFIER"), string_copy(get_modifier_description(modifier))); bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_MOUSE_SCROLLED_GLOBAL, &env_vars ); env_vars_destroy(&env_vars); } void bar_manager_handle_mouse_entered(struct bar_manager* bar_manager, struct bar_item* bar_item) { if (!bar_item) return; bar_item_mouse_entered(bar_item); } void bar_manager_handle_mouse_exited(struct bar_manager* bar_manager, struct bar_item* bar_item) { if (!bar_item) { for (int i = 0; i < bar_manager->bar_item_count; i++) bar_item_mouse_exited(bar_manager->bar_items[i]); } else { bar_item_mouse_exited(bar_item); } } void bar_manager_handle_volume_change(struct bar_manager* bar_manager, float volume) { struct env_vars env_vars; env_vars_init(&env_vars); char volume_str[16]; snprintf(volume_str, 16, "%d", (int)(volume*100. + 0.5)); env_vars_set(&env_vars, string_copy("INFO"), string_copy(volume_str)); bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_VOLUME_CHANGE, &env_vars ); env_vars_destroy(&env_vars); } void bar_manager_handle_wifi_change(struct bar_manager* bar_manager, char* ssid) { struct env_vars env_vars; env_vars_init(&env_vars); env_vars_set(&env_vars, string_copy("INFO"), string_copy(ssid)); bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_WIFI_CHANGE, &env_vars ); env_vars_destroy(&env_vars); } void bar_manager_handle_brightness_change(struct bar_manager* bar_manager, float brightness) { struct env_vars env_vars; env_vars_init(&env_vars); char brightness_str[16]; snprintf(brightness_str, 16, "%d", (int)(brightness*100. + 0.5)); env_vars_set(&env_vars, string_copy("INFO"), string_copy(brightness_str)); bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_BRIGHTNESS_CHANGE, &env_vars ); env_vars_destroy(&env_vars); } void bar_manager_handle_power_source_change(struct bar_manager* bar_manager, char* state) { struct env_vars env_vars; env_vars_init(&env_vars); env_vars_set(&env_vars, string_copy("INFO"), string_copy(state)); bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_POWER_SOURCE_CHANGE, &env_vars ); env_vars_destroy(&env_vars); } void bar_manager_handle_media_change(struct bar_manager* bar_manager, char* info) { struct env_vars env_vars; env_vars_init(&env_vars); env_vars_set(&env_vars, string_copy("INFO"), string_copy(info)); bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_MEDIA_CHANGE, &env_vars ); env_vars_destroy(&env_vars); } void bar_manager_handle_media_cover_change(struct bar_manager* bar_manager, CGImageRef image) { bool needs_refresh = false; if (image_set_image(&bar_manager->current_artwork, image, CGRectNull, false)){ for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; needs_refresh |= bar_item_set_media_cover(bar_item, &bar_manager->current_artwork); } } if (needs_refresh) bar_manager_refresh(bar_manager, false, false); } void bar_manager_handle_front_app_switch(struct bar_manager* bar_manager, char* info) { struct env_vars env_vars; env_vars_init(&env_vars); if (info) env_vars_set(&env_vars, string_copy("INFO"), info); bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_FRONT_APP_SWITCHED, &env_vars ); env_vars_destroy(&env_vars); } void bar_manager_handle_space_windows_change(struct bar_manager* bar_manager, char* info) { struct env_vars env_vars; env_vars_init(&env_vars); if (info) env_vars_set(&env_vars, string_copy("INFO"), string_copy(info)); bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_SPACE_WINDOWS_CHANGE, &env_vars ); env_vars_destroy(&env_vars); } void bar_manager_handle_space_change(struct bar_manager* bar_manager, bool forced) { struct env_vars env_vars; env_vars_init(&env_vars); char info[19 * bar_manager->bar_count + 4]; info[0] = '{'; info[1] = '\n'; uint32_t cursor = 2; char separator[] = ","; bar_manager_freeze(bar_manager); bool force_refresh = false; for (int i = 0; i < bar_manager->bar_count; i++) { uint64_t dsid = display_space_id(bar_manager->bars[i]->did); bar_manager->bars[i]->sid = mission_control_index(dsid); bool was_shown = bar_manager->bars[i]->shown; bar_manager->bars[i]->shown = SLSSpaceGetType(g_connection, dsid) != 4 || bar_manager->show_in_fullscreen; bar_manager->needs_ordering |= !was_shown && bar_manager->bars[i]->shown; force_refresh |= !was_shown && bar_manager->bars[i]->shown; force_refresh |= !bar_manager->bars[i]->shown && was_shown; if (bar_manager->bars[i]->dsid != dsid) { bar_manager->bars[i]->dsid = dsid; if (!bar_manager->sticky && bar_manager->bars[i]->shown) bar_change_space(bar_manager->bars[i], dsid); } if (i == bar_manager->bar_count - 1) separator[0] = '\0'; snprintf(info + cursor, 19 * bar_manager->bar_count + 4 - cursor, "\t\"display-%d\": %d%s\n", bar_manager->bars[i]->adid, bar_manager->bars[i]->sid, separator ); cursor = strlen(info); } info[cursor] = '}'; info[cursor + 1] = '\0'; env_vars_set(&env_vars, string_copy("INFO"), string_copy(info)); bar_manager_update_space_components(bar_manager, forced); bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_SPACE_CHANGE, &env_vars ); bar_manager_unfreeze(bar_manager); bar_manager_refresh(bar_manager, force_refresh, false); env_vars_destroy(&env_vars); } void bar_manager_poll_active_display(struct bar_manager* bar_manager) { uint32_t aadid = display_active_display_adid(); if (aadid != bar_manager->active_adid) { bar_manager_handle_display_change(bar_manager); } } void bar_manager_handle_display_change(struct bar_manager* bar_manager) { bar_manager->active_adid = display_active_display_adid(); struct env_vars env_vars; env_vars_init(&env_vars); char adid_str[3]; snprintf(adid_str, 3, "%d", bar_manager->active_adid); env_vars_set(&env_vars, string_copy("INFO"), string_copy(adid_str)); bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_DISPLAY_CHANGE, &env_vars ); bar_manager_refresh(bar_manager, false, false); env_vars_destroy(&env_vars); } void bar_manager_handle_system_will_sleep(struct bar_manager* bar_manager) { bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_SYSTEM_WILL_SLEEP, NULL ); animator_destroy_display_link(&bar_manager->animator); bar_manager->sleeps = true; } void bar_manager_handle_system_woke(struct bar_manager* bar_manager) { if (bar_manager->sleeps) { bar_manager->sleeps = false; // Sometimes the system wake notification precedes the display layout // changes, so we queue a second wake event slightly later. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ usleep(500000); dispatch_async(dispatch_get_main_queue(), ^{ struct event event = { NULL, SYSTEM_WOKE }; event_post(&event); }); }); } bar_manager_display_changed(bar_manager); bar_manager_custom_events_trigger(bar_manager, COMMAND_SUBSCRIBE_SYSTEM_WOKE, NULL ); } void bar_manager_handle_notification(struct bar_manager* bar_manager, struct notification* notification) { char* name = custom_events_get_name_for_notification(&bar_manager->custom_events, notification->name); if (!name) { notification_destroy(notification); return; } struct env_vars env_vars; env_vars_init(&env_vars); if (notification->info) env_vars_set(&env_vars, string_copy("INFO"), string_copy(notification->info)); bar_manager_custom_events_trigger(bar_manager, name, &env_vars); env_vars_destroy(&env_vars); notification_destroy(notification); } void bar_manager_destroy(struct bar_manager* bar_manager) { for (int i = 0; i < bar_manager->bar_item_count; i++) { struct bar_item* bar_item = bar_manager->bar_items[i]; if (bar_item->event_port) { mach_send_message(bar_item->event_port, "k", 2, false); } } animator_destroy(&bar_manager->animator); while (bar_manager->bar_item_count > 0) { bar_manager_remove_item(bar_manager, bar_manager->bar_items[0]); } if (bar_manager->bar_items) free(bar_manager->bar_items); for (int i = 0; i < bar_manager->bar_count; i++) { bar_destroy(bar_manager->bars[i]); } bar_item_destroy(&bar_manager->default_item, false); custom_events_destroy(&bar_manager->custom_events); background_destroy(&bar_manager->background); if (bar_manager->bars) free(bar_manager->bars); CFRunLoopRemoveTimer(CFRunLoopGetMain(), bar_manager->clock, kCFRunLoopCommonModes); CFRunLoopTimerInvalidate(bar_manager->clock); CFRelease(bar_manager->clock); image_destroy(&bar_manager->current_artwork); } void bar_manager_serialize(struct bar_manager* bar_manager, FILE* rsp) { char indent[] = { "\t" }; fprintf(rsp, "{\n" "%s\"position\": \"%s\",\n" "%s\"topmost\": \"%s\",\n" "%s\"sticky\": \"%s\",\n" "%s\"hidden\": \"%s\",\n" "%s\"shadow\": \"%s\",\n" "%s\"font_smoothing\": \"%s\",\n" "%s\"show_in_fullscreen\": \"%s\",\n" "%s\"blur_radius\": %u,\n" "%s\"margin\": %d,\n", indent, bar_manager->position == POSITION_BOTTOM ? "bottom" : "top", indent, format_bool(bar_manager->topmost), indent, format_bool(bar_manager->sticky), indent, format_bool(bar_manager->any_bar_hidden), indent, format_bool(bar_manager->shadow), indent, format_bool(bar_manager->font_smoothing), indent, format_bool(bar_manager->show_in_fullscreen), indent, bar_manager->blur_radius, indent, bar_manager->margin ); background_serialize(&bar_manager->background, indent, rsp, false); fprintf(rsp, ",\n%s\"items\": [\n", indent); for (int i = 0; i < bar_manager->bar_item_count; i++) { fprintf(rsp, "%s\t \"%s\"", indent, bar_manager->bar_items[i]->name); if (i < bar_manager->bar_item_count - 1) fprintf(rsp, ",\n"); } fprintf(rsp, "\n%s]\n}\n", indent); } ================================================ FILE: src/bar_manager.h ================================================ #pragma once #include "bar.h" #include "bar_item.h" #include "animation.h" #define CLOCK_CALLBACK(name) void name(CFRunLoopTimerRef timer, void *context) typedef CLOCK_CALLBACK(clock_callback); #define DISPLAY_MAIN_PATTERN 0 #define DISPLAY_ALL_PATTERN UINT32_MAX #define TOPMOST_LEVEL_WINDOW 'w' #define TOPMOST_LEVEL_ALL 'a' struct bar_manager { CFRunLoopTimerRef clock; bool frozen; bool sleeps; bool shadow; bool topmost; bool sticky; bool font_smoothing; bool any_bar_hidden; bool needs_ordering; bool might_need_clipping; bool bar_needs_update; bool bar_needs_resize; bool show_in_fullscreen; uint32_t displays; char position; int margin; uint32_t blur_radius; uint32_t notch_width; uint32_t notch_offset; uint32_t notch_display_height; uint32_t active_adid; uint32_t window_level; struct bar** bars; uint32_t bar_count; uint32_t active_displays; struct bar_item** bar_items; struct bar_item default_item; uint32_t bar_item_count; struct background background; struct custom_events custom_events; struct animator animator; struct image current_artwork; }; void bar_manager_init(struct bar_manager* bar_manager); void bar_manager_begin(struct bar_manager* bar_manager); void bar_manager_reset(struct bar_manager* bar_manager); struct bar_item* bar_manager_create_item(struct bar_manager* bar_manager); void bar_manager_remove_item(struct bar_manager* bar_manager, struct bar_item* bar_item); void bar_manager_move_item(struct bar_manager* bar_manager, struct bar_item* item, struct bar_item* reference, bool before); void bar_manager_handle_notification(struct bar_manager* bar_manager, struct notification* notification); void bar_manager_animator_refresh(struct bar_manager* bar_manager, uint64_t time); void bar_manager_update(struct bar_manager* bar_manager, bool forced); void bar_manager_update_space_components(struct bar_manager* bar_manager, bool forced); bool bar_manager_set_margin(struct bar_manager* bar_manager, int margin); bool bar_manager_set_y_offset(struct bar_manager* bar_manager, int y_offset); bool bar_manager_set_bar_height(struct bar_manager* bar_manager, int height); bool bar_manager_set_background_blur(struct bar_manager* bar_manager, uint32_t radius); bool bar_manager_set_position(struct bar_manager* bar_manager, char pos); bool bar_manager_set_spaces(struct bar_manager* bar_manager, bool value); bool bar_manager_set_spaces_for_all_displays(struct bar_manager* bar_manager, bool value); bool bar_manager_set_displays(struct bar_manager* bar_manager, uint32_t displays); bool bar_manager_set_hidden(struct bar_manager* bar_manager, uint32_t sid, bool hidden); bool bar_manager_set_topmost(struct bar_manager* bar_manager, char level, bool topmost); bool bar_manager_set_sticky(struct bar_manager *bar_manager, bool sticky); bool bar_manager_set_shadow(struct bar_manager* bar_manager, bool shadow); bool bar_manager_set_font_smoothing(struct bar_manager* bar_manager, bool smoothing); bool bar_manager_set_show_in_fullscreen(struct bar_manager* bar_manager, bool show_in_fullscreen); bool bar_manager_set_notch_width(struct bar_manager* bar_manager, uint32_t width); bool bar_manager_set_notch_offset(struct bar_manager* bar_manager, uint32_t offset); bool bar_manager_set_notch_display_height(struct bar_manager* bar_manager, uint32_t offset); void bar_manager_sort(struct bar_manager* bar_manager, struct bar_item** ordering, uint32_t count); struct bar_item* bar_manager_get_item_by_point(struct bar_manager* bar_manager, CGPoint point, struct window** window_out); struct bar* bar_manager_get_bar_by_point(struct bar_manager* bar_manager, CGPoint point); struct popup* bar_manager_get_popup_by_point(struct bar_manager* bar_manager, CGPoint point); struct bar_item* bar_manager_get_item_by_wid(struct bar_manager* bar_manager, uint32_t wid, struct window** window_out); struct popup* bar_manager_get_popup_by_wid(struct bar_manager* bar_manager, uint32_t wid); struct bar* bar_manager_get_bar_by_wid(struct bar_manager* bar_manager, uint32_t wid); int bar_manager_get_item_index_for_name(struct bar_manager* bar_manager, char* name); uint32_t bar_manager_length_for_bar_side(struct bar_manager* bar_manager, struct bar* bar, char side); bool bar_manager_mouse_over_any_popup(struct bar_manager* bar_manager); bool bar_manager_mouse_over_any_bar(struct bar_manager* bar_manager); void bar_manager_freeze(struct bar_manager* bar_manager); void bar_manager_unfreeze(struct bar_manager* bar_manager); void bar_manager_display_changed(struct bar_manager* bar_manager); void bar_manager_display_resized(struct bar_manager* bar_manager, uint32_t did); void bar_manager_display_moved(struct bar_manager* bar_manager, uint32_t did); void bar_manager_display_removed(struct bar_manager* bar_manager, uint32_t did); void bar_manager_display_added(struct bar_manager* bar_manager, uint32_t did); void bar_manager_refresh(struct bar_manager* bar_manager, bool forced, bool threaded); void bar_manager_resize(struct bar_manager* bar_manager); void bar_manager_poll_active_display(struct bar_manager* bar_manager); void bar_manager_handle_mouse_entered_global(struct bar_manager* bar_manager); void bar_manager_handle_mouse_exited_global(struct bar_manager* bar_manager); void bar_manager_handle_mouse_scrolled_global(struct bar_manager* bar_manager, int scroll_delta, uint32_t did, uint32_t modifier); void bar_manager_handle_mouse_entered(struct bar_manager* bar_manager, struct bar_item* bar_item); void bar_manager_handle_mouse_exited(struct bar_manager* bar_manager, struct bar_item* bar_item); void bar_manager_handle_front_app_switch(struct bar_manager* bar_manager, char* info); void bar_manager_handle_space_change(struct bar_manager* bar_manager, bool forced); void bar_manager_handle_display_change(struct bar_manager* bar_manager); void bar_manager_handle_system_woke(struct bar_manager* bar_manager); void bar_manager_handle_system_will_sleep(struct bar_manager* bar_manager); void bar_manager_handle_volume_change(struct bar_manager* bar_manager, float volume); void bar_manager_handle_wifi_change(struct bar_manager* bar_manager, char* ssid); void bar_manager_handle_brightness_change(struct bar_manager* bar_manager, float brightness); void bar_manager_handle_power_source_change(struct bar_manager* bar_manager, char* state); void bar_manager_handle_media_change(struct bar_manager* bar_manager, char* info); void bar_manager_handle_media_cover_change(struct bar_manager* bar_manager, CGImageRef image); void bar_manager_handle_space_windows_change(struct bar_manager* bar_manager, char* info); void bar_manager_custom_events_trigger(struct bar_manager* bar_manager, char* name, struct env_vars* env_vars); void bar_manager_destroy(struct bar_manager* bar_manager); void bar_manager_serialize(struct bar_manager* bar_manager, FILE* rsp); ================================================ FILE: src/color.c ================================================ #include "color.h" #include "bar_manager.h" #include "animation.h" static bool color_update_hex(struct color* color) { uint32_t prev = color->hex; color->hex = (((uint32_t)(color->a * 255.f)) << 24) + (((uint32_t)(color->r * 255.f)) << 16) + (((uint32_t)(color->g * 255.f)) << 8) + (((uint32_t)(color->b * 255.f)) << 0); return prev != color->hex; } void color_init(struct color* color, uint32_t hex) { color_set_hex(color, hex); } bool color_set_hex(struct color* color, uint32_t hex) { color->a = clamp(((hex >> 24) & 0xff) / 255.f, 0.f, 1.f); color->r = clamp(((hex >> 16) & 0xff) / 255.f, 0.f, 1.f); color->g = clamp(((hex >> 8) & 0xff) / 255.f, 0.f, 1.f); color->b = clamp(((hex >> 0) & 0xff) / 255.f, 0.f, 1.f); return color_update_hex(color); } bool color_set_alpha(struct color* color, float alpha) { color->a = clamp(alpha, 0.f, 1.f); return color_update_hex(color); } bool color_set_r(struct color* color, float red) { color->r = clamp(red, 0.f, 1.f); return color_update_hex(color); } bool color_set_g(struct color* color, float green) { color->g = clamp(green, 0.f, 1.f); return color_update_hex(color); } bool color_set_b(struct color* color, float blue) { color->b = clamp(blue, 0.f, 1.f); return color_update_hex(color); } bool color_parse_sub_domain(struct color* color, FILE* rsp, struct token property, char* message) { bool needs_refresh = false; if (token_equals(property, PROPERTY_COLOR_HEX)) { ANIMATE_BYTES(color_set_hex, color, color->hex, token_to_int(get_token(&message))); } else if (token_equals(property, PROPERTY_COLOR_ALPHA)) { ANIMATE_FLOAT(color_set_alpha, color, color->a, token_to_float(get_token(&message))); } else if (token_equals(property, PROPERTY_COLOR_RED)) { ANIMATE_FLOAT(color_set_r, color, color->r, token_to_float(get_token(&message))); } else if (token_equals(property, PROPERTY_COLOR_GREEN)) { ANIMATE_FLOAT(color_set_g, color, color->g, token_to_float(get_token(&message))); } else if (token_equals(property, PROPERTY_COLOR_BLUE)) { ANIMATE_FLOAT(color_set_b, color, color->b, token_to_float(get_token(&message))); } else { respond(rsp, "[?] Color: Invalid property '%s'\n", property); } return needs_refresh; } ================================================ FILE: src/color.h ================================================ #pragma once #include #include #include "misc/helpers.h" struct color { float r; float g; float b; float a; uint32_t hex; }; static struct color g_transparent = { 0 }; void color_init(struct color* color, uint32_t hex); bool color_set_hex(struct color* color, uint32_t hex); bool color_set_alpha(struct color* color, float alpha); bool color_set_r(struct color* color, float red); bool color_set_g(struct color* color, float green); bool color_set_b(struct color* color, float blue); bool color_parse_sub_domain(struct color* color, FILE* rsp, struct token property, char* message); ================================================ FILE: src/custom_events.c ================================================ #include "custom_events.h" static struct custom_event* custom_event_create(void) { return malloc(sizeof(struct custom_event)); } void custom_event_init(struct custom_event* custom_event, char* name, char* notification) { custom_event->name = name; custom_event->notification = notification; } void custom_event_destroy(struct custom_event* custom_event) { if (custom_event->name) free(custom_event->name); if (custom_event->notification) free(custom_event->notification); free(custom_event); } void custom_events_init(struct custom_events* custom_events) { custom_events->count = 0; custom_events->events = NULL; // System Events custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_FRONT_APP_SWITCHED), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_SPACE_CHANGE), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_DISPLAY_CHANGE), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_SYSTEM_WOKE), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_MOUSE_ENTERED), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_MOUSE_EXITED), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_MOUSE_CLICKED), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_MOUSE_SCROLLED), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_SYSTEM_WILL_SLEEP), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_MOUSE_ENTERED_GLOBAL), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_MOUSE_EXITED_GLOBAL), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_MOUSE_SCROLLED_GLOBAL), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_VOLUME_CHANGE), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_BRIGHTNESS_CHANGE), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_POWER_SOURCE_CHANGE), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_WIFI_CHANGE), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_MEDIA_CHANGE), NULL); custom_events_append(custom_events, string_copy(COMMAND_SUBSCRIBE_SPACE_WINDOWS_CHANGE), NULL); } void custom_events_append(struct custom_events* custom_events, char* name, char* notification) { if (custom_events_get_flag_for_name(custom_events, name) > 0) { if (name) free(name); if (notification) free(notification); return; } custom_events->count++; custom_events->events = (struct custom_event**) realloc( custom_events->events, sizeof(struct custom_event*) * custom_events->count); custom_events->events[custom_events->count - 1] = custom_event_create(); custom_events->events[custom_events->count - 1]->name = name; custom_events->events[custom_events->count - 1]->notification = notification; if (notification) workspace_create_custom_observer(&g_workspace_context, notification); } uint64_t custom_events_get_flag_for_name(struct custom_events* custom_events, char* name) { for (int i = 0; i < custom_events->count; i++) { if (strcmp(name, custom_events->events[i]->name) == 0) { return 1ULL << i; } } return 0; } char* custom_events_get_name_for_notification(struct custom_events* custom_events, char* notification) { for (int i = 0; i < custom_events->count; i++) { if (!custom_events->events[i]->notification) continue; if (strcmp(notification, custom_events->events[i]->notification) == 0) { return custom_events->events[i]->name; } } return NULL; } void custom_events_destroy(struct custom_events* custom_events) { for (int i = 0; i < custom_events->count; i++) { custom_event_destroy(custom_events->events[i]); } free(custom_events->events); } void custom_events_serialize(struct custom_events* custom_events, FILE* rsp) { fprintf(rsp, "{\n"); for (int i = 0; i < custom_events->count; i++) { fprintf(rsp, "\t\"%s\": {\n" "\t\t\"bit\": %llu,\n" "\t\t\"notification\": \"%s\"\n", custom_events->events[i]->name, 1ULL << i, custom_events->events[i]->notification); if (i < custom_events->count - 1) fprintf(rsp, "\t},\n"); } fprintf(rsp, "\t}\n}\n"); } ================================================ FILE: src/custom_events.h ================================================ #pragma once #include "misc/helpers.h" #define UPDATE_FRONT_APP_SWITCHED 1ULL #define UPDATE_SPACE_CHANGE (1ULL << 1) #define UPDATE_DISPLAY_CHANGE (1ULL << 2) #define UPDATE_SYSTEM_WOKE (1ULL << 3) #define UPDATE_MOUSE_ENTERED (1ULL << 4) #define UPDATE_MOUSE_EXITED (1ULL << 5) #define UPDATE_MOUSE_CLICKED (1ULL << 6) #define UPDATE_MOUSE_SCROLLED (1ULL << 7) #define UPDATE_SYSTEM_WILL_SLEEP (1ULL << 8) #define UPDATE_ENTERED_GLOBAL (1ULL << 9) #define UPDATE_EXITED_GLOBAL (1ULL << 10) #define UPDATE_SCROLLED_GLOBAL (1ULL << 11) #define UPDATE_VOLUME_CHANGE (1ULL << 12) #define UPDATE_BRIGHTNESS_CHANGE (1ULL << 13) #define UPDATE_POWER_SOURCE_CHANGE (1ULL << 14) #define UPDATE_WIFI_CHANGE (1ULL << 15) #define UPDATE_MEDIA_CHANGE (1ULL << 16) #define UPDATE_SPACE_WINDOWS_CHANGE (1ULL << 17) extern void* g_workspace_context; extern void workspace_create_custom_observer(void** context, char* name); struct custom_event { char* name; char* notification; }; void custom_event_init(struct custom_event* custom_event, char* name, char* notification); struct custom_events { uint32_t count; struct custom_event** events; }; void custom_events_init(struct custom_events* custom_events); void custom_events_append(struct custom_events* custom_events, char* name, char* notification); uint64_t custom_events_get_flag_for_name(struct custom_events* custom_events, char* name); char* custom_events_get_name_for_notification(struct custom_events* custom_events, char* notification); void custom_events_destroy(struct custom_events* custom_events); void custom_events_serialize(struct custom_events* custom_events, FILE* rsp); ================================================ FILE: src/display.c ================================================ #include "display.h" #include "misc/helpers.h" extern int workspace_display_notch_height(uint32_t did); extern int g_connection; extern int g_space_management_mode; extern bool g_brightness_events; float g_last_brightness = -1.f; static void brightness_handler(void* notification_center, uint32_t did, void* name, const void* sender, CFDictionaryRef info) { float b = 0; float* brightness = &b; DisplayServicesGetBrightness(did, brightness); if (g_last_brightness < *brightness - 1e-2 || g_last_brightness > *brightness + 1e-2) { g_last_brightness = *brightness; struct event event = { (void*) brightness, BRIGHTNESS_CHANGED }; event_post(&event); } } static DISPLAY_EVENT_HANDLER(display_handler) { if (flags & kCGDisplayAddFlag) { struct event event = { (void *)(intptr_t) did, DISPLAY_ADDED }; event_post(&event); if (g_brightness_events && DisplayServicesCanChangeBrightness(did)) DisplayServicesRegisterForBrightnessChangeNotifications(did, did, (void*)brightness_handler); } else if (flags & kCGDisplayRemoveFlag) { struct event event = { (void *)(intptr_t) did, DISPLAY_REMOVED }; event_post(&event); if (g_brightness_events && DisplayServicesCanChangeBrightness(did)) DisplayServicesUnregisterForBrightnessChangeNotifications(did, did); } else if (flags & kCGDisplayMovedFlag) { struct event event = { (void *)(intptr_t) did, DISPLAY_MOVED }; event_post(&event); } else if (flags & kCGDisplayDesktopShapeChangedFlag) { struct event event = { (void *)(intptr_t) did, DISPLAY_RESIZED }; event_post(&event); } } CFStringRef display_uuid(uint32_t did) { CFUUIDRef uuid_ref = CGDisplayCreateUUIDFromDisplayID(did); if (!uuid_ref) return NULL; CFStringRef uuid_str = CFUUIDCreateString(NULL, uuid_ref); CFRelease(uuid_ref); return uuid_str; } CGRect display_bounds(uint32_t did) { return CGDisplayBounds(did); } uint64_t display_space_id(uint32_t did) { CFStringRef uuid = display_uuid(did); if (!uuid) return 0; uint64_t sid = SLSManagedDisplayGetCurrentSpace(g_connection, uuid); CFRelease(uuid); return sid; } uint64_t *display_space_list(uint32_t did, int *count) { CFStringRef uuid = display_uuid(did); if (!uuid) return NULL; CFArrayRef display_spaces_ref = SLSCopyManagedDisplaySpaces(g_connection); if (!display_spaces_ref) return NULL; uint64_t *space_list = NULL; int display_spaces_count = CFArrayGetCount(display_spaces_ref); for (int i = 0; i < display_spaces_count; ++i) { CFDictionaryRef display_ref = CFArrayGetValueAtIndex(display_spaces_ref, i); CFStringRef identifier = CFDictionaryGetValue(display_ref, CFSTR("Display Identifier")); if (!CFEqual(uuid, identifier)) continue; CFArrayRef spaces_ref = CFDictionaryGetValue(display_ref, CFSTR("Spaces")); int spaces_count = CFArrayGetCount(spaces_ref); space_list = malloc(sizeof(uint64_t) * spaces_count); *count = spaces_count; for (int j = 0; j < spaces_count; ++j) { CFDictionaryRef space_ref = CFArrayGetValueAtIndex(spaces_ref, j); CFNumberRef sid_ref = CFDictionaryGetValue(space_ref, CFSTR("id64")); CFNumberGetValue(sid_ref, CFNumberGetType(sid_ref), &space_list[j]); } } CFRelease(display_spaces_ref); CFRelease(uuid); return space_list; } int display_arrangement(uint32_t did) { if (display_active_display_count() == 1) { uint32_t result = 0; uint32_t count = 0; CGGetActiveDisplayList(1, &result, &count); if (did == result && count == 1) return 1; else return 0; } CFStringRef uuid = display_uuid(did); if (!uuid) return 0; CFArrayRef displays = SLSCopyManagedDisplays(g_connection); if (!displays) { CFRelease(uuid); return 0; } int result = 0; int displays_count = CFArrayGetCount(displays); for (int i = 0; i < displays_count; ++i) { if (CFEqual(CFArrayGetValueAtIndex(displays, i), uuid)) { result = i + 1; break; } } CFRelease(displays); CFRelease(uuid); return result; } uint32_t display_main_display_id(void) { return CGMainDisplayID(); } static CFStringRef display_active_display_uuid(void) { if (g_space_management_mode != 1) { CGEventRef event = CGEventCreate(NULL); if (!event) return NULL; CGPoint cursor = CGEventGetLocation(event); uint32_t count = 0; uint32_t* dids = display_active_display_list(&count); uint32_t mouse_did = 0; for (uint32_t i = 0; i < count; i++) { if (CGRectContainsPoint(CGDisplayBounds(dids[i]), cursor)) { mouse_did = dids[i]; break; } } if (dids) free(dids); CFRelease(event); return display_uuid(mouse_did); } else return SLSCopyActiveMenuBarDisplayIdentifier(g_connection); } uint32_t display_active_display_id(void) { if (display_active_display_count() == 1) { uint32_t did = 0; uint32_t count = 0; CGGetActiveDisplayList(1, &did, &count); if (count == 1) return did; else { printf("ERROR (id): No active display detected!\n"); return 0; } } CFStringRef uuid = display_active_display_uuid(); if (!uuid) return 0; CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, uuid); uint32_t result = CGDisplayGetDisplayIDFromUUID(uuid_ref); CFRelease(uuid_ref); CFRelease(uuid); return result; } CFStringRef display_arrangement_display_uuid(int arrangement) { CFStringRef result = NULL; CFArrayRef displays = SLSCopyManagedDisplays(g_connection); int displays_count = CFArrayGetCount(displays); for (int i = 0; i < displays_count; ++i) { if ((i+1) != arrangement) continue; result = CFRetain(CFArrayGetValueAtIndex(displays, i)); break; } CFRelease(displays); return result; } uint32_t display_arrangement_display_id(int arrangement) { uint32_t result = 0; CFArrayRef displays = SLSCopyManagedDisplays(g_connection); int displays_count = CFArrayGetCount(displays); for (int i = 0; i < displays_count; ++i) { if ((i+1) != arrangement) continue; CFUUIDRef uuid_ref = CFUUIDCreateFromString(NULL, CFArrayGetValueAtIndex(displays, i)); result = CGDisplayGetDisplayIDFromUUID(uuid_ref); CFRelease(uuid_ref); break; } CFRelease(displays); return result; } uint32_t display_active_display_adid(void) { if (display_active_display_count() == 1) return 1; CFStringRef uuid = display_active_display_uuid(); if (!uuid) return 0; CFArrayRef displays = SLSCopyManagedDisplays(g_connection); if (!displays) { CFRelease(uuid); return 0; } int result = 0; int displays_count = CFArrayGetCount(displays); for (int i = 0; i < displays_count; ++i) { if (CFEqual(CFArrayGetValueAtIndex(displays, i), uuid)) { result = i + 1; break; } } CFRelease(displays); CFRelease(uuid); return result; } bool display_menu_bar_visible(void) { int status = 0; SLSGetMenuBarAutohideEnabled(g_connection, &status); return !status; } CGRect display_menu_bar_rect(uint32_t did) { CGRect bounds = {}; #ifdef __x86_64__ SLSGetRevealedMenuBarBounds(&bounds, g_connection, display_space_id(did)); #elif __arm64__ int notch_height = workspace_display_notch_height(did); if (notch_height) { bounds.size.height = notch_height + 6; } else { bounds.size.height = 24; } bounds.size.width = CGDisplayPixelsWide(did); #endif return bounds; } uint32_t display_active_display_count(void) { uint32_t count; CGGetActiveDisplayList(0, NULL, &count); return count; } uint32_t *display_active_display_list(uint32_t *count) { int display_count = display_active_display_count(); uint32_t *result = malloc(sizeof(uint32_t) * display_count); CGGetActiveDisplayList(display_count, result, count); return result; } bool display_begin() { return CGDisplayRegisterReconfigurationCallback(display_handler, NULL) == kCGErrorSuccess; } bool display_end() { return CGDisplayRemoveReconfigurationCallback(display_handler, NULL) == kCGErrorSuccess; } void forced_brightness_event() { g_last_brightness = -1.f; brightness_handler(NULL, display_active_display_id(), NULL, NULL, NULL); } void begin_receiving_brightness_events() { if (g_brightness_events) return; g_brightness_events = true; uint32_t count; uint32_t* result = display_active_display_list(&count); for (int i = 0; i < count; i++) { uint32_t did = *(result + i); if (DisplayServicesCanChangeBrightness(did)) { DisplayServicesRegisterForBrightnessChangeNotifications(did, did, (void*)brightness_handler); } } } void display_serialize(FILE* rsp) { uint32_t count = 0; uint32_t* display_ids = display_active_display_list(&count); if (!display_ids) return; fprintf(rsp, "[\n"); for (int i = 0; i < count; i++) { fprintf(rsp, "\t{\n"); uint32_t did = display_arrangement_display_id(i + 1); CFStringRef uuid_ref = display_uuid(did); CGRect frame = CGDisplayBounds(did); char* uuid = NULL; if (uuid_ref) { uuid = cfstring_copy(uuid_ref); CFRelease(uuid_ref); } fprintf(rsp, "\t\t\"arrangement-id\":%d,\n", display_arrangement(did)); fprintf(rsp, "\t\t\"DirectDisplayID\":%d,\n", did); fprintf(rsp, "\t\t\"UUID\":\"%s\",\n", uuid ? uuid : ""); fprintf(rsp, "\t\t\"frame\":{\n\t\t\"x\":%.4f,\n\t\t\"y\":%.4f,\n\t\t\"w\":%.4f,\n\t\t\"h\":%.4f\n\t\t}\n", frame.origin.x, frame.origin.y, frame.size.width, frame.size.height); if (i == count - 1) fprintf(rsp, "\t}\n"); else fprintf(rsp, "\t},\n"); if (uuid) free(uuid); } fprintf(rsp, "]\n"); free(display_ids); } ================================================ FILE: src/display.h ================================================ #pragma once #include "event.h" #include "misc/helpers.h" #define DISPLAY_EVENT_HANDLER(name) void name(uint32_t did, CGDisplayChangeSummaryFlags flags, void *context) typedef DISPLAY_EVENT_HANDLER(display_callback); uint32_t display_main_display_id(void); uint32_t display_active_display_id(void); uint32_t display_active_display_adid(void); uint32_t display_arrangement_display_id(int arrangement); bool display_menu_bar_visible(void); CGRect display_menu_bar_rect(uint32_t did); uint32_t display_active_display_count(void); uint32_t* display_active_display_list(uint32_t* count); bool display_begin(void); bool display_end(void); CFStringRef display_uuid(uint32_t did); CGRect display_bounds(uint32_t did); uint64_t display_space_id(uint32_t did); uint64_t* display_space_list(uint32_t did, int* count); int display_arrangement(uint32_t did); void forced_brightness_event(); void begin_receiving_brightness_events(); void display_serialize(FILE* rsp); ================================================ FILE: src/event.c ================================================ #include "event.h" #include "bar_manager.h" #include "custom_events.h" #include "hotload.h" extern struct bar_manager g_bar_manager; extern int g_connection; extern int g_space_management_mode; static void event_distributed_notification(void* context) { bar_manager_handle_notification(&g_bar_manager, context); } static void event_application_front_switched(void* context) { bar_manager_handle_front_app_switch(&g_bar_manager, context); } static void event_space_changed(void* context) { bar_manager_handle_space_change(&g_bar_manager, false); } static void event_display_changed(void* context) { bar_manager_handle_display_change(&g_bar_manager); } static void event_display_added(void* context) { uint32_t did = (uint32_t)(intptr_t)context; bar_manager_display_added(&g_bar_manager, did); } static void event_display_removed(void* context) { uint32_t did = (uint32_t)(intptr_t)context; bar_manager_display_removed(&g_bar_manager, did); } static void event_display_moved(void* context) { uint32_t did = (uint32_t)(intptr_t)context; bar_manager_display_moved(&g_bar_manager, did); } static void event_display_resized(void* context) { uint32_t did = (uint32_t)(intptr_t)context; bar_manager_display_resized(&g_bar_manager, did); } static void event_menu_bar_hidden_changed(void* context) { bar_manager_resize(&g_bar_manager); g_bar_manager.bar_needs_update = true; bar_manager_refresh(&g_bar_manager, false, false); } static void event_system_woke(void* context) { bar_manager_handle_system_woke(&g_bar_manager); } static void event_system_will_sleep(void* context) { bar_manager_handle_system_will_sleep(&g_bar_manager); } static void event_shell_refresh(void* context) { bar_manager_update(&g_bar_manager, false); } static void event_animator_refresh(void* context) { bar_manager_animator_refresh(&g_bar_manager, (uint64_t)context); } static void event_mach_message(void* context) { handle_message_mach(context); } static void event_mouse_up(void* context) { CGPoint point = CGEventGetLocation(context); uint32_t wid = get_wid_from_cg_event(context); CGEventType type = CGEventGetType(context); uint32_t mouse_button_code = CGEventGetIntegerValueField(context, kCGMouseEventButtonNumber); uint32_t modifier_keys = CGEventGetFlags(context); struct window* window = NULL; struct bar_item* bar_item = bar_manager_get_item_by_wid(&g_bar_manager, wid, &window ); if (!bar_item || bar_item->type == BAR_COMPONENT_GROUP) { bar_item = bar_manager_get_item_by_point(&g_bar_manager, point, &window); } struct bar* bar = bar_manager_get_bar_by_wid(&g_bar_manager, wid); struct popup* popup = bar_manager_get_popup_by_wid(&g_bar_manager, wid); if (!bar_item && !popup && !bar) return; CGPoint point_in_window_coords = CGPointZero; if (bar_item && window) { point_in_window_coords.x = point.x - window->origin.x; point_in_window_coords.y = point.y - window->origin.y; } bar_item_on_click(bar_item, type, mouse_button_code, modifier_keys, point_in_window_coords); if (bar_item && bar_item->needs_update) bar_manager_refresh(&g_bar_manager, false, false); } static void event_mouse_dragged(void* context) { CGPoint point = CGEventGetLocation(context); uint32_t wid = get_wid_from_cg_event(context); struct window* window = NULL; struct bar_item* bar_item = bar_manager_get_item_by_wid(&g_bar_manager, wid, &window ); if (!bar_item || !bar_item->has_slider) return; CGPoint point_in_window_coords = CGPointZero; if (bar_item && window) { point_in_window_coords.x = point.x - window->origin.x; point_in_window_coords.y = point.y - window->origin.y; } bar_item_on_drag(bar_item, point_in_window_coords); if (bar_item->needs_update) bar_manager_refresh(&g_bar_manager, false, false); } static void event_mouse_entered(void* context) { uint32_t wid = get_wid_from_cg_event(context); struct bar* bar = bar_manager_get_bar_by_wid(&g_bar_manager, wid); if (bar) { // Handle global mouse entered event if (!bar->mouse_over && !bar_manager_mouse_over_any_popup(&g_bar_manager)) { bar->mouse_over = true; bar_manager_handle_mouse_entered_global(&g_bar_manager); } return; } struct popup* popup = bar_manager_get_popup_by_wid(&g_bar_manager, wid); if (popup) { // Handle global mouse entered event if (!popup->mouse_over && !bar_manager_mouse_over_any_bar(&g_bar_manager)) { popup->mouse_over = true; bar_manager_handle_mouse_entered_global(&g_bar_manager); } return; } struct bar_item* bar_item = bar_manager_get_item_by_wid(&g_bar_manager, wid, NULL ); bar_manager_handle_mouse_entered(&g_bar_manager, bar_item); } static void event_mouse_exited(void* context) { uint32_t wid = get_wid_from_cg_event(context); struct bar* bar = NULL,* bar_target = NULL; struct popup* popup,* popup_target = NULL; struct window* origin_window = NULL; bool over_target = false; CGPoint point = CGEventGetLocation(context); if ((bar = bar_manager_get_bar_by_wid(&g_bar_manager, wid))) { origin_window = &bar->window; popup_target = bar_manager_get_popup_by_point(&g_bar_manager, point ); over_target = (popup_target != NULL); } else if ((popup = bar_manager_get_popup_by_wid(&g_bar_manager, wid))) { origin_window = &popup->window; bar_target = bar_manager_get_bar_by_point(&g_bar_manager, point); over_target = (bar_target != NULL); } if (bar || popup) { // Handle global mouse exited event CGRect frame = origin_window->frame; frame.origin = origin_window->origin; frame = CGRectInset(frame, 1, 1); bool over_origin = CGRectContainsPoint(frame, point); if (!over_origin && !over_target) { if (bar) bar->mouse_over = false; else if (popup) popup->mouse_over = false; bar_manager_handle_mouse_exited_global(&g_bar_manager); } else if (!over_origin && over_target) { if (bar) { bar->mouse_over = false; if (popup_target) popup_target->mouse_over = true; } else { if (bar_target) bar_target->mouse_over = true; if (popup) { popup->mouse_over = false; bool has_complex_mask = popup->host->update_mask & (UPDATE_MOUSE_EXITED | UPDATE_EXITED_GLOBAL); if (has_complex_mask && bar_manager_get_item_by_point(&g_bar_manager, point, NULL) != popup->host) { bar_manager_handle_mouse_exited(&g_bar_manager, popup->host); } } } } return; } struct window* window = NULL; struct bar_item* bar_item = bar_manager_get_item_by_wid(&g_bar_manager, wid, &window ); if (bar_item && bar_item->update_mask & UPDATE_EXITED_GLOBAL && bar_manager_get_popup_by_point(&g_bar_manager, point) == &bar_item->popup) { return; } if (bar_item) bar_manager_handle_mouse_exited(&g_bar_manager, bar_item); } #define SCROLL_TIMEOUT 150000000 struct { uint64_t timestamp; int delta_y; } g_scroll_info; static void event_mouse_scrolled(void* context) { CGPoint point = CGEventGetLocation(context); uint32_t wid = get_wid_from_cg_event(context); int scroll_delta = CGEventGetIntegerValueField(context, kCGScrollWheelEventDeltaAxis1); uint32_t modifier_keys = CGEventGetFlags(context); uint64_t event_time = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW_APPROX); if (g_scroll_info.timestamp + SCROLL_TIMEOUT > event_time) { g_scroll_info.delta_y += scroll_delta; return; } else { if (g_scroll_info.timestamp + 2*SCROLL_TIMEOUT < event_time) g_scroll_info.delta_y = 0; g_scroll_info.timestamp = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW_APPROX); } struct bar_item* bar_item = bar_manager_get_item_by_wid(&g_bar_manager, wid, NULL ); if (!bar_item || bar_item->type == BAR_COMPONENT_GROUP) { bar_item = bar_manager_get_item_by_point(&g_bar_manager, point, NULL); } if (!bar_item) { struct bar* bar = bar_manager_get_bar_by_wid(&g_bar_manager, wid); if (bar) { // Handle global mouse scrolled event if (bar->mouse_over && !bar_manager_mouse_over_any_popup(&g_bar_manager)) { bar_manager_handle_mouse_scrolled_global(&g_bar_manager, scroll_delta + g_scroll_info.delta_y, bar->adid, modifier_keys ); } g_scroll_info.delta_y = 0; return; } struct popup* popup = bar_manager_get_popup_by_wid(&g_bar_manager, wid); if (popup) { // Handle global mouse scrolled event if (popup->mouse_over && !bar_manager_mouse_over_any_bar(&g_bar_manager)) { bar_manager_handle_mouse_scrolled_global(&g_bar_manager, scroll_delta + g_scroll_info.delta_y, popup->adid, modifier_keys ); } g_scroll_info.delta_y = 0; return; } } bar_item_on_scroll(bar_item, scroll_delta + g_scroll_info.delta_y, modifier_keys ); if (bar_item && bar_item->needs_update) bar_manager_refresh(&g_bar_manager, false, false); g_scroll_info.delta_y = 0; } static void event_volume_changed(void* context) { bar_manager_handle_volume_change(&g_bar_manager, *(float*)context); } static void event_wifi_changed(void* context) { bar_manager_handle_wifi_change(&g_bar_manager, (char*)context); } static void event_brightness_changed(void* context) { bar_manager_handle_brightness_change(&g_bar_manager, *(float*)context); } static void event_power_source_changed(void* context) { bar_manager_handle_power_source_change(&g_bar_manager, (char*)context); } static void event_media_changed(void* context) { bar_manager_handle_media_change(&g_bar_manager, (char*)context); } static void event_cover_changed(void* context) { bar_manager_handle_media_cover_change(&g_bar_manager, (CGImageRef)context); } static void event_space_windows_changed(void* context) { bar_manager_handle_space_windows_change(&g_bar_manager, (char*)context); } static void event_hotload(void* context) { bar_manager_destroy(&g_bar_manager); bar_manager_init(&g_bar_manager); bar_manager_begin(&g_bar_manager); exec_config_file(); } typedef void callback_type(void*); static callback_type* event_handler[] = { [APPLICATION_FRONT_SWITCHED] = event_application_front_switched, [SPACE_CHANGED] = event_space_changed, [DISPLAY_ADDED] = event_display_added, [DISPLAY_REMOVED] = event_display_removed, [DISPLAY_MOVED] = event_display_moved, [DISPLAY_RESIZED] = event_display_resized, [DISPLAY_CHANGED] = event_display_changed, [MOUSE_UP] = event_mouse_up, [MOUSE_DRAGGED] = event_mouse_dragged, [MOUSE_ENTERED] = event_mouse_entered, [MOUSE_EXITED] = event_mouse_exited, [MOUSE_SCROLLED] = event_mouse_scrolled, [VOLUME_CHANGED] = event_volume_changed, [WIFI_CHANGED] = event_wifi_changed, [BRIGHTNESS_CHANGED] = event_brightness_changed, [POWER_SOURCE_CHANGED] = event_power_source_changed, [MEDIA_CHANGED] = event_media_changed, [COVER_CHANGED] = event_cover_changed, [DISTRIBUTED_NOTIFICATION] = event_distributed_notification, [MENU_BAR_HIDDEN_CHANGED] = event_menu_bar_hidden_changed, [SYSTEM_WOKE] = event_system_woke, [SYSTEM_WILL_SLEEP] = event_system_will_sleep, [SHELL_REFRESH] = event_shell_refresh, [ANIMATOR_REFRESH] = event_animator_refresh, [MACH_MESSAGE] = event_mach_message, [HOTLOAD] = event_hotload, [SPACE_WINDOWS_CHANGED] = event_space_windows_changed, }; void event_post(struct event *event) { if (event->type == EVENT_TYPE_UNKNOWN) return; static bool initialized = false; static pthread_mutex_t event_mutex; if (!initialized && event->type == INIT_MUTEX) { pthread_mutexattr_t mattr; pthread_mutexattr_init(&mattr); pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&event_mutex, &mattr); initialized = true; return; } else if (event->type == INIT_MUTEX) { error("Trying to reinitialize the event mutex! abort..\n"); } else if (!initialized) error("The event mutex is not ready! abort..\n"); if (event->type == ANIMATOR_REFRESH) { // We try to lock the mutex up to 1ms and then concede (skip the frame) to // avoid deadlocking occuring due to the CVDisplayLink. int locked; for (int i = 0; i < 10; i++) { if ((locked = pthread_mutex_trylock(&event_mutex)) == 0) break; usleep(100); } if (locked != 0) return; } else { pthread_mutex_lock(&event_mutex); if (g_space_management_mode != 1) { bar_manager_poll_active_display(&g_bar_manager); } } event_handler[event->type](event->context); windows_unfreeze(); pthread_mutex_unlock(&event_mutex); } ================================================ FILE: src/event.h ================================================ #pragma once #include "bar_manager.h" #include "message.h" enum event_type { EVENT_TYPE_UNKNOWN, APPLICATION_FRONT_SWITCHED, SPACE_CHANGED, DISPLAY_ADDED, DISPLAY_REMOVED, DISPLAY_MOVED, DISPLAY_RESIZED, DISPLAY_CHANGED, MENU_BAR_HIDDEN_CHANGED, SYSTEM_WOKE, SYSTEM_WILL_SLEEP, SHELL_REFRESH, ANIMATOR_REFRESH, MACH_MESSAGE, MOUSE_UP, MOUSE_DRAGGED, MOUSE_ENTERED, MOUSE_EXITED, MOUSE_SCROLLED, VOLUME_CHANGED, WIFI_CHANGED, BRIGHTNESS_CHANGED, POWER_SOURCE_CHANGED, MEDIA_CHANGED, COVER_CHANGED, SPACE_WINDOWS_CHANGED, DISTRIBUTED_NOTIFICATION, HOTLOAD, INIT_MUTEX, EVENT_TYPE_COUNT }; struct event { void* context; enum event_type type; }; void event_post(struct event *event); ================================================ FILE: src/font.c ================================================ #include "font.h" #include "animation.h" #include "bar_manager.h" struct feature_mapping { char opentype_tag[5]; int truetype_feature; int truetype_selector; }; // Non-exhaustive list of OpenType tags and corresponding TrueType features and selectors struct feature_mapping feature_mappings[] = { {"liga", kLigaturesType, kCommonLigaturesOnSelector}, {"dlig", kLigaturesType, kRareLigaturesOnSelector}, {"tnum", kNumberSpacingType, kMonospacedNumbersSelector}, {"pnum", kNumberSpacingType, kProportionalNumbersSelector}, {"smcp", kLowerCaseType, kLowerCaseSmallCapsSelector}, {"c2sc", kUpperCaseType, kUpperCaseSmallCapsSelector}, {"onum", kNumberCaseType, kLowerCaseNumbersSelector}, {"lnum", kNumberCaseType, kUpperCaseNumbersSelector}, {"afrc", kFractionsType, kVerticalFractionsSelector}, {"frac", kFractionsType, kDiagonalFractionsSelector}, {"subs", kVerticalPositionType, kInferiorsSelector}, {"sups", kVerticalPositionType, kSuperiorsSelector}, {"zero", kTypographicExtrasType, kSlashedZeroOnSelector}, {"swsh", kContextualAlternatesType, kSwashAlternatesOnSelector}, {"cswh", kContextualAlternatesType, kContextualSwashAlternatesOnSelector}, {"calt", kContextualAlternatesType, kContextualAlternatesOnSelector}, // kStylisticAlternativesType = 35 {"salt", 35, 2}, {"ss01", 35, 2}, {"ss02", 35, 4}, {"ss03", 35, 6}, {"ss04", 35, 8}, {"ss05", 35, 10}, {"ss06", 35, 12}, {"ss07", 35, 14}, {"ss08", 35, 16}, {"ss09", 35, 18}, {"ss10", 35, 20}, {"ss11", 35, 22}, {"ss12", 35, 24}, {"ss13", 35, 26}, {"ss14", 35, 28}, {"ss15", 35, 30}, {"ss16", 35, 32}, {"ss17", 35, 34}, {"ss18", 35, 36}, {"ss19", 35, 38}, {"ss20", 35, 40}, {"", 0, 0} }; void get_truetype_feature(const char* opentype_tag, int* truetype_feature, int* truetype_selector) { for (int i = 0; feature_mappings[i].opentype_tag[0] != '\0'; ++i) { if (strcmp(feature_mappings[i].opentype_tag, opentype_tag) == 0) { *truetype_feature = feature_mappings[i].truetype_feature; *truetype_selector = feature_mappings[i].truetype_selector; return; } } } void font_register(char* font_path) { CFStringRef url_string = CFStringCreateWithCString(kCFAllocatorDefault, font_path, kCFStringEncodingUTF8); if (url_string) { CFURLRef url_ref = CFURLCreateWithString(kCFAllocatorDefault, url_string, NULL ); if (url_ref) { CTFontManagerRegisterFontsForURL(url_ref, kCTFontManagerScopeProcess, NULL ); CFRelease(url_ref); } CFRelease(url_string); } free(font_path); } void font_create_ctfont(struct font* font) { CFStringRef family_ref = CFStringCreateWithCString(NULL, font->family, kCFStringEncodingUTF8); CFStringRef style_ref = CFStringCreateWithCString(NULL, font->style, kCFStringEncodingUTF8); CFNumberRef size_ref = CFNumberCreate(NULL, kCFNumberFloat32Type, &font->size ); const void *keys[] = { kCTFontFamilyNameAttribute, kCTFontStyleNameAttribute, kCTFontSizeAttribute }; const void *values[] = { family_ref, style_ref, size_ref }; CFDictionaryRef attr = CFDictionaryCreate(NULL, keys, values, array_count(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes(attr); if (font->ct_font) CFRelease(font->ct_font); if (font->features) { char* features_copy = string_copy(font->features); char* feature = strtok(features_copy, ","); while (feature) { int feature_name = 0; int feature_selector = 0; bool valid_feature = false; if (sscanf(feature, "%d:%d", &feature_name, &feature_selector) == 2) { valid_feature = true; } else if (strlen(feature) == 4) { get_truetype_feature(feature, &feature_name, &feature_selector); if (feature_name != 0) { valid_feature = true; } } if (valid_feature) { CFNumberRef name = CFNumberCreate(NULL, kCFNumberIntType, &feature_name); CFNumberRef value = CFNumberCreate(NULL, kCFNumberIntType, &feature_selector); CTFontDescriptorRef new_descriptor = CTFontDescriptorCreateCopyWithFeature(descriptor, name, value); CFRelease(descriptor); descriptor = new_descriptor; CFRelease(name); CFRelease(value); } feature = strtok(NULL, ","); } free(features_copy); } font->ct_font = CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL); CFRelease(descriptor); CFRelease(attr); CFRelease(size_ref); CFRelease(style_ref); CFRelease(family_ref); } void font_init(struct font* font) { font->size = 14.f; font->style = string_copy("Bold"); font->family = string_copy("Hack Nerd Font"); font_create_ctfont(font); } bool font_set_style(struct font* font, char* style, bool forced) { if (!style) return false; if (!forced && font->style && string_equals(font->style, style)) { free(style); return false; } if (font->style && style != font->style) free(font->style); font->style = style; font->font_changed = true; return true; } bool font_set_family(struct font* font, char* family, bool forced) { if (!family) return false; if (!forced && font->family && string_equals(font->family, family)) { free(family); return false; } if (font->family) free(font->family); font->family = family; font->font_changed = true; return true; } bool font_set_size(struct font* font, float size) { if (font->size == size) return false; font->size = size; font->font_changed = true; return true; } bool font_set_features(struct font* font, char* features) { if (!features) return false; if (font->features && string_equals(font->features, features)) { free(features); return false; } if (font->features) free(font->features); font->features = features; font->font_changed = true; return true; } bool font_set(struct font* font, char* font_string, bool forced) { if (!font_string) return false; float size = 10.0f; char font_properties[2][255] = { {}, {} }; sscanf(font_string, "%254[^:]:%254[^:]:%f", font_properties[0], font_properties[1], &size ); free(font_string); bool change = font_set_family(font, string_copy(font_properties[0]), forced); change |= font_set_style(font, string_copy(font_properties[1]), forced); change |= font_set_size(font, size); return change; } void font_clear_pointers(struct font* font) { font->ct_font = NULL; font->family = NULL; font->style = NULL; font->features = NULL; } void font_destroy(struct font* font) { if (font->style) free(font->style); if (font->family) free(font->family); if (font->features) free(font->features); if (font->ct_font) CFRelease(font->ct_font); font_clear_pointers(font); } bool font_parse_sub_domain(struct font* font, FILE* rsp, struct token property, char* message) { bool needs_refresh = false; if (token_equals(property, PROPERTY_FONT_SIZE)) { struct token token = get_token(&message); ANIMATE_FLOAT(font_set_size, font, font->size, token_to_float(token)); } else if (token_equals(property, PROPERTY_FONT_FAMILY)) { struct token token = get_token(&message); needs_refresh = font_set_family(font, token_to_string(token), false); } else if (token_equals(property, PROPERTY_FONT_STYLE)) { struct token token = get_token(&message); needs_refresh = font_set_style(font, token_to_string(token), false); } else if (token_equals(property, PROPERTY_FONT_FEATURES)) { struct token token = get_token(&message); needs_refresh = font_set_features(font, token_to_string(token)); } else { respond(rsp, "[!] Text: Invalid property '%s'\n", property.text); } return needs_refresh; } ================================================ FILE: src/font.h ================================================ #pragma once #include #include "misc/helpers.h" struct font { CTFontRef ct_font; bool font_changed; float size; char* family; char* style; char* features; }; void font_register(char* font_path); void font_init(struct font* font); void font_destroy(struct font* font); bool font_set(struct font* font, char* font_string, bool forced); bool font_set_size(struct font* font, float size); bool font_set_family(struct font* font, char* family, bool forced); bool font_set_style(struct font* font, char* style, bool forced); void font_create_ctfont(struct font* font); void font_clear_pointers(struct font* font); bool font_parse_sub_domain(struct font* font, FILE* rsp, struct token property, char* message); ================================================ FILE: src/graph.c ================================================ #include "graph.h" void graph_init(struct graph* graph) { graph->width = 0; graph->cursor = 0; graph->line_width = 0.5; graph->fill = true; graph->overrides_fill_color = false; graph->enabled = true; color_init(&graph->line_color, 0xffcccccc); color_init(&graph->fill_color, 0xffcccccc); } void graph_setup(struct graph* graph, uint32_t width) { graph->width = width; graph->y = malloc(sizeof(float) * width); memset(graph->y, 0, sizeof(float) * width); } float graph_get_y(struct graph* graph, uint32_t i) { if (!graph->enabled) return 0.f; return graph->y[ (graph->cursor + i)%graph->width ]; } void graph_push_back(struct graph* graph, float y) { if (!graph->enabled) return; graph->y[graph->cursor] = y; ++graph->cursor; graph->cursor %= graph->width; } uint32_t graph_get_length(struct graph* graph) { if (graph->enabled) return graph->width; return 0; } void graph_calculate_bounds(struct graph* graph, uint32_t x, uint32_t y, uint32_t height) { graph->bounds.size.height = height; graph->bounds.origin.x = x; graph->bounds.origin.y = y - graph->bounds.size.height / 2 + graph->line_width; } void graph_draw(struct graph* graph, CGContextRef context) { uint32_t x = graph->bounds.origin.x + (graph->rtl ? graph->width : 0); uint32_t y = graph->bounds.origin.y; uint32_t height = graph->bounds.size.height; uint32_t sample_width = 1; bool fill = graph->fill; CGContextSaveGState(context); CGContextSetRGBStrokeColor(context, graph->line_color.r, graph->line_color.g, graph->line_color.b, graph->line_color.a ); if (graph->overrides_fill_color) CGContextSetRGBFillColor(context, graph->fill_color.r, graph->fill_color.g, graph->fill_color.b, graph->fill_color.a ); else CGContextSetRGBFillColor(context, graph->line_color.r, graph->line_color.g, graph->line_color.b, 0.2 * graph->line_color.a); CGContextSetLineWidth(context, graph->line_width); CGMutablePathRef p = CGPathCreateMutable(); uint32_t start_x = x; if (graph->rtl) { CGPathMoveToPoint(p, NULL, x, y + graph_get_y(graph, graph->width - 1) * height); for (int i = graph->width - 1; i > 0; --i, x -= sample_width) { CGPathAddLineToPoint(p, NULL, x, y + graph_get_y(graph, i) * height); } } else { CGPathMoveToPoint(p, NULL, x, y + graph_get_y(graph, 0) * height); for (int i = graph->width - 1; i > 0; --i, x += sample_width) { CGPathAddLineToPoint(p, NULL, x, y + graph_get_y(graph, i) * height); } } CGContextAddPath(context, p); CGContextStrokePath(context); if (fill) { if (graph->rtl) { CGPathAddLineToPoint(p, NULL, x + sample_width, y); } else { CGPathAddLineToPoint(p, NULL, x - sample_width, y); } CGPathAddLineToPoint(p, NULL, start_x, y); CGPathCloseSubpath(p); CGContextAddPath(context, p); CGContextFillPath(context); } CGPathRelease(p); CGContextRestoreGState(context); } void graph_serialize(struct graph* graph, char* indent, FILE* rsp) { fprintf(rsp, "%s\"color\": \"0x%x\",\n" "%s\"fill_color\": \"0x%x\",\n" "%s\"line_width\": \"%f\",\n" "%s\"data\": [\n", indent, graph->line_color.hex, indent, graph->fill_color.hex, indent, graph->line_width, indent); int counter = 0; for (int i = 0; i < graph->width; i++) { if (counter++ > 0) fprintf(rsp, ",\n"); fprintf(rsp, "%s\t\"%f\"", indent, graph->y[i]); } fprintf(rsp, "\n%s]", indent); } void graph_destroy(struct graph* graph) { if (!graph->enabled) return; if (graph->y) free(graph->y); graph->y = NULL; } bool graph_parse_sub_domain(struct graph* graph, FILE* rsp, struct token property, char* message) { if (token_equals(property, PROPERTY_COLOR)) { return color_set_hex(&graph->line_color, token_to_uint32t(get_token(&message))); } else if (token_equals(property, PROPERTY_FILL_COLOR)) { graph->overrides_fill_color = true; return color_set_hex(&graph->fill_color, token_to_uint32t(get_token(&message))); } else if (token_equals(property, PROPERTY_LINE_WIDTH)) { graph->line_width = token_to_float(get_token(&message)); return true; } else { struct key_value_pair key_value_pair = get_key_value_pair(property.text, '.' ); if (key_value_pair.key && key_value_pair.value) { struct token subdom = {key_value_pair.key,strlen(key_value_pair.key)}; struct token entry = {key_value_pair.value,strlen(key_value_pair.value)}; if (token_equals(subdom, SUB_DOMAIN_COLOR)) { return color_parse_sub_domain(&graph->line_color, rsp, entry, message); } else if (token_equals(subdom, SUB_DOMAIN_FILL_COLOR)) { return color_parse_sub_domain(&graph->fill_color, rsp, entry, message); } else { respond(rsp, "[!] Graph: Invalid subdomain '%s'\n", subdom.text); } } else { respond(rsp, "[!] Graph: Invalid property '%s'\n", property.text); } } return false; } ================================================ FILE: src/graph.h ================================================ #pragma once #include "misc/helpers.h" #include "color.h" struct graph { bool rtl; bool fill; bool enabled; bool overrides_fill_color; float* y; uint32_t width; uint32_t cursor; float line_width; CGRect bounds; struct color line_color; struct color fill_color; }; void graph_init(struct graph* graph); void graph_setup(struct graph* graph, uint32_t width); void graph_push_back(struct graph* graph, float y); float graph_get_y(struct graph* graph, uint32_t i); uint32_t graph_get_length(struct graph* graph); void graph_calculate_bounds(struct graph* graph, uint32_t x, uint32_t y, uint32_t height); void graph_draw(struct graph* graph, CGContextRef context); void graph_destroy(struct graph* graph); void graph_serialize(struct graph* graph, char* indent, FILE* rsp); bool graph_parse_sub_domain(struct graph* graph, FILE* rsp, struct token property, char* message); ================================================ FILE: src/group.c ================================================ #include "group.h" #include "bar.h" static struct bar_item* group_get_first_member(struct group* group, struct bar* bar) { if (group->num_members == 1) return NULL; int min = INT32_MAX; struct bar_item* first_item = NULL; for (int i = 1; i < group->num_members; i++) { struct bar_item* member = group->members[i]; if (bar_draws_item(bar, member)) { struct window* window = bar_item_get_window(member, bar->adid); if (window->origin.x < min) { min = window->origin.x; first_item = member; } } } return first_item; } static struct bar_item* group_get_last_member(struct group* group, struct bar* bar) { if (group->num_members == 1) return NULL; int max = INT32_MIN; struct bar_item* last_item = NULL; for (int i = 1; i < group->num_members; i++) { struct bar_item* member = group->members[i]; if (bar_draws_item(bar, member)) { struct window* window = bar_item_get_window(member, bar->adid); if (window->origin.x + window->frame.size.width > max) { max = window->origin.x + window->frame.size.width; last_item = member; } } } return last_item; } struct group* group_create() { struct group* group = malloc(sizeof(struct group)); memset(group, 0, sizeof(struct group)); return group; } void group_init(struct group* group) { group->num_members = 0; group->members = NULL; } bool group_is_item_member(struct group* group, struct bar_item* item) { for (uint32_t i = 0; i < group->num_members; i++) { if (group->members[i] == item) return true; } return false; } void group_add_member(struct group* group, struct bar_item* item) { if (group_is_item_member(group, item)) return; if (item->group && item->group->members && item->group->members[0] == item) { for (int i = 1; i < item->group->num_members; i++) { group_add_member(group, item->group->members[i]); } } else { group->num_members++; group->members = realloc(group->members, sizeof(struct bar_item*)*group->num_members); group->members[group->num_members - 1] = item; item->group = group; } } uint32_t group_get_length(struct group* group, struct bar* bar) { int len = group->last_window->origin.x + group->last_window->frame.size.width + group->last_item->background.padding_right + group->first_item->background.padding_left - group->first_window->origin.x; return max(len, 0); } void group_remove_member(struct group* group, struct bar_item* bar_item) { if (group->num_members <= 0) return; struct bar_item* tmp[group->num_members - 1]; int count = 0; for (int i = 0; i < group->num_members; i++) { if (group->members[i] == bar_item) continue; tmp[count++] = group->members[i]; } group->num_members--; group->members = realloc(group->members, sizeof(struct bar_item*)*group->num_members); memcpy(group->members, tmp, sizeof(struct bar_item*)*group->num_members); } void group_destroy(struct group* group) { for (int i = 0; i < group->num_members; i++) { group->members[i]->group = NULL; } if (group->members) free(group->members); free(group); } void group_calculate_bounds(struct group* group, struct bar* bar, uint32_t y) { group->first_item = group_get_first_member(group, bar); group->first_window = bar_item_get_window(group->first_item, bar->adid); group->last_item = group_get_last_member(group, bar); group->last_window = bar_item_get_window(group->last_item, bar->adid); if (!group->first_window || !group->last_window) { group->bounds.origin = g_nirvana; return; } uint32_t group_length = group_get_length(group, bar); CGPoint shadow_offsets = bar_item_calculate_shadow_offsets(group->members[0]); group->bounds = (CGRect){{group->first_window->origin.x - group->first_item->background.padding_left, group->first_window->origin.y}, {group_length + shadow_offsets.x + shadow_offsets.y, group->first_window->frame.size.height}}; background_calculate_bounds(&group->members[0]->background, max(shadow_offsets.x, 0), y + group->members[0]->y_offset, group_get_length(group, bar), group->members[0]->background.bounds.size.height); } void group_serialize(struct group* group, char* indent, FILE* rsp) { int counter = 0; for (int i = 1; i < group->num_members; i++) { if (!group->members[i]) continue; if (counter++ > 0) fprintf(rsp, ",\n"); fprintf(rsp, "%s\"%s\"", indent, group->members[i]->name); } } ================================================ FILE: src/group.h ================================================ #pragma once #include "bar_item.h" struct bar; struct group { CGRect bounds; struct window* first_window; struct window* last_window; struct bar_item* first_item; struct bar_item* last_item; uint32_t num_members; struct bar_item** members; }; struct group* group_create(); void group_init(struct group* group); void group_set_name(struct group* group, char* _name); void group_add_member(struct group* group, struct bar_item* item); void group_remove_member(struct group* group, struct bar_item* bar_item); uint32_t group_get_length(struct group* group, struct bar* bar); void group_calculate_bounds(struct group* group, struct bar* bar, uint32_t y); void group_destroy(struct group* group); void group_serialize(struct group* group, char* indent, FILE* rsp); ================================================ FILE: src/hotload.c ================================================ #include "bar_manager.h" #include "event.h" #include #include extern char g_config_file[4096]; extern char g_name[256]; bool g_hotload = false; int64_t g_last_hotload = 0; void hotload_set_state(int state) { g_hotload = state; } int hotload_get_state() { return g_hotload; } bool set_config_file_path(char* file) { char* path = realpath(file, NULL); if (path) { snprintf(g_config_file, sizeof(g_config_file), "%s", path); free(path); return true; } return false; } static bool get_config_file(char *restrict filename, char *restrict buffer, int buffer_size) { char *xdg_home = getenv("XDG_CONFIG_HOME"); if (xdg_home && *xdg_home) { snprintf(buffer, buffer_size, "%s/%s/%s", xdg_home, g_name, filename); if (file_exists(buffer)) return true; } char *home = getenv("HOME"); if (!home) return false; snprintf(buffer, buffer_size, "%s/.config/%s/%s", home, g_name, filename); if (file_exists(buffer)) return true; snprintf(buffer, buffer_size, "%s/.%s", home, filename); return file_exists(buffer); } void exec_config_file() { if (!*g_config_file && !get_config_file("sketchybarrc", g_config_file, sizeof(g_config_file))) { printf("could not locate config file..\n"); return; } if (!file_exists(g_config_file)) { printf("file '%s' does not exist..\n", g_config_file); return; } setenv("CONFIG_DIR", dirname(g_config_file), 1); chdir(dirname(g_config_file)); if (!ensure_executable_permission(g_config_file)) { printf("could not set the executable permission bit for '%s'\n", g_config_file); return; } if (!fork_exec(g_config_file, NULL)) { printf("failed to execute file '%s'\n", g_config_file); return; } } static void handler(ConstFSEventStreamRef stream, void* context, size_t count, void* paths, const FSEventStreamEventFlags* flags, const FSEventStreamEventId* ids) { if (g_hotload && count > 0) { // Limit the hotload rate to avoid locking up the system on a hotload loop int64_t time = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW_APPROX); if (time - g_last_hotload > (1ULL << 30)) { g_last_hotload = time; struct event event = { NULL, HOTLOAD }; event_post(&event); } } } int begin_receiving_config_change_events() { char* file = dirname(g_config_file); CFStringRef file_ref = CFStringCreateWithCString( kCFAllocatorDefault, file, kCFStringEncodingUTF8); CFArrayRef paths = CFArrayCreate(NULL, (const void**)&file_ref, 1, &kCFTypeArrayCallBacks); FSEventStreamRef stream = FSEventStreamCreate( kCFAllocatorDefault, handler, NULL, paths, kFSEventStreamEventIdSinceNow, 0.5, kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents); CFRelease(file_ref); CFRelease(paths); FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); FSEventStreamStart(stream); return 0; } ================================================ FILE: src/hotload.h ================================================ #include #define HOTLOAD_STATE_ENABLED true #define HOTLOAD_STATE_DISABLED false void exec_config_file(); void begin_receiving_config_change_events(); void hotload_set_state(int state); int hotload_get_state(); bool set_config_file_path(char* file); ================================================ FILE: src/image.c ================================================ #include "image.h" #include "misc/helpers.h" #include "shadow.h" #include "workspace.h" #include "media.h" void image_init(struct image* image) { image->enabled = false; image->image_ref = NULL; image->data_ref = NULL; image->bounds = CGRectNull; image->size = CGSizeZero; image->scale = 1.0; image->path = NULL; image->corner_radius = 0; image->border_width = 0; image->y_offset = 0; image->padding_left = 0; image->padding_right = 0; image->link = NULL; shadow_init(&image->shadow); color_init(&image->border_color, 0xcccccccc); } bool image_set_enabled(struct image* image, bool enabled) { if (image->enabled == enabled) return false; image->enabled = enabled; return true; } bool image_set_link(struct image* image, struct image* link) { if (image->link == link) return false; image->link = link; if (link) image->enabled = true; return true; } bool image_load(struct image* image, char* path, FILE* rsp) { if (!path) return false; char* app = string_copy(path); if (image->path) free(image->path); image->path = string_copy(path); char* res_path = resolve_path(path); CGImageRef new_image_ref = NULL; float scale = 1.f; struct key_value_pair app_kv = get_key_value_pair(app, '.'); if (app_kv.key && app_kv.value && strcmp(app_kv.key, "app") == 0) { CGImageRef app_icon = workspace_icon_for_app(app_kv.value); scale = workspace_get_scale(); scale *= scale; if (app_icon) new_image_ref = app_icon; else { respond(rsp, "[!] Image: Invalid application name: '%s'\n", app_kv.value); free(res_path); free(app); return false; } } else if (app_kv.key && app_kv.value && strcmp(app_kv.key, "space") == 0) { uint32_t sid = atoi(app_kv.value); CGImageRef space_img = space_capture(sid); if (space_img) new_image_ref = space_img; else { respond(rsp, "[!] Image: Invalid Space ID: '%s'\n", app_kv.value); free(res_path); free(app); return false; } } else if (strcmp(path, "media.artwork") == 0) { free(res_path); free(app); begin_receiving_media_events(); return image_set_link(image, &g_bar_manager.current_artwork); } else if (file_exists(res_path)) { CGDataProviderRef data_provider = CGDataProviderCreateWithFilename(res_path); if (data_provider) { if (strlen(res_path) > 3 && string_equals(&res_path[strlen(res_path) - 4], ".png")) new_image_ref = CGImageCreateWithPNGDataProvider(data_provider, NULL, false, kCGRenderingIntentDefault); else { new_image_ref = CGImageCreateWithJPEGDataProvider(data_provider, NULL, false, kCGRenderingIntentDefault); } CFRelease(data_provider); } else { respond(rsp, "[!] Image: Invalid Image Format: '%s'\n", app_kv.value); free(res_path); free(app); return false; } } else if (strlen(res_path) == 0) { image_destroy(image); free(res_path); free(app); return false; } else { respond(rsp, "[!] Image: File '%s' not found\n", res_path); free(res_path); free(app); return false; } if (new_image_ref) { image_set_image(image, new_image_ref, (CGRect){{0,0}, {CGImageGetWidth(new_image_ref) / scale, CGImageGetHeight(new_image_ref) / scale }}, true ); } else { if (new_image_ref) CFRelease(new_image_ref); printf("Could not open image file at: %s\n", res_path); fprintf(rsp, "Could not open image file at: %s\n", res_path); } free(res_path); free(app); return true; } static bool image_data_equals(struct image* image, CFDataRef new_data_ref) { bool equals = false; if (image->image_ref && image->data_ref) { uint32_t old_len = CFDataGetLength(image->data_ref); uint32_t new_len = CFDataGetLength(new_data_ref); if (old_len == new_len) equals = memcmp(CFDataGetBytePtr(image->data_ref), CFDataGetBytePtr(new_data_ref), old_len ) == 0; } return equals; } void image_copy(struct image* image, CGImageRef source) { if (source) image->image_ref = CGImageCreateCopy(source); } bool image_set_image(struct image* image, CGImageRef new_image_ref, CGRect bounds, bool forced) { if (!new_image_ref) { if (image->image_ref) CGImageRelease(image->image_ref); if (image->data_ref) CFRelease(image->data_ref); image->image_ref = NULL; image->data_ref = NULL; return false; } if (image->link) image_set_link(image, NULL); CFDataRef new_data_ref = CGDataProviderCopyData(CGImageGetDataProvider(new_image_ref)); if (!forced && image_data_equals(image, new_data_ref) && CGSizeEqualToSize(image->size, bounds.size) ) { CFRelease(new_data_ref); CGImageRelease(new_image_ref); return false; } if (image->image_ref) CGImageRelease(image->image_ref); if (image->data_ref) CFRelease(image->data_ref); image->size = bounds.size; image->bounds = (CGRect){{0, 0}, {bounds.size.width * image->scale, bounds.size.height * image->scale}}; image->image_ref = new_image_ref; image->data_ref = new_data_ref; image->enabled = true; return true; } bool image_set_scale(struct image* image, float scale) { if (scale == image->scale) return false; image->scale = scale; image->bounds = (CGRect){{image->bounds.origin.x, image->bounds.origin.y}, {image->size.width * image->scale, image->size.height * image->scale}}; return true; } bool image_set_corner_radius(struct image* image, uint32_t corner_radius) { if (image->corner_radius == corner_radius) return false; image->corner_radius = corner_radius; return true; } bool image_set_border_width(struct image* image, float border_width) { if (image->border_width == border_width) return false; image->border_width = border_width; return true; } bool image_set_border_color(struct image* image, uint32_t color) { return color_set_hex(&image->border_color, color); } bool image_set_padding_left(struct image* image, int padding_left) { if (image->padding_left == padding_left) return false; image->padding_left = padding_left; return true; } bool image_set_padding_right(struct image* image, int padding_right) { if (image->padding_right == padding_right) return false; image->padding_right = padding_right; return true; } bool image_set_yoffset(struct image* image, int yoffset) { if (image->y_offset == yoffset) return false; image->y_offset = yoffset; return true; } CGSize image_get_size(struct image* image) { return (CGSize){ .width = image->bounds.size.width + image->padding_left + image->padding_right + (image->shadow.enabled ? image->shadow.offset.x : 0), .height = image->bounds.size.height + 2*abs(image->y_offset) }; } void image_calculate_bounds(struct image* image, uint32_t x, uint32_t y) { if (image->link && image->link->image_ref) { float internal_scale = 32.f / CGImageGetHeight(image->link->image_ref); CGRect bounds = (CGRect){{0,0}, { CGImageGetWidth(image->link->image_ref) * internal_scale, 32.f }}; image->size = bounds.size; image->bounds = (CGRect){{0, 0}, {bounds.size.width * image->scale, bounds.size.height * image->scale}}; } image->bounds.origin.x = x + image->padding_left; image->bounds.origin.y = y - image->bounds.size.height / 2 + image->y_offset; } void image_draw(struct image* image, CGContextRef context) { if ((!image->link && !image->image_ref) || (image->link && !image->link->image_ref)) return; if (image->shadow.enabled) { CGContextSaveGState(context); CGRect sbounds = shadow_get_bounds(&image->shadow, image->bounds); CGContextSetRGBFillColor(context, image->shadow.color.r, image->shadow.color.g, image->shadow.color.b, image->shadow.color.a); CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRoundedRect(path, NULL, sbounds, image->corner_radius, image->corner_radius); CGContextAddPath(context, path); CGContextDrawPath(context, kCGPathFillStroke); CFRelease(path); CGContextRestoreGState(context); } CGContextSaveGState(context); if (image->bounds.size.height > 2*image->corner_radius && image->bounds.size.width > 2*image->corner_radius) { CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRoundedRect(path, NULL, image->bounds, image->corner_radius, image->corner_radius); CGContextAddPath(context, path); CGContextClip(context); CFRelease(path); } CGContextDrawImage(context, image->bounds, image->link ? image->link->image_ref : image->image_ref); if (image->bounds.size.height > 2*image->corner_radius && image->bounds.size.width > 2*image->corner_radius) { CGContextSetLineWidth(context, 2*image->border_width); CGContextSetRGBStrokeColor(context, image->border_color.r, image->border_color.g, image->border_color.b, image->border_color.a); CGContextSetRGBFillColor(context, 0, 0, 0, 0); CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRoundedRect(path, NULL, image->bounds, image->corner_radius, image->corner_radius); CGContextAddPath(context, path); CGContextDrawPath(context, kCGPathFillStroke); CFRelease(path); } CGContextRestoreGState(context); } void image_clear_pointers(struct image* image) { image->image_ref = NULL; image->data_ref = NULL; image->path = NULL; } void image_destroy(struct image* image) { CGImageRelease(image->image_ref); if (image->data_ref) CFRelease(image->data_ref); if (image->path) free(image->path); image_clear_pointers(image); } void image_serialize(struct image* image, char* indent, FILE* rsp) { fprintf(rsp, "%s\"value\": \"%s\",\n" "%s\"drawing\": \"%s\",\n" "%s\"scale\": %f", indent, image->path, indent, format_bool(image->enabled), indent, image->scale ); } bool image_parse_sub_domain(struct image* image, FILE* rsp, struct token property, char* message) { bool needs_refresh = false; if (token_equals(property, PROPERTY_STRING)) { return image_load(image, token_to_string(get_token(&message)), rsp); } else if (token_equals(property, PROPERTY_DRAWING)) { return image_set_enabled(image, evaluate_boolean_state(get_token(&message), image->enabled) ); } else if (token_equals(property, PROPERTY_SCALE)) { ANIMATE_FLOAT(image_set_scale, image, image->scale, token_to_float(get_token(&message))); } else if (token_equals(property, PROPERTY_CORNER_RADIUS)) { ANIMATE(image_set_corner_radius, image, image->corner_radius, token_to_uint32t(get_token(&message))); } else if (token_equals(property, PROPERTY_PADDING_LEFT)) { ANIMATE(image_set_padding_left, image, image->padding_left, token_to_int(get_token(&message))); } else if (token_equals(property, PROPERTY_PADDING_RIGHT)) { ANIMATE(image_set_padding_right, image, image->padding_right, token_to_int(get_token(&message))); } else if (token_equals(property, PROPERTY_YOFFSET)) { ANIMATE(image_set_yoffset, image, image->y_offset, token_to_int(get_token(&message))); } else if (token_equals(property, PROPERTY_BORDER_WIDTH)) { ANIMATE_FLOAT(image_set_border_width, image, image->border_width, token_to_float(get_token(&message))); } else if (token_equals(property, PROPERTY_BORDER_COLOR)) { struct token token = get_token(&message); ANIMATE_BYTES(image_set_border_color, image, image->border_color.hex, token_to_int(token)); } else { struct key_value_pair key_value_pair = get_key_value_pair(property.text, '.' ); if (key_value_pair.key && key_value_pair.value) { struct token subdom = {key_value_pair.key,strlen(key_value_pair.key)}; struct token entry = {key_value_pair.value,strlen(key_value_pair.value)}; if (token_equals(subdom, SUB_DOMAIN_BORDER_COLOR)) { return color_parse_sub_domain(&image->border_color, rsp, entry, message); } else if (token_equals(subdom, SUB_DOMAIN_SHADOW)) { return shadow_parse_sub_domain(&image->shadow, rsp, entry, message ); } else { respond(rsp, "[?] Image: Invalid subdomain: %s \n", property.text); } } else { respond(rsp, "[?] Image: Unknown property: %s \n", property.text); } } return needs_refresh; } ================================================ FILE: src/image.h ================================================ #pragma once #include "shadow.h" #include "misc/defines.h" extern CGImageRef workspace_icon_for_app(char* app); struct image { bool enabled; float scale; CGSize size; CGRect bounds; char* path; CGImageRef image_ref; CFDataRef data_ref; struct shadow shadow; struct color border_color; float border_width; uint32_t corner_radius; int padding_left; int padding_right; int y_offset; struct image* link; }; void image_init(struct image* image); bool image_set_enabled(struct image* image, bool enabled); void image_copy(struct image* image, CGImageRef source); bool image_set_image(struct image* image, CGImageRef new_image_ref, CGRect bounds, bool forced); bool image_load(struct image* image, char* path, FILE* rsp); bool image_set_scale(struct image* image, float scale); CGSize image_get_size(struct image* image); void image_calculate_bounds(struct image* image, uint32_t x, uint32_t y); void image_draw(struct image* image, CGContextRef context); void image_clear_pointers(struct image* image); void image_destroy(struct image* image); void image_serialize(struct image* image, char* indent, FILE* rsp); bool image_parse_sub_domain(struct image* image, FILE* rsp, struct token property, char* message); ================================================ FILE: src/mach.c ================================================ #include "mach.h" #include #include #include #include mach_port_t mach_get_bs_port(char* bs_name) { mach_port_name_t task = mach_task_self(); mach_port_t bs_port; if (task_get_special_port(task, TASK_BOOTSTRAP_PORT, &bs_port ) != KERN_SUCCESS) { return 0; } mach_port_t port; if (bootstrap_look_up(bs_port, bs_name, &port ) != KERN_SUCCESS) { return 0; } return port; } void mach_receive_message(mach_port_t port, struct mach_buffer* buffer, bool timeout) { *buffer = (struct mach_buffer) { 0 }; mach_msg_return_t msg_return; if (timeout) msg_return = mach_msg(&buffer->message.header, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, sizeof(struct mach_buffer), port, 100, MACH_PORT_NULL ); else msg_return = mach_msg(&buffer->message.header, MACH_RCV_MSG, 0, sizeof(struct mach_buffer), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL ); if (msg_return != MACH_MSG_SUCCESS) { buffer->message.descriptor.address = NULL; } } char* mach_send_message(mach_port_t port, char* message, uint32_t len, bool await_response) { if (!message || !port) return NULL; mach_port_t response_port; mach_port_name_t task = mach_task_self(); if (await_response) { if (mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &response_port ) != KERN_SUCCESS) { return NULL; } if (mach_port_insert_right(task, response_port, response_port, MACH_MSG_TYPE_MAKE_SEND)!= KERN_SUCCESS) { return NULL; } } struct mach_message msg = { 0 }; msg.header.msgh_remote_port = port; if (await_response) { msg.header.msgh_local_port = response_port; msg.header.msgh_id = response_port; msg.header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND, 0, MACH_MSGH_BITS_COMPLEX ); } else { msg.header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND & MACH_MSGH_BITS_REMOTE_MASK, 0, 0, MACH_MSGH_BITS_COMPLEX ); } msg.header.msgh_size = sizeof(struct mach_message); msg.msgh_descriptor_count = 1; msg.descriptor.address = message; msg.descriptor.size = len * sizeof(char); msg.descriptor.copy = MACH_MSG_VIRTUAL_COPY; msg.descriptor.deallocate = false; msg.descriptor.type = MACH_MSG_OOL_DESCRIPTOR; mach_msg(&msg.header, MACH_SEND_MSG, sizeof(struct mach_message), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL ); if (await_response) { struct mach_buffer buffer = { 0 }; mach_receive_message(response_port, &buffer, true); char* rsp = NULL; if (buffer.message.descriptor.address) { rsp = malloc(strlen(buffer.message.descriptor.address) + 1); memcpy(rsp, buffer.message.descriptor.address, strlen(buffer.message.descriptor.address) + 1); } else { rsp = malloc(1); *rsp = '\0'; } mach_msg_destroy(&buffer.message.header); mach_port_mod_refs(task, response_port, MACH_PORT_RIGHT_RECEIVE, -1); mach_port_deallocate(task, response_port); return rsp; } return NULL; } void mach_message_callback(CFMachPortRef port, void* message, CFIndex size, void* context) { struct mach_server* mach_server = context; struct mach_buffer buffer; buffer.message = *(struct mach_message*)message; mach_server->handler(&buffer); mach_msg_destroy(&buffer.message.header); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" extern char g_name[256]; bool mach_server_begin(struct mach_server* mach_server, mach_handler handler) { mach_server->task = mach_task_self(); if (mach_port_allocate(mach_server->task, MACH_PORT_RIGHT_RECEIVE, &mach_server->port ) != KERN_SUCCESS) { return false; } struct mach_port_limits limits = {}; limits.mpl_qlimit = MACH_PORT_QLIMIT_LARGE; if (mach_port_set_attributes(mach_server->task, mach_server->port, MACH_PORT_LIMITS_INFO, (mach_port_info_t)&limits, MACH_PORT_LIMITS_INFO_COUNT) != KERN_SUCCESS) { return false; } if (mach_port_insert_right(mach_server->task, mach_server->port, mach_server->port, MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS) { return false; } if (task_get_special_port(mach_server->task, TASK_BOOTSTRAP_PORT, &mach_server->bs_port) != KERN_SUCCESS) { return false; } char bs_name[256]; snprintf(bs_name, 256, MACH_BS_NAME_FMT, g_name); if (bootstrap_register(mach_server->bs_port, bs_name, mach_server->port ) != KERN_SUCCESS) { return false; } mach_server->handler = handler; mach_server->is_running = true; CFMachPortContext context = {0, (void*)mach_server}; CFMachPortRef cf_mach_port = CFMachPortCreateWithPort(NULL, mach_server->port, mach_message_callback, &context, false ); CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource(NULL, cf_mach_port, 0 ); CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode); CFRelease(source); CFRelease(cf_mach_port); return true; } #pragma clang diagnostic pop ================================================ FILE: src/mach.h ================================================ #pragma once #include #include #include #include #include #include #define MACH_BS_NAME_FMT "git.felix.%s" struct mach_message { mach_msg_header_t header; mach_msg_size_t msgh_descriptor_count; mach_msg_ool_descriptor_t descriptor; }; struct mach_buffer { struct mach_message message; mach_msg_trailer_t trailer; }; #define MACH_HANDLER(name) void name(struct mach_buffer* message) typedef MACH_HANDLER(mach_handler); struct mach_server { bool is_running; mach_port_name_t task; mach_port_t port; mach_port_t bs_port; mach_handler* handler; }; bool mach_server_begin(struct mach_server* mach_server, mach_handler handler); char* mach_send_message(mach_port_t port, char* message, uint32_t len, bool await_response); mach_port_t mach_get_bs_port(char* bs_name); ================================================ FILE: src/media.h ================================================ void initialize_media_events(); void begin_receiving_media_events(); void forced_media_change_event(); ================================================ FILE: src/media.m ================================================ #include "event.h" #include // The media remote private framework was locked for use on macOS 15.3. extern void MRMediaRemoteRegisterForNowPlayingNotifications(dispatch_queue_t queue); extern void MRMediaRemoteGetNowPlayingInfo(dispatch_queue_t queue, void (^block)(NSDictionary* dict)); extern void MRMediaRemoteGetNowPlayingApplicationIsPlaying(dispatch_queue_t queue, void (^block)(BOOL playing)); extern void MRMediaRemoteGetNowPlayingApplicationDisplayName(int null, dispatch_queue_t queue, void (^block)(CFStringRef name)); extern NSString* kMRMediaRemoteNowPlayingApplicationIsPlayingDidChangeNotification; extern NSString* kMRMediaRemoteNowPlayingInfoDidChangeNotification; extern NSString* kMRMediaRemoteNowPlayingApplicationDidChangeNotification; extern NSString* kMRMediaRemoteNowPlayingInfoAlbum; extern NSString* kMRMediaRemoteNowPlayingInfoArtist; extern NSString* kMRMediaRemoteNowPlayingInfoTitle; extern NSString* kMRMediaRemoteNowPlayingInfoArtworkMIMEType; extern NSString* kMRMediaRemoteNowPlayingInfoArtworkData; extern NSString* kMRMediaRemoteNowPlayingApplicationDisplayNameUserInfoKey; @interface media_context : NSObject {} @property const char* app; @property const char* artist; @property const char* title; @property const char* album; @property CGImageRef artwork; @property BOOL playing; - (id)init; @end @implementation media_context - (id)init { if ((self = [super init])) { [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(media_change:) name:kMRMediaRemoteNowPlayingInfoDidChangeNotification object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(playing_change:) name:kMRMediaRemoteNowPlayingApplicationIsPlayingDidChangeNotification object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(media_change:) name:kMRMediaRemoteNowPlayingApplicationDidChangeNotification object:nil]; self.app = NULL; self.artist = NULL; self.title = NULL; self.album = NULL; } return self; } char* g_media_info = NULL; bool g_media_events = false; - (void) update { @autoreleasepool { if (self.app && self.artist && self.title && self.album) { char* escaped_artist = escape_string((char*)self.artist); char* escaped_title = escape_string((char*)self.title); char* escaped_album = escape_string((char*)self.album); uint32_t info_len = strlen(self.app) + strlen(escaped_artist) + strlen(escaped_title) + strlen(escaped_album) + 256; char info[info_len]; snprintf(info, info_len, "{\n" "\t\"state\": \"%s\",\n" "\t\"title\": \"%s\",\n" "\t\"album\": \"%s\",\n" "\t\"artist\": \"%s\",\n" "\t\"app\": \"%s\"\n}", self.playing ? "playing" : "paused", escaped_title, escaped_album, escaped_artist, self.app ); free(escaped_artist); free(escaped_title); free(escaped_album); if (self.artwork) { struct event cover_event = { self.artwork, COVER_CHANGED }; event_post(&cover_event); } if (!g_media_info || strcmp(info, g_media_info) != 0) { g_media_info = realloc(g_media_info, info_len); memcpy(g_media_info, info, info_len); char payload_info[info_len]; memcpy(payload_info, info, info_len); struct event event = { payload_info, MEDIA_CHANGED }; event_post(&event); } } } } - (void)playing_change:(NSNotification *)notification { if (!g_media_events) return; MRMediaRemoteGetNowPlayingApplicationIsPlaying(dispatch_get_main_queue(), ^(BOOL playing) { self.playing = playing; [self media_change:notification]; }); } - (void)media_change:(NSNotification *)notification { if (!g_media_events) return; MRMediaRemoteGetNowPlayingApplicationDisplayName(0, dispatch_get_main_queue(), ^(CFStringRef name_nr) { if (!name_nr) return; CFStringRef name = CFStringCreateCopy(CFAllocatorGetDefault(), name_nr); @autoreleasepool { MRMediaRemoteGetNowPlayingInfo(dispatch_get_main_queue(), ^(NSDictionary* dict) { @autoreleasepool { if (dict && name) { NSString* app = (NSString*)name; NSString* artist = [dict objectForKey:kMRMediaRemoteNowPlayingInfoArtist]; NSString* title = [dict objectForKey:kMRMediaRemoteNowPlayingInfoTitle]; NSString* album = [dict objectForKey:kMRMediaRemoteNowPlayingInfoAlbum]; if (artist && title && album && name) { self.app = (char*)[app UTF8String]; self.artist = (char*)[artist UTF8String]; self.title = (char*)[title UTF8String]; self.album = (char*)[album UTF8String]; NSString* mime_type = [dict objectForKey:kMRMediaRemoteNowPlayingInfoArtworkMIMEType]; NSData* ns_data = [dict objectForKey:kMRMediaRemoteNowPlayingInfoArtworkData]; CGImageRef image = NULL; if (mime_type && ns_data) { CFDataRef data = CFDataCreate(NULL, [ns_data bytes], [ns_data length]); CGDataProviderRef provider = CGDataProviderCreateWithCFData(data); if (provider) { CGImageSourceRef source = CGImageSourceCreateWithDataProvider(provider, NULL); if (source) { image = CGImageSourceCreateImageAtIndex(source, 0, NULL); CFRelease(source); } CFRelease(provider); } CFRelease(data); } self.artwork = image; [self update]; } } } CFRelease(name); }); } }); } @end media_context* g_media_context = NULL; void initialize_media_events() { MRMediaRemoteRegisterForNowPlayingNotifications(dispatch_get_main_queue()); g_media_context = [media_context alloc]; [g_media_context init]; } void begin_receiving_media_events() { g_media_events = true; } void forced_media_change_event() { if (g_media_info) free(g_media_info); g_media_info = NULL; [g_media_context playing_change:NULL]; } ================================================ FILE: src/message.c ================================================ #include "message.h" #include "app_windows.h" #include "bar_manager.h" #include "misc/defines.h" #include "hotload.h" #include "misc/helpers.h" #include "volume.h" #include "media.h" #include "wifi.h" #include "power.h" extern struct bar_manager g_bar_manager; static struct bar_item** get_bar_items_for_regex(struct token reg, FILE* rsp, uint32_t* count) { struct bar_item** bar_items = NULL; char* regstring = malloc(sizeof(char)*(reg.length - 1)); memcpy(regstring, ®.text[1], reg.length - 2); regstring[reg.length - 2] = '\0'; regex_t regex; int reti; reti = regcomp(®ex, regstring, 0); free(regstring); if (reti) { respond(rsp, "[!] Regex: Could not compile regex '%s'\n", reg.text); return NULL; } for (int i = 0; i < g_bar_manager.bar_item_count; i++) { struct bar_item* bar_item = g_bar_manager.bar_items[i]; reti = regexec(®ex, bar_item->name, 0, NULL, 0); if (!reti) { ++*count; bar_items = realloc(bar_items, sizeof(struct bar_item*)* *count); bar_items[*count - 1] = bar_item; } else if (reti != REG_NOMATCH) { char buf[1024]; regerror(reti, ®ex, buf, sizeof(buf)); respond(rsp, "[!] Regex: Regex match failed '%s'\n", buf); return NULL; } } if (!bar_items) { respond(rsp, "[?] Regex: No match found for regex '%s'\n", reg.text); } regfree(®ex); return bar_items; } static void handle_domain_subscribe(FILE* rsp, struct token domain, char* message) { struct token name = get_token(&message); int item_index_for_name = bar_manager_get_item_index_for_name(&g_bar_manager, name.text ); if (item_index_for_name < 0) { respond(rsp, "[!] Subscribe: Item not found '%s'\n", name.text); return; } struct bar_item* bar_item = g_bar_manager.bar_items[item_index_for_name]; bar_item_parse_subscribe_message(bar_item, message, rsp); } static void handle_domain_trigger(FILE* rsp, struct token domain, char* message) { struct token event = get_token(&message); struct env_vars env_vars; env_vars_init(&env_vars); struct token token = get_token(&message); while (token.text && token.length > 0) { struct key_value_pair key_value_pair = get_key_value_pair(token.text, '='); if (key_value_pair.key && key_value_pair.value) { env_vars_set(&env_vars, string_copy(key_value_pair.key), string_copy(key_value_pair.value)); } token = get_token(&message); } if (token_equals(event, COMMAND_SUBSCRIBE_SPACE_CHANGE)) { bar_manager_handle_space_change(&g_bar_manager, true); } else if (token_equals(event, COMMAND_SUBSCRIBE_DISPLAY_CHANGE)) { bar_manager_handle_display_change(&g_bar_manager); } else if (token_equals(event, COMMAND_SUBSCRIBE_SPACE_WINDOWS_CHANGE)) { forced_space_windows_event(); } else if (token_equals(event, COMMAND_SUBSCRIBE_VOLUME_CHANGE)) { forced_volume_event(); } else if (token_equals(event, COMMAND_SUBSCRIBE_MEDIA_CHANGE)) { forced_media_change_event(); } else if (token_equals(event, COMMAND_SUBSCRIBE_WIFI_CHANGE)) { forced_network_event(); } else if (token_equals(event, COMMAND_SUBSCRIBE_POWER_SOURCE_CHANGE)) { forced_power_event(); } else { bar_manager_custom_events_trigger(&g_bar_manager, event.text, &env_vars); } env_vars_destroy(&env_vars); } static void handle_domain_push(FILE* rsp, struct token domain, char* message) { struct token name = get_token(&message); int item_index_for_name = bar_manager_get_item_index_for_name(&g_bar_manager, name.text ); if (item_index_for_name < 0) { respond(rsp, "[!] Push: Item '%s' not found\n", name.text); return; } struct bar_item* bar_item = g_bar_manager.bar_items[item_index_for_name]; if (bar_item->type != BAR_COMPONENT_GRAPH) { respond(rsp, "[!] Push: Item '%s' not a graph\n", name.text); return; } struct token y = get_token(&message); while (y.text && y.length > 0) { graph_push_back(&bar_item->graph, token_to_float(y)); y = get_token(&message); } bar_item_needs_update(bar_item); } static void handle_domain_rename(FILE* rsp, struct token domain, char* message) { struct token old_name = get_token(&message); struct token new_name = get_token(&message); int item_index_for_old_name = bar_manager_get_item_index_for_name(&g_bar_manager, old_name.text); int item_index_for_new_name = bar_manager_get_item_index_for_name(&g_bar_manager, new_name.text); if (item_index_for_old_name < 0 || item_index_for_new_name >= 0) { respond(rsp, "[!] Rename: Failed to rename item: %s -> %s\n", old_name.text, new_name.text); return; } bar_item_set_name(g_bar_manager.bar_items[item_index_for_old_name], token_to_string(new_name) ); } static void handle_domain_clone(FILE* rsp, struct token domain, char* message) { struct token name = get_token(&message); struct token parent = get_token(&message); struct token modifier = get_token(&message); struct bar_item* parent_item = NULL; int parent_index = bar_manager_get_item_index_for_name(&g_bar_manager, parent.text ); if (parent_index >= 0) parent_item = g_bar_manager.bar_items[parent_index]; else { respond(rsp, "[!] Clone: Parent Item '%s' not found\n", parent.text); return; } if (bar_manager_get_item_index_for_name(&g_bar_manager, name.text) >= 0) { respond(rsp, "[?] Clone: Item '%s' already exists\n", name.text); return; } struct bar_item* bar_item = bar_manager_create_item(&g_bar_manager); bar_item_inherit_from_item(bar_item, parent_item); bar_item_set_name(bar_item, token_to_string(name)); if (token_equals(modifier, ARGUMENT_COMMON_VAL_BEFORE)) bar_manager_move_item(&g_bar_manager, bar_item, parent_item, true); else if (token_equals(modifier, ARGUMENT_COMMON_VAL_AFTER)) bar_manager_move_item(&g_bar_manager, bar_item, parent_item, false); bar_item_needs_update(bar_item); } static void handle_domain_add(FILE* rsp, struct token domain, char* message) { struct token command = get_token(&message); if (token_equals(command, COMMAND_ADD_EVENT)) { struct token event = get_token(&message); if (strlen(message) > 0) custom_events_append(&g_bar_manager.custom_events, token_to_string(event), token_to_string(get_token(&message))); else custom_events_append(&g_bar_manager.custom_events, token_to_string(event), NULL ); return; } struct token name = get_token(&message); struct token position = get_token(&message); if (bar_manager_get_item_index_for_name(&g_bar_manager, name.text) >= 0) { respond(rsp, "[?] Add: Item '%s' already exists\n", name.text); return; } struct bar_item* bar_item = bar_manager_create_item(&g_bar_manager); if (!bar_item_set_type(bar_item, command.text)) { respond(rsp, "[?] Add %s: Invalid type '%s', assuming 'item'\n", name.text, command.text ); } if (bar_item->type != BAR_COMPONENT_GROUP && !bar_item_set_position(bar_item, position.text)) { respond(rsp, "[!] Add %s: Illegal position '%s'\n", name.text, position.text); bar_manager_remove_item(&g_bar_manager, bar_item); return; } if (!bar_item_set_name(bar_item, token_to_string(name))) { respond(rsp, "[!] Add: Illegal name '%s'\n", name.text); bar_manager_remove_item(&g_bar_manager, bar_item); return; } if (token_equals(command, COMMAND_ADD_ITEM)) { } else if (command.length > 0) { if (bar_item->type == BAR_COMPONENT_GRAPH) { struct token width = get_token(&message); graph_setup(&bar_item->graph, token_to_uint32t(width)); } else if (bar_item->type == BAR_COMPONENT_SLIDER) { struct token width = get_token(&message); slider_setup(&bar_item->slider, token_to_uint32t(width)); } else if (bar_item->type == BAR_COMPONENT_ALIAS) { char* tmp_name = string_copy(name.text); struct key_value_pair key_value_pair = get_key_value_pair(tmp_name, ','); if (!key_value_pair.key || !key_value_pair.value) alias_setup(&bar_item->alias, token_to_string(name), NULL); else alias_setup(&bar_item->alias, string_copy(key_value_pair.key), string_copy(key_value_pair.value)); free(tmp_name); } else if (bar_item->type == BAR_COMPONENT_SLIDER) { char* tmp_name = string_copy(name.text); struct key_value_pair key_value_pair = get_key_value_pair(tmp_name, ','); if (!key_value_pair.key || !key_value_pair.value) alias_setup(&bar_item->alias, token_to_string(name), NULL); else alias_setup(&bar_item->alias, string_copy(key_value_pair.key), string_copy(key_value_pair.value)); free(tmp_name); } else if (bar_item->type == BAR_COMPONENT_GROUP) { struct token member = position; bool first = true; while (member.text && member.length > 0) { uint32_t count = 0; struct bar_item** bar_items = NULL; if (member.length > 1 && member.text[0] == REGEX_DELIMITER && member.text[member.length - 1] == REGEX_DELIMITER ) { bar_items = get_bar_items_for_regex(member, rsp, &count); } else { int index = bar_manager_get_item_index_for_name(&g_bar_manager, member.text ); if (index >= 0) { bar_items = realloc(bar_items, sizeof(struct bar_item*)); bar_items[0] = g_bar_manager.bar_items[index]; count = 1; } else { respond(rsp, "[?] Add (Group) %s: Failed to add member '%s', item not found\n", bar_item->name, member.text); } } if (bar_items && count > 0) { for (int i = 0; i < count; i++) { if (first) { if (bar_items[i]->position == POSITION_POPUP) { popup_add_item(&bar_items[i]->parent->popup, bar_item); bar_item->position = POSITION_POPUP; } first = false; } group_add_member(bar_item->group, bar_items[i]); } free(bar_items); } else if (first) { bar_manager_remove_item(&g_bar_manager, bar_item); break; } member = get_token(&message); } } } if (position.text[0] == POSITION_POPUP) { char* pair = string_copy(position.text); struct key_value_pair key_value_pair = get_key_value_pair(pair, '.'); if (key_value_pair.key && key_value_pair.value) { int item_index_for_name = bar_manager_get_item_index_for_name(&g_bar_manager, key_value_pair.value); if (item_index_for_name < 0) { respond(rsp, "[!] Add (Popup) %s: Item '%s' is not a valid popup host\n", bar_item->name, key_value_pair.value); free(pair); bar_manager_remove_item(&g_bar_manager, bar_item); return; } struct bar_item* target_item = g_bar_manager.bar_items[item_index_for_name]; popup_add_item(&target_item->popup, bar_item); } free(pair); } bar_item_needs_update(bar_item); } static void handle_domain_default(FILE* rsp, struct token domain, char* message) { bar_item_parse_set_message(&g_bar_manager.default_item, message, rsp); } static bool handle_domain_bar(FILE *rsp, struct token domain, char *message) { struct token command = get_token(&message); bool needs_refresh = false; if (token_equals(command, PROPERTY_MARGIN)) { struct token token = get_token(&message); ANIMATE(bar_manager_set_margin, &g_bar_manager, g_bar_manager.margin, token_to_int(token) ); } else if (token_equals(command, PROPERTY_YOFFSET)) { struct token token = get_token(&message); ANIMATE(bar_manager_set_y_offset, &g_bar_manager, g_bar_manager.background.y_offset, token_to_int(token) ); } else if (token_equals(command, PROPERTY_BLUR_RADIUS)) { struct token token = get_token(&message); ANIMATE(bar_manager_set_background_blur, &g_bar_manager, g_bar_manager.blur_radius, token_to_int(token) ); } else if (token_equals(command, PROPERTY_FONT_SMOOTHING)) { struct token state = get_token(&message); needs_refresh = bar_manager_set_font_smoothing(&g_bar_manager, evaluate_boolean_state(state, g_bar_manager.font_smoothing)); } else if (token_equals(command, PROPERTY_SHADOW)) { struct token state = get_token(&message); needs_refresh = bar_manager_set_shadow(&g_bar_manager, evaluate_boolean_state(state, g_bar_manager.shadow)); } else if (token_equals(command, PROPERTY_NOTCH_WIDTH)) { struct token token = get_token(&message); ANIMATE(bar_manager_set_notch_width, &g_bar_manager, g_bar_manager.notch_width, token_to_int(token) ); } else if (token_equals(command, PROPERTY_NOTCH_OFFSET)) { struct token token = get_token(&message); ANIMATE(bar_manager_set_notch_offset, &g_bar_manager, g_bar_manager.notch_offset, token_to_int(token) ); } else if (token_equals(command, PROPERTY_NOTCH_DISPLAY_HEIGHT)) { struct token token = get_token(&message); ANIMATE(bar_manager_set_notch_display_height, &g_bar_manager, g_bar_manager.notch_display_height, token_to_int(token) ); } else if (token_equals(command, PROPERTY_HIDDEN)) { struct token state = get_token(&message); uint32_t adid = 0; if (token_equals(state, "current")) { adid = display_active_display_adid(); if (adid > 0 && adid <= g_bar_manager.bar_count) needs_refresh = bar_manager_set_hidden(&g_bar_manager, adid, !g_bar_manager.bars[adid - 1]->hidden); else printf("No bar on display %u \n", adid); } else needs_refresh = bar_manager_set_hidden(&g_bar_manager, adid, evaluate_boolean_state(state, g_bar_manager.any_bar_hidden)); } else if (token_equals(command, PROPERTY_TOPMOST)) { struct token token = get_token(&message); if (token_equals(token, ARGUMENT_WINDOW)) { needs_refresh = bar_manager_set_topmost(&g_bar_manager, TOPMOST_LEVEL_WINDOW, true ); } else { needs_refresh = bar_manager_set_topmost(&g_bar_manager, TOPMOST_LEVEL_ALL, evaluate_boolean_state(token, g_bar_manager.topmost)); } } else if (token_equals(command, PROPERTY_STICKY)) { struct token token = get_token(&message); needs_refresh = bar_manager_set_sticky(&g_bar_manager, evaluate_boolean_state(token, g_bar_manager.sticky)); } else if (token_equals(command, PROPERTY_DISPLAY)) { struct token display = get_token(&message); uint32_t display_pattern = 0; uint32_t count; char** list = token_split(display, ',', &count); if (list && count > 0) { for (int i = 0; i < count; i++) { if (strcmp(list[i], ARGUMENT_DISPLAY_ALL) == 0) { display_pattern = DISPLAY_ALL_PATTERN; } else if (strcmp(list[i], ARGUMENT_DISPLAY_MAIN) == 0) { display_pattern = DISPLAY_MAIN_PATTERN; } else { display_pattern |= 1 << (strtoul(list[i], NULL, 0) - 1); } } free(list); } needs_refresh = bar_manager_set_displays(&g_bar_manager, display_pattern); } else if (token_equals(command, PROPERTY_POSITION)) { struct token position = get_token(&message); if (position.length > 0) needs_refresh = bar_manager_set_position(&g_bar_manager, position.text[0]); } else if (token_equals(command, PROPERTY_CLIP)) { respond(rsp, "[!] Bar: Invalid property 'clip'\n"); } else if (token_equals(command, PROPERTY_HEIGHT)) { struct token token = get_token(&message); ANIMATE(bar_manager_set_bar_height, &g_bar_manager, g_bar_manager.background.bounds.size.height, token_to_int(token) ); } else if (token_equals(command, PROPERTY_SHOW_IN_FULLSCREEN)) { struct token token = get_token(&message); needs_refresh = bar_manager_set_show_in_fullscreen(&g_bar_manager, evaluate_boolean_state(token, g_bar_manager.show_in_fullscreen)); } else needs_refresh = background_parse_sub_domain(&g_bar_manager.background, rsp, command, message); return needs_refresh; } static char* reformat_batch_key_value_pair(struct token token) { struct key_value_pair key_value_pair = get_key_value_pair(token.text, '='); if (!key_value_pair.key) return NULL; char* rbr_msg = malloc((strlen(key_value_pair.key) + (key_value_pair.value ? strlen(key_value_pair.value) : 0) + 3) * sizeof(char) ); pack_key_value_pair(rbr_msg, &key_value_pair); return rbr_msg; } static char* get_batch_line(char** message) { char* cursor = *message; bool end_of_batch = false; while (true) { if (*cursor == '\0' && *(cursor + 1) == '\0') { end_of_batch = true; break; } if (*cursor == '\0' && *(cursor + 1) == '-') break; cursor++; } char* rbr_msg = malloc(sizeof(char) * (cursor - *message + 2)); memcpy(rbr_msg, *message, sizeof(char) * (cursor - *message + 1)); *(rbr_msg + (cursor - *message + 1)) = '\0'; if (end_of_batch) *message = cursor; else *message = cursor + 1; return rbr_msg; } static void handle_domain_query(FILE* rsp, struct token domain, char* message) { struct token token = get_token(&message); if (token_equals(token, COMMAND_QUERY_DEFAULT_ITEMS)) { print_all_menu_items(rsp); } else if (token_equals(token, COMMAND_QUERY_ITEM)) { struct token name = get_token(&message); int item_index_for_name = bar_manager_get_item_index_for_name(&g_bar_manager, name.text ); if (item_index_for_name < 0) { respond(rsp, "[!] Query: Item '%s' not found\n", name.text); return; } bar_item_serialize(g_bar_manager.bar_items[item_index_for_name], rsp); } else if (token_equals(token, COMMAND_QUERY_BAR)) { bar_manager_serialize(&g_bar_manager, rsp); } else if (token_equals(token, COMMAND_QUERY_DEFAULTS)) { bar_item_serialize(&g_bar_manager.default_item, rsp); } else if (token_equals(token, COMMAND_QUERY_EVENTS)) { custom_events_serialize(&g_bar_manager.custom_events, rsp); } else if (token_equals(token, COMMAND_QUERY_DISPLAYS)) { display_serialize(rsp); } else { struct token name = token; int item_index_for_name = bar_manager_get_item_index_for_name(&g_bar_manager, name.text ); if (item_index_for_name < 0) { respond(rsp, "[!] Query: Invalid query, or item '%s' not found \n", name.text); return; } bar_item_serialize(g_bar_manager.bar_items[item_index_for_name], rsp); } } static void handle_domain_remove(FILE* rsp, struct token domain, char* message) { struct token name = get_token(&message); uint32_t count = 0; struct bar_item** bar_items = NULL; if (name.length > 1 && name.text[0] == REGEX_DELIMITER && name.text[name.length - 1] == REGEX_DELIMITER ) { bar_items = get_bar_items_for_regex(name, rsp, &count); } else { int item_index_for_name = bar_manager_get_item_index_for_name(&g_bar_manager, name.text ); if (item_index_for_name < 0) { respond(rsp, "[!] Remove: Item '%s' not found\n", name.text); return; } bar_items = realloc(bar_items, sizeof(struct bar_item*)); bar_items[0] = g_bar_manager.bar_items[item_index_for_name]; count = 1; } if (!bar_items || count == 0) return; for (int i = 0; i < count; i++) { bar_manager_remove_item(&g_bar_manager, bar_items[i]); } if (bar_items) free(bar_items); } static void handle_domain_move(FILE* rsp, struct token domain, char* message) { struct token name = get_token(&message); struct token direction = get_token(&message); struct token reference = get_token(&message); int item_index = bar_manager_get_item_index_for_name(&g_bar_manager, name.text); int reference_item_index = bar_manager_get_item_index_for_name(&g_bar_manager, reference.text); if (item_index < 0 || reference_item_index < 0) { respond(rsp, "[!] Move: Item '%s' or '%s' not found\n", name.text, reference.text); return; } bar_manager_move_item(&g_bar_manager, g_bar_manager.bar_items[item_index], g_bar_manager.bar_items[reference_item_index], token_equals(direction, ARGUMENT_COMMON_VAL_BEFORE)); bar_item_needs_update(g_bar_manager.bar_items[item_index]); } static void handle_domain_order(FILE* rsp, struct token domain, char* message) { struct bar_item* ordering[g_bar_manager.bar_item_count]; memset(ordering, 0, sizeof(struct bar_item*)*g_bar_manager.bar_item_count); uint32_t count = 0; struct token name = get_token(&message); while (name.text && name.length > 0) { int index = bar_manager_get_item_index_for_name(&g_bar_manager, name.text); if (index < 0) { respond(rsp, "[!] Order: Item '%s' not found\n", name.text); name = get_token(&message); continue; } ordering[count] = g_bar_manager.bar_items[index]; count++; name = get_token(&message); } bar_manager_sort(&g_bar_manager, ordering, count); bar_manager_refresh(&g_bar_manager, false, false); } void handle_message_mach(struct mach_buffer* buffer) { if (!buffer->message.descriptor.address) return; char* message = buffer->message.descriptor.address; char* response = NULL; size_t length = 0; FILE* rsp = open_memstream(&response, &length); fprintf(rsp, ""); g_bar_manager.animator.interp_function = '\0'; g_bar_manager.animator.duration = 0; bar_manager_freeze(&g_bar_manager); struct token command = get_token(&message); bool bar_needs_refresh = false; while (command.text && command.length > 0) { if (token_equals(command, DOMAIN_SET)) { struct token name = get_token(&message); uint32_t count = 0; struct bar_item** bar_items = NULL; if (name.length > 1 && name.text[0] == REGEX_DELIMITER && name.text[name.length - 1] == REGEX_DELIMITER ) { bar_items = get_bar_items_for_regex(name, rsp, &count); } else { int item_index_for_name = bar_manager_get_item_index_for_name(&g_bar_manager, name.text); if (item_index_for_name < 0) { respond(rsp, "[!] Set: Item not found '%s'\n", name.text); } else { bar_items = realloc(bar_items, sizeof(struct bar_item*)); bar_items[0] = g_bar_manager.bar_items[item_index_for_name]; count = 1; } } if (!bar_items || count == 0) { char* rest = get_batch_line(&message); free(rest); } else { struct token token = get_token(&message); while (token.text && token.length > 0) { for (int i = 0; i < count; i++) { struct token tmp = {string_copy(token.text), token.length}; char* rbr_msg = reformat_batch_key_value_pair(tmp); free(tmp.text); if (!rbr_msg) { respond(rsp, "[!] Set (%s): Expected = pair, but got: '%s'\n", bar_items[i]->name, token.text); break; } bar_item_parse_set_message(bar_items[i], rbr_msg, rsp); free(rbr_msg); } if (message && *message == '-') break; token = get_token(&message); } free(bar_items); } } else if (token_equals(command, DOMAIN_DEFAULT)) { struct token token = get_token(&message); while (token.text && token.length > 0) { char* rbr_msg = reformat_batch_key_value_pair(token); if (!rbr_msg) { respond(rsp, "[!] Set (default): Expected = pair, but got: '%s'\n", token.text); break; } handle_domain_default(rsp, command, rbr_msg); free(rbr_msg); if (message && *message == '-') break; token = get_token(&message); } } else if (token_equals(command, DOMAIN_ANIMATE)) { g_bar_manager.animator.interp_function = get_token(&message).text[0]; g_bar_manager.animator.duration = token_to_uint32t(get_token(&message)); } else if (token_equals(command, DOMAIN_BAR)) { struct token token = get_token(&message); while (token.text && token.length > 0) { char* rbr_msg = reformat_batch_key_value_pair(token); if (!rbr_msg) { respond(rsp, "[!] Bar: Expected = pair, but got: '%s'\n", token.text); break; } bar_needs_refresh |= handle_domain_bar(rsp, command, rbr_msg); free(rbr_msg); if (message && *message == '-') break; token = get_token(&message); } } else if (token_equals(command, DOMAIN_ADD)) { char* rbr_msg = get_batch_line(&message); handle_domain_add(rsp, command, rbr_msg); free(rbr_msg); } else if (token_equals(command, DOMAIN_CLONE)) { char* rbr_msg = get_batch_line(&message); handle_domain_clone(rsp, command, rbr_msg); free(rbr_msg); } else if (token_equals(command, DOMAIN_SUBSCRIBE)) { char* rbr_msg = get_batch_line(&message); handle_domain_subscribe(rsp, command, rbr_msg); free(rbr_msg); } else if (token_equals(command, DOMAIN_PUSH)) { char* rbr_msg = get_batch_line(&message); handle_domain_push(rsp, command, rbr_msg); free(rbr_msg); } else if (token_equals(command, DOMAIN_UPDATE)) { bar_manager_update(&g_bar_manager, true); bar_needs_refresh = true; } else if (token_equals(command, DOMAIN_TRIGGER)) { char* rbr_msg = get_batch_line(&message); handle_domain_trigger(rsp, command, rbr_msg); free(rbr_msg); } else if (token_equals(command, DOMAIN_QUERY)) { char* rbr_msg = get_batch_line(&message); handle_domain_query(rsp, command, rbr_msg); free(rbr_msg); } else if (token_equals(command, DOMAIN_REORDER)) { char* rbr_msg = get_batch_line(&message); handle_domain_order(rsp, command, rbr_msg); free(rbr_msg); } else if (token_equals(command, DOMAIN_MOVE)) { char* rbr_msg = get_batch_line(&message); handle_domain_move(rsp, command, rbr_msg); free(rbr_msg); } else if (token_equals(command, DOMAIN_REMOVE)) { char* rbr_msg = get_batch_line(&message); handle_domain_remove(rsp, command, rbr_msg); bar_needs_refresh = true; free(rbr_msg); } else if (token_equals(command, DOMAIN_RENAME)) { char* rbr_msg = get_batch_line(&message); handle_domain_rename(rsp, command, rbr_msg); free(rbr_msg); } else if (token_equals(command, DOMAIN_EXIT)) { bar_manager_destroy(&g_bar_manager); exit(0); } else if (token_equals(command, DOMAIN_HOTLOAD)) { struct token token = get_token(&message); hotload_set_state(evaluate_boolean_state(token, hotload_get_state())); } else if (token_equals(command, DOMAIN_ADD_FONT)) { struct token token = get_token(&message); font_register(token_to_string(token)); } else if (token_equals(command, DOMAIN_RELOAD)) { char* rbr_msg = get_batch_line(&message); char* cur = rbr_msg; struct token token = get_token(&cur); bool reload = false; if (token.length > 0 && token.text) { if (!set_config_file_path(token.text)) { respond(rsp, "[?] Reload: Invalid config path '%s'\n", token.text); } else reload = true; } else reload = true; free(rbr_msg); if (reload) { struct event event = { NULL, HOTLOAD }; event_post(&event); } } else { char* rbr_msg = get_batch_line(&message); respond(rsp, "[!] Unknown domain '%s'\n", command.text); free(rbr_msg); } command = get_token(&message); } if (bar_needs_refresh) { if (g_bar_manager.bar_needs_resize) bar_manager_resize(&g_bar_manager); g_bar_manager.bar_needs_update = true; } animator_lock(&g_bar_manager.animator); bar_manager_unfreeze(&g_bar_manager); bar_manager_refresh(&g_bar_manager, false, false); if (rsp) fclose(rsp); response[length] = '\0'; mach_send_message(buffer->message.header.msgh_remote_port, response, length + 1, false ); if (response) free(response); } MACH_HANDLER(mach_message_handler) { struct event event = { message, MACH_MESSAGE }; event_post(&event); } ================================================ FILE: src/message.h ================================================ #pragma once #include #include "alias.h" #include "background.h" #include "bar_item.h" #include "bar_manager.h" #include "display.h" #include "group.h" #include "slider.h" #include "mach.h" #include "event.h" #include "misc/helpers.h" #include "misc/defines.h" MACH_HANDLER(mach_message_handler); void handle_message_mach(struct mach_buffer* buffer); ================================================ FILE: src/misc/defines.h ================================================ #pragma once #define DOMAIN_ADD "--add" #define COMMAND_ADD_ITEM "item" #define COMMAND_ADD_COMPONENT "component" #define COMMAND_ADD_EVENT "event" #define DOMAIN_UPDATE "--update" #define DOMAIN_PUSH "--push" #define DOMAIN_TRIGGER "--trigger" #define DOMAIN_DEFAULT "--default" #define COMMAND_DEFAULT_RESET "reset" #define DOMAIN_CLONE "--clone" #define DOMAIN_RENAME "--rename" #define DOMAIN_REORDER "--reorder" #define DOMAIN_REMOVE "--remove" #define DOMAIN_MOVE "--move" #define DOMAIN_SET "--set" #define DOMAIN_ANIMATE "--animate" #define DOMAIN_EXIT "--exit" #define DOMAIN_HOTLOAD "--hotload" #define DOMAIN_RELOAD "--reload" #define DOMAIN_ADD_FONT "--load-font" #define SUB_DOMAIN_ICON "icon" #define SUB_DOMAIN_LABEL "label" #define SUB_DOMAIN_BACKGROUND "background" #define SUB_DOMAIN_GRAPH "graph" #define SUB_DOMAIN_ALIAS "alias" #define SUB_DOMAIN_POPUP "popup" #define SUB_DOMAIN_SHADOW "shadow" #define SUB_DOMAIN_IMAGE "image" #define SUB_DOMAIN_KNOB "knob" #define SUB_DOMAIN_SLIDER "slider" #define SUB_DOMAIN_FONT "font" #define SUB_DOMAIN_COLOR "color" #define SUB_DOMAIN_BORDER_COLOR "border_color" #define SUB_DOMAIN_HIGHLIGHT_COLOR "highlight_color" #define SUB_DOMAIN_FILL_COLOR "fill_color" #define PROPERTY_FONT "font" #define PROPERTY_COLOR "color" #define PROPERTY_HIGHLIGHT "highlight" #define PROPERTY_HIGHLIGHT_COLOR "highlight_color" #define PROPERTY_PADDING_LEFT "padding_left" #define PROPERTY_PADDING_RIGHT "padding_right" #define PROPERTY_HEIGHT "height" #define PROPERTY_BORDER_COLOR "border_color" #define PROPERTY_BORDER_WIDTH "border_width" #define PROPERTY_CORNER_RADIUS "corner_radius" #define PROPERTY_FILL_COLOR "fill_color" #define PROPERTY_LINE_WIDTH "line_width" #define PROPERTY_BLUR_RADIUS "blur_radius" #define PROPERTY_DRAWING "drawing" #define PROPERTY_CLIP "clip" #define PROPERTY_DISTANCE "distance" #define PROPERTY_ANGLE "angle" #define PROPERTY_SCALE "scale" #define PROPERTY_STRING "string" #define PROPERTY_SCROLL_TEXTS "scroll_texts" #define PROPERTY_SCROLL_DURATION "scroll_duration" #define PROPERTY_COLOR_HEX "hex" #define PROPERTY_COLOR_ALPHA "alpha" #define PROPERTY_COLOR_RED "red" #define PROPERTY_COLOR_GREEN "green" #define PROPERTY_COLOR_BLUE "blue" #define PROPERTY_FONT_FAMILY "family" #define PROPERTY_FONT_STYLE "style" #define PROPERTY_FONT_SIZE "size" #define PROPERTY_FONT_FEATURES "features" #define PROPERTY_UPDATES "updates" #define PROPERTY_POSITION "position" #define PROPERTY_ASSOCIATED_DISPLAY "associated_display" #define PROPERTY_ASSOCIATED_SPACE "associated_space" #define PROPERTY_UPDATE_FREQ "update_freq" #define PROPERTY_SCRIPT "script" #define PROPERTY_CLICK_SCRIPT "click_script" #define PROPERTY_ICON "icon" #define PROPERTY_XOFFSET "x_offset" #define PROPERTY_YOFFSET "y_offset" #define PROPERTY_WIDTH "width" #define PROPERTY_LABEL "label" #define PROPERTY_CACHE_SCRIPTS "cache_scripts" #define PROPERTY_LAZY "lazy" #define PROPERTY_IGNORE_ASSOCIATION "ignore_association" #define PROPERTY_EVENT_PORT "mach_helper" #define PROPERTY_PERCENTAGE "percentage" #define PROPERTY_MAX_CHARS "max_chars" #define DOMAIN_BAR "--bar" #define PROPERTY_POSITION "position" #define PROPERTY_MARGIN "margin" #define PROPERTY_DISPLAY "display" #define PROPERTY_SPACE "space" #define PROPERTY_TOPMOST "topmost" #define PROPERTY_STICKY "sticky" #define PROPERTY_SHOW_IN_FULLSCREEN "show_in_fullscreen" #define PROPERTY_HIDDEN "hidden" #define PROPERTY_FONT_SMOOTHING "font_smoothing" #define PROPERTY_SHADOW "shadow" #define PROPERTY_ALIGN "align" #define PROPERTY_NOTCH_WIDTH "notch_width" #define PROPERTY_NOTCH_OFFSET "notch_offset" #define PROPERTY_NOTCH_DISPLAY_HEIGHT "notch_display_height" #define PROPERTY_HORIZONTAL "horizontal" #define DOMAIN_SUBSCRIBE "--subscribe" #define COMMAND_SUBSCRIBE_FRONT_APP_SWITCHED "front_app_switched" #define COMMAND_SUBSCRIBE_SPACE_CHANGE "space_change" #define COMMAND_SUBSCRIBE_DISPLAY_CHANGE "display_change" #define COMMAND_SUBSCRIBE_SYSTEM_WOKE "system_woke" #define COMMAND_SUBSCRIBE_SYSTEM_WILL_SLEEP "system_will_sleep" #define COMMAND_SUBSCRIBE_VOLUME_CHANGE "volume_change" #define COMMAND_SUBSCRIBE_WIFI_CHANGE "wifi_change" #define COMMAND_SUBSCRIBE_BRIGHTNESS_CHANGE "brightness_change" #define COMMAND_SUBSCRIBE_POWER_SOURCE_CHANGE "power_source_change" #define COMMAND_SUBSCRIBE_MEDIA_CHANGE "media_change" #define COMMAND_SUBSCRIBE_MOUSE_ENTERED "mouse.entered" #define COMMAND_SUBSCRIBE_MOUSE_EXITED "mouse.exited" #define COMMAND_SUBSCRIBE_MOUSE_CLICKED "mouse.clicked" #define COMMAND_SUBSCRIBE_MOUSE_SCROLLED "mouse.scrolled" #define COMMAND_SUBSCRIBE_MOUSE_ENTERED_GLOBAL "mouse.entered.global" #define COMMAND_SUBSCRIBE_MOUSE_EXITED_GLOBAL "mouse.exited.global" #define COMMAND_SUBSCRIBE_MOUSE_SCROLLED_GLOBAL "mouse.scrolled.global" #define COMMAND_SUBSCRIBE_SPACE_WINDOWS_CHANGE "space_windows_change" #define DOMAIN_QUERY "--query" #define COMMAND_QUERY_DEFAULT_ITEMS "default_menu_items" #define COMMAND_QUERY_ITEM "item" #define COMMAND_QUERY_DEFAULTS "defaults" #define COMMAND_QUERY_BAR "bar" #define COMMAND_QUERY_EVENTS "events" #define COMMAND_QUERY_DISPLAYS "displays" #define ARGUMENT_COMMON_VAL_ON "on" #define ARGUMENT_COMMON_VAL_NOT_OFF "!off" #define ARGUMENT_COMMON_VAL_TRUE "true" #define ARGUMENT_COMMON_VAL_NOT_FALSE "!false" #define ARGUMENT_COMMON_VAL_ONE "1" #define ARGUMENT_COMMON_VAL_NOT_ZERO "!0" #define ARGUMENT_COMMON_VAL_YES "yes" #define ARGUMENT_COMMON_VAL_NOT_NO "!no" #define ARGUMENT_COMMON_VAL_OFF "off" #define ARGUMENT_COMMON_VAL_NOT_ON "!on" #define ARGUMENT_COMMON_VAL_FALSE "false" #define ARGUMENT_COMMON_VAL_NOT_TRUE "!true" #define ARGUMENT_COMMON_VAL_ZERO "0" #define ARGUMENT_COMMON_VAL_NOT_ONE "!1" #define ARGUMENT_COMMON_VAL_NO "no" #define ARGUMENT_COMMON_VAL_NOT_YES "!yes" #define ARGUMENT_COMMON_VAL_TOGGLE "toggle" #define ARGUMENT_COMMON_VAL_BEFORE "before" #define ARGUMENT_COMMON_VAL_AFTER "after" #define ARGUMENT_DISPLAY_MAIN "main" #define ARGUMENT_DISPLAY_ALL "all" #define ARGUMENT_UPDATES_WHEN_SHOWN "when_shown" #define ARGUMENT_DYNAMIC "dynamic" #define ARGUMENT_WINDOW "window" #define POSITION_TOP 't' #define POSITION_BOTTOM 'b' #define POSITION_LEFT 'l' #define POSITION_RIGHT 'r' #define POSITION_CENTER 'c' #define POSITION_POPUP 'p' #define POSITION_CENTER_LEFT 'q' #define POSITION_CENTER_RIGHT 'e' #define TYPE_GRAPH "graph" #define TYPE_SPACE "space" #define TYPE_ALIAS "alias" #define TYPE_GROUP "bracket" #define TYPE_SLIDER "slider" #define TYPE_ITEM "item" #define REGEX_DELIMITER '/' ================================================ FILE: src/misc/env_vars.h ================================================ #pragma once #include #include #include struct key_value_pair { char* key; char* value; }; struct env_vars { uint32_t count; struct key_value_pair** vars; }; static inline void env_vars_init(struct env_vars* env_vars) { env_vars->vars = NULL; env_vars->count = 0; } static inline void env_vars_unset(struct env_vars* env_vars, char* key) { struct key_value_pair* key_value_pair = NULL; for (int i = 0; i < env_vars->count; i++) { if (strcmp(env_vars->vars[i]->key, key) == 0) key_value_pair = env_vars->vars[i]; } if (key_value_pair == NULL) return; if (env_vars->count == 1) { free(env_vars->vars); env_vars->vars = NULL; env_vars->count = 0; } else { struct key_value_pair* tmp[env_vars->count - 1]; int count = 0; for (int i = 0; i < env_vars->count; i++) { if (env_vars->vars[i] == key_value_pair) continue; tmp[count++] = env_vars->vars[i]; } env_vars->count--; env_vars->vars = realloc(env_vars->vars, sizeof(struct key_value_pair*)*env_vars->count); memcpy(env_vars->vars, tmp, sizeof(struct key_value_pair*)*env_vars->count); } if (key_value_pair->key) free(key_value_pair->key); if (key_value_pair->value) free(key_value_pair->value); free(key_value_pair); } static inline void env_vars_set(struct env_vars* env_vars, char* key, char* value) { env_vars_unset(env_vars, key); env_vars->count++; env_vars->vars = realloc(env_vars->vars, env_vars->count * sizeof(struct key_value_pair*)); env_vars->vars[env_vars->count - 1] = malloc(sizeof(struct key_value_pair)); env_vars->vars[env_vars->count - 1]->key = key; env_vars->vars[env_vars->count - 1]->value = value; } static inline char* env_vars_get_value_for_key(struct env_vars* env_vars, char* key) { for (int i = 0; i < env_vars->count; i++) { if (strcmp(env_vars->vars[i]->key, key) == 0) return env_vars->vars[i]->value; } return NULL; } static inline char* env_vars_copy_serialized_representation(struct env_vars* env_vars, uint32_t* len) { uint32_t length = 0; for (int i = 0; i < env_vars->count; i++) { length += env_vars->vars[i]->key ? strlen(env_vars->vars[i]->key) + 1 : 1; length += env_vars->vars[i]->value ? strlen(env_vars->vars[i]->value) + 1 : 1; } uint32_t caret = 0; char* seri = (char*)malloc(++length); for (int i = 0; i < env_vars->count; i++) { if (env_vars->vars[i]->key) { uint32_t len = strlen(env_vars->vars[i]->key) + 1; memcpy(seri + caret, env_vars->vars[i]->key, len ); caret += len; } else { seri[caret++] = '\0'; } if (env_vars->vars[i]->value) { uint32_t len = strlen(env_vars->vars[i]->value) + 1; memcpy(seri + caret, env_vars->vars[i]->value, len ); caret += len; } else { seri[caret++] = '\0'; } } seri[caret++] = '\0'; assert(caret == length); *len = length; return seri; } static inline void env_vars_destroy(struct env_vars* env_vars) { for (int i = 0; i < env_vars->count; i++) { if (env_vars->vars[i]->key) free(env_vars->vars[i]->key); if (env_vars->vars[i]->value) free(env_vars->vars[i]->value); free(env_vars->vars[i]); } free(env_vars->vars); } ================================================ FILE: src/misc/extern.h ================================================ #include extern CGError DisplayServicesRegisterForBrightnessChangeNotifications(uint32_t did, uint32_t passthrough, void* callback); extern CGError DisplayServicesRegisterForAmbientLightCompensationNotifications(uint32_t did, uint32_t passthrough, void* callback); extern CGError DisplayServicesUnregisterForBrightnessChangeNotifications(uint32_t did, uint32_t passthrough); extern CGError DisplayServicesUnregisterForAmbientLightCompensationNotifications(uint32_t did, uint32_t passthrough); extern CGError DisplayServicesGetBrightness(uint32_t did, float* brightness); extern CGError DisplayServicesCanChangeBrightness(uint32_t did); extern CGError DisplayServicesAmbientLightCompensationEnabled(uint32_t did, bool* out); extern CFArrayRef SLSCopyManagedDisplaySpaces(int cid); extern uint32_t SLSGetActiveSpace(int cid); extern CFStringRef SLSCopyManagedDisplayForSpace(int cid, uint64_t sid); extern CFArrayRef SLSHWCaptureSpace(int64_t cid, int64_t sid, int64_t flags); extern CGError SLSGetWindowOwner(int cid, uint32_t wid, int* out_cid); extern CGError SLSConnectionGetPID(int cid, pid_t *pid); extern CFArrayRef SLSCopyWindowsWithOptionsAndTags(int cid, uint32_t owner, CFArrayRef spaces, uint32_t options, uint64_t *set_tags, uint64_t *clear_tags); extern CFTypeRef SLSWindowQueryWindows(int cid, CFArrayRef windows, uint32_t options); extern CFTypeRef SLSWindowQueryResultCopyWindows(CFTypeRef window_query); extern int SLSWindowIteratorGetCount(CFTypeRef iterator); extern bool SLSWindowIteratorAdvance(CFTypeRef iterator); extern uint32_t SLSWindowIteratorGetParentID(CFTypeRef iterator); extern uint32_t SLSWindowIteratorGetWindowID(CFTypeRef iterator); extern uint64_t SLSWindowIteratorGetTags(CFTypeRef iterator); extern uint64_t SLSWindowIteratorGetAttributes(CFTypeRef iterator); extern CGError SLSRegisterNotifyProc(void* callback, uint32_t event, void* context); extern CGError SLSRequestNotificationsForWindows(int cid, uint32_t* wid_list, uint32_t list_count); extern CFUUIDRef CGDisplayCreateUUIDFromDisplayID(uint32_t did); extern CFArrayRef SLSCopyManagedDisplays(int cid); extern uint64_t SLSManagedDisplayGetCurrentSpace(int cid, CFStringRef uuid); extern CFStringRef SLSCopyBestManagedDisplayForRect(int cid, CGRect rect); extern CGError SLSGetCurrentCursorLocation(int cid, CGPoint *point); extern CFStringRef SLSCopyActiveMenuBarDisplayIdentifier(int cid); extern CGError SLSGetMenuBarAutohideEnabled(int cid, int *enabled); extern CGError SLSGetRevealedMenuBarBounds(CGRect *rect, int cid, uint64_t sid); extern CFStringRef SLSCopyBestManagedDisplayForPoint(int cid, CGPoint point); extern CGError SLSSetMenuBarVisibilityOverrideOnDisplay(int cid, int did, bool override); extern CGError SLSSetMenuBarAutohideEnabled(int cid, bool enabled); extern CGError SLSFlushWindowContentRegion(int cid, uint32_t wid, void* dirty); extern CFTypeRef SLSTransactionCreate(int cid); extern CGError SLSTransactionOrderWindow(CFTypeRef transaction, uint32_t wid, int mode, uint32_t relativeToWID); extern CGError SLSTransactionSetWindowLevel(CFTypeRef transaction, uint32_t wid, int level); extern CGError SLSTransactionSetWindowShape(CFTypeRef transaction, uint32_t wid, float x_offset, float y_offset, CFTypeRef shape); extern CGError SLSTransactionMoveWindowWithGroup(CFTypeRef transaction, uint32_t wid, CGPoint point); extern CGError SLSTransactionCommitUsingMethod(CFTypeRef transaction, uint32_t method); extern CGError SLSTransactionCommit(CFTypeRef transaction, uint32_t async); extern CFTypeRef CGRegionCreateEmptyRegion(void); extern CGError SLSDisableUpdate(int cid); extern CGError SLSReenableUpdate(int cid); extern CGError SLSNewWindowWithOpaqueShapeAndContext(int cid, int type, CFTypeRef region, CFTypeRef opaque_shape, int options, uint64_t *tags, float x, float y, int tag_size, uint32_t *wid, void *context); extern CGError SLSNewWindow(int cid, int type, float x, float y, CFTypeRef region, uint64_t *wid); extern CGError SLSReleaseWindow(int cid, uint32_t wid); extern CGError SLSSetWindowTags(int cid, uint32_t wid, uint64_t* tags, int tag_size); extern CGError SLSClearWindowTags(int cid, uint32_t wid, uint64_t* tags, int tag_size); extern CGError SLSSetWindowShape(int cid, uint32_t wid, float x_offset, float y_offset, CFTypeRef shape); extern CGError SLSSetWindowOpaqueShape(int cid, uint32_t wid, float x_offset, float y_offset, CFTypeRef region); extern CGError SLSSetWindowResolution(int cid, uint32_t wid, double res); extern CGError SLSSetWindowOpacity(int cid, uint32_t wid, bool isOpaque); extern CGError SLSSetWindowAlpha(int cid, uint32_t wid, float alpha); extern CGError SLSSetWindowBackgroundBlurRadius(int cid, uint32_t wid, uint32_t radius); extern CGError SLSOrderWindow(int cid, uint32_t wid, int mode, uint32_t relativeToWID); extern CGError SLSSetWindowLevel(int cid, uint32_t wid, int level); extern CGContextRef SLWindowContextCreate(int cid, uint32_t wid, CFDictionaryRef options); extern CGError CGSNewRegionWithRect(CGRect *rect, CFTypeRef *outRegion); extern CGError SLSAddActivationRegion(uint32_t cid, uint32_t wid, CFTypeRef region); extern CGError SLSAddTrackingRect(uint32_t cid, uint32_t wid, CGRect rect); extern CGError SLSClearActivationRegion(uint32_t cid, uint32_t wid); extern CGError SLSRemoveAllTrackingAreas(uint32_t cid, uint32_t wid); extern CGError SLSMoveWindow(int cid, uint32_t wid, CGPoint* point); extern CGError SLSWindowSetShadowProperties(uint32_t wid, CFDictionaryRef properties); extern CGError SLSAddWindowToWindowOrderingGroup(int cid, uint32_t parent_wid, uint32_t child_wid, int order); extern CGError SLSRemoveFromOrderingGroup(int cid, uint32_t wid); extern CGError SLSReassociateWindowsSpacesByGeometry(int cid, CFArrayRef wids); extern CGError SLSMoveWindowsToManagedSpace(int cid, CFArrayRef window_list, uint64_t sid); extern CGError SLSMoveWindowWithGroup(int cid, uint32_t wid, CGPoint* point); extern void SLSCaptureWindowsContentsToRectWithOptions(uint32_t cid, uint64_t* wid, bool meh, CGRect bounds, uint32_t flags, CGImageRef* image); extern int SLSGetScreenRectForWindow(uint32_t cid, uint32_t wid, CGRect* out); extern int SLSSpaceGetType(int cid, uint64_t sid); extern CGError SLSAddSurface(int cid, uint32_t wid, uint32_t* outSID); extern CGError SLSRemoveSurface(int cid, uint32_t wid, uint32_t sid); extern CGError SLSBindSurface(int cid, uint32_t wid, uint32_t sid, int param1, int param2, unsigned int context_id); extern CGError SLSSetSurfaceBounds(int cid, uint32_t wid, uint32_t sid, CGRect bounds); extern CGError SLSSetSurfaceOpacity(int cid, uint32_t wid, uint32_t sid, bool opaque); extern CGError SLSOrderSurface(int cid, uint32_t wid, uint32_t surface, int mode, uint32_t other_surface); extern CGError SLSSetSurfaceResolution(int cid, uint32_t wid, uint32_t sid, CGFloat scale); extern CGError SLSFlushSurface(int cid, uint32_t wid, uint32_t surface, int param); extern CGError SLSSetSurfaceColorSpace(int cid, uint32_t wid, uint32_t surface, CGColorSpaceRef color_space); extern int SLSSpaceCreate(int cid, int one, int zero); extern CGError SLSSpaceSetAbsoluteLevel(int cid, int sid, int level); extern CGError SLSShowSpaces(int cid, CFArrayRef space_list); extern CGError SLSHideSpaces(int cid, CFArrayRef space_list); extern CGError SLSSpaceAddWindowsAndRemoveFromSpaces(int cid, int sid, CFArrayRef array, int seven); ================================================ FILE: src/misc/help.h ================================================ static const char help_str[] = { "Usage: %s [options]\n\n" "Startup: \n" " -c, --config CONFIGFILE\tRead CONFIGFILE as the configuration file\n" " \tDefault CONFIGFILE is ~/.config/sketchybar/sketchybarrc\n\n" "Set global bar properties, see https://felixkratz.github.io/SketchyBar/config/bar\n" " --bar = ... =\n\n" "Items and their properties, see https://felixkratz.github.io/SketchyBar/config/items\n" " --add item \tAdd item to bar\n" " --set = ... =\n" " \tChange item properties\n" " --default = ... =\n" " \tChange default properties for new items\n" " --set popup.=\n" " \tConfigure item popup menu\n" " \tSee https://felixkratz.github.io/SketchyBar/config/popups\n" " --reorder ... \tReorder items\n" " --move before \n" " --move after \n" " \tMove item relative to reference item\n" " --clone [optional: before/after]\n" " \tClone parent to create new item\n" " --rename \tRename item\n" " --remove \tRemove item\n\n" "Special components, see https://felixkratz.github.io/SketchyBar/config/components\n" " --add graph \n" " \tAdd graph component\n" " --push ... \n" " \tPush data points to a graph\n" " --add space \tAdd space component\n" " --add bracket ... \n" " \tAdd bracket component\n" " --add alias \n" " \tAdd alias component\n" " --add slider \n" " \tAdd slider component\n\n" "Events and Scripting, see https://felixkratz.github.io/SketchyBar/config/events\n" " --subscribe ... \n" " \tSubscribe to events\n" " --add event [optional: ]\n" " \tCreate custom event\n" " --trigger [optional: = ... =]\n" " \tTrigger custom event\n\n" "Querying information, see https://felixkratz.github.io/SketchyBar/config/querying\n" " --query bar \tQuery bar properties\n" " --query \tQuery item properties\n" " --query defaults \tQuery default properties\n" " --query events \tQuery events\n" " --query default_menu_items\tQuery names of available items for aliases\n\n" "Animations, see https://felixkratz.github.io/SketchyBar/config/animations\n" " --animate \\\n" " --bar ... \\\n" " --set ... \n" " \tAnimate from given source to target property values\n\n" "Reloading the config\n" " --hotload \tEnable or disable the config hotloader\n" " --reload [optional: ]\tReload the current or the given config\n\n" }; ================================================ FILE: src/misc/helpers.h ================================================ #pragma once #include #include #include #include #include #include #include "env_vars.h" #include "defines.h" #include "extern.h" #define array_count(a) (sizeof((a)) / sizeof(*(a))) #define max(a, b) (a > b ? a : b) #define min(a, b) (a < b ? a : b) #define clamp(x, l, u) (min(max(x, l), u)) #define MAXLEN 512 #define FORK_TIMEOUT 60 extern int g_connection; struct signal_args { struct env_vars env_vars; void *entity; void *param1; }; static CGPoint g_nirvana = {-9999, -9999}; static double deg_to_rad = 2.* M_PI / 360.; struct token { char *text; unsigned int length; }; struct notification { char* name; char* info; }; static inline struct notification* notification_create() { struct notification* notification = malloc(sizeof(struct notification)); memset(notification, 0, sizeof(struct notification)); return notification; } static inline void notification_destroy(struct notification* notification) { if (notification->name) free(notification->name); if (notification->info) free(notification->info); free(notification); } static inline double function_linear(double x) { return x; } static inline double function_square(double x) { return x*x; } static inline double function_tanh(double x) { double a = 0.52; return a * tanh(2. * atanh(1. / (2. * a)) * (x - 0.5)) + 0.5; } static inline double function_sin(double x) { return sin(M_PI / 2. * x); } static inline double function_exp(double x) { return x*exp(x - 1.); } static inline double function_circ(double x) { return sqrt(1.f - powf(x - 1.f, 2.f)); } static inline char* format_bool(bool b) { return b ? "on" : "off"; } static inline char* escape_string(char* string) { if (!string) return NULL; int len = strlen(string); char* buffer = malloc(2*len + 1); int cursor = 0; for (int i = 0; i < len; i++) { if (string[i] == '"') { buffer[cursor++] = '\\'; buffer[cursor++] = '"'; } else if (string[i] == '\n') { buffer[cursor++] = '\\'; buffer[cursor++] = 'n'; } else { buffer[cursor++] = string[i]; } } buffer[cursor] = '\0'; return buffer; } static inline void respond(FILE* rsp, char* response, ...) { time_t t = time(NULL); struct tm ltime = *localtime(&t); printf("[%d-%02d-%02d %02d:%02d:%02d] ", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday, ltime.tm_hour, ltime.tm_min, ltime.tm_sec ); va_list args_rsp; va_list args_stdout; va_start(args_rsp, response); va_copy(args_stdout, args_rsp); vfprintf(rsp, response, args_rsp); vfprintf(stdout, response, args_stdout); va_end(args_rsp); va_end(args_stdout); } static inline struct key_value_pair get_key_value_pair(char *token, char split) { struct key_value_pair key_value_pair; key_value_pair.key = token; while (*token) { if (token[0] == split) break; ++token; } if (*token != split) { key_value_pair.key = NULL; key_value_pair.value = NULL; } else if (token[1]) { *token = '\0'; key_value_pair.value = token + 1; } else { *token = '\0'; key_value_pair.value = NULL; } return key_value_pair; } static inline void pack_key_value_pair(char* cursor, struct key_value_pair* key_value_pair) { uint32_t key_len = strlen(key_value_pair->key); uint32_t val_len = key_value_pair->value ? strlen(key_value_pair->value) : 0; memcpy(cursor, key_value_pair->key, key_len); cursor += key_len; *cursor++ = '\0'; memcpy(cursor, key_value_pair->value, val_len); cursor += val_len; *cursor++ = '\0'; *cursor++ = '\0'; } static inline bool is_root(void) { return getuid() == 0 || geteuid() == 0; } static inline bool string_equals(const char *a, const char *b) { return a && b && strcmp(a, b) == 0; } static inline char* get_type_description(uint32_t type) { switch (type) { case kCGEventLeftMouseUp: return "left"; case kCGEventRightMouseUp: return "right"; default: return "other"; } } static inline char* get_modifier_description(uint32_t modifier) { static char description[64]; description[0] = '\0'; if (modifier & kCGEventFlagMaskShift) strcat(description, "shift,"); if (modifier & kCGEventFlagMaskControl) strcat(description, "ctrl,"); if (modifier & kCGEventFlagMaskAlternate) strcat(description, "alt,"); if (modifier & kCGEventFlagMaskCommand) strcat(description, "cmd,"); if (modifier & kCGEventFlagMaskSecondaryFn) strcat(description, "fn,"); int len = strlen(description); if (len > 0 && description[len - 1] == ',') { description[len - 1] = '\0'; } return description[0] ? description : "none"; } static inline char** token_split(struct token token, char split, uint32_t* count) { if (!token.text || token.length == 0) return NULL; char** list = NULL; *count = 0; int prev = -1; for (int i = 0; i < token.length + 1; i++) { if (token.text[i] == split || token.text[i] == '\0') { list = realloc(list, sizeof(char*) * ++*count); token.text[i] = '\0'; list[*count - 1] = &token.text[prev + 1]; prev = i; } } return list; } static inline bool token_equals(struct token token, char *match) { char *at = match; for (int i = 0; i < token.length; ++i, ++at) { if ((*at == 0) || (token.text[i] != *at)) { return false; } } return *at == 0; } static inline char *token_to_string(struct token token) { char *result = malloc(token.length + 1); if (!result) return NULL; memcpy(result, token.text, token.length); result[token.length] = '\0'; return result; } static inline uint32_t token_to_uint32t(struct token token) { char buffer[token.length + 1]; memcpy(buffer, token.text, token.length); buffer[token.length] = '\0'; return strtoul(buffer, NULL, 0); } static inline int token_to_int(struct token token) { char buffer[token.length + 1]; memcpy(buffer, token.text, token.length); buffer[token.length] = '\0'; return (int) strtol(buffer, NULL, 0); } static inline float token_to_float(struct token token) { char buffer[token.length + 1]; memcpy(buffer, token.text, token.length); buffer[token.length] = '\0'; return strtof(buffer, NULL); } static inline struct token get_token(char **message) { struct token token; token.text = *message; while (**message) { ++(*message); } token.length = *message - token.text; if ((*message)[0] == '\0' && (*message)[1] != '\0') { ++(*message); } else { // NOTE(koekeishiya): don't go past the null-terminator } return token; } static inline bool evaluate_boolean_state(struct token state, bool previous_state) { if (token_equals(state, ARGUMENT_COMMON_VAL_ON) || token_equals(state, ARGUMENT_COMMON_VAL_YES) || token_equals(state, ARGUMENT_COMMON_VAL_TRUE) || token_equals(state, ARGUMENT_COMMON_VAL_ONE) || token_equals(state, ARGUMENT_COMMON_VAL_NOT_OFF) || token_equals(state, ARGUMENT_COMMON_VAL_NOT_NO) || token_equals(state, ARGUMENT_COMMON_VAL_NOT_FALSE) || token_equals(state, ARGUMENT_COMMON_VAL_NOT_ZERO) ) return true; else if (token_equals(state, ARGUMENT_COMMON_VAL_TOGGLE)) return !previous_state; else return false; } static inline uint32_t get_set_bit_position(uint32_t mask) { if (mask == 0) return UINT32_MAX; uint32_t pos = 0; while (!(mask & 1)) { mask >>= 1; pos++; } return pos; } static inline void clip_rect(CGContextRef context, CGRect region, float clip, uint32_t corner_radius) { CGMutablePathRef path = CGPathCreateMutable(); if (corner_radius > region.size.height / 2.f || corner_radius > region.size.width / 2.f) corner_radius = region.size.height > region.size.width ? region.size.width / 2.f : region.size.height / 2.f; CGPathAddRoundedRect(path, NULL, region, corner_radius, corner_radius); CGContextSetBlendMode(context, kCGBlendModeDestinationOut); CGContextSetRGBFillColor(context, 0.f, 0.f, 0.f, clip); CGContextAddPath(context, path); CGContextDrawPath(context, kCGPathFillStroke); CGContextSetBlendMode(context, kCGBlendModeNormal); CFRelease(path); } static inline CGRect cgrect_mirror_y(CGRect rect, float y) { CGRect mirrored_rect = rect; mirrored_rect.origin.y = 2*y - rect.origin.y; return mirrored_rect; } static inline bool cgrect_contains_point(CGRect* r, CGPoint* p) { return p->x >= r->origin.x && p->x <= r->origin.x + r->size.width && p->y >= r->origin.y && p->y <= r->origin.y + r->size.height; } static inline char *string_escape_quote(char *s) { if (!s) return NULL; char *cursor = s; int num_quotes = 0; while (*cursor) { if (*cursor == '"') ++num_quotes; ++cursor; } if (!num_quotes) return NULL; int size_in_bytes = (int)(cursor - s) + num_quotes; char *result = malloc(sizeof(char) * (size_in_bytes+1)); result[size_in_bytes] = '\0'; for (char *dst = result, *cursor = s; *cursor; ++cursor) { if (*cursor == '"') *dst++ = '\\'; *dst++ = *cursor; } return result; } static inline CFArrayRef cfarray_of_cfnumbers(void *values, size_t size, int count, CFNumberType type) { CFNumberRef temp[count]; for (int i = 0; i < count; ++i) { temp[i] = CFNumberCreate(NULL, type, ((char *)values) + (size * i)); } CFArrayRef result = CFArrayCreate(NULL, (const void **)temp, count, &kCFTypeArrayCallBacks); for (int i = 0; i < count; ++i) { CFRelease(temp[i]); } return result; } static inline char *cfstring_copy(CFStringRef string) { CFIndex num_bytes = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8); char *result = malloc(num_bytes + 1); if (!result) return NULL; if (!CFStringGetCString(string, result, num_bytes + 1, kCFStringEncodingUTF8)) { free(result); result = NULL; } return result; } static inline char *string_copy(char *s) { int length = strlen(s); char *result = malloc(length + 1); if (!result) return NULL; memcpy(result, s, length); result[length] = '\0'; return result; } static inline char* read_file(char* path) { int fd = open(path, O_RDONLY); int len = lseek(fd, 0, SEEK_END); char* file = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); free(path); return string_copy(file); } static inline char* resolve_path(char* path) { if (!path) return NULL; if (path[0] == '~') { char* home = getenv("HOME"); char buf[512]; snprintf(buf, sizeof(buf), "%s%s", home, &path[1]); free(path); return string_copy(buf); } return path; } static inline bool file_exists(char *filename) { struct stat buffer; if (stat(filename, &buffer) != 0) { return false; } if (buffer.st_mode & S_IFDIR) { return false; } return true; } static inline bool ensure_executable_permission(char *filename) { struct stat buffer; if (stat(filename, &buffer) != 0) { return false; } bool is_executable = buffer.st_mode & S_IXUSR; if (!is_executable && chmod(filename, S_IXUSR | buffer.st_mode) != 0) { return false; } return true; } static inline bool sync_exec(char *command, struct env_vars *env_vars) { if (env_vars) { for (int i = 0; i < env_vars->count; i++) { setenv(env_vars->vars[i]->key, env_vars->vars[i]->value, 1); } } char *exec[] = { "/usr/bin/env", "sh", "-c", command, NULL}; return execvp(exec[0], exec); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" static inline bool fork_exec(char *command, struct env_vars* env_vars) { int pid = vfork(); if (pid == -1) return false; if (pid != 0) return true; alarm(FORK_TIMEOUT); exit(sync_exec(command, env_vars)); } #pragma clang diagnostic pop static inline int mission_control_index(uint64_t sid) { uint64_t result = 0; int desktop_cnt = 1; CFArrayRef display_spaces_ref = SLSCopyManagedDisplaySpaces(g_connection); int display_spaces_count = CFArrayGetCount(display_spaces_ref); for (int i = 0; i < display_spaces_count; ++i) { CFDictionaryRef display_ref = CFArrayGetValueAtIndex(display_spaces_ref, i); CFArrayRef spaces_ref = CFDictionaryGetValue(display_ref, CFSTR("Spaces")); int spaces_count = CFArrayGetCount(spaces_ref); for (int j = 0; j < spaces_count; ++j) { CFDictionaryRef space_ref = CFArrayGetValueAtIndex(spaces_ref, j); CFNumberRef sid_ref = CFDictionaryGetValue(space_ref, CFSTR("id64")); CFNumberGetValue(sid_ref, CFNumberGetType(sid_ref), &result); if (sid == result) goto out; ++desktop_cnt; } } desktop_cnt = 0; out: CFRelease(display_spaces_ref); return desktop_cnt; } static inline uint64_t dsid_from_sid(uint32_t sid) { uint64_t result = 0; int desktop_cnt = 1; CFArrayRef display_spaces_ref = SLSCopyManagedDisplaySpaces(g_connection); int display_spaces_count = CFArrayGetCount(display_spaces_ref); for (int i = 0; i < display_spaces_count; ++i) { CFDictionaryRef display_ref = CFArrayGetValueAtIndex(display_spaces_ref, i); CFArrayRef spaces_ref = CFDictionaryGetValue(display_ref, CFSTR("Spaces")); int spaces_count = CFArrayGetCount(spaces_ref); for (int j = 0; j < spaces_count; ++j) { CFDictionaryRef space_ref = CFArrayGetValueAtIndex(spaces_ref, j); CFNumberRef sid_ref = CFDictionaryGetValue(space_ref, CFSTR("id64")); CFNumberGetValue(sid_ref, CFNumberGetType(sid_ref), &result); if (sid == desktop_cnt) goto out; ++desktop_cnt; } } result = 0; out: CFRelease(display_spaces_ref); return result; } static inline CGImageRef space_capture(uint32_t sid) { uint64_t dsid = dsid_from_sid(sid); CGImageRef image = NULL; if (dsid) { CFArrayRef result = SLSHWCaptureSpace(g_connection, dsid, 0); uint32_t count = CFArrayGetCount(result); if (count > 0) { image = (CGImageRef)CFRetain(CFArrayGetValueAtIndex(result, 0)); } CFRelease(result); } return image; } static inline uint32_t display_id_for_space(uint32_t sid) { uint64_t dsid = dsid_from_sid(sid); if (!dsid) return 0; CFStringRef uuid_string = SLSCopyManagedDisplayForSpace(g_connection, dsid); if (!uuid_string) return 0; CFUUIDRef uuid = CFUUIDCreateFromString(NULL, uuid_string); uint32_t id = CGDisplayGetDisplayIDFromUUID(uuid); CFRelease(uuid); CFRelease(uuid_string); return id; } static inline void error(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(EXIT_FAILURE); } static inline int get_wid_from_cg_event(CGEventRef event) { return CGEventGetIntegerValueField(event, 0x33); } ================================================ FILE: src/mouse.c ================================================ #include #include "mouse.h" static const EventTypeSpec mouse_events [] = { { kEventClassMouse, kEventMouseUp }, { kEventClassMouse, kEventMouseDragged }, { kEventClassMouse, kEventMouseEntered }, { kEventClassMouse, kEventMouseExited }, { kEventClassMouse, kEventMouseWheelMoved }, { kEventClassMouse, kEventMouseScroll } }; static int carbon_event_translation[] = { [kEventMouseUp] = MOUSE_UP, [kEventMouseDragged] = MOUSE_DRAGGED, [kEventMouseEntered] = MOUSE_ENTERED, [kEventMouseExited] = MOUSE_EXITED, [kEventMouseWheelMoved] = MOUSE_SCROLLED, [kEventMouseScroll] = MOUSE_SCROLLED }; static pascal OSStatus mouse_handler(EventHandlerCallRef next, EventRef e, void *data) { enum event_type event_type = carbon_event_translation[GetEventKind(e)]; CGEventRef cg_event = CopyEventCGEvent(e); struct event event = { (void *) cg_event, event_type }; event_post(&event); CFRelease(cg_event); return CallNextEventHandler(next, e); } void mouse_begin(void) { InstallEventHandler(GetEventDispatcherTarget(), NewEventHandlerUPP(mouse_handler), GetEventTypeCount(mouse_events), mouse_events, 0, 0); } ================================================ FILE: src/mouse.h ================================================ #pragma once #include "event.h" void mouse_begin(void); ================================================ FILE: src/popup.c ================================================ #include "popup.h" #include "bar_item.h" #include "bar_manager.h" #include "bar.h" #include "animation.h" void popup_init(struct popup* popup, struct bar_item* host) { popup->drawing = false; popup->horizontal = false; popup->mouse_over = false; popup->overrides_cell_size = false; popup->needs_ordering = false; popup->anchor = (CGPoint){0, 0}; popup->y_offset = 0; popup->adid = 0; popup->align = POSITION_LEFT; popup->blur_radius = 0; popup->topmost = true; popup->num_items = 0; popup->cell_size = 30; popup->items = NULL; popup->host = host; background_init(&popup->background); window_init(&popup->window); color_set_hex(&popup->background.border_color, 0xffff0000); color_set_hex(&popup->background.color, 0x44000000); } static CGRect popup_get_frame(struct popup* popup) { return (CGRect){{popup->anchor.x, popup->anchor.y}, {popup->background.bounds.size.width, popup->background.bounds.size.height}}; } static bool popup_set_blur_radius(struct popup* popup, uint32_t radius) { if (popup->blur_radius == radius) return false; popup->blur_radius = radius; window_set_blur_radius(&popup->window, radius); return false; } static void popup_order_windows(struct popup* popup) { int level = popup->topmost ? (kCGPopUpMenuWindowLevel) : (kCGBackstopMenuLevel + 1); window_set_level(&popup->window, level); window_order(&popup->window, NULL, W_ABOVE); struct window* previous_window = NULL; struct window* first_window = NULL; for (int i = 0; i < popup->num_items; i++) { struct bar_item* bar_item = popup->items[i]; struct window* window = bar_item_get_window(bar_item, popup->adid); window_set_level(window, level); if (!first_window) first_window = window; if (bar_item->type == BAR_COMPONENT_GROUP) { if (first_window) window_order(window, first_window, W_BELOW); else window_order(window, &popup->window, W_ABOVE); continue; } if (previous_window) window_order(window, previous_window, W_ABOVE); else window_order(window, &popup->window, W_ABOVE); previous_window = window; } } static void popup_calculate_popup_anchor_for_bar_item(struct popup* popup, struct bar_item* bar_item, struct bar* bar) { if (popup->adid != g_bar_manager.active_adid) return; struct window* window = bar_item_get_window(bar_item, popup->adid); if (!bar_item->popup.overrides_cell_size) bar_item->popup.cell_size = window->frame.size.height; popup_calculate_bounds(&bar_item->popup, bar); CGPoint anchor = window->origin; if (bar_item->position != POSITION_POPUP || popup->horizontal) { if (bar_item->popup.align == POSITION_CENTER) { anchor.x += (window->frame.size.width - bar_item->popup.background.bounds.size.width) / 2; } else if (bar_item->popup.align == POSITION_LEFT) { anchor.x -= bar_item->background.padding_left; } else { anchor.x += window->frame.size.width - bar_item->popup.background.bounds.size.width; } anchor.y += (g_bar_manager.position == POSITION_BOTTOM ? (- bar_item->popup.background.bounds.size.height) : window->frame.size.height); } else if (bar_item->parent) { struct popup* host = &bar_item->parent->popup; anchor.x = host->window.origin.x; if (bar_item->popup.align == POSITION_LEFT) { anchor.x -= bar_item->popup.background.bounds.size.width; } else { anchor.x += host->window.frame.size.width; } anchor.y -= host->background.border_width; } popup_set_anchor(&bar_item->popup, anchor, popup->adid); } void popup_calculate_bounds(struct popup* popup, struct bar* bar) { uint32_t y = popup->background.border_width; uint32_t x = 0; uint32_t total_item_width = 0; uint32_t width = 0; uint32_t height = 0; if (popup->background.enabled && popup->background.image.enabled) { uint32_t image_width = image_get_size(&popup->background.image).width; width = image_width + 2*popup->background.border_width; } if (popup->horizontal) { for (int j = 0; j < popup->num_items; j++) { struct bar_item* bar_item = popup->items[j]; if (!bar_item->drawing) continue; if (bar_item->type == BAR_COMPONENT_GROUP) continue; uint32_t cell_height = max(bar_item_get_height(bar_item), popup->cell_size ); total_item_width += bar_item->background.padding_right + bar_item->background.padding_left + bar_item_get_length(bar_item, false); if (cell_height > height && popup->horizontal) height = cell_height; } if (popup->background.enabled && popup->background.image.enabled) { uint32_t image_height = image_get_size(&popup->background.image).height; if (image_height > height) height = image_height; x = (width - total_item_width) / 2; } } for (int j = 0; j < popup->num_items; j++) { struct bar_item* bar_item = NULL; bar_item = popup->items[j]; if (!bar_item->drawing) continue; if (bar_item->type == BAR_COMPONENT_GROUP) continue; uint32_t cell_height = max(bar_item_get_height(bar_item), popup->cell_size ); uint32_t item_x = max((int)x + bar_item->background.padding_left, 0); uint32_t item_height = popup->horizontal ? height : cell_height; uint32_t item_y = item_height / 2; uint32_t item_width = bar_item->background.padding_right + bar_item->background.padding_left + bar_item_calculate_bounds(bar_item, item_height, 0, item_y ); uint32_t bar_item_display_length = bar_item_get_length(bar_item, true); if (popup->adid > 0) { CGRect frame = {{popup->anchor.x + item_x, popup->anchor.y + y}, {bar_item_display_length, item_height } }; window_set_frame(bar_item_get_window(bar_item, popup->adid), frame); } if (bar_item->popup.drawing) popup_calculate_popup_anchor_for_bar_item(popup, bar_item, bar); if (item_width > width && !popup->horizontal) width = item_width; if (popup->horizontal) x += item_width; else y += cell_height; } for (int j = 0; j < popup->num_items; j++) { if (popup->adid <= 0) break; struct bar_item* bar_item = NULL; bar_item = popup->items[j]; if (!bar_item->drawing) continue; if (bar_item->type != BAR_COMPONENT_GROUP) continue; uint32_t cell_height = popup->cell_size; if (bar_item->group->num_members > 2) { cell_height = max(bar_item_get_height(bar_item->group->members[1]), popup->cell_size ); } uint32_t item_height = popup->horizontal ? height : cell_height; uint32_t item_y = item_height / 2; group_calculate_bounds(bar_item->group, bar, item_y); window_set_frame(bar_item_get_window(bar_item->group->members[0], popup->adid ), bar_item->group->bounds ); } if (popup->horizontal) { if (!popup->background.enabled || !popup->background.image.enabled) { width = x + popup->background.border_width; } y += height; } else if (!popup->background.enabled || !popup->background.image.enabled) { width += popup->background.border_width; } y += popup->background.border_width; popup->background.bounds.size.width = width; popup->background.bounds.size.height = y; image_calculate_bounds(&popup->background.image, popup->background.border_width, popup->background.border_width + popup->background.image.bounds.size.height / 2); if (popup->adid > 0) window_set_frame(&popup->window, popup_get_frame(popup)); } static void popup_create_window(struct popup* popup) { popup->drawing = true; if (popup == &g_bar_manager.default_item.popup) return; window_create(&popup->window,(CGRect){{popup->anchor.x, popup->anchor.y}, {popup->background.bounds.size.width, popup->background.bounds.size.height}}); if (!popup->background.shadow.enabled) window_disable_shadow(&popup->window); CGContextSetInterpolationQuality(popup->window.context, kCGInterpolationNone); context_set_font_smoothing(popup->window.context, g_bar_manager.font_smoothing); window_set_blur_radius(&popup->window, popup->blur_radius); popup->needs_ordering = true; } static void popup_close_window(struct popup* popup) { if (popup == &g_bar_manager.default_item.popup) return; window_close(&popup->window); } static bool popup_contains_item(struct popup* popup, struct bar_item* bar_item) { for (int i = 0; i < popup->num_items; i++) { if (popup->items[i] == bar_item) return true; } return false; } void popup_add_item(struct popup* popup, struct bar_item* bar_item) { if (popup_contains_item(popup, bar_item)) return; if (bar_item->parent) { popup_remove_item(&bar_item->parent->popup, bar_item); } popup->num_items++; popup->items = realloc(popup->items, sizeof(struct bar_item*)*popup->num_items); popup->items[popup->num_items - 1] = bar_item; bar_item->parent = popup->host; popup->needs_ordering = true; if (popup->num_items == 1){ popup_draw(popup); } } void popup_remove_item(struct popup* popup, struct bar_item* bar_item) { if (popup->num_items == 0 || !popup_contains_item(popup, bar_item)) return; else if (popup->num_items == 1) { free(popup->items); popup->items = NULL; popup->num_items = 0; popup_close_window(popup); return; } struct bar_item* tmp[popup->num_items - 1]; int count = 0; for (int i = 0; i < popup->num_items; i++) { if (popup->items[i] == bar_item) continue; tmp[count++] = popup->items[i]; } popup->num_items--; popup->items = realloc(popup->items, sizeof(struct bar_item*)*popup->num_items); memcpy(popup->items, tmp, sizeof(struct bar_item*)*popup->num_items); } void popup_set_anchor(struct popup* popup, CGPoint anchor, uint32_t adid) { popup->anchor = anchor; popup->anchor.y += popup->y_offset; if ((popup->adid != adid)) { popup->needs_ordering = true; for (int i = 0; i < popup->num_items; i++) { bar_item_needs_update(popup->items[i]); } } popup->adid = adid; } void popup_clear_pointers(struct popup* popup) { popup->items = NULL; popup->num_items = 0; popup->host = NULL; window_clear(&popup->window); } bool popup_set_drawing(struct popup* popup, bool drawing) { if (popup->drawing == drawing) return false; if (!drawing) popup_close_window(popup); popup->drawing = drawing; popup->adid = 0; return true; } void popup_draw(struct popup* popup) { if (!popup->drawing || popup->adid < 1 || popup->num_items == 0) return; windows_freeze(); if (!popup->window.id) popup_create_window(popup); if (!window_apply_frame(&popup->window, false) && !popup->host->needs_update) return; CGContextClearRect(popup->window.context, popup->background.bounds); window_assign_mouse_tracking_area(&popup->window, popup->window.frame); bool shadow = popup->background.shadow.enabled; popup->background.shadow.enabled = false; background_draw(&popup->background, popup->window.context); popup->background.shadow.enabled = shadow; CGContextFlush(popup->window.context); window_flush(&popup->window); if (popup->needs_ordering) { popup_order_windows(popup); popup->needs_ordering = false; } } void popup_change_space(struct popup* popup, uint64_t dsid, uint32_t adid) { for (int i = 0; i < popup->num_items; i++) { struct bar_item* bar_item = popup->items[i]; bar_item_change_space(bar_item, dsid, adid); } if (popup->drawing) { window_send_to_space(&popup->window, dsid); } } void popup_destroy(struct popup* popup) { for (int i = 0; i < popup->num_items; i++) { bar_manager_remove_item(&g_bar_manager, popup->items[i]); } if (popup->items) free(popup->items); background_destroy(&popup->background); popup_close_window(popup); } void popup_serialize(struct popup* popup, char* indent, FILE* rsp) { char align[32] = { 0 }; switch (popup->align) { case POSITION_LEFT: snprintf(align, 32, "left"); break; case POSITION_RIGHT: snprintf(align, 32, "right"); break; case POSITION_CENTER: snprintf(align, 32, "center"); break; case POSITION_BOTTOM: snprintf(align, 32, "bottom"); break; case POSITION_TOP: snprintf(align, 32, "top"); break; default: snprintf(align, 32, "invalid"); break; } fprintf(rsp, "%s\"drawing\": \"%s\",\n" "%s\"horizontal\": \"%s\",\n" "%s\"height\": %d,\n" "%s\"blur_radius\": %u,\n" "%s\"y_offset\": %d,\n" "%s\"align\": \"%s\",\n" "%s\"background\": {\n", indent, format_bool(popup->drawing), indent, format_bool(popup->horizontal), indent, popup->overrides_cell_size ? popup->cell_size : -1, indent, popup->blur_radius, indent, popup->y_offset, indent, align, indent ); char deeper_indent[strlen(indent) + 2]; snprintf(deeper_indent, strlen(indent) + 2, "%s\t", indent); background_serialize(&popup->background, deeper_indent, rsp, true); fprintf(rsp, "\n%s},\n%s\"items\": [\n", indent, indent); for (int i = 0; i < popup->num_items; i++) { fprintf(rsp, "%s\t \"%s\"", indent, popup->items[i]->name); if (i < popup->num_items - 1) fprintf(rsp, ",\n"); } fprintf(rsp, "\n%s]", indent); } static bool popup_set_yoffset(struct popup* popup, int y_offset) { if (popup->y_offset == y_offset) return false; popup->y_offset = y_offset; return true; } static bool popup_set_cell_size(struct popup* popup, int size) { if (popup->cell_size == size && popup->overrides_cell_size) return false; popup->overrides_cell_size = true; popup->cell_size = size; return true; } static bool popup_set_topmost(struct popup* popup, bool topmost) { if (topmost == popup->topmost) return false; popup->topmost = topmost; popup->needs_ordering = true; return true; } bool popup_parse_sub_domain(struct popup* popup, FILE* rsp, struct token property, char* message) { bool needs_refresh = false; if (token_equals(property, PROPERTY_YOFFSET)) { ANIMATE(popup_set_yoffset, popup, popup->y_offset, token_to_int(get_token(&message))); } else if (token_equals(property, PROPERTY_DRAWING)) { return popup_set_drawing(popup, evaluate_boolean_state(get_token(&message), popup->drawing) ); } else if (token_equals(property, PROPERTY_HORIZONTAL)) { popup->horizontal = evaluate_boolean_state(get_token(&message), popup->horizontal ); return true; } else if (token_equals(property, PROPERTY_ALIGN)) { popup->align = get_token(&message).text[0]; return true; } else if (token_equals(property, PROPERTY_HEIGHT)) { ANIMATE(popup_set_cell_size, popup, popup->cell_size, token_to_int(get_token(&message))); } else if (token_equals(property, PROPERTY_BLUR_RADIUS)) { ANIMATE(popup_set_blur_radius, popup, popup->blur_radius, token_to_int(get_token(&message))); return false; } else if (token_equals(property, PROPERTY_TOPMOST)) { return popup_set_topmost(popup, evaluate_boolean_state(get_token(&message), popup->topmost )); } else { struct key_value_pair key_value_pair = get_key_value_pair(property.text, '.' ); if (key_value_pair.key && key_value_pair.value) { struct token subdom = { key_value_pair.key, strlen(key_value_pair.key) }; struct token entry = {key_value_pair.value,strlen(key_value_pair.value)}; if (token_equals(subdom, SUB_DOMAIN_BACKGROUND)) return background_parse_sub_domain(&popup->background, rsp, entry, message ); else { respond(rsp, "[!] Popup: Invalid subdomain '%s'\n", subdom.text); } } else { respond(rsp, "[!] Popup: Invalid property '%s'\n", property.text); } } return needs_refresh; } ================================================ FILE: src/popup.h ================================================ #pragma once #include "background.h" #include "misc/helpers.h" #include "window.h" struct bar_item; struct bar; struct popup { bool drawing; bool horizontal; bool overrides_cell_size; bool mouse_over; bool needs_ordering; bool topmost; char align; uint32_t adid; uint32_t cell_size; uint32_t blur_radius; int y_offset; CGPoint anchor; struct window window; struct bar_item* host; struct bar_item** items; uint32_t num_items; struct background background; }; void popup_init(struct popup* popup, struct bar_item* host); void popup_set_anchor(struct popup* popup, CGPoint anchor, uint32_t adid); void popup_add_item(struct popup* popup, struct bar_item* item); bool popup_set_drawing(struct popup* popup, bool drawing); void popup_remove_item(struct popup* popup, struct bar_item* bar_item); void popup_clear_pointers(struct popup* popup); uint32_t popup_get_width(struct popup* popup); void popup_calculate_bounds(struct popup* popup, struct bar* bar); void popup_draw(struct popup* popup); void popup_destroy(struct popup* popup); void popup_change_space(struct popup* popup, uint64_t dsid, uint32_t adid); void popup_serialize(struct popup* popup, char* indent, FILE* rsp); bool popup_parse_sub_domain(struct popup* popup, FILE* rsp, struct token property, char* message); ================================================ FILE: src/power.c ================================================ #include "power.h" #include "event.h" uint32_t g_power_source = 0; void power_handler(void* context) { CFTypeRef info = IOPSCopyPowerSourcesInfo(); CFStringRef type = IOPSGetProvidingPowerSourceType(info); if (CFStringCompare(type, POWER_AC_KEY, 0) == 0) { if (g_power_source != POWER_AC) { g_power_source = POWER_AC; char source[8]; snprintf(source, 8, "AC"); struct event event = { (void*) source, POWER_SOURCE_CHANGED }; event_post(&event); } } else if (CFStringCompare(type, POWER_BATTERY_KEY, 0) == 0) { if (g_power_source != POWER_BATTERY) { g_power_source = POWER_BATTERY; char source[8]; snprintf(source, 8, "BATTERY"); struct event event = { (void*) source, POWER_SOURCE_CHANGED }; event_post(&event); } } CFRelease(info); } void forced_power_event() { g_power_source = 0; power_handler(NULL); } void begin_receiving_power_events() { CFRunLoopSourceRef source = IOPSNotificationCreateRunLoopSource(power_handler, NULL); CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); } ================================================ FILE: src/power.h ================================================ #include #include #define POWER_AC_KEY CFSTR(kIOPMACPowerKey) #define POWER_BATTERY_KEY CFSTR(kIOPMBatteryPowerKey) #define POWER_UPS_KEY CFSTR(kIOPMUPSPowerKey) #define POWER_AC 1 #define POWER_BATTERY 2 void forced_power_event(); void begin_receiving_power_events(); ================================================ FILE: src/shadow.c ================================================ #include "shadow.h" #include "bar_manager.h" void shadow_init(struct shadow* shadow) { shadow->enabled = false; shadow->angle = 30; shadow->distance = 5; shadow->offset.x = ((float)shadow->distance) *cos(((double)shadow->angle)*deg_to_rad); shadow->offset.y = -((float)shadow->distance) *sin(((double)shadow->angle)*deg_to_rad); color_init(&shadow->color, 0xff000000); } static bool shadow_set_enabled(struct shadow* shadow, bool enabled) { if (shadow->enabled == enabled) return false; shadow->enabled = enabled; return true; } static bool shadow_set_angle(struct shadow* shadow, uint32_t angle) { if (shadow->angle == angle) return false; shadow->angle = angle; shadow->offset.x = ((float)shadow->distance)*cos(((double)shadow->angle)*deg_to_rad); shadow->offset.y = -((float)shadow->distance)*sin(((double)shadow->angle)*deg_to_rad); return true; } static bool shadow_set_distance(struct shadow* shadow, uint32_t distance) { if (shadow->distance == distance) return false; shadow->distance = distance; shadow->offset.x = ((float)shadow->distance) *cos(((double)shadow->angle)*deg_to_rad); shadow->offset.y = -((float)shadow->distance) *sin(((double)shadow->angle)*deg_to_rad); return true; } static bool shadow_set_color(struct shadow* shadow, uint32_t color) { bool changed = shadow_set_enabled(shadow, true); return color_set_hex(&shadow->color, color) || changed; } CGRect shadow_get_bounds(struct shadow* shadow, CGRect reference_bounds) { return (CGRect){{reference_bounds.origin.x + shadow->offset.x, reference_bounds.origin.y + shadow->offset.y }, reference_bounds.size }; } void shadow_serialize(struct shadow* shadow, char* indent, FILE* rsp) { fprintf(rsp, "%s\"drawing\": \"%s\",\n" "%s\"color\": \"0x%x\",\n" "%s\"angle\": %u,\n" "%s\"distance\": %u", indent, format_bool(shadow->enabled), indent, shadow->color.hex, indent, shadow->angle, indent, shadow->distance ); } bool shadow_parse_sub_domain(struct shadow* shadow, FILE* rsp, struct token property, char* message) { bool needs_refresh = false; if (token_equals(property, PROPERTY_DRAWING)) { needs_refresh = shadow_set_enabled(shadow, evaluate_boolean_state(get_token(&message), shadow->enabled )); } else if (token_equals(property, PROPERTY_DISTANCE)) { struct token token = get_token(&message); ANIMATE(shadow_set_distance, shadow, shadow->distance, token_to_int(token) ); } else if (token_equals(property, PROPERTY_ANGLE)) { struct token token = get_token(&message); ANIMATE(shadow_set_angle, shadow, shadow->angle, token_to_int(token)); } else if (token_equals(property, PROPERTY_COLOR)) { struct token token = get_token(&message); ANIMATE_BYTES(shadow_set_color, shadow, shadow->color.hex, token_to_int(token) ); } else { struct key_value_pair key_value_pair = get_key_value_pair(property.text, '.' ); if (key_value_pair.key && key_value_pair.value) { struct token subdom = {key_value_pair.key,strlen(key_value_pair.key)}; struct token entry = {key_value_pair.value,strlen(key_value_pair.value)}; if (token_equals(subdom, SUB_DOMAIN_COLOR)) { return color_parse_sub_domain(&shadow->color, rsp, entry, message); } else { respond(rsp, "[!] Shadow: Invalid subdomain '%s'\n", subdom.text); } } else { respond(rsp, "[!] Shadow: Invalid property '%s'\n", property.text); } } return needs_refresh; } ================================================ FILE: src/shadow.h ================================================ #pragma once #include "misc/helpers.h" #include "color.h" struct shadow { bool enabled; uint32_t angle; uint32_t distance; CGPoint offset; struct color color; }; void shadow_init(struct shadow* shadow); CGRect shadow_get_bounds(struct shadow* shadow, CGRect reference_bounds); void shadow_serialize(struct shadow* shadow, char* indent, FILE* rsp); bool shadow_parse_sub_domain(struct shadow* shadow, FILE* rsp, struct token property, char* message); ================================================ FILE: src/sketchybar.c ================================================ #include "bar_manager.h" #include "event.h" #include "workspace.h" #include "mach.h" #include "mouse.h" #include "message.h" #include "power.h" #include "wifi.h" #include "misc/help.h" #include "media.h" #include "hotload.h" #include #define LCFILE_PATH_FMT "/tmp/%s_%s.lock" #define CLIENT_OPT_LONG "--message" #define CLIENT_OPT_SHRT "-m" #define VERSION_OPT_LONG "--version" #define VERSION_OPT_SHRT "-v" #define CONFIG_OPT_LONG "--config" #define CONFIG_OPT_SHRT "-c" #define HELP_OPT_LONG "--help" #define HELP_OPT_SHRT "-h" #define MAJOR 2 #define MINOR 23 #define PATCH 0 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000 extern CGError SLSWindowManagementBridgeSetDelegate(void* delegate); #endif extern CGError SLSRegisterNotifyProc(void* callback, uint32_t event, void* context); extern int SLSGetSpaceManagementMode(int cid); extern int SLSMainConnectionID(void); extern int RunApplicationEventLoop(void); int g_connection; CFTypeRef g_transaction; int g_space_management_mode; struct bar_manager g_bar_manager; struct mach_server g_mach_server; void *g_workspace_context; char g_name[256]; char g_config_file[4096]; char g_lock_file[MAXLEN]; bool g_volume_events; bool g_brightness_events; int64_t g_disable_capture = 0; pid_t g_pid = 0; static int client_send_message(int argc, char **argv) { if (argc <= 1) { return EXIT_SUCCESS; } char *user = getenv("USER"); if (!user) { error("sketchybar-msg: 'env USER' not set! abort..\n"); } int message_length = argc; int argl[argc]; for (int i = 1; i < argc; ++i) { argl[i] = strlen(argv[i]); message_length += argl[i] + 1; } char* message = malloc((sizeof(char) * (message_length + 1))); char* temp = message; for (int i = 1; i < argc; ++i) { memcpy(temp, argv[i], argl[i]); temp += argl[i]; *temp++ = '\0'; } *temp++ = '\0'; char bs_name[256]; snprintf(bs_name, 256, MACH_BS_NAME_FMT, g_name); char* rsp = mach_send_message(mach_get_bs_port(bs_name), message, message_length, true ); free(message); if (!rsp) return EXIT_SUCCESS; if (strlen(rsp) > 2 && rsp[1] == '!') { fprintf(stderr, "%s", rsp); return EXIT_FAILURE; } else { fprintf(stdout, "%s", rsp); } free(rsp); return EXIT_SUCCESS; } static void acquire_lockfile(void) { int handle = open(g_lock_file, O_CREAT | O_WRONLY, 0600); if (handle == -1) { error("%s: could not create lock-file! abort..\n", g_name); } struct flock lockfd = { .l_start = 0, .l_len = 0, .l_pid = getpid(), .l_type = F_WRLCK, .l_whence = SEEK_SET }; if (fcntl(handle, F_SETLK, &lockfd) == -1) { error("%s: could not acquire lock-file... already running?\n", g_name); } } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" static inline void init_misc_settings(void) { char *user = getenv("USER"); if (!user) { error("%s: 'env USER' not set! abort..\n", g_name); } snprintf(g_lock_file, sizeof(g_lock_file), LCFILE_PATH_FMT, g_name, user); signal(SIGCHLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); CGSetLocalEventsSuppressionInterval(0.0f); CGEnableEventStateCombining(false); g_connection = SLSMainConnectionID(); g_space_management_mode = SLSGetSpaceManagementMode(g_connection); g_volume_events = false; g_brightness_events = false; } #pragma clang diagnostic pop static void parse_arguments(int argc, char **argv) { if ((string_equals(argv[1], VERSION_OPT_LONG)) || (string_equals(argv[1], VERSION_OPT_SHRT))) { fprintf(stdout, "sketchybar-v%d.%d.%d\n", MAJOR, MINOR, PATCH); exit(EXIT_SUCCESS); } else if ((string_equals(argv[1], HELP_OPT_LONG)) || (string_equals(argv[1], HELP_OPT_SHRT))) { printf(help_str, argv[0]); exit(EXIT_SUCCESS); } else if ((string_equals(argv[1], CLIENT_OPT_LONG)) || (string_equals(argv[1], CLIENT_OPT_SHRT))) { exit(client_send_message(argc-1, argv+1)); } else if ((string_equals(argv[1], CONFIG_OPT_LONG)) || (string_equals(argv[1], CONFIG_OPT_SHRT))) { if (argc < 3) { printf("[!] Error: Too few arguments for argument 'config'.\n"); } else { if (set_config_file_path(argv[2])) return; printf("[!] Error: Specified config file path invalid.\n"); } exit(EXIT_FAILURE); } exit(client_send_message(argc, argv)); } static void space_events(uint32_t event, void* data, size_t data_length, void* context) { struct event ev = { NULL, SPACE_CHANGED }; event_post(&ev); } static void system_events(uint32_t event, void* data, size_t data_length, void* context) { if (event == 1322) { g_disable_capture = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW_APPROX); } else if (event == 905) { g_disable_capture = -1; } else { g_disable_capture = 0; } } int main(int argc, char **argv) { snprintf(g_name, sizeof(g_name), "%s", basename(argv[0])); if (is_root()) error("%s: running as root is not allowed! abort..\n", g_name); setenv("BAR_NAME", g_name, 1); if (argc > 1) parse_arguments(argc, argv); pid_for_task(mach_task_self(), &g_pid); init_misc_settings(); acquire_lockfile(); SLSRegisterNotifyProc((void*)system_events, 904, NULL); SLSRegisterNotifyProc((void*)system_events, 905, NULL); SLSRegisterNotifyProc((void*)system_events, 1401, NULL); SLSRegisterNotifyProc((void*)system_events, 1508, NULL); SLSRegisterNotifyProc((void*)system_events, 1322, NULL); if (__builtin_available(macOS 13.0, *)) { SLSRegisterNotifyProc((void*)space_events, 1327, NULL); SLSRegisterNotifyProc((void*)space_events, 1328, NULL); } struct event init = { NULL, INIT_MUTEX }; event_post(&init); workspace_event_handler_init(&g_workspace_context); bar_manager_init(&g_bar_manager); mouse_begin(); display_begin(); workspace_event_handler_begin(&g_workspace_context); windows_freeze(); bar_manager_begin(&g_bar_manager); windows_unfreeze(); if (!mach_server_begin(&g_mach_server, mach_message_handler)) error("%s: could not initialize daemon! abort..\n", g_name); begin_receiving_power_events(); begin_receiving_network_events(); initialize_media_events(); exec_config_file(); begin_receiving_config_change_events(); #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000 if (__builtin_available(macos 14.0, *)) { SLSWindowManagementBridgeSetDelegate(NULL); } #endif RunApplicationEventLoop(); return 0; } ================================================ FILE: src/slider.c ================================================ #include "slider.h" #include "bar_manager.h" #include "animation.h" static bool slider_set_width(struct slider* slider, uint32_t width) { if (width == slider->background.bounds.size.width) return false; slider->background.bounds.size.width = width; return true; } static bool slider_set_foreground_color(struct slider* slider, uint32_t color) { if (slider->foreground_color == color) return false; slider->foreground_color = color; return background_set_color(&slider->foreground, color); } static bool slider_set_percentage(struct slider* slider, uint32_t percentage) { if (percentage == slider->percentage) return false; slider->percentage = max(min(percentage, 100), 0); return true; } uint32_t slider_get_percentage_for_point(struct slider* slider, CGPoint point) { float delta = point.x - slider->background.bounds.origin.x; if (delta < 0) delta = 0; uint32_t percentage = delta / slider->background.bounds.size.width * 100.f + 0.5f; return min(percentage, 100); } void slider_cancel_drag(struct slider* slider) { slider->is_dragged = false; } bool slider_handle_drag(struct slider* slider, CGPoint point) { uint32_t percentage = slider_get_percentage_for_point(slider, point); slider->is_dragged = true; return slider_set_percentage(slider, percentage); } void slider_init(struct slider* slider) { slider->percentage = 0; slider->background.bounds.size.width = 100; slider->is_dragged = false; slider->foreground_color = 0xff0000ff; text_init(&slider->knob); background_init(&slider->background); background_init(&slider->foreground); background_set_color(&slider->background, 0xff000000); background_set_color(&slider->foreground, slider->foreground_color); } void slider_clear_pointers(struct slider* slider) { background_clear_pointers(&slider->background); background_clear_pointers(&slider->foreground); text_clear_pointers(&slider->knob); } void slider_setup(struct slider* slider, uint32_t width) { slider->background.bounds.size.width = width; background_set_enabled(&slider->background, true); background_set_enabled(&slider->foreground, true); } uint32_t slider_get_length(struct slider* slider) { return slider->background.bounds.size.width; } void slider_calculate_bounds(struct slider* slider, uint32_t x, uint32_t y) { background_calculate_bounds(&slider->background, x, y, slider->background.bounds.size.width, slider->background.bounds.size.height); background_calculate_bounds(&slider->foreground, x, y, slider->background.bounds.size.width * ((float)slider->percentage)/100.f, slider->background.bounds.size.height); int32_t raw_offset = ((float)slider->percentage)/100.f * slider->background.bounds.size.width - slider->knob.bounds.size.width / 2.; uint32_t knob_offset = max(min(raw_offset, slider->background.bounds.size.width - (slider->knob.bounds.size.width + 1.f)), 0.f); text_calculate_bounds(&slider->knob, x + knob_offset, y); } void slider_draw(struct slider* slider, CGContextRef context) { background_draw(&slider->background, context); background_draw(&slider->foreground, context); text_draw(&slider->knob, context); } void slider_destroy(struct slider* slider) { background_destroy(&slider->background); background_destroy(&slider->foreground); text_destroy(&slider->knob); slider_clear_pointers(slider); } void slider_serialize(struct slider* slider, char* indent, FILE* rsp) { fprintf(rsp, "%s\"highlight_color\": \"0x%x\",\n" "%s\"percentage\": \"%d\",\n" "%s\"width\": \"%d\",\n", indent, slider->foreground_color, indent, slider->percentage, indent, (int)slider->background.bounds.size.width); char deeper_indent[strlen(indent) + 2]; snprintf(deeper_indent, strlen(indent) + 2, "%s\t", indent); fprintf(rsp, "%s\"background\": {\n", indent); background_serialize(&slider->background, deeper_indent, rsp, false); fprintf(rsp, "\n%s},\n", indent); fprintf(rsp, "%s\"knob\": {\n", indent); text_serialize(&slider->knob, deeper_indent, rsp); fprintf(rsp, "\n%s}", indent); } bool slider_parse_sub_domain(struct slider* slider, FILE* rsp, struct token property, char* message) { bool needs_refresh = false; if (token_equals(property, PROPERTY_PERCENTAGE)) { struct token token = get_token(&message); if (!slider->is_dragged) { ANIMATE(slider_set_percentage, slider, slider->percentage, token_to_uint32t(token)); } } else if (token_equals(property, PROPERTY_HIGHLIGHT_COLOR)) { struct token token = get_token(&message); ANIMATE_BYTES(slider_set_foreground_color, slider, slider->foreground_color, token_to_uint32t(token) ); } else if (token_equals(property, PROPERTY_WIDTH)) { struct token token = get_token(&message); if (!slider->is_dragged) { ANIMATE(slider_set_width, slider, slider->background.bounds.size.width, token_to_uint32t(token) ); } } else if (token_equals(property, SUB_DOMAIN_KNOB)) { struct token dummy = { PROPERTY_STRING, strlen(PROPERTY_STRING)}; needs_refresh = text_parse_sub_domain(&slider->knob, rsp, dummy, message ); } else { struct key_value_pair key_value_pair = get_key_value_pair(property.text, '.'); if (key_value_pair.key && key_value_pair.value) { struct token subdom = { key_value_pair.key, strlen(key_value_pair.key) }; struct token entry = { key_value_pair.value, strlen(key_value_pair.value) }; if (token_equals(subdom, SUB_DOMAIN_BACKGROUND)) { background_parse_sub_domain(&slider->foreground, rsp, entry, message); background_set_color(&slider->foreground, slider->foreground_color); return background_parse_sub_domain(&slider->background, rsp, entry, message); } else if (token_equals(subdom, SUB_DOMAIN_KNOB)) return text_parse_sub_domain(&slider->knob, rsp, entry, message); else { respond(rsp, "[!] Slider: Invalid subdomain '%s' \n", subdom.text); } } else { respond(rsp, "[!] Slider: Invalid property '%s'\n", property.text); } } return needs_refresh; } ================================================ FILE: src/slider.h ================================================ #pragma once #include "background.h" #include "text.h" struct slider { bool is_dragged; uint32_t percentage; uint32_t foreground_color; struct text knob; struct background background; struct background foreground; }; void slider_init(struct slider* slider); void slider_clear_pointers(struct slider* slider); void slider_setup(struct slider* slider, uint32_t width); void slider_calculate_bounds(struct slider* slider, uint32_t x, uint32_t y); void slider_draw(struct slider* slider, CGContextRef context); bool slider_handle_drag(struct slider* slider, CGPoint point); uint32_t slider_get_percentage_for_point(struct slider* slider, CGPoint point); uint32_t slider_get_length(struct slider* slider); void slider_cancel_drag(struct slider* slider); void slider_destroy(struct slider* slider); void slider_serialize(struct slider* slider, char* indent, FILE* rsp); bool slider_parse_sub_domain(struct slider* graph, FILE* rsp, struct token property, char* message); ================================================ FILE: src/text.c ================================================ #include "text.h" #include "bar_manager.h" static void text_calculate_truncated_width(struct text* text, CFDictionaryRef attributes) { if (text->max_chars > 0) { uint32_t len = strlen(text->string) + 4; char buffer[len]; memset(buffer, 0, len); char* read = text->string; char* write = buffer; uint32_t counter = 0; while (*read) { if ((*read & 0xC0) != 0x80) counter++; if (counter > text->max_chars) { break; } *write++ = *read++; } CFStringRef string = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); if (string) { CFAttributedStringRef attr_string = CFAttributedStringCreate(NULL, string, attributes); CTLineRef line = CTLineCreateWithAttributedString(attr_string); CGRect bounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsUseGlyphPathBounds); text->width = (uint32_t)(bounds.size.width + 1.5); CFRelease(attr_string); CFRelease(line); CFRelease(string); } } } static void text_prepare_line(struct text* text) { const void *keys[] = { kCTFontAttributeName, kCTForegroundColorFromContextAttributeName }; if (text->font.font_changed) { font_create_ctfont(&text->font); text->font.font_changed = false; } const void *values[] = { text->font.ct_font, kCFBooleanTrue }; CFDictionaryRef attributes = CFDictionaryCreate(NULL, keys, values, array_count(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef string = CFStringCreateWithCString(NULL, text->string, kCFStringEncodingUTF8); if (!string) string = CFStringCreateWithCString(NULL, "Warning: Malformed UTF-8 string", kCFStringEncodingUTF8 ); CFAttributedStringRef attr_string = CFAttributedStringCreate(NULL, string, attributes); text->line.line = CTLineCreateWithAttributedString(attr_string); CTLineGetTypographicBounds(text->line.line, &text->line.ascent, &text->line.descent, NULL ); text->bounds = CTLineGetBoundsWithOptions(text->line.line, kCTLineBoundsUseGlyphPathBounds); text->bounds.size.width = (uint32_t) (text->bounds.size.width + 1.5); text->bounds.size.height = (uint32_t) (text->bounds.size.height + 1.5); text->bounds.origin.x = (int32_t) (text->bounds.origin.x + 0.5); text->bounds.origin.y = (int32_t) (text->bounds.origin.y + 0.5); text->width = text->bounds.size.width; CFRelease(string); CFRelease(attr_string); text_calculate_truncated_width(text, attributes); CFRelease(attributes); } static void text_destroy_line(struct text* text) { if (text->line.line) CFRelease(text->line.line); text->line.line = NULL; } bool text_set_max_chars(struct text* text, uint32_t max_chars) { if (text->max_chars == max_chars) return false; text->max_chars = max_chars; if (strlen(text->string) > text->max_chars) { text_set_string(text, text->string, true); } return strlen(text->string) > text->max_chars; } bool text_set_string(struct text* text, char* string, bool forced) { if (!string) return false; if (!forced && text->string && strcmp(text->string, string) == 0) { if (!(string == text->string)) free(string); return false; } if (text->line.line) text_destroy_line(text); if (string != text->string && text->string) free(text->string); text->string = string; text_prepare_line(text); return true; } void text_copy(struct text* text, struct text* source) { font_set_family(&text->font, string_copy(source->font.family), true); font_set_style(&text->font, string_copy(source->font.style), true); font_set_size(&text->font, source->font.size); text_set_string(text, string_copy(source->string), true); } bool text_set_font(struct text* text, char* font_string, bool forced) { bool changed = font_set(&text->font, font_string, forced); return changed; } void text_init(struct text* text) { text->drawing = true; text->highlight = false; text->has_const_width = false; text->custom_width = 0; text->padding_left = 0; text->padding_right = 0; text->y_offset = 0; text->max_chars = 0; text->align = POSITION_LEFT; text->scroll = 0.f; text->scroll_duration = 100; text->string = string_copy(""); text_set_string(text, text->string, false); shadow_init(&text->shadow); background_init(&text->background); font_init(&text->font); color_init(&text->color, 0xffffffff); color_init(&text->highlight_color, 0xff000000); } static bool text_set_color(struct text* text, uint32_t color) { return color_set_hex(&text->color, color); } static bool text_set_highlight_color(struct text* text, uint32_t color) { return color_set_hex(&text->highlight_color, color); } static bool text_set_padding_left(struct text* text, int padding) { if (text->padding_left == padding) return false; text->padding_left = padding; return true; } static bool text_set_padding_right(struct text* text, int padding) { if (text->padding_right == padding) return false; text->padding_right = padding; return true; } static bool text_set_yoffset(struct text* text, int offset) { if (text->y_offset == offset) return false; text->y_offset = offset; return true; } static bool text_set_scroll_duration(struct text* text, int duration) { if (duration < 0) return false; text->scroll_duration = duration; return false; } static bool text_set_width(struct text* text, int width) { if (width < 0) { bool prev = text->has_const_width; text->has_const_width = false; return prev != text->has_const_width; } if (text->custom_width == width && text->has_const_width) return false; text->custom_width = width; text->has_const_width = true; return true; } void text_clear_pointers(struct text* text) { text->string = NULL; text->line.line = NULL; background_clear_pointers(&text->background); font_clear_pointers(&text->font); } uint32_t text_get_length(struct text* text, bool override) { if (!text->drawing) return 0; if (text->font.font_changed) { text_set_string(text, text->string, true); } int len = text->width + text->padding_left + text->padding_right; if ((!text->has_const_width || override) && text->background.enabled && text->background.image.enabled) { CGSize image_size = image_get_size(&text->background.image); if (image_size.width > len) { return image_size.width; } } if (text->has_const_width && !override) return text->custom_width; return (len < 0 ? 0 : len); } uint32_t text_get_height(struct text* text) { return text->drawing ? text->bounds.size.height : 0; } void text_destroy(struct text* text) { background_destroy(&text->background); font_destroy(&text->font); if (text->string) free(text->string); text_destroy_line(text); text_clear_pointers(text); } void text_calculate_bounds(struct text* text, uint32_t x, uint32_t y) { if (text->align == POSITION_CENTER && text->has_const_width) text->bounds.origin.x = (int)x + ((int)text->custom_width - (int)text_get_length(text, true)) / 2; else if (text->align == POSITION_RIGHT && text->has_const_width) text->bounds.origin.x = (int)x + (int)text->custom_width - (int)text_get_length(text, true); else text->bounds.origin.x = x; text->bounds.origin.y =(uint32_t)(y - ((text->line.ascent - text->line.descent) / 2)); if (text->background.enabled) { uint32_t height = text->background.overrides_height ? text->background.bounds.size.height : text->bounds.size.height; background_calculate_bounds(&text->background, x, y, text_get_length(text, false), height ); } } bool text_set_scroll(struct text* text, float scroll) { if (text->scroll == scroll) return false; text->scroll = scroll; return true; } bool text_animate_scroll(struct text* text) { if (text->max_chars == 0) return false; if (text->scroll != 0) return false; if (text->has_const_width && text->custom_width < text->width) return false; if (text->width == 0 || text->width == text->bounds.size.width) return false; g_bar_manager.animator.duration = text->scroll_duration * (text->bounds.size.width / text->width); g_bar_manager.animator.interp_function = INTERP_FUNCTION_LINEAR; bool needs_refresh = false; ANIMATE_FLOAT(text_set_scroll, text, text->scroll, max(text->bounds.size.width, 0)); struct animation* animation = animation_create(); float initial_value = text->scroll; float final_value = -max(text->width, 0); animation_setup(animation, (void*)text, (bool (*)(void*, int))text_set_scroll, *(int*)&initial_value, *(int*)&final_value, 0, INTERP_FUNCTION_LINEAR ); animation->as_float = true; animator_add(&g_bar_manager.animator, animation); g_bar_manager.animator.duration = text->scroll_duration; ANIMATE_FLOAT(text_set_scroll, text, text->scroll, 0); g_bar_manager.animator.duration = 0; g_bar_manager.animator.interp_function = '\0'; return needs_refresh; } void text_draw(struct text* text, CGContextRef context) { if (!text->drawing) return; if (text->background.enabled) background_draw(&text->background, context); CGContextSaveGState(context); if (text->max_chars > 0) { CGMutablePathRef path = CGPathCreateMutable(); CGRect bounds = text->bounds; bounds.size.width = text->width; bounds.origin.x += text->padding_left; bounds.origin.y = -9999.f; bounds.size.height = 2.f*9999.f; CGPathAddRect(path, NULL, bounds); CGContextAddPath(context, path); CGContextClip(context); CFRelease(path); } if (text->shadow.enabled) { CGContextSetRGBFillColor(context, text->shadow.color.r, text->shadow.color.g, text->shadow.color.b, text->shadow.color.a ); CGRect bounds = shadow_get_bounds(&text->shadow, text->bounds); CGContextSetTextPosition(context, bounds.origin.x + text->padding_left, bounds.origin.y + text->y_offset ); CTLineDraw(text->line.line, context); } struct color color = text->highlight ? text->highlight_color : text->color; CGContextSetRGBFillColor(context, color.r, color.g, color.b, color.a); CGContextSetTextPosition(context, text->bounds.origin.x + text->padding_left - text->scroll, text->bounds.origin.y + text->y_offset ); CTLineDraw(text->line.line, context); CGContextRestoreGState(context); } void text_serialize(struct text* text, char* indent, FILE* rsp) { char align[32] = { 0 }; switch (text->align) { case POSITION_LEFT: snprintf(align, 32, "left"); break; case POSITION_RIGHT: snprintf(align, 32, "right"); break; case POSITION_CENTER: snprintf(align, 32, "center"); break; case POSITION_BOTTOM: snprintf(align, 32, "bottom"); break; case POSITION_TOP: snprintf(align, 32, "top"); break; default: snprintf(align, 32, "invalid"); break; } fprintf(rsp, "%s\"value\": \"%s\",\n" "%s\"drawing\": \"%s\",\n" "%s\"highlight\": \"%s\",\n" "%s\"color\": \"0x%x\",\n" "%s\"highlight_color\": \"0x%x\",\n" "%s\"padding_left\": %d,\n" "%s\"padding_right\": %d,\n" "%s\"y_offset\": %d,\n" "%s\"font\": \"%s:%s:%.2f\",\n" "%s\"width\": %d,\n" "%s\"scroll_duration\": %d,\n" "%s\"align\": \"%s\",\n" "%s\"background\": {\n", indent, text->string, indent, format_bool(text->drawing), indent, format_bool(text->highlight), indent, text->color.hex, indent, text->highlight_color.hex, indent, text->padding_left, indent, text->padding_right, indent, text->y_offset, indent, text->font.family, text->font.style, text->font.size, indent, text->custom_width, indent, text->scroll_duration, indent, align, indent ); char deeper_indent[strlen(indent) + 2]; snprintf(deeper_indent, strlen(indent) + 2, "%s\t", indent); background_serialize(&text->background, deeper_indent, rsp, true); fprintf(rsp, "\n%s},\n%s\"shadow\": {\n", indent, indent); shadow_serialize(&text->shadow, deeper_indent, rsp); fprintf(rsp, "\n%s}", indent); } bool text_parse_sub_domain(struct text* text, FILE* rsp, struct token property, char* message) { bool needs_refresh = false; if (token_equals(property, PROPERTY_COLOR)) { struct token token = get_token(&message); ANIMATE_BYTES(text_set_color, text, text->color.hex, token_to_int(token)); } else if (token_equals(property, PROPERTY_HIGHLIGHT)) { bool highlight = evaluate_boolean_state(get_token(&message), text->highlight ); if (g_bar_manager.animator.duration > 0) { if (text->highlight && !highlight) { animator_cancel(&g_bar_manager.animator, text, (animator_function*)text_set_color); uint32_t target = text->color.hex; text_set_color(text, text->highlight_color.hex); ANIMATE_BYTES(text_set_color, text, text->color.hex, target ); } else if (!text->highlight && highlight) { animator_cancel(&g_bar_manager.animator, text, (animator_function*)text_set_highlight_color); uint32_t target = text->highlight_color.hex; text_set_highlight_color(text, text->color.hex); ANIMATE_BYTES(text_set_highlight_color, text, text->highlight_color.hex, target ); } } needs_refresh = text->highlight != highlight; text->highlight = highlight; } else if (token_equals(property, PROPERTY_FONT)) needs_refresh = text_set_font(text, string_copy(message), false); else if (token_equals(property, PROPERTY_HIGHLIGHT_COLOR)) { struct token token = get_token(&message); ANIMATE_BYTES(text_set_highlight_color, text, text->highlight_color.hex, token_to_int(token) ); } else if (token_equals(property, PROPERTY_PADDING_LEFT)) { struct token token = get_token(&message); ANIMATE(text_set_padding_left, text, text->padding_left, token_to_int(token) ); } else if (token_equals(property, PROPERTY_PADDING_RIGHT)) { struct token token = get_token(&message); ANIMATE(text_set_padding_right, text, text->padding_right, token_to_int(token) ); } else if (token_equals(property, PROPERTY_YOFFSET)) { struct token token = get_token(&message); ANIMATE(text_set_yoffset, text, text->y_offset, token_to_int(token)); } else if (token_equals(property, PROPERTY_SCROLL_DURATION)) { struct token token = get_token(&message); text_set_scroll_duration(text, token_to_int(token)); } else if (token_equals(property, PROPERTY_WIDTH)) { struct token token = get_token(&message); if (token_equals(token, ARGUMENT_DYNAMIC)) { ANIMATE(text_set_width, text, text->custom_width, text_get_length(text, true)); struct animation* animation = animation_create(); animation_setup(animation, text, (bool (*)(void*, int))&text_set_width, text->custom_width, -1, 0, INTERP_FUNCTION_LINEAR ); animator_add(&g_bar_manager.animator, animation); } else { ANIMATE(text_set_width, text, text_get_length(text, false), token_to_int(token) ); } } else if (token_equals(property, PROPERTY_DRAWING)) { bool prev = text->drawing; text->drawing = evaluate_boolean_state(get_token(&message), text->drawing); return prev != text->drawing; } else if (token_equals(property, PROPERTY_ALIGN)) { char prev = text->align; text->align = get_token(&message).text[0]; return prev != text->align; } else if (token_equals(property, PROPERTY_STRING)) { uint32_t pre_width = text_get_length(text, false); bool changed = text_set_string(text, token_to_string(get_token(&message)), false ); if (changed && g_bar_manager.animator.duration > 0) { uint32_t post_width = text_get_length(text, false); if (post_width != pre_width) { text_set_width(text, pre_width); ANIMATE(text_set_width, text, pre_width, post_width); struct animation* animation = animation_create(); animation_setup(animation, text, (bool (*)(void*, int))&text_set_width, text->custom_width, -1, 0, INTERP_FUNCTION_LINEAR ); animator_add(&g_bar_manager.animator, animation); } } return changed; } else if (token_equals(property, PROPERTY_MAX_CHARS)) { return text_set_max_chars(text, token_to_int(get_token(&message))); } else { struct key_value_pair key_value_pair = get_key_value_pair(property.text, '.' ); if (key_value_pair.key && key_value_pair.value) { struct token subdom = { key_value_pair.key, strlen(key_value_pair.key) }; struct token entry = { key_value_pair.value, strlen(key_value_pair.value) }; if (token_equals(subdom, SUB_DOMAIN_BACKGROUND)) return background_parse_sub_domain(&text->background, rsp, entry, message ); else if (token_equals(subdom, SUB_DOMAIN_SHADOW)) return shadow_parse_sub_domain(&text->shadow, rsp, entry, message); else if (token_equals(subdom, SUB_DOMAIN_FONT)) return font_parse_sub_domain(&text->font, rsp, entry, message); else if (token_equals(subdom, SUB_DOMAIN_COLOR)) return color_parse_sub_domain(&text->color, rsp, entry, message); else if (token_equals(subdom, SUB_DOMAIN_HIGHLIGHT_COLOR)) return color_parse_sub_domain(&text->highlight_color, rsp, entry, message); else respond(rsp, "[!] Text: Invalid subdomain '%s' \n", subdom.text); } else { respond(rsp, "[!] Text: Invalid property '%s'\n", property.text); } } return needs_refresh; } ================================================ FILE: src/text.h ================================================ #pragma once #include #include "background.h" #include "font.h" struct text_line { CTLineRef line; CGFloat ascent; CGFloat descent; }; struct text { bool highlight; bool drawing; bool has_const_width; char align; char* string; int y_offset; int padding_left; int padding_right; uint32_t custom_width; uint32_t max_chars; uint32_t scroll_duration; float scroll; float width; CGRect bounds; struct font font; struct text_line line; struct color color; struct color highlight_color; struct shadow shadow; struct background background; }; void text_init(struct text* text); void text_clear_pointers(struct text* text); uint32_t text_get_length(struct text* text, bool override); uint32_t text_get_height(struct text* text); bool text_set_string(struct text* text, char* string, bool forced); bool text_set_font(struct text* text, char* font_string, bool forced); void text_copy(struct text* text, struct text* source); bool text_animate_scroll(struct text* text); void text_calculate_bounds(struct text* text, uint32_t x, uint32_t y); void text_draw(struct text* text, CGContextRef context); void text_destroy(struct text* text); void text_serialize(struct text* text, char* indent, FILE* rsp); bool text_parse_sub_domain(struct text* text, FILE* rsp, struct token property, char* message); ================================================ FILE: src/volume.c ================================================ #include "volume.h" #include "event.h" extern bool g_volume_events; #if __MAC_OS_X_VERSION_MAX_ALLOWED < 120000 #define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster #endif static AudioObjectPropertyAddress kHardwareDevicePropertyAddress = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; static AudioObjectPropertyAddress kVolumeMainPropertyAddress = { kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }; static AudioObjectPropertyAddress kVolumeLeftPropertyAddress = { kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, 1 }; static AudioObjectPropertyAddress kMuteMainPropertyAddress = { kAudioDevicePropertyMute, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }; static AudioObjectPropertyAddress kMuteLeftPropertyAddress = { kAudioDevicePropertyMute, kAudioObjectPropertyScopeOutput, 1 }; static float g_last_volume = -1.f; static OSStatus handler(AudioObjectID id, uint32_t address_count, const AudioObjectPropertyAddress* addresses, void* context) { float v = 0; float* volume = &v; uint32_t muted_main = 0; uint32_t size = sizeof(uint32_t); AudioObjectGetPropertyData(id, &kMuteMainPropertyAddress, 0, NULL, &size, &muted_main ); uint32_t muted_left = 0; size = sizeof(uint32_t); AudioObjectGetPropertyData(id, &kMuteLeftPropertyAddress, 0, NULL, &size, &muted_left ); size = sizeof(float); float volume_main = 0.f; AudioObjectGetPropertyData(id, &kVolumeMainPropertyAddress, 0, NULL, &size, &volume_main ); size = sizeof(float); float volume_left = 0.f; AudioObjectGetPropertyData(id, &kVolumeLeftPropertyAddress, 0, NULL, &size, &volume_left ); if (volume_left > 0.f) { *volume = (muted_left || muted_main) ? 0.f : volume_left; } else { *volume = muted_main ? 0.f : volume_main; } if (*volume > g_last_volume + 1e-2 || *volume < g_last_volume - 1e-2) { g_last_volume = *volume; struct event event = { (void*) volume, VOLUME_CHANGED }; event_post(&event); } return KERN_SUCCESS; } static AudioObjectID g_audio_id = 0; OSStatus device_changed(AudioObjectID id, uint32_t address_count, const AudioObjectPropertyAddress* addresses, void* context) { AudioObjectID new_id = 0; uint32_t size = sizeof(AudioObjectID); AudioObjectGetPropertyData(kAudioObjectSystemObject, &kHardwareDevicePropertyAddress, 0, NULL, &size, &new_id ); if (g_audio_id) { AudioObjectRemovePropertyListener(g_audio_id, &kMuteMainPropertyAddress, &handler, NULL ); AudioObjectRemovePropertyListener(g_audio_id, &kMuteLeftPropertyAddress, &handler, NULL ); AudioObjectRemovePropertyListener(g_audio_id, &kVolumeMainPropertyAddress, &handler, NULL ); AudioObjectRemovePropertyListener(g_audio_id, &kVolumeLeftPropertyAddress, &handler, NULL ); } AudioObjectAddPropertyListener(new_id, &kMuteMainPropertyAddress, &handler, NULL ); AudioObjectAddPropertyListener(new_id, &kMuteLeftPropertyAddress, &handler, NULL ); AudioObjectAddPropertyListener(new_id, &kVolumeMainPropertyAddress, &handler, NULL ); AudioObjectAddPropertyListener(new_id, &kVolumeLeftPropertyAddress, &handler, NULL ); g_last_volume = -1.f; g_audio_id = new_id; handler(g_audio_id, address_count, addresses, context); return KERN_SUCCESS; } void forced_volume_event() { g_last_volume = -1.f; handler(g_audio_id, 0, 0, 0); } void begin_receiving_volume_events() { if (g_volume_events) return; g_volume_events = true; AudioObjectID id = 0; uint32_t size = sizeof(AudioObjectID); AudioObjectGetPropertyData(kAudioObjectSystemObject, &kHardwareDevicePropertyAddress, 0, NULL, &size, &id ); g_audio_id = id; AudioObjectAddPropertyListener(id, &kMuteLeftPropertyAddress, &handler, NULL ); AudioObjectAddPropertyListener(id, &kMuteMainPropertyAddress, &handler, NULL ); AudioObjectAddPropertyListener(id, &kVolumeLeftPropertyAddress, &handler, NULL ); AudioObjectAddPropertyListener(id, &kVolumeMainPropertyAddress, &handler, NULL ); AudioObjectAddPropertyListener(kAudioObjectSystemObject, &kHardwareDevicePropertyAddress, &device_changed, NULL ); } ================================================ FILE: src/volume.h ================================================ #include "CoreAudio/CoreAudio.h" #include void forced_volume_event(); void begin_receiving_volume_events(); ================================================ FILE: src/wifi.h ================================================ void forced_network_event(); void begin_receiving_network_events(); ================================================ FILE: src/wifi.m ================================================ #include #include #include "wifi.h" #include "event.h" void update_ssid(SCDynamicStoreRef store, CFArrayRef keys, void* info) { @autoreleasepool { NSData* data = [[[CWWiFiClient sharedWiFiClient] interface] ssidData]; char ssid[[data length] + 1]; memcpy(ssid, [data bytes], [data length]); ssid[[data length]] = '\0'; struct event event = { (void*) ssid, WIFI_CHANGED }; event_post(&event); } } void forced_network_event() { update_ssid(NULL, NULL, NULL); } void begin_receiving_network_events() { SCDynamicStoreContext context = { 0, NULL, NULL, NULL, NULL }; SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("network"), update_ssid, &context ); const void* values[] = { CFSTR(".*/Network/Global/IPv4") }; CFArrayRef keys = CFArrayCreate(NULL, values, 1, &kCFTypeArrayCallBacks); SCDynamicStoreSetNotificationKeys(store, NULL, keys); CFRunLoopSourceRef loop_source = SCDynamicStoreCreateRunLoopSource(NULL, store, 0 ); CFRunLoopAddSource(CFRunLoopGetCurrent(), loop_source, kCFRunLoopDefaultMode ); CFRelease(keys); } ================================================ FILE: src/window.c ================================================ #include "window.h" #include "bar_manager.h" #include "misc/helpers.h" extern struct bar_manager g_bar_manager; extern int64_t g_disable_capture; int g_space = 0; void window_init(struct window* window) { window->context = NULL; window->parent = NULL; window->frame = CGRectNull; window->id = 0; window->origin = CGPointZero; window->surface_id = 0; window->needs_move = false; window->needs_resize = false; window->order_mode = W_ABOVE; } static CFTypeRef window_create_region(struct window* window, CGRect frame) { CFTypeRef frame_region; CGSNewRegionWithRect(&frame, &frame_region); return frame_region; } void window_create(struct window* window, CGRect frame) { uint64_t set_tags = kCGSExposeFadeTagBit | kCGSPreventsActivationTagBit; uint64_t clear_tags = 0; window->origin = frame.origin; window->frame.origin = CGPointZero; window->frame.size = frame.size; frame.origin = CGPointZero; uint32_t id; CFTypeRef frame_region = window_create_region(window, frame); CFTypeRef empty_region = CGRegionCreateEmptyRegion(); SLSNewWindowWithOpaqueShapeAndContext(g_connection, kCGBackingStoreBuffered, frame_region, empty_region, 13 | (1 << 18), &set_tags, window->origin.x, window->origin.y, 64, &id, NULL ); CFRelease(empty_region); CFRelease(frame_region); window->id = id; SLSSetWindowResolution(g_connection, window->id, 2.0f); SLSSetWindowTags(g_connection, window->id, &set_tags, 64); SLSClearWindowTags(g_connection, window->id, &clear_tags, 64); SLSSetWindowOpacity(g_connection, window->id, 0); window->context = SLWindowContextCreate(g_connection, window->id, NULL); CGContextSetInterpolationQuality(window->context, kCGInterpolationNone); window->needs_move = false; window->needs_resize = false; if (g_bar_manager.sticky) { if (!g_space) { g_space = SLSSpaceCreate(g_connection, 1, 0); SLSSpaceSetAbsoluteLevel(g_connection, g_space, 0); CFArrayRef space_list = cfarray_of_cfnumbers(&g_space, sizeof(uint32_t), 1, kCFNumberSInt32Type); SLSShowSpaces(g_connection, space_list); CFRelease(space_list); } CFArrayRef window_list = cfarray_of_cfnumbers(&window->id, sizeof(uint32_t), 1, kCFNumberSInt32Type); SLSSpaceAddWindowsAndRemoveFromSpaces(g_connection, g_space, window_list, 0x7 ); CFRelease(window_list); } } void window_clear(struct window* window) { window->context = NULL; window->parent = NULL; window->id = 0; window->origin = CGPointZero; window->frame = CGRectNull; window->needs_move = false; window->needs_resize = false; } void window_flush(struct window* window) { SLSFlushWindowContentRegion(g_connection, window->id, NULL); } void windows_freeze() { if (g_transaction) return; SLSDisableUpdate(g_connection); g_transaction = SLSTransactionCreate(g_connection); } void windows_unfreeze() { if (g_transaction) { SLSTransactionCommit(g_transaction, 0); CFRelease(g_transaction); g_transaction = NULL; SLSReenableUpdate(g_connection); } } void window_set_frame(struct window* window, CGRect frame) { if (window->needs_move || !CGPointEqualToPoint(window->origin, frame.origin)) { window->needs_move = true; window->origin = frame.origin; } if (window->needs_resize || !CGSizeEqualToSize(window->frame.size, frame.size)) { window->needs_resize = true; window->frame.size = frame.size; } } void window_move(struct window* window, CGPoint point) { window->origin = point; if (__builtin_available(macOS 12.0, *)) { // Monterey and later windows_freeze(); SLSTransactionMoveWindowWithGroup(g_transaction, window->id, point); } else { // Big Sur and previous SLSMoveWindow(g_connection, window->id, &point); CFNumberRef number = CFNumberCreate(NULL, kCFNumberSInt32Type, &window->id ); const void* values[1] = { number }; CFArrayRef array = CFArrayCreate(NULL, values , 1, &kCFTypeArrayCallBacks); SLSReassociateWindowsSpacesByGeometry(g_connection, array); CFRelease(array); CFRelease(number); } } bool window_apply_frame(struct window* window, bool forced) { windows_freeze(); if (window->needs_resize || forced) { CFTypeRef frame_region = window_create_region(window, window->frame); if (__builtin_available(macOS 26.0, *)) { SLSSetWindowShape(g_connection, window->id, window->origin.x, window->origin.y, frame_region); } else if (__builtin_available(macOS 13.0, *)) { // Ventura and later SLSSetWindowShape(g_connection, window->id, g_nirvana.x, g_nirvana.y, frame_region); window_move(window, window->origin); } else { // Monterey and previous if (window->parent) { SLSOrderWindow(g_connection, window->id, 0, window->parent->id); } SLSSetWindowShape(g_connection, window->id, 0, 0, frame_region); if (window->parent) { CGContextClearRect(window->context, window->frame); CGContextFlush(window->context); window_order(window, window->parent, window->order_mode); } window_move(window, window->origin); } CFRelease(frame_region); window->needs_move = false; window->needs_resize = false; return true; } else if (window->needs_move) { window_move(window, window->origin); window->needs_move = false; return false; } return false; } void window_send_to_space(struct window* window, uint64_t dsid) { CFArrayRef window_list = cfarray_of_cfnumbers(&window->id, sizeof(uint32_t), 1, kCFNumberSInt32Type); SLSMoveWindowsToManagedSpace(g_connection, window_list, dsid); if (CGPointEqualToPoint(window->origin, g_nirvana)) { SLSMoveWindow(g_connection, window->id, &g_nirvana); } CFRelease(window_list); } void window_close(struct window* window) { if (!window->id) return; windows_unfreeze(); SLSOrderWindow(g_connection, window->id, 0, 0); CGContextRelease(window->context); SLSReleaseWindow(g_connection, window->id); window_clear(window); } void window_set_level(struct window* window, uint32_t level) { windows_freeze(); if (__builtin_available(macOS 14.0, *)) { // Sonoma and later SLSTransactionSetWindowLevel(g_transaction, window->id, level); } else { // Ventura and previous SLSSetWindowLevel(g_connection, window->id, level); } } void window_order(struct window* window, struct window* parent, int mode) { windows_freeze(); window->parent = parent; if (mode != W_OUT) window->order_mode = mode; if (__builtin_available(macOS 14.0, *)) { // Sonoma and later SLSTransactionOrderWindow(g_transaction, window->id, mode, parent ? parent->id : 0); } else { // Ventura and previous SLSOrderWindow(g_connection, window->id, mode, parent ? parent->id : 0); } } void window_assign_mouse_tracking_area(struct window* window, CGRect rect) { SLSRemoveAllTrackingAreas(g_connection, window->id); SLSAddTrackingRect(g_connection, window->id, rect); } void window_set_blur_radius(struct window* window, uint32_t blur_radius) { SLSSetWindowBackgroundBlurRadius(g_connection, window->id, blur_radius); } void context_set_font_smoothing(CGContextRef context, bool smoothing) { CGContextSetAllowsFontSmoothing(context, smoothing); } void window_disable_shadow(struct window* window) { CFIndex shadow_density = 0; CFNumberRef shadow_density_cf = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &shadow_density ); const void *keys[1] = { CFSTR("com.apple.WindowShadowDensity") }; const void *values[1] = { shadow_density_cf }; CFDictionaryRef shadow_props_cf = CFDictionaryCreate(NULL, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); SLSWindowSetShadowProperties(window->id, shadow_props_cf); CFRelease(shadow_density_cf); CFRelease(shadow_props_cf); } CGImageRef window_capture(struct window* window, bool* disabled) { if (g_disable_capture) { int64_t time = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW_APPROX); if (g_disable_capture < 0) { *disabled = true; return NULL; } else if (time - g_disable_capture > (1ULL << 30)) { g_disable_capture = 0; } else { *disabled = true; return NULL; } } *disabled = false; CGImageRef image_ref = NULL; uint64_t wid = window->id; SLSCaptureWindowsContentsToRectWithOptions(g_connection, &wid, true, CGRectNull, 1 << 8, &image_ref ); CGRect bounds; SLSGetScreenRectForWindow(g_connection, wid, &bounds); bounds.size.width = (uint32_t) (bounds.size.width + 0.5); window->frame.size = bounds.size; return image_ref; } ================================================ FILE: src/window.h ================================================ #pragma once #include "misc/helpers.h" #define kCGSExposeFadeTagBit (1ULL << 1) #define kCGSPreventsActivationTagBit (1ULL << 16) #define W_ABOVE 1 #define W_OUT 0 #define W_BELOW -1 extern CFTypeRef g_transaction; struct window { struct window* parent; int order_mode; bool needs_move; bool needs_resize; uint32_t id; uint32_t surface_id; CGRect frame; CGPoint origin; CGContextRef context; }; void window_init(struct window* window); void window_create(struct window* window, CGRect frame); void window_close(struct window* window); void window_clear(struct window* window); void window_flush(struct window* window); void window_move(struct window* window, CGPoint point); void window_set_frame(struct window* window, CGRect frame); bool window_apply_frame(struct window* window, bool forced); void window_send_to_space(struct window* window, uint64_t dsid); void window_set_blur_radius(struct window* window, uint32_t blur_radius); void window_disable_shadow(struct window* window); void window_set_level(struct window* window, uint32_t level); void window_order(struct window* window, struct window* parent, int mode); void window_assign_mouse_tracking_area(struct window* window, CGRect rect); CGImageRef window_capture(struct window* window, bool* disabled); void context_set_font_smoothing(CGContextRef context, bool smoothing); void windows_freeze(); void windows_unfreeze(); ================================================ FILE: src/workspace.h ================================================ #pragma once #include "event.h" void workspace_create_custom_observer (void **context, char* notification); void workspace_event_handler_init(void **context); void workspace_event_handler_begin(void **context); void workspace_event_handler_end(void *context); int workspace_display_notch_height(uint32_t did); float workspace_get_scale(); CGImageRef workspace_icon_for_app(char* app); char* workspace_copy_app_name_for_pid(pid_t pid); ================================================ FILE: src/workspace.m ================================================ #include "workspace.h" #include "misc/helpers.h" #include @interface workspace_context : NSObject { } - (id)init; - (void)addCustomObserver:(NSString *)name; @end float workspace_get_scale() { @autoreleasepool { float scale = 1.f; NSArray* screens = [NSScreen screens]; for (int i = 0; i < [screens count]; i++) { NSScreen* screen = screens[i]; float screen_scale = [screen backingScaleFactor]; if (screen_scale > scale) scale = screen_scale; } return scale; } } void workspace_event_handler_init(void **context) { workspace_context *ws_context = [workspace_context alloc]; *context = ws_context; } void workspace_event_handler_begin(void **context) { workspace_context *ws_context = *context; [ws_context init]; } void workspace_event_handler_end(void *context) { workspace_context *ws_context = (workspace_context *) context; [ws_context dealloc]; } void workspace_create_custom_observer (void **context, char* notification) { workspace_context *ws_context = *context; [ws_context addCustomObserver:@(notification)]; } int workspace_display_notch_height(uint32_t did) { if (!CGDisplayIsBuiltin(did)) return 0; int height = 0; #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 120000 if (__builtin_available(macos 12.0, *)) { @autoreleasepool { for (NSScreen *screen in [NSScreen screens]) { if ([[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue] == did) { height = screen.safeAreaInsets.top; } } } } #endif return height; } void forced_front_app_event() { @autoreleasepool { NSString* name = [[[NSWorkspace sharedWorkspace] frontmostApplication] localizedName]; if (name) { const char* front_app = [name cStringUsingEncoding:NSUTF8StringEncoding]; if (front_app) { struct event event = { string_copy((char*)front_app), APPLICATION_FRONT_SWITCHED }; event_post(&event); } } } } char* workspace_copy_app_name_for_pid(pid_t pid) { @autoreleasepool { NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; const char* result = [[app localizedName] UTF8String]; return result ? string_copy((char*)result) : NULL; } } CGImageRef workspace_icon_for_app(char* app) { @autoreleasepool { NSString* ns_app = [NSString stringWithUTF8String:app]; NSURL* path = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:ns_app]; if (!path) { bool recovered = false; NSArray* running_apps = [[NSWorkspace sharedWorkspace] runningApplications]; for (NSRunningApplication* app in running_apps) { if ([[app localizedName] isEqualToString:ns_app]) { ns_app = [app bundleIdentifier]; if (ns_app) { path = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:ns_app]; recovered = true; } break; } } if (!recovered) return NULL; } NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile:path.path]; if (!image) return NULL; float scale = workspace_get_scale(); NSRect rect = NSMakeRect( 0, 0, 32 * scale, 32 * scale); return (CGImageRef)CFRetain([image CGImageForProposedRect: &rect context: NULL hints: NULL]); } } @implementation workspace_context - (id)init { if ((self = [super init])) { [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(activeDisplayDidChange:) name:@"NSWorkspaceActiveDisplayDidChangeNotification" object:nil]; [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(activeSpaceDidChange:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil]; [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(appSwitched:) name:NSWorkspaceDidActivateApplicationNotification object:nil]; [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(willSleep:) name:NSWorkspaceWillSleepNotification object:nil]; [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(didWake:) name:NSWorkspaceDidWakeNotification object:nil]; [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeMenuBarHiding:) name:@"AppleInterfaceMenuBarHidingChangedNotification" object:nil]; [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(didWake:) name:@"com.apple.screenIsUnlocked" object:nil]; } return self; } - (void)addCustomObserver:(NSString *)name { [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(allDistributedNotifications:) name:name object:nil]; } - (void)dealloc { [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } - (void) allDistributedNotifications:(NSNotification *)note { @autoreleasepool { struct notification* notification = notification_create(); notification->name = string_copy((char*)[[note name] UTF8String]); if (note.userInfo && [NSJSONSerialization isValidJSONObject:note.userInfo]) { NSData* data = [NSJSONSerialization dataWithJSONObject:note.userInfo options:NSJSONWritingPrettyPrinted error:NULL]; if (data && [data length] > 0) { char* info = malloc([data length] + 1); memcpy(info, [data bytes], [data length]); info[[data length]] = '\0'; notification->info = info; } } struct event event = { notification, DISTRIBUTED_NOTIFICATION }; event_post(&event); } } - (void)willSleep:(NSNotification *)notification { struct event event = { NULL, SYSTEM_WILL_SLEEP }; event_post(&event); } - (void)didWake:(NSNotification *)notification { struct event event = { NULL, SYSTEM_WOKE }; event_post(&event); } - (void)appSwitched:(NSNotification *)notification { @autoreleasepool { char* name = NULL; if (notification && notification.userInfo) { NSRunningApplication* app = [notification.userInfo objectForKey:NSWorkspaceApplicationKey]; if (app) { char* app_name_tmp = (char*)[[app localizedName] UTF8String]; if (app_name_tmp) name = string_copy(app_name_tmp); } } struct event event = { name, APPLICATION_FRONT_SWITCHED }; event_post(&event); } } - (void)didChangeMenuBarHiding:(NSNotification *)notification { struct event event = { NULL, MENU_BAR_HIDDEN_CHANGED }; event_post(&event); } - (void)activeDisplayDidChange:(NSNotification *)notification { struct event event = { NULL, DISPLAY_CHANGED }; event_post(&event); } - (void)activeSpaceDidChange:(NSNotification *)notification { struct event event = { NULL, SPACE_CHANGED }; event_post(&event); } @end