pax_global_header00006660000000000000000000000064143550676310014524gustar00rootroot0000000000000052 comment=008a2a26b6e7d266d190ab85d6c25cf4b8a7bd06 iwgtk-0.9/000077500000000000000000000000001435506763100125215ustar00rootroot00000000000000iwgtk-0.9/CHANGELOG000066400000000000000000000057631435506763100137460ustar00rootroot00000000000000## 0.9 * Now requires iwd >=1.29 * Use correct installation path for systemd unit file * Gracefully handle "access denied" DBus error * Recover DPP state and QR code after window is closed and re-opened * Fix various memory leaks * Reduce unnecessary string copies * Fix rare segmentation fault in Station Diagnostics window ## 0.8 * New build-time dependency: Meson * New build-time dependency: scdoc * Preliminary i18n support via gettext. No translations are available yet, but contributions are welcome! * Dark theme support via config file option * Customizable time/date format via config file option * Customizable window dimensions via config file options * New iwgtk(5) man page to document the config file * Fix memory leak ## 0.7 * New dependency: libqrencode * New Provision menu with initial Easy Connect (DPP) support * Move WPS and hidden network connection buttons into Provision menu. * New config file in which icon colors can be customized * Check whether required icons are available, and override icon theme to Adwaita if any are missing. * Various UI tweaks ## 0.6 * Clear available networks when a scan begins, rather than after the scan ends * Allow indicator daemon to be started while a window is open * Fix memory leak * Include XDG autostart file and place it in /etc/xdg/autostart/ * Change category in .desktop file from "Network" to "Settings" ## 0.5 * Ported application to GTK4 * Indicator icons provide signal strength information via iwd's SignalLevelAgent API * Indicator icons for disabled adapters/devices * Uses adwaita-icon-theme icons instead of supplying our own * Continue running indicator daemon if iwd goes down or if all devices are removed * Do not launch additional windows if one is already open * Do not launch window if iwd is down * More stable UI (widgets no longer move around when changing mode, etc) * Show distinct connect buttons for hidden networks * Provide ability to hide passwords when entering them * Tooltips for various UI elements * More descriptive indicator icon tooltips * Display network SSID in "connected" and "disconnected" messages. * Includes systemd unit file * Station diagnostics via iwd's StationDiagnostic API * Removed option for numerical signal strength display * Changed application ID to org.twosheds.iwgtk ## 0.4 * Added application icon * Added support for multiple windows per process * Added indicator (tray) icons * New command line options ## 0.3 * Reset UI widget to its correct state if the D-Bus call fails for a user-initiated property change. * Do not assume in network_lookup() that the network being looked up has already been registered. This fixes a segmentation fault. ## 0.2 * Added support for displaying and connecting to hidden networks * Added a yellow icon for the "connecting" state, to distinguish it from the "connected" state * Updated Makefile to respect standard variable names (DESTDIR, prefix, etc) * Added iwgtk.desktop file * Added man page ## 0.1 Initial release iwgtk-0.9/COPYING000066400000000000000000001045151435506763100135620ustar00rootroot00000000000000 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 . iwgtk-0.9/README.md000066400000000000000000000071151435506763100140040ustar00rootroot00000000000000## About iwgtk is a wireless networking GUI for Linux. It is a front-end for [iwd (iNet Wireless Daemon)](https://iwd.wiki.kernel.org/), with supported functionality similar to that of iwctl. Features include viewing and connecting to available networks, managing known networks, provisioning new networks via WPS or Wi-Fi Easy Connect, and an indicator (tray) icon displaying connection status and signal strength. ![Screenshot](screenshot/iwgtk-station-mode.png) ## Usage Launch the application window: `iwgtk` Launch the indicator daemon: `iwgtk -i` ### Autostarting The most common use case for iwgtk is to start the indicator daemon every time you log into your desktop. If your desktop environment supports the XDG autostart standard, this should happen automatically due to the `iwgtk-indicator.desktop` file which is placed in `/etc/xdg/autostart/` during installation. A systemd unit file to start the indicator daemon is also provided. If your distro uses systemd and your desktop environment supports systemd's `graphical-session.target` unit, then iwgtk can be started at the beginning of every desktop session by enabling the `iwgtk.service` unit. ### Configuration Icon colors and other options can be customized by editing the application's configuration file. The system-wide configuration file is located at `$(sysconfdir)/iwgtk.conf`. `$(sysconfdir)` is usually either `/etc` or `/usr/local/etc`, depending on your build-time `prefix` setting. `iwgtk.conf` can be copied to `~/.config/iwgtk.conf` if desired, in which case the system-wide configuration file will be ignored. For further instructions, please see `man 5 iwgtk` and refer to the comments in `iwgtk.conf`. ## Dependencies ### Runtime dependencies * iwd (>= 1.29) * gtk4 (>= 4.6) * libqrencode * adwaita-icon-theme (or an equivalent icon package) ### Build dependencies * meson (>= 0.60.0) * scdoc ## Installation To build iwgtk and install to `/usr/local`, run: ``` meson setup build cd build meson compile sudo meson install ``` To install to `/usr` instead of `/usr/local`, replace `meson setup build` with: ``` meson setup --prefix=/usr build ``` ## Troubleshooting ### The indicator icon doesn't show up iwgtk's icon should show up on any system tray which supports the StatusNotifierItem API. If your tray only supports the older XEmbed API, then a compatibility layer such as [snixembed](https://git.sr.ht/~steef/snixembed) is required. The following trays support StatusNotifierItem: * KDE Plasma * swaybar * xfce4-panel (*must be built with the optional libdbusmenu-gtk3 dependency*) The following trays only support XEmbed, and require a compatibility layer: * AwesomeWM * i3bar ### iwgtk and iwctl only work with superuser privileges As of iwd 1.23, membership in either the `netdev` or `wheel` group is required to control iwd: ``` # usermod -a -G netdev YOUR_USER_ACCOUNT ``` If no `netdev` group exists on your system, then you'll need to create it prior to running the above `usermod` command: ``` # groupadd netdev ``` ## License Copyright 2020-2023 Jesse Lentz and contributors (see below) iwgtk is licensed under the GPL version 3 or later. The application icon is from the [wifi states](https://thenounproject.com/iconsguru/collection/wifi-states/) collection by [i cons](https://thenounproject.com/iconsguru/) from the Noun Project. This icon is licensed under the [Creative Commons BY license](https://creativecommons.org/licenses/by/3.0/us/legalcode). ## Contributors * Jove Yu * Jaron Viëtor (Thulinma) * tinywrkb * Érico Nogueira Rolim * VaguePenguin * Andrew Benson * Alex Piechowski (grepsedawk) * Luigi Baldoni iwgtk-0.9/meson.build000066400000000000000000000030411435506763100146610ustar00rootroot00000000000000project( 'iwgtk', 'c', version: '0.9', license: 'GPL-3.0-or-later', meson_version: '>=0.60.0' ) prefix = get_option('prefix') sysconfdir = get_option('sysconfdir') datadir = get_option('datadir') mandir = get_option('mandir') localedir = get_option('localedir') add_project_arguments( '-D SYSCONFDIR="@0@"'.format(prefix / sysconfdir), '-D LOCALEDIR="@0@"'.format(prefix / localedir), '-D PACKAGE="@0@"'.format(meson.project_name()), '-D VERSION="@0@"'.format(meson.project_version()), language: 'c' ) subdir('src') dependencies = [ dependency('gtk4', version: '>=4.6'), dependency('libqrencode') ] executable( 'iwgtk', sources: src_files, dependencies: dependencies, install: true ) subdir('po') scdoc = find_program('scdoc', native: true) foreach i: [1,5] name = 'iwgtk.@0@'.format(i) src = 'misc/@0@.scd'.format(name) dir = mandir / 'man@0@'.format(i) custom_target( input: src, output: name, command: scdoc, feed: true, capture: true, install: true, install_dir: dir ) endforeach install_data( 'misc/iwgtk.conf', install_dir: sysconfdir ) install_data( 'misc/iwgtk-indicator.desktop', install_dir: sysconfdir / 'xdg/autostart' ) install_data( 'misc/iwgtk.service', install_dir: prefix / 'lib/systemd/user' ) install_data( 'misc/iwgtk.desktop', install_dir: datadir / 'applications' ) install_data( 'misc/iwgtk.svg', install_dir: datadir / 'icons/hicolor/scalable/apps' ) iwgtk-0.9/misc/000077500000000000000000000000001435506763100134545ustar00rootroot00000000000000iwgtk-0.9/misc/iwgtk-indicator.desktop000066400000000000000000000002711435506763100201460ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=iwgtk Comment=Wireless network management utility Exec=iwgtk -i Icon=iwgtk Categories=GTK;Settings;HardwareSettings; Terminal=false NoDisplay=true iwgtk-0.9/misc/iwgtk.1.scd000066400000000000000000000032751435506763100154420ustar00rootroot00000000000000iwgtk(1) # NAME iwgtk - Wireless network management GUI # SYNOPSIS *iwgtk* [_options_] # OPTIONS *-i, --indicators* Launch indicator daemon *-n, --notifications* Enable desktop notifications (default) *-N, --no-notifications* Disable desktop notifications *-h, --help* Show command line options and quit *-V, --version* Print version number and quit # DESCRIPTION iwgtk is a graphical utility for managing wireless network connections via iwd. Supported functionality is similar to that of iwctl. # CONFIGURATION Icon colors and other options can be customized by editing the application's configuration file. The system-wide configuration file is located at */etc/iwgtk.conf*. This file can be copied to *~/.config/iwgtk.conf* if desired, in which case the system-wide configuration file will be ignored. For further instructions, please see _iwgtk_(5) and refer to the comments in *iwgtk.conf*. # REPORTING BUGS Report bugs using the issue tracker on Github . # COPYRIGHT Copyright 2020-2023 Jesse Lentz and contributors iwgtk 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. iwgtk 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 iwgtk. If not, see . # SEE ALSO _iwgtk_(5) iwgtk-0.9/misc/iwgtk.5.scd000066400000000000000000000073361435506763100154500ustar00rootroot00000000000000iwgtk(5) # NAME iwgtk - Configuration file # LOCATION The system-wide configuration file is located at *$(sysconfdir)/iwgtk.conf*. *$(sysconfdir)* is usually either */etc* or */usr/local/etc*, depending on your build-time *prefix* setting. *iwgtk.conf* can be copied to *~/.config/iwgtk.conf* if user-specific configuration is desired. If a user configuration file exists, then the system-wide configuration file will be ignored. # SYNTAX The configuration file uses the "key file" syntax, which consists of groups of key-value pairs. Blank lines are ignored, and lines beginning with a "#" are considered comments. Groups are started by a header line containing the group name enclosed in square brackets. A group is ended implicitly by the start of the next group or by the end of the file. Every option must be contained in a group. If a group contains the same option key multiple times, the last occurrence wins. Unrecognized group or option keys are silently ignored. Any option which is not explicitly specified will take on its default value. Group names, option keys, and values are all case-sensitive. Options have the form key=value. Spaces before or after the '=' character are ignored. Newline, tab, carriage return and backslash characters in values must be escaped as \\n, \\t, \\r, and \\\\, respectively. To preserve leading spaces in values, these may be escaped as \\s. Possible option types include strings, integers, and booleans. Boolean options must be specified as either "true" or "false". Although colors are technically specified as strings, they are regarded herein as a distinct type. # COLOR VALUES Color values may consist of: - A standard name (Taken from the CSS specification). - A hexadecimal value in the form "#rgb", "#rrggbb", "#rrrgggbbb" or "#rrrrggggbbbb" - A hexadecimal value in the form "#rgba", "#rrggbbaa", or "#rrrrggggbbbbaaaa" - A RGB color in the form "rgb(r,g,b)" (In this case the color will have full opacity) - A RGBA color in the form "rgba(r,g,b,a)" Where "r", "g", "b", and "a" are respectively the red, green, blue and alpha color values. In the last two cases, "r", "g", and "b" are either integers in the range 0 to 255 or percentage values in the range 0% to 100%, and "a" is a floating point value in the range 0 to 1. See: https://docs.gtk.org/gdk4/method.RGBA.parse.html List of standard color names:++ https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color_keywords # OPTIONS ## [indicator.colors.station] Indicator icon colors: station mode [- *Option* :- *Type* :- *Default value* |[ connected :[ color :[ lime | connecting : color : yellow | disconnected : color : gray ## [indicator.colors.ap] Indicator icon colors: AP mode [- *Option* :- *Type* :- *Default value* |[ up :[ color :[ lime | down : color : gray ## [indicator.colors.adhoc] Indicator icon colors: ad-hoc mode [- *Option* :- *Type* :- *Default value* |[ up :[ color :[ lime | down : color : gray ## [indicator.colors.disabled] Indicator icon colors: disabled device or adapter [- *Option* :- *Type* :- *Default value* |[ device :[ color :[ gray | adapter : color : gray ## [network.colors] Available network list - Signal strength icon colors [- *Option* :- *Type* :- *Default value* |[ connected :[ color :[ lime | connecting : color : yellow | known : color : darkgreen | unknown : color : gray | hidden : color : gray ## [known-network] Known network list - last connection date/time - format string [- *Option* :- *Type* :- *Default value* |[ last-connection-time.format :[ string :[ %x\\n%l:%M %p ## [window] Dark mode and window dimensions [- *Option* :- *Type* :- *Default value* |[ dark :[ boolean :[ false | width : integer : 440 | height : integer : 600 # SEE ALSO _iwgtk_(1) iwgtk-0.9/misc/iwgtk.conf000066400000000000000000000032771435506763100154610ustar00rootroot00000000000000# # Only specify options that you wish to change. To use an option's default value, leave it # commented. # # Color values may consist of: # * A standard name (Taken from the CSS specification). # * A hexadecimal value in the form "#rgb", "#rrggbb", "#rrrgggbbb" or "#rrrrggggbbbb" # * A hexadecimal value in the form "#rgba", "#rrggbbaa", or "#rrrrggggbbbbaaaa" # * A RGB color in the form "rgb(r,g,b)" (In this case the color will have full opacity) # * A RGBA color in the form "rgba(r,g,b,a)" # # Where "r", "g", "b", and "a" are respectively the red, green, blue and alpha color # values. In the last two cases, "r", "g", and "b" are either integers in the range 0 to # 255 or percentage values in the range 0% to 100%, and "a" is a floating point value in # the range 0 to 1. # # See: https://docs.gtk.org/gdk4/method.RGBA.parse.html # List of standard color names: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color_keywords # # # Indicator icon colors: station mode # [indicator.colors.station] #connected=lime #connecting=yellow #disconnected=gray # # Indicator icon colors: AP mode # [indicator.colors.ap] #up=lime #down=gray # # Indicator icon colors: ad-hoc mode # [indicator.colors.adhoc] #up=lime #down=gray # # Indicator icon colors: disabled device or adapter # [indicator.colors.disabled] #device=gray #adapter=gray # # Available network list - Signal strength icon colors # [network.colors] #connected=lime #connecting=yellow #known=darkgreen #unknown=gray #hidden=gray # # Known network list - last connection date/time - format string # [known-network] #last-connection-time.format=%x\n%l:%M %p # # Dark mode and window dimensions # [window] #dark=false #width=440 #height=600 iwgtk-0.9/misc/iwgtk.desktop000066400000000000000000000002471435506763100161770ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=iwgtk Comment=Wireless network management utility Exec=iwgtk Icon=iwgtk Categories=GTK;Settings;HardwareSettings; Terminal=false iwgtk-0.9/misc/iwgtk.service000066400000000000000000000003431435506763100161630ustar00rootroot00000000000000[Unit] Description=iwgtk Documentation=man:iwgtk(1) Wants=iwd.service PartOf=graphical-session.target After=graphical-session.target [Service] ExecStart=iwgtk -i Restart=on-failure [Install] WantedBy=graphical-session.target iwgtk-0.9/misc/iwgtk.svg000066400000000000000000000067021435506763100153270ustar00rootroot00000000000000 image/svg+xml wifi14 wifi14 iwgtk-0.9/po/000077500000000000000000000000001435506763100131375ustar00rootroot00000000000000iwgtk-0.9/po/.gitignore000066400000000000000000000000121435506763100151200ustar00rootroot00000000000000iwgtk.pot iwgtk-0.9/po/LINGUAS000066400000000000000000000000001435506763100141520ustar00rootroot00000000000000iwgtk-0.9/po/POTFILES000066400000000000000000000004051435506763100143060ustar00rootroot00000000000000src/adapter.c src/adhoc.c src/agent.c src/ap.c src/device.c src/diagnostic.c src/dialog.c src/dpp.c src/hidden.c src/icon.c src/indicator.c src/known_network.c src/main.c src/network.c src/sni.c src/station.c src/switch.c src/utilities.c src/window.c src/wps.c iwgtk-0.9/po/meson.build000066400000000000000000000001241435506763100152760ustar00rootroot00000000000000i18n = import('i18n') i18n.gettext( meson.project_name(), preset: 'glib' ) iwgtk-0.9/screenshot/000077500000000000000000000000001435506763100146765ustar00rootroot00000000000000iwgtk-0.9/screenshot/iwgtk-station-mode.png000066400000000000000000002030561435506763100211400ustar00rootroot00000000000000PNG  IHDRziBiCCPICC profile(}=H@_SR*P,q*BZu0ChҐ8 ?.κ: "ƃ~{2S͎q@,#܊xEя3T* u_<ܟGɛ 3L7,uMKOf%I!>'3ď\]~\tXa##6ۘ 8FQT -jʚ/ %B XD "dT2,DiH1%k@wfarM Ł@fǶ8W'鵖9z&;.#i ~Fߔn[s@Jh<ۿgrR bKGD pHYsu*1tIME7 Z{tEXtCommentCreated with GIMPW IDATxwtT-ɦn {&]:DT,`ן] 4;BRRۦ @9'wvw3sg9gAzo?ΞelҤ1ڵA AGƘoP̝3ةcGڶi-VA5EE.yJw77ڴn%A=ss>Z-ʹsf5kB… 8۶~ص{xs B̌m0w,#xUÊUOC&o"SXXS&33S. *<=Jkkr'F#_}5Nf5bGM2UA=h!'iii|݋>{?po|wDnD=߯/DZZX]AuT*DNNF@.",<-Y^7#Gyɧ8u4*Q#ρ~k uCWh٢ o`ʺOSlY0GGG^}mw!!x{y/AZL<N֭033h˗/sp(ӦLA'JJJyu|gcưd"5b8K)8VȡÇaBc' `kZyL5BXB1 G!'<ĈH[JKKv>8f!!P(p"..prX[6,-9xn.՞vb%Bg&;_P7;$0t<24+_pi+h4P*M %R+..6 PJQQ`DRPPbR}*ABRA^ m;v8bw "DjBŴW]oGd)V]i1V>FٓR$"M4=P*( vBpP ΝP}._NAW$Z+G>4/`k;u&EEE &><M#J]vcΤG1%FaQCcG:w 9# G.T 6g#pt^q4OYh4֬'lëynH W&3%!ׯ[3y]:wb9doQy#g>LMݺ};GjPc4ʊȨDF]#uȲ!deԠ |N!&6 N7;AU\vU^v˭أR!p`Al='N4yӧNG)ײ`\f͜X:}ʌpws-J$$^".!& iܨ!yyu,V4Բ2*IMhpʊ#;|,Fn OyZ4A!Z4#8{]GS7^OTJLƆpssSR.̳aggɓQRyPG@qxybVq.-vX @V}nz"*NT3t۷l> p IWzu&n O/OӠf4 jӋԚI3Φޫk vOYC}]uruqaSO¯q7z?O'!ޠp0$ш`J7EEE4 Ni#jqĩ#~ 113iht|KS.eGL"xALnkPӮfV̙=8oFHKOss f̜MQQ}~sd 5YDFg羾<~D26f]rBfff&"(:z?<McmmL wj1pWUx ~;x0}Z6  Yb1 &d*  NQQXQA;p'ł BFFABAA[Qm(A:u w7"   ܭ2 9nuwsrN25.<2ɡP_5XbsZ "7!Ydf尴^爝RB,9sFFr$͇S &+WOIIIu>`cl3 C;> fg{[-RsDrr"<\<(B92HIJ'* a(27b,mX;`J?څ:z-۳rv 9#I+!N<'N.8:K( B=A}^^n{;NNGTi2ҫސ2\J7<, h\puUӈ&lА*5IH@GCINGg J%n$r)!VXVZbWOwfP{WFc x3x< FciSC<:51׽SS{3R A.B /!j cS+iҸ1FRr2]@ii)Ύ]? i,Ǘ>A\|h5( R *?k2!m`ͺW Vw׳qWGhx&'ʺRY8;91iެál۱^O>dMm[T߸Yy.D33QOZ4 a}q/{t335$]J6MLJ,[ӵ#j5%5gBjj,mAVz~7>4J"XZZʌ9øKg}a nbt OXE>9m*-[4G.s$ϊgVӻGwƏ͖mJ!ܬGx_p}( ?CW_%3۫'v4un޺6Z]s ;K l* @\Cxb.]5b\*PΚzQ||}|75y?HMMw6 |!Oii): 0ODzܙ̚1~SBe)8;5lX8gý˛u܉WֿFcNL'˼gɘ嵗\7O-sn!ƊtiZ3Bx0<g{ A; 14+=4mBjZyy,}j6}y11ٷ#GMh~~>99pSBاWO;Ĥ$<[ƍJUb98ή;qFC'C7g5ƨP`ieî<>ȯFD 9"B|zg4ptP7|АScei~ۯcIXXXp_8ƚ}"MΞr"텧GcMi'<ǀ+T*h߈&g:VfdTa X54LżҶ +=??V{iNͺm}APB#vF3]gag4}Z:}p3vH Yi,4\\̢],ߢrq<=ybc,\~gWpi}a b;UO> S+^dffKϯ@KO.[:F`֜9M);מ%&w0 KLAVKK? >f4̌'lZZA94RRRBT)[,,,$.>|{;;<=Q(gB`^Bt4:..>$1) ZWNLJ"5- =^֞gN?j_?yvolmlh\޾(vO%+-Z (@"&_GVnk^6^Ҷ PT ܐ(߼kh԰Qt:t:][XXX]챷\psuMܖE03䓑WBB•fƛ_艍t:cVQ*"pcR(Aս SBO9ϵAť@PP*/Qt#6@Uvh&waӊ (P P׷ m_/{cK47.BB &_K`rS{[@[]DDb#3}23%}Z81+jZDPAfˉr$͇S O!%YOI-.f4 0@\Ua9ê[0MsERPDPA0!m шh`0`0(--5oB0k1AAr)JT*U9, Tߋ 2ASO#U%(55Yb?ZPב$Ԕ$*;$" zT*XaaiMtTxu=p,,%2F(BCJ9\NN"bF(5:llnh KBAF2K[r~\n[PzGFj << B92HIJPZkz1BAě~VF:<BDJAGVFz;)0+3̴Tr7 wcB03=wntg͝Ą*a!>?sy%TbH_ "!}~Z''ZQ *}}CBY5aec+ff!+3] "UCAzsCcik,ĺՄƂ̴T1 䆄0?/X=CgAm@t w#AA?(  PXiN>#B(Pxw~ǟn3X;~¹_~УǪNII o뽟>FV-1DP:Cԅ $^J2'g>L@wZ9u _\KzFzsdCxÏjm>/T:;FxY8ʬI M-صw__O7~ Iy0 |~:kk]bR_|5q8;3z0}1g ##Y ?MoѴIc:k˚1}$>rii4mҘcbcS4ㅗ^agX6nK)4oĤ 㱱ì]*<B6mPU+߷/!+-5exxxh#Oii):x{ 2Bg_P_|+d4yv:+61|5Ͽ7Es'Oe=/mx_}~2 ;NLfL8k++^A?(J}c{g}ؤq6bL fffiݚGy.BLFf&1=BvP*nz^s-kkk\֥3:w^.;[4/h2-]ƊgVR*VYp0 :o=ݺoc49zN;gޯ鉋s9ؑ#L`,}jSfΪ,:Ғ2{O'pl8/F~. 7W ΞжUK^qW3+;;ws׫;tȦ[\r+4\i1v:oO`V<׾ąvO0ba!'7ܹ\t:]Yq1~7zvZEN=zNڕKSm:ןRc ɮDV_shތS&Em(?,RAm8z8;={Cj83gԡ}R)+֭Z0e>}t{eF>4w?>=YV a! `0ΥM]@T}-Bп/ ޵>^\-y3<8NrOE 7/ഭ 陙 IDATB=g{>t{r9U=]Oٵg];us:sáGtC cr,_?^^<.:r_Ф?M냭 dddKS=JD9:_ڙO7~ڴahs/шRK 2᷽ԪYM5D:Of!kݒ^~tTy%JBIJaa!i=FS(:KKKWbTJB +3',<QC?.FЩC{F ;:{biղEдoNJgV퍣CmݮMkWn14"E1lx5KW(JzϿĬߨ-GX%w능ʼGg̈Uz^J%ϮXθzx54jX6z[J%̓9Sgߧ'~>ިT*ΆGu3T]re=4CO^ݱ0u2~V8~;6Mg'i%1g ʵ;/3rkߦ~D<=IMK_Yi5Q 4kX!-kZl W+s9s|}+hڴ13WX,8|ZZZUz\S!;vvҊrǛ_TU]ӡ*\psuh{;{쫵j|B֭L]Z5xJJbKԀiixWZ[[ƏƲg^ӃDN?fG pǥPL !tx~V.ywWӱAx!6>B'k^Go/tlǥ8:⠳! Bxg'R) lZ+K"A\ۅϸ TjmmV EȴHAAPAVdzA F٢^PADA &r.r~!47`f W3fŮ( :RS^Wn#jBk[[7_ͱO6_EPTbmk5Q ±RY%1d;# 1063W%Ŕ.CJ9椦\".F(56v:llPTU7tbz lv7ԗ-wjh4,R\ZOjBWob/FQZZ`b_oN !Ξ8.5Ӱc$=-=.̸ۈ:"ϙE#7K| ?8}z߱k7~ÆW^\|<_| .חcFᠫ|Ŀ5 صIqq1A>kkJ:"[ >!!$8(CG6h }z4s|f bXXhL狋Kx)))aY\oΜE >>  ..u!9q11?;{SϣA\ zP;W^"_ĨwwԴ4kEEhfȠq#,υL:R&qrlpCi6ƍ1qF^ϳo9z-Wiи0m\o8_n_O6N䪧{) ds1:L$/\@RU(Zo//Fʉb ̘3CC:Z2uDח$&͘EzFFyHJƮ:p_+Z9͡K+C2s 2>zRRRBaa2YF< ~Əca˯lx8{~q-ݻukNiղoa^t3a4pt`˜QL:\ֿ63Ma|uto޳&6!Z6oԙsرk7#ዯIgcF3ao66L=R,ظ8jnj iSn+!-=tl߾R;^֖mک# @P`AҥKr_=r8yyyj+k7WWSYѻGwF#CG(((Cv <Kyx˽~ <:ej[o ϣT*8z;@өC{ B 33^ xHt_xM|k*#|KǚiҍF Դ Ԟ@BB"IIp(;v&++ wwΜ5Gg.%Q#oO7~ Iy0 |~:k+FaNNNuzV~;0=EEE</\({ș0SR*i0M<@֭;J*((S固_o&MGJe~ܻ?#37WW ͛@Ll,.FW)?pvrBTVcP@ :{{|;JJJiߦ5i=~EO<ϳcn O+<"BL׵ۛWk1ӛҚex.A^W=K)TJcF ``[폃dff{bfV7~Aiw ݻW|ۯMe+]:ud˜h֌G7nԈ>b4m\6}V9?=w;D(G o ϿΎz+ݿƌ"&6NGҥK|g9GVvvZZRSSkw'OE,3M-[LcÍ1hhOD8rG $(OУt?)'ҫGYyx:OxYӧPXXȰݳ龮-#=zMmklմlkx׸pMi'1l`:kgc>^l)Sqqv4kӦЪEKvSg!JV07/SKgjЕJ%O?O#6.~D ȇ~΄Q*on2@Bb"-5+ww޽C7OqqvѱbYUfg0E׿=V}1ǘ0v {vlxe->4jؐF ^y8 Y8onY[[QTTTXQQ*F˻u x{yQ(4NNCd㗔/ҥSG ޭ+'OGÞ =Z';'۔v֭jYztLJ׽B|B"lISxu͋lќX4wϜ? -=?gkz1x]BGF#j331 h4@'z=wdeeH`mmV-_FfV9xzx0ulVUƂӷwz2fpfLLAA!~9+-A`t7k#IpwvuqYHp JKK)((05t4mƜNzK?c'N`?po߾(dkcCZz:M %T5(UjR ipe1&55YnbⱵյ/^yK?<ھ]|%il۱m;v;j$}z,aPFOL~e6ZXGxb* qqv&!1 U˗kҩ{`ĸEV򶾅))..ԙ3h֬2'.f3굳T'"}.dP#YUݕQ]=j|<:CFJJJ ;Ep@n^ BJDĔ͞pGgow{VkF!&6Rc"EzRELSyzʻ_/zӱ9?*JDPDzx{F0*(*J5W~>o3|bcM M" B(Ղ_.lɃ4\ '?vP(P(䗪Tnl d:" *NۅZL 7M ռju$RS;z)B(u@z׋s7ATbmkGQa!Q4pvAŞ`jJX"PnϿJщsRS.}^*F-;6vu;VBAjXtBAjkMYNuiBȵ2 R_k[ u;&Ft23JeXZ`sN ZU,Bh4>/7v:9 R(**CNIYHQ#z">/KPkSR~h4ueƻg7wJPkSR~ !d>?[wPꈎ`0u֞:%e!GMrGF) oNVBz#  B( ;Atsu Rx  a5KP|Rxu\X:w[m?JMV_Hll[mpnێY ۫)3g!ӫ'%%%F:{~tKIc&[sթsWۖi}IHLi3HOO=^e5~G/ҦUKztƠ0zdv G ##3%O`$;;۶Aqq1[dL>3cbi߈>{xrb223Y:Og_n"--M3qXllS;áб};lJ?&6/$lmmֹBYy}| 4 # 5ulgh4o?ztVi[+˫O#swзwONwhvq(th׎a{#q~~W LmVzB}z:uhϰL+\~q-ݻAUIJħᅦ]ֿOo:uRPPs3['.]^' IDAT =ž}yrbz6n4 ۟.:igxh8vqqqf1xyz^7~!}Q7@qcX Lͮ<-{=O7~ Iy0 |~:k+n`k oE;qRe9/䡔0u8n .,Z$g#Li[2t¸ѣ8|(/`˯m`7r_ 2]{gM bۻ'j338:2t@lYl)hPvEEL51t}{^w4k+~>xP4jذ\W6n޾}6x?ˊg3߼u;^a!|VM?ѫ==w70SR5n4pt/7QRRR\pP ;v;>?_v݇x97VsSӧ֍38"CVW hټ9iWȟ ӧL6y,ի1!// ?l/ 8( ԉPEPJ0sqT&ţbg|ful޶={TV `0g-|g^@"dʄ~L?ݺMq|7L;4gԴ)̋zS&#JX/jZYY᪕*J9l(^٣RP/Nff_~1rt?bЀ8ED~еO_32qws1-S&L#C՜8׺ve4Ž]{8~$9gOQ/yL|1yx}͛3,Iv}>Ispvv?T*icd2Cz[v #Ĵɓh޴;w3x{ |=«Û6Jji aVy},ȟG)HHH$0 Lf U*oJB[T@bRyw77L&cwٶs'F5PT\|KiJHH$( LAѻGw.\D|B"8u^ݻ1eyH\||0dJ"{;;\]Mdz^/֣j/ȿ gJ?'OFuf'hAzuBSXX0ZmM2qOWǧLx=͛1}d-b϶-6c-[OXhoDBXhqx{Ѵq#v{* _n=~G`*R(ror<~M5|ֈh`̧ aȠf `D,>Nrd3(֔Q\\M`@R 0ҨA}C.^჏>#GU51m5iУ[W6oێ֖ݺ*J"/+goR\lިKKKšTЧgO5oWoO{f&ylںfMWh4ްN\/uNAF[鸕Y5fY"8)`C}< ;R21R  ӯo~?|›Z&f#7_y[]w}GwZ-+_ͅhFr>77L4&M\ܵSf͸p"LF_i6?s^ `0iQ*j yeZl ќm6,z8-ٵg/[`~5eޞ&ڳtdegn߶-'N$$(BB7y~<@BbR_ӑZm*眜[7mCLeΞ;o:Ö<޶M.9)ƛ~tО-۶cOHSkn;WkҬi<=U⸝| ,#=vÉS!9%!|*uvvxy7mBBbfà&MdghXYx{!TʛK3Յ?pLFFfˢT*j 5T*/D3y84LՕU2;4$ggNgƬRRRBM6ibqZ2 kky)Q]?D.ɒ( 9#r$Q* 6TO3/5/yXR #^ΈC1er܅h^;ZI4lPqc;fݺt1 YܰNެ^0+=oQXs7>R [Μwa͘Xquu )9DjZEffꆙel,rYռ%$`4+,'''MaRRSl@rJ*.uꘄꫦt:<<ԸTuݨN^_/sNzuի"b쌇ZmVV=Y;~T6nVlֶ U(,[e?''ظx4jh6_9suF~D_\%feN4},yym0xi|GteWG> uJRgaܔ8:8PٙĤ$T*s_z1(#^aE.*[KBb"8;HyB@ G* !" @ P GQ1- ( @ BX[تVH灨SDYPD"əTQKdnr( a? !H$HRT(mT]:/zwܓt #RCQ_-N^"E&↦4bDͱ0llU9:cgL&{=BQ_-NB{'lUFŒr{ԅP_DYa -Jd"[he:^NW˩S,xhrAӲ+@WKS,xhP[hQK@l#MaK"g@rkBX]@@ FШ@  @ P G@ 0GlԷg<:tffc. +AsA*q:f[@xQ-᧟qY^ya6~~fsrxu|}xo)>^ObR2wTvKLepqqE&ӻ~yoG4{]W tY cЀxdfenF.ĠP(e`~xyzțU+1sdBC{zO8 kj5;tLadR)NH .^Dhȿ#\5ȐLZL$"hj W[wm4iBb?&AP:+x]ե#CVo$$$i*I|B磣o9?_2z}DNHj:<ގ!#B\Bb,**7bL Ba%=1`ՓTOAvN:)3!)%!#޵ z}%%h?qWFf&Ͼ":tbc` 8ΜW`0Flmmx簲-hc˯-Y]>O[? oEDـ*a$i,z}I,1X1W˫yx=MOs0bduݺs^6+r6lٶݻrkUhd9ŴiՊ! J9矼0cl֯ӧOHd[Y4.=u/_]ÖB <=.>qSaR1zpZ4m=uo^=:h gkKMY^;?&<9gfDE+$&%̄ieʄq\IO^4]݆ѝ!ル #`Rh\y[b{F&+PJm_+%B1K>cHZK`$8mwe+XI`ٸi _1l$S&JO[2))__V˯O7I|/tH'#={9~$ygd0ԫV>"]Cӫ{wF _K\B=vg&gŻ>>?_v݇xmh2Z- PꆆέZd洩37 ?l/ 8 aL8?__ED!!tӗL0AL< Ms^N97mH떏9bAtۏsQm.gU8A*Qa3-ݔ,~~&)&WAfdHl*ȋ1 E %LTLbl&w\xD2239qt!Ĥ$$ Acnn899_m{RTwm5;s8xܼyKkzͫ HLb60|`Փ -w%fNn.6J*e2W˶;4b_ -za5ۡ+١JvJRUtƋF*R(8G˼4w>/͝OԒeȤRRRIL.#lF#e=l5) u { iLc{,ZrLgOZK =uelm٭+666׉2M #Zmviؠ>+JKKJN A:uNٵw@SX'I_c8r?XB@VKCEVK''Skq筮;J%M4fAS&Ӄ()-F?͵^ 2*//^"Q,lu_!9_8e PUL4[ak .Tۺ2wCamߤ@7Y&OsX[YѢY[  LN#9%w*axc"~;|--gVeU ӧqڴz?d*/۹g/soז'O&,4#$(:uټus]Zm:vụSHB E_glY^v'AzbmmMX5{ZR /bE rLD-@,;vzfz ^S[[[] %RAfV666(o>Dc-hPGѼ?4LՕU߲y_?") #*o^db-Jj4$=z$H̼û!&6GG\[Rr2o̬,23`Rl,r?_ryx\]]PWDovMu䒚h/O/I AT jbbc5-H2TXHJJ :j5..fm|t4-53?.ameUYehDsi6w_%//D5^8;9)85iL&Dz=~>>W JzFiWR^^+&9|prt4;v CTT*Z]ncyy$&&ackiKU=&2zb/!Lyj3'"~cry|we#-HEWr%fǝN*>XE APY4/tΞ}!gbUDʷK,^,JS X,#{&KܯD{EpτF@@ ,V7\;C[@ x04 'SNF'JD "߫C!*>"Eڱt!`BxO5"@`HEت牌`4yت Am#Hppr&3=UdslB(DP*wDi"yZ'w!IrɢP#2BQ삣s^ f\I7"S,S)--M%>!,*,$^#!3!QTXH^Nv] P !4D AKӋ+)ɵMB!A@{g:K Z}'l aixRSNnn.ZRQlqttB7~XYYT nyՊ'p bcc7RJKK%..rhެ~@p_+!秃HOO8z=DGGNΝ %#¢"υ jaYauѫgOlllD "Bx i47* |sT*[llmTPKvN6$$$RRRR% INNa<==*Ho")I:z&% ւL%J_ 'bbضm;:ʹ`Z4o/RiT* ͛c0HHHcVݺNhHȝ a^'?¯TrR:H@ 0C("x)[`0̎{<7TJ@?o~Totlٲߙ,Ȁo)UHPARt!^7,j4DdG xP|g>s cЀxdfenF.ĠP(e`~xyzțU+M%|{̜:PMwqq1寿SPP3[$O.\z=n DB^=iڤI x1R Jə@BBBppp!vgggg+r&KK_9zZVD&`%+~$Կ) Ox]۔I x^Ã}zSPP2~ ?1e3ԫƐsB4%%EZ8i+#3g_xȑ#@hvvӟ}\Jڕ+X֬LJ!#-!ݻ{E"ЭkWªV0Ma!OS{(-%''˗/sৃ4n܈c2К6m ~ H^=o+Q%D־LeEqVra5^{6ȰJX"P xxzxвy3tH?yRgXyս[q/1l`G=U՛T]$6@Ķ)++m 8DBNn.o)dzudeeS7,GcgWns ?lAf@pڵim\|HrJ NNNi6\\\ػ"ӧgGC1}ZؼyK@`0p).\f|}·V+ʎAu(9QlpZPI&CY1HˇD9 H .^A)/OO\]\Xn=S&Q4 xӫ# i8˯5'5 &1zpƍ?g1iS|͚o㱖-20~w%J~OAWb᧟EԼWX=Ξ3g˫d<޶ q 21zpҮ`o{{~wzboox7ع۾ ' h޴ I,Z(鈚?ӧp)XpL2iOxVh,a6 ʽs/~&ڵi?ЪeKfNZ٬qtp ql߱P= Fh HJJٳ–} j/u)z (BiŰ. 4 !ѥ;18H xh֔~T*|}<>Xߴ17h֤)9q.\FPXlZgh uaPnn5VD@%#+욠JoRThHHL`0gΧ7mHԽ}D"cj2,AAAo8y_ʹ ЩS&z"%e0E#,ƍ0I^^>m7^ݻѵSG==Wǂs'/iuwsŅGZÊxRAFcLggg1++y l4R@"0~l$Vk>( Z u=:3߿m۶"dkז~Vt$eNMKLh H ` (*.d't$^%o,Yoe5R)#W_#f箾SAYQQC qBCL?/!6c&oQ!;7GxWѽg5W^rOp~uvO-"u4l` $_$cT5F `GGjT$&%Ӡ~=F6ݝQ xi|ꆆ-#S\\ET(PͣU̟}zsGpP iצ5@R)o.]a68;v%<9j$CFp? Ip` Y<=}*];wyݕ9{.:/_]$5:]Eb41U6ggg˯\>Mǎl?x=Y'~'ss4t5rxv ]s 1Yz4PVb:j9Mf̮t k++ojz=Νa{KJJHLJB[T#>>f0+;Դ+x{yTɾp%=9ii@NЬIc]F84jdfc(.*FvI>=#+WpSo//JII% {;_gNEHZь˖ΪD,;;5߬Ґƅ '0fLƛin8r9|SbSt%+Š +$<`nxNqsmw?Տ k|:u\qggg͎yⴒ[j;62F"Tcvo䞩/j&W).._~%bjP;u  6ll92ko O+$ʀȂG{&Iá՟Ktmhխրu12F@ xĹgc7\ aBoy{-;v@YWQ*tA@ ,{6GXٙ1c"_*mSoF(>Q/^aĀ"@`U+@ت牌`4yت Am#Hppr&3=UdsL@ xEP*wDi"yZ'wLqq 88ab@ [dz}R)*-* kkkrIRXXHa4X"|}}P*@`BhHJNvގ:ΨTo&P(PT8;),,$;'M[z=qx{cgg'JJ {P8$\SѐTEJ%PkBRC@?J¼R$%%ޫ|lXԒe<}1^ʫ ?l̋Z̋ss)&VȻϿ;+DF#LMK[rq[就! zhiir\YY۰11( |ؿ^$$&>377 WW;t!7]GVKNIe9u2abv_g'gZjIx>]^9z\]-!4C899U{ZQѠhЕP>4jemvvXYYG+vwʚ+iw4o2 c9!_}->> _cwWώ{6x OO 5-+޼=&2^?ћ 1Y[4o֔eИe5_|񿯙:qx~A>gϟx^]Oto>ӛtSgmZ[223y9Dfg0 菻)]/j|}|20BOOORSSM^}0zLrss(-Օ!rEr'''\]zN岊 Q~[%9%Դ48:8Ы{H$кeK}7,6y̋zEe?R Z?W|B߮tB7f4v*k]mquqa߁ ߏ==]پk_S|o9)|a#II>d0AVVlݱ+++zvJNM:HZz:.u0zPnNxxТY3:w𾌞07mfh4|:_#c/)h1k6-6e F6mGDamMm4wyBر{eeem݊A#H0lں?TϗÆvwa1zp֮[OV1=r/EQQڴfF#΅,^& uNVNsһgwUtŘ> ]9r/iӪC0u0,پk'N晙ӫ<ہ_ yzxвyy}ҩ#Gp)nM/1lv:-W| ܾ]=rMumК! HwW2ex|lꆅ2vhʧ ?lAf@pڵim\|HrJ NNNi6\\\ػ"ӧgJEhH!!Պ`aXrsro3cܜ\bccjUNۛgws^vW^>ǎ䨑4_c'Nw?),͍D9W撔 @Rr2?ƍ%==^hOz{ާ Ωvp뎝 8D0N=cGۋO=CK*rܳ۶uF1ciøH^{-t?ht܉ȑٲ}~9I0cdΝc-[ǹS!{2n3ŷ{~ Va&ݿs ,%/?pws3{_~f“c"x=/Šymg>z0rNCʤqckԴ+DNƌۛI3"%S{Xwl۹!#ӫ' vSJu83.:'rWǶ5ݺ%~IMJiѴ)<ѻ>>{ϥCvu瓒rG+ $$$CEonfwxwjN1t0^JT;bk[ٺ}Ws^"5]ȴIhެ)3gfhXKF@p`=Ep>:ubemc`) p{9}W'W;/iXK&W͚̚W+z|F0kkkXUߓׇBG.Ӹa {H8;9!Lym6z:]) b29S Ma!F//\]]hҸ-[1WcU|7L;4gԴ)̋zS&Q>jJ_&M߄ӡ6d_YKfV8yS'M[̞78(a#2a<~>3멙=[Դhf7F,{rIrsH`S3W7 m`sKHG.w k+zjui1&O_޾6#!1}%Q Gt:3ӧp)XM2d&==9τ$ |E^qeee=fVEFf 1'ӬIc"GqouQ*5&LIָx8cǀ=Z|lYdԮM+<7# s9~w}QTs9$ AD ''GMBxuUepm۴&6Y-5%_8* ?_3Ofm`8s.WP(,6WPi40q fW7M[Vjj%#+욠JUhLA?Plv@Myk!L~5\]qqqTP`gg+YYYddfQBk-q^^zj&yyl޶TlJ^EޠiF^M7bIȠ{f|+vƍZ10y¸[^ ٰ~}~?|}Mgn<}֬ m-h qvaB-vr!\ >M]IfæwGиQV 89:֘c"[]JBneUs̜: cdמ~io,YM ??N7ӫ+?A:ҩC{,$XMUtmJG Ef\x:GGG|ymˉ' Td2r+d29f޸=m:8q4'Naڠ@<=wك=7(zi޴ Kx)_onMWd>–;,JDΗ_}m>NO{}t-J7"j8˫]i+0uJo`9CMjj3h$-|uZy P۩HLJAzU%ıc9yGvx5 ua1 <苗9 JJJHr!Jۧ7Wsܡ} a;oU|Z|}H$$V^}zsB4#N (0qkݚyZO>E"ښ9/gnB/{ms={9y49$GV sx G.ҨR)oW/am8;9s% 3jMoC?C^^>fϺx% !rd*>ͼrV?HYy95Z z={LtnGx468o+..ԐcE-;#dVAN SRZJ]}}'JgQ;RYY6o&-5%G jL766}RgdDM|>mā{ojjFؖ6Զl>ƶϷy&3#_0it qX,v+VTߣG gedd?j=}Vd3xPXXHsstJ$##paO6oz)fIcBx€S@œ^ݕʪv"2짪CX/w4Ǎʫle wz (EpmBr5vz5ss9}))ɝs "06n JX>c pLdBjJĴϾ [mS-l|Bd2mĨ 7yEawajj (A ZP/8v.I" ( bTWKa1Օd! BT* DE(.aZyۉ֡3ĢT*T RĂ *PTśp#R\/&Dkuc T.EA!lBCLZ@ QaR7{gGPPTRDìnLv t`}tGg+yt" GADAAPADAAPA$jTad a!'AڨtH :=qxb=dA Bw`UE.KR 1F)0R<7&K}B,AIv~"aFL>p9ں|Z,BADlXS+d*J" "q5:18c0o'~KR!8N\.nKRDVATTZVR)3 b(%Bt6R[[ W(=? NhDJ G#BIee%nw+?nnl -XV]"bb $&&-pt)RƒrQRRRDӢ鈌DVχtt6mih"--(%A!" AIIEb|;ϧ[m-^} D8Lmz̽Jʘ8~s6>_3MM.+/"wC/84(_INLY39Jʙ5`_gƟ6Ӫ?aCGc٘1g~ANKӷO.֊g >EDyy9 9>j7x<ޖ5' 0%$VfhCV6k,ee[ i7ּͧNcE8u)clM]e={GDFņxwsg0O_6: s7;n^V]A8{;~&+3ۙ4aƍ }dr =|v}yi+`1:i"23ٺm;9ٽXZ9mP&BAm],]+g蝛Ìi[X~oņA19ԡ.b嫯QZVF\\3Oc_kgdXF^pau:rPF ;8@]m466;l019)x^\w֮W\΀m]PQYE^~~%P(jL7{ɉ}0u:g[᪙3HKMaε7P]SӦO?ɓ> ~q`g2ihseSI2*nٕ$%)頞i΍3uİFO?fT˭L}1f^>Ԕ:vZ\*+^z5eqqs~O?G2|L:o}2xG-gͻ1)w9ܽx &(3NǴ)9yb F]8ƌɽ 3Kvihh"LLhHe{dFwS.@rWFsZxlٺO_*$%,̦aDTޗ/ ݊Z'g2}֑Y;t ԰\x#Ͽ@cc# JAVTJ*nxulܴz*k;4-w̧9pF+*|dgtzWbS3{ "K2h]D.3ۼy 7# Z|Wϝg_fSOV^-V;X~ꩬzm5C?kFDm{Bp:;AuؘZ-F WVVR:$32yG)-++_bI[ 9_rw[!ލEUVVQnA蕙AIYA_T\BfFzY"`ܘt:zXJz&_:~{_O?F {hpK'FQo;b N:72r~쿂ˬV]TĠwBh7Th4z=&S555TUWQRko5oa}Њ8]n4(fsf >.6vT>Ng#zi{L! B8hп߉LCgoǗpc{5H 6W,GHG@k_${K!7*JB`֌D#Bf Fi)ccdBP@JrrHth]fݎ˽׺zh6M wqT*luml4F<#SO?uH2 7;lraʪ*6oRAT ]yZuP/7H*3CK˯ͷ ߎWd<}rm`3O[t\~Z!'+ Ķ;~[Or \ZQQj" -[??_LL @%:b m߹;ZbQ\R }0ce3/|?^#Dw* FK;+gľ}HNJbkHHg#m7FFz:jҲ2_082x0O %56H-L]9yȉ}P]S"Wܿh!_@1ܘ6e2nw (L]]=Ĉ;og ٶ}/IVL~-e҄KL1 7qcGseS8~6„˦IUu _=sΘQزug?qcFۘ⮻^[xֈJrPPV!0ġE&u~|DAQ J%*JD0fthwl)B(!gt" GADAAPADAAPA$jTad a!'AڨtH :=qxb=dA Bw`UE.KR 1F)0R<7&K}B,AIv~"aFL>p9ں|Z,BADlXS+d*J" "q5:18c0o'~KR!8N\.nKRDVATTZVR)3 b(%Bt6R[[ W(=? NhDJ G#BIee%nw+?nnl -XV]"bb $&&-pt)RƒrQRRRDӢ鈌DVχtt6mih"--(%A!" AIIEb|;ϧ[m-^} D8Lmz̽Jʘ8~s6>_3MM.+/"wC/7m cy!u\yTVUI)Jrb"W͚PZVάy=ӍF6)VUu576v"8jƌ9xe p^2}rVT|?+V_F -¤$HX0 |TUWSWW.!t8PT)!:4FcjA\`)-+jؘFwnMUm> t:c/ũCN`Gevn~&MĽ?ȎXh4̺|:zl\s-Duew8xᥕlߵ )kdq v_~5[n|iy)/PXTLNVs6&wqBXA-mg2b86ň>{[og҄ L76e1'{(+/UPT\lf꤉gdbͻkinn洡C8b uu _o!>>u?qcyLJE&';Ct:/D;uu`^O$yqN#[Yf_q9wQ\R@Eey!痔RT\BY3f''Ǣg ڛnf -59@uMAݓF^nM%bλT mvp8IIJ:gns㭷3rL41Ͽઙ3:rkg_E!O#5%ˊ^fͻk8~#G\3}xx𑯣 IDAT vӧN`_ ^eY{Ll {w/^† ̹D1mdN0Q 551Fr ;}{뒝e(++;f?EEE$'':;"##yjc,})^:ǔK'ƪ앑VA;?~o-[n#-5u3 I-> )D{Q*˯femV |L:-~WYYEoF޹9Ml۾{G>KHِ +3CPkN8qcF虖b)|~q#﹗=&luOa,E!Чw.VD%(:Hos\GOdz /Zinn`wQ 8. uhRoHhL PU]F Z겛KIN뮥7ּlT{#Q:vh0M̙=3NNNcW^[ͽ 0:6ϔ  xX4 ~&&y|9 y86QW)xh}ppeN7Dr4( f͘N:"dV h4VU`?tl,<>!"LBB~T( HIN t̡bqZw^Ҳr)!Ҳ7nwޑ:JMՈqdiAfgMNV=Oͮ|̦㠁ع+_ w2|i%LGDDВ{oxw·U*>u2A\ ?e+K>`uIr0Ll۾ܜ'9e!V,(.By5CZ[C-;㮅X, z%з3_vP??W7I^^~oNT0zH.^B޹\9s'CrR]CBB<y8$͌tn&62QԔqWz2ccVf&ͼoľ}iO L[f_/wNNnL2GFb!Of3zl۾ɗ$W&S2i%]&߿ckaZQɸⲩL?Mae̤믞9g g̨ elٺɸ1mLq]w/X-{<*+pNEGGc6ZMch<JIjJJyy3r`wQ^=;8N{STܲMnN6jGAa!*LlTTVҷ^^SSv-#ujD(.)!-5Ͽ[o3{RzV!/(@vpg;w}##"HO>m9q)Gޛ(.)".6Ԑrk)Il}LrMfFͿlaInԯ_OAa!nn|eU֊ {hkr221m0=}Vd3xPXXHsstJ$##x>ܱ`!f;nY G:"lBVVVAhY.QUUEJLzfͽF#%%u:|ۭR0Y"86!t;=zkY c;HrRRADaRy@߈ ':C>c pLdBjJĴO  Vthddd,j1L1*BXWԋvJt (A ZP/8<]BA{ 1qF+˥0˜rb]LP^J%:C,Q: e`avu (.C " T*q&H+K ZX#zC,*-BBAD[Vg Hi͞!AF1T*T*0=ӡݱU >ևu] NW?$jTA!ABAA!ABAA8nQA}5,A8JX_k!&D5 )UeN,I)P„Z*Kxܘ, A]N'}1FtR_kkA! UcIN?(+w p܋cDdC¼.Kt:q\n<^/;f~?JZEDDQQQhZZ-J ۊpWHmm-?_xx<^Fjjl( :=FN+5, UHN'MvNT"( 677cZihwif&1|c AHEEII)>JNN#22Zf|>ӉNcTABAIii~@AONj4 :eV[pnRSRRS t=(qAk:JJJډ`TT餥W;CӑJFF:QQFPRR![u%<͛ A",Zۍ{`2d-Gtt4TVUaʭVr:y[b63|qi!|ǟij } =epRx1'z_lO?jtZ-}a¸hGT\R)npm}VyN /7OX|]]G{ZRS:RR[˺ץ|U Ơ'='\ߟXLG-ys vݼnݎ1!=rdXWm]>SB%!( )1{^ǃCAhDd$z^ODDDh ȈH[Jrb"G] W< \i̽r9Y I7ޢo_5 ɓ8?P;ʺ{[=1X,fV~zm5'ӥeV?4S&^Brr2󮻑+~?3 ys8~.;vƟ6Ӫ?SCܨV7ތZf¸M&˱VTA;YT&w| aRR$Z, v|>kW!t8PT)!:4FcjA\PIJL8a\<";W_';7x޾x<]>.%s9h4[c|o|(iE/+/W^蕙@]].{fŕ蝓Ӧd],~a4Qfq<ē~M_QnP(ZoͩC0qܸ`̨QL1䇍?q-7?ke%=z0md(gL}@^~/BZj{K 'g  䝵kٹkWv7qϒut]1FRb"dql3Y[0znv&Mqc~YA?|]ضxi+S'M ֿ{nNNv/ּfN:/FPP[W#K1Yxy%556z0c4z]7bWߠDrќ~ w(-+#..ӧy5dznG\2n,#/8wG&';Ct:/3v Cz=KBB+ gq[E?x홖J޺u{NW4Zns㭷3rL41Ͽઙ3:rkg_E!O#5%Z-J+^z5eqqs9>Yag0}5]5˦p޹p%lΜG:ӦL1uRSS3j$.ϰO?NFÊjnSTTDrr21"--nٓ JB{NN: .fK+3jd#K3}iի 9e0sZmŗ\&i I-a6%L/s^=,Y#Ǭ^4g9fH^\*WL w_M o;3VJUuQ#F0nhr91TUWc6:<\:a<ɝX~ mR\R"Jq ظiuuTVUquЧw.O-{O9!egiq5x_OqI ^{1LR{s'b4TVUt,lf5CHss3uV0O>eOAT>vg<Xø\.}vS{xɿeZf^IT1BB~'dnZxc!B1s9gx,؄G^ͦB˖>#Цp>-ED;s :䜽FEiP(̚1g/22hZVӱf$'% P6:e+hlldٓc]̘61sgb W⃏>noܴɓߋ/}f-R{O> r-tĢ@Ray23INJ{19AƘQ# |+F Ǎ>tw.YTYr455qǂDFDk'e]ǜY39k5 >RYN .vRZVNbuCK˯ͷ ߎWd<}rm`3>c Ώ,&۶ 7';INnr>u)os} h-FEEc08KP^n P|111֖P~%w_Ǧ͛j)-/#7'?҃˟D;{ZH\{իYzu'K_+^ -5@ F^ȶ;:c623uw!ʥ?GQ#Yx }zr$/b=OPTTVUdƒ^8x@ s]z_W|KnN6W]s]鱁xլyy~&N.*gD w䎻bX0urB>̘~Y[f_/wNNnL2GFb!Of3zl۾ɗ$W&S2i%]&߿ckaZQɸⲩL?Mae̤믞9g g̨ elٺ ,=UAQtt4f> oh@HR:\ϛCHO訨7Jq7(,d[6 HOohfjh'-[пppPX;deܜl@͇*u;˖HII&.62,ܽ;XZ˻13-]yLKEt? }Ej]766̖SUEjJJs&'J#jCڲ'h2}v.yyDFDk&|>y6sS755Q\RBE\l,i"[ c>f},[n232xle O S~-,6."OAa!n.򽲪 kE=zlxQcQVVNFFz0hZ%'BC;U*%. C[!yJi?m Ѥۂe"##[/fVdQA’^;05UCAEv0A: Rahm׿APBALr)08c,!Ah#J!hy2 CK0o;Q:tXJeZXC*x{$ՕV `„h}!J "B`C3$4LfO t*JT*`͞ЮABAl#BP(vB!Q       qD 샬! ?d QZu5)0!Z'O,AN(tbIJ!&(&4RY^dIO t%r:OD0̈5ݧ.Z[O]E(a @Kr pŜLEYiPP^~?F'#'bw鞰]*~Ӊvzi5P**"""BբjQ*eVV 㷾DFjkkq8 t6RScCT1tZaArDBt:n:|ev4DA!JCK3v7QTTDLA2E*Bx\.JJJ|W*tZt:ꠠ577x<8NƠ- v]%$ :JJK; zzt/Vht-tN'Z]|>w^A1K:\pPRRNH'-5u":T22҉҄6JH8~eV? at IDAT᷶˭vL.YMFFUUjl!#rNgeh"# vg~Z96m zΕc^{h0L| b^x=xqqq@sƟr`y+:ovLtӍ/;kmw]X߹HL=jd}%4a|H)PcГޓK.K.sxEc^˴ɓuou?K ~w}SUS[ѰC~Y|^/*?mVxRnH###1с]uM +_y]h4{1~O, WZVΣ˖s9}n[_:d0G aJH80$%&;}z^χшHz=d ,f3X+/DFA~,y/ 8Rɼ+gsC̼+gPQYmNny` :FW#ē?ΪrͷV?v fr***9u)\9r6|-}%-N>R~ٺgR>xrqC gseف\O]4Bv;?Y=N< ~/X nbf VsB> ߟ{xs5֊ tR_sUա+n{d\.r ~%].|qV+gۓdgև47;C_3̽eqnIS'tӧN= \~T""]VfV+~ƴX#;4ۙ4aƍ }dr =| r^Z E%XfNHL{nNNv/ּfN:/FPP[W#K1Yxy%556z0c4z]k[ͷ7h4\ _o!>>u?qcyk!r/( 5%/=Ngqynz~Ӕ)`; vp8I9@Zj*}7U%In@)[dɆD@*- q8pϫ7{*8" -MHҤIh iժCsrr7'}+Y0g=6Hv1tt ;vv>cn:_ډv1\?DaQoΛ<̳\9p]6[T*y'mZ~2ٽ-h?N3v#G{c!\j^Ycaт[nw~SGbkqF7\Om.fϘƐWroxu,*NJswQ`0[5b3MiWm6â^ p,;h-[Aډ 526/lɔ5b8/:voУO0?̜7} U/$IB'2md{f)~`rx7xy3(ʡ}_~Ifvk-Q]A***)*.fetо6U̝ͿK_Dp!frY33jd}S#gAVvwr9.tܷv ~-Z[DLt/7_BԱsƏ>W̞1x4.P^B\+W,oSQ[믐P38v<?}zc4 !1>sۚsw=Og:S'uٍ"3;a}F_/}w7O=Yd;rwշlܽf5~ :b̨Q8j|K,vִγ8|o;oNRbtq}L=X~5nM`@i 4LM'T*g_fLeU%jh_s*** PTTC>Δݚ,\pwm+569\Nef躧? F#WY3ri' W5{W^lNll/qh}ÝMڵcYWZ~!ڕ cǠVi5ܦʣ{T*{,v9\sؐ 9CwSRR;7`|nٶ|λ(VXNeen#a\{J})֮bꄿɱk e6{`4hl]X}p8+%g94^^g:ir/>.!EXX9T( ˆr8Zu>DZ̟=󼟷%[g9 R`ism^SSQ(\9p;v ӡҹ3Srۍ+Կ?o//v}J ImE]d^k1u2lx_M6_񟯾vqN8ˤW(J.<jj|/n^~|CY=&xv6xllݱp[bpT*>L49J_񧟈霣A#bIrRZ-Gw>}r}67퇅w苔`l4M[ Rɜyo;/0 v=o4 H9}.;NdoF8k~UKy@p8GZ gs)+/G/$::9t)~>LbBBܑJri;"ͭ\M\呞A`@[?ffVf:]B#ǎ :Ǐӭe.Jbcb\WN3[,;v555L@Ѹ =[EEz<==s;+*+CKvΑ%::@s|LjQS\֨nlA w$_T\АVF_@||yh?~G%]l/sy[/V+չ6wzx(o0B8;u)9AjBK ՅwE) ekmlnvYSB?U=W ! T.jBBV=Zb+~{Z$7芶GF ! B!$B B!BP!ːQBqC !R&HJ`pLB |&hPڈr Z-huQBZ5ADrmL@`0;c6,/kk !$*JEE?VGFQI(A(ˇn\m? 'vy2 ޢ ۢAh1Lf, V:v;JOOERRP*V_-&S5MFv9jc0)--CTQkFV;,7d2QTTRsnw`00 @BѶ^OUEwb!;;"""- i" <f>bJ\ zR{[%l#NC\X&KTdBAϿ A qq4~:/~sO/uOŴOIimXر{7 AՃ1FZaa tȈ\l0FlV+Z7zy{hhrݬB.<o/o귶l,Y~=;g -O*W!ڨSDEDQ#1 f9?j~s[%%p-̙9"Bk-++g ӓՒ_P>h6ÔZAIAA:nR\RBEEV[BBE!AAAhtݍ <==N>_yʋ0b m$>[j56={8/(_|DNn.pOLbBsl?#>TTTȓOt"|{]1u f3wykW>t+WDAn\9p#M?tc\ӭL<Ƶ}tSkx٧ݮE9עӦoc$'eN긼woLBGXǒbÿ6RZZF)̟=~,nMx3~|̬l6~y`lڿO6m!44{1i8F >+]4j5)ɤ$'7&S5TW͌*+Ƞm4sHBCywMlyfҩcGncyy ?{Q\zNG^{9)pE_zq>ټox ̤ضc`0pM2j0fNҦlE 7B,\D &&:EVvdC -w2eF˯ζ[Ǭ6}< ߏ93gw3:_uOe̚!jg.Xr-3ӽkW=r11ь={֬? ||LUU5⪮Nvv6QQQ4ެ_8O>RܫO\WUT-deg9ۧݰ<Ąx>Rv}rv~}P(7ձk^Ə#WA=+K/ײc̘:?ӧNfᬺjjj_?)IfV6Sκuhϩ,:3O_fs-WSݰ<;'΋ʭp5|} z絰cĹkG*@FhhhR4 Zm8Qm%'%EGErr*+} IDATtVR/6H`th4rnM&Qͼ&Y+@ѠT*8~,[@TC=*("t:t>Po8xn%>kW~1uXõaoIINj6,5F"clGõ̳}v8tY}}}P(\5^^.jǧz8G99&^QQsJB ,,(hoPf_@Dxy=@+ wjf]{үӛ ;|'rE(gf/޺}1hôL3o0o]'ϴM[ Rɜyo;sk={p-Rj9z8SEE79Փ^HԇeR#tE((;}!|8ѡF#)/ֱ4n[NFMNn.95Ә9! ʤoL4ؘfn}񔕗{v ^/O/rrsy{D}|] тi]x8ܽ[WCJ=c:K k_tp***գ;kn1Frq]@Rb2ۧ7&Oj0xB1a͚ɔ8xg&ϚCRB%\#yu>r+`{Gz*ǝkִƭV+EE6k???õ Usj\3}xi'OLXؙJ lrlTfADtpQq1[)(d&!>_yM?+K@R0B>KK{jsNˋpP[[ɣ4o~555Rm6HlLKZiY עFעr ccz=x6~>Le]l09e;iճbӅM/*.F_XHhHH}+^/ >>ɗtӳEl_ZdffRר`0P]m">>UPV7;6]c֚АBCB} ???:]ұv%lO7qϝ (xџiJ}1ۇĤf#$8ǃ vy,Bwf\NQkrig(J-m٭s>GjAXTT)..&3<ʉ r%W@!V BeMM Dėk4\5oABIe8-cjÃΝd-QֆU*Z&B)PlVDJ !hZiۻMPj[B\ DakaL Bv)TjURm?ŷ+A(S(SRT ц"(#A(BPTOMcR3l5̓SDThzJ !$ xxxhHONfLR FG% AJQm9OP!Z1 J%m9!(A(M>Zb+~{Z$7芶GF ! B!$B B!BP! B! {DRZB!0%$lB?iB!A(BH !B!B!A(BH !B!B!A(BH !B!BْL&f3F]mv>uJ<==JJB<BBr&#AiڱZmLՔP*Ш5VB BDQQKEoaw`00 @BѶ^OUEwb!;;"""wH!D BLnnM.W**j5xzz:ZV+& XUj31ʻ$mh$7/~@`ju/鉏j`ynmm-YYDGhBh$77YKD??ZZl6]v\p~i3_?/k yB\|n!VEP#>>bJ΄S{[_?˯Ƀ[_f钎̜:A9Ni6r fq\u;K ˗v.* Ǎe䰡'8/ztg17ZZV럧}=bx޿j&:q*3HϙM.mדY3i q!>1i8"t:JJKGHOLJvL7HsO/uOŴOI-.APPPm6шjV[Q/oo4  ^^^U(Ѕ卾P;Gҳ{7ztK̨GX?ܞ[RZʒsy9cӸ[xIpشu+eeMfcڔlVߵufc;xgsgfObṗ^K];xyyutSӫPwoS̜6|5K_^xuLe]XVS0wL>SkhX:v;J W͛KvN̝5K:tO7IB|8=ˬ ^GcЀ3Oۺc'- ص3翟yE.ؘyO'((ygȃ ϷYsxzzRZZqcw&k&nGPpy^zڻnͷaY3=/Rvq}!Lj>.v9S'M`i h(w| nЮ]ZVƇ|ʧ[¨cRֵ+?>]ʩȈ|˺p__!~ɉ&##$,4w7ϒW8f0[5b3MB-RUUE~~[MTT w{FXh(jZ b4k;㖛ṧdkx ֟W(+ }& <⫯$;;Ç%ĵeG8Oգs=bKof4n^շ̰!2TU``i'NSJR66 ///***7tHIU+RQQIQq1+W,sT_8OG|%pNΡ[~u4-G63l`*5E.[& :`ڜyDGc2U R+:ve1jƸx}ܫ#zY:ӧNHqѤ І$FA g_Q䤤V9ВRV|Çys]?ᒎ5}*\7~{֬fF,!>8?rՋ~R>>T[\BZDFFp!yD7GT*w**+2͢˹WyEtt|9׉bTVV閭.A8b׭'bۥPy?O?Q9BPFtT4Zq(e7ȔI ~B }j_ۨ60{t<]jZNObaQ}zϿh:F]]y{o*-;35baێ]Aqx2or ­N ]l ?*C}+ ff#/pi=^^^<|=w6m&'~aD@k3X{eel޶|8fϘΈCx L&z>)'o9!I d˭7o |1fL²%ƺ[V^k 00Ѷ]ǏУ OL0~fy$&&PRRʊk4{}_o0h*XZts2}zC-(^5+'1!SYL?˺tp)'UwCmm-9k4<#f_!v,-VEQGK:2ά&ׅku:49xMիwY*Zc6IZ[󩭭u>r/dDGRQQp /,`ӡILLp/ 4$_lvZrr0M׮pM\v.b  qۯST~Ih9X$(0m***'R#,,q*ɄVmvW32PTMp3c6:s+@uu5:])Iz[/V+ԝ5CI|||Bq>Aj? _TTP?]B!Ղ|gB!tA2 ! T.jBBV=>>jղr˺dfeFwJ!F\ge/:.jj5jٌ^wot󻅡l pI:oWs1M&ϙMe]LB?{n!VEP#>>bJ΄S[젏>Ν:1wL|}}\šr2J%555x,?N;wEt⸱6}!? JŔ=>f1xƌ뎝{9 %7ez=ټ|7{j}YsXjj[Ye{$̝t/;ub8oztKeΝ8ATJHIN޽%.HtTzhvGnx |;urFED8?WqO r8JZMJr2)MTMzzWs@EyTWW-wpRXT6L^2Nepxu\7(0v8r8vGRXTRdDEF2~Zu;}zdڔx{{qת۹j\,:~ v7~' w @Ԯ|3No\̾vOJ^^>,W@.Myy,Zv1,Z0"~82i\s? [W=>̶IJ~A~>!7f48h"o/ZUUn}磮Nvv6QQQAك={8k|1O 1!*PRRR7_w$(0ȈBCPp֏>hr?v BAk64!:UYUnwY_ѠT*)nX-[yWHJHp|J>=b8G w>Ͽ +;;n\}ك33>vҸC2qq8xg:y"Q yfCpp0z=oGrMO8;v?:^0geM1l(* c0m|V^FvTTTRT\ءh 4LM'T*g_fLeU%FTGqBaQ6k}ZjoZ[|a(VkT( kÃ\v;/z& zܶUK3^lm3MJ%z.СO`XaRI$%wvz,[y#7^^^-1G:>ݷlغ}'1Ѭ ml/ɳ/@uu}ϴk.L< 8kgއl汇pXHH0v: BqNtOMu{|Ͼ e gB267ofiLNGΗJA ^gw (9BPV>Vs4-3j͞E| ?l6/555) {>GvNybaM&p} ̞1CB|<;wiӭe wE>d0mw>M[ѭ~~~$'~ IDAT%rq'';;י:qZԿztGT2?t{9?:s; q7l\&555ܶf-^^<}n!ظpѧrNO|fNȨÝ-^s~'$~aDYÊ]P?_@@~t薊7vQ irfb}ٻs͙OTd٬f{sydQ|isKz*ǝkZBeZ)**v־:[VKzFJDKJ$,4ԹN^~k/_@hHsxZm!!@ +;v휵lv qM_MM '3h4.#Hږ =#NbB[T) :Bϔ}q11nopquJrrP.6۸Ls(-+uf`` I 8rr󨬪BF.\>en(+\ ϗ ڧ=LvBzF*|!^ZV233smPa(BOO O(..B&Z.kjB BL!Įd)]!B BC'B>%]05f!d2k.aիwrB<ÊW`qwIENDB`iwgtk-0.9/src/000077500000000000000000000000001435506763100133105ustar00rootroot00000000000000iwgtk-0.9/src/adapter.c000066400000000000000000000073341435506763100151030ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" void adapter_set(Adapter *adapter) { { GVariant *name_var; const gchar *name; name_var = g_dbus_proxy_get_cached_property(adapter->proxy, "Name"); name = g_variant_get_string(name_var, NULL); gtk_label_set_text(GTK_LABEL(adapter->name_label), name); g_variant_unref(name_var); } { GVariant *vendor_var, *model_var; const gchar *vendor, *model; gchar *card_full_name; vendor_var = g_dbus_proxy_get_cached_property(adapter->proxy, "Vendor"); model_var = g_dbus_proxy_get_cached_property(adapter->proxy, "Model"); vendor = g_variant_get_string(vendor_var, NULL); model = g_variant_get_string(model_var, NULL); card_full_name = g_strconcat(vendor, " ", model, NULL); g_variant_unref(vendor_var); g_variant_unref(model_var); gtk_widget_set_tooltip_text(GTK_WIDGET(adapter->frame), card_full_name); g_free(card_full_name); } } Adapter* adapter_add(Window *window, GDBusObject *object, GDBusProxy *proxy) { Adapter *adapter; GtkWidget *vbox; GtkWidget *row1; adapter = g_malloc(sizeof(Adapter)); adapter->proxy = proxy; adapter->frame = gtk_frame_new(NULL); g_object_ref_sink(adapter->frame); gtk_widget_set_hexpand(adapter->frame, FALSE); gtk_box_append(GTK_BOX(window->adapter_hbox), adapter->frame); vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); gtk_frame_set_child(GTK_FRAME(adapter->frame), vbox); row1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); adapter->device_buttons = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); g_object_ref_sink(adapter->device_buttons); gtk_box_append(GTK_BOX(vbox), row1); gtk_box_append(GTK_BOX(vbox), adapter->device_buttons); adapter->name_label = gtk_label_new(NULL); g_object_ref_sink(adapter->name_label); gtk_box_append(GTK_BOX(row1), adapter->name_label); adapter->power_switch = switch_new(proxy, "Powered"); g_object_ref_sink(adapter->power_switch); gtk_box_append(GTK_BOX(row1), adapter->power_switch); gtk_widget_set_tooltip_text(GTK_WIDGET(adapter->power_switch), _("Enable/disable wireless adapter (RF kill)")); adapter->handler_update = g_signal_connect_swapped(proxy, "g-properties-changed", G_CALLBACK(adapter_set), adapter); adapter_set(adapter); couple_register(window, ADAPTER_DEVICE, 0, adapter, object); return adapter; } void adapter_remove(Window *window, Adapter *adapter) { gtk_box_remove(GTK_BOX(window->adapter_hbox), adapter->frame); couple_unregister(window, ADAPTER_DEVICE, 0, adapter); g_object_unref(adapter->frame); g_object_unref(adapter->device_buttons); g_object_unref(adapter->name_label); g_object_unref(adapter->power_switch); g_signal_handler_disconnect(adapter->proxy, adapter->handler_update); g_free(adapter); } void bind_adapter_device(Adapter *adapter, Device *device) { gtk_box_append(GTK_BOX(adapter->device_buttons), device->button); } void unbind_adapter_device(Adapter *adapter, Device *device) { gtk_box_remove(GTK_BOX(adapter->device_buttons), device->button); } iwgtk-0.9/src/adapter.h000066400000000000000000000024671435506763100151120ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_ADAPTER_H #define _IWGTK_ADAPTER_H typedef struct Adapter_s Adapter; struct Adapter_s { GDBusProxy *proxy; // Widgets GtkWidget *frame; GtkWidget *device_buttons; GtkWidget *name_label; GtkWidget *power_switch; // Handlers gulong handler_update; }; void adapter_set(Adapter *adapter); Adapter* adapter_add(Window *window, GDBusObject *object, GDBusProxy *proxy); void adapter_remove(Window *window, Adapter *adapter); void bind_adapter_device(Adapter *adapter, Device *device); void unbind_adapter_device(Adapter *adapter, Device *device); #endif iwgtk-0.9/src/adhoc.c000066400000000000000000000170041435506763100145340ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" static const CallbackMessages adhoc_start_messages = { N_("Ad-hoc node has been started"), N_("Failed to start ad-hoc node"), NULL, FALSE }; static const CallbackMessages adhoc_stop_messages = { N_("Ad-hoc node has been stopped"), N_("Failed to stop ad-hoc node"), NULL, FALSE }; void psk_toggle_changed(GtkCheckButton *psk_toggle, AdHocDialog *adhoc_dialog) { gboolean psk_state; psk_state = gtk_check_button_get_active(psk_toggle); gtk_widget_set_sensitive(adhoc_dialog->psk, psk_state); } void adhoc_dialog_launch(AdHoc *adhoc) { AdHocDialog *adhoc_dialog; GtkWidget *table, *buttons; adhoc_dialog = g_malloc(sizeof(AdHocDialog)); adhoc_dialog->adhoc = adhoc; adhoc_dialog->window = gtk_window_new(); gtk_window_set_title(GTK_WINDOW(adhoc_dialog->window), _("Start ad-hoc node")); adhoc_dialog->ssid = gtk_entry_new(); adhoc_dialog->psk = gtk_password_entry_new(); gtk_password_entry_set_show_peek_icon(GTK_PASSWORD_ENTRY(adhoc_dialog->psk), TRUE); buttons = dialog_buttons(adhoc_dialog, (SubmitCallback) adhoc_dialog_submit, adhoc_dialog->window); table = gtk_grid_new(); gtk_window_set_child(GTK_WINDOW(adhoc_dialog->window), table); adhoc_dialog->psk_toggle = gtk_check_button_new(); gtk_check_button_set_active(GTK_CHECK_BUTTON(adhoc_dialog->psk_toggle), TRUE); g_signal_connect(adhoc_dialog->psk_toggle, "toggled", G_CALLBACK(psk_toggle_changed), adhoc_dialog); gtk_grid_attach(GTK_GRID(table), gtk_label_new(_("SSID: ")), 0, 0, 1, 1); gtk_grid_attach(GTK_GRID(table), adhoc_dialog->ssid, 2, 0, 1, 1); gtk_grid_attach(GTK_GRID(table), gtk_label_new(_("Password: ")), 0, 1, 1, 1); gtk_grid_attach(GTK_GRID(table), adhoc_dialog->psk_toggle, 1, 1, 1, 1); gtk_grid_attach(GTK_GRID(table), adhoc_dialog->psk, 2, 1, 1, 1); gtk_grid_attach(GTK_GRID(table), buttons, 2, 2, 1, 1); grid_column_set_alignment(table, 0, GTK_ALIGN_END); grid_column_set_alignment(table, 2, GTK_ALIGN_START); g_signal_connect_swapped(adhoc_dialog->window, "destroy", G_CALLBACK(g_free), adhoc_dialog); gtk_widget_show(adhoc_dialog->window); } void adhoc_dialog_submit(AdHocDialog *adhoc_dialog) { const gchar *ssid, *psk; gboolean use_psk; const char *method; GVariant *parameters; use_psk = gtk_check_button_get_active(GTK_CHECK_BUTTON(adhoc_dialog->psk_toggle)); ssid = gtk_editable_get_text(GTK_EDITABLE(adhoc_dialog->ssid)); psk = gtk_editable_get_text(GTK_EDITABLE(adhoc_dialog->psk)); if (*ssid == '\0') { return; } if (use_psk) { if (*psk == '\0') { return; } method = "Start"; parameters = g_variant_new("(ss)", ssid, psk); } else { method = "StartOpen"; parameters = g_variant_new("(s)", ssid); } g_dbus_proxy_call( adhoc_dialog->adhoc->proxy, method, parameters, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_notify, (gpointer) &adhoc_start_messages); gtk_window_destroy(GTK_WINDOW(adhoc_dialog->window)); } void adhoc_button_clicked(AdHoc *adhoc) { GVariant *started_var; gboolean started; started_var = g_dbus_proxy_get_cached_property(adhoc->proxy, "Started"); started = g_variant_get_boolean(started_var); if (started) { g_dbus_proxy_call( adhoc->proxy, "Stop", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_notify, (gpointer) &adhoc_stop_messages); } else { adhoc_dialog_launch(adhoc); } g_variant_unref(started_var); } void adhoc_set(AdHoc *adhoc) { GVariant *started_var; gboolean started; started_var = g_dbus_proxy_get_cached_property(adhoc->proxy, "Started"); started = g_variant_get_boolean(started_var); // Remove old peer list if (adhoc->peer_list) { gtk_box_remove(GTK_BOX(adhoc->device->master), adhoc->peer_list); g_object_unref(adhoc->peer_list); } adhoc->peer_list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); gtk_widget_set_margin_start(adhoc->peer_list, 5); g_object_ref_sink(adhoc->peer_list); gtk_box_append(GTK_BOX(adhoc->device->master), adhoc->peer_list); if (started) { GVariant *peer_list_var; GVariantIter *iter; const gchar *peer_mac; int n; peer_list_var = g_dbus_proxy_get_cached_property(adhoc->proxy, "ConnectedPeers"); g_variant_get(peer_list_var, "as", &iter); n = 0; while (g_variant_iter_next(iter, "&s", &peer_mac)) { GtkWidget *peer_label; peer_label = gtk_label_new(peer_mac); gtk_box_append(GTK_BOX(adhoc->peer_list), peer_label); gtk_widget_set_halign(peer_label, GTK_ALIGN_START); n ++; } g_variant_iter_free(iter); g_variant_unref(peer_list_var); { gchar *n_peers_str; n_peers_str = g_strdup_printf(ngettext("One connected peer", "%d connected peers", n), n); gtk_label_set_text(GTK_LABEL(adhoc->n_peers), n_peers_str); g_free(n_peers_str); } gtk_button_set_label(GTK_BUTTON(adhoc->button), _("Stop node")); } else { gtk_label_set_text(GTK_LABEL(adhoc->n_peers), ""); gtk_button_set_label(GTK_BUTTON(adhoc->button), _("Start node")); } g_variant_unref(started_var); } AdHoc* adhoc_add(Window *window, GDBusObject *object, GDBusProxy *proxy) { AdHoc *adhoc; adhoc = g_malloc(sizeof(AdHoc)); adhoc->proxy = proxy; adhoc->button = gtk_button_new_with_label(NULL); g_object_ref_sink(adhoc->button); g_signal_connect_swapped(adhoc->button, "clicked", G_CALLBACK(adhoc_button_clicked), adhoc); adhoc->n_peers = gtk_label_new(NULL); g_object_ref_sink(adhoc->n_peers); adhoc->peer_list = NULL; gtk_widget_set_halign(adhoc->n_peers, GTK_ALIGN_START); gtk_widget_set_margin_start(adhoc->n_peers, 5); couple_register(window, DEVICE_ADHOC, 1, adhoc, object); adhoc->handler_update = g_signal_connect_swapped(proxy, "g-properties-changed", G_CALLBACK(adhoc_set), adhoc); return adhoc; } void adhoc_remove(Window *window, AdHoc *adhoc) { couple_unregister(window, DEVICE_ADHOC, 1, adhoc); g_object_unref(adhoc->button); g_object_unref(adhoc->n_peers); if (adhoc->peer_list) { g_object_unref(adhoc->peer_list); } g_signal_handler_disconnect(adhoc->proxy, adhoc->handler_update); g_free(adhoc); } void bind_device_adhoc(Device *device, AdHoc *adhoc) { adhoc->device = device; gtk_grid_attach(GTK_GRID(device->table), adhoc->button, 3, 0, 1, 1); gtk_box_append(GTK_BOX(device->master), adhoc->n_peers); adhoc_set(adhoc); } void unbind_device_adhoc(Device *device, AdHoc *adhoc) { adhoc->device = NULL; gtk_grid_remove(GTK_GRID(device->table), adhoc->button); gtk_box_remove(GTK_BOX(device->master), adhoc->n_peers); if (adhoc->peer_list) { gtk_box_remove(GTK_BOX(device->master), adhoc->peer_list); } } iwgtk-0.9/src/adhoc.h000066400000000000000000000033371435506763100145450ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_ADHOC_H #define _IWGTK_ADHOC_H typedef struct AdHoc_s AdHoc; typedef struct AdHocDialog_s AdHocDialog; typedef struct Device_s Device; struct AdHoc_s { GDBusProxy *proxy; Device *device; // Widgets GtkWidget *button; GtkWidget *n_peers; GtkWidget *peer_list; // Handlers gulong handler_update; }; struct AdHocDialog_s { AdHoc *adhoc; GtkWidget *window; GtkWidget *ssid; GtkWidget *psk; GtkWidget *psk_toggle; }; void psk_toggle_changed(GtkCheckButton *psk_toggle, AdHocDialog *adhoc_dialog); void adhoc_dialog_launch(AdHoc *adhoc); void adhoc_dialog_submit(AdHocDialog *adhoc_dialog); void adhoc_dialog_cancel(AdHocDialog *adhoc_dialog); void adhoc_button_clicked(AdHoc *adhoc); void adhoc_set(AdHoc *adhoc); AdHoc* adhoc_add(Window *window, GDBusObject *object, GDBusProxy *proxy); void adhoc_remove(Window *window, AdHoc *adhoc); void bind_device_adhoc(Device *device, AdHoc *adhoc); void unbind_device_adhoc(Device *device, AdHoc *adhoc); #endif iwgtk-0.9/src/agent.c000066400000000000000000000172001435506763100145520ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" GDBusArgInfo arg_network = {-1, "network", "o", NULL}; GDBusArgInfo arg_passphrase = {-1, "passphrase", "s", NULL}; GDBusArgInfo arg_username = {-1, "user", "s", NULL}; GDBusArgInfo arg_reason = {-1, "reason", "s", NULL}; GDBusInterfaceInfo agent_interface_info = { -1, IWD_IFACE_AGENT, (GDBusMethodInfo *[]) { &(GDBusMethodInfo) { -1, "Release", NULL, NULL, NULL }, &(GDBusMethodInfo) { -1, "RequestPassphrase", (GDBusArgInfo *[]) {&arg_network, NULL}, (GDBusArgInfo *[]) {&arg_passphrase, NULL}, NULL }, &(GDBusMethodInfo) { -1, "RequestPrivateKeyPassphrase", (GDBusArgInfo *[]) {&arg_network, NULL}, (GDBusArgInfo *[]) {&arg_passphrase, NULL}, NULL }, &(GDBusMethodInfo) { -1, "RequestUserNameAndPassword", (GDBusArgInfo *[]) {&arg_network, NULL}, (GDBusArgInfo *[]) {&arg_username, &arg_passphrase, NULL}, NULL }, &(GDBusMethodInfo) { -1, "RequestUserPassword", (GDBusArgInfo *[]) {&arg_network, &arg_username, NULL}, (GDBusArgInfo *[]) {&arg_passphrase, NULL}, NULL }, &(GDBusMethodInfo) { -1, "Cancel", (GDBusArgInfo *[]) {&arg_reason, NULL}, NULL, NULL }, NULL }, NULL, // Signal info NULL, // Property info NULL // Annotation info }; GDBusInterfaceVTable agent_interface_vtable = { (GDBusInterfaceMethodCallFunc) agent_method_call_handler, NULL, NULL }; void agent_register(GDBusProxy *proxy) { GError *err; Agent *agent; static const gchar *agent_error_msg = "Agent registration has failed: %s\n"; agent = g_malloc(sizeof(Agent)); agent->proxy = proxy; agent->invocation = NULL; agent->pass_widget = NULL; agent->window = NULL; err = NULL; agent->registration_id = g_dbus_connection_register_object( g_dbus_proxy_get_connection(proxy), IWGTK_PATH_AGENT, &agent_interface_info, &agent_interface_vtable, agent, (GDestroyNotify) agent_remove, &err); if (agent->registration_id == 0) { g_printerr(agent_error_msg, err->message); g_error_free(err); } g_dbus_proxy_call( proxy, "RegisterAgent", g_variant_new("(o)", IWGTK_PATH_AGENT), G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_log, (gpointer) agent_error_msg); } void agent_remove(Agent *agent) { g_free(agent); } void agent_method_call_handler(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, Agent *agent) { request_cancel(agent); agent->invocation = invocation; if (strcmp(method_name, "Release") == 0) { g_dbus_connection_unregister_object(connection, agent->registration_id); } else if (strcmp(method_name, "RequestPassphrase") == 0) { request_dialog(agent, USERNAME_NONE); } else if (strcmp(method_name, "RequestPrivateKeyPassphrase") == 0) { request_dialog(agent, USERNAME_NONE); } else if (strcmp(method_name, "RequestUserNameAndPassword") == 0) { request_dialog(agent, USERNAME_ASK); } else if (strcmp(method_name, "RequestUserPassword") == 0) { request_dialog(agent, USERNAME_TELL); } else if (strcmp(method_name, "Cancel") == 0) { const gchar *reason; gchar *message; g_variant_get(parameters, "(&s)", &reason); message = g_strdup_printf(_("Connection attempt has been canceled: %s"), reason); send_notification(message); g_free(message); g_dbus_method_invocation_return_value(invocation, NULL); } else { g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.UnknownMethod", "Unknown method"); } } void request_dialog(Agent *agent, guint8 request_type) { GVariant *parameters; GtkWidget *table, *user_widget; const gchar *network_path; int i; parameters = g_dbus_method_invocation_get_parameters(agent->invocation); agent->window = gtk_window_new(); gtk_window_set_title(GTK_WINDOW(agent->window), _("Wireless network credentials")); table = gtk_grid_new(); gtk_window_set_child(GTK_WINDOW(agent->window), table); if (request_type == USERNAME_NONE) { g_variant_get(parameters, "(&o)", &network_path); user_widget = NULL; agent->user_widget = NULL; } else { if (request_type == USERNAME_ASK) { g_variant_get(parameters, "(&o)", &network_path); user_widget = gtk_entry_new(); agent->user_widget = user_widget; } else if (request_type == USERNAME_TELL) { const gchar *username; g_variant_get(parameters, "(&o&s)", &network_path, &username); user_widget = gtk_label_new(username); agent->user_widget = NULL; } } { GDBusProxy *proxy; GVariant *ssid_var; const gchar *ssid; proxy = G_DBUS_PROXY(g_dbus_object_manager_get_interface(global.manager, network_path, IWD_IFACE_NETWORK)); ssid_var = g_dbus_proxy_get_cached_property(proxy, "Name"); ssid = g_variant_get_string(ssid_var, NULL); gtk_grid_attach(GTK_GRID(table), gtk_label_new(_("SSID: ")), 0, 0, 1, 1); gtk_grid_attach(GTK_GRID(table), gtk_label_new(ssid), 1, 0, 1, 1); g_variant_unref(ssid_var); g_object_unref(proxy); } i = 1; if (user_widget) { gtk_grid_attach(GTK_GRID(table), gtk_label_new(_("Username: ")), 0, i, 1, 1); gtk_grid_attach(GTK_GRID(table), user_widget, 1, i, 1, 1); i ++; } agent->pass_widget = gtk_password_entry_new(); gtk_password_entry_set_show_peek_icon(GTK_PASSWORD_ENTRY(agent->pass_widget), TRUE); gtk_grid_attach(GTK_GRID(table), gtk_label_new(_("Password: ")), 0, i, 1, 1); gtk_grid_attach(GTK_GRID(table), agent->pass_widget, 1, i, 1, 1); i ++; { GtkWidget *buttons; buttons = dialog_buttons(agent, (SubmitCallback) request_submit, agent->window); gtk_grid_attach(GTK_GRID(table), buttons, 1, i, 1, 1); } grid_column_set_alignment(table, 0, GTK_ALIGN_END); grid_column_set_alignment(table, 1, GTK_ALIGN_START); g_signal_connect_swapped(agent->window, "destroy", G_CALLBACK(request_cancel), agent); gtk_widget_show(agent->window); } void request_submit(Agent *agent) { const gchar *password; password = gtk_editable_get_text(GTK_EDITABLE(agent->pass_widget)); if (*password == '\0') { return; } if (agent->user_widget) { const gchar *username; username = gtk_editable_get_text(GTK_EDITABLE(agent->user_widget)); g_dbus_method_invocation_return_value(agent->invocation, g_variant_new("(ss)", username, password)); } else { g_dbus_method_invocation_return_value(agent->invocation, g_variant_new("(s)", password)); } agent->invocation = NULL; gtk_window_destroy(GTK_WINDOW(agent->window)); } void request_cancel(Agent *agent) { if (agent->invocation != NULL) { g_dbus_method_invocation_return_dbus_error(agent->invocation, "net.connman.iwd.Agent.Error.Canceled", "Connection attempt canceled"); agent->invocation = NULL; } } iwgtk-0.9/src/agent.h000066400000000000000000000027511435506763100145640ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_AGENT_H #define _IWGTK_AGENT_H #define USERNAME_NONE 0 #define USERNAME_ASK 1 #define USERNAME_TELL 2 typedef struct Agent_s Agent; struct Agent_s { guint registration_id; GDBusProxy *proxy; GDBusMethodInvocation *invocation; GtkWidget *window; GtkWidget *user_widget; GtkWidget *pass_widget; }; void agent_register(GDBusProxy *proxy); void agent_remove(Agent *agent); void agent_method_call_handler(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, Agent *data); void request_dialog(Agent *data, guint8 request_type); void request_submit(Agent *data); void request_cancel(Agent *data); #endif iwgtk-0.9/src/ap.c000066400000000000000000000124451435506763100140620ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" static const CallbackMessages ap_start_messages = { N_("AP has been started"), N_("Failed to start AP"), NULL, FALSE }; static const CallbackMessages ap_stop_messages = { N_("AP has been stopped"), N_("Failed to stop AP"), NULL, FALSE }; void ap_dialog_launch(AP *ap) { APDialog *dialog; GtkWidget *table, *buttons; dialog = g_malloc(sizeof(APDialog)); dialog->ap = ap; dialog->window = gtk_window_new(); gtk_window_set_title(GTK_WINDOW(dialog->window), _("Start wireless access point")); dialog->ssid = gtk_entry_new(); dialog->psk = gtk_password_entry_new(); gtk_password_entry_set_show_peek_icon(GTK_PASSWORD_ENTRY(dialog->psk), TRUE); buttons = dialog_buttons(dialog, (SubmitCallback) ap_dialog_submit, dialog->window); table = gtk_grid_new(); gtk_window_set_child(GTK_WINDOW(dialog->window), table); gtk_grid_attach(GTK_GRID(table), gtk_label_new(_("SSID: ")), 0, 0, 1, 1); gtk_grid_attach(GTK_GRID(table), dialog->ssid, 1, 0, 1, 1); gtk_grid_attach(GTK_GRID(table), gtk_label_new(_("Password: ")), 0, 1, 1, 1); gtk_grid_attach(GTK_GRID(table), dialog->psk, 1, 1, 1, 1); gtk_grid_attach(GTK_GRID(table), buttons, 1, 2, 1, 1); grid_column_set_alignment(table, 0, GTK_ALIGN_END); grid_column_set_alignment(table, 1, GTK_ALIGN_START); g_signal_connect_swapped(dialog->window, "destroy", G_CALLBACK(g_free), dialog); gtk_widget_show(dialog->window); } void ap_dialog_submit(APDialog *dialog) { const gchar *ssid, *psk; ssid = gtk_editable_get_text(GTK_EDITABLE(dialog->ssid)); psk = gtk_editable_get_text(GTK_EDITABLE(dialog->psk)); if (*ssid == '\0' || *psk == '\0') { return; } g_dbus_proxy_call( dialog->ap->proxy, "Start", g_variant_new("(ss)", ssid, psk), G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_notify, (gpointer) &ap_start_messages); gtk_window_destroy(GTK_WINDOW(dialog->window)); } void ap_button_clicked(AP *ap) { GVariant *started_var; gboolean started; started_var = g_dbus_proxy_get_cached_property(ap->proxy, "Started"); started = g_variant_get_boolean(started_var); if (started) { g_dbus_proxy_call( ap->proxy, "Stop", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_notify, (gpointer) &ap_stop_messages); } else { ap_dialog_launch(ap); } g_variant_unref(started_var); } void ap_set(AP *ap) { { GVariant *started_var; gboolean started; started_var = g_dbus_proxy_get_cached_property(ap->proxy, "Started"); started = g_variant_get_boolean(started_var); if (started) { gtk_button_set_label(GTK_BUTTON(ap->button), _("Stop AP")); } else { gtk_button_set_label(GTK_BUTTON(ap->button), _("Start AP")); } g_variant_unref(started_var); } { GVariant *name_var; name_var = g_dbus_proxy_get_cached_property(ap->proxy, "Name"); if (name_var) { gchar *ssid_label; ssid_label = g_strdup_printf(_("SSID: %s"), g_variant_get_string(name_var, NULL)); g_variant_unref(name_var); gtk_label_set_text(GTK_LABEL(ap->ssid), ssid_label); g_free(ssid_label); gtk_widget_show(ap->ssid); } else { gtk_widget_hide(ap->ssid); } } } AP* ap_add(Window *window, GDBusObject *object, GDBusProxy *proxy) { AP *ap; ap = g_malloc(sizeof(AP)); ap->proxy = proxy; ap->button = gtk_button_new_with_label(NULL); g_object_ref_sink(ap->button); g_signal_connect_swapped(ap->button, "clicked", G_CALLBACK(ap_button_clicked), ap); ap->ssid = gtk_label_new(NULL); g_object_ref_sink(ap->ssid); gtk_widget_hide(ap->ssid); gtk_widget_set_halign(ap->ssid, GTK_ALIGN_START); couple_register(window, DEVICE_AP, 1, ap, object); ap->handler_update = g_signal_connect_swapped(proxy, "g-properties-changed", G_CALLBACK(ap_set), ap); return ap; } void ap_remove(Window *window, AP *ap) { couple_unregister(window, DEVICE_AP, 1, ap); g_object_unref(ap->button); g_object_unref(ap->ssid); g_signal_handler_disconnect(ap->proxy, ap->handler_update); g_free(ap); } void bind_device_ap(Device *device, AP *ap) { ap->device = device; gtk_grid_attach(GTK_GRID(device->table), ap->button, 3, 0, 1, 1); gtk_box_append(GTK_BOX(device->master), ap->ssid); ap_set(ap); } void unbind_device_ap(Device *device, AP *ap) { ap->device = NULL; gtk_grid_remove(GTK_GRID(device->table), ap->button); gtk_box_remove(GTK_BOX(device->master), ap->ssid); } iwgtk-0.9/src/ap.h000066400000000000000000000027461435506763100140720ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_AP_H #define _IWGTK_AP_H typedef struct AP_s AP; typedef struct APDialog_s APDialog; typedef struct Device_s Device; struct AP_s { GDBusProxy *proxy; Device *device; // Widgets GtkWidget *button; GtkWidget *ssid; // Handlers gulong handler_update; }; struct APDialog_s { AP *ap; GtkWidget *window; GtkWidget *ssid; GtkWidget *psk; }; void ap_dialog_launch(AP *ap); void ap_dialog_submit(APDialog *data); void ap_dialog_cancel(APDialog *data); void ap_button_clicked(AP *data); void ap_set(AP *data); AP* ap_add(Window *window, GDBusObject *object, GDBusProxy *proxy); void ap_remove(Window *window, AP *ap); void bind_device_ap(Device *device, AP *ap); void unbind_device_ap(Device *device, AP *ap); #endif iwgtk-0.9/src/device.c000066400000000000000000000213301435506763100147120ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" void device_show(GtkToggleButton *button, Device *device) { if (gtk_toggle_button_get_active(button)) { gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(global.window->main), device->master); } } void device_set(Device *device) { { GVariant *mac_var; const gchar *mac; mac_var = g_dbus_proxy_get_cached_property(device->proxy, "Address"); mac = g_variant_get_string(mac_var, NULL); gtk_label_set_text(GTK_LABEL(device->mac_label), mac); g_variant_unref(mac_var); } { GVariant *mode_var; const gchar *mode; mode_var = g_dbus_proxy_get_cached_property(device->proxy, "Mode"); mode = g_variant_get_string(mode_var, NULL); gtk_combo_box_set_active_id(GTK_COMBO_BOX(device->mode_box), mode); g_variant_unref(mode_var); } } void mode_box_changed(GtkComboBox *box, Device *device) { const gchar *mode; mode = gtk_combo_box_get_active_id(box); set_remote_property(device->proxy, "Mode", g_variant_new_string(mode), (SetFunction) device_set, device); } GtkWidget* mode_box_new(GDBusProxy *adapter_proxy) { GtkWidget *box; GtkListStore *list_store; GtkTreeIter list_store_iter; GtkCellRenderer *cell_renderer; GVariant *supported_modes_var; GVariantIter supported_modes_iter; const gchar *supported_mode; list_store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); supported_modes_var = g_dbus_proxy_get_cached_property(adapter_proxy, "SupportedModes"); g_variant_iter_init(&supported_modes_iter, supported_modes_var); while (g_variant_iter_next(&supported_modes_iter, "&s", &supported_mode)) { const gchar *supported_mode_display; if (strcmp(supported_mode, "station") == 0) { supported_mode_display = _("Station"); } else if (strcmp(supported_mode, "ap") == 0) { supported_mode_display = _("AP"); } else if (strcmp(supported_mode, "ad-hoc") == 0) { supported_mode_display = _("Ad-hoc"); } else { supported_mode_display = supported_mode; } gtk_list_store_append(list_store, &list_store_iter); gtk_list_store_set(list_store, &list_store_iter, 0, supported_mode, 1, supported_mode_display, -1); } g_variant_unref(supported_modes_var); box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(list_store)); g_object_unref(list_store); cell_renderer = gtk_cell_renderer_text_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(box), cell_renderer, TRUE); gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(box), cell_renderer, "text", 1); gtk_combo_box_set_id_column(GTK_COMBO_BOX(box), 0); return box; } Device* device_add(Window *window, GDBusObject *object, GDBusProxy *proxy) { Device *device; device = g_malloc(sizeof(Device)); device->proxy = proxy; { GVariant *name_var; const gchar *name; name_var = g_dbus_proxy_get_cached_property(proxy, "Name"); name = g_variant_get_string(name_var, NULL); device->button = gtk_toggle_button_new_with_label(name); g_object_ref_sink(device->button); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(device->button), FALSE); gtk_toggle_button_set_group(GTK_TOGGLE_BUTTON(device->button), GTK_TOGGLE_BUTTON(window->known_network_button)); gtk_widget_set_hexpand(device->button, TRUE); g_signal_connect(device->button, "toggled", G_CALLBACK(device_show), device); g_variant_unref(name_var); } { GVariant *adapter_path_var; const gchar *adapter_path; GDBusObject *adapter_object; GDBusProxy *adapter_proxy; adapter_path_var = g_dbus_proxy_get_cached_property(proxy, "Adapter"); adapter_path = g_variant_get_string(adapter_path_var, NULL); adapter_object = g_dbus_object_manager_get_object(global.manager, adapter_path); g_variant_unref(adapter_path_var); couple_register(window, ADAPTER_DEVICE, 1, device, adapter_object); adapter_proxy = G_DBUS_PROXY(g_dbus_object_get_interface(adapter_object, IWD_IFACE_ADAPTER)); device->mode_box = mode_box_new(adapter_proxy); g_object_unref(adapter_proxy); g_object_ref_sink(device->mode_box); } device->master = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); g_object_ref_sink(device->master); device->table = gtk_grid_new(); g_object_ref_sink(device->table); gtk_box_append(GTK_BOX(device->master), device->table); gtk_box_append(GTK_BOX(device->master), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)); gtk_widget_set_size_request(device->table, 400, -1); gtk_widget_set_halign(device->table, GTK_ALIGN_CENTER); device->mac_label = gtk_label_new(NULL); g_object_ref_sink(device->mac_label); gtk_widget_set_tooltip_text(GTK_WIDGET(device->mac_label), _("MAC address")); { GtkWidget *up_label, *mode_label, *power_switch; power_switch = switch_new(proxy, "Powered"); gtk_widget_set_tooltip_text(GTK_WIDGET(power_switch), _("Enable/disable wireless interface")); up_label = gtk_label_new(_("Enabled: ")); mode_label = gtk_label_new(_("Mode: ")); gtk_grid_attach(GTK_GRID(device->table), device->mac_label, 0, 0, 1, 1); gtk_grid_attach(GTK_GRID(device->table), up_label, 1, 0, 1, 1); gtk_grid_attach(GTK_GRID(device->table), power_switch, 2, 0, 1, 1); gtk_grid_attach(GTK_GRID(device->table), mode_label, 1, 1, 1, 1); gtk_grid_attach(GTK_GRID(device->table), device->mode_box, 2, 1, 1, 1); gtk_widget_set_margin_start(up_label, 10); gtk_widget_set_margin_start(mode_label, 10); gtk_widget_set_margin_end(power_switch, 10); gtk_widget_set_margin_end(device->mode_box, 10); gtk_widget_set_halign(device->mac_label, GTK_ALIGN_CENTER); gtk_widget_set_size_request(device->mac_label, -1, 34); gtk_widget_set_halign(up_label, GTK_ALIGN_END); gtk_widget_set_halign(mode_label, GTK_ALIGN_END); gtk_widget_set_halign(power_switch, GTK_ALIGN_START); gtk_widget_set_halign(device->mode_box, GTK_ALIGN_START); gtk_widget_set_valign(device->mac_label, GTK_ALIGN_CENTER); gtk_widget_set_valign(up_label, GTK_ALIGN_CENTER); gtk_widget_set_valign(mode_label, GTK_ALIGN_CENTER); gtk_widget_set_valign(power_switch, GTK_ALIGN_CENTER); gtk_widget_set_valign(device->mode_box, GTK_ALIGN_CENTER); gtk_grid_set_row_spacing(GTK_GRID(device->table), 3); gtk_grid_set_column_spacing(GTK_GRID(device->table), 3); } device->handler_update = g_signal_connect_swapped(proxy, "g-properties-changed", G_CALLBACK(device_set), device); device_set(device); g_signal_connect(device->mode_box, "changed", G_CALLBACK(mode_box_changed), device); couple_register(window, DEVICE_STATION, 0, device, object); couple_register(window, DEVICE_AP, 0, device, object); couple_register(window, DEVICE_ADHOC, 0, device, object); couple_register(window, DEVICE_DIAGNOSTIC, 0, device, object); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(device->button), TRUE); return device; } void device_remove(Window *window, Device *device) { if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(device->button))) { ObjectList *device_list; GtkWidget *button_alt; button_alt = NULL; device_list = window->objects[OBJECT_DEVICE]; /* * TODO: Select the next-in-line device more intelligently. * e.g., prefer to switch to a device associated with the same adapter, if * possible. */ while (device_list != NULL) { Device *device_alt; device_alt = (Device *) device_list->data; if (device_alt != device) { button_alt = device_alt->button; break; } device_list = device_list->next; } if (!button_alt) { button_alt = window->known_network_button; } gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_alt), TRUE); } couple_unregister(window, DEVICE_STATION, 0, device); couple_unregister(window, DEVICE_AP, 0, device); couple_unregister(window, DEVICE_ADHOC, 0, device); couple_unregister(window, DEVICE_DIAGNOSTIC, 0, device); couple_unregister(window, ADAPTER_DEVICE, 1, device); g_object_unref(device->button); g_object_unref(device->mode_box); g_object_unref(device->master); g_object_unref(device->table); g_object_unref(device->mac_label); g_signal_handler_disconnect(device->proxy, device->handler_update); g_free(device); } iwgtk-0.9/src/device.h000066400000000000000000000026461435506763100147300ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_DEVICE_H #define _IWGTK_DEVICE_H typedef struct Device_s Device; typedef struct Window_s Window; struct Device_s { GDBusProxy *proxy; // Button for the tab switcher GtkWidget *button; // Widgets GtkWidget *master; GtkWidget *table; GtkWidget *mac_label; GtkWidget *mode_box; // Handlers gulong handler_update; }; void device_show(GtkToggleButton *button, Device *device); void device_set(Device *device); void mode_box_changed(GtkComboBox *box, Device *device); GtkWidget* mode_box_new(GDBusProxy *adapter_proxy); Device* device_add(Window *window, GDBusObject *object, GDBusProxy *proxy); void device_remove(Window *window, Device *device); #endif iwgtk-0.9/src/diagnostic.c000066400000000000000000000117711435506763100156070ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" void diagnostic_launch(StationDiagnostic *diagnostic) { g_dbus_proxy_call( diagnostic->proxy, "GetDiagnostics", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) diagnostic_results_cb, diagnostic); } void diagnostic_results_cb(GDBusProxy *proxy, GAsyncResult *res, StationDiagnostic *diagnostic) { GVariant *diagnostics_data; GError *err; err = NULL; diagnostics_data = g_dbus_proxy_call_finish(proxy, res, &err); if (diagnostics_data) { GtkWidget *table; GtkWidget *property; GtkWidget *value; table = gtk_grid_new(); gtk_grid_set_column_spacing(GTK_GRID(table), 10); gtk_widget_set_margin_start(table, 5); gtk_widget_set_margin_end(table, 5); gtk_widget_set_margin_top(table, 5); gtk_widget_set_margin_bottom(table, 5); property = new_label_bold(_("Property")); value = new_label_bold(_("Value")); diagnostic_table_insert(table, property, value, 0); { GVariantIter *iter; const gchar *property_str; GVariant *value_var; int i = 1; g_variant_get(diagnostics_data, "(a{sv})", &iter); while (g_variant_iter_next(iter, "{&sv}", &property_str, &value_var)) { property = gtk_label_new(property_str); if (g_variant_is_of_type(value_var, G_VARIANT_TYPE_STRING)) { value = gtk_label_new(g_variant_get_string(value_var, NULL)); } else { gchar *value_str; value_str = g_variant_print(value_var, FALSE); value = gtk_label_new(value_str); g_free(value_str); } diagnostic_table_insert(table, property, value, i ++); g_variant_unref(value_var); } g_variant_iter_free(iter); } g_variant_unref(diagnostics_data); diagnostic_window_show(diagnostic, table); } else { g_printerr("Failed to retrieve station diagnostics: %s\n", err->message); g_error_free(err); } } void diagnostic_table_insert(GtkWidget *table, GtkWidget *property, GtkWidget *value, int row) { gtk_widget_set_halign(property, GTK_ALIGN_END); gtk_widget_set_halign(value, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID(table), property, 0, row, 1, 1); gtk_grid_attach(GTK_GRID(table), value, 1, row, 1, 1); } void diagnostic_window_show(StationDiagnostic *diagnostic, GtkWidget *table) { GtkWidget *window; window = gtk_window_new(); { GVariant *device_name_var; const gchar *device_name; gchar *window_title; device_name_var = g_dbus_proxy_get_cached_property(diagnostic->device_proxy, "Name"); device_name = g_variant_get_string(device_name_var, NULL); window_title = g_strdup_printf(_("%s: Station diagnostics"), device_name); g_variant_unref(device_name_var); gtk_window_set_title(GTK_WINDOW(window), window_title); g_free(window_title); } { GtkEventController *controller; controller = gtk_event_controller_key_new(); g_signal_connect(controller, "key-pressed", G_CALLBACK(diagnostic_key_press), window); gtk_widget_add_controller(window, controller); } gtk_window_set_child(GTK_WINDOW(window), table); gtk_widget_show(window); } gboolean diagnostic_key_press(GtkEventControllerKey *controller, guint keyval, guint keycode, GdkModifierType state, GtkWindow *window) { if (keyval == GDK_KEY_Escape) { gtk_window_destroy(window); return TRUE; } return FALSE; } StationDiagnostic* diagnostic_add(Window *window, GDBusObject *object, GDBusProxy *proxy) { StationDiagnostic *diagnostic; diagnostic = g_malloc(sizeof(StationDiagnostic)); diagnostic->proxy = proxy; diagnostic->button = gtk_button_new_with_label(_("Diagnostics")); g_object_ref_sink(diagnostic->button); g_signal_connect_swapped(diagnostic->button, "clicked", G_CALLBACK(diagnostic_launch), diagnostic); couple_register(window, DEVICE_DIAGNOSTIC, 1, diagnostic, object); return diagnostic; } void diagnostic_remove(Window *window, StationDiagnostic *diagnostic) { couple_unregister(window, DEVICE_DIAGNOSTIC, 1, diagnostic); g_object_unref(diagnostic->button); g_free(diagnostic); } void bind_device_diagnostic(Device *device, StationDiagnostic *diagnostic) { diagnostic->device_proxy = device->proxy; gtk_grid_attach(GTK_GRID(device->table), diagnostic->button, 0, 1, 1, 1); } void unbind_device_diagnostic(Device *device, StationDiagnostic *diagnostic) { gtk_grid_remove(GTK_GRID(device->table), diagnostic->button); } iwgtk-0.9/src/diagnostic.h000066400000000000000000000033201435506763100156030ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_DIAGNOSTIC_H #define _IWGTK_DIAGNOSTIC_H typedef struct StationDiagnostic_s StationDiagnostic; struct StationDiagnostic_s { GDBusProxy *proxy; GDBusProxy *device_proxy; GtkWidget *button; }; void diagnostic_launch(StationDiagnostic *diagnostic); void diagnostic_results_cb(GDBusProxy *proxy, GAsyncResult *res, StationDiagnostic *diagnostic); void diagnostic_table_insert(GtkWidget *table, GtkWidget *property, GtkWidget *value, int row); void diagnostic_window_show(StationDiagnostic *diagnostic, GtkWidget *table); gboolean diagnostic_key_press(GtkEventControllerKey *controller, guint keyval, guint keycode, GdkModifierType state, GtkWindow *window); StationDiagnostic* diagnostic_add(Window *window, GDBusObject *object, GDBusProxy *proxy); void diagnostic_remove(Window *window, StationDiagnostic *diagnostic); void bind_device_diagnostic(Device *device, StationDiagnostic *diagnostic); void unbind_device_diagnostic(Device *device, StationDiagnostic *diagnostic); #endif iwgtk-0.9/src/dialog.c000066400000000000000000000044021435506763100147130ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" gboolean dialog_key_press(GtkEventControllerKey *controller, guint keyval, guint keycode, GdkModifierType state, Dialog *dialog) { switch (keyval) { case GDK_KEY_Return: dialog->submit_callback(dialog->user_data); return TRUE; case GDK_KEY_Escape: gtk_window_destroy(GTK_WINDOW(dialog->window)); return TRUE; } return FALSE; } GtkWidget* dialog_buttons(gpointer user_data, SubmitCallback submit_callback, GtkWidget *window) { GtkWidget *row, *submit_button, *cancel_button; Dialog *dialog; row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); submit_button = gtk_button_new_with_label(_("Submit")); gtk_box_append(GTK_BOX(row), submit_button); cancel_button = gtk_button_new_with_label(_("Cancel")); gtk_box_append(GTK_BOX(row), cancel_button); g_signal_connect_swapped(submit_button, "clicked", G_CALLBACK(submit_callback), user_data); g_signal_connect_swapped(cancel_button, "clicked", G_CALLBACK(gtk_window_destroy), window); dialog = g_malloc(sizeof(Dialog)); g_signal_connect_swapped(window, "destroy", G_CALLBACK(g_free), dialog); dialog->window = window; dialog->submit_callback = submit_callback; dialog->user_data = user_data; { GtkEventController *controller; controller = gtk_event_controller_key_new(); gtk_event_controller_set_propagation_phase(controller, GTK_PHASE_CAPTURE); g_signal_connect(controller, "key-pressed", G_CALLBACK(dialog_key_press), dialog); gtk_widget_add_controller(window, controller); } return row; } iwgtk-0.9/src/dialog.h000066400000000000000000000022671435506763100147270ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_DIALOG_H #define _IWGTK_DIALOG_H typedef struct Dialog_s Dialog; typedef void (*SubmitCallback) (gpointer data); struct Dialog_s { GtkWidget *window; SubmitCallback submit_callback; gpointer user_data; }; gboolean dialog_key_press(GtkEventControllerKey *controller, guint keyval, guint keycode, GdkModifierType state, Dialog *dialog); GtkWidget* dialog_buttons(gpointer user_data, SubmitCallback submit_callback, GtkWidget *window); #endif iwgtk-0.9/src/dpp.c000066400000000000000000000153231435506763100142430ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" #include #define QR_CODE_MARGIN 4 void qrcode_draw(GtkDrawingArea *area, cairo_t *cr, int width, int height, cairo_surface_t *qr_surface) { cairo_pattern_t *qr_pattern; { float sx, sy; sx = (float) cairo_image_surface_get_width(qr_surface) / (float) width; sy = (float) cairo_image_surface_get_height(qr_surface) / (float) height; cairo_surface_set_device_scale(qr_surface, sx, sy); } qr_pattern = cairo_pattern_create_for_surface(qr_surface); cairo_pattern_set_filter(qr_pattern, CAIRO_FILTER_NEAREST); cairo_set_source(cr, qr_pattern); cairo_pattern_destroy(qr_pattern); cairo_paint(cr); } GtkWidget* qrcode_widget_new(const gchar *uri) { QRcode *qrcode; cairo_surface_t *qr_surface; uint32_t *qr_data; gsize width_data, stride, stride_n; int width_surface; qrcode = QRcode_encodeString8bit(uri, 0, QR_ECLEVEL_L); if (!qrcode) { g_printerr("Failed to generate QR code\n"); return NULL; } width_data = qrcode->width; width_surface = width_data + 2*QR_CODE_MARGIN; qr_surface = cairo_image_surface_create(CAIRO_FORMAT_A1, width_surface, width_surface); stride = cairo_image_surface_get_stride(qr_surface); g_assert(stride % 4 == 0); stride_n = stride / 4; qr_data = (uint32_t *) cairo_image_surface_get_data(qr_surface); cairo_surface_flush(qr_surface); for (int i = 0; i < width_data; i ++) { for (int j = 0; j < width_data; j ++) { if (qrcode->data[i*width_data + j] & 0x01) { int index, bit, is, js; is = i + QR_CODE_MARGIN; js = j + QR_CODE_MARGIN; index = is*stride_n + js/32; bit = js % 32; qr_data[index] |= (0x00000001 << bit); } } } cairo_surface_mark_dirty(qr_surface); QRcode_free(qrcode); { GtkWidget *area; area = gtk_drawing_area_new(); gtk_drawing_area_set_content_width(GTK_DRAWING_AREA(area), PROVISION_MENU_WIDTH); gtk_drawing_area_set_content_height(GTK_DRAWING_AREA(area), PROVISION_MENU_WIDTH); { GtkStyleContext *context; GtkCssProvider *provider; context = gtk_widget_get_style_context(area); provider = gtk_css_provider_new(); gtk_css_provider_load_from_data(provider, "* {background: white;}", -1); gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_USER + 1); g_object_unref(provider); } gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), (GtkDrawingAreaDrawFunc) qrcode_draw, qr_surface, (GDestroyNotify) cairo_surface_destroy); return area; } } void dpp_qrcode_add(DPP *dpp) { { GVariant *uri_var; const gchar *uri; uri_var = g_dbus_proxy_get_cached_property(dpp->proxy, "URI"); uri = g_variant_get_string(uri_var, NULL); dpp->qrcode = qrcode_widget_new(uri); g_variant_unref(uri_var); } gtk_widget_set_hexpand(dpp->qrcode, FALSE); gtk_widget_set_halign(dpp->qrcode, GTK_ALIGN_CENTER); { GVariant *role_var; const gchar *role, *tooltip; role_var = g_dbus_proxy_get_cached_property(dpp->proxy, "Role"); role = g_variant_get_string(role_var, NULL); if (strcmp(role, "enrollee") == 0) { tooltip = _("Get network credentials"); } else { tooltip = _("Share network credentials"); } g_variant_unref(role_var); gtk_widget_set_tooltip_text(dpp->qrcode, tooltip); } gtk_box_insert_child_after(GTK_BOX(dpp->station->provision_vbox), dpp->qrcode, dpp->button); } void dpp_button_press(DPP *dpp) { g_dbus_proxy_call( dpp->proxy, dpp->button_method, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_log, "Failed to start or stop DPP: %s\n"); } void dpp_set(DPP *dpp) { gboolean started; { GVariant *started_var; started_var = g_dbus_proxy_get_cached_property(dpp->proxy, "Started"); started = g_variant_get_boolean(started_var); g_variant_unref(started_var); } if (started) { if (!dpp->qrcode) { dpp_qrcode_add(dpp); } gtk_button_set_label(GTK_BUTTON(dpp->button), _("Stop")); dpp->button_method = "Stop"; } else { if (dpp->qrcode) { gtk_box_remove(GTK_BOX(dpp->station->provision_vbox), dpp->qrcode); dpp->qrcode = NULL; } if (dpp->station->state == STATION_CONNECTED) { gtk_button_set_label(GTK_BUTTON(dpp->button), _("Share credentials")); dpp->button_method = "StartConfigurator"; } else { gtk_button_set_label(GTK_BUTTON(dpp->button), _("Get credentials")); dpp->button_method = "StartEnrollee"; } } } DPP* dpp_add(Window *window, GDBusObject *object, GDBusProxy *proxy) { DPP *dpp; dpp = g_malloc(sizeof(DPP)); dpp->proxy = proxy; dpp->qrcode = NULL; dpp->label = new_label_bold(_("Easy Connect")); g_object_ref_sink(dpp->label); gtk_widget_set_margin_top(dpp->label, 10); dpp->button = gtk_button_new(); g_object_ref_sink(dpp->button); g_signal_connect_swapped(dpp->button, "clicked", G_CALLBACK(dpp_button_press), dpp); couple_register(window, STATION_DPP, 1, dpp, object); return dpp; } void dpp_remove(Window *window, DPP *dpp) { couple_unregister(window, STATION_DPP, 1, dpp); g_object_unref(dpp->label); g_object_unref(dpp->button); g_free(dpp); } void bind_station_dpp(Station *station, DPP *dpp) { station->dpp = dpp; dpp->station = station; { GtkWidget *hidden; hidden = gtk_widget_get_first_child(station->provision_vbox); gtk_box_insert_child_after(GTK_BOX(station->provision_vbox), dpp->button, hidden); gtk_box_insert_child_after(GTK_BOX(station->provision_vbox), dpp->label, hidden); } dpp->handler_update = g_signal_connect_swapped(dpp->proxy, "g-properties-changed", G_CALLBACK(dpp_set), dpp); dpp_set(dpp); } void unbind_station_dpp(Station *station, DPP *dpp) { station->dpp = NULL; g_signal_handler_disconnect(dpp->proxy, dpp->handler_update); if (dpp->qrcode) { gtk_box_remove(GTK_BOX(station->provision_vbox), dpp->qrcode); dpp->qrcode = NULL; } gtk_box_remove(GTK_BOX(station->provision_vbox), dpp->label); gtk_box_remove(GTK_BOX(station->provision_vbox), dpp->button); } iwgtk-0.9/src/dpp.h000066400000000000000000000026711435506763100142520ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_DPP_H #define _IWGTK_DPP_H typedef struct DPP_s DPP; struct DPP_s { GDBusProxy *proxy; Station *station; const gchar *button_method; gulong handler_update; GtkWidget *label; GtkWidget *button; GtkWidget *qrcode; }; void qrcode_draw(GtkDrawingArea *area, cairo_t *cr, int width, int height, cairo_surface_t *qr_surface); GtkWidget* qrcode_widget_new(const gchar *uri); void dpp_qrcode_add(DPP *dpp); void dpp_button_press(DPP *dpp); void dpp_set(DPP *dpp); DPP* dpp_add(Window *window, GDBusObject *object, GDBusProxy *proxy); void dpp_remove(Window *window, DPP *dpp); void bind_station_dpp(Station *station, DPP *dpp); void unbind_station_dpp(Station *station, DPP *dpp); #endif iwgtk-0.9/src/hidden.c000066400000000000000000000071441435506763100147150ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" static const CallbackMessages connect_hidden_messages = { N_("Hidden network has been found"), N_("Failed to find hidden network"), NULL, FALSE }; void hidden_ssid_dialog(Station *station) { HiddenNetworkDialog *dialog; GtkWidget *table, *buttons; dialog = g_malloc(sizeof(HiddenNetworkDialog)); dialog->station = station; dialog->window = gtk_window_new(); gtk_window_set_title(GTK_WINDOW(dialog->window), _("Connect to hidden network")); dialog->ssid = gtk_entry_new(); table = gtk_grid_new(); gtk_window_set_child(GTK_WINDOW(dialog->window), table); buttons = dialog_buttons(dialog, (SubmitCallback) hidden_ssid_submit, dialog->window); gtk_grid_attach(GTK_GRID(table), gtk_label_new(_("SSID: ")), 0, 0, 1, 1); gtk_grid_attach(GTK_GRID(table), dialog->ssid, 1, 0, 1, 1); gtk_grid_attach(GTK_GRID(table), buttons, 1, 1, 1, 1); grid_column_set_alignment(table, 0, GTK_ALIGN_END); grid_column_set_alignment(table, 1, GTK_ALIGN_START); g_signal_connect_swapped(dialog->window, "destroy", G_CALLBACK(g_free), dialog); gtk_widget_show(dialog->window); } void hidden_ssid_submit(HiddenNetworkDialog *dialog) { const gchar *ssid; ssid = gtk_editable_get_text(GTK_EDITABLE(dialog->ssid)); if (*ssid == '\0') { return; } g_dbus_proxy_call( dialog->station->proxy, "ConnectHiddenNetwork", g_variant_new("(s)", ssid), G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_notify, (gpointer) &connect_hidden_messages); gtk_window_destroy(GTK_WINDOW(dialog->window)); } void station_add_hidden_network(Station *station, const gchar *address, const gchar *type, gint16 signal_strength, int index) { GtkWidget *status_icon; GtkWidget *address_label; GtkWidget *security_label; status_icon = gtk_picture_new(); gtk_widget_set_tooltip_text(status_icon, _("Hidden network")); { const gchar *icon_name; icon_name = station_icons[get_signal_level(signal_strength)]; symbolic_icon_set_image(icon_name, &colors.network_hidden, status_icon); } address_label = gtk_label_new(address); security_label = gtk_label_new(get_security_type(type)); gtk_widget_set_tooltip_text(address_label, _("MAC address")); gtk_widget_set_tooltip_text(security_label, _("Network security")); gtk_grid_attach(GTK_GRID(station->network_table), status_icon, 0, index, 1, 1); gtk_grid_attach(GTK_GRID(station->network_table), address_label, 1, index, 1, 1); gtk_grid_attach(GTK_GRID(station->network_table), security_label, 2, index, 1, 1); gtk_widget_set_halign(status_icon, GTK_ALIGN_START); gtk_widget_set_halign(address_label, GTK_ALIGN_START); gtk_widget_set_halign(security_label, GTK_ALIGN_START); gtk_widget_set_size_request(status_icon, 32, 32); gtk_widget_set_hexpand(address_label, TRUE); } iwgtk-0.9/src/hidden.h000066400000000000000000000022731435506763100147200ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_HIDDEN_H #define _IWGTK_HIDDEN_H typedef struct HiddenNetworkDialog_s HiddenNetworkDialog; typedef struct Station_s Station; struct HiddenNetworkDialog_s { Station *station; GtkWidget *window; GtkWidget *ssid; }; void hidden_ssid_dialog(Station *station); void hidden_ssid_submit(HiddenNetworkDialog *dialog); void station_add_hidden_network(Station *station, const gchar *address, const gchar *type, gint16 signal_strength, int index); #endif iwgtk-0.9/src/icon.c000066400000000000000000000106351435506763100144110ustar00rootroot00000000000000/* * Copyright 2022-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" #define FLOAT_00 0.0 #define FLOAT_64 (float) 0x64 / (float) 0xff #define FLOAT_80 (float) 0x80 / (float) 0xff #define FLOAT_ff 1.0 #define COLOR_LIME {FLOAT_00, FLOAT_ff, FLOAT_00, 1.0} #define COLOR_YELLOW {FLOAT_ff, FLOAT_ff, FLOAT_00, 1.0} #define COLOR_DARK_GREEN {FLOAT_00, FLOAT_64, FLOAT_00, 1.0} #define COLOR_GRAY {FLOAT_80, FLOAT_80, FLOAT_80, 1.0} ColorTable colors = { COLOR_LIME, // station_connected COLOR_YELLOW, // station_connecting COLOR_GRAY, // station_disconnected COLOR_LIME, // ap_up COLOR_GRAY, // ap_down COLOR_LIME, // adhoc_up COLOR_GRAY, // adhoc_down COLOR_GRAY, // disabled_device COLOR_GRAY, // disabled_adapter COLOR_LIME, // network_connected COLOR_YELLOW, // network_connecting COLOR_DARK_GREEN, // network_known COLOR_GRAY, // network_unknown COLOR_GRAY // network_hidden }; /* * Signal thresholds are in dBm. */ const gint16 signal_thresholds[] = {-60, -67, -74, -81}; const gchar* station_icons[] = { ICON_STATION_0, ICON_STATION_1, ICON_STATION_2, ICON_STATION_3, ICON_STATION_4 }; const GdkRGBA *color_status[] = { &colors.network_connected, &colors.network_connecting, &colors.network_known, &colors.network_unknown }; void icon_theme_set() { GdkDisplay *display; const gchar* icons[] = { ICON_STATION_0, ICON_STATION_1, ICON_STATION_2, ICON_STATION_3, ICON_STATION_4, ICON_STATION_OFFLINE, ICON_AP, ICON_ADHOC, ICON_DEVICE_DISABLED, ICON_ADAPTER_DISABLED, NULL }; display = gdk_display_get_default(); global.theme = gtk_icon_theme_get_for_display(display); for (int i = 0; icons[i] != NULL; i ++) { if (!gtk_icon_theme_has_icon(global.theme, icons[i])) { g_printerr("Icon theme '%s' is missing icon '%s': Overriding theme to Adwaita\n", gtk_icon_theme_get_theme_name(global.theme), icons[i]); global.theme = gtk_icon_theme_new(); gtk_icon_theme_set_theme_name(global.theme, "Adwaita"); break; } } } gint8 get_signal_level(gint16 signal_strength) { gint8 i; for (i = 0; i < N_SIGNAL_THRESHOLDS; i ++) { if (signal_strength > 100*signal_thresholds[i]) { return i; } } return N_SIGNAL_THRESHOLDS; } GtkSnapshot* symbolic_icon_get_snapshot(const gchar *icon_name, const GdkRGBA *icon_color) { GtkIconPaintable *icon; GtkSnapshot *snapshot; icon = gtk_icon_theme_lookup_icon( global.theme, icon_name, NULL, 32, 1, GTK_TEXT_DIR_NONE, GTK_ICON_LOOKUP_FORCE_SYMBOLIC ); snapshot = gtk_snapshot_new(); gtk_symbolic_paintable_snapshot_symbolic( GTK_SYMBOLIC_PAINTABLE(icon), GDK_SNAPSHOT(snapshot), 32.0, 32.0, icon_color, 1 ); g_object_unref(icon); return snapshot; } void symbolic_icon_set_image(const gchar *icon_name, const GdkRGBA *icon_color, GtkWidget *image) { GtkSnapshot *snapshot; GdkPaintable *paintable; snapshot = symbolic_icon_get_snapshot(icon_name, icon_color); paintable = gtk_snapshot_free_to_paintable(snapshot, NULL); gtk_picture_set_paintable(GTK_PICTURE(image), paintable); g_object_unref(paintable); } cairo_surface_t* symbolic_icon_get_surface(const gchar *icon_name, const GdkRGBA *icon_color) { GtkSnapshot *snapshot; cairo_surface_t *surface; snapshot = symbolic_icon_get_snapshot(icon_name, icon_color); surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 32, 32); { cairo_t *cr; GskRenderNode *node; cr = cairo_create(surface); node = gtk_snapshot_free_to_node(snapshot); gsk_render_node_draw(node, cr); gsk_render_node_unref(node); cairo_destroy(cr); } return surface; } iwgtk-0.9/src/icon.h000066400000000000000000000047371435506763100144240ustar00rootroot00000000000000/* * Copyright 2022-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_ICON_H #define _IWGTK_ICON_H typedef struct ColorTable_s ColorTable; #define ICON_STATION_0 "network-wireless-signal-excellent-symbolic" #define ICON_STATION_1 "network-wireless-signal-good-symbolic" #define ICON_STATION_2 "network-wireless-signal-ok-symbolic" #define ICON_STATION_3 "network-wireless-signal-weak-symbolic" #define ICON_STATION_4 "network-wireless-signal-none-symbolic" /* * TODO: Use a different icon for ad-hoc mode. */ #define ICON_STATION_OFFLINE "network-wireless-offline-symbolic" #define ICON_AP "network-wireless-hotspot-symbolic" #define ICON_ADHOC "network-wireless-hotspot-symbolic" #define ICON_DEVICE_DISABLED "network-wireless-disabled-symbolic" #define ICON_ADAPTER_DISABLED "network-wireless-hardware-disabled-symbolic" #define N_SIGNAL_THRESHOLDS 4 struct ColorTable_s { GdkRGBA station_connected; GdkRGBA station_connecting; GdkRGBA station_disconnected; GdkRGBA ap_up; GdkRGBA ap_down; GdkRGBA adhoc_up; GdkRGBA adhoc_down; GdkRGBA disabled_device; GdkRGBA disabled_adapter; GdkRGBA network_connected; GdkRGBA network_connecting; GdkRGBA network_known; GdkRGBA network_unknown; GdkRGBA network_hidden; }; extern ColorTable colors; extern const gint16 signal_thresholds[]; extern const gchar* station_icons[]; extern const GdkRGBA *color_status[]; void icon_theme_set(); gint8 get_signal_level(gint16 signal_strength); GtkSnapshot* symbolic_icon_get_snapshot(const gchar *icon_name, const GdkRGBA *icon_color); void symbolic_icon_set_image(const gchar *icon_name, const GdkRGBA *icon_color, GtkWidget *image); cairo_surface_t* symbolic_icon_get_surface(const gchar *icon_name, const GdkRGBA *icon_color); #endif iwgtk-0.9/src/indicator.c000066400000000000000000000274451435506763100154440ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" GDBusArgInfo arg_device = {-1, "device", "o", NULL}; GDBusArgInfo arg_level = {-1, "level", "y", NULL}; GDBusInterfaceInfo signal_agent_interface_info = { -1, IWD_IFACE_SIGNAL_LEVEL_AGENT, (GDBusMethodInfo *[]) { &(GDBusMethodInfo) { -1, "Release", (GDBusArgInfo *[]) {&arg_device, NULL}, NULL, NULL }, &(GDBusMethodInfo) { -1, "Changed", (GDBusArgInfo *[]) {&arg_device, &arg_level, NULL}, NULL, NULL }, NULL }, NULL, // Signal info NULL, // Property info NULL // Annotation info }; GDBusInterfaceVTable signal_agent_interface_vtable = { (GDBusInterfaceMethodCallFunc) signal_agent_method_call_handler, NULL, NULL }; Indicator* indicator_new(GDBusProxy *device_proxy) { Indicator *indicator; GDBusObject *device_object; GDBusProxy *adapter_proxy; indicator = g_malloc(sizeof(Indicator)); indicator->next = NULL; indicator->update_mode_handler = 0; indicator->signal_agent_id = 0; device_object = g_dbus_interface_get_object(G_DBUS_INTERFACE(device_proxy)); indicator->sni = sni_new(device_object); indicator->sni->context_menu_handler = (SNIActivateHandler) indicator_activate; indicator->sni->activate_handler = (SNIActivateHandler) indicator_activate; indicator->level = N_SIGNAL_THRESHOLDS + 1; sni_category_set(indicator->sni, "Hardware"); sni_id_set(indicator->sni, APPLICATION_ID); sni_status_set(indicator->sni, "Active"); { GVariant *adapter_path_var; const gchar *adapter_path; adapter_path_var = g_dbus_proxy_get_cached_property(device_proxy, "Adapter"); adapter_path = g_variant_get_string(adapter_path_var, NULL); adapter_proxy = G_DBUS_PROXY(g_dbus_object_manager_get_interface(global.manager, adapter_path, IWD_IFACE_ADAPTER)); g_variant_unref(adapter_path_var); } indicator->device_proxy = device_proxy; indicator->adapter_proxy = adapter_proxy; indicator->update_device_handler = g_signal_connect_swapped(device_proxy, "g-properties-changed", G_CALLBACK(indicator_set_device), indicator); indicator->update_adapter_handler = g_signal_connect_swapped(adapter_proxy, "g-properties-changed", G_CALLBACK(indicator_set_device), indicator); indicator_set_device(indicator); return indicator; } void indicator_rm(Indicator *indicator) { if (indicator->signal_agent_id != 0) { g_dbus_connection_unregister_object(g_dbus_proxy_get_connection(indicator->proxy), indicator->signal_agent_id); } sni_rm(indicator->sni); g_signal_handler_disconnect(indicator->device_proxy, indicator->update_device_handler); g_signal_handler_disconnect(indicator->adapter_proxy, indicator->update_adapter_handler); if (indicator->update_mode_handler != 0) { g_signal_handler_disconnect(indicator->proxy, indicator->update_mode_handler); } g_free(indicator); } void indicator_station_init_signal_agent(Indicator *indicator, GDBusProxy *station_proxy) { GError *err; const gchar *path; GVariant *levels; static const gchar *agent_error_msg = "Failed to register signal level agent: %s\n"; path = g_dbus_proxy_get_object_path(station_proxy); err = NULL; indicator->signal_agent_id = g_dbus_connection_register_object( g_dbus_proxy_get_connection(station_proxy), path, &signal_agent_interface_info, &signal_agent_interface_vtable, indicator, NULL, &err); if (err != NULL) { g_printerr(agent_error_msg, err->message); g_error_free(err); return; } levels = g_variant_new_fixed_array(G_VARIANT_TYPE_INT16, signal_thresholds, N_SIGNAL_THRESHOLDS, sizeof(gint16)); g_dbus_proxy_call( station_proxy, "RegisterSignalLevelAgent", g_variant_new("(o@an)", path, levels), G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_log, (gpointer) agent_error_msg); } void indicator_set_device(Indicator *indicator) { GVariant *powered_var; gboolean powered; powered_var = g_dbus_proxy_get_cached_property(indicator->adapter_proxy, "Powered"); powered = g_variant_get_boolean(powered_var); g_variant_unref(powered_var); if (!powered) { sni_title_set(indicator->sni, _("Wireless hardware is disabled")); sni_icon_pixmap_set(indicator->sni, symbolic_icon_get_surface(ICON_ADAPTER_DISABLED, &colors.disabled_adapter)); return; } powered_var = g_dbus_proxy_get_cached_property(indicator->device_proxy, "Powered"); powered = g_variant_get_boolean(powered_var); g_variant_unref(powered_var); if (!powered) { sni_title_set(indicator->sni, _("Wireless interface is disabled")); sni_icon_pixmap_set(indicator->sni, symbolic_icon_get_surface(ICON_DEVICE_DISABLED, &colors.disabled_device)); } } void indicator_set_station(Indicator *indicator) { GVariant *state_var; const gchar *state; state_var = g_dbus_proxy_get_cached_property(indicator->proxy, "State"); state = g_variant_get_string(state_var, NULL); if (!strcmp(state, "connected")) { indicator->status = INDICATOR_STATION_CONNECTED; indicator_set_station_connected_title(indicator, _("Connected to %s")); if (indicator->level <= N_SIGNAL_THRESHOLDS) { indicator_set_station_connected(indicator); } } else if (!strcmp(state, "connecting")) { indicator->status = INDICATOR_STATION_CONNECTING; indicator_set_station_connected_title(indicator, _("Connecting to %s")); if (indicator->level <= N_SIGNAL_THRESHOLDS) { indicator_set_station_connected(indicator); } } else { indicator->status = INDICATOR_STATION_DISCONNECTED; sni_title_set(indicator->sni, _("Not connected to any wireless network")); sni_icon_pixmap_set(indicator->sni, symbolic_icon_get_surface(ICON_STATION_OFFLINE, &colors.station_disconnected)); } g_variant_unref(state_var); } void indicator_set_station_connected(Indicator *indicator) { const GdkRGBA *color; if (indicator->status == INDICATOR_STATION_CONNECTED) { color = &colors.station_connected; } else if (indicator->status == INDICATOR_STATION_CONNECTING) { color = &colors.station_connecting; } else { g_printerr("Signal level update received, but the station is neither connected nor connecting\n"); return; } sni_icon_pixmap_set(indicator->sni, symbolic_icon_get_surface(station_icons[indicator->level], color)); } void indicator_set_station_connected_title(Indicator *indicator, const gchar *title_template) { GVariant *connected_network_var; connected_network_var = g_dbus_proxy_get_cached_property(indicator->proxy, "ConnectedNetwork"); if (connected_network_var) { const gchar *connected_network; GDBusProxy *network_proxy; connected_network = g_variant_get_string(connected_network_var, NULL); network_proxy = G_DBUS_PROXY(g_dbus_object_manager_get_interface(global.manager, connected_network, IWD_IFACE_NETWORK)); if (network_proxy) { sni_network_set_title(indicator->sni, network_proxy, title_template); g_object_unref(network_proxy); g_variant_unref(connected_network_var); } else { SNITitleDelayed *data; data = g_malloc(sizeof(SNITitleDelayed)); data->sni = indicator->sni; data->title_template = title_template; data->connected_network_var = connected_network_var; data->handler = g_signal_connect(global.manager, "object-added", G_CALLBACK(sni_set_title_delayed), data); } } else { g_printerr("ConnectedNetwork property was expected, but not found\n"); } } void sni_network_set_title(StatusNotifierItem *sni, GDBusProxy *network_proxy, const gchar *title_template) { GVariant *ssid_var; const gchar *ssid; gchar *title; ssid_var = g_dbus_proxy_get_cached_property(network_proxy, "Name"); ssid = g_variant_get_string(ssid_var, NULL); title = g_strdup_printf(title_template, ssid); sni_title_set(sni, title); g_free(title); g_variant_unref(ssid_var); } void sni_set_title_delayed(GDBusObjectManager *manager, GDBusObject *object, SNITitleDelayed *data) { const gchar *network_path; GDBusProxy *network_proxy; network_path = g_variant_get_string(data->connected_network_var, NULL); if (strcmp(g_dbus_object_get_object_path(object), network_path)) { return; } network_proxy = G_DBUS_PROXY(g_dbus_object_get_interface(object, IWD_IFACE_NETWORK)); if (network_proxy) { sni_network_set_title(data->sni, network_proxy, data->title_template); g_object_unref(network_proxy); } else { g_printerr("Failed to find interface of type " IWD_IFACE_NETWORK " on object %s\n", network_path); } g_signal_handler_disconnect(manager, data->handler); g_variant_unref(data->connected_network_var); g_free(data); } void indicator_set_ap(Indicator *indicator) { GVariant *started_var; started_var = g_dbus_proxy_get_cached_property(indicator->proxy, "Started"); if (g_variant_get_boolean(started_var)) { sni_title_set(indicator->sni, _("Access point is up")); sni_icon_pixmap_set(indicator->sni, symbolic_icon_get_surface(ICON_AP, &colors.ap_up)); } else { sni_title_set(indicator->sni, _("Access point is down")); sni_icon_pixmap_set(indicator->sni, symbolic_icon_get_surface(ICON_AP, &colors.ap_down)); } g_variant_unref(started_var); } void indicator_set_adhoc(Indicator *indicator) { GVariant *started_var; started_var = g_dbus_proxy_get_cached_property(indicator->proxy, "Started"); if (g_variant_get_boolean(started_var)) { sni_title_set(indicator->sni, _("Ad-hoc node is up")); sni_icon_pixmap_set(indicator->sni, symbolic_icon_get_surface(ICON_ADHOC, &colors.adhoc_up)); } else { sni_title_set(indicator->sni, _("Ad-hoc node is down")); sni_icon_pixmap_set(indicator->sni, symbolic_icon_get_surface(ICON_ADHOC, &colors.adhoc_down)); } g_variant_unref(started_var); } void indicator_activate(GDBusObject *device_object) { if (global.window != NULL) { gtk_window_destroy(GTK_WINDOW(global.window->window)); } else { ObjectList *list; window_launch(); list = global.window->objects[OBJECT_DEVICE]; while (list != NULL) { if (list->object == device_object) { Device *device; device = (Device *) list->data; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(device->button), TRUE); break; } list = list->next; } } } void signal_agent_method_call_handler(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, Indicator *indicator) { if (strcmp(method_name, "Changed") == 0) { g_variant_get(parameters, "(oy)", NULL, &indicator->level); g_dbus_method_invocation_return_value(invocation, NULL); if (indicator->level <= N_SIGNAL_THRESHOLDS) { indicator_set_station_connected(indicator); } else { g_printerr("Invalid signal level received\n"); } } else if (strcmp(method_name, "Release") == 0) { g_dbus_method_invocation_return_value(invocation, NULL); g_dbus_connection_unregister_object(connection, indicator->signal_agent_id); indicator->signal_agent_id = 0; } else { g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.UnknownMethod", "Unknown method"); } } iwgtk-0.9/src/indicator.h000066400000000000000000000051561435506763100154440ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_INDICATOR_H #define _IWGTK_INDICATOR_H typedef enum { INDICATOR_STATION_CONNECTED, INDICATOR_STATION_CONNECTING, INDICATOR_STATION_DISCONNECTED, } IndicatorStatus; typedef struct Indicator_s Indicator; typedef struct StatusNotifierItem_s StatusNotifierItem; typedef struct SNITitleDelayed_s SNITitleDelayed; struct Indicator_s { GDBusProxy *proxy; GDBusProxy *device_proxy; GDBusProxy *adapter_proxy; StatusNotifierItem *sni; gulong update_device_handler; gulong update_adapter_handler; gulong update_mode_handler; guint signal_agent_id; IndicatorStatus status; guint8 level; Indicator *next; }; struct SNITitleDelayed_s { StatusNotifierItem *sni; const gchar *title_template; GVariant *connected_network_var; gulong handler; }; typedef void (*IndicatorSetter) (Indicator *indicator); Indicator* indicator_new(GDBusProxy *device_proxy); void indicator_rm(Indicator *indicator); void indicator_station_init_signal_agent(Indicator *indicator, GDBusProxy *station_proxy); void indicator_set_device(Indicator *indicator); void indicator_set_station(Indicator *indicator); void indicator_set_station_connected(Indicator *indicator); void indicator_set_station_connected_title(Indicator *indicator, const gchar *title_template); void sni_network_set_title(StatusNotifierItem *sni, GDBusProxy *network_proxy, const gchar *title_template); void sni_set_title_delayed(GDBusObjectManager *manager, GDBusObject *object, SNITitleDelayed *data); void indicator_set_ap(Indicator *indicator); void indicator_set_adhoc(Indicator *indicator); void indicator_activate(GDBusObject *device_object); void signal_agent_method_call_handler(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, Indicator *indicator); #endif iwgtk-0.9/src/iwgtk.h000066400000000000000000000070611435506763100146120ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_H #define _IWGTK_H #include #include #include #define APPLICATION_ID "org.twosheds.iwgtk" #define IWD_BUS_NAME "net.connman.iwd" #define IWD_PATH_OBJECT_MANAGER "/" #define IWD_PATH_AGENT_MANAGER "/net/connman/iwd" #define IWD_PATH_PREFIX_LENGTH 17 #define IWGTK_PATH_AGENT "/Agent" #define IWD_IFACE_ADAPTER "net.connman.iwd.Adapter" #define IWD_IFACE_DEVICE "net.connman.iwd.Device" #define IWD_IFACE_STATION "net.connman.iwd.Station" #define IWD_IFACE_DIAGNOSTIC "net.connman.iwd.StationDiagnostic" #define IWD_IFACE_AP "net.connman.iwd.AccessPoint" #define IWD_IFACE_AD_HOC "net.connman.iwd.AdHoc" #define IWD_IFACE_DPP "net.connman.iwd.DeviceProvisioning" #define IWD_IFACE_WPS "net.connman.iwd.SimpleConfiguration" #define IWD_IFACE_NETWORK "net.connman.iwd.Network" #define IWD_IFACE_KNOWN_NETWORK "net.connman.iwd.KnownNetwork" #define IWD_IFACE_AGENT_MANAGER "net.connman.iwd.AgentManager" #define IWD_IFACE_AGENT "net.connman.iwd.Agent" #define IWD_IFACE_SIGNAL_LEVEL_AGENT "net.connman.iwd.SignalLevelAgent" typedef enum { ERROR_OTHER = 0, IWD_ERROR_BUSY, IWD_ERROR_FAILED, IWD_ERROR_INVALID_ARGUMENTS, IWD_ERROR_ALREADY_EXISTS, IWD_ERROR_NOT_CONNECTED, IWD_ERROR_NOT_CONFIGURED, IWD_ERROR_NOT_FOUND, IWD_ERROR_SERVICE_SET_OVERLAP, IWD_ERROR_ALREADY_PROVISIONED, IWD_ERROR_NOT_HIDDEN, IWD_ERROR_ABORTED, IWD_ERROR_NO_AGENT, IWD_ERROR_NOT_SUPPORTED, IWD_ERROR_TIMEOUT, IWD_ERROR_IN_PROGRESS, IWD_ERROR_WSC_SESSION_OVERLAP, IWD_ERROR_WSC_NO_CREDENTIALS, IWD_ERROR_WSC_TIME_EXPIRED, IWD_ERROR_WSC_WALK_TIME_EXPIRED, IWD_ERROR_WSC_NOT_REACHABLE, IWD_ERROR_INVALID_FORMAT, IWD_ERROR_NOT_AVAILABLE, IWD_ERROR_AGENT_CANCELED } IWDError; typedef struct ErrorMessage_s ErrorMessage; typedef struct CallbackMessages_s CallbackMessages; struct ErrorMessage_s { int code; const gchar *message; }; /* * If no message is desired on success/failure, set success_message and/or * failure_message to NULL. * * The last entry in error_table must have error code 0. * If no message is desired in this case, its message should be NULL. */ struct CallbackMessages_s { const gchar *success; const gchar *failure; const ErrorMessage *error_table; gboolean free; }; #include "sni.h" #include "indicator.h" #include "window.h" #include "main.h" #include "utilities.h" #include "icon.h" #include "dialog.h" #include "device.h" #include "adapter.h" #include "station.h" #include "ap.h" #include "adhoc.h" #include "dpp.h" #include "wps.h" #include "diagnostic.h" #include "known_network.h" #include "agent.h" #include "network.h" #include "hidden.h" #include "switch.h" #endif iwgtk-0.9/src/known_network.c000066400000000000000000000137441435506763100163720ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" static int n_known_networks = 0; void known_network_forget(GDBusProxy *proxy) { g_dbus_proxy_call( proxy, "Forget", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_log, "Failed to forget known network: %s\n"); } void known_network_set(KnownNetwork *kn) { { GVariant *name_var; const gchar *name; name_var = g_dbus_proxy_get_cached_property(kn->proxy, "Name"); name = g_variant_get_string(name_var, NULL); gtk_label_set_text(GTK_LABEL(kn->name_label), name); g_variant_unref(name_var); } { GVariant *type_var; const gchar *type_raw; type_var = g_dbus_proxy_get_cached_property(kn->proxy, "Type"); type_raw = g_variant_get_string(type_var, NULL); gtk_label_set_text(GTK_LABEL(kn->security_label), get_security_type(type_raw)); g_variant_unref(type_var); } { GVariant *hidden_var; gboolean hidden; hidden_var = g_dbus_proxy_get_cached_property(kn->proxy, "Hidden"); hidden = g_variant_get_boolean(hidden_var); if (hidden) { gtk_widget_show(kn->hidden_label); } else { gtk_widget_hide(kn->hidden_label); } g_variant_unref(hidden_var); } { GVariant *last_connected_var; last_connected_var = g_dbus_proxy_get_cached_property(kn->proxy, "LastConnectedTime"); if (last_connected_var) { const gchar *last_connected_raw; GDateTime *datetime_utc; GDateTime *datetime_local; gchar *datetime_formatted; last_connected_raw = g_variant_get_string(last_connected_var, NULL); datetime_utc = g_date_time_new_from_iso8601(last_connected_raw, NULL); datetime_local = g_date_time_to_local(datetime_utc); g_date_time_unref(datetime_utc); datetime_formatted = g_date_time_format(datetime_local, (global.last_connection_time_fmt ? global.last_connection_time_fmt : "%x\n%l:%M %p")); g_date_time_unref(datetime_local); gtk_label_set_text(GTK_LABEL(kn->last_connection_label), datetime_formatted); g_free(datetime_formatted); g_variant_unref(last_connected_var); } else { gtk_label_set_text(GTK_LABEL(kn->last_connection_label), _("Never")); } } } KnownNetwork* known_network_add(Window *window, GDBusObject *object, GDBusProxy *proxy) { KnownNetwork *kn; GtkWidget *name_box, *autoconnect_switch, *forget_button; kn = g_malloc(sizeof(KnownNetwork)); kn->proxy = proxy; kn->name_label = gtk_label_new(NULL); g_object_ref_sink(kn->name_label); gtk_widget_set_tooltip_text(kn->name_label, _("SSID")); kn->hidden_label = new_label_gray(_("(Hidden)")); g_object_ref_sink(kn->hidden_label); name_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_box_append(GTK_BOX(name_box), kn->name_label); gtk_box_append(GTK_BOX(name_box), kn->hidden_label); kn->security_label = gtk_label_new(NULL); g_object_ref_sink(kn->security_label); gtk_widget_set_tooltip_text(kn->security_label, _("Network security")); autoconnect_switch = switch_new(proxy, "AutoConnect"); gtk_widget_set_tooltip_text(autoconnect_switch, _("Automatically connect to this network")); forget_button = gtk_button_new_with_label(_("Forget")); g_signal_connect_swapped(forget_button, "clicked", G_CALLBACK(known_network_forget), proxy); gtk_widget_set_tooltip_text(forget_button, _("Forget this network")); kn->last_connection_label = gtk_label_new(NULL); g_object_ref_sink(kn->last_connection_label); gtk_widget_set_tooltip_text(kn->last_connection_label, _("Most recent connection to this network")); gtk_widget_set_hexpand(name_box, TRUE); gtk_widget_set_halign(name_box, GTK_ALIGN_START); gtk_widget_set_halign(kn->name_label, GTK_ALIGN_START); gtk_widget_set_halign(kn->hidden_label, GTK_ALIGN_START); gtk_widget_set_halign(kn->security_label, GTK_ALIGN_START); gtk_widget_set_halign(autoconnect_switch, GTK_ALIGN_START); gtk_widget_set_halign(forget_button, GTK_ALIGN_START); gtk_widget_set_halign(kn->last_connection_label, GTK_ALIGN_START); gtk_grid_attach(GTK_GRID(window->known_network_table), name_box, 0, n_known_networks, 1, 1); gtk_grid_attach(GTK_GRID(window->known_network_table), kn->security_label, 1, n_known_networks, 1, 1); gtk_grid_attach(GTK_GRID(window->known_network_table), autoconnect_switch, 2, n_known_networks, 1, 1); gtk_grid_attach(GTK_GRID(window->known_network_table), forget_button, 3, n_known_networks, 1, 1); gtk_grid_attach(GTK_GRID(window->known_network_table), kn->last_connection_label, 4, n_known_networks, 1, 1); kn->handler_update = g_signal_connect_swapped(proxy, "g-properties-changed", G_CALLBACK(known_network_set), kn); known_network_set(kn); n_known_networks ++; return kn; } void known_network_remove(Window *window, KnownNetwork *kn) { g_signal_handler_disconnect(kn->proxy, kn->handler_update); { int n; gtk_grid_query_child(GTK_GRID(window->known_network_table), kn->security_label, NULL, &n, NULL, NULL); gtk_grid_remove_row(GTK_GRID(window->known_network_table), n); } g_object_unref(kn->name_label); g_object_unref(kn->hidden_label); g_object_unref(kn->security_label); g_object_unref(kn->last_connection_label); g_free(kn); n_known_networks --; } iwgtk-0.9/src/known_network.h000066400000000000000000000023771435506763100163770ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_KNOWN_NETWORKS_H #define _IWGTK_KNOWN_NETWORKS_H typedef struct KnownNetwork_s KnownNetwork; struct KnownNetwork_s { GDBusProxy *proxy; gulong handler_update; // Widgets GtkWidget *name_label; GtkWidget *hidden_label; GtkWidget *security_label; GtkWidget *last_connection_label; }; void known_network_forget(GDBusProxy *proxy); KnownNetwork* known_network_add(Window *window, GDBusObject *object, GDBusProxy *proxy); void known_network_remove(Window *window, KnownNetwork *known_network); #endif iwgtk-0.9/src/main.c000066400000000000000000000260001435506763100143760ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include #include "iwgtk.h" GlobalData global; static const GDBusErrorEntry iwd_error_codes[] = { {IWD_ERROR_BUSY, "net.connman.iwd.Busy"}, {IWD_ERROR_FAILED, "net.connman.iwd.Failed"}, {IWD_ERROR_INVALID_ARGUMENTS, "net.connman.iwd.InvalidArguments"}, {IWD_ERROR_ALREADY_EXISTS, "net.connman.iwd.AlreadyExists"}, {IWD_ERROR_NOT_CONNECTED, "net.connman.iwd.NotConnected"}, {IWD_ERROR_NOT_CONFIGURED, "net.connman.iwd.NotConfigured"}, {IWD_ERROR_NOT_FOUND, "net.connman.iwd.NotFound"}, {IWD_ERROR_SERVICE_SET_OVERLAP, "net.connman.iwd.ServiceSetOverlap"}, {IWD_ERROR_ALREADY_PROVISIONED, "net.connman.iwd.AlreadyProvisioned"}, {IWD_ERROR_NOT_HIDDEN, "net.connman.iwd.NotHidden"}, {IWD_ERROR_ABORTED, "net.connman.iwd.Aborted"}, {IWD_ERROR_NO_AGENT, "net.connman.iwd.NoAgent"}, {IWD_ERROR_NOT_SUPPORTED, "net.connman.iwd.NotSupported"}, {IWD_ERROR_TIMEOUT, "net.connman.iwd.Timeout"}, {IWD_ERROR_IN_PROGRESS, "net.connman.iwd.InProgress"}, {IWD_ERROR_WSC_SESSION_OVERLAP, "net.connman.iwd.SimpleConfiguration.SessionOverlap"}, {IWD_ERROR_WSC_NO_CREDENTIALS, "net.connman.iwd.SimpleConfiguration.NoCredentials"}, {IWD_ERROR_WSC_TIME_EXPIRED, "net.connman.iwd.SimpleConfiguration.TimeExpired"}, {IWD_ERROR_WSC_WALK_TIME_EXPIRED, "net.connman.iwd.SimpleConfiguration.WalkTimeExpired"}, {IWD_ERROR_WSC_NOT_REACHABLE, "net.connman.iwd.SimpleConfiguration.NotReachable"}, {IWD_ERROR_INVALID_FORMAT, "net.connman.iwd.InvalidFormat"}, {IWD_ERROR_NOT_AVAILABLE, "net.connman.iwd.NotAvailable"}, {IWD_ERROR_AGENT_CANCELED, "net.connman.iwd.Agent.Error.Canceled"} }; static const GOptionEntry command_options[] = { { "indicators", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, N_("Start indicator (tray) icon daemon"), NULL }, { "notifications", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, N_("Enable desktop notifications (default)"), NULL }, { "no-notifications", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, N_("Disable desktop notifications"), NULL }, { "version", 'V', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL, N_("Print version number"), NULL }, {NULL} }; void object_manager_callback(GDBusObjectManagerClient *manager, GAsyncResult *res) { gboolean launch_window; if (global.state & WINDOW_LAUNCH_PENDING) { global.state &= ~WINDOW_LAUNCH_PENDING; g_application_release(G_APPLICATION(global.application)); launch_window = TRUE; } else { launch_window = FALSE; } { GError *err; err = NULL; global.manager = g_dbus_object_manager_client_new_finish(res, &err); if (err) { if (err->domain == G_DBUS_ERROR && err->code == G_DBUS_ERROR_ACCESS_DENIED) { g_printerr("Access denied: User must be a member of either the netdev or wheel group to control iwd\n"); } else { g_printerr("Error creating GDBusObjectManager: %s\n", err->message); } g_error_free(err); return; } } if (launch_window) { window_launch(); } if (global.state & INDICATOR_DAEMON) { add_all_dbus_objects(NULL); } g_signal_connect(global.manager, "interface-added", G_CALLBACK(interface_add), NULL); g_signal_connect(global.manager, "interface-removed", G_CALLBACK(interface_rm), NULL); g_signal_connect(global.manager, "object-added", G_CALLBACK(object_add), NULL); g_signal_connect(global.manager, "object-removed", G_CALLBACK(object_rm), NULL); { GDBusProxy *agent_manager; agent_manager = G_DBUS_PROXY(g_dbus_object_manager_get_interface(global.manager, IWD_PATH_AGENT_MANAGER, IWD_IFACE_AGENT_MANAGER)); agent_register(agent_manager); } } void iwd_up(GDBusConnection *connection) { global.state &= ~IWD_DOWN; g_dbus_object_manager_client_new( connection, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, IWD_BUS_NAME, IWD_PATH_OBJECT_MANAGER, NULL, NULL, NULL, NULL, (GAsyncReadyCallback) object_manager_callback, NULL ); } void iwd_down(GDBusConnection *connection) { global.state |= IWD_DOWN; send_notification(_("iwd is down")); if (global.window != NULL) { gtk_window_destroy(GTK_WINDOW(global.window->window)); g_printerr("Destroying iwgtk window: iwd has stopped running\n"); } if (global.manager != NULL) { g_object_unref(global.manager); global.manager = NULL; } if (global.state & WINDOW_LAUNCH_PENDING) { global.state &= ~WINDOW_LAUNCH_PENDING; g_application_release(G_APPLICATION(global.application)); g_printerr("Cannot launch iwgtk window: iwd is not running\n"); } } static void config_set_color(GKeyFile *conf, const gchar *group, const gchar *key, GdkRGBA *color) { gchar *value; value = g_key_file_get_string(conf, group, key, NULL); if (value) { if (!gdk_rgba_parse(color, value)) { g_printerr("Configuration file contains invalid color value: %s.%s=%s\n", group, key, value); } g_free(value); } } static gint config_get_int(GKeyFile *conf, const gchar *group, const gchar *key, gint value_default) { gint value; value = g_key_file_get_integer(conf, group, key, NULL); return (value ? value : value_default); } static void config_set_values(GKeyFile *conf) { config_set_color(conf, "indicator.colors.station", "connected", &colors.station_connected); config_set_color(conf, "indicator.colors.station", "connecting", &colors.station_connecting); config_set_color(conf, "indicator.colors.station", "disconnected", &colors.station_disconnected); config_set_color(conf, "indicator.colors.ap", "up", &colors.ap_up); config_set_color(conf, "indicator.colors.ap", "down", &colors.ap_down); config_set_color(conf, "indicator.colors.adhoc", "up", &colors.adhoc_up); config_set_color(conf, "indicator.colors.adhoc", "down", &colors.adhoc_down); config_set_color(conf, "indicator.colors.disabled", "device", &colors.disabled_device); config_set_color(conf, "indicator.colors.disabled", "adapter", &colors.disabled_adapter); config_set_color(conf, "network.colors", "connected", &colors.network_connected); config_set_color(conf, "network.colors", "connecting", &colors.network_connecting); config_set_color(conf, "network.colors", "known", &colors.network_known); config_set_color(conf, "network.colors", "unknown", &colors.network_unknown); config_set_color(conf, "network.colors", "hidden", &colors.network_hidden); if (global.last_connection_time_fmt) { g_free(global.last_connection_time_fmt); } global.last_connection_time_fmt = g_key_file_get_string(conf, "known-network", "last-connection-time.format", NULL); if (g_key_file_get_boolean(conf, "window", "dark", NULL)) { g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); } global.width = config_get_int(conf, "window", "width", 440); global.height = config_get_int(conf, "window", "height", 600); } static gboolean config_read_file(const gchar *path, GKeyFile *key_file) { GError *err; gboolean conf_loaded; err = NULL; conf_loaded = g_key_file_load_from_file(key_file, path, G_KEY_FILE_NONE, &err); if (err) { /* * Only show parser errors (G_KEY_FILE_ERROR_*). Silently ignore "file not * found" and other OS errors (G_FILE_ERROR_*). */ if (err->domain == G_KEY_FILE_ERROR) { g_printerr("Failed to parse configuration file '%s': %s\n", path, err->message); } g_error_free(err); } return conf_loaded; } static void config_load_attempt() { GKeyFile *key_file; gchar *user_conf; key_file = g_key_file_new(); user_conf = g_strconcat(g_get_user_config_dir(), "/iwgtk.conf", NULL); if (config_read_file(user_conf, key_file) || config_read_file(SYSCONFDIR "/iwgtk.conf", key_file)) { config_set_values(key_file); } g_free(user_conf); g_key_file_free(key_file); } void startup(GtkApplication *app) { { volatile gsize error_domain_volatile; g_dbus_error_register_error_domain("iwd-error-quark", &error_domain_volatile, iwd_error_codes, G_N_ELEMENTS(iwd_error_codes)); global.iwd_error_domain = (GQuark) error_domain_volatile; } icon_theme_set(); config_load_attempt(); g_bus_watch_name( G_BUS_TYPE_SYSTEM, IWD_BUS_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE, (GBusNameAppearedCallback) iwd_up, (GBusNameVanishedCallback) iwd_down, NULL, NULL ); } gint handle_local_options(GApplication *application, GVariantDict *options) { if (g_variant_dict_contains(options, "version")) { puts(PACKAGE " " VERSION); return 0; } return -1; } gint command_line(GApplication *application, GApplicationCommandLine *command_line) { GVariantDict *options; options = g_application_command_line_get_options_dict(command_line); if (g_variant_dict_contains(options, "notifications")) { global.state &= ~NOTIFICATIONS_DISABLE; } if (g_variant_dict_contains(options, "no-notifications")) { global.state |= NOTIFICATIONS_DISABLE; } if (g_variant_dict_contains(options, "indicators")) { if (global.state & INDICATOR_DAEMON) { g_printerr("Indicator daemon is already running\n"); } else { global.state |= INDICATOR_DAEMON; g_application_hold(application); if (global.manager) { add_all_dbus_objects(NULL); } } } else { window_launch(); } return 0; } int main (int argc, char **argv) { setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); bind_textdomain_codeset(PACKAGE, "UTF-8"); textdomain(PACKAGE); global.application = gtk_application_new(APPLICATION_ID, G_APPLICATION_HANDLES_COMMAND_LINE); g_application_set_option_context_summary(G_APPLICATION(global.application), _("iwgtk is a wireless networking GUI.")); g_application_add_main_option_entries(G_APPLICATION(global.application), command_options); g_signal_connect(global.application, "startup", G_CALLBACK(startup), NULL); g_signal_connect(global.application, "handle-local-options", G_CALLBACK(handle_local_options), NULL); g_signal_connect(global.application, "command-line", G_CALLBACK(command_line), NULL); { int status; status = g_application_run(G_APPLICATION(global.application), argc, argv); g_object_unref(global.application); return status; } } iwgtk-0.9/src/main.h000066400000000000000000000031511435506763100144050ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_MAIN_H #define _IWGTK_MAIN_H typedef struct GlobalData_s GlobalData; #define WINDOW_LAUNCH_PENDING (1 << 0) #define IWD_DOWN (1 << 1) #define INDICATOR_DAEMON (1 << 2) #define NOTIFICATIONS_DISABLE (1 << 3) struct GlobalData_s { GtkApplication *application; GtkIconTheme *theme; GDBusObjectManager *manager; GQuark iwd_error_domain; Window *window; Indicator *indicators; int width; int height; guint8 state; gchar *last_connection_time_fmt; }; extern GlobalData global; void object_manager_callback(GDBusObjectManagerClient *manager, GAsyncResult *res); void iwd_up(GDBusConnection *connection); void iwd_down(GDBusConnection *connection); void startup(GtkApplication *app); gint handle_local_options(GApplication *application, GVariantDict *options); gint command_line(GApplication *application, GApplicationCommandLine *command_line); #endif iwgtk-0.9/src/meson.build000066400000000000000000000005251435506763100154540ustar00rootroot00000000000000src_files = files( 'adapter.c', 'adhoc.c', 'agent.c', 'ap.c', 'device.c', 'diagnostic.c', 'dialog.c', 'dpp.c', 'hidden.c', 'icon.c', 'indicator.c', 'known_network.c', 'main.c', 'network.c', 'sni.c', 'station.c', 'switch.c', 'utilities.c', 'window.c', 'wps.c' ) iwgtk-0.9/src/network.c000066400000000000000000000167741435506763100151640ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" const ErrorMessage detailed_errors_network[] = { {IWD_ERROR_FAILED, N_("Incorrect passphrase")}, {IWD_ERROR_INVALID_FORMAT, N_("Invalid passphrase")}, {0, NULL} }; const gchar* get_security_type(const gchar *type_raw) { if (strcmp(type_raw, "open") == 0) { return _("Open"); } else if (strcmp(type_raw, "psk") == 0) { return _("PSK"); } else if (strcmp(type_raw, "8021x") == 0) { return _("EAP"); } else if (strcmp(type_raw, "wep") == 0) { return _("WEP"); } else { return type_raw; } } void connect_button_clicked(GtkButton *button, GDBusProxy *network_proxy) { CallbackMessages *messages; messages = g_malloc(sizeof(CallbackMessages)); messages->error_table = detailed_errors_network; messages->free = TRUE; { GVariant *ssid_var; const gchar *ssid; ssid_var = g_dbus_proxy_get_cached_property(network_proxy, "Name"); ssid = g_variant_get_string(ssid_var, NULL); messages->success = g_strdup_printf(_("Connected to %s"), ssid); messages->failure = g_strdup_printf(_("Failed to connect to %s"), ssid); g_variant_unref(ssid_var); } g_dbus_proxy_call( network_proxy, "Connect", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_notify, messages); } void disconnect_button_clicked(GtkButton *button, Network *network) { CallbackMessages *messages; messages = g_malloc(sizeof(CallbackMessages)); messages->error_table = NULL; messages->free = TRUE; { GVariant *ssid_var; const gchar *ssid; ssid_var = g_dbus_proxy_get_cached_property(network->proxy, "Name"); ssid = g_variant_get_string(ssid_var, NULL); messages->success = g_strdup_printf(_("Disconnected from %s"), ssid); messages->failure = g_strdup_printf(_("Failed to disconnect from %s"), ssid); g_variant_unref(ssid_var); } g_dbus_proxy_call( network->station->proxy, "Disconnect", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_notify, messages); } void network_set(Network *network) { NetworkStatus network_status; { GVariant *ssid_var; const gchar *ssid; ssid_var = g_dbus_proxy_get_cached_property(network->proxy, "Name"); ssid = g_variant_get_string(ssid_var, NULL); gtk_label_set_text(GTK_LABEL(network->ssid_label), ssid); g_variant_unref(ssid_var); } { GVariant *type_var; const gchar *type_raw; type_var = g_dbus_proxy_get_cached_property(network->proxy, "Type"); type_raw = g_variant_get_string(type_var, NULL); gtk_label_set_text(GTK_LABEL(network->security_label), get_security_type(type_raw)); g_variant_unref(type_var); } if (network->button_handler_id != 0) { g_signal_handler_disconnect(network->connect_button, network->button_handler_id); } { GVariant *connected_var; gboolean connected; GtkButton *button; connected_var = g_dbus_proxy_get_cached_property(network->proxy, "Connected"); connected = g_variant_get_boolean(connected_var); g_variant_unref(connected_var); button = GTK_BUTTON(network->connect_button); if (connected) { if (network->station->network_connected != network) { network->station->network_connected = network; } if (network->station->state == STATION_CONNECTED) { network_status = NETWORK_CONNECTED; gtk_widget_set_tooltip_text(network->status_icon, _("Connected")); } else { network_status = NETWORK_CONNECTING; gtk_widget_set_tooltip_text(network->status_icon, _("Connecting")); } gtk_button_set_label(button, _("Disconnect")); gtk_widget_set_tooltip_text(network->connect_button, _("Disconnect from network")); network->button_handler_id = g_signal_connect(button, "clicked", G_CALLBACK(disconnect_button_clicked), network); } else { GVariant *known_network_var; if (network->station->network_connected == network) { network->station->network_connected = NULL; } known_network_var = g_dbus_proxy_get_cached_property(network->proxy, "KnownNetwork"); if (known_network_var) { g_variant_unref(known_network_var); network_status = NETWORK_KNOWN; gtk_widget_set_tooltip_text(network->status_icon, _("Known network")); } else { network_status = NETWORK_UNKNOWN; gtk_widget_set_tooltip_text(network->status_icon, _("Unknown network")); } gtk_button_set_label(button, _("Connect")); gtk_widget_set_tooltip_text(network->connect_button, _("Connect to network")); network->button_handler_id = g_signal_connect(button, "clicked", G_CALLBACK(connect_button_clicked), network->proxy); } } { const gchar *icon_name; icon_name = station_icons[network->level]; symbolic_icon_set_image(icon_name, color_status[network_status], network->status_icon); } } void station_add_network(Station *station, GDBusProxy *network_proxy, gint16 signal_strength, int index) { Network *network; network = station->networks + index; network->proxy = network_proxy; network->station = station; network->level = get_signal_level(signal_strength); network->button_handler_id = 0; network->status_icon = gtk_picture_new(); network->ssid_label = gtk_label_new(NULL); network->security_label = gtk_label_new(NULL); network->connect_button = gtk_button_new(); gtk_widget_set_tooltip_text(network->ssid_label, _("SSID")); gtk_widget_set_tooltip_text(network->security_label, _("Network security type")); g_object_ref_sink(network->status_icon); g_object_ref_sink(network->ssid_label); g_object_ref_sink(network->security_label); g_object_ref_sink(network->connect_button); gtk_grid_attach(GTK_GRID(station->network_table), network->status_icon, 0, index, 1, 1); gtk_grid_attach(GTK_GRID(station->network_table), network->ssid_label, 1, index, 1, 1); gtk_grid_attach(GTK_GRID(station->network_table), network->security_label, 2, index, 1, 1); gtk_grid_attach(GTK_GRID(station->network_table), network->connect_button, 3, index, 1, 1); gtk_widget_set_halign(network->status_icon, GTK_ALIGN_START); gtk_widget_set_halign(network->ssid_label, GTK_ALIGN_START); gtk_widget_set_halign(network->security_label, GTK_ALIGN_START); gtk_widget_set_halign(network->connect_button, GTK_ALIGN_FILL); gtk_widget_set_hexpand(network->ssid_label, TRUE); gtk_widget_set_size_request(network->status_icon, 32, 32); gtk_widget_set_size_request(network->connect_button, 107, -1); network_set(network); network->handler_update = g_signal_connect_swapped(network_proxy, "g-properties-changed", G_CALLBACK(network_set), network); } void network_remove(Network *network) { g_object_unref(network->status_icon); g_object_unref(network->ssid_label); g_object_unref(network->security_label); g_object_unref(network->connect_button); g_signal_handler_disconnect(network->proxy, network->handler_update); } iwgtk-0.9/src/network.h000066400000000000000000000031671435506763100151610ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_NETWORK_H #define _IWGTK_NETWORK_H typedef struct Network_s Network; typedef struct Station_s Station; typedef enum { NETWORK_CONNECTED, NETWORK_CONNECTING, NETWORK_KNOWN, NETWORK_UNKNOWN } NetworkStatus; struct Network_s { GDBusProxy *proxy; Station *station; gint8 level; // Widgets GtkWidget *status_icon; GtkWidget *ssid_label; GtkWidget *security_label; GtkWidget *connect_button; // Handlers gulong handler_update; gulong button_handler_id; }; const gchar* get_security_type(const gchar *type_raw); void connect_button_clicked(GtkButton *button, GDBusProxy *network_proxy); void disconnect_button_clicked(GtkButton *button, Network *network); void network_set(Network *network); void station_add_network(Station *station, GDBusProxy *network_proxy, gint16 signal_strength, int index); void network_remove(Network *network); #endif iwgtk-0.9/src/sni.c000066400000000000000000000406521435506763100142540ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" #include GDBusArgInfo arg_x = {-1, "x", "i", NULL}; GDBusArgInfo arg_y = {-1, "y", "i", NULL}; GDBusArgInfo arg_delta = {-1, "delta", "i", NULL}; GDBusArgInfo arg_orientation = {-1, "orientation", "s", NULL}; GDBusArgInfo arg_status = {-1, "status", "s", NULL}; GDBusInterfaceInfo sni_interface_info = { -1, STATUS_NOTIFIER_ITEM_INTERFACE, (GDBusMethodInfo *[]) { &(GDBusMethodInfo) { -1, "ContextMenu", (GDBusArgInfo *[]) {&arg_x, &arg_y, NULL}, NULL, NULL }, &(GDBusMethodInfo) { -1, "Activate", (GDBusArgInfo *[]) {&arg_x, &arg_y, NULL}, NULL, NULL }, &(GDBusMethodInfo) { -1, "SecondaryActivate", (GDBusArgInfo *[]) {&arg_x, &arg_y, NULL}, NULL, NULL }, &(GDBusMethodInfo) { -1, "Scroll", (GDBusArgInfo *[]) {&arg_delta, &arg_orientation, NULL}, NULL, NULL }, NULL }, (GDBusSignalInfo *[]) { &(GDBusSignalInfo) { -1, "NewTitle", NULL, NULL }, &(GDBusSignalInfo) { -1, "NewIcon", NULL, NULL }, &(GDBusSignalInfo) { -1, "NewAttentionIcon", NULL, NULL }, &(GDBusSignalInfo) { -1, "NewOverlayIcon", NULL, NULL }, &(GDBusSignalInfo) { -1, "NewToolTip", NULL, NULL }, &(GDBusSignalInfo) { -1, "NewStatus", (GDBusArgInfo *[]) {&arg_status, NULL}, NULL }, NULL }, (GDBusPropertyInfo *[]) { &(GDBusPropertyInfo) { -1, "Category", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "Id", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "Title", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "Status", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "WindowId", "u", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "IconName", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "IconPixmap", "a(iiay)", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "OverlayIconName", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "OverlayIconPixmap", "a(iiay)", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "AttentionIconName", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "AttentionIconPixmap", "a(iiay)", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "AttentionMovieName", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "ToolTip", "(sa(iiay)ss)", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "ItemIsMenu", "b", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, &(GDBusPropertyInfo) { -1, "Menu", "o", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL }, NULL }, NULL // Annotation info }; GDBusInterfaceVTable sni_interface_vtable = { (GDBusInterfaceMethodCallFunc) sni_method_call, (GDBusInterfaceGetPropertyFunc) sni_get_property, NULL }; StatusNotifierItem* sni_new(gpointer user_data) { StatusNotifierItem *sni; sni = g_malloc0(sizeof(StatusNotifierItem)); sni->user_data = user_data; { static int sni_id = 0; sni->bus_name = g_strdup_printf("%s-%d-%d", STATUS_NOTIFIER_ITEM_BUS_NAME_PREFIX, getpid(), ++ sni_id); sni->owner_id = g_bus_own_name( G_BUS_TYPE_SESSION, sni->bus_name, G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE, (GBusAcquiredCallback) sni_bus_acquired, (GBusNameAcquiredCallback) sni_bus_name_acquired, (GBusNameLostCallback) sni_bus_name_lost, sni, NULL); } return sni; } void sni_rm(StatusNotifierItem *sni) { g_bus_unown_name(sni->owner_id); g_dbus_connection_unregister_object(sni->connection, sni->registration_id); if (sni->category != NULL) { g_variant_unref(sni->category); } if (sni->id != NULL) { g_variant_unref(sni->id); } if (sni->title != NULL) { g_variant_unref(sni->title); } if (sni->status != NULL) { g_variant_unref(sni->status); } if (sni->icon_name != NULL) { g_variant_unref(sni->icon_name); } if (sni->icon_pixmap != NULL) { g_variant_unref(sni->icon_pixmap); } if (sni->overlay_icon_name != NULL) { g_variant_unref(sni->overlay_icon_name); } if (sni->overlay_icon_pixmap != NULL) { g_variant_unref(sni->overlay_icon_pixmap); } if (sni->attention_icon_name != NULL) { g_variant_unref(sni->attention_icon_name); } if (sni->attention_icon_pixmap != NULL) { g_variant_unref(sni->attention_icon_pixmap); } if (sni->attention_movie_name != NULL) { g_variant_unref(sni->attention_movie_name); } if (sni->tooltip != NULL) { g_variant_unref(sni->tooltip); } if (sni->menu != NULL) { g_variant_unref(sni->menu); } g_free(sni->bus_name); g_free(sni); } void sni_bus_acquired(GDBusConnection *connection, const gchar *name, StatusNotifierItem *sni) { GError *err; sni->connection = connection; err = NULL; sni->registration_id = g_dbus_connection_register_object( connection, STATUS_NOTIFIER_ITEM_OBJECT_PATH, &sni_interface_info, &sni_interface_vtable, sni, NULL, &err); if (err != NULL) { g_printerr("Failed to register StatusNotifierItem object: %s\n", err->message); g_error_free(err); } } void sni_bus_name_acquired(GDBusConnection *connection, const gchar *name, StatusNotifierItem *sni) { g_bus_watch_name_on_connection( connection, STATUS_NOTIFIER_WATCHER_BUS_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE, (GBusNameAppearedCallback) sni_watcher_up, NULL, sni, NULL); } void sni_bus_name_lost(GDBusConnection *connection, const gchar *name, StatusNotifierItem *sni) { g_printerr("Bus name '%s' has been lost\n", name); } void sni_watcher_up(GDBusConnection *connection, const gchar *name, const gchar *name_owner, StatusNotifierItem *sni) { g_dbus_connection_call( connection, STATUS_NOTIFIER_WATCHER_BUS_NAME, STATUS_NOTIFIER_WATCHER_OBJECT_PATH, STATUS_NOTIFIER_WATCHER_INTERFACE, "RegisterStatusNotifierItem", g_variant_new("(s)", sni->bus_name), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) validate_method_call, "Failed to register StatusNotifierItem: %s\n"); } void validate_method_call(GDBusConnection *connection, GAsyncResult *res, const gchar *message) { GVariant *ret; GError *err; err = NULL; ret = g_dbus_connection_call_finish(connection, res, &err); if (ret) { g_variant_unref(ret); } else { g_printerr(message, err->message); g_error_free(err); } } void sni_method_call(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, StatusNotifierItem *sni) { if (strcmp(method_name, "ContextMenu") == 0) { if (sni->context_menu_handler != NULL) { sni->context_menu_handler(sni->user_data); } g_dbus_method_invocation_return_value(invocation, NULL); } else if (strcmp(method_name, "Activate") == 0) { if (sni->activate_handler != NULL) { sni->activate_handler(sni->user_data); } g_dbus_method_invocation_return_value(invocation, NULL); } else if (strcmp(method_name, "SecondaryActivate") == 0) { if (sni->secondary_activate_handler != NULL) { sni->secondary_activate_handler(sni->user_data); } g_dbus_method_invocation_return_value(invocation, NULL); } else if (strcmp(method_name, "Scroll") == 0) { if (sni->scroll_handler != NULL) { int delta; const gchar *orientation_str; SNIScrollOrientation orientation; g_variant_get(parameters, "(i&s)", &delta, &orientation_str); if (strcmp(orientation_str, "vertical") == 0) { orientation = SNI_ORIENTATION_VERTICAL; } else if (strcmp(orientation_str, "horizontal") == 0) { orientation = SNI_ORIENTATION_HORIZONTAL; } else { orientation = SNI_ORIENTATION_NONE; } sni->scroll_handler(delta, orientation, sni->user_data); } g_dbus_method_invocation_return_value(invocation, NULL); } else { g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.UnknownMethod", "Unknown method"); } } GVariant* sni_get_property(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, StatusNotifierItem *sni) { if (strcmp(property_name, "Category") == 0) { if (sni->category) { return g_variant_ref(sni->category); } } else if (strcmp(property_name, "Id") == 0) { if (sni->id) { return g_variant_ref(sni->id); } } else if (strcmp(property_name, "Title") == 0) { if (sni->title) { return g_variant_ref(sni->title); } } else if (strcmp(property_name, "Status") == 0) { if (sni->status) { return g_variant_ref(sni->status); } } else if (strcmp(property_name, "WindowId") == 0) { return g_variant_new_uint32(sni->window_id); } else if (strcmp(property_name, "IconName") == 0) { if (sni->icon_name) { return g_variant_ref(sni->icon_name); } } else if (strcmp(property_name, "IconPixmap") == 0) { if (sni->icon_pixmap) { return g_variant_ref(sni->icon_pixmap); } } else if (strcmp(property_name, "OverlayIconName") == 0) { if (sni->overlay_icon_name) { return g_variant_ref(sni->overlay_icon_name); } } else if (strcmp(property_name, "OverlayIconPixmap") == 0) { if (sni->overlay_icon_pixmap) { return g_variant_ref(sni->overlay_icon_pixmap); } } else if (strcmp(property_name, "AttentionIconName") == 0) { if (sni->attention_icon_name) { return g_variant_ref(sni->attention_icon_name); } } else if (strcmp(property_name, "AttentionIconPixmap") == 0) { if (sni->attention_icon_pixmap) { return g_variant_ref(sni->attention_icon_pixmap); } } else if (strcmp(property_name, "AttentionMovieName") == 0) { if (sni->attention_movie_name) { return g_variant_ref(sni->attention_movie_name); } } else if (strcmp(property_name, "ToolTip") == 0) { if (sni->tooltip) { return g_variant_ref(sni->tooltip); } } else if (strcmp(property_name, "ItemIsMenu") == 0) { return g_variant_new_boolean(sni->item_is_menu); } else if (strcmp(property_name, "Menu") == 0) { if (sni->menu) { return g_variant_ref(sni->menu); } } g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "Property '%s' is not implemented", property_name); return NULL; } void sni_emit_signal(StatusNotifierItem *sni, const gchar *signal_name, GVariant *parameters) { GError *err; err = NULL; g_dbus_connection_emit_signal( sni->connection, NULL, STATUS_NOTIFIER_ITEM_OBJECT_PATH, STATUS_NOTIFIER_ITEM_INTERFACE, signal_name, parameters, &err); if (err != NULL) { g_printerr("Failed to emit %s signal: %s\n", signal_name, err->message); g_error_free(err); } } gboolean sni_abstract_icon_pixmap_set(GVariant **sni_icon_pixmap, cairo_surface_t *surface) { GVariant *tuple[3]; if (cairo_image_surface_get_format(surface) != CAIRO_FORMAT_ARGB32) { g_printerr("Failed to set indicator icon: Invalid Cairo surface format\n"); cairo_surface_destroy(surface); return FALSE; } { unsigned char *argb; gint32 width, height; gsize n; argb = cairo_image_surface_get_data(surface); width = cairo_image_surface_get_width(surface); height = cairo_image_surface_get_height(surface); n = width*height; cairo_surface_flush(surface); if (G_BYTE_ORDER == G_LITTLE_ENDIAN) { guint32 *argb32; argb32 = (guint32 *) argb; for (int i = 0; i < n; i ++) { argb32[i] = g_htonl(argb32[i]); } } tuple[0] = g_variant_new_int32(width); tuple[1] = g_variant_new_int32(height); tuple[2] = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, argb, 4*n, 1); cairo_surface_destroy(surface); } { GVariant *tuple_var; tuple_var = g_variant_new_tuple(tuple, 3); if (*sni_icon_pixmap != NULL) { g_variant_unref(*sni_icon_pixmap); } *sni_icon_pixmap = g_variant_new_array(G_VARIANT_TYPE("(iiay)"), &tuple_var, 1); g_variant_ref_sink(*sni_icon_pixmap); } return TRUE; } void sni_category_set(StatusNotifierItem *sni, const gchar *category) { if (sni->category != NULL) { g_variant_unref(sni->category); } sni->category = g_variant_new_string(category); g_variant_ref_sink(sni->category); } void sni_id_set(StatusNotifierItem *sni, const gchar *id) { if (sni->id != NULL) { g_variant_unref(sni->id); } sni->id = g_variant_new_string(id); g_variant_ref_sink(sni->id); } void sni_title_set(StatusNotifierItem *sni, const gchar *title) { if (sni->title != NULL) { g_variant_unref(sni->title); } sni->title = g_variant_new_string(title); g_variant_ref_sink(sni->title); if (sni->connection) { sni_emit_signal(sni, "NewTitle", NULL); } } void sni_status_set(StatusNotifierItem *sni, const gchar *status) { if (sni->status != NULL) { g_variant_unref(sni->status); } sni->status = g_variant_new_string(status); g_variant_ref_sink(sni->status); if (sni->connection) { sni_emit_signal(sni, "NewStatus", g_variant_new("(s)", status)); } } void sni_icon_name_set(StatusNotifierItem *sni, const gchar *icon_name) { if (sni->icon_name != NULL) { g_variant_unref(sni->icon_name); } sni->icon_name = g_variant_new_string(icon_name); g_variant_ref_sink(sni->icon_name); if (sni->connection) { sni_emit_signal(sni, "NewIcon", NULL); } } void sni_icon_pixmap_set(StatusNotifierItem *sni, cairo_surface_t *surface) { if (sni_abstract_icon_pixmap_set(&sni->icon_pixmap, surface)) { if (sni->connection) { sni_emit_signal(sni, "NewIcon", NULL); } } } void sni_overlay_icon_name_set(StatusNotifierItem *sni, const gchar *overlay_icon_name) { if (sni->overlay_icon_name != NULL) { g_variant_unref(sni->overlay_icon_name); } sni->overlay_icon_name = g_variant_new_string(overlay_icon_name); g_variant_ref_sink(sni->overlay_icon_name); if (sni->connection) { sni_emit_signal(sni, "NewOverlayIcon", NULL); } } void sni_overlay_icon_pixmap_set(StatusNotifierItem *sni, cairo_surface_t *surface) { if (sni_abstract_icon_pixmap_set(&sni->overlay_icon_pixmap, surface)) { if (sni->connection) { sni_emit_signal(sni, "NewOverlayIcon", NULL); } } } void sni_attention_icon_name_set(StatusNotifierItem *sni, const gchar *attention_icon_name) { if (sni->attention_icon_name != NULL) { g_variant_unref(sni->attention_icon_name); } sni->attention_icon_name = g_variant_new_string(attention_icon_name); g_variant_ref_sink(sni->attention_icon_name); if (sni->connection) { sni_emit_signal(sni, "NewAttentionIcon", NULL); } } void sni_attention_icon_pixmap_set(StatusNotifierItem *sni, cairo_surface_t *surface) { if (sni_abstract_icon_pixmap_set(&sni->attention_icon_pixmap, surface)) { if (sni->connection) { sni_emit_signal(sni, "NewAttentionIcon", NULL); } } } void sni_attention_movie_name_set(StatusNotifierItem *sni, const gchar *attention_movie_name) { if (sni->attention_movie_name != NULL) { g_variant_unref(sni->attention_movie_name); } sni->attention_movie_name = g_variant_new_string(attention_movie_name); g_variant_ref_sink(sni->attention_movie_name); } iwgtk-0.9/src/sni.h000066400000000000000000000105731435506763100142600ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_SNI_H #define _IWGTK_SNI_H #define STATUS_NOTIFIER_ITEM_OBJECT_PATH "/StatusNotifierItem" #define STATUS_NOTIFIER_ITEM_INTERFACE "org.kde.StatusNotifierItem" #define STATUS_NOTIFIER_ITEM_BUS_NAME_PREFIX "org.kde.StatusNotifierItem" #define STATUS_NOTIFIER_WATCHER_OBJECT_PATH "/StatusNotifierWatcher" #define STATUS_NOTIFIER_WATCHER_INTERFACE "org.kde.StatusNotifierWatcher" #define STATUS_NOTIFIER_WATCHER_BUS_NAME "org.kde.StatusNotifierWatcher" typedef enum { SNI_ORIENTATION_NONE, SNI_ORIENTATION_VERTICAL, SNI_ORIENTATION_HORIZONTAL } SNIScrollOrientation; typedef void (*SNIActivateHandler) (gpointer user_data); typedef void (*SNIScrollHandler) (int delta, SNIScrollOrientation orientation, gpointer user_data); typedef struct StatusNotifierItem_s StatusNotifierItem; struct StatusNotifierItem_s { GDBusConnection *connection; gpointer user_data; guint owner_id; guint registration_id; gchar *bus_name; // Methods to be exported via DBus SNIActivateHandler context_menu_handler; SNIActivateHandler activate_handler; SNIActivateHandler secondary_activate_handler; SNIScrollHandler scroll_handler; // Properties to be exported via DBus GVariant *category; GVariant *id; GVariant *title; GVariant *status; guint32 window_id; GVariant *icon_name; GVariant *icon_pixmap; GVariant *overlay_icon_name; GVariant *overlay_icon_pixmap; GVariant *attention_icon_name; GVariant *attention_icon_pixmap; GVariant *attention_movie_name; GVariant *tooltip; gboolean item_is_menu; GVariant *menu; }; StatusNotifierItem* sni_new(gpointer user_data); void sni_rm(StatusNotifierItem *sni); void sni_bus_acquired(GDBusConnection *connection, const gchar *name, StatusNotifierItem *sni); void sni_bus_name_acquired(GDBusConnection *connection, const gchar *name, StatusNotifierItem *sni); void sni_bus_name_lost(GDBusConnection *connection, const gchar *name, StatusNotifierItem *sni); void sni_watcher_up(GDBusConnection *connection, const gchar *name, const gchar *name_owner, StatusNotifierItem *sni); void validate_method_call(GDBusConnection *connection, GAsyncResult *res, const gchar *message); void sni_method_call(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, StatusNotifierItem *sni); GVariant* sni_get_property(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, StatusNotifierItem *sni); void sni_emit_signal(StatusNotifierItem *sni, const gchar *signal_name, GVariant *parameters); gboolean sni_abstract_icon_pixmap_set(GVariant **sni_icon_pixmap, cairo_surface_t *surface); void sni_category_set(StatusNotifierItem *sni, const gchar *category); void sni_id_set(StatusNotifierItem *sni, const gchar *id); void sni_title_set(StatusNotifierItem *sni, const gchar *title); void sni_status_set(StatusNotifierItem *sni, const gchar *status); void sni_icon_name_set(StatusNotifierItem *sni, const gchar *icon_name); void sni_icon_pixmap_set(StatusNotifierItem *sni, cairo_surface_t *surface); void sni_overlay_icon_name_set(StatusNotifierItem *sni, const gchar *overlay_icon_name); void sni_overlay_icon_pixmap_set(StatusNotifierItem *sni, cairo_surface_t *surface); void sni_attention_icon_name_set(StatusNotifierItem *sni, const gchar *attention_icon_name); void sni_attention_icon_pixmap_set(StatusNotifierItem *sni, cairo_surface_t *surface); void sni_attention_movie_name_set(StatusNotifierItem *sni, const gchar *attention_movie_name); #endif iwgtk-0.9/src/station.c000066400000000000000000000225751435506763100151500ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" void station_set(Station *station) { gboolean scanning; { GVariant *scanning_var; scanning_var = g_dbus_proxy_get_cached_property(station->proxy, "Scanning"); scanning = g_variant_get_boolean(scanning_var); g_variant_unref(scanning_var); } gtk_widget_set_sensitive(station->scan_button, !scanning); gtk_button_set_child(GTK_BUTTON(station->scan_button), scanning ? station->scan_widget_scanning : station->scan_widget_idle); if (scanning) { station->state = STATION_SCANNING; station->network_connected = NULL; station_network_table_clear(station); } else { if (station->state == STATION_SCANNING) { // A scan has just completed station_network_table_build(station); } { GVariant *state_var; const gchar *state; state_var = g_dbus_proxy_get_cached_property(station->proxy, "State"); state = g_variant_get_string(state_var, NULL); if (strcmp(state, "connected") == 0) { station->state = STATION_CONNECTED; } else if (strcmp(state, "connecting") == 0) { station->state = STATION_CONNECTING; } else { station->state = STATION_DISCONNECTED; station->network_connected = NULL; } g_variant_unref(state_var); } if (station->network_connected) { network_set(station->network_connected); } } if (station->dpp) { dpp_set(station->dpp); } } Station* station_add(Window *window, GDBusObject *object, GDBusProxy *proxy) { Station *station; station = g_malloc(sizeof(Station)); station->proxy = proxy; station->dpp = NULL; station->state = STATION_SCANNING; station->n_networks = 0; station->network_connected = NULL; station->scan_button = gtk_button_new(); g_object_ref_sink(station->scan_button); g_signal_connect_swapped(station->scan_button, "clicked", G_CALLBACK(send_scan_request), station); station->scan_widget_idle = gtk_label_new(_("Scan")); g_object_ref_sink(station->scan_widget_idle); station->scan_widget_scanning = label_with_spinner(_("Scanning")); g_object_ref_sink(station->scan_widget_scanning); station->provision_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); g_object_ref_sink(station->provision_vbox); { GtkWidget *hidden_connect; hidden_connect = gtk_button_new_with_label(_("Hidden network")); gtk_widget_set_tooltip_text(hidden_connect, _("Connect to a hidden network")); gtk_widget_set_size_request(hidden_connect, PROVISION_MENU_WIDTH, -1); g_signal_connect_swapped(hidden_connect, "clicked", G_CALLBACK(hidden_ssid_dialog), station); gtk_box_append(GTK_BOX(station->provision_vbox), hidden_connect); } station->provision_menu = gtk_popover_new(); g_object_ref_sink(station->provision_menu); gtk_popover_set_has_arrow(GTK_POPOVER(station->provision_menu), FALSE); gtk_popover_set_child(GTK_POPOVER(station->provision_menu), station->provision_vbox); station->provision_button = gtk_button_new_with_label(_("Provision")); g_object_ref_sink(station->provision_button); g_signal_connect_swapped(station->provision_button, "clicked", G_CALLBACK(gtk_widget_show), station->provision_menu); gtk_widget_set_parent(station->provision_menu, station->provision_button); station->network_table = gtk_grid_new(); g_object_ref_sink(station->network_table); gtk_widget_set_size_request(station->scan_button, 110, -1); gtk_widget_set_halign(station->scan_button, GTK_ALIGN_FILL); gtk_grid_set_column_spacing(GTK_GRID(station->network_table), 10); gtk_grid_set_row_spacing(GTK_GRID(station->network_table), 10); gtk_widget_set_margin_start(station->network_table, 5); gtk_widget_set_margin_end(station->network_table, 5); gtk_widget_set_margin_bottom(station->network_table, 5); couple_register(window, DEVICE_STATION, 1, station, object); couple_register(window, STATION_DPP, 0, station, object); couple_register(window, STATION_WPS, 0, station, object); station->handler_update = g_signal_connect_swapped(proxy, "g-properties-changed", G_CALLBACK(station_set), station); station_set(station); return station; } void station_remove(Window *window, Station *station) { g_signal_handler_disconnect(station->proxy, station->handler_update); couple_unregister(window, DEVICE_STATION, 1, station); couple_unregister(window, STATION_DPP, 0, station); couple_unregister(window, STATION_WPS, 0, station); gtk_widget_unparent(station->provision_menu); g_object_unref(station->scan_button); g_object_unref(station->scan_widget_idle); g_object_unref(station->scan_widget_scanning); g_object_unref(station->provision_button); g_object_unref(station->provision_menu); g_object_unref(station->provision_vbox); station_network_table_clear(station); g_object_unref(station->network_table); g_free(station); } void bind_device_station(Device *device, Station *station) { gtk_grid_attach(GTK_GRID(device->table), station->scan_button, 3, 0, 1, 1); gtk_grid_attach(GTK_GRID(device->table), station->provision_button, 3, 1, 1, 1); gtk_box_append(GTK_BOX(device->master), station->network_table); } void unbind_device_station(Device *device, Station *station) { gtk_grid_remove(GTK_GRID(device->table), station->scan_button); gtk_grid_remove(GTK_GRID(device->table), station->provision_button); gtk_box_remove(GTK_BOX(device->master), station->network_table); } void send_scan_request(Station *station) { g_dbus_proxy_call( station->proxy, "Scan", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_log, "Scan request failed: %s\n"); } void insert_separator(Station *station, gint position) { GtkWidget *separator; separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); gtk_grid_attach(GTK_GRID(station->network_table), separator, 0, position, 5, 1); } void station_network_table_build(Station *station) { g_dbus_proxy_call( station->proxy, "GetOrderedNetworks", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) get_networks_callback, station); } void station_network_table_clear(Station *station) { { GtkWidget *child; while ((child = gtk_widget_get_first_child(station->network_table))) { gtk_grid_remove(GTK_GRID(station->network_table), child); } } if (station->n_networks != 0) { for (int i = 0; i < station->n_networks; i ++) { network_remove(station->networks + i); } station->n_networks = 0; g_free(station->networks); } } void get_networks_callback(GDBusProxy *proxy, GAsyncResult *res, Station *station) { GVariant *ordered_networks; GError *err; err = NULL; ordered_networks = g_dbus_proxy_call_finish(proxy, res, &err); if (ordered_networks) { GVariantIter *iter; const gchar *network_path; gint16 signal_strength; gint i; g_variant_get(ordered_networks, "(a(on))", &iter); station->n_networks = g_variant_iter_n_children(iter); station->networks = g_malloc(station->n_networks * sizeof(Network)); i = 0; while (g_variant_iter_next(iter, "(&on)", &network_path, &signal_strength)) { GDBusProxy *network_proxy; network_proxy = G_DBUS_PROXY(g_dbus_object_manager_get_interface(global.manager, network_path, IWD_IFACE_NETWORK)); if (network_proxy) { station_add_network(station, network_proxy, signal_strength, i ++); } else { g_printerr("Failed to find network object '%s'\n", network_path); } } if (station->n_networks > 0) { insert_separator(station, station->n_networks); } g_variant_iter_free(iter); g_variant_unref(ordered_networks); } else { g_printerr("Failed to retrieve available network list: %s\n", err->message); g_error_free(err); } g_dbus_proxy_call( proxy, "GetHiddenAccessPoints", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) get_hidden_networks_callback, station); } void get_hidden_networks_callback(GDBusProxy *proxy, GAsyncResult *res, Station *station) { GVariant *ordered_networks; GError *err; err = NULL; ordered_networks = g_dbus_proxy_call_finish(proxy, res, &err); if (ordered_networks) { gint i; GVariantIter *iter; const gchar *address; gint16 signal_strength; const gchar *type; i = 0; g_variant_get(ordered_networks, "(a(sns))", &iter); while (g_variant_iter_next(iter, "(&sn&s)", &address, &signal_strength, &type)) { i ++; station_add_hidden_network(station, address, type, signal_strength, station->n_networks + i); } if (i > 0) { insert_separator(station, station->n_networks + i + 1); } g_variant_iter_free(iter); g_variant_unref(ordered_networks); } else { g_printerr("Failed to retrieve hidden network list: %s\n", err->message); g_error_free(err); } } iwgtk-0.9/src/station.h000066400000000000000000000042241435506763100151440ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_STATION_H #define _IWGTK_STATION_H #define PROVISION_MENU_WIDTH 180 typedef struct Station_s Station; typedef struct Device_s Device; typedef struct Network_s Network; typedef struct DPP_s DPP; typedef enum { STATION_CONNECTED, STATION_CONNECTING, STATION_DISCONNECTED, STATION_SCANNING } StationState; struct Station_s { GDBusProxy *proxy; DPP *dpp; StationState state; gulong handler_update; // Networks gsize n_networks; Network *networks; Network *network_connected; // Widgets GtkWidget *scan_button; GtkWidget *scan_widget_idle; GtkWidget *scan_widget_scanning; GtkWidget *provision_button; GtkWidget *provision_menu; GtkWidget *provision_vbox; GtkWidget *network_table; }; void station_set(Station *station); Station* station_add(Window *window, GDBusObject *object, GDBusProxy *proxy); void station_remove(Window *window, Station *station); void bind_device_station(Device *device, Station *station); void unbind_device_station(Device *device, Station *station); void send_scan_request(Station *station); void insert_separator(Station *station, gint position); void station_network_table_build(Station *station); void station_network_table_clear(Station *station); void get_networks_callback(GDBusProxy *proxy, GAsyncResult *res, Station *station); void get_hidden_networks_callback(GDBusProxy *proxy, GAsyncResult *res, Station *station); #endif iwgtk-0.9/src/switch.c000066400000000000000000000052731435506763100147640ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" /* * At the time this handler is called, "active" is updated but "state" is not. * Returning FALSE causes "state" to be updated by the default handler. * (returning TRUE would cause "state" not to be updated). */ gboolean switch_handler(GtkSwitch *widget, gboolean state, SwitchData *switch_data) { set_remote_property(switch_data->proxy, switch_data->property, g_variant_new_boolean(state), (SetFunction) switch_set, switch_data); return FALSE; } void switch_set(SwitchData *switch_data) { GVariant *state_var; gboolean state; state_var = g_dbus_proxy_get_cached_property(switch_data->proxy, switch_data->property); state = g_variant_get_boolean(state_var); gtk_switch_set_active(GTK_SWITCH(switch_data->widget), state); g_variant_unref(state_var); } void switch_rm(GtkWidget *widget, SwitchData *switch_data) { g_signal_handler_disconnect(switch_data->proxy, switch_data->handler); g_free(switch_data); } GtkWidget* switch_new(GDBusProxy *proxy, const gchar *property) { SwitchData *switch_data; GVariant *state_var; gboolean state; switch_data = g_malloc(sizeof(SwitchData)); switch_data->proxy = proxy; switch_data->widget = gtk_switch_new(); switch_data->property = property; state_var = g_dbus_proxy_get_cached_property(proxy, property); state = g_variant_get_boolean(state_var); g_variant_unref(state_var); gtk_switch_set_active(GTK_SWITCH(switch_data->widget), state); switch_data->handler = g_signal_connect_swapped(proxy, "g-properties-changed", G_CALLBACK(switch_set), switch_data); g_signal_connect(switch_data->widget, "state-set", G_CALLBACK(switch_handler), switch_data); g_signal_connect(switch_data->widget, "destroy", G_CALLBACK(switch_rm), switch_data); gtk_widget_set_halign(switch_data->widget, GTK_ALIGN_CENTER); gtk_widget_set_valign(switch_data->widget, GTK_ALIGN_CENTER); gtk_widget_show(switch_data->widget); return switch_data->widget; } iwgtk-0.9/src/switch.h000066400000000000000000000022641435506763100147660ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_SWITCH_H #define _IWGTK_SWITCH_H typedef struct SwitchData_s SwitchData; struct SwitchData_s { GDBusProxy *proxy; GtkWidget *widget; const gchar *property; gulong handler; }; gboolean switch_handler(GtkSwitch *widget, gboolean state, SwitchData *switch_data); void switch_set(SwitchData *switch_data); void switch_rm(GtkWidget *widget, SwitchData *switch_data); GtkWidget* switch_new(GDBusProxy *proxy, const gchar *property); #endif iwgtk-0.9/src/utilities.c000066400000000000000000000200351435506763100154670ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" const gchar* get_error_detail(GError *err, const ErrorMessage *error_table) { gint i; if (error_table && err->domain == global.iwd_error_domain) { for (i = 0; error_table[i].code != 0; i ++) { if (err->code == error_table[i].code) { return gettext(error_table[i].message); } } } /* * Return err->message with its prefix removed. If we reach the end of the string for * some reason, then just return err->message in its entirety. */ for (i = 0; err->message[i] != ' '; i ++) { if (err->message[i] == '\0') { return err->message; } } return err->message + i + 1; } void method_call_notify(GDBusProxy *proxy, GAsyncResult *res, CallbackMessages *messages) { GVariant *ret; GError *err; err = NULL; ret = g_dbus_proxy_call_finish(proxy, res, &err); if (ret) { g_variant_unref(ret); if (messages->success) { send_notification(messages->free ? messages->success : gettext(messages->success)); } } else { if (messages->failure) { const gchar *err_detail; gchar *message_detailed; err_detail = get_error_detail(err, messages->error_table); message_detailed = g_strdup_printf("%s: %s", (messages->free ? messages->failure : gettext(messages->failure)), err_detail); send_notification(message_detailed); g_free(message_detailed); } g_printerr("%s\n", err->message); g_error_free(err); } if (messages->free) { g_free((void *) messages->success); g_free((void *) messages->failure); g_free((void *) messages); } } void method_call_log(GDBusProxy *proxy, GAsyncResult *res, const gchar *message) { GVariant *ret; GError *err; err = NULL; ret = g_dbus_proxy_call_finish(proxy, res, &err); if (ret) { g_variant_unref(ret); } else { g_printerr(message, err->message); g_error_free(err); } } /* * Return true if adapter 0 < adapter 1 and false if adapter 1 < adapter 0. */ gboolean adapter_sort(GDBusProxy *proxy0, GDBusProxy *proxy1) { const gchar *path0, *path1; guint adapter0, adapter1; static const gchar *adapter_path_fmt = "%u"; if (proxy0 == NULL || proxy1 == NULL) { return FALSE; } path0 = g_dbus_proxy_get_object_path(proxy0) + IWD_PATH_PREFIX_LENGTH; path1 = g_dbus_proxy_get_object_path(proxy1) + IWD_PATH_PREFIX_LENGTH; sscanf(path0, adapter_path_fmt, &adapter0); sscanf(path1, adapter_path_fmt, &adapter1); if (adapter0 < adapter1) { return TRUE; } return FALSE; } /* * Return true if device 0 < device 1 and false if device 1 < device 0. */ /* * TODO: * Use this to sort wlan* buttons within a device. */ gboolean device_sort(GDBusProxy *proxy0, GDBusProxy *proxy1) { const gchar *path0, *path1; guint adapter0, adapter1; guint dev0, dev1; static const gchar *device_path_fmt = "%u/%u"; if (proxy0 == NULL || proxy1 == NULL) { return FALSE; } path0 = g_dbus_proxy_get_object_path(proxy0) + IWD_PATH_PREFIX_LENGTH; path1 = g_dbus_proxy_get_object_path(proxy1) + IWD_PATH_PREFIX_LENGTH; sscanf(path0, device_path_fmt, &adapter0, &dev0); sscanf(path1, device_path_fmt, &adapter1, &dev1); if (adapter0 < adapter1) { return TRUE; } if (adapter1 < adapter0) { return FALSE; } /* * We know now that adapter0 == adapter1. */ if (dev0 < dev1) { return TRUE; } return FALSE; } void set_remote_property_callback(GDBusProxy *proxy, GAsyncResult *res, FailureClosure *failure) { GVariant *ret; GError *err; err = NULL; ret = g_dbus_proxy_call_finish(proxy, res, &err); if (ret) { g_variant_unref(ret); } else { g_printerr("Failed to set remote property '%s': %s\n", failure->property, err->message); g_error_free(err); failure->callback(failure->data); } g_free(failure); } /* * When a property is updated remotely, iwgtk responds by updating a widget. This widget * state change triggers a signal which causes set_remote_property() to be called. The * equality check in this function prevents the property change from being volleyed back * to iwd. This is kind of a hack; it would be more elegant if set_remote_property() were * only called for user-initiated state changes. */ void set_remote_property(GDBusProxy *proxy, const gchar *property, GVariant *value, SetFunction failure_callback, gpointer failure_data) { GVariant *value_cached; value_cached = g_dbus_proxy_get_cached_property(proxy, property); if (!g_variant_equal(value, value_cached)) { FailureClosure *failure_closure; failure_closure = g_malloc(sizeof(FailureClosure)); failure_closure->callback = failure_callback; failure_closure->data = failure_data; failure_closure->property = property; g_dbus_proxy_call( proxy, "org.freedesktop.DBus.Properties.Set", g_variant_new("(ssv)", g_dbus_proxy_get_interface_name(proxy), property, value), G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) set_remote_property_callback, failure_closure); } else { g_variant_unref(value); } g_variant_unref(value_cached); } GVariant* lookup_property(GVariant *dictionary, const gchar *property) { GVariantIter iter; const gchar *key; GVariant *value; g_variant_iter_init(&iter, dictionary); while (g_variant_iter_next(&iter, "{&sv}", &key, &value)) { if (strcmp(property, key) == 0) { return value; } g_variant_unref(value); } return NULL; } void send_notification(const gchar *text) { if (~global.state & NOTIFICATIONS_DISABLE) { GNotification *notification; notification = g_notification_new(PACKAGE); g_notification_set_body(notification, text); g_notification_set_priority(notification, G_NOTIFICATION_PRIORITY_NORMAL); g_application_send_notification(G_APPLICATION(global.application), NULL, notification); g_object_unref(notification); } } void grid_column_set_alignment(GtkWidget *grid, int col, GtkAlign align) { GtkWidget *cell; int i; i = 0; while ((cell = gtk_grid_get_child_at(GTK_GRID(grid), col, i))) { gtk_widget_set_halign(cell, align); i ++; } } GtkWidget* label_with_spinner(const gchar *text) { GtkWidget *box, *spinner; spinner = gtk_spinner_new(); gtk_spinner_start(GTK_SPINNER(spinner)); box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); gtk_box_append(GTK_BOX(box), spinner); gtk_box_append(GTK_BOX(box), gtk_label_new(text)); gtk_widget_set_halign(box, GTK_ALIGN_CENTER); gtk_widget_set_valign(box, GTK_ALIGN_CENTER); return box; } GtkWidget* new_label_bold(const gchar *text) { PangoAttrList *attr_list; GtkWidget *label; attr_list = pango_attr_list_new(); pango_attr_list_insert(attr_list, pango_attr_weight_new(PANGO_WEIGHT_BOLD)); label = gtk_label_new(text); gtk_label_set_attributes(GTK_LABEL(label), attr_list); pango_attr_list_unref(attr_list); return label; } GtkWidget* new_label_gray(const gchar *text) { PangoAttrList *attr_list; GtkWidget *label; attr_list = pango_attr_list_new(); pango_attr_list_insert(attr_list, pango_attr_weight_new(PANGO_WEIGHT_SEMILIGHT)); pango_attr_list_insert(attr_list, pango_attr_foreground_new(RGB_MAX/2, RGB_MAX/2, RGB_MAX/2)); label = gtk_label_new(text); gtk_label_set_attributes(GTK_LABEL(label), attr_list); pango_attr_list_unref(attr_list); return label; } iwgtk-0.9/src/utilities.h000066400000000000000000000036451435506763100155040ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_UTILITIES_H #define _IWGTK_UTILITIES_H #define RGB_MAX 65535 typedef void (*SetFunction) (gpointer data); typedef struct FailureClosure_s FailureClosure; struct FailureClosure_s { SetFunction callback; gpointer data; const gchar *property; }; const gchar* get_error_detail(GError *err, const ErrorMessage *error_table); void method_call_notify(GDBusProxy *proxy, GAsyncResult *res, CallbackMessages *data); void method_call_log(GDBusProxy *proxy, GAsyncResult *res, const gchar *message); gboolean adapter_sort(GDBusProxy *proxy0, GDBusProxy *proxy1); gboolean device_sort(GDBusProxy *proxy0, GDBusProxy *proxy1); void set_remote_property_callback(GDBusProxy *proxy, GAsyncResult *res, FailureClosure *failure); void set_remote_property(GDBusProxy *proxy, const gchar *property, GVariant *value, SetFunction failure_callback, gpointer failure_data); GVariant* lookup_property(GVariant *dictionary, const gchar *property); void send_notification(const gchar *text); void grid_column_set_alignment(GtkWidget *grid, int col, GtkAlign align); GtkWidget* label_with_spinner(const gchar *text); GtkWidget* new_label_bold(const gchar *text); GtkWidget* new_label_gray(const gchar *text); #endif iwgtk-0.9/src/window.c000066400000000000000000000324011435506763100147630ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" ObjectMethods object_methods[] = { {IWD_IFACE_KNOWN_NETWORK, (ConstructorFunction) known_network_add, (DestructorFunction) known_network_remove}, {IWD_IFACE_ADAPTER, (ConstructorFunction) adapter_add, (DestructorFunction) adapter_remove}, {IWD_IFACE_DEVICE, (ConstructorFunction) device_add, (DestructorFunction) device_remove}, {IWD_IFACE_STATION, (ConstructorFunction) station_add, (DestructorFunction) station_remove}, {IWD_IFACE_AP, (ConstructorFunction) ap_add, (DestructorFunction) ap_remove}, {IWD_IFACE_AD_HOC, (ConstructorFunction) adhoc_add, (DestructorFunction) adhoc_remove}, {IWD_IFACE_DPP, (ConstructorFunction) dpp_add, (DestructorFunction) dpp_remove}, {IWD_IFACE_WPS, (ConstructorFunction) wps_add, (DestructorFunction) wps_remove}, {IWD_IFACE_DIAGNOSTIC, (ConstructorFunction) diagnostic_add, (DestructorFunction) diagnostic_remove} }; CoupleMethods couple_methods[] = { {(BindFunction) bind_adapter_device, (UnbindFunction) unbind_adapter_device}, {(BindFunction) bind_device_station, (UnbindFunction) unbind_device_station}, {(BindFunction) bind_device_ap, (UnbindFunction) unbind_device_ap}, {(BindFunction) bind_device_adhoc, (UnbindFunction) unbind_device_adhoc}, {(BindFunction) bind_station_dpp, (UnbindFunction) unbind_station_dpp}, {(BindFunction) bind_station_wps, (UnbindFunction) unbind_station_wps}, {(BindFunction) bind_device_diagnostic, (UnbindFunction) unbind_device_diagnostic} }; void window_launch() { Window *window; if (global.window) { gtk_window_present(GTK_WINDOW(global.window->window)); return; } if (!global.manager) { if (global.state & IWD_DOWN) { g_printerr("Cannot launch iwgtk window: iwd is not running\n"); } else { global.state |= WINDOW_LAUNCH_PENDING; g_application_hold(G_APPLICATION(global.application)); } return; } window = g_malloc(sizeof(Window)); global.window = window; memset(window->objects, 0, sizeof(void *) * n_object_types); memset(window->couples, 0, sizeof(void *) * n_couple_types); window->window = gtk_application_window_new(global.application); gtk_window_set_title(GTK_WINDOW(window->window), PACKAGE); gtk_window_set_default_size(GTK_WINDOW(window->window), global.width, global.height); gtk_window_set_icon_name(GTK_WINDOW(window->window), PACKAGE); window->master = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); g_object_ref_sink(window->master); gtk_widget_set_size_request(window->master, 440, 300); gtk_window_set_child(GTK_WINDOW(window->window), window->master); window->known_network_button = gtk_toggle_button_new_with_label(_("Known networks")); g_object_ref_sink(window->known_network_button); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(window->known_network_button), TRUE); g_signal_connect(window->known_network_button, "toggled", G_CALLBACK(known_network_table_show), window); { GtkWidget *header; GtkWidget *known_network_button_vbox; header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); gtk_box_append(GTK_BOX(window->master), header); gtk_widget_set_margin_start(header, 5); gtk_widget_set_margin_end(header, 5); gtk_widget_set_margin_top(header, 5); window->adapter_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); g_object_ref_sink(window->adapter_hbox); gtk_widget_set_hexpand(window->adapter_hbox, TRUE); gtk_box_append(GTK_BOX(header), window->adapter_hbox); known_network_button_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_box_append(GTK_BOX(header), known_network_button_vbox); { GtkWidget *close_button; close_button = gtk_button_new_with_label(_("Close")); g_signal_connect_swapped(close_button, "clicked", G_CALLBACK(gtk_window_destroy), window->window); gtk_box_append(GTK_BOX(known_network_button_vbox), close_button); } gtk_box_append(GTK_BOX(known_network_button_vbox), window->known_network_button); } gtk_box_append(GTK_BOX(window->master), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)); window->main = gtk_scrolled_window_new(); g_object_ref_sink(window->main); gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(window->main), TRUE); gtk_box_append(GTK_BOX(window->master), window->main); window->known_network_table = gtk_grid_new(); g_object_ref_sink(window->known_network_table); gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(window->main), window->known_network_table); gtk_widget_set_margin_start(window->known_network_table, 5); gtk_widget_set_margin_end(window->known_network_table, 5); gtk_widget_set_margin_bottom(window->known_network_table, 5); gtk_grid_set_column_spacing(GTK_GRID(window->known_network_table), 10); gtk_grid_set_row_spacing(GTK_GRID(window->known_network_table), 10); add_all_dbus_objects(window); g_signal_connect_swapped(window->window, "destroy", G_CALLBACK(window_rm), window); gtk_widget_show(window->window); } void window_rm(Window *window) { for (int i = 0; i < n_object_types; i ++) { while (window->objects[i] != NULL) { object_methods[i].rm(window, window->objects[i]->data); { ObjectList *rm; rm = window->objects[i]; window->objects[i] = window->objects[i]->next; g_free(rm); } } } g_object_unref(window->master); g_object_unref(window->adapter_hbox); g_object_unref(window->main); g_object_unref(window->known_network_button); g_object_unref(window->known_network_table); g_free(window); global.window = NULL; } void known_network_table_show(GtkToggleButton *button, Window *window) { if (gtk_toggle_button_get_active(button)) { gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(window->main), window->known_network_table); } } /* * window=NULL implies that all interfaces are to be added to the indicator list, rather * than to a window. */ void add_all_dbus_objects(Window *window) { GList *object_list, *i; object_list = g_dbus_object_manager_get_objects(global.manager); for (i = object_list; i != NULL; i = i->next) { GDBusObject *object; object = (GDBusObject *) i->data; object_iterate_interfaces(NULL, object, window, interface_add); g_object_unref(object); } g_list_free(object_list); } void object_add(GDBusObjectManager *manager, GDBusObject *object, Window *window) { object_iterate_interfaces(manager, object, window, interface_add); } void object_rm(GDBusObjectManager *manager, GDBusObject *object, Window *window) { object_iterate_interfaces(manager, object, window, interface_rm); } void object_iterate_interfaces(GDBusObjectManager *manager, GDBusObject *object, Window *window, ObjectIterFunction method) { GList *interface_list, *j; interface_list = g_dbus_object_get_interfaces(object); for (j = interface_list; j != NULL; j = j->next) { GDBusProxy *proxy; proxy = (GDBusProxy *) j->data; method(manager, object, proxy, window); g_object_unref(proxy); } g_list_free(interface_list); } void interface_add(GDBusObjectManager *manager, GDBusObject *object, GDBusProxy *proxy, Window *window) { const gchar *name; name = g_dbus_proxy_get_interface_name(proxy); for (int i = 0; i < n_object_types; i ++) { if (strcmp(name, object_methods[i].interface) == 0) { if (window != NULL) { // This function has been invoked for a particular window. window_add_object(object, proxy, window, i); } else { /* * If window=NULL and manager=NULL, then we are only interested in * initializing the indicators. */ if (manager != NULL && global.window != NULL) { window_add_object(object, proxy, global.window, i); } /* * There are two scenarios when we want to run this: * (1) due to a new object/interface being added (window=NULL, manager!=NULL) * (2) right after the manager is initialized (window=NULL, manager=NULL */ if (global.state & INDICATOR_DAEMON) { Indicator **indicator; indicator = &global.indicators; while (*indicator != NULL) { GDBusObject *device_object; device_object = g_dbus_interface_get_object(G_DBUS_INTERFACE((*indicator)->device_proxy)); if (object == device_object) { IndicatorSetter indicator_set_mode; indicator_set_mode = NULL; switch (i) { case OBJECT_STATION: indicator_station_init_signal_agent(*indicator, proxy); indicator_set_mode = indicator_set_station; break; case OBJECT_ACCESS_POINT: indicator_set_mode = indicator_set_ap; break; case OBJECT_ADHOC: indicator_set_mode = indicator_set_adhoc; break; } if (indicator_set_mode != NULL) { (*indicator)->proxy = proxy; (*indicator)->update_mode_handler = g_signal_connect_swapped(proxy, "g-properties-changed", G_CALLBACK(indicator_set_mode), *indicator); indicator_set_mode(*indicator); } return; } indicator = &(*indicator)->next; } if (i == OBJECT_DEVICE) { *indicator = indicator_new(proxy); } } } break; } } } void object_list_append(ObjectList **list, GDBusObject *object, gpointer data) { while (*list != NULL) { list = &(*list)->next; } *list = g_malloc(sizeof(ObjectList)); (*list)->object = object; (*list)->data = data; (*list)->next = NULL; } void window_add_object(GDBusObject *object, GDBusProxy *proxy, Window *window, int type) { gpointer data; data = object_methods[type].new(window, object, proxy); object_list_append(&window->objects[type], object, data); } void interface_rm(GDBusObjectManager *manager, GDBusObject *object, GDBusProxy *proxy, Window *window) { const gchar *name; name = g_dbus_proxy_get_interface_name(proxy); for (int i = 0; i < n_object_types; i ++) { if (!strcmp(name, object_methods[i].interface)) { if (global.window != NULL) { ObjectList **list; list = &global.window->objects[i]; while ((*list)->object != object) { list = &(*list)->next; } object_methods[i].rm(global.window, (*list)->data); { ObjectList *rm; rm = *list; *list = (*list)->next; g_free(rm); } } if (global.state & INDICATOR_DAEMON) { Indicator **indicator; indicator = &global.indicators; while (*indicator != NULL) { GDBusObject *device_object; device_object = g_dbus_interface_get_object(G_DBUS_INTERFACE((*indicator)->device_proxy)); if (device_object == object) { if (proxy == (*indicator)->device_proxy) { // The indicator's device has been taken down; delete the indicator Indicator *rm; rm = *indicator; *indicator = (*indicator)->next; indicator_rm(rm); } else if (proxy == (*indicator)->proxy) { // The indicator's mode has changed, or it has been powered down g_signal_handler_disconnect(proxy, (*indicator)->update_mode_handler); (*indicator)->update_mode_handler = 0; } return; } indicator = &(*indicator)->next; } } break; } } } void couple_register(Window *window, CoupleType couple_type, int this, gpointer data, GDBusObject *object) { CoupleList **list; list = &window->couples[couple_type]; while (*list != NULL) { if ( (*list)->object == object) { if ( (*list)->data[this] == NULL) { (*list)->data[this] = data; } else { CoupleList *new_entry; new_entry = g_malloc(sizeof(CoupleList)); new_entry->object = object; new_entry->data[this] = data; new_entry->data[1 - this] = (*list)->data[1 - this]; new_entry->next = (*list)->next; *list = new_entry; } couple_methods[couple_type].bind( (*list)->data[0], (*list)->data[1] ); return; } else { list = &(*list)->next; } } { CoupleList *new_entry; new_entry = g_malloc(sizeof(CoupleList)); new_entry->object = object; new_entry->data[this] = data; new_entry->data[1 - this] = NULL; new_entry->next = NULL; *list = new_entry; } } void couple_unregister(Window *window, CoupleType couple_type, int this, gpointer data) { CoupleList **list; list = &window->couples[couple_type]; while (*list != NULL) { if ( (*list)->data[this] == data ) { if ( (*list)->data[1 - this] == NULL) { // Couple node is empty - delete it CoupleList *rm; rm = *list; *list = (*list)->next; g_free(rm); } else { // Unbind the couple couple_methods[couple_type].unbind( (*list)->data[0], (*list)->data[1] ); (*list)->data[this] = NULL; list = &(*list)->next; } } else { list = &(*list)->next; } } } iwgtk-0.9/src/window.h000066400000000000000000000067741435506763100150060ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _WINDOW_H #define _WINDOW_H #define n_object_types 9 typedef enum { OBJECT_KNOWN_NETWORK, OBJECT_ADAPTER, OBJECT_DEVICE, OBJECT_STATION, OBJECT_ACCESS_POINT, OBJECT_ADHOC, OBJECT_DPP, OBJECT_WPS, OBJECT_DIAGNOSTIC } ObjectType; #define n_couple_types 7 typedef enum { ADAPTER_DEVICE, DEVICE_STATION, DEVICE_AP, DEVICE_ADHOC, STATION_DPP, STATION_WPS, DEVICE_DIAGNOSTIC } CoupleType; typedef struct ObjectList_s ObjectList; typedef struct CoupleList_s CoupleList; typedef struct Window_s Window; typedef struct ObjectMethods_s ObjectMethods; typedef struct CoupleMethods_s CoupleMethods; struct ObjectList_s { GDBusObject *object; gpointer data; ObjectList *next; }; struct CoupleList_s { GDBusObject *object; gpointer data[2]; CoupleList *next; }; struct Window_s { GtkWidget *window; GtkWidget *master; GtkWidget *adapter_hbox; GtkWidget *main; GtkWidget *known_network_button; GtkWidget *known_network_table; ObjectList *objects[n_object_types]; CoupleList *couples[n_couple_types]; }; typedef gpointer (*ConstructorFunction) (Window *window, GDBusObject *object, GDBusProxy *proxy); typedef void (*DestructorFunction) (Window *window, gpointer data); struct ObjectMethods_s { const gchar *interface; ConstructorFunction new; DestructorFunction rm; }; typedef void (*BindFunction) (gpointer A, gpointer B); typedef void (*UnbindFunction) (gpointer A, gpointer B); struct CoupleMethods_s { BindFunction bind; UnbindFunction unbind; }; extern ObjectMethods object_methods[]; extern CoupleMethods couple_methods[]; typedef void (*ObjectIterFunction) (GDBusObjectManager *manager, GDBusObject *object, GDBusProxy *proxy, Window *window); void window_launch(); void window_rm(Window *window); void known_network_table_show(GtkToggleButton *button, Window *window); void add_all_dbus_objects(Window *window); void object_add(GDBusObjectManager *manager, GDBusObject *object, Window *window); void object_rm(GDBusObjectManager *manager, GDBusObject *object, Window *window); void object_iterate_interfaces(GDBusObjectManager *manager, GDBusObject *object, Window *window, ObjectIterFunction method); void interface_add(GDBusObjectManager *manager, GDBusObject *object, GDBusProxy *proxy, Window *window); void object_list_append(ObjectList **list, GDBusObject *object, gpointer data); void window_add_object(GDBusObject *object, GDBusProxy *proxy, Window *window, int type); void interface_rm(GDBusObjectManager *manager, GDBusObject *object, GDBusProxy *proxy, Window *window); void couple_register(Window *window, CoupleType couple_type, int this, gpointer data, GDBusObject *object); void couple_unregister(Window *window, CoupleType couple_type, int this, gpointer data); #endif iwgtk-0.9/src/wps.c000066400000000000000000000143551435506763100142750ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #include "iwgtk.h" const ErrorMessage detailed_errors_wps[] = { {IWD_ERROR_INVALID_FORMAT, N_("Invalid PIN")}, {IWD_ERROR_WSC_SESSION_OVERLAP, N_("Multiple APs found")}, {IWD_ERROR_WSC_TIME_EXPIRED, N_("No AP found in PIN mode")}, {IWD_ERROR_WSC_WALK_TIME_EXPIRED, N_("No AP found in push-button mode")}, {0, NULL} }; static const CallbackMessages wps_messages = { N_("WPS enrollment succeeded"), N_("WPS enrollment failed"), detailed_errors_wps, FALSE }; void wps_set_pushbutton(WPS *wps) { if (wps->handler_pushbutton != 0) { g_signal_handler_disconnect(wps->pushbutton, wps->handler_pushbutton); } gtk_button_set_label(GTK_BUTTON(wps->pushbutton), _("Push button")); wps->handler_pushbutton = g_signal_connect_swapped(wps->pushbutton, "clicked", G_CALLBACK(wps_connect_pushbutton), wps); } void wps_set_pin(WPS *wps) { if (wps->handler_pin != 0) { g_signal_handler_disconnect(wps->pin, wps->handler_pin); } gtk_button_set_label(GTK_BUTTON(wps->pin), _("PIN")); wps->handler_pin = g_signal_connect_swapped(wps->pin, "clicked", G_CALLBACK(wps_connect_pin_dialog), wps); } void wps_set_pushbutton_cancel(WPS *wps) { g_signal_handler_disconnect(wps->pushbutton, wps->handler_pushbutton); gtk_button_set_child(GTK_BUTTON(wps->pushbutton), label_with_spinner(_("Cancel"))); wps->handler_pushbutton = g_signal_connect_swapped(wps->pushbutton, "clicked", G_CALLBACK(wps_cancel), wps); } void wps_set_pin_cancel(WPS *wps) { g_signal_handler_disconnect(wps->pin, wps->handler_pin); gtk_button_set_child(GTK_BUTTON(wps->pin), label_with_spinner(_("Cancel"))); wps->handler_pin = g_signal_connect_swapped(wps->pin, "clicked", G_CALLBACK(wps_cancel), wps); } void wps_pushbutton_callback(GDBusProxy *proxy, GAsyncResult *res, WPS *wps) { wps_set_pushbutton(wps); method_call_notify(proxy, res, (CallbackMessages *) &wps_messages); } void wps_pin_callback(GDBusProxy *proxy, GAsyncResult *res, WPS *wps) { wps_set_pin(wps); method_call_notify(proxy, res, (CallbackMessages *) &wps_messages); } void wps_connect_pin_dialog(WPS *wps) { WPSDialog *wps_dialog; GtkWidget *table, *buttons; wps_dialog = g_malloc(sizeof(WPSDialog)); wps_dialog->wps = wps; wps_dialog->window = gtk_window_new(); gtk_window_set_title(GTK_WINDOW(wps_dialog->window), _("Provision network via WPS")); wps_dialog->pin = gtk_password_entry_new(); gtk_password_entry_set_show_peek_icon(GTK_PASSWORD_ENTRY(wps_dialog->pin), TRUE); buttons = dialog_buttons(wps_dialog, (SubmitCallback) wps_pin_dialog_submit, wps_dialog->window); table = gtk_grid_new(); gtk_window_set_child(GTK_WINDOW(wps_dialog->window), table); gtk_grid_attach(GTK_GRID(table), gtk_label_new(_("PIN: ")), 0, 0, 1, 1); gtk_grid_attach(GTK_GRID(table), wps_dialog->pin, 1, 0, 1, 1); gtk_grid_attach(GTK_GRID(table), buttons, 1, 1, 1, 1); grid_column_set_alignment(table, 0, GTK_ALIGN_END); grid_column_set_alignment(table, 1, GTK_ALIGN_START); g_signal_connect_swapped(wps_dialog->window, "destroy", G_CALLBACK(g_free), wps_dialog); gtk_widget_show(wps_dialog->window); } void wps_pin_dialog_submit(WPSDialog *wps_dialog) { const gchar *pin; pin = gtk_editable_get_text(GTK_EDITABLE(wps_dialog->pin)); if (*pin == '\0') { return; } g_dbus_proxy_call( wps_dialog->wps->proxy, "StartPin", g_variant_new("(s)", pin), G_DBUS_CALL_FLAGS_NONE, G_MAXINT, NULL, (GAsyncReadyCallback) wps_pin_callback, wps_dialog->wps); wps_set_pin_cancel(wps_dialog->wps); gtk_window_destroy(GTK_WINDOW(wps_dialog->window)); } void wps_connect_pushbutton(WPS *wps) { g_dbus_proxy_call( wps->proxy, "PushButton", NULL, G_DBUS_CALL_FLAGS_NONE, G_MAXINT, NULL, (GAsyncReadyCallback) wps_pushbutton_callback, wps); wps_set_pushbutton_cancel(wps); } void wps_cancel(WPS *wps, GtkWidget *button) { g_dbus_proxy_call( wps->proxy, "Cancel", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, (GAsyncReadyCallback) method_call_log, "Failed to cancel WPS enrollment: %s\n"); if (button == wps->pushbutton) { wps_set_pushbutton(wps); } else { wps_set_pin(wps); } } WPS* wps_add(Window *window, GDBusObject *object, GDBusProxy *proxy) { WPS *wps; wps = g_malloc(sizeof(WPS)); wps->proxy = proxy; wps->handler_pushbutton = 0; wps->handler_pin = 0; wps->label = new_label_bold(_("WPS")); g_object_ref_sink(wps->label); gtk_widget_set_margin_top(wps->label, 10); wps->pushbutton = gtk_button_new(); g_object_ref_sink(wps->pushbutton); wps_set_pushbutton(wps); wps->pin = gtk_button_new(); g_object_ref_sink(wps->pin); wps_set_pin(wps); couple_register(window, STATION_WPS, 1, wps, object); return wps; } void wps_remove(Window *window, WPS *wps) { couple_unregister(window, STATION_WPS, 1, wps); g_object_unref(wps->label); g_object_unref(wps->pushbutton); g_object_unref(wps->pin); g_free(wps); } void bind_station_wps(Station *station, WPS *wps) { wps->station = station; gtk_box_append(GTK_BOX(station->provision_vbox), wps->label); gtk_box_append(GTK_BOX(station->provision_vbox), wps->pushbutton); gtk_box_append(GTK_BOX(station->provision_vbox), wps->pin); } void unbind_station_wps(Station *station, WPS *wps) { gtk_box_remove(GTK_BOX(station->provision_vbox), wps->label); gtk_box_remove(GTK_BOX(station->provision_vbox), wps->pushbutton); gtk_box_remove(GTK_BOX(station->provision_vbox), wps->pin); } iwgtk-0.9/src/wps.h000066400000000000000000000034151435506763100142750ustar00rootroot00000000000000/* * Copyright 2020-2023 Jesse Lentz and contributors * * This file is part of iwgtk. * * iwgtk 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. * * iwgtk 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 iwgtk. If not, see . */ #ifndef _IWGTK_WPS_H #define _IWGTK_WPS_H typedef struct WPS_s WPS; typedef struct WPSDialog_s WPSDialog; struct WPS_s { GDBusProxy *proxy; Station *station; gulong handler_pushbutton; gulong handler_pin; GtkWidget *label; GtkWidget *pushbutton; GtkWidget *pin; }; struct WPSDialog_s { WPS *wps; GtkWidget *window; GtkWidget *pin; }; void wps_set_pushbutton(WPS *wps); void wps_set_pin(WPS *wps); void wps_set_pushbutton_cancel(WPS *wps); void wps_set_pin_cancel(WPS *wps); void wps_pushbutton_callback(GDBusProxy *proxy, GAsyncResult *res, WPS *wps); void wps_pin_callback(GDBusProxy *proxy, GAsyncResult *res, WPS *wps); void wps_connect_pin_dialog(WPS *wps); void wps_pin_dialog_submit(WPSDialog *wps_dialog); void wps_connect_pushbutton(WPS *wps); void wps_cancel(WPS *wps, GtkWidget *button); WPS* wps_add(Window *window, GDBusObject *object, GDBusProxy *proxy); void wps_remove(Window *window, WPS *wps); void bind_station_wps(Station *station, WPS *wps); void unbind_station_wps(Station *station, WPS *wps); #endif