diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/COPYING @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf27d64 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# casilda + +A simple wayland compositor widget for Gtk 4. +Originally created for [Cambalache](https://gitlab.gnome.org/jpu/cambalache) +workspace, it is useful for embedding other processes windows. + +The name follows the tradition of naming Wayland related projects after towns. +My hometown in Santa Fe, Argentina. + +## License + +Casilda is distributed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html), +version 2.1 (LGPL) as described in the COPYING file. + +## Source code + +Source code lives on GNOME gitlab [here](https://gitlab.gnome.org/jpu/casilda) + +`git clone https://gitlab.gnome.org/jpu/casilda.git` + +## Dependencies + +* [Meson](http://mesonbuild.com) build system +* [GTK](http://www.gtk.org) Version 4 +* wlroots - Library to create Wayland compositors + + +## Manual installation + +This is a regular meson package and can be installed the usual way. + +``` +# Configure project in _build directory +meson setup --wipe --prefix=~/.local _build . + +# Build and install in ~/.local +ninja -C _build install +``` + +## Contributing + +If you are interested in contributing you can open an issue [here](https://gitlab.gnome.org/jpu/casilda/-/issues) +and/or a merge request [here](https://gitlab.gnome.org/jpu/casilda/-/merge_requests) + +## Contact + +You can hang with us and ask us questions on Cambalache Matrix at #cambalache:gnome.org + +[Matrix](https://matrix.to/#/#cambalache:gnome.org) diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..3fab1cc --- /dev/null +++ b/meson.build @@ -0,0 +1,83 @@ +project('casilda', 'c', + version: '0.1.0', + meson_version: '>= 0.62.0', + default_options: [ 'warning_level=2', 'werror=false', 'c_std=gnu11', ], +) + +env = environment() + +cc = meson.get_compiler('c') + +config_h = configuration_data() +config_h.set_quoted('PACKAGE_VERSION', meson.project_version()) +configure_file(output: 'config.h', configuration: config_h) +add_project_arguments(['-I' + meson.project_build_root()], language: 'c') + +project_c_args = [] +test_c_args = [ + '-Wcast-align', + '-Wdeclaration-after-statement', + '-Werror=address', + '-Werror=array-bounds', + '-Werror=empty-body', + '-Werror=implicit', + '-Werror=implicit-function-declaration', + '-Werror=incompatible-pointer-types', + '-Werror=init-self', + '-Werror=int-conversion', + '-Werror=int-to-pointer-cast', + '-Werror=main', + '-Werror=misleading-indentation', + '-Werror=missing-braces', + '-Werror=missing-include-dirs', + '-Werror=nonnull', + '-Werror=overflow', + '-Werror=parenthesis', + '-Werror=pointer-arith', + '-Werror=pointer-to-int-cast', + '-Werror=redundant-decls', + '-Werror=return-type', + '-Werror=sequence-point', + '-Werror=shadow', + '-Werror=strict-prototypes', + '-Werror=trigraphs', + '-Werror=undef', + '-Werror=write-strings', + '-Wformat-nonliteral', + '-Wignored-qualifiers', + '-Wimplicit-function-declaration', + '-Wlogical-op', + '-Wmissing-declarations', + '-Wmissing-format-attribute', + '-Wmissing-include-dirs', + '-Wmissing-noreturn', + '-Wnested-externs', + '-Wno-cast-function-type', + '-Wno-dangling-pointer', + '-Wno-missing-field-initializers', + '-Wno-sign-compare', + '-Wno-unused-parameter', + '-Wold-style-definition', + '-Wpointer-arith', + '-Wredundant-decls', + '-Wstrict-prototypes', + '-Wswitch-default', + '-Wswitch-enum', + '-Wundef', + '-Wuninitialized', + '-Wunused', + '-fno-strict-aliasing', + ['-Werror=format-security', '-Werror=format=2'], +] +if get_option('buildtype') != 'plain' + test_c_args += '-fstack-protector-strong' +endif +foreach arg: test_c_args + if cc.has_multi_arguments(arg) + project_c_args += arg + endif +endforeach +add_project_arguments(project_c_args, language: 'c') + +subdir('src') + diff --git a/src/casilda-version.h.in b/src/casilda-version.h.in new file mode 100644 index 0000000..b38d0e5 --- /dev/null +++ b/src/casilda-version.h.in @@ -0,0 +1,97 @@ +/* casilda-version.h.in + * + * Copyright 2024 Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#if !defined(CASILDA_INSIDE) && !defined(CASILDA_COMPILATION) +# error "Only can be included directly." +#endif + +/** + * SECTION:casildaversion + * @short_description: casilda version checking + * + * casilda provides macros to check the version of the library + * at compile-time + */ + +/** + * CASILDA_MAJOR_VERSION: + * + * casilda major version component (e.g. 1 if %CASILDA_VERSION is 1.2.3) + */ +#define CASILDA_MAJOR_VERSION (@MAJOR_VERSION@) + +/** + * CASILDA_MINOR_VERSION: + * + * casilda minor version component (e.g. 2 if %CASILDA_VERSION is 1.2.3) + */ +#define CASILDA_MINOR_VERSION (@MINOR_VERSION@) + +/** + * CASILDA_MICRO_VERSION: + * + * casilda micro version component (e.g. 3 if %CASILDA_VERSION is 1.2.3) + */ +#define CASILDA_MICRO_VERSION (@MICRO_VERSION@) + +/** + * CASILDA_VERSION + * + * casilda version. + */ +#define CASILDA_VERSION (@VERSION@) + +/** + * CASILDA_VERSION_S: + * + * casilda version, encoded as a string, useful for printing and + * concatenation. + */ +#define CASILDA_VERSION_S "@VERSION@" + +#define CASILDA_ENCODE_VERSION(major,minor,micro) \ + ((major) << 24 | (minor) << 16 | (micro) << 8) + +/** + * CASILDA_VERSION_HEX: + * + * casilda version, encoded as an hexadecimal number, useful for + * integer comparisons. + */ +#define CASILDA_VERSION_HEX \ + (CASILDA_ENCODE_VERSION (CASILDA_MAJOR_VERSION, CASILDA_MINOR_VERSION, CASILDA_MICRO_VERSION)) + +/** + * CASILDA_CHECK_VERSION: + * @major: required major version + * @minor: required minor version + * @micro: required micro version + * + * Compile-time version checking. Evaluates to %TRUE if the version + * of casilda is greater than the required one. + */ +#define CASILDA_CHECK_VERSION(major,minor,micro) \ + (CASILDA_MAJOR_VERSION > (major) || \ + (CASILDA_MAJOR_VERSION == (major) && CASILDA_MINOR_VERSION > (minor)) || \ + (CASILDA_MAJOR_VERSION == (major) && CASILDA_MINOR_VERSION == (minor) && \ + CASILDA_MICRO_VERSION >= (micro))) diff --git a/src/casilda.h b/src/casilda.h index 807c906..ab726b5 100644 --- a/src/casilda.h +++ b/src/casilda.h @@ -1,14 +1,13 @@ -/* - * Cambalache shared library +/* casilda.h * - * Copyright (C) 2024 Juan Pablo Ugarte + * Copyright 2024 Juan Pablo Ugarte * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License. + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. * - * library is distributed in the hope that it will be useful, + * This library 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 * Lesser General Public License for more details. @@ -17,10 +16,19 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * - * Authors: - * Juan Pablo Ugarte + * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "cmb_compositor.h" +#include + +G_BEGIN_DECLS + +#define CASILDA_INSIDE +# include "casilda-version.h" +#undef CASILDA_INSIDE + +#include "casilda_compositor.h" + +G_END_DECLS diff --git a/src/casilda_compositor.c b/src/casilda_compositor.c new file mode 100644 index 0000000..82c595a --- /dev/null +++ b/src/casilda_compositor.c @@ -0,0 +1,1868 @@ +/* + * Cambalache Wayland Compositor Widget + * + * Copyright (C) 2024 Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Juan Pablo Ugarte + */ + +#define WLR_USE_UNSTABLE 1 + +#include "casilda_compositor.h" +#include "casilda_wayland_source.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xdg-shell-protocol.h" +#include + +#ifdef GDK_WINDOWING_WAYLAND +#include +#endif + +#if defined(GDK_WINDOWING_X11) && defined(HAVE_X11_XCB) +#include +#include +#include +#endif + +/* Auto free helpers */ +typedef struct wlr_texture WlrTexture; +typedef struct wlr_output_state WlrOutputState; +G_DEFINE_AUTOPTR_CLEANUP_FUNC(WlrTexture, wlr_texture_destroy); +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(WlrOutputState, wlr_output_state_finish); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(cairo_surface_t, cairo_surface_destroy); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(pixman_image_t, pixman_image_unref); + +typedef enum +{ + casilda_POINTER_MODE_FOWARD, + casilda_POINTER_MODE_RESIZE, + casilda_POINTER_MODE_MOVE, +} CasildaPointerMode; + +typedef struct CasildaCompositorToplevel CasildaCompositorToplevel; + +typedef struct +{ + GtkWidget *widget; + + /* wayland main loop integration */ + GSource *wl_source; + + /* Event controllers */ + GtkEventController *motion_controller; + GtkEventController *scroll_controller; + GtkEventController *key_controller; + GtkGesture *click_gesture; + + /* Frame Clock state */ + GdkFrameClock *frame_clock; + gboolean frame_clock_updating; + gulong frame_clock_source; + guint defered_present_event_source; + struct wlr_output_event_present defered_present_event; + + /* Wayland display */ + struct wl_display *wl_display; + + /* wlroots objects */ + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct wlr_scene *scene; + struct wlr_scene_output *scene_output; + struct wlr_scene_rect *bg; + + /* Custom wlr objects */ + struct wlr_keyboard keyboard; + struct wlr_pointer pointer; + struct wlr_backend backend; + struct wlr_output output; + + /* wlr interfaces */ + struct wlr_backend_impl backend_impl; + struct wlr_output_impl output_impl; + + gboolean backend_started; + + /* XDG shell */ + struct wlr_xdg_shell *xdg_shell; + struct wl_listener new_xdg_toplevel; + struct wl_listener new_xdg_popup; + GList *toplevels; + + /* XDG activation */ + struct wlr_xdg_activation_v1 *xdg_activation; + struct wl_listener request_activate; + + GHashTable *toplevel_state; + + /* Toplevel resize state */ + gdouble pointer_x, pointer_y; /* Current pointer position */ + CasildaCompositorToplevel *grabbed_toplevel; + CasildaPointerMode pointer_mode; + gdouble grab_x, grab_y; + struct wlr_box grab_box; + uint32_t resize_edges; + + /* Virtual Seat */ + struct wlr_seat *seat; + struct wl_listener request_cursor; + struct wl_listener request_set_selection; + + struct wl_listener on_frame; + struct wl_listener on_request_cursor; + struct wl_listener on_cursor_surface_commit; + gint hotspot_x; + gint hotspot_y; + GdkPixbuf *cursor_gdk_pixbuf; + GdkTexture *cursor_gdk_texture; + GdkCursor *cursor_gdk_cursor; + + /* GObject properties */ + gchar *socket; + + gchar *error_message; + PangoLayout *error_layout; + gint error_layout_width; + gint error_layout_height; + +} CasildaCompositorPrivate; + + +struct _CasildaCompositor +{ + GtkDrawingArea parent; +}; + + +typedef struct +{ + gboolean maximized, fullscreen; + gint x, y, width, height; +} CasildaCompositorToplevelState; + + +struct CasildaCompositorToplevel +{ + CasildaCompositorPrivate *priv; + struct wlr_xdg_toplevel *xdg_toplevel; + struct wlr_scene_tree *scene_tree; + + CasildaCompositorToplevelState old_state; + + /* This points to priv->toplevel_state[app_id] */ + CasildaCompositorToplevelState *state; + + /* Events */ + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener commit; + struct wl_listener destroy; + struct wl_listener request_move; + struct wl_listener request_resize; + struct wl_listener request_maximize; + struct wl_listener request_fullscreen; + struct wl_listener set_app_id; +}; + +typedef struct +{ + struct wlr_xdg_popup *xdg_popup; + struct wl_listener commit; + struct wl_listener destroy; +} CasildaCompositorPopup; + +enum +{ + PROP_0, + PROP_SOCKET, + PROP_ERROR_MESSAGE, + + N_PROPERTIES +}; + +enum +{ + CONTEXT_MENU, + + LAST_SIGNAL +}; + +static guint compositor_signals[LAST_SIGNAL] = { 0 }; + +static GParamSpec *properties[N_PROPERTIES]; + + +G_DEFINE_TYPE_WITH_PRIVATE(CasildaCompositor, casilda_compositor, GTK_TYPE_DRAWING_AREA); +#define GET_PRIVATE(d) ((CasildaCompositorPrivate *) casilda_compositor_get_instance_private((CasildaCompositor*)d)) + + +static void casilda_compositor_wlr_init(CasildaCompositorPrivate *priv); +static void casilda_compositor_set_error_message(CasildaCompositor *compositor, + const gchar *message); + + +static cairo_format_t +_cairo_format_from_pixman_format (pixman_format_code_t pixman_format) +{ + switch (pixman_format) + { + case PIXMAN_rgba_float: + return CAIRO_FORMAT_RGBA128F; + case PIXMAN_rgb_float: + return CAIRO_FORMAT_RGB96F; + case PIXMAN_a8r8g8b8: + return CAIRO_FORMAT_ARGB32; + case PIXMAN_x2r10g10b10: + return CAIRO_FORMAT_RGB30; + case PIXMAN_x8r8g8b8: + return CAIRO_FORMAT_RGB24; + case PIXMAN_a8: + return CAIRO_FORMAT_A8; + case PIXMAN_a1: + return CAIRO_FORMAT_A1; + case PIXMAN_r5g6b5: + return CAIRO_FORMAT_RGB16_565; + default: + return CAIRO_FORMAT_INVALID; + } + + return CAIRO_FORMAT_INVALID; +} + +static void +casilda_compositor_draw (GtkDrawingArea *area, + cairo_t *cr, + int width, + int height, + G_GNUC_UNUSED gpointer data) +{ + CasildaCompositorPrivate *priv = GET_PRIVATE (area); + struct wlr_scene_output *scene_output = priv->scene_output; + g_autoptr(WlrTexture) texture = NULL; + g_autoptr(cairo_surface_t) surface = NULL; + pixman_image_t *image = NULL; + cairo_format_t format; + struct timespec now; + g_auto(WlrOutputState) state = {0, }; + + if (priv->error_message) + { + cairo_move_to (cr, + width/2 - priv->error_layout_width/2, + height/2 - priv->error_layout_height/2); + pango_cairo_show_layout (cr, priv->error_layout); + return; + } + + wlr_output_state_init(&state); + + if (!wlr_scene_output_build_state(scene_output, &state, NULL)) + return; + + if (!(texture = wlr_texture_from_buffer(priv->renderer, state.buffer))) + return; + + if (!(image = wlr_pixman_texture_get_image(texture))) + return; + + format = _cairo_format_from_pixman_format (pixman_image_get_format (image)); + + if (format == CAIRO_FORMAT_INVALID) + return; + + surface = cairo_image_surface_create_for_data((gpointer)pixman_image_get_data (image), + format, + pixman_image_get_width (image), + pixman_image_get_height (image), + pixman_image_get_stride (image)); + + /* Use buffer as source and blit */ + cairo_set_source_surface(cr, surface, 0, 0); + cairo_paint (cr); + + /* TODO: try using a dmabuf + * https://docs.gtk.org/gdk4/method.DmabufTextureBuilder.set_update_region.html + */ + + wlr_output_commit_state(scene_output->output, &state); + + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(scene_output, &now); +} + +static void +on_casilda_compositor_output_frame(struct wl_listener *listener, + G_GNUC_UNUSED void *data) +{ + CasildaCompositorPrivate *priv = wl_container_of(listener, priv, on_frame); + struct wlr_scene_output *scene_output = priv->scene_output; + + if (!scene_output->output->needs_frame && !pixman_region32_not_empty( + &scene_output->pending_commit_damage)) + { + if (priv->frame_clock_updating) + { + gdk_frame_clock_end_updating(priv->frame_clock); + priv->frame_clock_updating = FALSE; + } + return; + } + + if (!priv->frame_clock_updating) + { + priv->frame_clock_updating = TRUE; + gdk_frame_clock_begin_updating(priv->frame_clock); + } + + gtk_widget_queue_draw(priv->widget); +} + +static void +casilda_compositor_size_allocate(GtkWidget *widget, int w, int h, int b) +{ + CasildaCompositorPrivate *priv = GET_PRIVATE (widget); + struct wlr_output_state state; + + GTK_WIDGET_CLASS (casilda_compositor_parent_class)->size_allocate (widget, w, h, b); + + /* Update background rectangle size */ + wlr_scene_rect_set_size(priv->bg, w, h); + + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + wlr_output_state_set_custom_mode(&state, w, h, 0); + wlr_output_commit_state(&priv->output, &state); + wlr_output_state_finish(&state); +} + +static void +casilda_composite_cursor_handler_remove(CasildaCompositorPrivate *priv) +{ + if (priv->on_cursor_surface_commit.link.next) + { + wl_list_remove(&priv->on_cursor_surface_commit.link); + memset(&priv->on_cursor_surface_commit, 0, sizeof(struct wl_listener)); + } +} + +static void +casilda_composite_reset_cursor(CasildaCompositorPrivate *priv) +{ + if (priv->widget) + gtk_widget_set_cursor(priv->widget, NULL); + + g_clear_object(&priv->cursor_gdk_cursor); + g_clear_object(&priv->cursor_gdk_texture); + g_clear_object(&priv->cursor_gdk_pixbuf); + + casilda_composite_cursor_handler_remove(priv); +} + +static void +casilda_compositor_reset_pointer_mode(CasildaCompositorPrivate *priv) +{ + priv->pointer_mode = casilda_POINTER_MODE_FOWARD; + priv->grabbed_toplevel = NULL; +} + + +static CasildaCompositorToplevel * +casilda_compositor_get_toplevel_at_pointer(CasildaCompositorPrivate *priv, + struct wlr_surface **surface, + double *sx, + double *sy) +{ + struct wlr_scene_node *node; + struct wlr_scene_buffer *scene_buffer; + struct wlr_scene_surface *scene_surface; + struct wlr_scene_tree *parent; + + if (surface) + *surface = NULL; + + node = wlr_scene_node_at(&priv->scene->tree.node, + priv->pointer_x, + priv->pointer_y, + sx, + sy); + + if (!node || node->type != WLR_SCENE_NODE_BUFFER) + return NULL; + + if (!(scene_buffer = wlr_scene_buffer_from_node(node))) + return NULL; + + if (!(scene_surface = wlr_scene_surface_try_from_buffer(scene_buffer))) + return NULL; + + if (surface) + *surface = scene_surface->surface; + + parent = node->parent; + while (parent && !parent->node.data) + parent = parent->node.parent; + + return parent ? parent->node.data : NULL; +} + +static void +casilda_compositor_toplevel_configure(CasildaCompositorToplevel *toplevel, + gint x, + gint y, + gint width, + gint height) +{ + wlr_scene_node_set_position(&toplevel->scene_tree->node, x, y); + + if (width && height) + { + toplevel->xdg_toplevel->scheduled.width = width; + toplevel->xdg_toplevel->scheduled.height = height; + wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base); + } +} + +static void +casilda_compositor_toplevel_save_position(CasildaCompositorToplevel *toplevel) +{ + CasildaCompositorToplevelState *state = toplevel->state; + + if (!state) + return; + + /* Get position from scene node */ + state->x = toplevel->scene_tree->node.x; + state->y = toplevel->scene_tree->node.y; + + g_debug("%s %s %dx%d %dx%d maximized=%d fullscreen=%d", + __func__, + toplevel->xdg_toplevel->app_id, + state->x, + state->y, + state->width, + state->height, + state->maximized, + state->fullscreen); +} + +static void +casilda_compositor_toplevel_save_size(CasildaCompositorToplevel *toplevel, + gint width, + gint height) +{ + CasildaCompositorToplevelState *state = toplevel->state; + + if (!state) + return; + + /* Assign current state from toplevel */ + state->width = width; + state->height = height; + + g_debug("%s %s %dx%d %dx%d maximized=%d fullscreen=%d", + __func__, + toplevel->xdg_toplevel->app_id, + state->x, + state->y, + state->width, + state->height, + state->maximized, + state->fullscreen); +} + +static void +casilda_compositor_toplevel_toggle_maximize_fullscreen(CasildaCompositorToplevel *toplevel, + gboolean fullscreen) +{ + CasildaCompositorPrivate *priv = toplevel->priv; + struct wlr_xdg_toplevel *xdg_toplevel = toplevel->xdg_toplevel; + CasildaCompositorToplevelState *state = toplevel->state; + gboolean value; + + if (!xdg_toplevel->base->initialized || !xdg_toplevel->base->configured) + return; + + if (fullscreen) + { + value = xdg_toplevel->requested.fullscreen; + if (xdg_toplevel->current.fullscreen == value) + return; + + xdg_toplevel->scheduled.fullscreen = value; + + if (state) + state->fullscreen = value; + } + else + { + value = xdg_toplevel->requested.maximized; + if (xdg_toplevel->current.maximized == value) + return; + + xdg_toplevel->scheduled.maximized = value; + + if (state) + state->maximized = value; + } + + if (value) + { + GtkWidget *widget = priv->widget; + + toplevel->old_state.x = toplevel->scene_tree->node.x; + toplevel->old_state.y = toplevel->scene_tree->node.y; + toplevel->old_state.width = xdg_toplevel->current.width; + toplevel->old_state.height = xdg_toplevel->current.height; + + casilda_compositor_toplevel_configure(toplevel, + 0, 0, + gtk_widget_get_width(widget), + gtk_widget_get_height(widget)); + } + else + { + casilda_compositor_toplevel_configure(toplevel, + toplevel->old_state.x, + toplevel->old_state.y, + toplevel->old_state.width, + toplevel->old_state.height); + } +} + +static void +casilda_compositor_handle_pointer_resize_toplevel(CasildaCompositorPrivate *priv) +{ + CasildaCompositorToplevel *toplevel = priv->grabbed_toplevel; + struct wlr_xdg_toplevel *xdg_toplevel = toplevel->xdg_toplevel; + struct wlr_box box; + gint border_x = priv->pointer_x - priv->grab_x; + gint border_y = priv->pointer_y - priv->grab_y; + gint new_left = priv->grab_box.x; + gint new_right = priv->grab_box.x + priv->grab_box.width; + gint new_top = priv->grab_box.y; + gint new_bottom = priv->grab_box.y + priv->grab_box.height; + gint new_width, new_height, min_width, min_height; + + min_width = xdg_toplevel->current.min_width; + min_height = xdg_toplevel->current.min_height; + + if (priv->resize_edges & WLR_EDGE_TOP) + { + new_top = border_y; + if (new_top >= new_bottom) + new_top = new_bottom - 1; + } + else if (priv->resize_edges & WLR_EDGE_BOTTOM) + { + new_bottom = border_y; + if (new_bottom <= new_top) + new_bottom = new_top + 1; + } + + if (priv->resize_edges & WLR_EDGE_LEFT) + { + new_left = border_x; + if (new_left >= new_right) + new_left = new_right - 1; + } + else if (priv->resize_edges & WLR_EDGE_RIGHT) + { + new_right = border_x; + if (new_right <= new_left) + new_right = new_left + 1; + } + + new_width = new_right - new_left; + new_height = new_bottom - new_top; + + if (new_width < min_width && new_height < min_height) + return; + + if (new_width < min_width) + { + if (priv->resize_edges & WLR_EDGE_LEFT) + new_left -= min_width - new_width; + new_width = min_width; + } + + if (new_height < min_height) + { + if (priv->resize_edges & WLR_EDGE_TOP) + new_top -= min_height - new_height; + new_height = min_height; + } + + wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &box); + + wlr_xdg_toplevel_set_size(toplevel->xdg_toplevel, new_width, new_height); + + /* FIXME: we probably need to wait for the new size to be in effect + * before setting the position + */ + wlr_scene_node_set_position(&toplevel->scene_tree->node, + new_left - box.x, + new_top - box.y); + + casilda_compositor_toplevel_save_position(toplevel); + casilda_compositor_toplevel_save_size(toplevel, new_width, new_height); +} + +static void +casilda_compositor_handle_pointer_motion(CasildaCompositorPrivate *priv) +{ + if (priv->pointer_mode == casilda_POINTER_MODE_MOVE) + { + wlr_scene_node_set_position(&priv->grabbed_toplevel->scene_tree->node, + priv->pointer_x - priv->grab_x, + priv->pointer_y - priv->grab_y); + + casilda_compositor_toplevel_save_position(priv->grabbed_toplevel); + } + else if (priv->pointer_mode == casilda_POINTER_MODE_RESIZE) + { + casilda_compositor_handle_pointer_resize_toplevel(priv); + } + else + { + CasildaCompositorToplevel *toplevel; + struct wlr_surface *surface; + double sx, sy; + + toplevel = casilda_compositor_get_toplevel_at_pointer(priv, &surface, &sx, &sy); + + if (!toplevel) + casilda_composite_reset_cursor(priv); + + if (surface) + { + uint32_t time = gtk_event_controller_get_current_event_time(priv->motion_controller); + wlr_seat_pointer_notify_enter(priv->seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(priv->seat, time, sx, sy); + } + else + wlr_seat_pointer_clear_focus(priv->seat); + } +} + +static void +on_motion_controller_enter (G_GNUC_UNUSED GtkEventControllerMotion *self, + gdouble x, + gdouble y, + CasildaCompositorPrivate *priv) +{ + priv->pointer_x = x; + priv->pointer_y = y; + casilda_compositor_handle_pointer_motion(priv); + wlr_seat_pointer_notify_frame(priv->seat); +} + +static void +on_motion_controller_leave (G_GNUC_UNUSED GtkEventControllerMotion *self, + CasildaCompositorPrivate *priv) +{ + wlr_seat_pointer_clear_focus(priv->seat); +} + +static void +on_motion_controller_motion (G_GNUC_UNUSED GtkEventControllerMotion *self, + gdouble x, + gdouble y, + CasildaCompositorPrivate *priv) +{ + /* Clamp pointer to widget coordinates */ + priv->pointer_x = CLAMP(x, 0, gtk_widget_get_width (priv->widget)); + priv->pointer_y = CLAMP(y, 0, gtk_widget_get_height (priv->widget)); + + casilda_compositor_handle_pointer_motion(priv); + wlr_seat_pointer_notify_frame(priv->seat); +} + +static gboolean +on_scroll_controller_scroll (GtkEventControllerScroll *self, + gdouble dx, + gdouble dy, + CasildaCompositorPrivate *priv) +{ + uint32_t time_msec = gtk_event_controller_get_current_event_time(GTK_EVENT_CONTROLLER(self)); + gint idx, idy; + + idx = dx * WLR_POINTER_AXIS_DISCRETE_STEP; + idy = dy * WLR_POINTER_AXIS_DISCRETE_STEP; + + if (idx != 0) + wlr_seat_pointer_notify_axis(priv->seat, + time_msec, + WL_POINTER_AXIS_HORIZONTAL_SCROLL, + idx, + idx, + WL_POINTER_AXIS_SOURCE_WHEEL, + WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL); + + if (idy != 0) + wlr_seat_pointer_notify_axis(priv->seat, + time_msec, + WL_POINTER_AXIS_VERTICAL_SCROLL, + idy, + idy, + WL_POINTER_AXIS_SOURCE_WHEEL, + WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL); + + wlr_seat_pointer_notify_frame(priv->seat); + + return TRUE; +} + +static void +casilda_compositor_focus_toplevel(CasildaCompositorToplevel *toplevel, + struct wlr_surface *surface) +{ + CasildaCompositorPrivate *priv = toplevel->priv; + struct wlr_surface *focused_surface = priv->seat->keyboard_state.focused_surface; + + if (focused_surface == surface) + return; + + if (focused_surface) + { + struct wlr_xdg_toplevel *focused_toplevel = + wlr_xdg_toplevel_try_from_wlr_surface(focused_surface); + + if (focused_toplevel) + wlr_xdg_toplevel_set_activated(focused_toplevel, false); + } + + /* Move it to the front */ + wlr_scene_node_raise_to_top(&toplevel->scene_tree->node); + wlr_xdg_toplevel_set_activated(toplevel->xdg_toplevel, true); + + priv->toplevels = g_list_remove(priv->toplevels, toplevel); + priv->toplevels = g_list_prepend(priv->toplevels, toplevel); + + wlr_seat_keyboard_notify_enter(priv->seat, + toplevel->xdg_toplevel->base->surface, + priv->keyboard.keycodes, + priv->keyboard.num_keycodes, + &priv->keyboard.modifiers); +} + +static void +casilda_compositor_seat_pointer_notify(GtkGestureClick *self, + CasildaCompositorPrivate *priv, + gint button, + enum wl_pointer_button_state state) +{ + uint32_t time_msec, wl_button; + struct wlr_surface *surface = NULL; + CasildaCompositorToplevel *toplevel; + double sx, sy; + + button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(self)); + + if (button == 1) + wl_button = BTN_LEFT; + else if (button == 2) + wl_button = BTN_MIDDLE; + else if (button == 3) + wl_button = BTN_RIGHT; + else + { + g_message("%s unknown button %u", __func__, button); + return; + } + + time_msec = gtk_event_controller_get_current_event_time(GTK_EVENT_CONTROLLER(self)); + + wlr_seat_pointer_notify_button(priv->seat, time_msec, wl_button, state); + wlr_seat_pointer_notify_frame(priv->seat); + + toplevel = casilda_compositor_get_toplevel_at_pointer(priv, &surface, &sx, &sy); + + if (state == WL_POINTER_BUTTON_STATE_RELEASED) + casilda_compositor_reset_pointer_mode(priv); + else if (toplevel) + casilda_compositor_focus_toplevel(toplevel, surface); +} + +static void +on_click_gesture_pressed (GtkGestureClick *self, + G_GNUC_UNUSED gint n_press, + gdouble x, + gdouble y, + CasildaCompositorPrivate *priv) +{ + gint button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(self)); + + gtk_widget_grab_focus (priv->widget); + + if (button == 3) + { + g_signal_emit (priv->widget, + compositor_signals[CONTEXT_MENU], + 0, + (gint)x, + (gint)y); + return; + } + + casilda_compositor_seat_pointer_notify(self, priv, button, WL_POINTER_BUTTON_STATE_PRESSED); +} + +static void +on_click_gesture_released (GtkGestureClick* self, + G_GNUC_UNUSED gint n_press, + G_GNUC_UNUSED gdouble x, + G_GNUC_UNUSED gdouble y, + CasildaCompositorPrivate *priv) +{ + gint button = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(self)); + if (button == 3) + return; + + casilda_compositor_seat_pointer_notify(self, priv, button, WL_POINTER_BUTTON_STATE_RELEASED); +} + +static void +casilda_compositor_seat_key_notify(GtkEventControllerKey *self, + CasildaCompositorPrivate *priv, + uint32_t key, + uint32_t state) +{ + uint32_t time_msec = gtk_event_controller_get_current_event_time(GTK_EVENT_CONTROLLER(self)); + wlr_seat_keyboard_notify_key(priv->seat, time_msec, key - 8, state); +} + +static gboolean +on_key_controller_key_pressed (GtkEventControllerKey* self, + G_GNUC_UNUSED guint keyval, + guint keycode, + G_GNUC_UNUSED GdkModifierType state, + CasildaCompositorPrivate *priv) +{ + casilda_compositor_seat_key_notify(self, priv, keycode, WL_KEYBOARD_KEY_STATE_PRESSED); + return TRUE; +} + +static void +on_key_controller_key_released (GtkEventControllerKey* self, + G_GNUC_UNUSED guint keyval, + guint keycode, + G_GNUC_UNUSED GdkModifierType state, + CasildaCompositorPrivate *priv) +{ + casilda_compositor_seat_key_notify(self, priv, keycode, WL_KEYBOARD_KEY_STATE_RELEASED); +} + +static gboolean +on_key_controller_modifiers (G_GNUC_UNUSED GtkEventControllerKey *self, + GdkModifierType state, + CasildaCompositorPrivate *priv) +{ + struct wlr_keyboard_modifiers modifiers = { 0, }; + guint wl_state = 0; + + if (state & GDK_SHIFT_MASK) + wl_state |= WLR_MODIFIER_SHIFT; + else if (state & GDK_LOCK_MASK) + wl_state |= WLR_MODIFIER_CAPS; + else if (state & GDK_CONTROL_MASK) + wl_state |= WLR_MODIFIER_CTRL; + else if (state & GDK_ALT_MASK) + wl_state |= WLR_MODIFIER_ALT; + else if (state & GDK_SUPER_MASK) + wl_state |= WLR_MODIFIER_LOGO; + else if (state & GDK_HYPER_MASK) + wl_state |= WLR_MODIFIER_MOD2; + else if (state & GDK_META_MASK) + wl_state |= WLR_MODIFIER_MOD3; + + modifiers.depressed = wl_state; + + wlr_seat_keyboard_notify_modifiers(priv->seat, &modifiers); + + return TRUE; +} + +static void +_on_pixbuf_destroy_notify (guchar *pixels, G_GNUC_UNUSED gpointer data) +{ + g_free(pixels); +} + +static void +cursor_handle_surface_commit(struct wl_listener *listener, void *data) +{ + CasildaCompositorPrivate *priv = wl_container_of(listener, priv, on_cursor_surface_commit); + struct wlr_surface *surface = data; + WlrTexture *texture = NULL; + pixman_image_t *image = NULL; + gint height, stride; + + if (!(texture = wlr_surface_get_texture(surface))) + return; + + if (!(image = wlr_pixman_texture_get_image(texture))) + return; + + priv->hotspot_x -= surface->current.dx; + priv->hotspot_y -= surface->current.dy; + + if (pixman_image_get_format (image) != PIXMAN_a8r8g8b8) + { + casilda_composite_reset_cursor(priv); + return; + } + + height = pixman_image_get_height (image); + stride = pixman_image_get_stride (image); + + /* Create a GdkPixbuf with a copy of surface data */ + if(!(priv->cursor_gdk_pixbuf = gdk_pixbuf_new_from_data ( + g_memdup2(pixman_image_get_data (image), height * stride), + GDK_COLORSPACE_RGB, + TRUE, + 8, + pixman_image_get_width (image), + height, + stride, + _on_pixbuf_destroy_notify, + NULL + ))) + return; + + /* Create texture from pixbuf */ + if (!(priv->cursor_gdk_texture = gdk_texture_new_for_pixbuf(priv->cursor_gdk_pixbuf))) + return; + + /* Finally create cursor from texture */ + priv->cursor_gdk_cursor = gdk_cursor_new_from_texture(priv->cursor_gdk_texture, + priv->hotspot_x, + priv->hotspot_y, + NULL); + + /* Set cursor */ + if (priv->cursor_gdk_cursor) + gtk_widget_set_cursor(priv->widget, priv->cursor_gdk_cursor); + + /* Unlink handler */ + casilda_composite_cursor_handler_remove(priv); +} + +static void +on_seat_request_cursor(struct wl_listener *listener, void *data) +{ + CasildaCompositorPrivate *priv = wl_container_of(listener, priv, on_request_cursor); + struct wlr_seat_pointer_request_set_cursor_event *event = data; + struct wlr_seat_client *focused_client = + priv->seat->pointer_state.focused_client; + struct wlr_surface *surface = event->surface; + + if (focused_client != event->seat_client) + return; + + if (!surface) + return; + + priv->hotspot_x = event->hotspot_x; + priv->hotspot_y = event->hotspot_y; + + wlr_surface_send_enter(surface, &priv->output); + + /* We only keep track of the last cursor change */ + casilda_composite_cursor_handler_remove(priv); + + /* Update cursor once the surface has been committed */ + wl_signal_add(&surface->events.commit, &priv->on_cursor_surface_commit); + priv->on_cursor_surface_commit.notify = cursor_handle_surface_commit; +} + +static bool +casilda_compositor_backend_start(struct wlr_backend *wlr_backend) +{ + CasildaCompositorPrivate *priv = wl_container_of(wlr_backend, priv, backend); + g_info("Starting Casilda backend"); + priv->backend_started = true; + return true; +} + +static void +casilda_compositor_backend_destroy(struct wlr_backend *wlr_backend) +{ + CasildaCompositorPrivate *priv = wl_container_of(wlr_backend, priv, backend); + wlr_backend_finish(&priv->backend); + wlr_output_destroy(&priv->output); +} + +static uint32_t +casilda_compositor_backend_get_buffer_caps(G_GNUC_UNUSED struct wlr_backend *wlr_backend) +{ + return WLR_BUFFER_CAP_DATA_PTR | WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_SHM; +} + +static bool +casilda_compositor_output_commit(G_GNUC_UNUSED struct wlr_output *wlr_output, + G_GNUC_UNUSED const struct wlr_output_state *state) +{ + return true; +} + +static void casilda_compositor_output_destroy(G_GNUC_UNUSED struct wlr_output *wlr_output) +{ + /* TODO: disconnect from GdkFrameClock */ +} + +static void +casilda_compositor_backend_init(CasildaCompositorPrivate *priv) +{ + priv->backend_impl.start = casilda_compositor_backend_start; + priv->backend_impl.destroy = casilda_compositor_backend_destroy; + priv->backend_impl.get_buffer_caps = casilda_compositor_backend_get_buffer_caps; + wlr_backend_init(&priv->backend, &priv->backend_impl); +} + +static void +casilda_compositor_output_init(CasildaCompositorPrivate *priv) +{ + struct wlr_output_state state; + + wlr_output_state_init(&state); + + /* Initialize custom output iface */ + priv->output_impl.commit = casilda_compositor_output_commit; + priv->output_impl.destroy = casilda_compositor_output_destroy; + + /* Actual size will be set on size_allocate() */ + wlr_output_state_set_custom_mode(&state, 0, 0, 0); + + /* Init wlr output */ + wlr_output_init(&priv->output, + &priv->backend, + &priv->output_impl, + wl_display_get_event_loop(priv->wl_display), + &state); + + /* Set a name */ + wlr_output_set_name(&priv->output, "CasildaCompositor"); + wlr_output_set_description(&priv->output, "CasildaCompositor output"); + + /* Init output rendering */ + wlr_output_init_render(&priv->output, priv->allocator, priv->renderer); + + /* Sets up a listener for the frame event. */ + priv->on_frame.notify = on_casilda_compositor_output_frame; + wl_signal_add(&priv->output.events.frame, &priv->on_frame); + + /* Create a scene output */ + priv->scene_output = wlr_scene_output_create(priv->scene, &priv->output); + + /* Make output global */ + wlr_output_create_global(&priv->output, priv->wl_display); + + wlr_output_state_finish(&state); +} + +static void +casilda_pointer_mode_init(CasildaCompositorPrivate *priv) +{ + wlr_pointer_init(&priv->pointer, NULL, "Casilda-pointer"); + + priv->on_request_cursor.notify = on_seat_request_cursor; + wl_signal_add(&priv->seat->events.request_set_cursor, + &priv->on_request_cursor); + + priv->motion_controller = gtk_event_controller_motion_new(); + priv->scroll_controller = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES | + GTK_EVENT_CONTROLLER_SCROLL_DISCRETE); + priv->click_gesture = gtk_gesture_click_new(); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(priv->click_gesture), 0); + + g_signal_connect(priv->motion_controller, "enter", + G_CALLBACK(on_motion_controller_enter), + priv); + g_signal_connect(priv->motion_controller, "leave", + G_CALLBACK(on_motion_controller_leave), + priv); + g_signal_connect(priv->motion_controller, "motion", + G_CALLBACK(on_motion_controller_motion), + priv); + + g_signal_connect(priv->scroll_controller, "scroll", + G_CALLBACK(on_scroll_controller_scroll), + priv); + + g_signal_connect(priv->click_gesture, "pressed", + G_CALLBACK(on_click_gesture_pressed), + priv); + g_signal_connect(priv->click_gesture, "released", + G_CALLBACK(on_click_gesture_released), + priv); + + gtk_widget_add_controller(priv->widget, priv->motion_controller); + gtk_widget_add_controller(priv->widget, priv->scroll_controller); + gtk_widget_add_controller(priv->widget, GTK_EVENT_CONTROLLER(priv->click_gesture)); +} + +static void +casilda_compositor_keyboard_init(CasildaCompositorPrivate *priv) +{ + struct xkb_keymap *keymap = NULL; + struct xkb_state *state = NULL; + GdkDevice *gkeyboard; + GdkDisplay *gdisplay; + GdkSeat *gseat; + + wlr_keyboard_init(&priv->keyboard, NULL, "Casilda-keyboard"); + + gdisplay = gtk_widget_get_display(priv->widget); + gseat = gdk_display_get_default_seat(gdisplay); + gkeyboard = gdk_seat_get_keyboard(gseat); + +#ifdef GDK_WINDOWING_WAYLAND + if (GDK_IS_WAYLAND_DEVICE (gkeyboard)) + { + keymap = gdk_wayland_device_get_xkb_keymap (gkeyboard); + + /* TODO: add a way to get keymap state from gtk wayland backend + * Or even better add a way to get the layout directly + */ + + xkb_keymap_ref(keymap); + } +#endif + +#if defined(GDK_WINDOWING_X11) && defined(HAVE_X11_XCB) + if (GDK_IS_X11_DEVICE_XI2 (gkeyboard)) + { + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + Display *dpy = gdk_x11_display_get_xdisplay(GDK_X11_DISPLAY(gdisplay)); + + keymap = xkb_x11_keymap_new_from_device(context, + XGetXCBConnection(dpy), + gdk_x11_device_get_id(gkeyboard), + XKB_KEYMAP_COMPILE_NO_FLAGS); + state = xkb_x11_state_new_from_device (keymap, + XGetXCBConnection(dpy), + gdk_x11_device_get_id(gkeyboard)); + xkb_context_unref(context); + } +#endif + + /* Fallback to US */ + if (keymap == NULL) + { + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + keymap = xkb_keymap_new_from_names(context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS); + xkb_context_unref(context); + } + + /* Set keymap */ + wlr_keyboard_set_keymap(&priv->keyboard, keymap); + + /* Update layout if present */ + if (state) + { + gint active_layout = -1; + + for (guint i = 0; i < xkb_keymap_num_layouts (keymap); i++) + { + if (xkb_state_layout_index_is_active(state, + i, + XKB_STATE_LAYOUT_EFFECTIVE)) + active_layout = i; + + g_debug("\t %i %s", i, xkb_keymap_layout_get_name (keymap, i)); + } + + if (active_layout >= 0) + wlr_keyboard_notify_modifiers(&priv->keyboard, + priv->keyboard.modifiers.depressed, + priv->keyboard.modifiers.latched, + priv->keyboard.modifiers.locked, + active_layout); + xkb_state_unref(state); + } + + xkb_keymap_unref(keymap); + + wlr_seat_set_keyboard(priv->seat, &priv->keyboard); + + priv->key_controller = gtk_event_controller_key_new(); + g_signal_connect(priv->key_controller, "key-pressed", + G_CALLBACK(on_key_controller_key_pressed), + priv); + g_signal_connect(priv->key_controller, "key-released", + G_CALLBACK(on_key_controller_key_released), + priv); + g_signal_connect(priv->key_controller, "modifiers", + G_CALLBACK(on_key_controller_modifiers), + priv); + gtk_widget_add_controller(priv->widget, priv->key_controller); +} + +static void +casilda_compositor_init (CasildaCompositor *object) +{ + CasildaCompositorPrivate *priv = GET_PRIVATE (object); + + gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(object), + casilda_compositor_draw, + NULL, + NULL); + priv->widget = GTK_WIDGET(object); + gtk_widget_set_focusable(priv->widget, TRUE); + + /* Toplevel state */ + priv->toplevel_state = g_hash_table_new_full(g_str_hash, + g_str_equal, + g_free, + g_free); + + casilda_compositor_backend_init(priv); + casilda_compositor_wlr_init (priv); + casilda_compositor_output_init(priv); + casilda_pointer_mode_init(priv); + casilda_compositor_keyboard_init(priv); + + casilda_compositor_reset_pointer_mode(priv); + + priv->wl_source = casilda_wayland_source_new(priv->wl_display); + g_source_attach (priv->wl_source, NULL); + + /* Start the backend. */ + if (!wlr_backend_start(&priv->backend)) + { + /* TODO: handle error */ + return; + } +} + +void +casilda_compositor_cleanup (CasildaCompositor *object) +{ + CasildaCompositorPrivate *priv = GET_PRIVATE (object); + + g_clear_pointer(&priv->toplevel_state, g_hash_table_destroy); + g_clear_pointer(&priv->socket, g_free); + + g_clear_object(&priv->error_layout); + g_clear_object(&priv->motion_controller); + g_clear_object(&priv->scroll_controller); + g_clear_object(&priv->key_controller); + g_clear_object(&priv->click_gesture); + + priv->widget = NULL; + casilda_composite_reset_cursor(priv); + + wl_display_destroy_clients(priv->wl_display); + + wlr_keyboard_finish(&priv->keyboard); + wlr_pointer_finish(&priv->pointer); + wlr_scene_node_destroy(&priv->scene->tree.node); + wlr_allocator_destroy(priv->allocator); + wlr_renderer_destroy(priv->renderer); + wlr_backend_destroy(&priv->backend); + wl_display_destroy(priv->wl_display); + + g_source_destroy (priv->wl_source); +} + +static void +casilda_compositor_finalize (GObject *object) +{ + casilda_compositor_cleanup(CASILDA_COMPOSITOR(object)); + G_OBJECT_CLASS (casilda_compositor_parent_class)->finalize (object); +} + + +static void +casilda_compositor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + g_return_if_fail (CASILDA_IS_COMPOSITOR (object)); + + switch (prop_id) + { + case PROP_ERROR_MESSAGE: + casilda_compositor_set_error_message (CASILDA_COMPOSITOR (object), + g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +casilda_compositor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CasildaCompositorPrivate *priv; + + g_return_if_fail (CASILDA_IS_COMPOSITOR (object)); + priv = GET_PRIVATE (object); + + switch (prop_id) + { + case PROP_SOCKET: + g_value_set_string (value, priv->socket); + break; + case PROP_ERROR_MESSAGE: + g_value_set_string (value, pango_layout_get_text (priv->error_layout)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_casilda_compositor_frame_clock_update (G_GNUC_UNUSED GdkFrameClock *self, + CasildaCompositorPrivate *priv) +{ + wlr_output_send_frame(&priv->output); +} + +static void +casilda_compositor_realize(GtkWidget *widget) +{ + CasildaCompositorPrivate *priv = GET_PRIVATE (widget); + + GTK_WIDGET_CLASS (casilda_compositor_parent_class)->realize (widget); + + priv->frame_clock = gtk_widget_get_frame_clock(widget); + priv->frame_clock_source = + g_signal_connect(priv->frame_clock, "update", + G_CALLBACK(on_casilda_compositor_frame_clock_update), + priv); +} + +static void +casilda_compositor_unrealize(GtkWidget *widget) +{ + CasildaCompositorPrivate *priv = GET_PRIVATE (widget); + + if (priv->frame_clock && priv->frame_clock_source) + { + g_signal_handler_disconnect(priv->frame_clock, priv->frame_clock_source); + priv->frame_clock_source = 0; + } + + GTK_WIDGET_CLASS (casilda_compositor_parent_class)->unrealize (widget); +} + +static void +casilda_compositor_class_init (CasildaCompositorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = casilda_compositor_finalize; + object_class->set_property = casilda_compositor_set_property; + object_class->get_property = casilda_compositor_get_property; + + widget_class->size_allocate = casilda_compositor_size_allocate; + widget_class->realize = casilda_compositor_realize; + widget_class->unrealize = casilda_compositor_unrealize; + + /* Properties */ + properties[PROP_SOCKET] = + g_param_spec_string ("socket", "Unix Socket", + "The unix socket file to connect to this compositor", + NULL, + G_PARAM_READABLE); + + properties[PROP_ERROR_MESSAGE] = + g_param_spec_string ("error-message", "Error message", + "Error message to show instead of compositor", + NULL, + G_PARAM_READWRITE); + + /* Signals */ + compositor_signals[CONTEXT_MENU] = + g_signal_new ("context-menu", + G_OBJECT_CLASS_TYPE (klass), + 0, 0, + NULL, NULL, NULL, + G_TYPE_NONE, 2, + G_TYPE_INT, + G_TYPE_INT); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); +} + + +CasildaCompositor * +casilda_compositor_new (const gchar *socket) +{ + return g_object_new (CASILDA_COMPOSITOR_TYPE, "socket", socket, NULL); +} + +/* wlroots */ + +static void +seat_request_set_selection(struct wl_listener *listener, void *data) +{ + CasildaCompositorPrivate *priv = wl_container_of(listener, priv, request_set_selection); + struct wlr_seat_request_set_selection_event *event = data; + + wlr_seat_set_selection(priv->seat, event->source, event->serial); + /* TODO: integrate with Gtk clipboard */ +} + +static void +xdg_toplevel_map(struct wl_listener *listener, G_GNUC_UNUSED void *data) +{ + CasildaCompositorToplevel *toplevel = wl_container_of(listener, toplevel, map); + struct wlr_xdg_toplevel *xdg_toplevel = toplevel->xdg_toplevel; + CasildaCompositorToplevelState *state = toplevel->state; + + toplevel->priv->toplevels = g_list_prepend(toplevel->priv->toplevels, toplevel); + + casilda_compositor_focus_toplevel(toplevel, xdg_toplevel->base->surface); + + if (state) + { + /* Restore this window state */ + xdg_toplevel->scheduled.fullscreen = state->fullscreen; + xdg_toplevel->scheduled.maximized = state->maximized; + + g_debug("%s %s %dx%d %dx%d maximized=%d fullscreen=%d", + __func__, + xdg_toplevel->app_id, + state->x, + state->y, + state->width, + state->height, + state->maximized, + state->fullscreen); + + if (state->fullscreen || state->maximized) + { + GtkWidget *widget = toplevel->priv->widget; + + toplevel->old_state = *state; + + casilda_compositor_toplevel_configure(toplevel, + 0, 0, + gtk_widget_get_width(widget), + gtk_widget_get_height(widget)); + } + else + casilda_compositor_toplevel_configure(toplevel, + state->x, + state->y, + state->width, + state->height); + } +} + +static void +xdg_toplevel_unmap(struct wl_listener *listener, G_GNUC_UNUSED void *data) +{ + CasildaCompositorToplevel *toplevel = wl_container_of(listener, toplevel, unmap); + + if (toplevel == toplevel->priv->grabbed_toplevel) + casilda_compositor_reset_pointer_mode(toplevel->priv); + + toplevel->state = NULL; + + toplevel->priv->toplevels = g_list_remove(toplevel->priv->toplevels, toplevel); +} + +static void +xdg_toplevel_commit(struct wl_listener *listener, G_GNUC_UNUSED void *data) +{ + CasildaCompositorToplevel *toplevel = wl_container_of(listener, toplevel, commit); + + if (toplevel->xdg_toplevel->base->initial_commit) + wlr_xdg_toplevel_set_size(toplevel->xdg_toplevel, 0, 0); +} + +static void +xdg_toplevel_destroy(struct wl_listener *listener, G_GNUC_UNUSED void *data) +{ + CasildaCompositorToplevel *toplevel = wl_container_of(listener, toplevel, destroy); + + wl_list_remove(&toplevel->map.link); + wl_list_remove(&toplevel->unmap.link); + wl_list_remove(&toplevel->commit.link); + wl_list_remove(&toplevel->destroy.link); + wl_list_remove(&toplevel->request_move.link); + wl_list_remove(&toplevel->request_resize.link); + wl_list_remove(&toplevel->request_maximize.link); + wl_list_remove(&toplevel->request_fullscreen.link); + + g_free(toplevel); +} + +static gboolean +casilda_compositor_toplevel_has_focus(CasildaCompositorToplevel *toplevel) +{ + CasildaCompositorPrivate *priv = toplevel->priv; + struct wlr_surface *focused_surface = + priv->seat->pointer_state.focused_surface; + + return toplevel->xdg_toplevel->base->surface == + wlr_surface_get_root_surface(focused_surface); +} + +static void +xdg_toplevel_request_move(struct wl_listener *listener, G_GNUC_UNUSED void *data) +{ + CasildaCompositorToplevel *toplevel = wl_container_of(listener, toplevel, request_move); + CasildaCompositorPrivate *priv = toplevel->priv; + + if (!casilda_compositor_toplevel_has_focus(toplevel)) + return; + + priv->grabbed_toplevel = toplevel; + priv->pointer_mode = casilda_POINTER_MODE_MOVE; + priv->grab_x = priv->pointer_x - toplevel->scene_tree->node.x; + priv->grab_y = priv->pointer_y - toplevel->scene_tree->node.y; +} + +static void +xdg_toplevel_request_resize(struct wl_listener *listener, void *data) +{ + CasildaCompositorToplevel *toplevel = wl_container_of(listener, toplevel, request_resize); + CasildaCompositorPrivate *priv = toplevel->priv; + struct wlr_scene_tree *scene_tree = toplevel->scene_tree; + struct wlr_xdg_toplevel_resize_event *event = data; + struct wlr_box box; + double border_x, border_y; + + if (!casilda_compositor_toplevel_has_focus(toplevel)) + return; + + priv->grabbed_toplevel = toplevel; + priv->pointer_mode = casilda_POINTER_MODE_RESIZE; + priv->resize_edges = event->edges; + + wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &box); + + border_x = scene_tree->node.x + box.x + + ((event->edges & WLR_EDGE_RIGHT) ? box.width : 0); + border_y = scene_tree->node.y + box.y + + ((event->edges & WLR_EDGE_BOTTOM) ? box.height : 0); + priv->grab_x = priv->pointer_x - border_x; + priv->grab_y = priv->pointer_y - border_y; + + priv->grab_box = box; + priv->grab_box.x += scene_tree->node.x; + priv->grab_box.y += scene_tree->node.y; +} + +static void +xdg_toplevel_request_maximize(struct wl_listener *listener, G_GNUC_UNUSED void *data) +{ + CasildaCompositorToplevel *toplevel = + wl_container_of(listener, toplevel, request_maximize); + + casilda_compositor_toplevel_toggle_maximize_fullscreen(toplevel, FALSE); +} + +static void +xdg_toplevel_request_fullscreen(struct wl_listener *listener, G_GNUC_UNUSED void *data) +{ + CasildaCompositorToplevel *toplevel = + wl_container_of(listener, toplevel, request_fullscreen); + + casilda_compositor_toplevel_toggle_maximize_fullscreen(toplevel, TRUE); +} + +static void +xdg_toplevel_set_app_id(struct wl_listener *listener,G_GNUC_UNUSED void *data) +{ + CasildaCompositorToplevel *toplevel = + wl_container_of(listener, toplevel, set_app_id); + const gchar *app_id = toplevel->xdg_toplevel->app_id; + + toplevel->state = NULL; + + if (!g_str_has_prefix (app_id, "Casilda:")) + return; + + toplevel->state = g_hash_table_lookup(toplevel->priv->toplevel_state, app_id); + + if (!toplevel->state) + { + /* Allocate new state struct */ + toplevel->state = g_new0(CasildaCompositorToplevelState, 1); + + /* Start new windows in the top left corner */ + toplevel->state->x = 32; + toplevel->state->y = 32; + + /* Insert it in out server hash table */ + g_hash_table_insert(toplevel->priv->toplevel_state, + g_strdup(app_id), + toplevel->state); + } + + g_debug("%s %s %dx%d %dx%d", + __func__, + toplevel->xdg_toplevel->app_id, + toplevel->state->x, + toplevel->state->y, + toplevel->state->width, + toplevel->state->height); +} + +static void +server_new_xdg_toplevel(struct wl_listener *listener, void *data) +{ + CasildaCompositorPrivate *priv = wl_container_of(listener, priv, new_xdg_toplevel); + struct wlr_xdg_toplevel *xdg_toplevel = data; + CasildaCompositorToplevel *toplevel; + + toplevel = g_new0(CasildaCompositorToplevel, 1); + toplevel->priv = priv; + toplevel->xdg_toplevel = xdg_toplevel; + toplevel->scene_tree = + wlr_scene_xdg_surface_create(&priv->scene->tree, + xdg_toplevel->base); + toplevel->scene_tree->node.data = toplevel; + xdg_toplevel->base->data = toplevel->scene_tree; + + toplevel->map.notify = xdg_toplevel_map; + wl_signal_add(&xdg_toplevel->base->surface->events.map, &toplevel->map); + toplevel->unmap.notify = xdg_toplevel_unmap; + wl_signal_add(&xdg_toplevel->base->surface->events.unmap, &toplevel->unmap); + toplevel->commit.notify = xdg_toplevel_commit; + wl_signal_add(&xdg_toplevel->base->surface->events.commit, &toplevel->commit); + + toplevel->destroy.notify = xdg_toplevel_destroy; + wl_signal_add(&xdg_toplevel->events.destroy, &toplevel->destroy); + + toplevel->request_move.notify = xdg_toplevel_request_move; + wl_signal_add(&xdg_toplevel->events.request_move, &toplevel->request_move); + toplevel->request_resize.notify = xdg_toplevel_request_resize; + wl_signal_add(&xdg_toplevel->events.request_resize, &toplevel->request_resize); + toplevel->request_maximize.notify = xdg_toplevel_request_maximize; + wl_signal_add(&xdg_toplevel->events.request_maximize, &toplevel->request_maximize); + toplevel->request_fullscreen.notify = xdg_toplevel_request_fullscreen; + wl_signal_add(&xdg_toplevel->events.request_fullscreen, &toplevel->request_fullscreen); + + toplevel->set_app_id.notify = xdg_toplevel_set_app_id; + wl_signal_add(&xdg_toplevel->events.set_app_id, &toplevel->set_app_id); +} + +static void +xdg_popup_commit(struct wl_listener *listener, G_GNUC_UNUSED void *data) +{ + CasildaCompositorPopup *popup = wl_container_of(listener, popup, commit); + + if (popup->xdg_popup->base->initial_commit) + wlr_xdg_surface_schedule_configure(popup->xdg_popup->base); +} + +static void +xdg_popup_destroy(struct wl_listener *listener, G_GNUC_UNUSED void *data) +{ + CasildaCompositorPopup *popup = wl_container_of(listener, popup, destroy); + + wl_list_remove(&popup->commit.link); + wl_list_remove(&popup->destroy.link); + g_free(popup); +} + +static void +server_new_xdg_popup(G_GNUC_UNUSED struct wl_listener *listener, void *data) +{ + CasildaCompositorPopup *popup = g_new0(CasildaCompositorPopup, 1); + struct wlr_xdg_popup *xdg_popup = data; + struct wlr_xdg_surface *parent; + + popup->xdg_popup = xdg_popup; + + if(!(parent = wlr_xdg_surface_try_from_wlr_surface(xdg_popup->parent))) + return; + + xdg_popup->base->data = wlr_scene_xdg_surface_create(parent->data, + xdg_popup->base); + + popup->commit.notify = xdg_popup_commit; + wl_signal_add(&xdg_popup->base->surface->events.commit, &popup->commit); + + popup->destroy.notify = xdg_popup_destroy; + wl_signal_add(&xdg_popup->events.destroy, &popup->destroy); +} + +static void +server_request_activate(struct wl_listener *listener, void *data) +{ + CasildaCompositorPrivate *priv = wl_container_of(listener, priv, request_activate); + struct wlr_xdg_activation_v1_request_activate_event *event = data; + struct wlr_xdg_toplevel *xdg_toplevel = + wlr_xdg_toplevel_try_from_wlr_surface(event->surface); + + if (!xdg_toplevel) + return; + + for (GList *l = priv->toplevels; l; l = g_list_next(l)) + { + CasildaCompositorToplevel *toplevel = l->data; + + if (toplevel->xdg_toplevel != xdg_toplevel) + continue; + + wlr_scene_node_raise_to_top(&toplevel->scene_tree->node); + } +} + +static gchar * +casilda_compositor_get_socket(void) +{ + gchar *tmp = g_dir_make_tmp("Casilda-compositor-XXXXXX", NULL); + gchar *retval = g_build_filename(tmp, "wayland.sock", NULL); + g_free(tmp); + return retval; +} + +static void +casilda_compositor_wlr_init(CasildaCompositorPrivate *priv) +{ + priv->wl_display = wl_display_create(); + + priv->renderer = wlr_pixman_renderer_create(); + if (priv->renderer == NULL) + { + g_warning("failed to create wlr_renderer"); + return; + } + + wlr_renderer_init_wl_display(priv->renderer, priv->wl_display); + + priv->allocator = wlr_allocator_autocreate(&priv->backend, priv->renderer); + if (priv->allocator == NULL) + { + g_warning("failed to create wlr_allocator"); + return; + } + + wlr_compositor_create(priv->wl_display, 5, priv->renderer); + wlr_subcompositor_create(priv->wl_display); + wlr_data_device_manager_create(priv->wl_display); + + /* Create a scene graph a wlroots abstraction that handles all rendering */ + priv->scene = wlr_scene_create(); + + /* Disable direct scanout */ + priv->scene->direct_scanout = FALSE; + + /* Background color */ + priv->bg = wlr_scene_rect_create(&priv->scene->tree, + 100, 100, + (float[4]){ 1.0f, 1.f, 1.f, 1 }); + wlr_scene_node_set_position(&priv->bg->node, 0, 0); + + /* Set up xdg-shell version 3 */ + priv->xdg_shell = wlr_xdg_shell_create(priv->wl_display, 3); + priv->new_xdg_toplevel.notify = server_new_xdg_toplevel; + wl_signal_add(&priv->xdg_shell->events.new_toplevel, &priv->new_xdg_toplevel); + priv->new_xdg_popup.notify = server_new_xdg_popup; + wl_signal_add(&priv->xdg_shell->events.new_popup, &priv->new_xdg_popup); + + /* Set up xdg-activation */ + priv->xdg_activation = wlr_xdg_activation_v1_create(priv->wl_display); + priv->request_activate.notify = server_request_activate; + wl_signal_add(&priv->xdg_activation->events.request_activate, &priv->request_activate); + + /* Configure seat */ + priv->seat = wlr_seat_create(priv->wl_display, "seat0"); + priv->request_set_selection.notify = seat_request_set_selection; + wl_signal_add(&priv->seat->events.request_set_selection, + &priv->request_set_selection); + + wlr_seat_set_capabilities(priv->seat, + WL_SEAT_CAPABILITY_POINTER | + WL_SEAT_CAPABILITY_KEYBOARD); + + priv->socket = casilda_compositor_get_socket(); + + if (wl_display_add_socket(priv->wl_display, priv->socket)) + g_warning("Error adding socket file %s\n", priv->socket); +} + +void +casilda_compositor_set_bg_color(CasildaCompositor *compositor, + gdouble red, + gdouble green, + gdouble blue) +{ + CasildaCompositorPrivate *priv = GET_PRIVATE (compositor); + wlr_scene_rect_set_color(priv->bg, (float[4]){ red, green, blue, 1 }); +} + + +void +casilda_compositor_forget_toplevel_state(CasildaCompositor *compositor) +{ + CasildaCompositorPrivate *priv = GET_PRIVATE (compositor); + g_hash_table_remove_all(priv->toplevel_state); +} + +static void +casilda_compositor_set_error_message(CasildaCompositor *compositor, + const gchar *message) +{ + CasildaCompositorPrivate *priv = GET_PRIVATE (compositor); + + priv->error_message = g_strdup(message); + + if (!priv->error_layout) + priv->error_layout = pango_layout_new(gtk_widget_get_pango_context (priv->widget)); + + if (message) + { + gint width, height; + + pango_layout_set_text(priv->error_layout, message, -1); + pango_layout_get_size(priv->error_layout, &width, &height); + + priv->error_layout_width = PANGO_PIXELS(width); + priv->error_layout_height = PANGO_PIXELS(height); + } + + gtk_widget_queue_draw (priv->widget); +} diff --git a/src/casilda_compositor.h b/src/casilda_compositor.h new file mode 100644 index 0000000..39bcb67 --- /dev/null +++ b/src/casilda_compositor.h @@ -0,0 +1,37 @@ +/* + * Cambalache Wayland Compositor Widget + * + * Copyright (C) 2024 Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Juan Pablo Ugarte + */ + +#pragma once + +#include + +#define CASILDA_COMPOSITOR_TYPE (casilda_compositor_get_type ()) +G_DECLARE_FINAL_TYPE (CasildaCompositor, casilda_compositor, CASILDA, COMPOSITOR, GtkDrawingArea) + +CasildaCompositor *casilda_compositor_new (const gchar *socket); +void casilda_compositor_cleanup (CasildaCompositor *object); +void casilda_compositor_set_bg_color (CasildaCompositor *compositor, + gdouble red, + gdouble green, + gdouble blue); +void casilda_compositor_forget_toplevel_state (CasildaCompositor *compositor); diff --git a/src/casilda_wayland_source.c b/src/casilda_wayland_source.c new file mode 100644 index 0000000..81f811e --- /dev/null +++ b/src/casilda_wayland_source.c @@ -0,0 +1,95 @@ +/* + * Cambalache Wayland GLib Integration + * + * Copyright (C) 2024 Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Juan Pablo Ugarte + */ + +#include "casilda_wayland_source.h" + +typedef struct +{ + GSource source; + struct wl_display *display; +} CasildaWaylandSource; + +#define CASILDA_WAYLAND_SOURCE(s) ((CasildaWaylandSource *)s) + +static gboolean +casilda_wayland_source_prepare (GSource *base, int *timeout) +{ + CasildaWaylandSource *source = CASILDA_WAYLAND_SOURCE(base); + + *timeout = -1; + + wl_display_flush_clients (source->display); + + return FALSE; +} + +static gboolean +casilda_wayland_source_check (GSource *base) +{ + CasildaWaylandSource *source = CASILDA_WAYLAND_SOURCE(base); + struct wl_event_loop *loop = wl_display_get_event_loop (source->display); + + /* Since there is no way to know if there are idle source, dispatch them! */ + wl_event_loop_dispatch_idle (loop); + + return FALSE; +} + + +static gboolean +casilda_wayland_source_dispatch (GSource *base, + G_GNUC_UNUSED GSourceFunc callback, + G_GNUC_UNUSED void *data) +{ + CasildaWaylandSource *source = CASILDA_WAYLAND_SOURCE(base); + struct wl_event_loop *loop = wl_display_get_event_loop (source->display); + + wl_event_loop_dispatch (loop, 0); + + return TRUE; +} + + +static GSourceFuncs casilda_wayland_source_funcs = +{ + .prepare = casilda_wayland_source_prepare, + .check = casilda_wayland_source_check, + .dispatch = casilda_wayland_source_dispatch, +}; + + +GSource * +casilda_wayland_source_new (struct wl_display *display) +{ + struct wl_event_loop *loop = wl_display_get_event_loop (display); + GSource *source = g_source_new (&casilda_wayland_source_funcs, + sizeof (CasildaWaylandSource)); + + CASILDA_WAYLAND_SOURCE(source)->display = display; + + g_source_add_unix_fd (source, + wl_event_loop_get_fd (loop), + G_IO_IN | G_IO_ERR); + + return source; +} diff --git a/src/casilda_wayland_source.h b/src/casilda_wayland_source.h new file mode 100644 index 0000000..7e6c894 --- /dev/null +++ b/src/casilda_wayland_source.h @@ -0,0 +1,30 @@ +/* + * Cambalache Wayland GLib Integration + * + * Copyright (C) 2024 Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Juan Pablo Ugarte + */ + +#pragma once + +#include +#include + +GSource *casilda_wayland_source_new (struct wl_display *display); + diff --git a/src/meson.build b/src/meson.build index b552408..d5aa59e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,33 @@ -wlroots_dep = dependency('wlroots-0.18', version: '>= 0.18.0') +api_version = '0.1' + +casilda_sources = [ + 'casilda_compositor.c', + 'casilda_wayland_source.c', +] + +casilda_headers = [ + 'casilda.h', + 'casilda_compositor.h', + 'casilda_wayland_source.h', +] + +version_split = meson.project_version().split('.') +version_conf = configuration_data() +version_conf.set('VERSION', meson.project_version()) +version_conf.set('MAJOR_VERSION', version_split[0]) +version_conf.set('MINOR_VERSION', version_split[1]) +version_conf.set('MICRO_VERSION', version_split[2]) + +configure_file( + input: 'casilda-version.h.in', + output: 'casilda-version.h', + configuration: version_conf, + install: true, + install_dir: get_option('includedir') / 'casilda' +) + +gtk4_dep = dependency('gtk4', version: '>= 4.14') +wlroots_dep = dependency('wlroots', version: '>= 0.18') wayland_server_dep = dependency('wayland-server', version: '>=1.22') epoxy_dep = dependency('epoxy', version: '>=1.5') xkbcommon = dependency( @@ -30,7 +59,7 @@ wayland_scanner = find_program( native: true, ) -lib_dependencies = [ +casilda_deps = [ gtk4_dep, wlroots_dep, xkbcommon, @@ -43,7 +72,7 @@ lib_dependencies = [ lib_c_args = [] if xkbcommon_x11_dep.found() and x11_xcb_dep.found() - lib_dependencies += [xkbcommon_x11_dep, x11_xcb_dep] + casilda_deps += [xkbcommon_x11_dep, x11_xcb_dep] lib_c_args += ['-DHAVE_X11_XCB=1'] endif @@ -66,32 +95,33 @@ wayland_protocol_sources = [ xdg_shell_server_h, ] -sources = [ - 'cmb_compositor.c', - 'cmb_wayland_source.c', -] - -headers = [ - 'cambalache.h', - 'cmb_wayland_source.h', - 'cmb_compositor.h', -] - -cambalache = shared_library('cambalache-1', - sources + wayland_protocol_sources, - dependencies: lib_dependencies, - c_args : lib_c_args, - install: true, +casilda_lib = shared_library('casilda-' + api_version, + casilda_sources + wayland_protocol_sources, + dependencies: casilda_deps, + c_args: lib_c_args, + install: true, ) -gnome.generate_gir( - cambalache, - sources: sources + headers, - includes: 'Gtk-4.0', - nsversion: '1.0', - namespace: 'Cambalache', - identifier_prefix: 'Cmb', - symbol_prefix: 'cmb', - header: 'cambalache.h', - install: true, +install_headers(casilda_headers, subdir: 'casilda') + +pkg = import('pkgconfig') +pkg.generate( + description: 'A simple Gtk wayland compositor widget', + libraries: casilda_lib, + name: 'casilda', + filebase: 'casilda-' + api_version, + version: meson.project_version(), + subdirs: 'casilda', + requires: 'gtk-4.0', + install_dir: get_option('libdir') / 'pkgconfig' +) + +gnome = import('gnome') +casilda_gir = gnome.generate_gir(casilda_lib, + nsversion: api_version, + namespace: 'Casilda', + symbol_prefix: 'casilda', + identifier_prefix: 'Casilda', + includes: [ 'Gtk-4.0' ], + install: true, )