././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.428963 weston-8.0.0/0000755000175000017460000000000000000000000013317 5ustar00simonwheel00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.308963 weston-8.0.0/.editorconfig0000644000175000017460000000034200000000000015773 0ustar00simonwheel00000000000000root = true [*] charset = utf-8 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true indent_style = tab indent_size = 8 max_line_length = 80 [*.xml] indent_style = space indent_size = 2 tab_width = 8 ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.308963 weston-8.0.0/CONTRIBUTING.md0000644000175000017460000003556000000000000015561 0ustar00simonwheel00000000000000Contributing to Weston ======================= Finding something to work on ---------------------------- Weston's development is [tracked on GitLab](https://gitlab.freedesktop.org/wayland/weston). In addition to reviewing code submissions (see below), we use the issue tracker to discuss both bugfixes and development of new features. The '[good for new contributors](https://gitlab.freedesktop.org/wayland/weston/issues?label_name%5B%5D=Good+for+new+contributors)' label is used for issues the development team thinks are a good place to begin working on Weston. These issues cover features or bugfixes which are small, self-contained, don't require much specific background knowledge, and aren't blocked by more complex work. If you have picked an issue you would like to work on, you may want to mention in the issue tracker that you would like to pick it up. You can also discuss it with the developers in the issue tracker, or on the [mailing list](https://lists.freedesktop.org/mailman/listinfo/wayland-devel). Many developers also use IRC through [Freenode](https://freenode.net)'s `#wayland` channel; however you may need to wait some time for a response on IRC, which requires keeping your client connected. If you cannot stay for a long time (potentially some hours due to timezone differences), then you may want to send your question to the list or issue tracker instead. Sending patches --------------- Patches should be sent via [GitLab merge requests](https://docs.gitlab.com/ce/gitlab-basics/add-merge-request.html). Weston is [hosted on freedesktop.org's GitLab](https://gitlab.freedesktop.org/wayland/weston/): in order to submit code, you should create an account on this GitLab instance, fork the core Weston repository, push your changes to a branch in your new repository, and then submit these patches for review through a merge request. Weston formerly accepted patches via `git-send-email`, sent to **wayland-devel\@lists.freedesktop.org**; these were [tracked using Patchwork](https://patchwork.freedesktop.org/projects/wayland/). Some old patches continue to be sent this way, and we may accept small new patches sent to the list, but please send all new patches through GitLab merge requests. Formatting and separating commits --------------------------------- Unlike many projects using GitHub and GitLab, Weston has a [linear, 'recipe' style history](http://www.bitsnbites.eu/git-history-work-log-vs-recipe/). This means that every commit should be small, digestible, stand-alone, and functional. Rather than a purely chronological commit history like this: doc: final docs for view transforms fix tests when disabled, redo broken doc formatting better transformed-view iteration (thanks Hannah!) try to catch more cases in tests tests: add new spline test fix compilation on splines doc: notes on reticulating splines compositor: add spline reticulation for view transforms we aim to have a clean history which only reflects the final state, broken up into functional groupings: compositor: add spline reticulation for view transforms compositor: new iterator for view transforms tests: add view-transform correctness tests doc: fix Doxygen formatting for view transforms This ensures that the final patch series only contains the final state, without the changes and missteps taken along the development process. The first line of a commit message should contain a prefix indicating what part is affected by the patch followed by one sentence that describes the change. For examples: compositor-drm: Support modifiers for drm_fb and input: do not forward unmatched touch-ups If in doubt what prefix to use, look at other commits that change the same file(s) as the patch being sent. The body of the commit message should describe what the patch changes and why, and also note any particular side effects. This shouldn't be empty on most of the cases. It shouldn't take a lot of effort to write a commit message for an obvious change, so an empty commit message body is only acceptable if the questions "What?" and "Why?" are already answered on the one-line summary. The lines of the commit message should have at most 76 characters, to cope with the way git log presents them. See [notes on commit messages] for a recommended reading on writing commit messages. Your patches should also include a Signed-off-by line with your name and email address which indicates that you agree to the [Developer's Certificate of Origin 1.1](DCO-1.1.txt). If you're not the patch's original author, you should also gather S-o-b's by them (and/or whomever gave the patch to you.) The significance of this is that it certifies that you created the patch, that it was created under an appropriate open source license, or provided to you under those terms. This lets us indicate a chain of responsibility for the copyright status of the code. We won't reject patches that lack S-o-b, but it is strongly recommended. When you re-send patches, revised or not, it would be very good to document the changes compared to the previous revision in the commit message and/or the merge request. If you have already received Reviewed-by or Acked-by tags, you should evaluate whether they still apply and include them in the respective commit messages. Otherwise the tags may be lost, reviewers miss the credit they deserve, and the patches may cause redundant review effort. Tracking patches and following up --------------------------------- Once submitted to GitLab, your patches will be reviewed by the Weston development team on GitLab. Review may be entirely positive and result in your code landing instantly, in which case, great! You're done. However, we may ask you to make some revisions: fixing some bugs we've noticed, working to a slightly different design, or adding documentation and tests. If you do get asked to revise the patches, please bear in mind the notes above. You should use `git rebase -i` to make revisions, so that your patches follow the clear linear split documented above. Following that split makes it easier for reviewers to understand your work, and to verify that the code you're submitting is correct. A common request is to split single large patch into multiple patches. This can happen, for example, if when adding a new feature you notice a bug in Weston's core which you need to fix to progress. Separating these changes into separate commits will allow us to verify and land the bugfix quickly, pushing part of your work for the good of everyone, whilst revision and discussion continues on the larger feature part. It also allows us to direct you towards reviewers who best understand the different areas you are working on. When you have made any requested changes, please rebase the commits, verify that they still individually look good, then force-push your new branch to GitLab. This will update the merge request and notify everyone subscribed to your merge request, so they can review it again. There are also [many GitLab CLI clients](https://about.gitlab.com/applications/#cli-clients), if you prefer to avoid the web interface. It may be difficult to follow review comments without using the web interface though, so we do recommend using this to go through the review process, even if you use other clients to track the list of available patches. Coding style ------------ You should follow the style of the file you're editing. In general, we try to follow the rules below. **Note: this file uses spaces due to markdown rendering issues for tabs. Code must be indented using tabs.** - indent with tabs, and a tab is always 8 characters wide - opening braces are on the same line as the if statement; - no braces in an if-body with just one statement; - if one of the branches of an if-else condition has braces, then the other branch should also have braces; - there is always an empty line between variable declarations and the code; ```c static int my_function(void) { int a = 0; if (a) b(); else c(); if (a) { b(); c(); } else { d(); } } ``` - lines should be less than 80 characters wide; - when breaking lines with functions calls, the parameters are aligned with the opening parentheses; - when assigning a variable with the result of a function call, if the line would be longer we break it around the equal '=' sign if it makes sense; ```c long_variable_name = function_with_a_really_long_name(parameter1, parameter2, parameter3, parameter4); x = function_with_a_really_long_name(parameter1, parameter2, parameter3, parameter4); ``` Conduct ======= As a freedesktop.org project, Wayland follows the Contributor Covenant, found at: https://www.freedesktop.org/wiki/CodeOfConduct Please conduct yourself in a respectful and civilised manner when interacting with community members on mailing lists, IRC, or bug trackers. The community represents the project as a whole, and abusive or bullying behaviour is not tolerated by the project. Licensing ========= Weston is licensed with the intention to be usable anywhere X.org is. Originally, X.org was covered under the MIT X11 license, but changed to the MIT Expat license. Similarly, Weston was covered initially as MIT X11 licensed, but changed to the MIT Expat license, following in X.org's footsteps. Other than wording, the two licenses are substantially the same, with the exception of a no-advertising clause in X11 not included in Expat. New source code files should specify the MIT Expat license in their boilerplate, as part of the copyright statement. Review ====== All patches, even trivial ones, require at least one positive review (Reviewed-by). Additionally, if no Reviewed-by's have been given by people with commit access, there needs to be at least one Acked-by from someone with commit access. A person with commit access is expected to be able to evaluate the patch with respect to the project scope and architecture. The below review guidelines are intended to be interpreted in spirit, not by the letter. There may be circumstances where some guidelines are better ignored. We rely very much on the judgement of reviewers and commit rights holders. During review, the following matters should be checked: - The commit message explains why the change is being made. - The code fits the project's scope. - The code license is the same MIT licence the project generally uses. - Stable ABI or API is not broken. - Stable ABI or API additions must be justified by actual use cases, not only by speculation. They must also be documented, and it is strongly recommended to include tests exercising the additions in the test suite. - The code fits the existing software architecture, e.g. no layering violations. - The code is correct and does not introduce new failures for existing users, does not add new corner-case bugs, and does not introduce new compiler warnings. - The patch does what it says in the commit message and changes nothing else. - The patch is a single logical change. If the commit message addresses multiple points, it is a hint that the commit might need splitting up. - A bug fix should target the underlying root cause instead of hiding symptoms. If a complete fix is not practical, partial fixes are acceptable if they come with code comments and filed Gitlab issues for the remaining bugs. - The bug root cause rule applies to external software components as well, e.g. do not work around kernel driver issues in userspace. - The test suite passes. - The code does not depend on API or ABI which has no working free open source implementation. - The code is not dead or untestable. E.g. if there are no free open source software users for it then it is effectively dead code. - The code is written to be easy to understand, or if code cannot be clear enough on its own there are code comments to explain it. - The code is minimal, i.e. prefer refactor and re-use when possible unless clarity suffers. - The code adheres to the style guidelines. - In a patch series, every intermediate step adheres to the above guidelines. Commit rights ============= Commit rights will be granted to anyone who requests them and fulfills the below criteria: - Submitted some (10 as a rule of thumb) non-trivial (not just simple spelling fixes and whitespace adjustment) patches that have been merged already. - Are actively participating in public discussions about their work (on the mailing list or IRC). This should not be interpreted as a requirement to review other peoples patches but just make sure that patch submission isn't one-way communication. Cross-review is still highly encouraged. - Will be regularly contributing further patches. This includes regular contributors to other parts of the open source graphics stack who only do the occasional development in this project. - Agrees to use their commit rights in accordance with the documented merge criteria, tools, and processes. To apply for commit rights, create a new issue in gitlab for the respective project and give it the "accounts" label. Committers are encouraged to request their commit rights get removed when they no longer contribute to the project. Commit rights will be reinstated when they come back to the project. Maintainers and committers should encourage contributors to request commit rights, especially junior contributors tend to underestimate their skills. Stabilising for releases ======================== A release cycle ends with a stable release which also starts a new cycle and lifts any code freezes. Gradual code freezing towards a stable release starts with an alpha release. The release stages of a cycle are: - **Alpha release**: Signified by version number #.#.91. Major features must have landed before this. Major features include invasive code motion and refactoring, high risk changes, and new stable library ABI. - **Beta release**: Signified by version number #.#.92. Minor features must have landed before this. Minor features include all new features that are not major, low risk changes, clean-ups, and documentation. Stable ABI that was new in the alpha release can be removed before a beta release if necessary. - **Release candidates (RC)**: Signified by version number #.#.93 and up to #.#.99. Bug fixes that are not release critical must have landed before this. Release critical bug fixes can still be landed after this, but they may call for another RC. - **Stable release**: Signified by version number #.#.0. Ideally no changes since the last RC. Mind that version #.#.90 is never released. It is used during development when no code freeze is in effect. Stable branches and point releases are not covered by the above. [git documentation]: http://git-scm.com/documentation [notes on commit messages]: http://who-t.blogspot.de/2009/12/on-commit-messages.html ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.308963 weston-8.0.0/COPYING0000644000175000017460000000256200000000000014357 0ustar00simonwheel00000000000000Copyright © 2008-2012 Kristian Høgsberg Copyright © 2010-2012 Intel Corporation Copyright © 2010-2011 Benjamin Franzke Copyright © 2011-2012 Collabora, Ltd. Copyright © 2010 Red Hat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- The above is the version of the MIT "Expat" License used by X.org: http://cgit.freedesktop.org/xorg/xserver/tree/COPYING ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.308963 weston-8.0.0/DCO-1.1.txt0000644000175000017460000000261500000000000014766 0ustar00simonwheel00000000000000Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 1 Letterman Drive Suite D4700 San Francisco, CA, 94129 Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.308963 weston-8.0.0/README.md0000644000175000017460000003406300000000000014604 0ustar00simonwheel00000000000000Weston ====== ![screenshot of skeletal Weston desktop](doc/wayland-screenshot.jpg) Weston is the reference implementation of a Wayland compositor, as well as a useful environment in and of itself. Out of the box, Weston provides a very basic desktop, or a full-featured environment for non-desktop uses such as automotive, embedded, in-flight, industrial, kiosks, set-top boxes and TVs. It also provides a library allowing other projects to build their own full-featured environments on top of Weston's core. The core focus of Weston is correctness and reliability. Weston aims to be lean and fast, but more importantly, to be predictable. Whilst Weston does have known bugs and shortcomings, we avoid unknown or variable behaviour as much as possible, including variable performance such as occasional spikes in frame display time. A small suite of example or demo clients are also provided: though they can be useful in themselves, their main purpose is to be an example or test case for others building compositors or clients. If you are after a more mainline desktop experience, the [GNOME](https://www.gnome.org) and [KDE](https://www.kde.org) projects provide full-featured desktop environments built on the Wayland protocol. Many other projects also exist providing Wayland clients and desktop environments: you are not limited to just what you can find in Weston. Reporting issues and contributing ================================= Weston's development is [hosted on freedesktop.org GitLab](https://gitlab.freedesktop.org/wayland/weston/). Please also see [the contributing document](CONTRIBUTING.md), which details how to make code or non-technical contributions to Weston. Building Weston =============== Weston is built using [Meson](https://mesonbuild.com/). Weston often depends on the current release versions of [Wayland](https://gitlab.freedesktop.org/wayland/wayland) and [wayland-protocols](https://cgit.freedesktop.org/wayland/wayland-protocols). If necessary, the latest Meson can be installed as a user with: $ pip3 install --user meson Weston's Meson build does not do autodetection and it defaults to all features enabled, which means you likely hit missing dependencies on the first try. If a dependency is avoidable through a build option, the error message should tell you what option can be used to avoid it. You may need to disable several features if you want to avoid certain dependencies. $ git clone https://gitlab.freedesktop.org/wayland/weston.git $ cd weston $ meson build/ --prefix=... $ ninja -C build/ install $ cd .. The `meson` command populates the build directory. This step can fail due to missing dependencies. Any build options you want can be added on that line, e.g. `meson build/ --prefix=... -Ddemo-clients=false`. All the build options can be found in the file [meson_options.txt](meson_options.txt). Once the build directory has been successfully populated, you can inspect the configuration with `meson configure build/`. If you need to change an option, you can do e.g. `meson configure build/ -Ddemo-clients=false`. Every push to the Weston master repository and its forks is built using GitLab CI. [Reading the configuration](.gitlab-ci.yml) may provide a useful example of how to build and install Weston. More [detailed documentation on building Weston](https://wayland.freedesktop.org/building.html) is available on the Wayland site. There are also more details on [how to run and write tests](https://wayland.freedesktop.org/testing.html). For building the documentation see [weston-doc](#weston-doc). Running Weston ============== Once Weston is installed, most users can simply run it by typing `weston`. This will launch Weston inside whatever environment you launch it from: when launched from a text console, it will take over that console. When launched from inside an existing Wayland or X11 session, it will start a 'nested' instance of Weston inside a window in that session. Help is available by running `weston --help`, or `man weston`, which will list the available configuration options and display backends. It can also be configured through a file on disk; more information on this can be found through `man weston.ini`. In some special cases, such as when running remotely or without logind's session control, Weston may not be able to run directly from a text console. In these situations, you can instead execute the `weston-launch` helper, which will gain privileged access to input and output devices by running as root, then granting access to the main Weston binary running as your user. Running Weston this way is not recommended unless necessary. Weston-doc ========== For documenting weston we use [sphinx](http://www.sphinx-doc.org/en/master/) together with [breathe](https://breathe.readthedocs.io/en/latest/) that understands XMLs databases generated by doxygen. So far, this is a compromise until better tools are available in order to remove the doxygen dependency. You should be able to install both sphinx and breathe extension using pip3 command, or your package manager. Doxygen should be available using your distribution package manager. Once those are set-up, run `meson` with `-Ddoc=true` option in order to enable building the documentation. Installation will place the documentation in the prefix's path under datadir (i.e., `share/doc`). Adding and improving documentation ---------------------------------- For re-generating the documentation a special `docs` target has been added. Although first time you build (and subsequently install) weston, you'll see the documentation being built, updates to the spinx documentation files or to the source files will only be updated when using `docs` target! Example: ~~~~ $ ninja install # generates and installs the documentation # time passes, hack hack, add doc in sources or rST files $ ninja install # not sufficient, docs will not be updated $ ninja docs && ninja install # run 'docs' then install ~~~~ Improving/adding documentation can be done by modifying rST files under `doc/sphinx/` directory or by modifying the source code using doxygen directives. Libweston ========= Libweston is an effort to separate the re-usable parts of Weston into a library. Libweston provides most of the boring and tedious bits of correctly implementing core Wayland protocols and interfacing with input and output systems, so that people who just want to write a new "Wayland window manager" (WM) or a small desktop environment (DE) can focus on the WM part. Libweston was first introduced in Weston 1.12, and is expected to continue evolving through many Weston releases before it achieves a stable API and feature completeness. Libweston's primary purpose is exporting an API for creating Wayland compositors. Libweston's secondary purpose is to export the weston_config API so that third party plugins and helper programs can read `weston.ini` if they want to. However, these two scopes are orthogonal and independent. At no point will the compositor functionality use or depend on the weston_config functionality. API/ABI (in)stability and parallel installability ------------------------------------------------- As libweston's API surface is huge, it is impossible to get it right in one go. Therefore developers reserve the right to break the API/ABI and bump the major version to signify that. For git snapshots of the master branch, the API/ABI can break any time without warning. Libweston major can be bumped only once during a development cycle. This should happen on the first patch that breaks the API or ABI. Further breaks before the next Weston major.0.0 release do not cause a bump. This means that libweston API and ABI are allowed to break also after an alpha release, up to the final release. However, breaks after alpha should be judged by the usual practices for allowing minor features, fixes only, or critical fixes only. To make things tolerable for libweston users despite API/ABI breakages, different libweston major versions are designed to be perfectly parallel-installable. This way external projects can easily depend on a particular API/ABI-version. Thus they do not have to fight over which ABI-version is installed in a user's system. This allows a user to install many different compositors each requiring a different libweston ABI-version without tricks or conflicts. Note, that versions of Weston itself will not be parallel-installable, only libweston is. For more information about parallel installability, see http://ometer.com/parallel.html Versioning scheme ----------------- In order to provide consistent, easy to use versioning, libweston follows the rules in the Apache Portable Runtime Project http://apr.apache.org/versioning.html. The document provides the full details, with the gist summed below: - Major - backward incompatible changes. - Minor - new backward compatible features. - Patch - internal (implementation specific) fixes. Weston and libweston have separate version numbers in meson.build. All releases are made by the Weston version number. Libweston version number matches the Weston version number in all releases except maybe pre-releases. Pre-releases have the Weston micro version 91 or greater. A pre-release is allowed to install a libweston version greater than the Weston version in case libweston major was bumped. In that case, the libweston version must be Weston major + 1. Pkg-config files are named after libweston major, but carry the Weston version number. This means that Weston pre-release 2.1.91 may install libweston-3.pc for the future libweston 3.0.0, but the .pc file says the version is still 2.1.91. When a libweston user wants to depend on the fully stable API and ABI of a libweston major, he should use (e.g. for major 3): PKG_CHECK_MODULES(LIBWESTON, [libweston-3 >= 3.0.0]) Depending only on libweston-3 without a specific version number still allows pre-releases which might have different API or ABI. Forward compatibility --------------------- Inspired by ATK, Qt and KDE programs/libraries, libjpeg-turbo, GDK, NetworkManager, js17, lz4 and many others, libweston uses a macro to restrict the API visible to the developer - REQUIRE_LIBWESTON_API_VERSION. Note that different projects focus on different aspects - upper and/or lower version check, default to visible/hidden old/new symbols and so on. libweston aims to guard all newly introduced API, in order to prevent subtle breaks that a simple recompile (against a newer version) might cause. The macro is of the format 0x$MAJOR$MINOR and does not include PATCH version. As mentioned in the Versioning scheme section, the latter does not reflect any user visible API changes, thus should be not considered part of the API version. All new symbols should be guarded by the macro like the example given below: ~~~~ #if REQUIRE_LIBWESTON_API_VERSION >= 0x0101 bool weston_ham_sandwich(void); #endif ~~~~ In order to use the said symbol, the one will have a similar code in their configure.ac: ~~~~ PKG_CHECK_MODULES(LIBWESTON, [libweston-1 >= 1.1]) AC_DEFINE(REQUIRE_LIBWESTON_API_VERSION, [0x0101]) ~~~~ If the user is _not_ interested in forward compatibility, they can use 0xffff or similar high value. Yet doing so is not recommended. Libweston design goals ---------------------- The high-level goal of libweston is to decouple the compositor from the shell implementation (what used to be shell plugins). Thus, instead of launching 'weston' with various arguments to choose the shell, one would launch the shell itself, e.g. 'weston-desktop', 'weston-ivi', 'orbital', etc. The main executable (the hosting program) will implement the shell, while libweston will be used for a fundamental compositor implementation. Libweston is also intended for use by other project developers who want to create new "Wayland WMs". Details: - All configuration and user interfaces will be outside of libweston. This includes command line parsing, configuration files, and runtime (graphical) UI. - The hosting program (main executable) will be in full control of all libweston options. Libweston should not have user settable options that would work behind the hosting program's back, except perhaps debugging features and such. - Signal handling will be outside of libweston. - Child process execution and management will be outside of libweston. - The different backends (drm, fbdev, x11, etc) will be an internal detail of libweston. Libweston will not support third party backends. However, hosting programs need to handle backend-specific configuration due to differences in behaviour and available features. - Renderers will be libweston internal details too, though again the hosting program may affect the choice of renderer if the backend allows, and maybe set renderer-specific options. - plugin design ??? - xwayland ??? - weston-launch is still with libweston even though it can only launch Weston and nothing else. We would like to allow it to launch any compositor, but since it gives by design root access to input devices and DRM, how can we restrict it to intended programs? There are still many more details to be decided. For packagers ------------- Always build Weston with --with-cairo=image. The Weston project is (will be) intended to be split into several binary packages, each with its own dependencies. The maximal split would be roughly like this: - libweston (minimal dependencies): + headless backend + wayland backend - gl-renderer (depends on GL libs etc.) - drm-backend (depends on libdrm, libgbm, libudev, libinput, ...) - x11-backend (depends of X11/xcb libs) - xwayland (depends on X11/xcb libs) - fbdev-backend (depends on libudev...) - rdp-backend (depends on freerdp) - weston (the executable, not parallel-installable): + desktop shell + ivi-shell + fullscreen shell + weston-info, weston-terminal, etc. we install by default + screen-share - weston demos (not parallel-installable) + weston-simple-* programs + possibly all the programs we build but do not install by default - and possibly more... Everything should be parallel-installable across libweston major ABI-versions (libweston-1.so, libweston-2.so, etc.), except those explicitly mentioned. Weston's build may not sanely allow this yet, but this is the intention. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3356297 weston-8.0.0/clients/0000755000175000017460000000000000000000000014760 5ustar00simonwheel00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3122964 weston-8.0.0/clients/calibrator.c0000644000175000017460000001730100000000000017250 0ustar00simonwheel00000000000000/* * Copyright © 2012 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "window.h" #include "shared/helpers.h" #include /* Our points for the calibration must be not be on a line */ static const struct { float x_ratio, y_ratio; } test_ratios[] = { { 0.20, 0.40 }, { 0.80, 0.60 }, { 0.40, 0.80 } }; struct calibrator { struct tests { int32_t drawn_x, drawn_y; int32_t clicked_x, clicked_y; } tests[ARRAY_LENGTH(test_ratios)]; int current_test; struct display *display; struct window *window; struct widget *widget; }; /* * Calibration algorithm: * * The equation we want to apply at event time where x' and y' are the * calibrated co-ordinates. * * x' = Ax + By + C * y' = Dx + Ey + F * * For example "zero calibration" would be A=1.0 B=0.0 C=0.0, D=0.0, E=1.0, * and F=0.0. * * With 6 unknowns we need 6 equations to find the constants: * * x1' = Ax1 + By1 + C * y1' = Dx1 + Ey1 + F * ... * x3' = Ax3 + By3 + C * y3' = Dx3 + Ey3 + F * * In matrix form: * * x1' x1 y1 1 A * x2' = x2 y2 1 x B * x3' x3 y3 1 C * * So making the matrix M we can find the constants with: * * A x1' * B = M^-1 x x2' * C x3' * * (and similarly for D, E and F) * * For the calibration the desired values x, y are the same values at which * we've drawn at. * */ static void finish_calibration (struct calibrator *calibrator) { struct weston_matrix m; struct weston_matrix inverse; struct weston_vector x_calib, y_calib; int i; /* * x1 y1 1 0 * x2 y2 1 0 * x3 y3 1 0 * 0 0 0 1 */ memset(&m, 0, sizeof(m)); for (i = 0; i < (int)ARRAY_LENGTH(test_ratios); i++) { m.d[i] = calibrator->tests[i].clicked_x; m.d[i + 4] = calibrator->tests[i].clicked_y; m.d[i + 8] = 1; } m.d[15] = 1; weston_matrix_invert(&inverse, &m); memset(&x_calib, 0, sizeof(x_calib)); memset(&y_calib, 0, sizeof(y_calib)); for (i = 0; i < (int)ARRAY_LENGTH(test_ratios); i++) { x_calib.f[i] = calibrator->tests[i].drawn_x; y_calib.f[i] = calibrator->tests[i].drawn_y; } /* Multiples into the vector */ weston_matrix_transform(&inverse, &x_calib); weston_matrix_transform(&inverse, &y_calib); printf ("Calibration values: %f %f %f %f %f %f\n", x_calib.f[0], x_calib.f[1], x_calib.f[2], y_calib.f[0], y_calib.f[1], y_calib.f[2]); exit(0); } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct calibrator *calibrator = data; int32_t x, y; if (state == WL_POINTER_BUTTON_STATE_PRESSED && button == BTN_LEFT) { input_get_position(input, &x, &y); calibrator->tests[calibrator->current_test].clicked_x = x; calibrator->tests[calibrator->current_test].clicked_y = y; calibrator->current_test--; if (calibrator->current_test < 0) finish_calibration(calibrator); } widget_schedule_redraw(widget); } static void touch_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data) { struct calibrator *calibrator = data; calibrator->tests[calibrator->current_test].clicked_x = x; calibrator->tests[calibrator->current_test].clicked_y = y; calibrator->current_test--; if (calibrator->current_test < 0) finish_calibration(calibrator); widget_schedule_redraw(widget); } static void redraw_handler(struct widget *widget, void *data) { struct calibrator *calibrator = data; struct rectangle allocation; cairo_surface_t *surface; cairo_t *cr; int32_t drawn_x, drawn_y; widget_get_allocation(calibrator->widget, &allocation); surface = window_get_surface(calibrator->window); cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); cairo_paint(cr); drawn_x = test_ratios[calibrator->current_test].x_ratio * allocation.width; drawn_y = test_ratios[calibrator->current_test].y_ratio * allocation.height; calibrator->tests[calibrator->current_test].drawn_x = drawn_x; calibrator->tests[calibrator->current_test].drawn_y = drawn_y; cairo_translate(cr, drawn_x, drawn_y); cairo_set_line_width(cr, 2.0); cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); cairo_move_to(cr, 0, -10.0); cairo_line_to(cr, 0, 10.0); cairo_stroke(cr); cairo_move_to(cr, -10.0, 0); cairo_line_to(cr, 10.0, 0.0); cairo_stroke(cr); cairo_destroy(cr); cairo_surface_destroy(surface); } static struct calibrator * calibrator_create(struct display *display, bool enable_button) { struct calibrator *calibrator; calibrator = malloc(sizeof *calibrator); if (calibrator == NULL) return NULL; calibrator->window = window_create(display); calibrator->widget = window_add_widget(calibrator->window, calibrator); window_set_title(calibrator->window, "Wayland calibrator"); calibrator->display = display; calibrator->current_test = ARRAY_LENGTH(test_ratios) - 1; if (enable_button) widget_set_button_handler(calibrator->widget, button_handler); widget_set_touch_down_handler(calibrator->widget, touch_handler); widget_set_redraw_handler(calibrator->widget, redraw_handler); window_set_fullscreen(calibrator->window, 1); return calibrator; } static void calibrator_destroy(struct calibrator *calibrator) { widget_destroy(calibrator->widget); window_destroy(calibrator->window); free(calibrator); } static void help(const char *name) { fprintf(stderr, "Usage: %s [args...]\n", name); fprintf(stderr, " -m, --enable-mouse Enable mouse for testing the touchscreen\n"); fprintf(stderr, " -h, --help Display this help message\n"); } int main(int argc, char *argv[]) { struct display *display; struct calibrator *calibrator; int c; bool enable_mouse = 0; struct option opts[] = { { "enable-mouse", no_argument, NULL, 'm' }, { "help", no_argument, NULL, 'h' }, { 0, 0, NULL, 0 } }; while ((c = getopt_long(argc, argv, "mh", opts, NULL)) != -1) { switch (c) { case 'm': enable_mouse = 1; break; case 'h': help(argv[0]); exit(EXIT_FAILURE); default: break; } } display = display_create(&argc, argv); if (display == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } calibrator = calibrator_create(display, enable_mouse); if (!calibrator) return -1; display_run(display); calibrator_destroy(calibrator); display_destroy(display); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3122964 weston-8.0.0/clients/clickdot.c0000644000175000017460000002040700000000000016723 0ustar00simonwheel00000000000000/* * Copyright © 2010 Intel Corporation * Copyright © 2012 Collabora, Ltd. * Copyright © 2012 Jonas Ådahl * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "window.h" #include "shared/helpers.h" #include "shared/xalloc.h" struct clickdot { struct display *display; struct window *window; struct widget *widget; cairo_surface_t *buffer; struct { int32_t x, y; } dot; struct { int32_t x, y; int32_t old_x, old_y; } line; int reset; struct input *cursor_timeout_input; struct toytimer cursor_timeout; }; static void draw_line(struct clickdot *clickdot, cairo_t *cr, struct rectangle *allocation) { cairo_t *bcr; cairo_surface_t *tmp_buffer = NULL; if (clickdot->reset) { tmp_buffer = clickdot->buffer; clickdot->buffer = NULL; clickdot->line.x = -1; clickdot->line.y = -1; clickdot->line.old_x = -1; clickdot->line.old_y = -1; clickdot->reset = 0; } if (clickdot->buffer == NULL) { clickdot->buffer = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation->width, allocation->height); bcr = cairo_create(clickdot->buffer); cairo_set_source_rgba(bcr, 0, 0, 0, 0); cairo_rectangle(bcr, 0, 0, allocation->width, allocation->height); cairo_fill(bcr); } else bcr = cairo_create(clickdot->buffer); if (tmp_buffer) { cairo_set_source_surface(bcr, tmp_buffer, 0, 0); cairo_rectangle(bcr, 0, 0, allocation->width, allocation->height); cairo_clip(bcr); cairo_paint(bcr); cairo_surface_destroy(tmp_buffer); } if (clickdot->line.x != -1 && clickdot->line.y != -1) { if (clickdot->line.old_x != -1 && clickdot->line.old_y != -1) { cairo_set_line_width(bcr, 2.0); cairo_set_source_rgb(bcr, 1, 1, 1); cairo_translate(bcr, -allocation->x, -allocation->y); cairo_move_to(bcr, clickdot->line.old_x, clickdot->line.old_y); cairo_line_to(bcr, clickdot->line.x, clickdot->line.y); cairo_stroke(bcr); } clickdot->line.old_x = clickdot->line.x; clickdot->line.old_y = clickdot->line.y; } cairo_destroy(bcr); cairo_set_source_surface(cr, clickdot->buffer, allocation->x, allocation->y); cairo_set_operator(cr, CAIRO_OPERATOR_ADD); cairo_rectangle(cr, allocation->x, allocation->y, allocation->width, allocation->height); cairo_clip(cr); cairo_paint(cr); } static void redraw_handler(struct widget *widget, void *data) { static const double r = 10.0; struct clickdot *clickdot = data; cairo_surface_t *surface; cairo_t *cr; struct rectangle allocation; widget_get_allocation(clickdot->widget, &allocation); surface = window_get_surface(clickdot->window); cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_set_source_rgba(cr, 0, 0, 0, 0.8); cairo_fill(cr); draw_line(clickdot, cr, &allocation); cairo_translate(cr, clickdot->dot.x + 0.5, clickdot->dot.y + 0.5); cairo_set_line_width(cr, 1.0); cairo_set_source_rgb(cr, 0.1, 0.9, 0.9); cairo_move_to(cr, 0.0, -r); cairo_line_to(cr, 0.0, r); cairo_move_to(cr, -r, 0.0); cairo_line_to(cr, r, 0.0); cairo_arc(cr, 0.0, 0.0, r, 0.0, 2.0 * M_PI); cairo_stroke(cr); cairo_destroy(cr); cairo_surface_destroy(surface); } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { struct clickdot *clickdot = data; window_schedule_redraw(clickdot->window); } static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data) { struct clickdot *clickdot = data; if (state == WL_KEYBOARD_KEY_STATE_RELEASED) return; switch (sym) { case XKB_KEY_Escape: display_exit(clickdot->display); break; } } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct clickdot *clickdot = data; if (state == WL_POINTER_BUTTON_STATE_PRESSED && button == BTN_LEFT) input_get_position(input, &clickdot->dot.x, &clickdot->dot.y); widget_schedule_redraw(widget); } static void cursor_timeout_reset(struct clickdot *clickdot) { toytimer_arm_once_usec(&clickdot->cursor_timeout, 500 * 1000); } static int motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct clickdot *clickdot = data; clickdot->line.x = x; clickdot->line.y = y; window_schedule_redraw(clickdot->window); cursor_timeout_reset(clickdot); clickdot->cursor_timeout_input = input; return CURSOR_BLANK; } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct clickdot *clickdot = data; clickdot->reset = 1; } static void leave_handler(struct widget *widget, struct input *input, void *data) { struct clickdot *clickdot = data; clickdot->reset = 1; } static void cursor_timeout_func(struct toytimer *tt) { struct clickdot *clickdot = container_of(tt, struct clickdot, cursor_timeout); input_set_pointer_image(clickdot->cursor_timeout_input, CURSOR_LEFT_PTR); } static struct clickdot * clickdot_create(struct display *display) { struct clickdot *clickdot; clickdot = xzalloc(sizeof *clickdot); clickdot->window = window_create(display); clickdot->widget = window_frame_create(clickdot->window, clickdot); window_set_title(clickdot->window, "Wayland ClickDot"); clickdot->display = display; clickdot->buffer = NULL; window_set_key_handler(clickdot->window, key_handler); window_set_user_data(clickdot->window, clickdot); window_set_keyboard_focus_handler(clickdot->window, keyboard_focus_handler); widget_set_redraw_handler(clickdot->widget, redraw_handler); widget_set_button_handler(clickdot->widget, button_handler); widget_set_motion_handler(clickdot->widget, motion_handler); widget_set_resize_handler(clickdot->widget, resize_handler); widget_set_leave_handler(clickdot->widget, leave_handler); widget_schedule_resize(clickdot->widget, 500, 400); clickdot->dot.x = 250; clickdot->dot.y = 200; clickdot->line.x = -1; clickdot->line.y = -1; clickdot->line.old_x = -1; clickdot->line.old_y = -1; clickdot->reset = 0; toytimer_init(&clickdot->cursor_timeout, CLOCK_MONOTONIC, display, cursor_timeout_func); return clickdot; } static void clickdot_destroy(struct clickdot *clickdot) { toytimer_fini(&clickdot->cursor_timeout); if (clickdot->buffer) cairo_surface_destroy(clickdot->buffer); widget_destroy(clickdot->widget); window_destroy(clickdot->window); free(clickdot); } int main(int argc, char *argv[]) { struct display *display; struct clickdot *clickdot; display = display_create(&argc, argv); if (display == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } clickdot = clickdot_create(display); display_run(display); clickdot_destroy(clickdot); display_destroy(display); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3122964 weston-8.0.0/clients/cliptest.c0000644000175000017460000003646500000000000016771 0ustar00simonwheel00000000000000/* * Copyright © 2012 Collabora, Ltd. * Copyright © 2012 Rob Clark * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* cliptest: for debugging calculate_edges() function. * controls: * clip box position: mouse left drag, keys: w a s d * clip box size: mouse right drag, keys: i j k l * surface orientation: mouse wheel, keys: n m * surface transform disable key: r */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libweston/vertex-clipping.h" #include "shared/xalloc.h" #include "window.h" typedef float GLfloat; struct geometry { pixman_box32_t clip; pixman_box32_t surf; float s; /* sin phi */ float c; /* cos phi */ float phi; }; struct weston_view { struct { int enabled; } transform; struct geometry *geometry; }; static void weston_view_to_global_float(struct weston_view *view, float sx, float sy, float *x, float *y) { struct geometry *g = view->geometry; /* pure rotation around origin by sine and cosine */ *x = g->c * sx + g->s * sy; *y = -g->s * sx + g->c * sy; } /* ---------------------- copied begins -----------------------*/ /* Keep this in sync with what is in gl-renderer.c! */ #define max(a, b) (((a) > (b)) ? (a) : (b)) #define min(a, b) (((a) > (b)) ? (b) : (a)) /* * Compute the boundary vertices of the intersection of the global coordinate * aligned rectangle 'rect', and an arbitrary quadrilateral produced from * 'surf_rect' when transformed from surface coordinates into global coordinates. * The vertices are written to 'ex' and 'ey', and the return value is the * number of vertices. Vertices are produced in clockwise winding order. * Guarantees to produce either zero vertices, or 3-8 vertices with non-zero * polygon area. */ static int calculate_edges(struct weston_view *ev, pixman_box32_t *rect, pixman_box32_t *surf_rect, GLfloat *ex, GLfloat *ey) { struct clip_context ctx; int i, n; GLfloat min_x, max_x, min_y, max_y; struct polygon8 surf = { { surf_rect->x1, surf_rect->x2, surf_rect->x2, surf_rect->x1 }, { surf_rect->y1, surf_rect->y1, surf_rect->y2, surf_rect->y2 }, 4 }; ctx.clip.x1 = rect->x1; ctx.clip.y1 = rect->y1; ctx.clip.x2 = rect->x2; ctx.clip.y2 = rect->y2; /* transform surface to screen space: */ for (i = 0; i < surf.n; i++) weston_view_to_global_float(ev, surf.x[i], surf.y[i], &surf.x[i], &surf.y[i]); /* find bounding box: */ min_x = max_x = surf.x[0]; min_y = max_y = surf.y[0]; for (i = 1; i < surf.n; i++) { min_x = min(min_x, surf.x[i]); max_x = max(max_x, surf.x[i]); min_y = min(min_y, surf.y[i]); max_y = max(max_y, surf.y[i]); } /* First, simple bounding box check to discard early transformed * surface rects that do not intersect with the clip region: */ if ((min_x >= ctx.clip.x2) || (max_x <= ctx.clip.x1) || (min_y >= ctx.clip.y2) || (max_y <= ctx.clip.y1)) return 0; /* Simple case, bounding box edges are parallel to surface edges, * there will be only four edges. We just need to clip the surface * vertices to the clip rect bounds: */ if (!ev->transform.enabled) return clip_simple(&ctx, &surf, ex, ey); /* Transformed case: use a general polygon clipping algorithm to * clip the surface rectangle with each side of 'rect'. * The algorithm is Sutherland-Hodgman, as explained in * http://www.codeguru.com/cpp/misc/misc/graphics/article.php/c8965/Polygon-Clipping.htm * but without looking at any of that code. */ n = clip_transformed(&ctx, &surf, ex, ey); if (n < 3) return 0; return n; } /* ---------------------- copied ends -----------------------*/ static void geometry_set_phi(struct geometry *g, float phi) { g->phi = phi; g->s = sin(phi); g->c = cos(phi); } static void geometry_init(struct geometry *g) { g->clip.x1 = -50; g->clip.y1 = -50; g->clip.x2 = -10; g->clip.y2 = -10; g->surf.x1 = -20; g->surf.y1 = -20; g->surf.x2 = 20; g->surf.y2 = 20; geometry_set_phi(g, 0.0); } struct ui_state { uint32_t button; int down; int down_pos[2]; struct geometry geometry; }; struct cliptest { struct window *window; struct widget *widget; struct display *display; int fullscreen; struct ui_state ui; struct geometry geometry; struct weston_view view; }; static void draw_polygon_closed(cairo_t *cr, GLfloat *x, GLfloat *y, int n) { int i; cairo_move_to(cr, x[0], y[0]); for (i = 1; i < n; i++) cairo_line_to(cr, x[i], y[i]); cairo_line_to(cr, x[0], y[0]); } static void draw_polygon_labels(cairo_t *cr, GLfloat *x, GLfloat *y, int n) { char str[16]; int i; for (i = 0; i < n; i++) { snprintf(str, 16, "%d", i); cairo_move_to(cr, x[i], y[i]); cairo_show_text(cr, str); } } static void draw_coordinates(cairo_t *cr, double ox, double oy, GLfloat *x, GLfloat *y, int n) { char str[64]; int i; cairo_font_extents_t ext; cairo_font_extents(cr, &ext); for (i = 0; i < n; i++) { snprintf(str, 64, "%d: %14.9f, %14.9f", i, x[i], y[i]); cairo_move_to(cr, ox, oy + ext.height * (i + 1)); cairo_show_text(cr, str); } } static void draw_box(cairo_t *cr, pixman_box32_t *box, struct weston_view *view) { GLfloat x[4], y[4]; if (view) { weston_view_to_global_float(view, box->x1, box->y1, &x[0], &y[0]); weston_view_to_global_float(view, box->x2, box->y1, &x[1], &y[1]); weston_view_to_global_float(view, box->x2, box->y2, &x[2], &y[2]); weston_view_to_global_float(view, box->x1, box->y2, &x[3], &y[3]); } else { x[0] = box->x1; y[0] = box->y1; x[1] = box->x2; y[1] = box->y1; x[2] = box->x2; y[2] = box->y2; x[3] = box->x1; y[3] = box->y2; } draw_polygon_closed(cr, x, y, 4); } static void draw_geometry(cairo_t *cr, struct weston_view *view, GLfloat *ex, GLfloat *ey, int n) { struct geometry *g = view->geometry; float cx, cy; draw_box(cr, &g->surf, view); cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.4); cairo_fill(cr); weston_view_to_global_float(view, g->surf.x1 - 4, g->surf.y1 - 4, &cx, &cy); cairo_arc(cr, cx, cy, 1.5, 0.0, 2.0 * M_PI); if (view->transform.enabled == 0) cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.8); cairo_fill(cr); draw_box(cr, &g->clip, NULL); cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 0.4); cairo_fill(cr); if (n) { draw_polygon_closed(cr, ex, ey, n); cairo_set_source_rgb(cr, 0.0, 1.0, 0.0); cairo_stroke(cr); cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 0.5); draw_polygon_labels(cr, ex, ey, n); } } static void redraw_handler(struct widget *widget, void *data) { struct cliptest *cliptest = data; struct geometry *g = cliptest->view.geometry; struct rectangle allocation; cairo_t *cr; cairo_surface_t *surface; GLfloat ex[8]; GLfloat ey[8]; int n; n = calculate_edges(&cliptest->view, &g->clip, &g->surf, ex, ey); widget_get_allocation(cliptest->widget, &allocation); surface = window_get_surface(cliptest->window); cr = cairo_create(surface); widget_get_allocation(cliptest->widget, &allocation); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_clip(cr); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 0, 0, 0, 1); cairo_paint(cr); cairo_translate(cr, allocation.x, allocation.y); cairo_set_line_width(cr, 1.0); cairo_move_to(cr, allocation.width / 2.0, 0.0); cairo_line_to(cr, allocation.width / 2.0, allocation.height); cairo_move_to(cr, 0.0, allocation.height / 2.0); cairo_line_to(cr, allocation.width, allocation.height / 2.0); cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0); cairo_stroke(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); cairo_push_group(cr); cairo_translate(cr, allocation.width / 2.0, allocation.height / 2.0); cairo_scale(cr, 4.0, 4.0); cairo_set_line_width(cr, 0.5); cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL); cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cr, 5.0); draw_geometry(cr, &cliptest->view, ex, ey, n); cairo_pop_group_to_source(cr); cairo_paint(cr); cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); cairo_select_font_face(cr, "monospace", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 12.0); draw_coordinates(cr, 10.0, 10.0, ex, ey, n); cairo_destroy(cr); cairo_surface_destroy(surface); } static int motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct cliptest *cliptest = data; struct ui_state *ui = &cliptest->ui; struct geometry *ref = &ui->geometry; struct geometry *geom = &cliptest->geometry; float dx, dy; if (!ui->down) return CURSOR_LEFT_PTR; dx = (x - ui->down_pos[0]) * 0.25; dy = (y - ui->down_pos[1]) * 0.25; switch (ui->button) { case BTN_LEFT: geom->clip.x1 = ref->clip.x1 + dx; geom->clip.y1 = ref->clip.y1 + dy; /* fall through */ case BTN_RIGHT: geom->clip.x2 = ref->clip.x2 + dx; geom->clip.y2 = ref->clip.y2 + dy; break; default: return CURSOR_LEFT_PTR; } widget_schedule_redraw(cliptest->widget); return CURSOR_BLANK; } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct cliptest *cliptest = data; struct ui_state *ui = &cliptest->ui; ui->button = button; if (state == WL_POINTER_BUTTON_STATE_PRESSED) { ui->down = 1; input_get_position(input, &ui->down_pos[0], &ui->down_pos[1]); } else { ui->down = 0; ui->geometry = cliptest->geometry; } } static void axis_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t axis, wl_fixed_t value, void *data) { struct cliptest *cliptest = data; struct geometry *geom = &cliptest->geometry; if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) return; geometry_set_phi(geom, geom->phi + (M_PI / 12.0) * wl_fixed_to_double(value)); cliptest->view.transform.enabled = 1; widget_schedule_redraw(cliptest->widget); } static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data) { struct cliptest *cliptest = data; struct geometry *g = &cliptest->geometry; if (state == WL_KEYBOARD_KEY_STATE_RELEASED) return; switch (sym) { case XKB_KEY_Escape: display_exit(cliptest->display); return; case XKB_KEY_w: g->clip.y1 -= 1; g->clip.y2 -= 1; break; case XKB_KEY_a: g->clip.x1 -= 1; g->clip.x2 -= 1; break; case XKB_KEY_s: g->clip.y1 += 1; g->clip.y2 += 1; break; case XKB_KEY_d: g->clip.x1 += 1; g->clip.x2 += 1; break; case XKB_KEY_i: g->clip.y2 -= 1; break; case XKB_KEY_j: g->clip.x2 -= 1; break; case XKB_KEY_k: g->clip.y2 += 1; break; case XKB_KEY_l: g->clip.x2 += 1; break; case XKB_KEY_n: geometry_set_phi(g, g->phi + (M_PI / 24.0)); cliptest->view.transform.enabled = 1; break; case XKB_KEY_m: geometry_set_phi(g, g->phi - (M_PI / 24.0)); cliptest->view.transform.enabled = 1; break; case XKB_KEY_r: geometry_set_phi(g, 0.0); cliptest->view.transform.enabled = 0; break; default: return; } widget_schedule_redraw(cliptest->widget); } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { struct cliptest *cliptest = data; window_schedule_redraw(cliptest->window); } static void fullscreen_handler(struct window *window, void *data) { struct cliptest *cliptest = data; cliptest->fullscreen ^= 1; window_set_fullscreen(window, cliptest->fullscreen); } static struct cliptest * cliptest_create(struct display *display) { struct cliptest *cliptest; cliptest = xzalloc(sizeof *cliptest); cliptest->view.geometry = &cliptest->geometry; cliptest->view.transform.enabled = 0; geometry_init(&cliptest->geometry); geometry_init(&cliptest->ui.geometry); cliptest->window = window_create(display); cliptest->widget = window_frame_create(cliptest->window, cliptest); window_set_title(cliptest->window, "cliptest"); cliptest->display = display; window_set_user_data(cliptest->window, cliptest); widget_set_redraw_handler(cliptest->widget, redraw_handler); widget_set_button_handler(cliptest->widget, button_handler); widget_set_motion_handler(cliptest->widget, motion_handler); widget_set_axis_handler(cliptest->widget, axis_handler); window_set_keyboard_focus_handler(cliptest->window, keyboard_focus_handler); window_set_key_handler(cliptest->window, key_handler); window_set_fullscreen_handler(cliptest->window, fullscreen_handler); /* set minimum size */ widget_schedule_resize(cliptest->widget, 200, 100); /* set current size */ widget_schedule_resize(cliptest->widget, 500, 400); return cliptest; } static struct timespec begin_time; static void reset_timer(void) { clock_gettime(CLOCK_MONOTONIC, &begin_time); } static double read_timer(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return (double)(t.tv_sec - begin_time.tv_sec) + 1e-9 * (t.tv_nsec - begin_time.tv_nsec); } static int benchmark(void) { struct weston_view view; struct geometry geom; GLfloat ex[8], ey[8]; int i; double t; const int N = 1000000; geom.clip.x1 = -19; geom.clip.y1 = -19; geom.clip.x2 = 19; geom.clip.y2 = 19; geom.surf.x1 = -20; geom.surf.y1 = -20; geom.surf.x2 = 20; geom.surf.y2 = 20; geometry_set_phi(&geom, 0.0); view.transform.enabled = 1; view.geometry = &geom; reset_timer(); for (i = 0; i < N; i++) { geometry_set_phi(&geom, (float)i / 360.0f); calculate_edges(&view, &geom.clip, &geom.surf, ex, ey); } t = read_timer(); printf("%d calls took %g s, average %g us/call\n", N, t, t / N * 1e6); return 0; } static void cliptest_destroy(struct cliptest *cliptest) { widget_destroy(cliptest->widget); window_destroy(cliptest->window); free(cliptest); } int main(int argc, char *argv[]) { struct display *d; struct cliptest *cliptest; if (argc > 1) { if (argc == 2 && !strcmp(argv[1], "-b")) return benchmark(); printf("Usage: %s [OPTIONS]\n -b run benchmark\n", argv[0]); return 1; } d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } cliptest = cliptest_create(d); display_run(d); cliptest_destroy(cliptest); display_destroy(d); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3122964 weston-8.0.0/clients/confine.c0000644000175000017460000003105100000000000016545 0ustar00simonwheel00000000000000/* * Copyright © 2010 Intel Corporation * Copyright © 2012 Collabora, Ltd. * Copyright © 2012 Jonas Ådahl * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "window.h" #include "shared/helpers.h" #include "shared/xalloc.h" #define NUM_COMPLEX_REGION_RECTS 9 static bool option_complex_confine_region; static bool option_help; struct confine { struct display *display; struct window *window; struct widget *widget; cairo_surface_t *buffer; struct { int32_t x, y; int32_t old_x, old_y; } line; int reset; struct input *cursor_timeout_input; struct toytimer cursor_timeout; bool pointer_confined; bool complex_confine_region_enabled; bool complex_confine_region_dirty; struct rectangle complex_confine_region[NUM_COMPLEX_REGION_RECTS]; }; static void draw_line(struct confine *confine, cairo_t *cr, struct rectangle *allocation) { cairo_t *bcr; cairo_surface_t *tmp_buffer = NULL; if (confine->reset) { tmp_buffer = confine->buffer; confine->buffer = NULL; confine->line.x = -1; confine->line.y = -1; confine->line.old_x = -1; confine->line.old_y = -1; confine->reset = 0; } if (confine->buffer == NULL) { confine->buffer = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation->width, allocation->height); bcr = cairo_create(confine->buffer); cairo_set_source_rgba(bcr, 0, 0, 0, 0); cairo_rectangle(bcr, 0, 0, allocation->width, allocation->height); cairo_fill(bcr); } else bcr = cairo_create(confine->buffer); if (tmp_buffer) { cairo_set_source_surface(bcr, tmp_buffer, 0, 0); cairo_rectangle(bcr, 0, 0, allocation->width, allocation->height); cairo_clip(bcr); cairo_paint(bcr); cairo_surface_destroy(tmp_buffer); } if (confine->line.x != -1 && confine->line.y != -1) { if (confine->line.old_x != -1 && confine->line.old_y != -1) { cairo_set_line_width(bcr, 2.0); cairo_set_source_rgb(bcr, 1, 1, 1); cairo_translate(bcr, -allocation->x, -allocation->y); cairo_move_to(bcr, confine->line.old_x, confine->line.old_y); cairo_line_to(bcr, confine->line.x, confine->line.y); cairo_stroke(bcr); } confine->line.old_x = confine->line.x; confine->line.old_y = confine->line.y; } cairo_destroy(bcr); cairo_set_source_surface(cr, confine->buffer, allocation->x, allocation->y); cairo_set_operator(cr, CAIRO_OPERATOR_ADD); cairo_rectangle(cr, allocation->x, allocation->y, allocation->width, allocation->height); cairo_clip(cr); cairo_paint(cr); } static void calculate_complex_confine_region(struct confine *confine) { struct rectangle allocation; int32_t x, y, w, h; struct rectangle *rs = confine->complex_confine_region; if (!confine->complex_confine_region_dirty) return; widget_get_allocation(confine->widget, &allocation); x = allocation.x; y = allocation.y; w = allocation.width; h = allocation.height; /* * The code below constructs a region made up of rectangles that * is then used to set up both an illustrative shaded region in the * widget and a confine region used when confining the pointer. */ rs[0].x = x + (int)round(w * 0.05); rs[0].y = y + (int)round(h * 0.15); rs[0].width = (int)round(w * 0.35); rs[0].height = (int)round(h * 0.7); rs[1].x = rs[0].x + rs[0].width; rs[1].y = y + (int)round(h * 0.45); rs[1].width = (int)round(w * 0.09); rs[1].height = (int)round(h * 0.1); rs[2].x = rs[1].x + rs[1].width; rs[2].y = y + (int)round(h * 0.48); rs[2].width = (int)round(w * 0.02); rs[2].height = (int)round(h * 0.04); rs[3].x = rs[2].x + rs[2].width; rs[3].y = y + (int)round(h * 0.45); rs[3].width = (int)round(w * 0.09); rs[3].height = (int)round(h * 0.1); rs[4].x = rs[3].x + rs[3].width; rs[4].y = y + (int)round(h * 0.15); rs[4].width = (int)round(w * 0.35); rs[4].height = (int)round(h * 0.7); rs[5].x = x + (int)round(w * 0.05); rs[5].y = y + (int)round(h * 0.05); rs[5].width = rs[0].width + rs[1].width + rs[2].width + rs[3].width + rs[4].width; rs[5].height = (int)round(h * 0.10); rs[6].x = x + (int)round(w * 0.1); rs[6].y = rs[4].y + rs[4].height + (int)round(h * 0.02); rs[6].width = (int)round(w * 0.8); rs[6].height = (int)round(h * 0.03); rs[7].x = x + (int)round(w * 0.05); rs[7].y = rs[6].y + rs[6].height; rs[7].width = (int)round(w * 0.9); rs[7].height = (int)round(h * 0.03); rs[8].x = x + (int)round(w * 0.1); rs[8].y = rs[7].y + rs[7].height; rs[8].width = (int)round(w * 0.8); rs[8].height = (int)round(h * 0.03); confine->complex_confine_region_dirty = false; } static void draw_complex_confine_region_mask(struct confine *confine, cairo_t *cr) { int i; calculate_complex_confine_region(confine); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); for (i = 0; i < NUM_COMPLEX_REGION_RECTS; i++) { cairo_rectangle(cr, confine->complex_confine_region[i].x, confine->complex_confine_region[i].y, confine->complex_confine_region[i].width, confine->complex_confine_region[i].height); cairo_set_source_rgba(cr, 0.14, 0.14, 0.14, 0.9); cairo_fill(cr); } } static void redraw_handler(struct widget *widget, void *data) { struct confine *confine = data; cairo_surface_t *surface; cairo_t *cr; struct rectangle allocation; widget_get_allocation(confine->widget, &allocation); surface = window_get_surface(confine->window); cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_set_source_rgba(cr, 0, 0, 0, 0.8); cairo_fill(cr); if (confine->complex_confine_region_enabled) { draw_complex_confine_region_mask(confine, cr); } draw_line(confine, cr, &allocation); cairo_destroy(cr); cairo_surface_destroy(surface); } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { struct confine *confine = data; window_schedule_redraw(confine->window); } static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data) { struct confine *confine = data; if (state == WL_KEYBOARD_KEY_STATE_RELEASED) return; switch (sym) { case XKB_KEY_Escape: display_exit(confine->display); break; case XKB_KEY_BackSpace: cairo_surface_destroy(confine->buffer); confine->buffer = NULL; window_schedule_redraw(confine->window); break; case XKB_KEY_m: window_set_maximized(confine->window, !window_is_maximized(window)); break; } } static void toggle_pointer_confine(struct confine *confine, struct input *input) { if (confine->pointer_confined) { window_unconfine_pointer(confine->window); } else if (confine->complex_confine_region_enabled) { calculate_complex_confine_region(confine); window_confine_pointer_to_rectangles( confine->window, input, confine->complex_confine_region, NUM_COMPLEX_REGION_RECTS); } else { window_confine_pointer_to_widget(confine->window, confine->widget, input); } confine->pointer_confined = !confine->pointer_confined; } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct confine *confine = data; bool is_pressed = state == WL_POINTER_BUTTON_STATE_PRESSED; if (is_pressed && button == BTN_LEFT) toggle_pointer_confine(confine, input); widget_schedule_redraw(widget); } static void cursor_timeout_reset(struct confine *confine) { toytimer_arm_once_usec(&confine->cursor_timeout, 500 * 1000); } static int motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct confine *confine = data; confine->line.x = x; confine->line.y = y; window_schedule_redraw(confine->window); cursor_timeout_reset(confine); confine->cursor_timeout_input = input; return CURSOR_BLANK; } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct confine *confine = data; confine->reset = 1; if (confine->complex_confine_region_enabled) { confine->complex_confine_region_dirty = true; if (confine->pointer_confined) { calculate_complex_confine_region(confine); window_update_confine_rectangles( confine->window, confine->complex_confine_region, NUM_COMPLEX_REGION_RECTS); } } } static void leave_handler(struct widget *widget, struct input *input, void *data) { struct confine *confine = data; confine->reset = 1; } static void cursor_timeout_func(struct toytimer *tt) { struct confine *confine = container_of(tt, struct confine, cursor_timeout); input_set_pointer_image(confine->cursor_timeout_input, CURSOR_LEFT_PTR); } static void pointer_unconfined(struct window *window, struct input *input, void *data) { struct confine *confine = data; confine->pointer_confined = false; } static struct confine * confine_create(struct display *display) { struct confine *confine; confine = xzalloc(sizeof *confine); confine->window = window_create(display); confine->widget = window_frame_create(confine->window, confine); window_set_title(confine->window, "Wayland Confine"); confine->display = display; confine->buffer = NULL; window_set_key_handler(confine->window, key_handler); window_set_user_data(confine->window, confine); window_set_keyboard_focus_handler(confine->window, keyboard_focus_handler); window_set_pointer_confined_handler(confine->window, NULL, pointer_unconfined); widget_set_redraw_handler(confine->widget, redraw_handler); widget_set_button_handler(confine->widget, button_handler); widget_set_motion_handler(confine->widget, motion_handler); widget_set_resize_handler(confine->widget, resize_handler); widget_set_leave_handler(confine->widget, leave_handler); widget_schedule_resize(confine->widget, 500, 400); confine->line.x = -1; confine->line.y = -1; confine->line.old_x = -1; confine->line.old_y = -1; confine->reset = 0; toytimer_init(&confine->cursor_timeout, CLOCK_MONOTONIC, display, cursor_timeout_func); return confine; } static void confine_destroy(struct confine *confine) { toytimer_fini(&confine->cursor_timeout); if (confine->buffer) cairo_surface_destroy(confine->buffer); widget_destroy(confine->widget); window_destroy(confine->window); free(confine); } static const struct weston_option confine_options[] = { { WESTON_OPTION_BOOLEAN, "complex-confine-region", 0, &option_complex_confine_region }, { WESTON_OPTION_BOOLEAN, "help", 0, &option_help }, }; static void print_help(const char *argv0) { printf("Usage: %s [--complex-confine-region]\n", argv0); } int main(int argc, char *argv[]) { struct display *display; struct confine *confine; if (parse_options(confine_options, ARRAY_LENGTH(confine_options), &argc, argv) > 1 || option_help) { print_help(argv[0]); return 0; } display = display_create(&argc, argv); if (display == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } confine = confine_create(display); if (option_complex_confine_region) { confine->complex_confine_region_dirty = true; confine->complex_confine_region_enabled = true; } display_run(display); confine_destroy(confine); display_destroy(display); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3122964 weston-8.0.0/clients/content_protection.c0000644000175000017460000002762100000000000021054 0ustar00simonwheel00000000000000/* * Copyright © 2018 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "weston-content-protection-client-protocol.h" #include "window.h" #include #define WIDTH 500 #define HEIGHT 400 #define FRAME_H 18 #define FRAME_W 5 #define BUTTON_WIDTH 65 #define BUTTON_HEIGHT 20 enum protection_mode { RELAXED, ENFORCED }; struct protected_content_player { struct weston_content_protection *protection; struct weston_protected_surface *psurface; struct display *display; struct window *window; struct widget *widget; struct button_t *b0, *b1, *off, *enforced, *relaxed; int width, height, x, y; enum weston_protected_surface_type protection_type; enum protection_mode mode; }; struct button_t { struct window *window; struct widget *widget; struct protected_content_player *pc_player; const char *name; }; /** * An event to tell the client that there is a change in protection status * * This event is sent whenever there is a change in content * protection. The content protection status can be ON or OFF. ON * in case of the desired protection type is accepted on all * connectors, and Off in case of any of the connector * content-protection property is changed from "enabled" */ static void handle_status_changed(void *data, struct weston_protected_surface *psurface, uint32_t status) { struct protected_content_player *pc_player = data; enum weston_protected_surface_type event_status = status; switch (event_status) { case WESTON_PROTECTED_SURFACE_TYPE_HDCP_0: pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_HDCP_0; break; case WESTON_PROTECTED_SURFACE_TYPE_HDCP_1: pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_HDCP_1; break; case WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED: default: pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED; } window_schedule_redraw(pc_player->window); } static const struct weston_protected_surface_listener pc_player_listener = { handle_status_changed, }; static void draw_content(cairo_surface_t *surface, int x, int y, int width, int height, enum weston_protected_surface_type type, enum protection_mode mode) { cairo_t *cr; cairo_text_extents_t extents; const char *content_text; const char *mode_text; cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle(cr, x, y, width, height); if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_0) cairo_set_source_rgba(cr, 0, 1.0, 0, 1.0); else if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_1) cairo_set_source_rgba(cr, 0, 0, 1.0, 1.0); else cairo_set_source_rgba(cr, 1.0, 0, 0, 1.0); cairo_fill(cr); cairo_set_source_rgba(cr, 0, 0, 0, 1.0); cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 15); if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_0) content_text = "Content-Type : Type-0"; else if (type == WESTON_PROTECTED_SURFACE_TYPE_HDCP_1) content_text = "Content-Type : Type-1"; else content_text = "Content-Type : Unprotected"; cairo_text_extents(cr, content_text, &extents); cairo_move_to(cr, width/2 - (extents.width/2), height/2 - (extents.height/2)); cairo_show_text(cr, content_text); if (mode == ENFORCED) mode_text = "Mode : Enforced"; else mode_text = "Mode : Relaxed"; cairo_text_extents(cr, mode_text, &extents); cairo_move_to(cr, width / 2 - (extents.width / 2), 2 * height / 3 - (2 * extents.height / 3)); cairo_show_text(cr, mode_text); cairo_fill(cr); cairo_destroy(cr); } static void redraw_handler(struct widget *widget, void *data) { struct protected_content_player *pc_player = data; cairo_surface_t *surface; struct rectangle rect; widget_get_allocation(pc_player->widget, &rect); surface = window_get_surface(pc_player->window); if (surface == NULL || cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "failed to create cairo egl surface\n"); return; } draw_content(surface, rect.x, rect.y, rect.width, rect.height, pc_player->protection_type, pc_player->mode); cairo_surface_destroy(surface); } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct rectangle allocation; struct protected_content_player *pc_player = data; widget_get_allocation(pc_player->widget, &allocation); widget_set_allocation(pc_player->b0->widget, allocation.x + 20, allocation.y + 30, BUTTON_WIDTH, BUTTON_HEIGHT); widget_set_allocation(pc_player->b1->widget, allocation.x + 20 + BUTTON_WIDTH + 5, allocation.y + 30, BUTTON_WIDTH, BUTTON_HEIGHT); widget_set_allocation(pc_player->off->widget, allocation.x + 20 + 2 * (BUTTON_WIDTH + 5), allocation.y + 30, BUTTON_WIDTH, BUTTON_HEIGHT); widget_set_allocation(pc_player->enforced->widget, allocation.x + 20 + 3 * (BUTTON_WIDTH + 5), allocation.y + 30, BUTTON_WIDTH, BUTTON_HEIGHT); widget_set_allocation(pc_player->relaxed->widget, allocation.x + 20 + 4 * (BUTTON_WIDTH + 5), allocation.y + 30, BUTTON_WIDTH, BUTTON_HEIGHT); } static void buttons_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct button_t *b = data; struct protected_content_player *pc_player = b->pc_player; struct wl_surface *surface; if (strcmp(b->name, "ENFORCED") == 0) { weston_protected_surface_enforce(pc_player->psurface); pc_player->mode = ENFORCED; window_schedule_redraw(pc_player->window); } else if (strcmp(b->name, "RELAXED") == 0) { weston_protected_surface_relax(pc_player->psurface); pc_player->mode = RELAXED; window_schedule_redraw(pc_player->window); } else if (strcmp(b->name, "TYPE-0") == 0) weston_protected_surface_set_type(pc_player->psurface, WESTON_PROTECTED_SURFACE_TYPE_HDCP_0); else if (strcmp(b->name, "TYPE-1") == 0) weston_protected_surface_set_type(pc_player->psurface, WESTON_PROTECTED_SURFACE_TYPE_HDCP_1); else weston_protected_surface_set_type(pc_player->psurface, WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED); surface = window_get_wl_surface(pc_player->window); wl_surface_commit(surface); } static void handle_global(struct display *display, uint32_t name, const char *interface, uint32_t version, void *data) { struct protected_content_player *pc_player = data; if (strcmp(interface, "weston_content_protection") == 0) { pc_player->protection = display_bind(display, name, &weston_content_protection_interface, 1); } } static void buttons_redraw_handler(struct widget *widget, void *data) { struct button_t *b = data; cairo_surface_t *surface; struct rectangle allocation; cairo_t *cr; surface = window_get_surface(b->window); widget_get_allocation(b->widget, &allocation); cr = cairo_create(surface); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 1, 1, 1, 1); cairo_fill(cr); cairo_set_source_rgba(cr, 0, 0, 0, 1.0); cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 10); cairo_move_to(cr, allocation.x + 5, allocation.y + 15); cairo_show_text(cr, b->name); cairo_fill(cr); cairo_destroy(cr); cairo_surface_destroy(surface); } static struct button_t* create_button(struct protected_content_player *pc_player, const char *name) { struct button_t *b; b = zalloc(sizeof(struct button_t)); if (b == NULL) { fprintf(stderr, "Failed to allocate memory for button.\n"); exit(0); } b->widget = widget_add_widget(pc_player->widget, b); b->window = pc_player->window; b->pc_player = pc_player; b->name = name; widget_set_redraw_handler(b->widget, buttons_redraw_handler); widget_set_button_handler(b->widget, buttons_handler); return b; } static void destroy_button(struct button_t *b) { if (!b) return; widget_destroy(b->widget); free(b); } static void free_pc_player(struct protected_content_player *pc_player) { if (!pc_player) return; destroy_button(pc_player->b0); destroy_button(pc_player->b1); destroy_button(pc_player->off); destroy_button(pc_player->enforced); destroy_button(pc_player->relaxed); widget_destroy(pc_player->widget); window_destroy(pc_player->window); free(pc_player); } int main(int argc, char *argv[]) { struct protected_content_player *pc_player; struct display *d; static const char str_type_0[] = "TYPE-0"; static const char str_type_1[] = "TYPE-1"; static const char str_type_off[] = "OFF"; static const char str_type_enforced[] = "ENFORCED"; static const char str_type_relaxed[] = "RELAXED"; struct wl_surface *surface; pc_player = zalloc(sizeof(struct protected_content_player)); if (pc_player == NULL) { fprintf(stderr, "failed to allocate memory: %m\n"); return -1; } d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %m\n"); return -1; } pc_player->protection_type = WESTON_PROTECTED_SURFACE_TYPE_UNPROTECTED; pc_player->mode = RELAXED; pc_player->width = WIDTH * 2.0/4.0; pc_player->height = HEIGHT * 2.0/4.0; pc_player->x = WIDTH * 1.0/4.0; pc_player->y = HEIGHT * 1.0/4.0; pc_player->window = window_create(d); pc_player->widget = window_frame_create(pc_player->window, pc_player); pc_player->display = d; display_set_user_data(d, pc_player); display_set_global_handler(d, handle_global); surface = window_get_wl_surface(pc_player->window); if (pc_player->protection == NULL) { printf("The content-protection object is NULL\n"); return -1; } pc_player->psurface = weston_content_protection_get_protection(pc_player->protection, surface); weston_protected_surface_add_listener(pc_player->psurface, &pc_player_listener, pc_player); pc_player->b0 = create_button(pc_player, str_type_0); pc_player->b1 = create_button(pc_player, str_type_1); pc_player->off = create_button(pc_player, str_type_off); pc_player->enforced = create_button(pc_player, str_type_enforced); pc_player->relaxed = create_button(pc_player, str_type_relaxed); window_set_title(pc_player->window, "Player"); widget_set_redraw_handler(pc_player->widget, redraw_handler); widget_set_resize_handler(pc_player->widget, resize_handler); window_schedule_resize(pc_player->window, WIDTH, HEIGHT); widget_schedule_redraw(pc_player->b0->widget); widget_schedule_redraw(pc_player->b1->widget); widget_schedule_redraw(pc_player->off->widget); display_run(d); weston_protected_surface_destroy(pc_player->psurface); weston_content_protection_destroy(pc_player->protection); free_pc_player(pc_player); display_destroy(d); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3122964 weston-8.0.0/clients/desktop-shell.c0000644000175000017460000011514600000000000017712 0ustar00simonwheel00000000000000/* * Copyright © 2011 Kristian Høgsberg * Copyright © 2011 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "window.h" #include "shared/cairo-util.h" #include #include "shared/helpers.h" #include "shared/xalloc.h" #include #include "shared/file-util.h" #include "weston-desktop-shell-client-protocol.h" #define DEFAULT_CLOCK_FORMAT CLOCK_FORMAT_MINUTES #define DEFAULT_SPACING 10 extern char **environ; /* defined by libc */ enum clock_format { CLOCK_FORMAT_MINUTES, CLOCK_FORMAT_SECONDS, CLOCK_FORMAT_NONE }; struct desktop { struct display *display; struct weston_desktop_shell *shell; struct unlock_dialog *unlock_dialog; struct task unlock_task; struct wl_list outputs; int want_panel; enum weston_desktop_shell_panel_position panel_position; enum clock_format clock_format; struct window *grab_window; struct widget *grab_widget; struct weston_config *config; bool locking; enum cursor_type grab_cursor; int painted; }; struct surface { void (*configure)(void *data, struct weston_desktop_shell *desktop_shell, uint32_t edges, struct window *window, int32_t width, int32_t height); }; struct output; struct panel { struct surface base; struct output *owner; struct window *window; struct widget *widget; struct wl_list launcher_list; struct panel_clock *clock; int painted; enum weston_desktop_shell_panel_position panel_position; enum clock_format clock_format; uint32_t color; }; struct background { struct surface base; struct output *owner; struct window *window; struct widget *widget; int painted; char *image; int type; uint32_t color; }; struct output { struct wl_output *output; uint32_t server_output_id; struct wl_list link; int x; int y; struct panel *panel; struct background *background; }; struct panel_launcher { struct widget *widget; struct panel *panel; cairo_surface_t *icon; int focused, pressed; char *path; struct wl_list link; struct wl_array envp; struct wl_array argv; }; struct panel_clock { struct widget *widget; struct panel *panel; struct toytimer timer; char *format_string; time_t refresh_timer; }; struct unlock_dialog { struct window *window; struct widget *widget; struct widget *button; int button_focused; int closing; struct desktop *desktop; }; static void panel_add_launchers(struct panel *panel, struct desktop *desktop); static void sigchild_handler(int s) { int status; pid_t pid; while (pid = waitpid(-1, &status, WNOHANG), pid > 0) fprintf(stderr, "child %d exited\n", pid); } static int is_desktop_painted(struct desktop *desktop) { struct output *output; wl_list_for_each(output, &desktop->outputs, link) { if (output->panel && !output->panel->painted) return 0; if (output->background && !output->background->painted) return 0; } return 1; } static void check_desktop_ready(struct window *window) { struct display *display; struct desktop *desktop; display = window_get_display(window); desktop = display_get_user_data(display); if (!desktop->painted && is_desktop_painted(desktop)) { desktop->painted = 1; weston_desktop_shell_desktop_ready(desktop->shell); } } static void panel_launcher_activate(struct panel_launcher *widget) { char **argv; pid_t pid; pid = fork(); if (pid < 0) { fprintf(stderr, "fork failed: %s\n", strerror(errno)); return; } if (pid) return; argv = widget->argv.data; if (setsid() == -1) exit(EXIT_FAILURE); if (execve(argv[0], argv, widget->envp.data) < 0) { fprintf(stderr, "execl '%s' failed: %s\n", argv[0], strerror(errno)); exit(1); } } static void panel_launcher_redraw_handler(struct widget *widget, void *data) { struct panel_launcher *launcher = data; struct rectangle allocation; cairo_t *cr; cr = widget_cairo_create(launcher->panel->widget); widget_get_allocation(widget, &allocation); allocation.x += allocation.width / 2 - cairo_image_surface_get_width(launcher->icon) / 2; if (allocation.width > allocation.height) allocation.x += allocation.width / 2 - allocation.height / 2; allocation.y += allocation.height / 2 - cairo_image_surface_get_height(launcher->icon) / 2; if (allocation.height > allocation.width) allocation.y += allocation.height / 2 - allocation.width / 2; if (launcher->pressed) { allocation.x++; allocation.y++; } cairo_set_source_surface(cr, launcher->icon, allocation.x, allocation.y); cairo_paint(cr); if (launcher->focused) { cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.4); cairo_mask_surface(cr, launcher->icon, allocation.x, allocation.y); } cairo_destroy(cr); } static int panel_launcher_motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct panel_launcher *launcher = data; widget_set_tooltip(widget, basename((char *)launcher->path), x, y); return CURSOR_LEFT_PTR; } static void set_hex_color(cairo_t *cr, uint32_t color) { cairo_set_source_rgba(cr, ((color >> 16) & 0xff) / 255.0, ((color >> 8) & 0xff) / 255.0, ((color >> 0) & 0xff) / 255.0, ((color >> 24) & 0xff) / 255.0); } static void panel_redraw_handler(struct widget *widget, void *data) { cairo_surface_t *surface; cairo_t *cr; struct panel *panel = data; cr = widget_cairo_create(panel->widget); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); set_hex_color(cr, panel->color); cairo_paint(cr); cairo_destroy(cr); surface = window_get_surface(panel->window); cairo_surface_destroy(surface); panel->painted = 1; check_desktop_ready(panel->window); } static int panel_launcher_enter_handler(struct widget *widget, struct input *input, float x, float y, void *data) { struct panel_launcher *launcher = data; launcher->focused = 1; widget_schedule_redraw(widget); return CURSOR_LEFT_PTR; } static void panel_launcher_leave_handler(struct widget *widget, struct input *input, void *data) { struct panel_launcher *launcher = data; launcher->focused = 0; widget_destroy_tooltip(widget); widget_schedule_redraw(widget); } static void panel_launcher_button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct panel_launcher *launcher; launcher = widget_get_user_data(widget); widget_schedule_redraw(widget); if (state == WL_POINTER_BUTTON_STATE_RELEASED) panel_launcher_activate(launcher); } static void panel_launcher_touch_down_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data) { struct panel_launcher *launcher; launcher = widget_get_user_data(widget); launcher->focused = 1; widget_schedule_redraw(widget); } static void panel_launcher_touch_up_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, void *data) { struct panel_launcher *launcher; launcher = widget_get_user_data(widget); launcher->focused = 0; widget_schedule_redraw(widget); panel_launcher_activate(launcher); } static void clock_func(struct toytimer *tt) { struct panel_clock *clock = container_of(tt, struct panel_clock, timer); widget_schedule_redraw(clock->widget); } static void panel_clock_redraw_handler(struct widget *widget, void *data) { struct panel_clock *clock = data; cairo_t *cr; struct rectangle allocation; cairo_text_extents_t extents; time_t rawtime; struct tm * timeinfo; char string[128]; time(&rawtime); timeinfo = localtime(&rawtime); strftime(string, sizeof string, clock->format_string, timeinfo); widget_get_allocation(widget, &allocation); if (allocation.width == 0) return; cr = widget_cairo_create(clock->panel->widget); cairo_set_font_size(cr, 14); cairo_text_extents(cr, string, &extents); if (allocation.x > 0) allocation.x += allocation.width - DEFAULT_SPACING * 1.5 - extents.width; else allocation.x += allocation.width / 2 - extents.width / 2; allocation.y += allocation.height / 2 - 1 + extents.height / 2; cairo_move_to(cr, allocation.x + 1, allocation.y + 1); cairo_set_source_rgba(cr, 0, 0, 0, 0.85); cairo_show_text(cr, string); cairo_move_to(cr, allocation.x, allocation.y); cairo_set_source_rgba(cr, 1, 1, 1, 0.85); cairo_show_text(cr, string); cairo_destroy(cr); } static int clock_timer_reset(struct panel_clock *clock) { struct itimerspec its; its.it_interval.tv_sec = clock->refresh_timer; its.it_interval.tv_nsec = 0; its.it_value.tv_sec = clock->refresh_timer; its.it_value.tv_nsec = 0; toytimer_arm(&clock->timer, &its); return 0; } static void panel_destroy_clock(struct panel_clock *clock) { widget_destroy(clock->widget); toytimer_fini(&clock->timer); free(clock); } static void panel_add_clock(struct panel *panel) { struct panel_clock *clock; clock = xzalloc(sizeof *clock); clock->panel = panel; panel->clock = clock; switch (panel->clock_format) { case CLOCK_FORMAT_MINUTES: clock->format_string = "%a %b %d, %I:%M %p"; clock->refresh_timer = 60; break; case CLOCK_FORMAT_SECONDS: clock->format_string = "%a %b %d, %I:%M:%S %p"; clock->refresh_timer = 1; break; case CLOCK_FORMAT_NONE: assert(!"not reached"); } toytimer_init(&clock->timer, CLOCK_MONOTONIC, window_get_display(panel->window), clock_func); clock_timer_reset(clock); clock->widget = widget_add_widget(panel->widget, clock); widget_set_redraw_handler(clock->widget, panel_clock_redraw_handler); } static void panel_resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct panel_launcher *launcher; struct panel *panel = data; int x = 0; int y = 0; int w = height > width ? width : height; int h = w; int horizontal = panel->panel_position == WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP || panel->panel_position == WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM; int first_pad_h = horizontal ? 0 : DEFAULT_SPACING / 2; int first_pad_w = horizontal ? DEFAULT_SPACING / 2 : 0; wl_list_for_each(launcher, &panel->launcher_list, link) { widget_set_allocation(launcher->widget, x, y, w + first_pad_w + 1, h + first_pad_h + 1); if (horizontal) x += w + first_pad_w; else y += h + first_pad_h; first_pad_h = first_pad_w = 0; } if (panel->clock_format == CLOCK_FORMAT_SECONDS) w = 170; else /* CLOCK_FORMAT_MINUTES */ w = 150; if (horizontal) x = width - w; else y = height - (h = DEFAULT_SPACING * 3); if (panel->clock) widget_set_allocation(panel->clock->widget, x, y, w + 1, h + 1); } static void panel_destroy(struct panel *panel); static void panel_configure(void *data, struct weston_desktop_shell *desktop_shell, uint32_t edges, struct window *window, int32_t width, int32_t height) { struct desktop *desktop = data; struct surface *surface = window_get_user_data(window); struct panel *panel = container_of(surface, struct panel, base); struct output *owner; if (width < 1 || height < 1) { /* Shell plugin configures 0x0 for redundant panel. */ owner = panel->owner; panel_destroy(panel); owner->panel = NULL; return; } switch (desktop->panel_position) { case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: case WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM: height = 32; break; case WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT: case WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT: switch (desktop->clock_format) { case CLOCK_FORMAT_NONE: width = 32; break; case CLOCK_FORMAT_MINUTES: width = 150; break; case CLOCK_FORMAT_SECONDS: width = 170; break; } break; } window_schedule_resize(panel->window, width, height); } static void panel_destroy_launcher(struct panel_launcher *launcher) { wl_array_release(&launcher->argv); wl_array_release(&launcher->envp); free(launcher->path); cairo_surface_destroy(launcher->icon); widget_destroy(launcher->widget); wl_list_remove(&launcher->link); free(launcher); } static void panel_destroy(struct panel *panel) { struct panel_launcher *tmp; struct panel_launcher *launcher; if (panel->clock) panel_destroy_clock(panel->clock); wl_list_for_each_safe(launcher, tmp, &panel->launcher_list, link) panel_destroy_launcher(launcher); widget_destroy(panel->widget); window_destroy(panel->window); free(panel); } static struct panel * panel_create(struct desktop *desktop, struct output *output) { struct panel *panel; struct weston_config_section *s; panel = xzalloc(sizeof *panel); panel->owner = output; panel->base.configure = panel_configure; panel->window = window_create_custom(desktop->display); panel->widget = window_add_widget(panel->window, panel); wl_list_init(&panel->launcher_list); window_set_title(panel->window, "panel"); window_set_user_data(panel->window, panel); widget_set_redraw_handler(panel->widget, panel_redraw_handler); widget_set_resize_handler(panel->widget, panel_resize_handler); panel->panel_position = desktop->panel_position; panel->clock_format = desktop->clock_format; if (panel->clock_format != CLOCK_FORMAT_NONE) panel_add_clock(panel); s = weston_config_get_section(desktop->config, "shell", NULL, NULL); weston_config_section_get_color(s, "panel-color", &panel->color, 0xaa000000); panel_add_launchers(panel, desktop); return panel; } static cairo_surface_t * load_icon_or_fallback(const char *icon) { cairo_surface_t *surface = cairo_image_surface_create_from_png(icon); cairo_status_t status; cairo_t *cr; status = cairo_surface_status(surface); if (status == CAIRO_STATUS_SUCCESS) return surface; cairo_surface_destroy(surface); fprintf(stderr, "ERROR loading icon from file '%s', error: '%s'\n", icon, cairo_status_to_string(status)); /* draw fallback icon */ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 20, 20); cr = cairo_create(surface); cairo_set_source_rgba(cr, 0.8, 0.8, 0.8, 1); cairo_paint(cr); cairo_set_source_rgba(cr, 0, 0, 0, 1); cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); cairo_rectangle(cr, 0, 0, 20, 20); cairo_move_to(cr, 4, 4); cairo_line_to(cr, 16, 16); cairo_move_to(cr, 4, 16); cairo_line_to(cr, 16, 4); cairo_stroke(cr); cairo_destroy(cr); return surface; } static void panel_add_launcher(struct panel *panel, const char *icon, const char *path) { struct panel_launcher *launcher; char *start, *p, *eq, **ps; int i, j, k; launcher = xzalloc(sizeof *launcher); launcher->icon = load_icon_or_fallback(icon); launcher->path = xstrdup(path); wl_array_init(&launcher->envp); wl_array_init(&launcher->argv); for (i = 0; environ[i]; i++) { ps = wl_array_add(&launcher->envp, sizeof *ps); *ps = environ[i]; } j = 0; start = launcher->path; while (*start) { for (p = start, eq = NULL; *p && !isspace(*p); p++) if (*p == '=') eq = p; if (eq && j == 0) { ps = launcher->envp.data; for (k = 0; k < i; k++) if (strncmp(ps[k], start, eq - start) == 0) { ps[k] = start; break; } if (k == i) { ps = wl_array_add(&launcher->envp, sizeof *ps); *ps = start; i++; } } else { ps = wl_array_add(&launcher->argv, sizeof *ps); *ps = start; j++; } while (*p && isspace(*p)) *p++ = '\0'; start = p; } ps = wl_array_add(&launcher->envp, sizeof *ps); *ps = NULL; ps = wl_array_add(&launcher->argv, sizeof *ps); *ps = NULL; launcher->panel = panel; wl_list_insert(panel->launcher_list.prev, &launcher->link); launcher->widget = widget_add_widget(panel->widget, launcher); widget_set_enter_handler(launcher->widget, panel_launcher_enter_handler); widget_set_leave_handler(launcher->widget, panel_launcher_leave_handler); widget_set_button_handler(launcher->widget, panel_launcher_button_handler); widget_set_touch_down_handler(launcher->widget, panel_launcher_touch_down_handler); widget_set_touch_up_handler(launcher->widget, panel_launcher_touch_up_handler); widget_set_redraw_handler(launcher->widget, panel_launcher_redraw_handler); widget_set_motion_handler(launcher->widget, panel_launcher_motion_handler); } enum { BACKGROUND_SCALE, BACKGROUND_SCALE_CROP, BACKGROUND_TILE, BACKGROUND_CENTERED }; static void background_draw(struct widget *widget, void *data) { struct background *background = data; cairo_surface_t *surface, *image; cairo_pattern_t *pattern; cairo_matrix_t matrix; cairo_t *cr; double im_w, im_h; double sx, sy, s; double tx, ty; struct rectangle allocation; surface = window_get_surface(background->window); cr = widget_cairo_create(background->widget); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); if (background->color == 0) cairo_set_source_rgba(cr, 0.0, 0.0, 0.2, 1.0); else set_hex_color(cr, background->color); cairo_paint(cr); widget_get_allocation(widget, &allocation); image = NULL; if (background->image) image = load_cairo_surface(background->image); else if (background->color == 0) { char *name = file_name_with_datadir("pattern.png"); image = load_cairo_surface(name); free(name); } if (image && background->type != -1) { im_w = cairo_image_surface_get_width(image); im_h = cairo_image_surface_get_height(image); sx = im_w / allocation.width; sy = im_h / allocation.height; pattern = cairo_pattern_create_for_surface(image); switch (background->type) { case BACKGROUND_SCALE: cairo_matrix_init_scale(&matrix, sx, sy); cairo_pattern_set_matrix(pattern, &matrix); cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD); break; case BACKGROUND_SCALE_CROP: s = (sx < sy) ? sx : sy; /* align center */ tx = (im_w - s * allocation.width) * 0.5; ty = (im_h - s * allocation.height) * 0.5; cairo_matrix_init_translate(&matrix, tx, ty); cairo_matrix_scale(&matrix, s, s); cairo_pattern_set_matrix(pattern, &matrix); cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD); break; case BACKGROUND_TILE: cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); break; case BACKGROUND_CENTERED: s = (sx < sy) ? sx : sy; if (s < 1.0) s = 1.0; /* align center */ tx = (im_w - s * allocation.width) * 0.5; ty = (im_h - s * allocation.height) * 0.5; cairo_matrix_init_translate(&matrix, tx, ty); cairo_matrix_scale(&matrix, s, s); cairo_pattern_set_matrix(pattern, &matrix); break; } cairo_set_source(cr, pattern); cairo_pattern_destroy (pattern); cairo_surface_destroy(image); cairo_mask(cr, pattern); } cairo_destroy(cr); cairo_surface_destroy(surface); background->painted = 1; check_desktop_ready(background->window); } static void background_destroy(struct background *background); static void background_configure(void *data, struct weston_desktop_shell *desktop_shell, uint32_t edges, struct window *window, int32_t width, int32_t height) { struct output *owner; struct background *background = (struct background *) window_get_user_data(window); if (width < 1 || height < 1) { /* Shell plugin configures 0x0 for redundant background. */ owner = background->owner; background_destroy(background); owner->background = NULL; return; } if (!background->image) { widget_set_viewport_destination(background->widget, width, height); width = 1; height = 1; } widget_schedule_resize(background->widget, width, height); } static void unlock_dialog_redraw_handler(struct widget *widget, void *data) { struct unlock_dialog *dialog = data; struct rectangle allocation; cairo_surface_t *surface; cairo_t *cr; cairo_pattern_t *pat; double cx, cy, r, f; cr = widget_cairo_create(widget); widget_get_allocation(dialog->widget, &allocation); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 0, 0, 0, 0.6); cairo_fill(cr); cairo_translate(cr, allocation.x, allocation.y); if (dialog->button_focused) f = 1.0; else f = 0.7; cx = allocation.width / 2.0; cy = allocation.height / 2.0; r = (cx < cy ? cx : cy) * 0.4; pat = cairo_pattern_create_radial(cx, cy, r * 0.7, cx, cy, r); cairo_pattern_add_color_stop_rgb(pat, 0.0, 0, 0.86 * f, 0); cairo_pattern_add_color_stop_rgb(pat, 0.85, 0.2 * f, f, 0.2 * f); cairo_pattern_add_color_stop_rgb(pat, 1.0, 0, 0.86 * f, 0); cairo_set_source(cr, pat); cairo_pattern_destroy(pat); cairo_arc(cr, cx, cy, r, 0.0, 2.0 * M_PI); cairo_fill(cr); widget_set_allocation(dialog->button, allocation.x + cx - r, allocation.y + cy - r, 2 * r, 2 * r); cairo_destroy(cr); surface = window_get_surface(dialog->window); cairo_surface_destroy(surface); } static void unlock_dialog_button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct unlock_dialog *dialog = data; struct desktop *desktop = dialog->desktop; if (button == BTN_LEFT) { if (state == WL_POINTER_BUTTON_STATE_RELEASED && !dialog->closing) { display_defer(desktop->display, &desktop->unlock_task); dialog->closing = 1; } } } static void unlock_dialog_touch_down_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data) { struct unlock_dialog *dialog = data; dialog->button_focused = 1; widget_schedule_redraw(widget); } static void unlock_dialog_touch_up_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, void *data) { struct unlock_dialog *dialog = data; struct desktop *desktop = dialog->desktop; dialog->button_focused = 0; widget_schedule_redraw(widget); display_defer(desktop->display, &desktop->unlock_task); dialog->closing = 1; } static void unlock_dialog_keyboard_focus_handler(struct window *window, struct input *device, void *data) { window_schedule_redraw(window); } static int unlock_dialog_widget_enter_handler(struct widget *widget, struct input *input, float x, float y, void *data) { struct unlock_dialog *dialog = data; dialog->button_focused = 1; widget_schedule_redraw(widget); return CURSOR_LEFT_PTR; } static void unlock_dialog_widget_leave_handler(struct widget *widget, struct input *input, void *data) { struct unlock_dialog *dialog = data; dialog->button_focused = 0; widget_schedule_redraw(widget); } static struct unlock_dialog * unlock_dialog_create(struct desktop *desktop) { struct display *display = desktop->display; struct unlock_dialog *dialog; struct wl_surface *surface; dialog = xzalloc(sizeof *dialog); dialog->window = window_create_custom(display); dialog->widget = window_frame_create(dialog->window, dialog); window_set_title(dialog->window, "Unlock your desktop"); window_set_user_data(dialog->window, dialog); window_set_keyboard_focus_handler(dialog->window, unlock_dialog_keyboard_focus_handler); dialog->button = widget_add_widget(dialog->widget, dialog); widget_set_redraw_handler(dialog->widget, unlock_dialog_redraw_handler); widget_set_enter_handler(dialog->button, unlock_dialog_widget_enter_handler); widget_set_leave_handler(dialog->button, unlock_dialog_widget_leave_handler); widget_set_button_handler(dialog->button, unlock_dialog_button_handler); widget_set_touch_down_handler(dialog->button, unlock_dialog_touch_down_handler); widget_set_touch_up_handler(dialog->button, unlock_dialog_touch_up_handler); surface = window_get_wl_surface(dialog->window); weston_desktop_shell_set_lock_surface(desktop->shell, surface); window_schedule_resize(dialog->window, 260, 230); return dialog; } static void unlock_dialog_destroy(struct unlock_dialog *dialog) { window_destroy(dialog->window); free(dialog); } static void unlock_dialog_finish(struct task *task, uint32_t events) { struct desktop *desktop = container_of(task, struct desktop, unlock_task); weston_desktop_shell_unlock(desktop->shell); unlock_dialog_destroy(desktop->unlock_dialog); desktop->unlock_dialog = NULL; } static void desktop_shell_configure(void *data, struct weston_desktop_shell *desktop_shell, uint32_t edges, struct wl_surface *surface, int32_t width, int32_t height) { struct window *window = wl_surface_get_user_data(surface); struct surface *s = window_get_user_data(window); s->configure(data, desktop_shell, edges, window, width, height); } static void desktop_shell_prepare_lock_surface(void *data, struct weston_desktop_shell *desktop_shell) { struct desktop *desktop = data; if (!desktop->locking) { weston_desktop_shell_unlock(desktop->shell); return; } if (!desktop->unlock_dialog) { desktop->unlock_dialog = unlock_dialog_create(desktop); desktop->unlock_dialog->desktop = desktop; } } static void desktop_shell_grab_cursor(void *data, struct weston_desktop_shell *desktop_shell, uint32_t cursor) { struct desktop *desktop = data; switch (cursor) { case WESTON_DESKTOP_SHELL_CURSOR_NONE: desktop->grab_cursor = CURSOR_BLANK; break; case WESTON_DESKTOP_SHELL_CURSOR_BUSY: desktop->grab_cursor = CURSOR_WATCH; break; case WESTON_DESKTOP_SHELL_CURSOR_MOVE: desktop->grab_cursor = CURSOR_DRAGGING; break; case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_TOP: desktop->grab_cursor = CURSOR_TOP; break; case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM: desktop->grab_cursor = CURSOR_BOTTOM; break; case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_LEFT: desktop->grab_cursor = CURSOR_LEFT; break; case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_RIGHT: desktop->grab_cursor = CURSOR_RIGHT; break; case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_TOP_LEFT: desktop->grab_cursor = CURSOR_TOP_LEFT; break; case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_TOP_RIGHT: desktop->grab_cursor = CURSOR_TOP_RIGHT; break; case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM_LEFT: desktop->grab_cursor = CURSOR_BOTTOM_LEFT; break; case WESTON_DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM_RIGHT: desktop->grab_cursor = CURSOR_BOTTOM_RIGHT; break; case WESTON_DESKTOP_SHELL_CURSOR_ARROW: default: desktop->grab_cursor = CURSOR_LEFT_PTR; } } static const struct weston_desktop_shell_listener listener = { desktop_shell_configure, desktop_shell_prepare_lock_surface, desktop_shell_grab_cursor }; static void background_destroy(struct background *background) { widget_destroy(background->widget); window_destroy(background->window); free(background->image); free(background); } static struct background * background_create(struct desktop *desktop, struct output *output) { struct background *background; struct weston_config_section *s; char *type; background = xzalloc(sizeof *background); background->owner = output; background->base.configure = background_configure; background->window = window_create_custom(desktop->display); background->widget = window_add_widget(background->window, background); window_set_user_data(background->window, background); widget_set_redraw_handler(background->widget, background_draw); widget_set_transparent(background->widget, 0); s = weston_config_get_section(desktop->config, "shell", NULL, NULL); weston_config_section_get_string(s, "background-image", &background->image, NULL); weston_config_section_get_color(s, "background-color", &background->color, 0x00000000); weston_config_section_get_string(s, "background-type", &type, "tile"); if (type == NULL) { fprintf(stderr, "%s: out of memory\n", program_invocation_short_name); exit(EXIT_FAILURE); } if (strcmp(type, "scale") == 0) { background->type = BACKGROUND_SCALE; } else if (strcmp(type, "scale-crop") == 0) { background->type = BACKGROUND_SCALE_CROP; } else if (strcmp(type, "tile") == 0) { background->type = BACKGROUND_TILE; } else if (strcmp(type, "centered") == 0) { background->type = BACKGROUND_CENTERED; } else { background->type = -1; fprintf(stderr, "invalid background-type: %s\n", type); } free(type); return background; } static int grab_surface_enter_handler(struct widget *widget, struct input *input, float x, float y, void *data) { struct desktop *desktop = data; return desktop->grab_cursor; } static void grab_surface_destroy(struct desktop *desktop) { widget_destroy(desktop->grab_widget); window_destroy(desktop->grab_window); } static void grab_surface_create(struct desktop *desktop) { struct wl_surface *s; desktop->grab_window = window_create_custom(desktop->display); window_set_user_data(desktop->grab_window, desktop); s = window_get_wl_surface(desktop->grab_window); weston_desktop_shell_set_grab_surface(desktop->shell, s); desktop->grab_widget = window_add_widget(desktop->grab_window, desktop); /* We set the allocation to 1x1 at 0,0 so the fake enter event * at 0,0 will go to this widget. */ widget_set_allocation(desktop->grab_widget, 0, 0, 1, 1); widget_set_enter_handler(desktop->grab_widget, grab_surface_enter_handler); } static void output_destroy(struct output *output) { if (output->background) background_destroy(output->background); if (output->panel) panel_destroy(output->panel); wl_output_destroy(output->output); wl_list_remove(&output->link); free(output); } static void desktop_destroy_outputs(struct desktop *desktop) { struct output *tmp; struct output *output; wl_list_for_each_safe(output, tmp, &desktop->outputs, link) output_destroy(output); } static void output_handle_geometry(void *data, struct wl_output *wl_output, int x, int y, int physical_width, int physical_height, int subpixel, const char *make, const char *model, int transform) { struct output *output = data; output->x = x; output->y = y; if (output->panel) window_set_buffer_transform(output->panel->window, transform); if (output->background) window_set_buffer_transform(output->background->window, transform); } static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int width, int height, int refresh) { } static void output_handle_done(void *data, struct wl_output *wl_output) { } static void output_handle_scale(void *data, struct wl_output *wl_output, int32_t scale) { struct output *output = data; if (output->panel) window_set_buffer_scale(output->panel->window, scale); if (output->background) window_set_buffer_scale(output->background->window, scale); } static const struct wl_output_listener output_listener = { output_handle_geometry, output_handle_mode, output_handle_done, output_handle_scale }; static void output_init(struct output *output, struct desktop *desktop) { struct wl_surface *surface; if (desktop->want_panel) { output->panel = panel_create(desktop, output); surface = window_get_wl_surface(output->panel->window); weston_desktop_shell_set_panel(desktop->shell, output->output, surface); } output->background = background_create(desktop, output); surface = window_get_wl_surface(output->background->window); weston_desktop_shell_set_background(desktop->shell, output->output, surface); } static void create_output(struct desktop *desktop, uint32_t id) { struct output *output; output = zalloc(sizeof *output); if (!output) return; output->output = display_bind(desktop->display, id, &wl_output_interface, 2); output->server_output_id = id; wl_output_add_listener(output->output, &output_listener, output); wl_list_insert(&desktop->outputs, &output->link); /* On start up we may process an output global before the shell global * in which case we can't create the panel and background just yet */ if (desktop->shell) output_init(output, desktop); } static void output_remove(struct desktop *desktop, struct output *output) { struct output *cur; struct output *rep = NULL; if (!output->background) { output_destroy(output); return; } /* Find a wl_output that is a clone of the removed wl_output. * We don't want to leave the clone without a background or panel. */ wl_list_for_each(cur, &desktop->outputs, link) { if (cur == output) continue; /* XXX: Assumes size matches. */ if (cur->x == output->x && cur->y == output->y) { rep = cur; break; } } if (rep) { /* If found and it does not already have a background or panel, * hand over the background and panel so they don't get * destroyed. * * We never create multiple backgrounds or panels for clones, * but if the compositor moves outputs, a pair of wl_outputs * might become "clones". This may happen temporarily when * an output is about to be removed and the rest are reflowed. * In this case it is correct to let the background/panel be * destroyed. */ if (!rep->background) { rep->background = output->background; output->background = NULL; rep->background->owner = rep; } if (!rep->panel) { rep->panel = output->panel; output->panel = NULL; if (rep->panel) rep->panel->owner = rep; } } output_destroy(output); } static void global_handler(struct display *display, uint32_t id, const char *interface, uint32_t version, void *data) { struct desktop *desktop = data; if (!strcmp(interface, "weston_desktop_shell")) { desktop->shell = display_bind(desktop->display, id, &weston_desktop_shell_interface, 1); weston_desktop_shell_add_listener(desktop->shell, &listener, desktop); } else if (!strcmp(interface, "wl_output")) { create_output(desktop, id); } } static void global_handler_remove(struct display *display, uint32_t id, const char *interface, uint32_t version, void *data) { struct desktop *desktop = data; struct output *output; if (!strcmp(interface, "wl_output")) { wl_list_for_each(output, &desktop->outputs, link) { if (output->server_output_id == id) { output_remove(desktop, output); break; } } } } static void panel_add_launchers(struct panel *panel, struct desktop *desktop) { struct weston_config_section *s; char *icon, *path; const char *name; int count; count = 0; s = NULL; while (weston_config_next_section(desktop->config, &s, &name)) { if (strcmp(name, "launcher") != 0) continue; weston_config_section_get_string(s, "icon", &icon, NULL); weston_config_section_get_string(s, "path", &path, NULL); if (icon != NULL && path != NULL) { panel_add_launcher(panel, icon, path); count++; } else { fprintf(stderr, "invalid launcher section\n"); } free(icon); free(path); } if (count == 0) { char *name = file_name_with_datadir("terminal.png"); /* add default launcher */ panel_add_launcher(panel, name, BINDIR "/weston-terminal"); free(name); } } static void parse_panel_position(struct desktop *desktop, struct weston_config_section *s) { char *position; desktop->want_panel = 1; weston_config_section_get_string(s, "panel-position", &position, "top"); if (strcmp(position, "top") == 0) { desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP; } else if (strcmp(position, "bottom") == 0) { desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM; } else if (strcmp(position, "left") == 0) { desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT; } else if (strcmp(position, "right") == 0) { desktop->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT; } else { /* 'none' is valid here */ if (strcmp(position, "none") != 0) fprintf(stderr, "Wrong panel position: %s\n", position); desktop->want_panel = 0; } free(position); } static void parse_clock_format(struct desktop *desktop, struct weston_config_section *s) { char *clock_format; weston_config_section_get_string(s, "clock-format", &clock_format, ""); if (strcmp(clock_format, "minutes") == 0) desktop->clock_format = CLOCK_FORMAT_MINUTES; else if (strcmp(clock_format, "seconds") == 0) desktop->clock_format = CLOCK_FORMAT_SECONDS; else if (strcmp(clock_format, "none") == 0) desktop->clock_format = CLOCK_FORMAT_NONE; else desktop->clock_format = DEFAULT_CLOCK_FORMAT; free(clock_format); } int main(int argc, char *argv[]) { struct desktop desktop = { 0 }; struct output *output; struct weston_config_section *s; const char *config_file; desktop.unlock_task.run = unlock_dialog_finish; wl_list_init(&desktop.outputs); config_file = weston_config_get_name_from_env(); desktop.config = weston_config_parse(config_file); s = weston_config_get_section(desktop.config, "shell", NULL, NULL); weston_config_section_get_bool(s, "locking", &desktop.locking, true); parse_panel_position(&desktop, s); parse_clock_format(&desktop, s); desktop.display = display_create(&argc, argv); if (desktop.display == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } display_set_user_data(desktop.display, &desktop); display_set_global_handler(desktop.display, global_handler); display_set_global_handler_remove(desktop.display, global_handler_remove); /* Create panel and background for outputs processed before the shell * global interface was processed */ if (desktop.want_panel) weston_desktop_shell_set_panel_position(desktop.shell, desktop.panel_position); wl_list_for_each(output, &desktop.outputs, link) if (!output->panel) output_init(output, &desktop); grab_surface_create(&desktop); signal(SIGCHLD, sigchild_handler); display_run(desktop.display); /* Cleanup */ grab_surface_destroy(&desktop); desktop_destroy_outputs(&desktop); if (desktop.unlock_dialog) unlock_dialog_destroy(desktop.unlock_dialog); weston_desktop_shell_destroy(desktop.shell); display_destroy(desktop.display); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3122964 weston-8.0.0/clients/dnd.c0000644000175000017460000005145700000000000015705 0ustar00simonwheel00000000000000/* * Copyright © 2010 Kristian Høgsberg * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "window.h" #include "shared/cairo-util.h" #include "shared/helpers.h" #include "shared/xalloc.h" struct dnd_drag; struct pointer { struct input *input; bool dragging; struct wl_list link; }; struct dnd { struct window *window; struct widget *widget; struct display *display; uint32_t key; struct item *items[16]; int self_only; struct dnd_drag *current_drag; struct wl_list pointers; }; struct dnd_drag { cairo_surface_t *translucent; cairo_surface_t *opaque; int hotspot_x, hotspot_y; struct dnd *dnd; struct input *input; uint32_t time; struct item *item; int x_offset, y_offset; int width, height; uint32_t dnd_action; const char *mime_type; struct wl_surface *drag_surface; struct wl_data_source *data_source; }; struct item { cairo_surface_t *surface; int seed; int x, y; }; struct dnd_flower_message { int seed, x_offset, y_offset; }; static const int item_width = 64; static const int item_height = 64; static const int item_padding = 16; static const char flower_mime_type[] = "application/x-wayland-dnd-flower"; static const char text_mime_type[] = "text/plain;charset=utf-8"; static struct item * item_create(struct display *display, int x, int y, int seed) { struct item *item; struct timeval tv; item = malloc(sizeof *item); if (item == NULL) return NULL; gettimeofday(&tv, NULL); item->seed = seed ? seed : tv.tv_usec; srandom(item->seed); const int petal_count = 3 + random() % 5; const double r1 = 20 + random() % 10; const double r2 = 5 + random() % 12; const double u = (10 + random() % 90) / 100.0; const double v = (random() % 90) / 100.0; cairo_t *cr; int i; double t, dt = 2 * M_PI / (petal_count * 2); double x1, y1, x2, y2, x3, y3; struct rectangle rect; rect.width = item_width; rect.height = item_height; item->surface = display_create_surface(display, NULL, &rect, SURFACE_SHM); item->x = x; item->y = y; cr = cairo_create(item->surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 0, 0, 0, 0); cairo_paint(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); cairo_translate(cr, item_width / 2, item_height / 2); t = random(); cairo_move_to(cr, cos(t) * r1, sin(t) * r1); for (i = 0; i < petal_count; i++, t += dt * 2) { x1 = cos(t) * r1; y1 = sin(t) * r1; x2 = cos(t + dt) * r2; y2 = sin(t + dt) * r2; x3 = cos(t + 2 * dt) * r1; y3 = sin(t + 2 * dt) * r1; cairo_curve_to(cr, x1 - y1 * u, y1 + x1 * u, x2 + y2 * v, y2 - x2 * v, x2, y2); cairo_curve_to(cr, x2 - y2 * v, y2 + x2 * v, x3 + y3 * u, y3 - x3 * u, x3, y3); } cairo_close_path(cr); cairo_set_source_rgba(cr, 0.5 + (random() % 50) / 49.0, 0.5 + (random() % 50) / 49.0, 0.5 + (random() % 50) / 49.0, 0.5 + (random() % 100) / 99.0); cairo_fill_preserve(cr); cairo_set_line_width(cr, 1); cairo_set_source_rgba(cr, 0.5 + (random() % 50) / 49.0, 0.5 + (random() % 50) / 49.0, 0.5 + (random() % 50) / 49.0, 0.5 + (random() % 100) / 99.0); cairo_stroke(cr); cairo_destroy(cr); return item; } static void dnd_redraw_handler(struct widget *widget, void *data) { struct dnd *dnd = data; struct rectangle allocation; cairo_t *cr; cairo_surface_t *surface, *item_surface; unsigned int i; surface = window_get_surface(dnd->window); cr = cairo_create(surface); widget_get_allocation(dnd->widget, &allocation); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 0, 0, 0, 0.8); cairo_fill(cr); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_clip(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { if (!dnd->items[i]) continue; if (dnd->current_drag && dnd->items[i] == dnd->current_drag->item) item_surface = dnd->current_drag->translucent; else item_surface = dnd->items[i]->surface; cairo_set_source_surface(cr, item_surface, dnd->items[i]->x + allocation.x, dnd->items[i]->y + allocation.y); cairo_paint(cr); } cairo_destroy(cr); cairo_surface_destroy(surface); } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { struct dnd *dnd = data; window_schedule_redraw(dnd->window); } static int dnd_add_item(struct dnd *dnd, struct item *item) { unsigned int i; for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { if (dnd->items[i] == 0) { dnd->items[i] = item; return i; } } return -1; } static struct item * dnd_get_item(struct dnd *dnd, int32_t x, int32_t y) { struct item *item; struct rectangle allocation; unsigned int i; widget_get_allocation(dnd->widget, &allocation); x -= allocation.x; y -= allocation.y; for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { item = dnd->items[i]; if (item && item->x <= x && x < item->x + item_width && item->y <= y && y < item->y + item_height) return item; } return NULL; } static int lookup_dnd_cursor(uint32_t dnd_action) { if (dnd_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) return CURSOR_DND_MOVE; else if (dnd_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) return CURSOR_DND_COPY; return CURSOR_DND_FORBIDDEN; } static void dnd_drag_update_cursor(struct dnd_drag *dnd_drag) { int cursor; if (dnd_drag->mime_type == NULL) cursor = CURSOR_DND_FORBIDDEN; else cursor = lookup_dnd_cursor(dnd_drag->dnd_action); input_set_pointer_image(dnd_drag->input, cursor); } static void dnd_drag_update_surface(struct dnd_drag *dnd_drag) { struct dnd *dnd = dnd_drag->dnd; cairo_surface_t *surface; struct wl_buffer *buffer; if (dnd_drag->mime_type && dnd_drag->dnd_action) surface = dnd_drag->opaque; else surface = dnd_drag->translucent; buffer = display_get_buffer_for_surface(dnd->display, surface); wl_surface_attach(dnd_drag->drag_surface, buffer, 0, 0); wl_surface_damage(dnd_drag->drag_surface, 0, 0, dnd_drag->width, dnd_drag->height); wl_surface_commit(dnd_drag->drag_surface); } static void data_source_target(void *data, struct wl_data_source *source, const char *mime_type) { struct dnd_drag *dnd_drag = data; dnd_drag->mime_type = mime_type; dnd_drag_update_surface(dnd_drag); dnd_drag_update_cursor(dnd_drag); } static void data_source_send(void *data, struct wl_data_source *source, const char *mime_type, int32_t fd) { struct dnd_flower_message dnd_flower_message; struct dnd_drag *dnd_drag = data; char buffer[128]; int n; if (strcmp(mime_type, flower_mime_type) == 0) { dnd_flower_message.seed = dnd_drag->item->seed; dnd_flower_message.x_offset = dnd_drag->x_offset; dnd_flower_message.y_offset = dnd_drag->y_offset; if (write(fd, &dnd_flower_message, sizeof dnd_flower_message) < 0) abort(); } else if (strcmp(mime_type, text_mime_type) == 0) { n = snprintf(buffer, sizeof buffer, "seed=%d x=%d y=%d\n", dnd_drag->item->seed, dnd_drag->x_offset, dnd_drag->y_offset); if (write(fd, buffer, n) < 0) abort(); } close(fd); } static void dnd_drag_destroy(struct dnd_drag *dnd_drag, bool delete_item) { struct dnd *dnd = dnd_drag->dnd; unsigned int i; wl_data_source_destroy(dnd_drag->data_source); if (delete_item) { for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { if (dnd_drag->item == dnd->items[i]) { dnd->items[i] = NULL; break; } } /* Destroy the item that has been dragged out */ cairo_surface_destroy(dnd_drag->item->surface); free(dnd_drag->item); } dnd->current_drag = NULL; wl_surface_destroy(dnd_drag->drag_surface); cairo_surface_destroy(dnd_drag->translucent); cairo_surface_destroy(dnd_drag->opaque); free(dnd_drag); } static void data_source_cancelled(void *data, struct wl_data_source *source) { struct dnd_drag *dnd_drag = data; struct dnd *dnd = dnd_drag->dnd; /* The 'cancelled' event means that the source is no longer in * use by the drag (or current selection). We need to clean * up the drag object created and the local state. */ dnd_drag_destroy(dnd_drag, false); window_schedule_redraw(dnd->window); } static void data_source_dnd_drop_performed(void *data, struct wl_data_source *source) { } static void data_source_dnd_finished(void *data, struct wl_data_source *source) { struct dnd_drag *dnd_drag = data; struct dnd *dnd = dnd_drag->dnd; bool delete_item; delete_item = dnd_drag->dnd_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; /* The operation is already finished, we can destroy all * related data. */ dnd_drag_destroy(dnd_drag, delete_item); window_schedule_redraw(dnd->window); } static void data_source_action(void *data, struct wl_data_source *source, uint32_t dnd_action) { struct dnd_drag *dnd_drag = data; dnd_drag->dnd_action = dnd_action; dnd_drag_update_surface(dnd_drag); dnd_drag_update_cursor(dnd_drag); } static const struct wl_data_source_listener data_source_listener = { data_source_target, data_source_send, data_source_cancelled, data_source_dnd_drop_performed, data_source_dnd_finished, data_source_action, }; static cairo_surface_t * create_drag_icon(struct dnd_drag *dnd_drag, struct item *item, int32_t x, int32_t y, double opacity) { struct dnd *dnd = dnd_drag->dnd; cairo_surface_t *surface; struct rectangle rectangle; cairo_pattern_t *pattern; cairo_t *cr; rectangle.width = item_width; rectangle.height = item_height; surface = display_create_surface(dnd->display, NULL, &rectangle, SURFACE_SHM); cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_surface(cr, item->surface, 0, 0); pattern = cairo_pattern_create_rgba(0, 0, 0, opacity); cairo_mask(cr, pattern); cairo_pattern_destroy(pattern); cairo_destroy(cr); dnd_drag->hotspot_x = x - item->x; dnd_drag->hotspot_y = y - item->y; dnd_drag->width = rectangle.width; dnd_drag->height = rectangle.height; return surface; } static int create_drag_source(struct dnd *dnd, struct input *input, uint32_t time, int32_t x, int32_t y) { struct item *item; struct rectangle allocation; struct dnd_drag *dnd_drag; struct display *display; struct wl_compositor *compositor; struct wl_buffer *buffer; unsigned int i; uint32_t serial; cairo_surface_t *icon; uint32_t actions; widget_get_allocation(dnd->widget, &allocation); item = dnd_get_item(dnd, x, y); x -= allocation.x; y -= allocation.y; if (item) { dnd_drag = xmalloc(sizeof *dnd_drag); dnd_drag->dnd = dnd; dnd_drag->input = input; dnd_drag->time = time; dnd_drag->item = item; dnd_drag->x_offset = x - item->x; dnd_drag->y_offset = y - item->y; dnd_drag->dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; dnd_drag->mime_type = NULL; actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; display = window_get_display(dnd->window); compositor = display_get_compositor(display); serial = display_get_serial(display); dnd_drag->drag_surface = wl_compositor_create_surface(compositor); if (display_get_data_device_manager_version(display) < WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION) { /* Data sources version < 3 will not get action * nor dnd_finished events, as we can't honor * the "move" action at the time of finishing * drag-and-drop, do it preemptively here. */ for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { if (item == dnd->items[i]){ dnd->items[i] = NULL; break; } } } if (dnd->self_only) { dnd_drag->data_source = NULL; } else { dnd_drag->data_source = display_create_data_source(dnd->display); if (!dnd_drag->data_source) { fprintf(stderr, "No data device manager\n"); abort(); } wl_data_source_add_listener(dnd_drag->data_source, &data_source_listener, dnd_drag); wl_data_source_offer(dnd_drag->data_source, flower_mime_type); wl_data_source_offer(dnd_drag->data_source, text_mime_type); } if (display_get_data_device_manager_version(display) >= WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION) { wl_data_source_set_actions(dnd_drag->data_source, actions); } wl_data_device_start_drag(input_get_data_device(input), dnd_drag->data_source, window_get_wl_surface(dnd->window), dnd_drag->drag_surface, serial); dnd_drag->opaque = create_drag_icon(dnd_drag, item, x, y, 1); dnd_drag->translucent = create_drag_icon(dnd_drag, item, x, y, 0.2); if (dnd->self_only) icon = dnd_drag->opaque; else icon = dnd_drag->translucent; buffer = display_get_buffer_for_surface(dnd->display, icon); wl_surface_attach(dnd_drag->drag_surface, buffer, -dnd_drag->hotspot_x, -dnd_drag->hotspot_y); wl_surface_damage(dnd_drag->drag_surface, 0, 0, dnd_drag->width, dnd_drag->height); wl_surface_commit(dnd_drag->drag_surface); dnd->current_drag = dnd_drag; window_schedule_redraw(dnd->window); return 0; } else return -1; } static int lookup_cursor(struct dnd *dnd, int x, int y) { struct item *item; item = dnd_get_item(dnd, x, y); if (item) return CURSOR_HAND1; else return CURSOR_LEFT_PTR; } /* Update all the mouse pointers in the window appropriately. * Optionally, skip one (which will be the current pointer just * about to start a drag). This is done here to save a scan * through the pointer list. */ static void update_pointer_images_except(struct dnd *dnd, struct input *except) { struct pointer *pointer; int32_t x, y; wl_list_for_each(pointer, &dnd->pointers, link) { if (pointer->input == except) { pointer->dragging = true; continue; } input_get_position(pointer->input, &x, &y); input_set_pointer_image(pointer->input, lookup_cursor(dnd, x, y)); } } static void dnd_button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct dnd *dnd = data; int32_t x, y; input_get_position(input, &x, &y); if (state == WL_POINTER_BUTTON_STATE_PRESSED) { input_ungrab(input); if (create_drag_source(dnd, input, time, x, y) == 0) { input_set_pointer_image(input, CURSOR_DRAGGING); update_pointer_images_except(dnd, input); } } } static void dnd_touch_down_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data) { struct dnd *dnd = data; int32_t int_x, int_y; if (id > 0) return; int_x = (int32_t)x; int_y = (int32_t)y; if (create_drag_source(dnd, input, time, int_x, int_y) == 0) touch_grab(input, 0); } static int dnd_enter_handler(struct widget *widget, struct input *input, float x, float y, void *data) { struct dnd *dnd = data; struct pointer *new_pointer = malloc(sizeof *new_pointer); if (new_pointer) { new_pointer->input = input; new_pointer->dragging = false; wl_list_insert(dnd->pointers.prev, &new_pointer->link); } return lookup_cursor(dnd, x, y); } static void dnd_leave_handler(struct widget *widget, struct input *input, void *data) { struct dnd *dnd = data; struct pointer *pointer, *tmp; wl_list_for_each_safe(pointer, tmp, &dnd->pointers, link) if (pointer->input == input) { wl_list_remove(&pointer->link); free(pointer); } } static int dnd_motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct dnd *dnd = data; struct pointer *pointer; wl_list_for_each(pointer, &dnd->pointers, link) if (pointer->input == input) { if (pointer->dragging) return CURSOR_DRAGGING; break; } return lookup_cursor(data, x, y); } static void dnd_data_handler(struct window *window, struct input *input, float x, float y, const char **types, void *data) { struct dnd *dnd = data; int i, has_flower = 0; if (!types) return; for (i = 0; types[i]; i++) if (strcmp(types[i], flower_mime_type) == 0) has_flower = 1; if (dnd_get_item(dnd, x, y) || dnd->self_only || !has_flower) { input_accept(input, NULL); } else { input_accept(input, flower_mime_type); } } static void dnd_receive_func(void *data, size_t len, int32_t x, int32_t y, void *user_data) { struct dnd *dnd = user_data; struct dnd_flower_message *message = data; struct item *item; struct rectangle allocation; if (len == 0) { return; } else if (len != sizeof *message) { fprintf(stderr, "odd message length %zu, expected %zu\n", len, sizeof *message); return; } widget_get_allocation(dnd->widget, &allocation); item = item_create(dnd->display, x - message->x_offset - allocation.x, y - message->y_offset - allocation.y, message->seed); dnd_add_item(dnd, item); update_pointer_images_except(dnd, NULL); window_schedule_redraw(dnd->window); } static void dnd_drop_handler(struct window *window, struct input *input, int32_t x, int32_t y, void *data) { struct dnd *dnd = data; struct dnd_flower_message message; if (dnd_get_item(dnd, x, y)) { fprintf(stderr, "got 'drop', but no target\n"); return; } if (!dnd->self_only) { input_receive_drag_data(input, flower_mime_type, dnd_receive_func, dnd); } else if (dnd->current_drag) { message.seed = dnd->current_drag->item->seed; message.x_offset = dnd->current_drag->x_offset; message.y_offset = dnd->current_drag->y_offset; dnd_receive_func(&message, sizeof message, x, y, dnd); } else { fprintf(stderr, "ignoring drop from another client\n"); } } static struct dnd * dnd_create(struct display *display) { struct dnd *dnd; int x, y; int32_t width, height; unsigned int i; dnd = xzalloc(sizeof *dnd); dnd->window = window_create(display); dnd->widget = window_frame_create(dnd->window, dnd); window_set_title(dnd->window, "Wayland Drag and Drop Demo"); dnd->display = display; dnd->key = 100; wl_list_init(&dnd->pointers); for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { x = (i % 4) * (item_width + item_padding) + item_padding; y = (i / 4) * (item_height + item_padding) + item_padding; if ((i ^ (i >> 2)) & 1) dnd->items[i] = item_create(display, x, y, 0); else dnd->items[i] = NULL; } window_set_user_data(dnd->window, dnd); window_set_keyboard_focus_handler(dnd->window, keyboard_focus_handler); window_set_data_handler(dnd->window, dnd_data_handler); window_set_drop_handler(dnd->window, dnd_drop_handler); widget_set_redraw_handler(dnd->widget, dnd_redraw_handler); widget_set_enter_handler(dnd->widget, dnd_enter_handler); widget_set_leave_handler(dnd->widget, dnd_leave_handler); widget_set_motion_handler(dnd->widget, dnd_motion_handler); widget_set_button_handler(dnd->widget, dnd_button_handler); widget_set_touch_down_handler(dnd->widget, dnd_touch_down_handler); width = 4 * (item_width + item_padding) + item_padding; height = 4 * (item_height + item_padding) + item_padding; window_frame_set_child_size(dnd->widget, width, height); return dnd; } static void dnd_destroy(struct dnd *dnd) { widget_destroy(dnd->widget); window_destroy(dnd->window); free(dnd); } int main(int argc, char *argv[]) { struct display *d; struct dnd *dnd; int self_only = 0; if (argc == 2 && !strcmp(argv[1], "--self-only")) self_only = 1; else if (argc > 1) { printf("Usage: %s [OPTIONS]\n --self-only\n", argv[0]); return 1; } d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } dnd = dnd_create(d); if (self_only) dnd->self_only = 1; display_run(d); dnd_destroy(dnd); display_destroy(d); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3122964 weston-8.0.0/clients/editor.c0000644000175000017460000012254200000000000016420 0ustar00simonwheel00000000000000/* * Copyright © 2012 Openismus GmbH * Copyright © 2012 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "shared/helpers.h" #include "shared/xalloc.h" #include "window.h" #include "text-input-unstable-v1-client-protocol.h" struct text_entry { struct widget *widget; struct window *window; char *text; int active; bool panel_visible; uint32_t cursor; uint32_t anchor; struct { char *text; int32_t cursor; char *commit; PangoAttrList *attr_list; } preedit; struct { PangoAttrList *attr_list; int32_t cursor; } preedit_info; struct { int32_t cursor; int32_t anchor; uint32_t delete_index; uint32_t delete_length; bool invalid_delete; } pending_commit; struct zwp_text_input_v1 *text_input; PangoLayout *layout; struct { xkb_mod_mask_t shift_mask; } keysym; uint32_t serial; uint32_t reset_serial; uint32_t content_purpose; bool click_to_show; char *preferred_language; bool button_pressed; }; struct editor { struct zwp_text_input_manager_v1 *text_input_manager; struct wl_data_source *selection; char *selected_text; struct display *display; struct window *window; struct widget *widget; struct text_entry *entry; struct text_entry *editor; struct text_entry *active_entry; }; static const char * utf8_end_char(const char *p) { while ((*p & 0xc0) == 0x80) p++; return p; } static const char * utf8_prev_char(const char *s, const char *p) { for (--p; p >= s; --p) { if ((*p & 0xc0) != 0x80) return p; } return NULL; } static const char * utf8_next_char(const char *p) { if (*p != 0) return utf8_end_char(++p); return NULL; } static void move_up(const char *p, uint32_t *cursor) { const char *posr, *posr_i; char text[16]; xkb_keysym_to_utf8(XKB_KEY_Return, text, sizeof(text)); posr = strstr(p, text); while (posr) { if (*cursor > (unsigned)(posr-p)) { posr_i = strstr(posr+1, text); if (!posr_i || !(*cursor > (unsigned)(posr_i-p))) { *cursor = posr-p; break; } posr = posr_i; } else { break; } } } static void move_down(const char *p, uint32_t *cursor) { const char *posr; char text[16]; xkb_keysym_to_utf8(XKB_KEY_Return, text, sizeof(text)); posr = strstr(p, text); while (posr) { if (*cursor <= (unsigned)(posr-p)) { *cursor = posr-p + 1; break; } posr = strstr(posr+1, text); } } static void text_entry_redraw_handler(struct widget *widget, void *data); static void text_entry_button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data); static void text_entry_touch_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float tx, float ty, void *data); static int text_entry_motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data); static void text_entry_insert_at_cursor(struct text_entry *entry, const char *text, int32_t cursor, int32_t anchor); static void text_entry_set_preedit(struct text_entry *entry, const char *preedit_text, int preedit_cursor); static void text_entry_delete_text(struct text_entry *entry, uint32_t index, uint32_t length); static void text_entry_delete_selected_text(struct text_entry *entry); static void text_entry_reset_preedit(struct text_entry *entry); static void text_entry_commit_and_reset(struct text_entry *entry); static void text_entry_get_cursor_rectangle(struct text_entry *entry, struct rectangle *rectangle); static void text_entry_update(struct text_entry *entry); static void text_input_commit_string(void *data, struct zwp_text_input_v1 *text_input, uint32_t serial, const char *text) { struct text_entry *entry = data; if ((entry->serial - serial) > (entry->serial - entry->reset_serial)) { fprintf(stderr, "Ignore commit. Serial: %u, Current: %u, Reset: %u\n", serial, entry->serial, entry->reset_serial); return; } if (entry->pending_commit.invalid_delete) { fprintf(stderr, "Ignore commit. Invalid previous delete_surrounding event.\n"); memset(&entry->pending_commit, 0, sizeof entry->pending_commit); return; } text_entry_reset_preedit(entry); if (entry->pending_commit.delete_length) { text_entry_delete_text(entry, entry->pending_commit.delete_index, entry->pending_commit.delete_length); } else { text_entry_delete_selected_text(entry); } text_entry_insert_at_cursor(entry, text, entry->pending_commit.cursor, entry->pending_commit.anchor); memset(&entry->pending_commit, 0, sizeof entry->pending_commit); widget_schedule_redraw(entry->widget); } static void clear_pending_preedit(struct text_entry *entry) { memset(&entry->pending_commit, 0, sizeof entry->pending_commit); pango_attr_list_unref(entry->preedit_info.attr_list); entry->preedit_info.cursor = 0; entry->preedit_info.attr_list = NULL; memset(&entry->preedit_info, 0, sizeof entry->preedit_info); } static void text_input_preedit_string(void *data, struct zwp_text_input_v1 *text_input, uint32_t serial, const char *text, const char *commit) { struct text_entry *entry = data; if ((entry->serial - serial) > (entry->serial - entry->reset_serial)) { fprintf(stderr, "Ignore preedit_string. Serial: %u, Current: %u, Reset: %u\n", serial, entry->serial, entry->reset_serial); clear_pending_preedit(entry); return; } if (entry->pending_commit.invalid_delete) { fprintf(stderr, "Ignore preedit_string. Invalid previous delete_surrounding event.\n"); clear_pending_preedit(entry); return; } if (entry->pending_commit.delete_length) { text_entry_delete_text(entry, entry->pending_commit.delete_index, entry->pending_commit.delete_length); } else { text_entry_delete_selected_text(entry); } text_entry_set_preedit(entry, text, entry->preedit_info.cursor); entry->preedit.commit = strdup(commit); entry->preedit.attr_list = pango_attr_list_ref(entry->preedit_info.attr_list); clear_pending_preedit(entry); text_entry_update(entry); widget_schedule_redraw(entry->widget); } static void text_input_delete_surrounding_text(void *data, struct zwp_text_input_v1 *text_input, int32_t index, uint32_t length) { struct text_entry *entry = data; uint32_t text_length; entry->pending_commit.delete_index = entry->cursor + index; entry->pending_commit.delete_length = length; entry->pending_commit.invalid_delete = false; text_length = strlen(entry->text); if (entry->pending_commit.delete_index > text_length || length > text_length || entry->pending_commit.delete_index + length > text_length) { fprintf(stderr, "delete_surrounding_text: Invalid index: %d," \ "length %u'; cursor: %u text length: %u\n", index, length, entry->cursor, text_length); entry->pending_commit.invalid_delete = true; return; } } static void text_input_cursor_position(void *data, struct zwp_text_input_v1 *text_input, int32_t index, int32_t anchor) { struct text_entry *entry = data; entry->pending_commit.cursor = index; entry->pending_commit.anchor = anchor; } static void text_input_preedit_styling(void *data, struct zwp_text_input_v1 *text_input, uint32_t index, uint32_t length, uint32_t style) { struct text_entry *entry = data; PangoAttribute *attr1 = NULL; PangoAttribute *attr2 = NULL; if (!entry->preedit_info.attr_list) entry->preedit_info.attr_list = pango_attr_list_new(); switch (style) { case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_DEFAULT: case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE: attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); break; case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_INCORRECT: attr1 = pango_attr_underline_new(PANGO_UNDERLINE_ERROR); attr2 = pango_attr_underline_color_new(65535, 0, 0); break; case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_SELECTION: attr1 = pango_attr_background_new(0.3 * 65535, 0.3 * 65535, 65535); attr2 = pango_attr_foreground_new(65535, 65535, 65535); break; case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT: case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_ACTIVE: attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); attr2 = pango_attr_weight_new(PANGO_WEIGHT_BOLD); break; case ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_INACTIVE: attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); attr2 = pango_attr_foreground_new(0.3 * 65535, 0.3 * 65535, 0.3 * 65535); break; } if (attr1) { attr1->start_index = entry->cursor + index; attr1->end_index = entry->cursor + index + length; pango_attr_list_insert(entry->preedit_info.attr_list, attr1); } if (attr2) { attr2->start_index = entry->cursor + index; attr2->end_index = entry->cursor + index + length; pango_attr_list_insert(entry->preedit_info.attr_list, attr2); } } static void text_input_preedit_cursor(void *data, struct zwp_text_input_v1 *text_input, int32_t index) { struct text_entry *entry = data; entry->preedit_info.cursor = index; } static void text_input_modifiers_map(void *data, struct zwp_text_input_v1 *text_input, struct wl_array *map) { struct text_entry *entry = data; entry->keysym.shift_mask = keysym_modifiers_get_mask(map, "Shift"); } static void text_input_keysym(void *data, struct zwp_text_input_v1 *text_input, uint32_t serial, uint32_t time, uint32_t key, uint32_t state, uint32_t modifiers) { struct text_entry *entry = data; const char *new_char; if (key == XKB_KEY_Left || key == XKB_KEY_Right) { if (state != WL_KEYBOARD_KEY_STATE_RELEASED) return; if (key == XKB_KEY_Left) new_char = utf8_prev_char(entry->text, entry->text + entry->cursor); else new_char = utf8_next_char(entry->text + entry->cursor); if (new_char != NULL) { entry->cursor = new_char - entry->text; } if (!(modifiers & entry->keysym.shift_mask)) entry->anchor = entry->cursor; widget_schedule_redraw(entry->widget); return; } if (key == XKB_KEY_Up || key == XKB_KEY_Down) { if (state != WL_KEYBOARD_KEY_STATE_RELEASED) return; if (key == XKB_KEY_Up) move_up(entry->text, &entry->cursor); else move_down(entry->text, &entry->cursor); if (!(modifiers & entry->keysym.shift_mask)) entry->anchor = entry->cursor; widget_schedule_redraw(entry->widget); return; } if (key == XKB_KEY_BackSpace) { const char *start, *end; if (state != WL_KEYBOARD_KEY_STATE_RELEASED) return; text_entry_commit_and_reset(entry); start = utf8_prev_char(entry->text, entry->text + entry->cursor); if (start == NULL) return; end = utf8_next_char(start); text_entry_delete_text(entry, start - entry->text, end - start); return; } if (key == XKB_KEY_Tab || key == XKB_KEY_KP_Enter || key == XKB_KEY_Return) { char text[16]; if (state != WL_KEYBOARD_KEY_STATE_RELEASED) return; xkb_keysym_to_utf8(key, text, sizeof(text)); text_entry_insert_at_cursor(entry, text, 0, 0); return; } } static void text_input_enter(void *data, struct zwp_text_input_v1 *text_input, struct wl_surface *surface) { struct text_entry *entry = data; if (surface != window_get_wl_surface(entry->window)) return; entry->active++; text_entry_update(entry); entry->reset_serial = entry->serial; widget_schedule_redraw(entry->widget); } static void text_input_leave(void *data, struct zwp_text_input_v1 *text_input) { struct text_entry *entry = data; text_entry_commit_and_reset(entry); entry->active--; if (!entry->active) { zwp_text_input_v1_hide_input_panel(text_input); entry->panel_visible = false; } widget_schedule_redraw(entry->widget); } static void text_input_input_panel_state(void *data, struct zwp_text_input_v1 *text_input, uint32_t state) { } static void text_input_language(void *data, struct zwp_text_input_v1 *text_input, uint32_t serial, const char *language) { fprintf(stderr, "input language is %s \n", language); } static void text_input_text_direction(void *data, struct zwp_text_input_v1 *text_input, uint32_t serial, uint32_t direction) { struct text_entry *entry = data; PangoContext *context = pango_layout_get_context(entry->layout); PangoDirection pango_direction; switch (direction) { case ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_LTR: pango_direction = PANGO_DIRECTION_LTR; break; case ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_RTL: pango_direction = PANGO_DIRECTION_RTL; break; case ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_AUTO: default: pango_direction = PANGO_DIRECTION_NEUTRAL; } pango_context_set_base_dir(context, pango_direction); } static const struct zwp_text_input_v1_listener text_input_listener = { text_input_enter, text_input_leave, text_input_modifiers_map, text_input_input_panel_state, text_input_preedit_string, text_input_preedit_styling, text_input_preedit_cursor, text_input_commit_string, text_input_cursor_position, text_input_delete_surrounding_text, text_input_keysym, text_input_language, text_input_text_direction }; static void data_source_target(void *data, struct wl_data_source *source, const char *mime_type) { } static void data_source_send(void *data, struct wl_data_source *source, const char *mime_type, int32_t fd) { struct editor *editor = data; if (write(fd, editor->selected_text, strlen(editor->selected_text) + 1) < 0) fprintf(stderr, "write failed: %s\n", strerror(errno)); close(fd); } static void data_source_cancelled(void *data, struct wl_data_source *source) { wl_data_source_destroy(source); } static const struct wl_data_source_listener data_source_listener = { data_source_target, data_source_send, data_source_cancelled }; static void paste_func(void *buffer, size_t len, int32_t x, int32_t y, void *data) { struct editor *editor = data; struct text_entry *entry = editor->active_entry; char *pasted_text; if (!entry) return; pasted_text = malloc(len + 1); strncpy(pasted_text, buffer, len); pasted_text[len] = '\0'; text_entry_insert_at_cursor(entry, pasted_text, 0, 0); free(pasted_text); } static void editor_copy_cut(struct editor *editor, struct input *input, bool cut) { struct text_entry *entry = editor->active_entry; if (!entry) return; if (entry->cursor != entry->anchor) { int start_index = MIN(entry->cursor, entry->anchor); int end_index = MAX(entry->cursor, entry->anchor); int len = end_index - start_index; editor->selected_text = realloc(editor->selected_text, len + 1); strncpy(editor->selected_text, &entry->text[start_index], len); editor->selected_text[len] = '\0'; if (cut) text_entry_delete_text(entry, start_index, len); editor->selection = display_create_data_source(editor->display); if (!editor->selection) return; wl_data_source_offer(editor->selection, "text/plain;charset=utf-8"); wl_data_source_add_listener(editor->selection, &data_source_listener, editor); input_set_selection(input, editor->selection, display_get_serial(editor->display)); } } static void editor_paste(struct editor *editor, struct input *input) { input_receive_selection_data(input, "text/plain;charset=utf-8", paste_func, editor); } static void menu_func(void *data, struct input *input, int index) { struct window *window = data; struct editor *editor = window_get_user_data(window); fprintf(stderr, "picked entry %d\n", index); switch (index) { case 0: editor_copy_cut(editor, input, true); break; case 1: editor_copy_cut(editor, input, false); break; case 2: editor_paste(editor, input); break; } } static void show_menu(struct editor *editor, struct input *input, uint32_t time) { int32_t x, y; static const char *entries[] = { "Cut", "Copy", "Paste" }; input_get_position(input, &x, &y); window_show_menu(editor->display, input, time, editor->window, x + 10, y + 20, menu_func, entries, ARRAY_LENGTH(entries)); } static struct text_entry* text_entry_create(struct editor *editor, const char *text) { struct text_entry *entry; entry = xzalloc(sizeof *entry); entry->widget = widget_add_widget(editor->widget, entry); entry->window = editor->window; entry->text = strdup(text); entry->active = 0; entry->panel_visible = false; entry->cursor = strlen(text); entry->anchor = entry->cursor; entry->text_input = zwp_text_input_manager_v1_create_text_input(editor->text_input_manager); zwp_text_input_v1_add_listener(entry->text_input, &text_input_listener, entry); widget_set_redraw_handler(entry->widget, text_entry_redraw_handler); widget_set_button_handler(entry->widget, text_entry_button_handler); widget_set_motion_handler(entry->widget, text_entry_motion_handler); widget_set_touch_down_handler(entry->widget, text_entry_touch_handler); return entry; } static void text_entry_destroy(struct text_entry *entry) { widget_destroy(entry->widget); zwp_text_input_v1_destroy(entry->text_input); g_clear_object(&entry->layout); free(entry->text); free(entry->preferred_language); free(entry); } static void redraw_handler(struct widget *widget, void *data) { struct editor *editor = data; cairo_surface_t *surface; struct rectangle allocation; cairo_t *cr; surface = window_get_surface(editor->window); widget_get_allocation(editor->widget, &allocation); cr = cairo_create(surface); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_clip(cr); cairo_translate(cr, allocation.x, allocation.y); /* Draw background */ cairo_push_group(cr); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 1, 1, 1, 1); cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); cairo_fill(cr); cairo_pop_group_to_source(cr); cairo_paint(cr); cairo_destroy(cr); cairo_surface_destroy(surface); } static void text_entry_allocate(struct text_entry *entry, int32_t x, int32_t y, int32_t width, int32_t height) { widget_set_allocation(entry->widget, x, y, width, height); } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct editor *editor = data; struct rectangle allocation; widget_get_allocation(editor->widget, &allocation); text_entry_allocate(editor->entry, allocation.x + 20, allocation.y + 20, width - 40, height / 2 - 40); text_entry_allocate(editor->editor, allocation.x + 20, allocation.y + height / 2 + 20, width - 40, height / 2 - 40); } static void text_entry_activate(struct text_entry *entry, struct wl_seat *seat) { struct wl_surface *surface = window_get_wl_surface(entry->window); if (entry->click_to_show && entry->active) { entry->panel_visible = !entry->panel_visible; if (entry->panel_visible) zwp_text_input_v1_show_input_panel(entry->text_input); else zwp_text_input_v1_hide_input_panel(entry->text_input); return; } if (!entry->click_to_show) zwp_text_input_v1_show_input_panel(entry->text_input); zwp_text_input_v1_activate(entry->text_input, seat, surface); } static void text_entry_deactivate(struct text_entry *entry, struct wl_seat *seat) { zwp_text_input_v1_deactivate(entry->text_input, seat); } static void text_entry_update_layout(struct text_entry *entry) { char *text; PangoAttrList *attr_list; assert(entry->cursor <= (strlen(entry->text) + (entry->preedit.text ? strlen(entry->preedit.text) : 0))); if (entry->preedit.text) { text = xmalloc(strlen(entry->text) + strlen(entry->preedit.text) + 1); strncpy(text, entry->text, entry->cursor); strcpy(text + entry->cursor, entry->preedit.text); strcpy(text + entry->cursor + strlen(entry->preedit.text), entry->text + entry->cursor); } else { text = strdup(entry->text); } if (entry->cursor != entry->anchor) { int start_index = MIN(entry->cursor, entry->anchor); int end_index = MAX(entry->cursor, entry->anchor); PangoAttribute *attr; attr_list = pango_attr_list_copy(entry->preedit.attr_list); if (!attr_list) attr_list = pango_attr_list_new(); attr = pango_attr_background_new(0.3 * 65535, 0.3 * 65535, 65535); attr->start_index = start_index; attr->end_index = end_index; pango_attr_list_insert(attr_list, attr); attr = pango_attr_foreground_new(65535, 65535, 65535); attr->start_index = start_index; attr->end_index = end_index; pango_attr_list_insert(attr_list, attr); } else { attr_list = pango_attr_list_ref(entry->preedit.attr_list); } if (entry->preedit.text && !entry->preedit.attr_list) { PangoAttribute *attr; if (!attr_list) attr_list = pango_attr_list_new(); attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); attr->start_index = entry->cursor; attr->end_index = entry->cursor + strlen(entry->preedit.text); pango_attr_list_insert(attr_list, attr); } if (entry->layout) { pango_layout_set_text(entry->layout, text, -1); pango_layout_set_attributes(entry->layout, attr_list); } free(text); pango_attr_list_unref(attr_list); } static void text_entry_update(struct text_entry *entry) { struct rectangle cursor_rectangle; zwp_text_input_v1_set_content_type(entry->text_input, ZWP_TEXT_INPUT_V1_CONTENT_HINT_NONE, entry->content_purpose); zwp_text_input_v1_set_surrounding_text(entry->text_input, entry->text, entry->cursor, entry->anchor); if (entry->preferred_language) zwp_text_input_v1_set_preferred_language(entry->text_input, entry->preferred_language); text_entry_get_cursor_rectangle(entry, &cursor_rectangle); zwp_text_input_v1_set_cursor_rectangle(entry->text_input, cursor_rectangle.x, cursor_rectangle.y, cursor_rectangle.width, cursor_rectangle.height); zwp_text_input_v1_commit_state(entry->text_input, ++entry->serial); } static void text_entry_insert_at_cursor(struct text_entry *entry, const char *text, int32_t cursor, int32_t anchor) { char *new_text = xmalloc(strlen(entry->text) + strlen(text) + 1); strncpy(new_text, entry->text, entry->cursor); strcpy(new_text + entry->cursor, text); strcpy(new_text + entry->cursor + strlen(text), entry->text + entry->cursor); free(entry->text); entry->text = new_text; if (anchor >= 0) entry->anchor = entry->cursor + strlen(text) + anchor; else entry->anchor = entry->cursor + 1 + anchor; if (cursor >= 0) entry->cursor += strlen(text) + cursor; else entry->cursor += 1 + cursor; text_entry_update_layout(entry); widget_schedule_redraw(entry->widget); text_entry_update(entry); } static void text_entry_reset_preedit(struct text_entry *entry) { entry->preedit.cursor = 0; free(entry->preedit.text); entry->preedit.text = NULL; free(entry->preedit.commit); entry->preedit.commit = NULL; pango_attr_list_unref(entry->preedit.attr_list); entry->preedit.attr_list = NULL; } static void text_entry_commit_and_reset(struct text_entry *entry) { char *commit = NULL; if (entry->preedit.commit) commit = strdup(entry->preedit.commit); text_entry_reset_preedit(entry); if (commit) { text_entry_insert_at_cursor(entry, commit, 0, 0); free(commit); } zwp_text_input_v1_reset(entry->text_input); text_entry_update(entry); entry->reset_serial = entry->serial; } static void text_entry_set_preedit(struct text_entry *entry, const char *preedit_text, int preedit_cursor) { text_entry_reset_preedit(entry); if (!preedit_text) return; entry->preedit.text = strdup(preedit_text); entry->preedit.cursor = preedit_cursor; text_entry_update_layout(entry); widget_schedule_redraw(entry->widget); } static uint32_t text_entry_try_invoke_preedit_action(struct text_entry *entry, int32_t x, int32_t y, uint32_t button, enum wl_pointer_button_state state) { int index, trailing; uint32_t cursor; const char *text; if (!entry->preedit.text) return 0; pango_layout_xy_to_index(entry->layout, x * PANGO_SCALE, y * PANGO_SCALE, &index, &trailing); text = pango_layout_get_text(entry->layout); cursor = g_utf8_offset_to_pointer(text + index, trailing) - text; if (cursor < entry->cursor || cursor > entry->cursor + strlen(entry->preedit.text)) { return 0; } if (state == WL_POINTER_BUTTON_STATE_RELEASED) zwp_text_input_v1_invoke_action(entry->text_input, button, cursor - entry->cursor); return 1; } static bool text_entry_has_preedit(struct text_entry *entry) { return entry->preedit.text && (strlen(entry->preedit.text) > 0); } static void text_entry_set_cursor_position(struct text_entry *entry, int32_t x, int32_t y, bool move_anchor) { int index, trailing; const char *text; uint32_t cursor; pango_layout_xy_to_index(entry->layout, x * PANGO_SCALE, y * PANGO_SCALE, &index, &trailing); text = pango_layout_get_text(entry->layout); cursor = g_utf8_offset_to_pointer(text + index, trailing) - text; if (move_anchor) entry->anchor = cursor; if (text_entry_has_preedit(entry)) { text_entry_commit_and_reset(entry); assert(!text_entry_has_preedit(entry)); } if (entry->cursor == cursor) return; entry->cursor = cursor; text_entry_update_layout(entry); widget_schedule_redraw(entry->widget); text_entry_update(entry); } static void text_entry_delete_text(struct text_entry *entry, uint32_t index, uint32_t length) { uint32_t l; assert(index <= strlen(entry->text)); assert(index + length <= strlen(entry->text)); assert(index + length >= length); l = strlen(entry->text + index + length); memmove(entry->text + index, entry->text + index + length, l + 1); if (entry->cursor > (index + length)) entry->cursor -= length; else if (entry->cursor > index) entry->cursor = index; entry->anchor = entry->cursor; text_entry_update_layout(entry); widget_schedule_redraw(entry->widget); text_entry_update(entry); } static void text_entry_delete_selected_text(struct text_entry *entry) { uint32_t start_index = entry->anchor < entry->cursor ? entry->anchor : entry->cursor; uint32_t end_index = entry->anchor < entry->cursor ? entry->cursor : entry->anchor; if (entry->anchor == entry->cursor) return; text_entry_delete_text(entry, start_index, end_index - start_index); entry->anchor = entry->cursor; } static void text_entry_get_cursor_rectangle(struct text_entry *entry, struct rectangle *rectangle) { struct rectangle allocation; PangoRectangle extents; PangoRectangle cursor_pos; widget_get_allocation(entry->widget, &allocation); if (entry->preedit.text && entry->preedit.cursor < 0) { rectangle->x = 0; rectangle->y = 0; rectangle->width = 0; rectangle->height = 0; return; } pango_layout_get_extents(entry->layout, &extents, NULL); pango_layout_get_cursor_pos(entry->layout, entry->cursor + entry->preedit.cursor, &cursor_pos, NULL); rectangle->x = allocation.x + (allocation.height / 2) + PANGO_PIXELS(cursor_pos.x); rectangle->y = allocation.y + 10 + PANGO_PIXELS(cursor_pos.y); rectangle->width = PANGO_PIXELS(cursor_pos.width); rectangle->height = PANGO_PIXELS(cursor_pos.height); } static void text_entry_draw_cursor(struct text_entry *entry, cairo_t *cr) { PangoRectangle extents; PangoRectangle cursor_pos; if (entry->preedit.text && entry->preedit.cursor < 0) return; pango_layout_get_extents(entry->layout, &extents, NULL); pango_layout_get_cursor_pos(entry->layout, entry->cursor + entry->preedit.cursor, &cursor_pos, NULL); cairo_set_line_width(cr, 1.0); cairo_move_to(cr, PANGO_PIXELS(cursor_pos.x), PANGO_PIXELS(cursor_pos.y)); cairo_line_to(cr, PANGO_PIXELS(cursor_pos.x), PANGO_PIXELS(cursor_pos.y) + PANGO_PIXELS(cursor_pos.height)); cairo_stroke(cr); } static int text_offset_left(struct rectangle *allocation) { return 10; } static int text_offset_top(struct rectangle *allocation) { return allocation->height / 2; } static void text_entry_redraw_handler(struct widget *widget, void *data) { struct text_entry *entry = data; cairo_surface_t *surface; struct rectangle allocation; cairo_t *cr; surface = window_get_surface(entry->window); widget_get_allocation(entry->widget, &allocation); cr = cairo_create(surface); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_clip(cr); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_push_group(cr); cairo_translate(cr, allocation.x, allocation.y); cairo_set_source_rgba(cr, 1, 1, 1, 1); cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); cairo_fill(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); if (entry->active) { cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); cairo_set_line_width (cr, 3); cairo_set_source_rgba(cr, 0, 0, 1, 1.0); cairo_stroke(cr); } cairo_set_source_rgba(cr, 0, 0, 0, 1); cairo_translate(cr, text_offset_left(&allocation), text_offset_top(&allocation)); if (!entry->layout) entry->layout = pango_cairo_create_layout(cr); else pango_cairo_update_layout(cr, entry->layout); text_entry_update_layout(entry); pango_cairo_show_layout(cr, entry->layout); text_entry_draw_cursor(entry, cr); cairo_pop_group_to_source(cr); cairo_paint(cr); cairo_destroy(cr); cairo_surface_destroy(surface); } static int text_entry_motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct text_entry *entry = data; struct rectangle allocation; int tx, ty; if (!entry->button_pressed) { return CURSOR_IBEAM; } widget_get_allocation(entry->widget, &allocation); tx = x - allocation.x - text_offset_left(&allocation); ty = y - allocation.y - text_offset_top(&allocation); text_entry_set_cursor_position(entry, tx, ty, false); return CURSOR_IBEAM; } static void text_entry_button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct text_entry *entry = data; struct rectangle allocation; struct editor *editor; int32_t x, y; uint32_t result; widget_get_allocation(entry->widget, &allocation); input_get_position(input, &x, &y); x -= allocation.x + text_offset_left(&allocation); y -= allocation.y + text_offset_top(&allocation); editor = window_get_user_data(entry->window); switch (button) { case BTN_LEFT: entry->button_pressed = (state == WL_POINTER_BUTTON_STATE_PRESSED); if (state == WL_POINTER_BUTTON_STATE_PRESSED) input_grab(input, entry->widget, button); else input_ungrab(input); break; case BTN_RIGHT: if (state == WL_POINTER_BUTTON_STATE_PRESSED) show_menu(editor, input, time); break; } if (text_entry_has_preedit(entry)) { result = text_entry_try_invoke_preedit_action(entry, x, y, button, state); if (result) return; } if (state == WL_POINTER_BUTTON_STATE_PRESSED && button == BTN_LEFT) { struct wl_seat *seat = input_get_seat(input); text_entry_activate(entry, seat); editor->active_entry = entry; text_entry_set_cursor_position(entry, x, y, true); } } static void text_entry_touch_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float tx, float ty, void *data) { struct text_entry *entry = data; struct wl_seat *seat = input_get_seat(input); struct rectangle allocation; struct editor *editor; int32_t x, y; widget_get_allocation(entry->widget, &allocation); x = tx - (allocation.x + text_offset_left(&allocation)); y = ty - (allocation.y + text_offset_top(&allocation)); editor = window_get_user_data(entry->window); text_entry_activate(entry, seat); editor->active_entry = entry; text_entry_set_cursor_position(entry, x, y, true); } static void editor_button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct editor *editor = data; if (button != BTN_LEFT) { return; } if (state == WL_POINTER_BUTTON_STATE_PRESSED) { struct wl_seat *seat = input_get_seat(input); text_entry_deactivate(editor->entry, seat); text_entry_deactivate(editor->editor, seat); editor->active_entry = NULL; } } static void editor_touch_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float tx, float ty, void *data) { struct editor *editor = data; struct wl_seat *seat = input_get_seat(input); text_entry_deactivate(editor->entry, seat); text_entry_deactivate(editor->editor, seat); editor->active_entry = NULL; } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { struct editor *editor = data; window_schedule_redraw(editor->window); } static int handle_bound_key(struct editor *editor, struct input *input, uint32_t sym, uint32_t time) { switch (sym) { case XKB_KEY_X: editor_copy_cut(editor, input, true); return 1; case XKB_KEY_C: editor_copy_cut(editor, input, false); return 1; case XKB_KEY_V: editor_paste(editor, input); return 1; default: return 0; } } static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data) { struct editor *editor = data; struct text_entry *entry; const char *new_char; char text[16]; uint32_t modifiers; if (!editor->active_entry) return; entry = editor->active_entry; if (state != WL_KEYBOARD_KEY_STATE_PRESSED) return; modifiers = input_get_modifiers(input); if ((modifiers & MOD_CONTROL_MASK) && (modifiers & MOD_SHIFT_MASK) && handle_bound_key(editor, input, sym, time)) return; switch (sym) { case XKB_KEY_BackSpace: text_entry_commit_and_reset(entry); new_char = utf8_prev_char(entry->text, entry->text + entry->cursor); if (new_char != NULL) text_entry_delete_text(entry, new_char - entry->text, (entry->text + entry->cursor) - new_char); break; case XKB_KEY_Delete: text_entry_commit_and_reset(entry); new_char = utf8_next_char(entry->text + entry->cursor); if (new_char != NULL) text_entry_delete_text(entry, entry->cursor, new_char - (entry->text + entry->cursor)); break; case XKB_KEY_Left: text_entry_commit_and_reset(entry); new_char = utf8_prev_char(entry->text, entry->text + entry->cursor); if (new_char != NULL) { entry->cursor = new_char - entry->text; if (!(input_get_modifiers(input) & MOD_SHIFT_MASK)) entry->anchor = entry->cursor; widget_schedule_redraw(entry->widget); } break; case XKB_KEY_Right: text_entry_commit_and_reset(entry); new_char = utf8_next_char(entry->text + entry->cursor); if (new_char != NULL) { entry->cursor = new_char - entry->text; if (!(input_get_modifiers(input) & MOD_SHIFT_MASK)) entry->anchor = entry->cursor; widget_schedule_redraw(entry->widget); } break; case XKB_KEY_Up: text_entry_commit_and_reset(entry); move_up(entry->text, &entry->cursor); if (!(input_get_modifiers(input) & MOD_SHIFT_MASK)) entry->anchor = entry->cursor; widget_schedule_redraw(entry->widget); break; case XKB_KEY_Down: text_entry_commit_and_reset(entry); move_down(entry->text, &entry->cursor); if (!(input_get_modifiers(input) & MOD_SHIFT_MASK)) entry->anchor = entry->cursor; widget_schedule_redraw(entry->widget); break; case XKB_KEY_Escape: break; default: if (xkb_keysym_to_utf8(sym, text, sizeof(text)) <= 0) break; text_entry_commit_and_reset(entry); text_entry_insert_at_cursor(entry, text, 0, 0); break; } widget_schedule_redraw(entry->widget); } static void global_handler(struct display *display, uint32_t name, const char *interface, uint32_t version, void *data) { struct editor *editor = data; if (!strcmp(interface, "zwp_text_input_manager_v1")) { editor->text_input_manager = display_bind(display, name, &zwp_text_input_manager_v1_interface, 1); } } /** Display help for command line options, and exit */ static bool opt_help = false; /** Require a distinct click to show the input panel (virtual keyboard) */ static bool opt_click_to_show = false; /** Set a specific (RFC-3066) language. Used for the virtual keyboard, etc. */ static const char *opt_preferred_language = NULL; /** * \brief command line options for editor */ static const struct weston_option editor_options[] = { { WESTON_OPTION_BOOLEAN, "help", 'h', &opt_help }, { WESTON_OPTION_BOOLEAN, "click-to-show", 'C', &opt_click_to_show }, { WESTON_OPTION_STRING, "preferred-language", 'L', &opt_preferred_language }, }; static void usage(const char *program_name, int exit_code) { unsigned k; fprintf(stderr, "Usage: %s [OPTIONS] [FILENAME]\n\n", program_name); for (k = 0; k < ARRAY_LENGTH(editor_options); k++) { const struct weston_option *p = &editor_options[k]; if (p->name) { fprintf(stderr, " --%s", p->name); if (p->type != WESTON_OPTION_BOOLEAN) fprintf(stderr, "=VALUE"); fprintf(stderr, "\n"); } if (p->short_name) { fprintf(stderr, " -%c", p->short_name); if (p->type != WESTON_OPTION_BOOLEAN) fprintf(stderr, "VALUE"); fprintf(stderr, "\n"); } } exit(exit_code); } /* Load the contents of a file into a UTF-8 text buffer and return it. * * Caller is responsible for freeing the buffer when done. * On error, returns NULL. */ static char * read_file(char *filename) { char *buffer = NULL; int buf_size, read_size; FILE *fin; int errsv; fin = fopen(filename, "r"); if (fin == NULL) goto error; /* Determine required buffer size */ if (fseek(fin, 0, SEEK_END) != 0) goto error; buf_size = ftell(fin); if (buf_size < 0) goto error; rewind(fin); /* Create buffer and read in the text */ buffer = (char*) malloc(sizeof(char) * (buf_size + 1)); if (buffer == NULL) goto error; read_size = fread(buffer, sizeof(char), buf_size, fin); fclose(fin); if (buf_size != read_size) goto error; buffer[buf_size] = '\0'; return buffer; error: errsv = errno; if (fin) fclose(fin); free(buffer); errno = errsv ? errsv : EINVAL; return NULL; } int main(int argc, char *argv[]) { struct editor editor; char *text_buffer = NULL; parse_options(editor_options, ARRAY_LENGTH(editor_options), &argc, argv); if (opt_help) usage(argv[0], EXIT_SUCCESS); if (argc > 1) { if (argv[1][0] == '-') usage(argv[0], EXIT_FAILURE); text_buffer = read_file(argv[1]); if (text_buffer == NULL) { fprintf(stderr, "could not read file '%s': %s\n", argv[1], strerror(errno)); return -1; } } memset(&editor, 0, sizeof editor); editor.display = display_create(&argc, argv); if (editor.display == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); free(text_buffer); return -1; } display_set_user_data(editor.display, &editor); display_set_global_handler(editor.display, global_handler); if (editor.text_input_manager == NULL) { fprintf(stderr, "No text input manager global\n"); display_destroy(editor.display); free(text_buffer); return -1; } editor.window = window_create(editor.display); editor.widget = window_frame_create(editor.window, &editor); if (text_buffer) editor.entry = text_entry_create(&editor, text_buffer); else editor.entry = text_entry_create(&editor, "Entry"); editor.entry->click_to_show = opt_click_to_show; if (opt_preferred_language) editor.entry->preferred_language = strdup(opt_preferred_language); editor.editor = text_entry_create(&editor, "Numeric"); editor.editor->content_purpose = ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NUMBER; editor.editor->click_to_show = opt_click_to_show; editor.selection = NULL; editor.selected_text = NULL; window_set_title(editor.window, "Text Editor"); window_set_key_handler(editor.window, key_handler); window_set_keyboard_focus_handler(editor.window, keyboard_focus_handler); window_set_user_data(editor.window, &editor); widget_set_redraw_handler(editor.widget, redraw_handler); widget_set_resize_handler(editor.widget, resize_handler); widget_set_button_handler(editor.widget, editor_button_handler); widget_set_touch_down_handler(editor.widget, editor_touch_handler); window_schedule_resize(editor.window, 500, 400); display_run(editor.display); if (editor.selected_text) free(editor.selected_text); if (editor.selection) wl_data_source_destroy(editor.selection); text_entry_destroy(editor.entry); text_entry_destroy(editor.editor); widget_destroy(editor.widget); window_destroy(editor.window); display_destroy(editor.display); free(text_buffer); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3122964 weston-8.0.0/clients/eventdemo.c0000644000175000017460000003354000000000000017117 0ustar00simonwheel00000000000000/* * Copyright © 2011 Tim Wiederhake * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /** * \file eventdemo.c * \brief Demonstrate the use of Wayland's toytoolkit. * * Heavily commented demo program that can report all events that are * dispatched to the window. For other functionality, eg. opengl/egl, * drag and drop, etc. have a look at the other demos. * \author Tim Wiederhake */ #include "config.h" #include #include #include #include #include #include #include #include "shared/helpers.h" #include "window.h" /** window title */ static char *title = "EventDemo"; /** window width */ static int width = 500; /** window height */ static int height = 400; /** set if window has no borders */ static bool noborder = false; /** if non-zero, maximum window width */ static int width_max = 0; /** if non-zero, maximum window height */ static int height_max = 0; /** set to log redrawing */ static bool log_redraw = false; /** set to log resizing */ static bool log_resize = false; /** set to log keyboard focus */ static bool log_focus = false; /** set to log key events */ static bool log_key = false; /** set to log button events */ static bool log_button = false; /** set to log axis events */ static bool log_axis = false; /** set to log motion events */ static bool log_motion = false; /** * \struct eventdemo * \brief Holds all data the program needs per window * * In this demo the struct holds the position of a * red rectangle that is drawn in the window's area. */ struct eventdemo { struct window *window; struct widget *widget; struct display *display; int x, y, w, h; bool print_pointer_frame; }; /** * \brief CALLBACK function, Wayland requests the window to redraw. * \param widget widget to be redrawn * \param data user data associated to the window * * Draws a red rectangle as demonstration of per-window data. */ static void redraw_handler(struct widget *widget, void *data) { struct eventdemo *e = data; cairo_surface_t *surface; cairo_t *cr; struct rectangle rect; if (log_redraw) printf("redraw\n"); widget_get_allocation(e->widget, &rect); surface = window_get_surface(e->window); cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height); cairo_set_source_rgba(cr, 0, 0, 0, 0.8); cairo_fill(cr); cairo_rectangle(cr, e->x, e->y, e->w, e->h); cairo_set_source_rgba(cr, 1.0, 0, 0, 1); cairo_fill(cr); cairo_destroy(cr); cairo_surface_destroy(surface); } /** * \brief CALLBACK function, Wayland requests the window to resize. * \param widget widget to be resized * \param width desired width * \param height desired height * \param data user data associated to the window */ static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct eventdemo *e = data; if (log_resize) printf("resize width: %d, height: %d\n", width, height); /* if a maximum width is set, constrain to it */ if (width_max && width_max < width) width = width_max; /* if a maximum height is set, constrain to it */ if (height_max && height_max < height) height = height_max; /* set the new window dimensions */ widget_set_size(e->widget, width, height); } /** * \brief CALLBACK function, Wayland informs about keyboard focus change * \param window window * \param device device that caused the focus change * \param data user data associated to the window */ static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { int32_t x, y; struct eventdemo *e = data; if (log_focus) { if (device) { input_get_position(device, &x, &y); printf("focus x: %d, y: %d\n", x, y); } else { printf("focus lost\n"); } } window_schedule_redraw(e->window); } /** * \brief CALLBACK function, Wayland informs about key event * \param window window * \param input input * \param time time * \param key keycode * \param unicode associated character * \param state pressed or released * \param data user data associated to the window */ static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t unicode, enum wl_keyboard_key_state state, void *data) { uint32_t modifiers = input_get_modifiers(input); if (!log_key) return; printf("key key: %u, unicode: %u, state: %s, modifiers: 0x%x\n", key, unicode, (state == WL_KEYBOARD_KEY_STATE_PRESSED) ? "pressed" : "released", modifiers); } /** * \brief CALLBACK function, Wayland informs about button event * \param widget widget * \param input input device that caused the button event * \param time time the event happened * \param button button * \param state pressed or released * \param data user data associated to the window */ static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct eventdemo *e = data; int32_t x, y; if (!log_button) return; e->print_pointer_frame = true; input_get_position(input, &x, &y); printf("button time: %u, button: %u, state: %s, x: %d, y: %d\n", time, button, (state == WL_POINTER_BUTTON_STATE_PRESSED) ? "pressed" : "released", x, y); } /** * \brief CALLBACK function, Wayland informs about axis event * \param widget widget * \param input input device that caused the axis event * \param time time the event happened * \param axis vertical or horizontal * \param value amount of scrolling * \param data user data associated to the widget */ static void axis_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t axis, wl_fixed_t value, void *data) { struct eventdemo *e = data; if (!log_axis) return; e->print_pointer_frame = true; printf("axis time: %u, axis: %s, value: %f\n", time, axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? "vertical" : "horizontal", wl_fixed_to_double(value)); } static void pointer_frame_handler(struct widget *widget, struct input *input, void *data) { struct eventdemo *e = data; if (!e->print_pointer_frame) return; printf("pointer frame\n"); e->print_pointer_frame = false; } static void axis_source_handler(struct widget *widget, struct input *input, uint32_t source, void *data) { const char *axis_source; struct eventdemo *e = data; if (!log_axis) return; e->print_pointer_frame = true; switch (source) { case WL_POINTER_AXIS_SOURCE_WHEEL: axis_source = "wheel"; break; case WL_POINTER_AXIS_SOURCE_FINGER: axis_source = "finger"; break; case WL_POINTER_AXIS_SOURCE_CONTINUOUS: axis_source = "continuous"; break; default: axis_source = ""; break; } printf("axis source: %s\n", axis_source); } static void axis_stop_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t axis, void *data) { struct eventdemo *e = data; if (!log_axis) return; e->print_pointer_frame = true; printf("axis stop time: %u, axis: %s\n", time, axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? "vertical" : "horizontal"); } static void axis_discrete_handler(struct widget *widget, struct input *input, uint32_t axis, int32_t discrete, void *data) { struct eventdemo *e = data; if (!log_axis) return; e->print_pointer_frame = true; printf("axis discrete axis: %u value: %d\n", axis, discrete); } /** * \brief CALLBACK function, Waylands informs about pointer motion * \param widget widget * \param input input device that caused the motion event * \param time time the event happened * \param x absolute x position * \param y absolute y position * \param x x position relative to the window * \param y y position relative to the window * \param data user data associated to the window * * Demonstrates the use of different cursors */ static int motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct eventdemo *e = data; if (log_motion) { printf("motion time: %u, x: %f, y: %f\n", time, x, y); e->print_pointer_frame = true; } if (x > e->x && x < e->x + e->w) if (y > e->y && y < e->y + e->h) return CURSOR_HAND1; return CURSOR_LEFT_PTR; } /** * \brief Create and initialise a new eventdemo window. * The returned eventdemo instance should be destroyed using \c eventdemo_destroy(). * \param d associated display */ static struct eventdemo * eventdemo_create(struct display *d) { struct eventdemo *e; e = zalloc(sizeof (struct eventdemo)); if (e == NULL) return NULL; e->window = window_create(d); if (noborder) { /* Demonstrate how to create a borderless window. * Move windows with META + left mouse button. */ e->widget = window_add_widget(e->window, e); } else { e->widget = window_frame_create(e->window, e); window_set_title(e->window, title); } e->display = d; /* The eventdemo window draws a red rectangle as a demonstration * of per-window data. The dimensions of that rectangle are set * here. */ e->x = width * 1.0 / 4.0; e->w = width * 2.0 / 4.0; e->y = height * 1.0 / 4.0; e->h = height * 2.0 / 4.0; /* Connect the user data to the window */ window_set_user_data(e->window, e); /* Set the callback redraw handler for the window */ widget_set_redraw_handler(e->widget, redraw_handler); /* Set the callback resize handler for the window */ widget_set_resize_handler(e->widget, resize_handler); /* Set the callback focus handler for the window */ window_set_keyboard_focus_handler(e->window, keyboard_focus_handler); /* Set the callback key handler for the window */ window_set_key_handler(e->window, key_handler); /* Set the callback button handler for the window */ widget_set_button_handler(e->widget, button_handler); /* Set the callback motion handler for the window */ widget_set_motion_handler(e->widget, motion_handler); /* Set the callback pointer frame handler for the window */ widget_set_pointer_frame_handler(e->widget, pointer_frame_handler); /* Set the callback axis handler for the window */ widget_set_axis_handlers(e->widget, axis_handler, axis_source_handler, axis_stop_handler, axis_discrete_handler); /* Initial drawing of the window */ window_schedule_resize(e->window, width, height); return e; } /** * \brief Destroy eventdemo instance previously created by \c eventdemo_create(). * \param eventdemo eventdemo instance to destroy */ static void eventdemo_destroy(struct eventdemo * eventdemo) { widget_destroy(eventdemo->widget); window_destroy(eventdemo->window); free(eventdemo); } /** * \brief command line options for eventdemo */ static const struct weston_option eventdemo_options[] = { { WESTON_OPTION_STRING, "title", 0, &title }, { WESTON_OPTION_INTEGER, "width", 'w', &width }, { WESTON_OPTION_INTEGER, "height", 'h', &height }, { WESTON_OPTION_INTEGER, "max-width", 0, &width_max }, { WESTON_OPTION_INTEGER, "max-height", 0, &height_max }, { WESTON_OPTION_BOOLEAN, "no-border", 'b', &noborder }, { WESTON_OPTION_BOOLEAN, "log-redraw", 0, &log_redraw }, { WESTON_OPTION_BOOLEAN, "log-resize", 0, &log_resize }, { WESTON_OPTION_BOOLEAN, "log-focus", 0, &log_focus }, { WESTON_OPTION_BOOLEAN, "log-key", 0, &log_key }, { WESTON_OPTION_BOOLEAN, "log-button", 0, &log_button }, { WESTON_OPTION_BOOLEAN, "log-axis", 0, &log_axis }, { WESTON_OPTION_BOOLEAN, "log-motion", 0, &log_motion }, }; /** * \brief Connects to the display, creates the window and hands over * to the main loop. */ int main(int argc, char *argv[]) { struct display *d; struct eventdemo *e; if (parse_options(eventdemo_options, ARRAY_LENGTH(eventdemo_options), &argc, argv) > 1) { unsigned k; printf("Usage: %s [OPTIONS]\n\n", argv[0]); for (k = 0; k < ARRAY_LENGTH(eventdemo_options); k++) { const struct weston_option* p = &eventdemo_options[k]; if (p->name) { printf(" --%s", p->name); if (p->type != WESTON_OPTION_BOOLEAN) printf("=VALUE"); putchar('\n'); } if (p->short_name) { printf(" -%c", p->short_name); if (p->type != WESTON_OPTION_BOOLEAN) printf("VALUE"); putchar('\n'); } } return 1; } if (!log_redraw && !log_resize && !log_focus && !log_key && !log_button && !log_axis && !log_motion) log_redraw = log_resize = log_focus = log_key = log_button = log_axis = log_motion = true; /* Connect to the display and have the arguments parsed */ d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } /* Create new eventdemo window */ e = eventdemo_create(d); if (e == NULL) { fprintf(stderr, "failed to create eventdemo: %s\n", strerror(errno)); return -1; } display_run(d); /* Release resources */ eventdemo_destroy(e); display_destroy(d); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3122964 weston-8.0.0/clients/flower.c0000644000175000017460000001263100000000000016425 0ustar00simonwheel00000000000000/* * Copyright © 2008 Kristian Høgsberg * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "window.h" struct flower { struct display *display; struct window *window; struct widget *widget; int width, height; }; static void set_random_color(cairo_t *cr) { cairo_set_source_rgba(cr, 0.5 + (random() % 50) / 49.0, 0.5 + (random() % 50) / 49.0, 0.5 + (random() % 50) / 49.0, 0.5 + (random() % 100) / 99.0); } static void draw_stuff(cairo_surface_t *surface, int width, int height) { const int petal_count = 3 + random() % 5; const double r1 = 60 + random() % 35; const double r2 = 20 + random() % 40; const double u = (10 + random() % 90) / 100.0; const double v = (random() % 90) / 100.0; cairo_t *cr; int i; double t, dt = 2 * M_PI / (petal_count * 2); double x1, y1, x2, y2, x3, y3; cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 0, 0, 0, 0); cairo_paint(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); cairo_translate(cr, width / 2, height / 2); cairo_move_to(cr, cos(0) * r1, sin(0) * r1); for (t = 0, i = 0; i < petal_count; i++, t += dt * 2) { x1 = cos(t) * r1; y1 = sin(t) * r1; x2 = cos(t + dt) * r2; y2 = sin(t + dt) * r2; x3 = cos(t + 2 * dt) * r1; y3 = sin(t + 2 * dt) * r1; cairo_curve_to(cr, x1 - y1 * u, y1 + x1 * u, x2 + y2 * v, y2 - x2 * v, x2, y2); cairo_curve_to(cr, x2 - y2 * v, y2 + x2 * v, x3 + y3 * u, y3 - x3 * u, x3, y3); } cairo_close_path(cr); set_random_color(cr); cairo_fill_preserve(cr); set_random_color(cr); cairo_stroke(cr); cairo_destroy(cr); } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct flower *flower = data; /* Don't resize me */ widget_set_size(flower->widget, flower->width, flower->height); } static void redraw_handler(struct widget *widget, void *data) { struct flower *flower = data; cairo_surface_t *surface; surface = window_get_surface(flower->window); if (surface == NULL || cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "failed to create cairo egl surface\n"); return; } draw_stuff(surface, flower->width, flower->height); cairo_surface_destroy(surface); } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct flower *flower = data; switch (button) { case BTN_LEFT: if (state == WL_POINTER_BUTTON_STATE_PRESSED) window_move(flower->window, input, display_get_serial(flower->display)); break; case BTN_MIDDLE: if (state == WL_POINTER_BUTTON_STATE_PRESSED) widget_schedule_redraw(widget); break; case BTN_RIGHT: if (state == WL_POINTER_BUTTON_STATE_PRESSED) window_show_frame_menu(flower->window, input, time); break; } } static void touch_down_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data) { struct flower *flower = data; window_move(flower->window, input, display_get_serial(flower->display)); } int main(int argc, char *argv[]) { struct flower flower; struct display *d; struct timeval tv; d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } gettimeofday(&tv, NULL); srandom(tv.tv_usec); flower.width = 200; flower.height = 200; flower.display = d; flower.window = window_create(d); flower.widget = window_add_widget(flower.window, &flower); window_set_title(flower.window, "Flower"); widget_set_resize_handler(flower.widget, resize_handler); widget_set_redraw_handler(flower.widget, redraw_handler); widget_set_button_handler(flower.widget, button_handler); widget_set_default_cursor(flower.widget, CURSOR_HAND1); widget_set_touch_down_handler(flower.widget, touch_down_handler); window_schedule_resize(flower.window, flower.width, flower.height); display_run(d); widget_destroy(flower.widget); window_destroy(flower.window); display_destroy(d); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3122964 weston-8.0.0/clients/fullscreen.c0000644000175000017460000003631000000000000017271 0ustar00simonwheel00000000000000/* * Copyright © 2008 Kristian Høgsberg * Copyright © 2012 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "window.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include struct fs_output { struct wl_list link; struct output *output; }; struct fullscreen { struct display *display; struct window *window; struct widget *widget; struct zwp_fullscreen_shell_v1 *fshell; enum zwp_fullscreen_shell_v1_present_method present_method; int width, height; int fullscreen; float pointer_x, pointer_y; int draw_cursor; struct wl_list output_list; struct fs_output *current_output; }; static void fullscreen_handler(struct window *window, void *data) { struct fullscreen *fullscreen = data; fullscreen->fullscreen ^= 1; window_set_fullscreen(window, fullscreen->fullscreen); } static void draw_string(cairo_t *cr, const char *fmt, ...) { char buffer[4096]; char *p, *end; va_list argp; cairo_text_extents_t text_extents; cairo_font_extents_t font_extents; cairo_save(cr); cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 14); cairo_font_extents (cr, &font_extents); va_start(argp, fmt); vsnprintf(buffer, sizeof(buffer), fmt, argp); p = buffer; while (*p) { end = strchr(p, '\n'); if (end) *end = 0; cairo_show_text(cr, p); cairo_text_extents (cr, p, &text_extents); cairo_rel_move_to (cr, -text_extents.x_advance, font_extents.height); if (end) p = end + 1; else break; } va_end(argp); cairo_restore(cr); } static void redraw_handler(struct widget *widget, void *data) { struct fullscreen *fullscreen = data; struct rectangle allocation; cairo_surface_t *surface; cairo_t *cr; int i; double x, y, border; const char *method_name[] = { "default", "center", "zoom", "zoom_crop", "stretch"}; surface = window_get_surface(fullscreen->window); if (surface == NULL || cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "failed to create cairo egl surface\n"); return; } widget_get_allocation(fullscreen->widget, &allocation); cr = widget_cairo_create(widget); cairo_set_source_rgb(cr, 0, 0, 0); cairo_paint (cr); cairo_set_source_rgb(cr, 0, 0, 1); cairo_set_line_width (cr, 10); cairo_rectangle(cr, 5, 5, allocation.width - 10, allocation.height - 10); cairo_stroke (cr); cairo_move_to(cr, allocation.x + 15, allocation.y + 25); cairo_set_source_rgb(cr, 1, 1, 1); if (fullscreen->fshell) { draw_string(cr, "Surface size: %d, %d\n" "Scale: %d, transform: %d\n" "Pointer: %f,%f\n" "Output: %s, present method: %s\n" "Keys: (s)cale, (t)ransform, si(z)e, (m)ethod,\n" " (o)utput, modes(w)itch, (q)uit\n", fullscreen->width, fullscreen->height, window_get_buffer_scale (fullscreen->window), window_get_buffer_transform (fullscreen->window), fullscreen->pointer_x, fullscreen->pointer_y, method_name[fullscreen->present_method], fullscreen->current_output ? output_get_model(fullscreen->current_output->output): "null"); } else { draw_string(cr, "Surface size: %d, %d\n" "Scale: %d, transform: %d\n" "Pointer: %f,%f\n" "Fullscreen: %d\n" "Keys: (s)cale, (t)ransform, si(z)e, (f)ullscreen, (q)uit\n", fullscreen->width, fullscreen->height, window_get_buffer_scale (fullscreen->window), window_get_buffer_transform (fullscreen->window), fullscreen->pointer_x, fullscreen->pointer_y, fullscreen->fullscreen); } y = 100; i = 0; while (y + 60 < fullscreen->height) { border = (i++ % 2 == 0) ? 1 : 0.5; x = 50; cairo_set_line_width (cr, border); while (x + 70 < fullscreen->width) { if (window_has_focus(fullscreen->window) && fullscreen->pointer_x >= x && fullscreen->pointer_x < x + 50 && fullscreen->pointer_y >= y && fullscreen->pointer_y < y + 40) { cairo_set_source_rgb(cr, 1, 0, 0); cairo_rectangle(cr, x, y, 50, 40); cairo_fill(cr); } cairo_set_source_rgb(cr, 0, 1, 0); cairo_rectangle(cr, x + border/2.0, y + border/2.0, 50, 40); cairo_stroke(cr); x += 60; } y += 50; } if (window_has_focus(fullscreen->window) && fullscreen->draw_cursor) { cairo_set_source_rgb(cr, 1, 1, 1); cairo_set_line_width (cr, 8); cairo_move_to(cr, fullscreen->pointer_x - 12, fullscreen->pointer_y - 12); cairo_line_to(cr, fullscreen->pointer_x + 12, fullscreen->pointer_y + 12); cairo_stroke(cr); cairo_move_to(cr, fullscreen->pointer_x + 12, fullscreen->pointer_y - 12); cairo_line_to(cr, fullscreen->pointer_x - 12, fullscreen->pointer_y + 12); cairo_stroke(cr); cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width (cr, 4); cairo_move_to(cr, fullscreen->pointer_x - 10, fullscreen->pointer_y - 10); cairo_line_to(cr, fullscreen->pointer_x + 10, fullscreen->pointer_y + 10); cairo_stroke(cr); cairo_move_to(cr, fullscreen->pointer_x + 10, fullscreen->pointer_y - 10); cairo_line_to(cr, fullscreen->pointer_x - 10, fullscreen->pointer_y + 10); cairo_stroke(cr); } cairo_destroy(cr); } static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data) { struct fullscreen *fullscreen = data; int transform, scale; static int current_size = 0; struct fs_output *fsout; struct wl_output *wl_output; int widths[] = { 640, 320, 800, 400 }; int heights[] = { 480, 240, 600, 300 }; if (state == WL_KEYBOARD_KEY_STATE_RELEASED) return; switch (sym) { case XKB_KEY_t: transform = window_get_buffer_transform (window); transform = (transform + 1) % 8; window_set_buffer_transform(window, transform); window_schedule_redraw(window); break; case XKB_KEY_s: scale = window_get_buffer_scale (window); if (scale == 1) scale = 2; else scale = 1; window_set_buffer_scale(window, scale); window_schedule_redraw(window); break; case XKB_KEY_z: if (fullscreen->fullscreen) break; current_size = (current_size + 1) % 4; fullscreen->width = widths[current_size]; fullscreen->height = heights[current_size]; window_schedule_resize(fullscreen->window, fullscreen->width, fullscreen->height); break; case XKB_KEY_m: if (!fullscreen->fshell) break; wl_output = NULL; if (fullscreen->current_output) wl_output = output_get_wl_output(fullscreen->current_output->output); fullscreen->present_method = (fullscreen->present_method + 1) % 5; zwp_fullscreen_shell_v1_present_surface(fullscreen->fshell, window_get_wl_surface(fullscreen->window), fullscreen->present_method, wl_output); window_schedule_redraw(window); break; case XKB_KEY_o: if (!fullscreen->fshell) break; fsout = fullscreen->current_output; wl_output = fsout ? output_get_wl_output(fsout->output) : NULL; /* Clear the current presentation */ zwp_fullscreen_shell_v1_present_surface(fullscreen->fshell, NULL, 0, wl_output); if (fullscreen->current_output) { if (fullscreen->current_output->link.next == &fullscreen->output_list) fsout = NULL; else fsout = wl_container_of(fullscreen->current_output->link.next, fsout, link); } else { fsout = wl_container_of(fullscreen->output_list.next, fsout, link); } fullscreen->current_output = fsout; wl_output = fsout ? output_get_wl_output(fsout->output) : NULL; zwp_fullscreen_shell_v1_present_surface(fullscreen->fshell, window_get_wl_surface(fullscreen->window), fullscreen->present_method, wl_output); window_schedule_redraw(window); break; case XKB_KEY_w: if (!fullscreen->fshell || !fullscreen->current_output) break; wl_output = NULL; if (fullscreen->current_output) wl_output = output_get_wl_output(fullscreen->current_output->output); zwp_fullscreen_shell_mode_feedback_v1_destroy( zwp_fullscreen_shell_v1_present_surface_for_mode(fullscreen->fshell, window_get_wl_surface(fullscreen->window), wl_output, 0)); window_schedule_redraw(window); break; case XKB_KEY_f: if (fullscreen->fshell) break; fullscreen->fullscreen ^= 1; window_set_fullscreen(window, fullscreen->fullscreen); break; case XKB_KEY_q: exit (0); break; } } static int motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct fullscreen *fullscreen = data; fullscreen->pointer_x = x; fullscreen->pointer_y = y; widget_schedule_redraw(widget); return fullscreen->draw_cursor ? CURSOR_BLANK : CURSOR_LEFT_PTR; } static int enter_handler(struct widget *widget, struct input *input, float x, float y, void *data) { struct fullscreen *fullscreen = data; fullscreen->pointer_x = x; fullscreen->pointer_y = y; widget_schedule_redraw(widget); return fullscreen->draw_cursor ? CURSOR_BLANK : CURSOR_LEFT_PTR; } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct fullscreen *fullscreen = data; switch (button) { case BTN_LEFT: if (state == WL_POINTER_BUTTON_STATE_PRESSED) window_move(fullscreen->window, input, display_get_serial(fullscreen->display)); break; case BTN_RIGHT: if (state == WL_POINTER_BUTTON_STATE_PRESSED) window_show_frame_menu(fullscreen->window, input, time); break; } } static void touch_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data) { struct fullscreen *fullscreen = data; window_move(fullscreen->window, input, display_get_serial(fullscreen->display)); } static void fshell_capability_handler(void *data, struct zwp_fullscreen_shell_v1 *fshell, uint32_t capability) { struct fullscreen *fullscreen = data; switch (capability) { case ZWP_FULLSCREEN_SHELL_V1_CAPABILITY_CURSOR_PLANE: fullscreen->draw_cursor = 0; break; default: break; } } struct zwp_fullscreen_shell_v1_listener fullscreen_shell_listener = { fshell_capability_handler }; static void usage(int error_code) { fprintf(stderr, "Usage: fullscreen [OPTIONS]\n\n" " -w \tSet window width to \n" " -h \tSet window height to \n" " --help\tShow this help text\n\n"); exit(error_code); } static void output_handler(struct output *output, void *data) { struct fullscreen *fullscreen = data; struct fs_output *fsout; /* If we've already seen this one, don't add it to the list */ wl_list_for_each(fsout, &fullscreen->output_list, link) if (fsout->output == output) return; fsout = zalloc(sizeof *fsout); if (fsout == NULL) { fprintf(stderr, "out of memory in output_handler\n"); return; } fsout->output = output; wl_list_insert(&fullscreen->output_list, &fsout->link); } static void global_handler(struct display *display, uint32_t id, const char *interface, uint32_t version, void *data) { struct fullscreen *fullscreen = data; if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { fullscreen->fshell = display_bind(display, id, &zwp_fullscreen_shell_v1_interface, 1); zwp_fullscreen_shell_v1_add_listener(fullscreen->fshell, &fullscreen_shell_listener, fullscreen); } } int main(int argc, char *argv[]) { struct fullscreen fullscreen; struct display *d; int i; fullscreen.width = 640; fullscreen.height = 480; fullscreen.fullscreen = 0; fullscreen.present_method = ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT; wl_list_init(&fullscreen.output_list); fullscreen.current_output = NULL; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-w") == 0) { if (++i >= argc) usage(EXIT_FAILURE); fullscreen.width = atol(argv[i]); } else if (strcmp(argv[i], "-h") == 0) { if (++i >= argc) usage(EXIT_FAILURE); fullscreen.height = atol(argv[i]); } else if (strcmp(argv[i], "--help") == 0) usage(EXIT_SUCCESS); else usage(EXIT_FAILURE); } d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } fullscreen.display = d; fullscreen.fshell = NULL; display_set_user_data(fullscreen.display, &fullscreen); display_set_global_handler(fullscreen.display, global_handler); display_set_output_configure_handler(fullscreen.display, output_handler); if (fullscreen.fshell) { fullscreen.window = window_create_custom(d); zwp_fullscreen_shell_v1_present_surface(fullscreen.fshell, window_get_wl_surface(fullscreen.window), fullscreen.present_method, NULL); /* If we get the CURSOR_PLANE capability, we'll change this */ fullscreen.draw_cursor = 1; } else { fullscreen.window = window_create(d); fullscreen.draw_cursor = 0; } fullscreen.widget = window_add_widget(fullscreen.window, &fullscreen); window_set_title(fullscreen.window, "Fullscreen"); widget_set_transparent(fullscreen.widget, 0); widget_set_default_cursor(fullscreen.widget, CURSOR_LEFT_PTR); widget_set_redraw_handler(fullscreen.widget, redraw_handler); widget_set_button_handler(fullscreen.widget, button_handler); widget_set_motion_handler(fullscreen.widget, motion_handler); widget_set_enter_handler(fullscreen.widget, enter_handler); widget_set_touch_down_handler(fullscreen.widget, touch_handler); window_set_key_handler(fullscreen.window, key_handler); window_set_fullscreen_handler(fullscreen.window, fullscreen_handler); window_set_user_data(fullscreen.window, &fullscreen); /* Hack to set minimum allocation so we can shrink later */ window_schedule_resize(fullscreen.window, 1, 1); window_schedule_resize(fullscreen.window, fullscreen.width, fullscreen.height); display_run(d); widget_destroy(fullscreen.widget); window_destroy(fullscreen.window); display_destroy(d); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3156297 weston-8.0.0/clients/gears.c0000644000175000017460000003141500000000000016231 0ustar00simonwheel00000000000000/* * Copyright © 2008 Kristian Høgsberg * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "window.h" struct gears { struct window *window; struct widget *widget; struct display *d; EGLDisplay display; EGLDisplay config; EGLContext context; GLfloat angle; struct { GLfloat rotx; GLfloat roty; } view; int button_down; int last_x, last_y; GLint gear_list[3]; int fullscreen; int frames; uint32_t last_fps; }; struct gear_template { GLfloat material[4]; GLfloat inner_radius; GLfloat outer_radius; GLfloat width; GLint teeth; GLfloat tooth_depth; }; static const struct gear_template gear_templates[] = { { { 0.8, 0.1, 0.0, 1.0 }, 1.0, 4.0, 1.0, 20, 0.7 }, { { 0.0, 0.8, 0.2, 1.0 }, 0.5, 2.0, 2.0, 10, 0.7 }, { { 0.2, 0.2, 1.0, 1.0 }, 1.3, 2.0, 0.5, 10, 0.7 }, }; static GLfloat light_pos[4] = {5.0, 5.0, 10.0, 0.0}; static void die(const char *msg) { fprintf(stderr, "%s", msg); exit(EXIT_FAILURE); } static void make_gear(const struct gear_template *t) { GLint i; GLfloat r0, r1, r2; GLfloat angle, da; GLfloat u, v, len; glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, t->material); r0 = t->inner_radius; r1 = t->outer_radius - t->tooth_depth / 2.0; r2 = t->outer_radius + t->tooth_depth / 2.0; da = 2.0 * M_PI / t->teeth / 4.0; glShadeModel(GL_FLAT); glNormal3f(0.0, 0.0, 1.0); /* draw front face */ glBegin(GL_QUAD_STRIP); for (i = 0; i <= t->teeth; i++) { angle = i * 2.0 * M_PI / t->teeth; glVertex3f(r0 * cos(angle), r0 * sin(angle), t->width * 0.5); glVertex3f(r1 * cos(angle), r1 * sin(angle), t->width * 0.5); if (i < t->teeth) { glVertex3f(r0 * cos(angle), r0 * sin(angle), t->width * 0.5); glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), t->width * 0.5); } } glEnd(); /* draw front sides of teeth */ glBegin(GL_QUADS); da = 2.0 * M_PI / t->teeth / 4.0; for (i = 0; i < t->teeth; i++) { angle = i * 2.0 * M_PI / t->teeth; glVertex3f(r1 * cos(angle), r1 * sin(angle), t->width * 0.5); glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), t->width * 0.5); glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), t->width * 0.5); glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), t->width * 0.5); } glEnd(); glNormal3f(0.0, 0.0, -1.0); /* draw back face */ glBegin(GL_QUAD_STRIP); for (i = 0; i <= t->teeth; i++) { angle = i * 2.0 * M_PI / t->teeth; glVertex3f(r1 * cos(angle), r1 * sin(angle), -t->width * 0.5); glVertex3f(r0 * cos(angle), r0 * sin(angle), -t->width * 0.5); if (i < t->teeth) { glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -t->width * 0.5); glVertex3f(r0 * cos(angle), r0 * sin(angle), -t->width * 0.5); } } glEnd(); /* draw back sides of teeth */ glBegin(GL_QUADS); da = 2.0 * M_PI / t->teeth / 4.0; for (i = 0; i < t->teeth; i++) { angle = i * 2.0 * M_PI / t->teeth; glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -t->width * 0.5); glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -t->width * 0.5); glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -t->width * 0.5); glVertex3f(r1 * cos(angle), r1 * sin(angle), -t->width * 0.5); } glEnd(); /* draw outward faces of teeth */ glBegin(GL_QUAD_STRIP); for (i = 0; i < t->teeth; i++) { angle = i * 2.0 * M_PI / t->teeth; glVertex3f(r1 * cos(angle), r1 * sin(angle), t->width * 0.5); glVertex3f(r1 * cos(angle), r1 * sin(angle), -t->width * 0.5); u = r2 * cos(angle + da) - r1 * cos(angle); v = r2 * sin(angle + da) - r1 * sin(angle); len = sqrt(u * u + v * v); u /= len; v /= len; glNormal3f(v, -u, 0.0); glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), t->width * 0.5); glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -t->width * 0.5); glNormal3f(cos(angle), sin(angle), 0.0); glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), t->width * 0.5); glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -t->width * 0.5); u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da); v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da); glNormal3f(v, -u, 0.0); glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), t->width * 0.5); glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -t->width * 0.5); glNormal3f(cos(angle), sin(angle), 0.0); } glVertex3f(r1 * cos(0), r1 * sin(0), t->width * 0.5); glVertex3f(r1 * cos(0), r1 * sin(0), -t->width * 0.5); glEnd(); glShadeModel(GL_SMOOTH); /* draw inside radius cylinder */ glBegin(GL_QUAD_STRIP); for (i = 0; i <= t->teeth; i++) { angle = i * 2.0 * M_PI / t->teeth; glNormal3f(-cos(angle), -sin(angle), 0.0); glVertex3f(r0 * cos(angle), r0 * sin(angle), -t->width * 0.5); glVertex3f(r0 * cos(angle), r0 * sin(angle), t->width * 0.5); } glEnd(); } static void update_fps(struct gears *gears, uint32_t time) { long diff_ms; static bool first_call = true; if (first_call) { gears->last_fps = time; first_call = false; } else gears->frames++; diff_ms = time - gears->last_fps; if (diff_ms > 5000) { float seconds = diff_ms / 1000.0; float fps = gears->frames / seconds; printf("%d frames in %6.3f seconds = %6.3f FPS\n", gears->frames, seconds, fps); fflush(stdout); gears->frames = 0; gears->last_fps = time; } } static void frame_callback(void *data, struct wl_callback *callback, uint32_t time) { struct gears *gears = data; update_fps(gears, time); gears->angle = (GLfloat) (time % 8192) * 360 / 8192.0; window_schedule_redraw(gears->window); if (callback) wl_callback_destroy(callback); } static const struct wl_callback_listener listener = { frame_callback }; static int motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct gears *gears = data; int offset_x, offset_y; float step = 0.5; if (gears->button_down) { offset_x = x - gears->last_x; offset_y = y - gears->last_y; gears->last_x = x; gears->last_y = y; gears->view.roty += offset_x * step; gears->view.rotx += offset_y * step; if (gears->view.roty >= 360) gears->view.roty = gears->view.roty - 360; if (gears->view.roty <= 0) gears->view.roty = gears->view.roty + 360; if (gears->view.rotx >= 360) gears->view.rotx = gears->view.rotx - 360; if (gears->view.rotx <= 0) gears->view.rotx = gears->view.rotx + 360; } return CURSOR_LEFT_PTR; } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct gears *gears = data; if (button == BTN_LEFT) { if (state == WL_POINTER_BUTTON_STATE_PRESSED) { gears->button_down = 1; input_get_position(input, &gears->last_x, &gears->last_y); } else { gears->button_down = 0; } } } static void redraw_handler(struct widget *widget, void *data) { struct rectangle window_allocation; struct rectangle allocation; struct wl_callback *callback; struct gears *gears = data; widget_get_allocation(gears->widget, &allocation); window_get_allocation(gears->window, &window_allocation); if (display_acquire_window_surface(gears->d, gears->window, gears->context) < 0) { die("Unable to acquire window surface, " "compiled without cairo-egl?\n"); } glViewport(allocation.x, window_allocation.height - allocation.height - allocation.y, allocation.width, allocation.height); glScissor(allocation.x, window_allocation.height - allocation.height - allocation.y, allocation.width, allocation.height); glEnable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glTranslatef(0.0, 0.0, -50); glRotatef(gears->view.rotx, 1.0, 0.0, 0.0); glRotatef(gears->view.roty, 0.0, 1.0, 0.0); glPushMatrix(); glTranslatef(-3.0, -2.0, 0.0); glRotatef(gears->angle, 0.0, 0.0, 1.0); glCallList(gears->gear_list[0]); glPopMatrix(); glPushMatrix(); glTranslatef(3.1, -2.0, 0.0); glRotatef(-2.0 * gears->angle - 9.0, 0.0, 0.0, 1.0); glCallList(gears->gear_list[1]); glPopMatrix(); glPushMatrix(); glTranslatef(-3.1, 4.2, 0.0); glRotatef(-2.0 * gears->angle - 25.0, 0.0, 0.0, 1.0); glCallList(gears->gear_list[2]); glPopMatrix(); glPopMatrix(); glFlush(); display_release_window_surface(gears->d, gears->window); callback = wl_surface_frame(window_get_wl_surface(gears->window)); wl_callback_add_listener(callback, &listener, gears); } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct gears *gears = data; int32_t size, big, small; /* Constrain child size to be square and at least 300x300 */ if (width < height) { small = width; big = height; } else { small = height; big = width; } if (gears->fullscreen) size = small; else size = big; widget_set_size(gears->widget, size, size); } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { window_schedule_redraw(window); } static void fullscreen_handler(struct window *window, void *data) { struct gears *gears = data; gears->fullscreen ^= 1; window_set_fullscreen(window, gears->fullscreen); } static struct gears * gears_create(struct display *display) { const int width = 450, height = 500; struct gears *gears; int i; gears = zalloc(sizeof *gears); gears->d = display; gears->window = window_create(display); gears->widget = window_frame_create(gears->window, gears); window_set_title(gears->window, "Wayland Gears"); gears->display = display_get_egl_display(gears->d); if (gears->display == NULL) die("failed to create egl display\n"); eglBindAPI(EGL_OPENGL_API); gears->config = display_get_argb_egl_config(gears->d); gears->context = eglCreateContext(gears->display, gears->config, EGL_NO_CONTEXT, NULL); if (gears->context == NULL) die("failed to create context\n"); if (!eglMakeCurrent(gears->display, NULL, NULL, gears->context)) die("failed to make context current\n"); for (i = 0; i < 3; i++) { gears->gear_list[i] = glGenLists(1); glNewList(gears->gear_list[i], GL_COMPILE); make_gear(&gear_templates[i]); glEndList(); } gears->button_down = 0; gears->last_x = 0; gears->last_y = 0; gears->view.rotx = 20.0; gears->view.roty = 30.0; printf("Warning: FPS count is limited by the wayland compositor or monitor refresh rate\n"); glEnable(GL_NORMALIZE); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(-1.0, 1.0, -1.0, 1.0, 5.0, 200.0); glMatrixMode(GL_MODELVIEW); glLightfv(GL_LIGHT0, GL_POSITION, light_pos); glEnable(GL_CULL_FACE); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_DEPTH_TEST); glClearColor(0, 0, 0, 0.92); window_set_user_data(gears->window, gears); widget_set_resize_handler(gears->widget, resize_handler); widget_set_redraw_handler(gears->widget, redraw_handler); widget_set_button_handler(gears->widget, button_handler); widget_set_motion_handler(gears->widget, motion_handler); window_set_keyboard_focus_handler(gears->window, keyboard_focus_handler); window_set_fullscreen_handler(gears->window, fullscreen_handler); window_schedule_resize(gears->window, width, height); return gears; } static void gears_destroy(struct gears *gears) { widget_destroy(gears->widget); window_destroy(gears->window); free(gears); } int main(int argc, char *argv[]) { struct display *d; struct gears *gears; d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } gears = gears_create(d); display_run(d); gears_destroy(gears); display_destroy(d); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3156297 weston-8.0.0/clients/image.c0000644000175000017460000002467400000000000016223 0ustar00simonwheel00000000000000/* * Copyright © 2008 Kristian Høgsberg * Copyright © 2009 Chris Wilson * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "window.h" #include "shared/cairo-util.h" struct image { struct window *window; struct widget *widget; struct display *display; char *filename; cairo_surface_t *image; int fullscreen; int *image_counter; int32_t width, height; struct { double x; double y; } pointer; bool button_pressed; bool initialized; cairo_matrix_t matrix; }; static double get_scale(struct image *image) { assert(image->matrix.xy == 0.0 && image->matrix.yx == 0.0 && image->matrix.xx == image->matrix.yy); return image->matrix.xx; } static void clamp_view(struct image *image) { struct rectangle allocation; double scale = get_scale(image); double sw, sh; sw = image->width * scale; sh = image->height * scale; widget_get_allocation(image->widget, &allocation); if (sw < allocation.width) { image->matrix.x0 = (allocation.width - image->width * scale) / 2; } else { if (image->matrix.x0 > 0.0) image->matrix.x0 = 0.0; if (sw + image->matrix.x0 < allocation.width) image->matrix.x0 = allocation.width - sw; } if (sh < allocation.height) { image->matrix.y0 = (allocation.height - image->height * scale) / 2; } else { if (image->matrix.y0 > 0.0) image->matrix.y0 = 0.0; if (sh + image->matrix.y0 < allocation.height) image->matrix.y0 = allocation.height - sh; } } static void redraw_handler(struct widget *widget, void *data) { struct image *image = data; struct rectangle allocation; cairo_t *cr; cairo_surface_t *surface; double width, height, doc_aspect, window_aspect, scale; cairo_matrix_t matrix; cairo_matrix_t translate; surface = window_get_surface(image->window); cr = cairo_create(surface); widget_get_allocation(image->widget, &allocation); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_clip(cr); cairo_push_group(cr); cairo_translate(cr, allocation.x, allocation.y); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 0, 0, 0, 1); cairo_paint(cr); if (!image->initialized) { image->initialized = true; width = cairo_image_surface_get_width(image->image); height = cairo_image_surface_get_height(image->image); doc_aspect = width / height; window_aspect = (double) allocation.width / allocation.height; if (doc_aspect < window_aspect) scale = allocation.height / height; else scale = allocation.width / width; image->width = width; image->height = height; cairo_matrix_init_scale(&image->matrix, scale, scale); clamp_view(image); } matrix = image->matrix; cairo_matrix_init_translate(&translate, allocation.x, allocation.y); cairo_matrix_multiply(&matrix, &matrix, &translate); cairo_set_matrix(cr, &matrix); cairo_set_source_surface(cr, image->image, 0, 0); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); cairo_paint(cr); cairo_pop_group_to_source(cr); cairo_paint(cr); cairo_destroy(cr); cairo_surface_destroy(surface); } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct image *image = data; clamp_view(image); } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { struct image *image = data; window_schedule_redraw(image->window); } static int enter_handler(struct widget *widget, struct input *input, float x, float y, void *data) { struct image *image = data; struct rectangle allocation; widget_get_allocation(image->widget, &allocation); x -= allocation.x; y -= allocation.y; image->pointer.x = x; image->pointer.y = y; return 1; } static void move_viewport(struct image *image, double dx, double dy) { double scale = get_scale(image); if (!image->initialized) return; cairo_matrix_translate(&image->matrix, -dx/scale, -dy/scale); clamp_view(image); window_schedule_redraw(image->window); } static int motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct image *image = data; struct rectangle allocation; widget_get_allocation(image->widget, &allocation); x -= allocation.x; y -= allocation.y; if (image->button_pressed) move_viewport(image, image->pointer.x - x, image->pointer.y - y); image->pointer.x = x; image->pointer.y = y; return image->button_pressed ? CURSOR_DRAGGING : CURSOR_LEFT_PTR; } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct image *image = data; if (button == BTN_LEFT) { image->button_pressed = state == WL_POINTER_BUTTON_STATE_PRESSED; if (state == WL_POINTER_BUTTON_STATE_PRESSED) input_set_pointer_image(input, CURSOR_DRAGGING); else input_set_pointer_image(input, CURSOR_LEFT_PTR); } } static void zoom(struct image *image, double scale) { double x = image->pointer.x; double y = image->pointer.y; cairo_matrix_t scale_matrix; if (!image->initialized) return; if (get_scale(image) * scale > 20.0 || get_scale(image) * scale < 0.02) return; cairo_matrix_init_identity(&scale_matrix); cairo_matrix_translate(&scale_matrix, x, y); cairo_matrix_scale(&scale_matrix, scale, scale); cairo_matrix_translate(&scale_matrix, -x, -y); cairo_matrix_multiply(&image->matrix, &image->matrix, &scale_matrix); clamp_view(image); } static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data) { struct image *image = data; if (state == WL_KEYBOARD_KEY_STATE_RELEASED) return; switch (sym) { case XKB_KEY_minus: zoom(image, 0.8); window_schedule_redraw(image->window); break; case XKB_KEY_equal: case XKB_KEY_plus: zoom(image, 1.2); window_schedule_redraw(image->window); break; case XKB_KEY_1: image->matrix.xx = 1.0; image->matrix.xy = 0.0; image->matrix.yx = 0.0; image->matrix.yy = 1.0; clamp_view(image); window_schedule_redraw(image->window); break; } } static void axis_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t axis, wl_fixed_t value, void *data) { struct image *image = data; if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL && input_get_modifiers(input) == MOD_CONTROL_MASK) { /* set zoom level to 2% per 10 axis units */ zoom(image, (1.0 - wl_fixed_to_double(value) / 500.0)); window_schedule_redraw(image->window); } else if (input_get_modifiers(input) == 0) { if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) move_viewport(image, 0, wl_fixed_to_double(value)); else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) move_viewport(image, wl_fixed_to_double(value), 0); } } static void fullscreen_handler(struct window *window, void *data) { struct image *image = data; image->fullscreen ^= 1; window_set_fullscreen(window, image->fullscreen); } static void close_handler(void *data) { struct image *image = data; *image->image_counter -= 1; if (*image->image_counter == 0) display_exit(image->display); widget_destroy(image->widget); window_destroy(image->window); free(image); } static struct image * image_create(struct display *display, const char *filename, int *image_counter) { struct image *image; char *b, *copy, title[512]; image = zalloc(sizeof *image); if (image == NULL) return image; copy = strdup(filename); b = basename(copy); snprintf(title, sizeof title, "Wayland Image - %s", b); free(copy); image->filename = strdup(filename); image->image = load_cairo_surface(filename); if (!image->image) { free(image->filename); free(image); return NULL; } image->window = window_create(display); image->widget = window_frame_create(image->window, image); window_set_title(image->window, title); image->display = display; image->image_counter = image_counter; *image_counter += 1; image->initialized = false; window_set_user_data(image->window, image); widget_set_redraw_handler(image->widget, redraw_handler); widget_set_resize_handler(image->widget, resize_handler); window_set_keyboard_focus_handler(image->window, keyboard_focus_handler); window_set_fullscreen_handler(image->window, fullscreen_handler); window_set_close_handler(image->window, close_handler); widget_set_enter_handler(image->widget, enter_handler); widget_set_motion_handler(image->widget, motion_handler); widget_set_button_handler(image->widget, button_handler); widget_set_axis_handler(image->widget, axis_handler); window_set_key_handler(image->window, key_handler); widget_schedule_resize(image->widget, 500, 400); return image; } int main(int argc, char *argv[]) { struct display *d; int i; int image_counter = 0; if (argc <= 1 || argv[1][0]=='-') { printf("Usage: %s image...\n", argv[0]); return 1; } d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } for (i = 1; i < argc; i++) image_create(d, argv[i], &image_counter); if (image_counter > 0) display_run(d); display_destroy(d); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.318963 weston-8.0.0/clients/ivi-shell-user-interface.c0000644000175000017460000010436400000000000021742 0ustar00simonwheel00000000000000/* * Copyright (C) 2013 DENSO CORPORATION * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shared/cairo-util.h" #include #include "shared/helpers.h" #include "shared/os-compatibility.h" #include "shared/xalloc.h" #include #include "shared/file-util.h" #include "ivi-application-client-protocol.h" #include "ivi-hmi-controller-client-protocol.h" /** * A reference implementation how to use ivi-hmi-controller interface to * interact with hmi-controller. This is launched from hmi-controller by using * hmi_client_start and create a pthread. * * The basic flow is as followed, * 1/ read configuration from weston.ini. * 2/ draw png file to surface according to configuration of weston.ini * 3/ set up UI by using ivi-hmi-controller protocol * 4/ Enter event loop * 5/ If a surface receives touch/pointer event, followings are invoked * according to type of event and surface * 5-1/ If a surface to launch ivi_application receive touch up, it execs * ivi-application configured in weston.ini. * 5-2/ If a surface to switch layout mode receive touch up, it sends a request, * ivi_hmi_controller_switch_mode, to hmi-controller. * 5-3/ If a surface to show workspace having launchers, it sends a request, * ivi_hmi_controller_home, to hmi-controller. * 5-4/ If touch down events happens in workspace, * ivi_hmi_controller_workspace_control is sent to slide workspace. * When control finished, event: ivi_hmi_controller_workspace_end_control * is received. */ /***************************************************************************** * structure, globals ****************************************************************************/ enum cursor_type { CURSOR_BOTTOM_LEFT, CURSOR_BOTTOM_RIGHT, CURSOR_BOTTOM, CURSOR_DRAGGING, CURSOR_LEFT_PTR, CURSOR_LEFT, CURSOR_RIGHT, CURSOR_TOP_LEFT, CURSOR_TOP_RIGHT, CURSOR_TOP, CURSOR_IBEAM, CURSOR_HAND1, CURSOR_WATCH, CURSOR_BLANK }; struct wlContextCommon { struct wl_display *wlDisplay; struct wl_registry *wlRegistry; struct wl_compositor *wlCompositor; struct wl_shm *wlShm; uint32_t formats; struct wl_seat *wlSeat; struct wl_pointer *wlPointer; struct wl_touch *wlTouch; struct ivi_application *iviApplication; struct ivi_hmi_controller *hmiCtrl; struct hmi_homescreen_setting *hmi_setting; struct wl_list list_wlContextStruct; struct wl_surface *enterSurface; int32_t is_home_on; struct wl_cursor_theme *cursor_theme; struct wl_cursor **cursors; struct wl_surface *pointer_surface; enum cursor_type current_cursor; uint32_t enter_serial; }; struct wlContextStruct { struct wlContextCommon *cmm; struct wl_surface *wlSurface; struct wl_buffer *wlBuffer; cairo_surface_t *ctx_image; void *data; uint32_t id_surface; struct wl_list link; }; struct hmi_homescreen_srf { uint32_t id; char *filePath; uint32_t color; }; struct hmi_homescreen_workspace { struct wl_array launcher_id_array; struct wl_list link; }; struct hmi_homescreen_launcher { uint32_t icon_surface_id; uint32_t workspace_id; char *icon; char *path; struct wl_list link; }; struct hmi_homescreen_setting { struct hmi_homescreen_srf background; struct hmi_homescreen_srf panel; struct hmi_homescreen_srf tiling; struct hmi_homescreen_srf sidebyside; struct hmi_homescreen_srf fullscreen; struct hmi_homescreen_srf random; struct hmi_homescreen_srf home; struct hmi_homescreen_srf workspace_background; struct wl_list workspace_list; struct wl_list launcher_list; char *cursor_theme; int32_t cursor_size; uint32_t transition_duration; uint32_t surface_id_offset; int32_t screen_num; }; /***************************************************************************** * Event Handler ****************************************************************************/ static void shm_format(void *data, struct wl_shm *pWlShm, uint32_t format) { struct wlContextCommon *pCtx = data; pCtx->formats |= (1 << format); } static struct wl_shm_listener shm_listenter = { shm_format }; static int32_t getIdOfWlSurface(struct wlContextCommon *pCtx, struct wl_surface *wlSurface) { struct wlContextStruct *pWlCtxSt = NULL; if (NULL == pCtx || NULL == wlSurface ) return 0; wl_list_for_each(pWlCtxSt, &pCtx->list_wlContextStruct, link) { if (pWlCtxSt->wlSurface == wlSurface) return pWlCtxSt->id_surface; } return -1; } static void set_pointer_image(struct wlContextCommon *pCtx, uint32_t index) { struct wl_cursor *cursor = NULL; struct wl_cursor_image *image = NULL; struct wl_buffer *buffer = NULL; if (!pCtx->wlPointer || !pCtx->cursors) return; if (CURSOR_BLANK == pCtx->current_cursor) { wl_pointer_set_cursor(pCtx->wlPointer, pCtx->enter_serial, NULL, 0, 0); return; } cursor = pCtx->cursors[pCtx->current_cursor]; if (!cursor) return; if (cursor->image_count <= index) { fprintf(stderr, "cursor index out of range\n"); return; } image = cursor->images[index]; buffer = wl_cursor_image_get_buffer(image); if (!buffer) return; wl_pointer_set_cursor(pCtx->wlPointer, pCtx->enter_serial, pCtx->pointer_surface, image->hotspot_x, image->hotspot_y); wl_surface_attach(pCtx->pointer_surface, buffer, 0, 0); wl_surface_damage(pCtx->pointer_surface, 0, 0, image->width, image->height); wl_surface_commit(pCtx->pointer_surface); } static void PointerHandleEnter(void *data, struct wl_pointer *wlPointer, uint32_t serial, struct wl_surface *wlSurface, wl_fixed_t sx, wl_fixed_t sy) { struct wlContextCommon *pCtx = data; pCtx->enter_serial = serial; pCtx->enterSurface = wlSurface; set_pointer_image(pCtx, 0); #ifdef _DEBUG printf("ENTER PointerHandleEnter: x(%d), y(%d)\n", sx, sy); #endif } static void PointerHandleLeave(void *data, struct wl_pointer *wlPointer, uint32_t serial, struct wl_surface *wlSurface) { struct wlContextCommon *pCtx = data; pCtx->enterSurface = NULL; #ifdef _DEBUG printf("ENTER PointerHandleLeave: serial(%d)\n", serial); #endif } static void PointerHandleMotion(void *data, struct wl_pointer *wlPointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { #ifdef _DEBUG printf("ENTER PointerHandleMotion: x(%d), y(%d)\n", sx, sy); #endif } /** * if a surface assigned as launcher receives touch-off event, invoking * ivi-application which configured in weston.ini with path to binary. */ extern char **environ; /*defied by libc */ static pid_t execute_process(char *path, char *argv[]) { pid_t pid = fork(); if (pid < 0) fprintf(stderr, "Failed to fork\n"); if (pid) return pid; if (-1 == execve(path, argv, environ)) { fprintf(stderr, "Failed to execve %s\n", path); exit(1); } return pid; } static int32_t launcher_button(uint32_t surfaceId, struct wl_list *launcher_list) { struct hmi_homescreen_launcher *launcher = NULL; wl_list_for_each(launcher, launcher_list, link) { char *argv[] = { NULL }; if (surfaceId != launcher->icon_surface_id) continue; execute_process(launcher->path, argv); return 1; } return 0; } /** * is-method to identify a surface set as launcher in workspace or workspace * itself. This is-method is used to decide whether request; * ivi_hmi_controller_workspace_control is sent or not. */ static int32_t isWorkspaceSurface(uint32_t id, struct hmi_homescreen_setting *hmi_setting) { struct hmi_homescreen_launcher *launcher = NULL; if (id == hmi_setting->workspace_background.id) return 1; wl_list_for_each(launcher, &hmi_setting->launcher_list, link) { if (id == launcher->icon_surface_id) return 1; } return 0; } /** * Decide which request is sent to hmi-controller */ static void touch_up(struct ivi_hmi_controller *hmi_ctrl, uint32_t id_surface, int32_t *is_home_on, struct hmi_homescreen_setting *hmi_setting) { if (launcher_button(id_surface, &hmi_setting->launcher_list)) { *is_home_on = 0; ivi_hmi_controller_home(hmi_ctrl, IVI_HMI_CONTROLLER_HOME_OFF); } else if (id_surface == hmi_setting->tiling.id) { ivi_hmi_controller_switch_mode(hmi_ctrl, IVI_HMI_CONTROLLER_LAYOUT_MODE_TILING); } else if (id_surface == hmi_setting->sidebyside.id) { ivi_hmi_controller_switch_mode(hmi_ctrl, IVI_HMI_CONTROLLER_LAYOUT_MODE_SIDE_BY_SIDE); } else if (id_surface == hmi_setting->fullscreen.id) { ivi_hmi_controller_switch_mode(hmi_ctrl, IVI_HMI_CONTROLLER_LAYOUT_MODE_FULL_SCREEN); } else if (id_surface == hmi_setting->random.id) { ivi_hmi_controller_switch_mode(hmi_ctrl, IVI_HMI_CONTROLLER_LAYOUT_MODE_RANDOM); } else if (id_surface == hmi_setting->home.id) { *is_home_on = !(*is_home_on); if (*is_home_on) { ivi_hmi_controller_home(hmi_ctrl, IVI_HMI_CONTROLLER_HOME_ON); } else { ivi_hmi_controller_home(hmi_ctrl, IVI_HMI_CONTROLLER_HOME_OFF); } } } /** * Even handler of Pointer event. IVI system is usually manipulated by touch * screen. However, some systems also have pointer device. * Release is the same behavior as touch off * Pressed is the same behavior as touch on */ static void PointerHandleButton(void *data, struct wl_pointer *wlPointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { struct wlContextCommon *pCtx = data; struct ivi_hmi_controller *hmi_ctrl = pCtx->hmiCtrl; const uint32_t id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); if (BTN_RIGHT == button) return; switch (state) { case WL_POINTER_BUTTON_STATE_RELEASED: touch_up(hmi_ctrl, id_surface, &pCtx->is_home_on, pCtx->hmi_setting); break; case WL_POINTER_BUTTON_STATE_PRESSED: if (isWorkspaceSurface(id_surface, pCtx->hmi_setting)) { ivi_hmi_controller_workspace_control(hmi_ctrl, pCtx->wlSeat, serial); } break; } #ifdef _DEBUG printf("ENTER PointerHandleButton: button(%d), state(%d)\n", button, state); #endif } static void PointerHandleAxis(void *data, struct wl_pointer *wlPointer, uint32_t time, uint32_t axis, wl_fixed_t value) { #ifdef _DEBUG printf("ENTER PointerHandleAxis: axis(%d), value(%d)\n", axis, value); #endif } static struct wl_pointer_listener pointer_listener = { PointerHandleEnter, PointerHandleLeave, PointerHandleMotion, PointerHandleButton, PointerHandleAxis }; /** * Even handler of touch event */ static void TouchHandleDown(void *data, struct wl_touch *wlTouch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { struct wlContextCommon *pCtx = data; struct ivi_hmi_controller *hmi_ctrl = pCtx->hmiCtrl; uint32_t id_surface = 0; if (0 == id) pCtx->enterSurface = surface; id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); /** * When touch down happens on surfaces of workspace, ask * hmi-controller to start control workspace to select page of * workspace. After sending seat to hmi-controller by * ivi_hmi_controller_workspace_control, * hmi-controller-homescreen doesn't receive any event till * hmi-controller sends back it. */ if (isWorkspaceSurface(id_surface, pCtx->hmi_setting)) { ivi_hmi_controller_workspace_control(hmi_ctrl, pCtx->wlSeat, serial); } } static void TouchHandleUp(void *data, struct wl_touch *wlTouch, uint32_t serial, uint32_t time, int32_t id) { struct wlContextCommon *pCtx = data; struct ivi_hmi_controller *hmi_ctrl = pCtx->hmiCtrl; const uint32_t id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); /** * triggering event according to touch-up happening on which surface. */ if (id == 0){ touch_up(hmi_ctrl, id_surface, &pCtx->is_home_on, pCtx->hmi_setting); } } static void TouchHandleMotion(void *data, struct wl_touch *wlTouch, uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { } static void TouchHandleFrame(void *data, struct wl_touch *wlTouch) { } static void TouchHandleCancel(void *data, struct wl_touch *wlTouch) { } static struct wl_touch_listener touch_listener = { TouchHandleDown, TouchHandleUp, TouchHandleMotion, TouchHandleFrame, TouchHandleCancel, }; /** * Handler of capabilities */ static void seat_handle_capabilities(void *data, struct wl_seat *seat, uint32_t caps) { struct wlContextCommon *p_wlCtx = (struct wlContextCommon*)data; struct wl_seat *wlSeat = p_wlCtx->wlSeat; struct wl_pointer *wlPointer = p_wlCtx->wlPointer; struct wl_touch *wlTouch = p_wlCtx->wlTouch; if (p_wlCtx->hmi_setting->cursor_theme) { if ((caps & WL_SEAT_CAPABILITY_POINTER) && !wlPointer){ wlPointer = wl_seat_get_pointer(wlSeat); wl_pointer_add_listener(wlPointer, &pointer_listener, data); } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && wlPointer){ wl_pointer_destroy(wlPointer); wlPointer = NULL; } p_wlCtx->wlPointer = wlPointer; } if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !wlTouch){ wlTouch = wl_seat_get_touch(wlSeat); wl_touch_add_listener(wlTouch, &touch_listener, data); } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && wlTouch){ wl_touch_destroy(wlTouch); wlTouch = NULL; } p_wlCtx->wlTouch = wlTouch; } static struct wl_seat_listener seat_Listener = { seat_handle_capabilities, }; /** * Registration of event * This event is received when hmi-controller server finished controlling * workspace. */ static void ivi_hmi_controller_workspace_end_control(void *data, struct ivi_hmi_controller *hmi_ctrl, int32_t is_controlled) { struct wlContextCommon *pCtx = data; const uint32_t id_surface = getIdOfWlSurface(pCtx, pCtx->enterSurface); if (is_controlled) return; /** * During being controlled by hmi-controller, any input event is not * notified. So when control ends with touch up, it invokes launcher * if up event happens on a launcher surface. * */ if (launcher_button(id_surface, &pCtx->hmi_setting->launcher_list)) { pCtx->is_home_on = 0; ivi_hmi_controller_home(hmi_ctrl, IVI_HMI_CONTROLLER_HOME_OFF); } } static const struct ivi_hmi_controller_listener hmi_controller_listener = { ivi_hmi_controller_workspace_end_control }; /** * Registration of interfaces */ static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct wlContextCommon *p_wlCtx = (struct wlContextCommon*)data; if (!strcmp(interface, "wl_compositor")) { p_wlCtx->wlCompositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); } else if (!strcmp(interface, "wl_shm")) { p_wlCtx->wlShm = wl_registry_bind(registry, name, &wl_shm_interface, 1); wl_shm_add_listener(p_wlCtx->wlShm, &shm_listenter, p_wlCtx); } else if (!strcmp(interface, "wl_seat")) { /* XXX: should be handling multiple wl_seats */ if (p_wlCtx->wlSeat) return; p_wlCtx->wlSeat = wl_registry_bind(registry, name, &wl_seat_interface, 1); wl_seat_add_listener(p_wlCtx->wlSeat, &seat_Listener, data); } else if (!strcmp(interface, "ivi_application")) { p_wlCtx->iviApplication = wl_registry_bind(registry, name, &ivi_application_interface, 1); } else if (!strcmp(interface, "ivi_hmi_controller")) { p_wlCtx->hmiCtrl = wl_registry_bind(registry, name, &ivi_hmi_controller_interface, 1); ivi_hmi_controller_add_listener(p_wlCtx->hmiCtrl, &hmi_controller_listener, p_wlCtx); } else if (!strcmp(interface, "wl_output")) { p_wlCtx->hmi_setting->screen_num++; } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static void frame_listener_func(void *data, struct wl_callback *callback, uint32_t time) { if (callback) wl_callback_destroy(callback); } static const struct wl_callback_listener frame_listener = { frame_listener_func }; /* * The following correspondences between file names and cursors was copied * from: https://bugs.kde.org/attachment.cgi?id=67313 */ static const char *bottom_left_corners[] = { "bottom_left_corner", "sw-resize", "size_bdiag" }; static const char *bottom_right_corners[] = { "bottom_right_corner", "se-resize", "size_fdiag" }; static const char *bottom_sides[] = { "bottom_side", "s-resize", "size_ver" }; static const char *grabbings[] = { "grabbing", "closedhand", "208530c400c041818281048008011002" }; static const char *left_ptrs[] = { "left_ptr", "default", "top_left_arrow", "left-arrow" }; static const char *left_sides[] = { "left_side", "w-resize", "size_hor" }; static const char *right_sides[] = { "right_side", "e-resize", "size_hor" }; static const char *top_left_corners[] = { "top_left_corner", "nw-resize", "size_fdiag" }; static const char *top_right_corners[] = { "top_right_corner", "ne-resize", "size_bdiag" }; static const char *top_sides[] = { "top_side", "n-resize", "size_ver" }; static const char *xterms[] = { "xterm", "ibeam", "text" }; static const char *hand1s[] = { "hand1", "pointer", "pointing_hand", "e29285e634086352946a0e7090d73106" }; static const char *watches[] = { "watch", "wait", "0426c94ea35c87780ff01dc239897213" }; struct cursor_alternatives { const char **names; size_t count; }; static const struct cursor_alternatives cursors[] = { { bottom_left_corners, ARRAY_LENGTH(bottom_left_corners) }, { bottom_right_corners, ARRAY_LENGTH(bottom_right_corners) }, { bottom_sides, ARRAY_LENGTH(bottom_sides) }, { grabbings, ARRAY_LENGTH(grabbings) }, { left_ptrs, ARRAY_LENGTH(left_ptrs) }, { left_sides, ARRAY_LENGTH(left_sides) }, { right_sides, ARRAY_LENGTH(right_sides) }, { top_left_corners, ARRAY_LENGTH(top_left_corners) }, { top_right_corners, ARRAY_LENGTH(top_right_corners) }, { top_sides, ARRAY_LENGTH(top_sides) }, { xterms, ARRAY_LENGTH(xterms) }, { hand1s, ARRAY_LENGTH(hand1s) }, { watches, ARRAY_LENGTH(watches) }, }; static void create_cursors(struct wlContextCommon *cmm) { uint32_t i = 0; uint32_t j = 0; struct wl_cursor *cursor = NULL; char *cursor_theme = cmm->hmi_setting->cursor_theme; int32_t cursor_size = cmm->hmi_setting->cursor_size; cmm->cursor_theme = wl_cursor_theme_load(cursor_theme, cursor_size, cmm->wlShm); cmm->cursors = xzalloc(ARRAY_LENGTH(cursors) * sizeof(cmm->cursors[0])); for (i = 0; i < ARRAY_LENGTH(cursors); i++) { cursor = NULL; for (j = 0; !cursor && j < cursors[i].count; ++j) { cursor = wl_cursor_theme_get_cursor( cmm->cursor_theme, cursors[i].names[j]); } if (!cursor) { fprintf(stderr, "could not load cursor '%s'\n", cursors[i].names[0]); } cmm->cursors[i] = cursor; } } static void destroy_cursors(struct wlContextCommon *cmm) { if (cmm->cursor_theme) wl_cursor_theme_destroy(cmm->cursor_theme); free(cmm->cursors); } /** * Internal method to prepare parts of UI */ static void createShmBuffer(struct wlContextStruct *p_wlCtx) { struct wl_shm_pool *pool; int fd = -1; int size = 0; int width = 0; int height = 0; int stride = 0; width = cairo_image_surface_get_width(p_wlCtx->ctx_image); height = cairo_image_surface_get_height(p_wlCtx->ctx_image); stride = cairo_image_surface_get_stride(p_wlCtx->ctx_image); size = stride * height; fd = os_create_anonymous_file(size); if (fd < 0) { fprintf(stderr, "creating a buffer file for %d B failed: %s\n", size, strerror(errno)); return ; } p_wlCtx->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (MAP_FAILED == p_wlCtx->data) { fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return; } pool = wl_shm_create_pool(p_wlCtx->cmm->wlShm, fd, size); p_wlCtx->wlBuffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); if (NULL == p_wlCtx->wlBuffer) { fprintf(stderr, "wl_shm_create_buffer failed: %s\n", strerror(errno)); close(fd); return; } wl_shm_pool_destroy(pool); close(fd); } static void destroyWLContextCommon(struct wlContextCommon *p_wlCtx) { destroy_cursors(p_wlCtx); if (p_wlCtx->pointer_surface) wl_surface_destroy(p_wlCtx->pointer_surface); if (p_wlCtx->wlCompositor) wl_compositor_destroy(p_wlCtx->wlCompositor); } static void destroyWLContextStruct(struct wlContextStruct *p_wlCtx) { if (p_wlCtx->wlSurface) wl_surface_destroy(p_wlCtx->wlSurface); if (p_wlCtx->ctx_image) { cairo_surface_destroy(p_wlCtx->ctx_image); p_wlCtx->ctx_image = NULL; } } static int createSurface(struct wlContextStruct *p_wlCtx) { p_wlCtx->wlSurface = wl_compositor_create_surface(p_wlCtx->cmm->wlCompositor); if (NULL == p_wlCtx->wlSurface) { printf("Error: wl_compositor_create_surface failed.\n"); destroyWLContextCommon(p_wlCtx->cmm); abort(); } return 0; } static void drawImage(struct wlContextStruct *p_wlCtx) { struct wl_callback *callback; int width = 0; int height = 0; int stride = 0; void *data = NULL; width = cairo_image_surface_get_width(p_wlCtx->ctx_image); height = cairo_image_surface_get_height(p_wlCtx->ctx_image); stride = cairo_image_surface_get_stride(p_wlCtx->ctx_image); data = cairo_image_surface_get_data(p_wlCtx->ctx_image); memcpy(p_wlCtx->data, data, stride * height); wl_surface_attach(p_wlCtx->wlSurface, p_wlCtx->wlBuffer, 0, 0); wl_surface_damage(p_wlCtx->wlSurface, 0, 0, width, height); callback = wl_surface_frame(p_wlCtx->wlSurface); wl_callback_add_listener(callback, &frame_listener, NULL); wl_surface_commit(p_wlCtx->wlSurface); } static void create_ivisurface(struct wlContextStruct *p_wlCtx, uint32_t id_surface, cairo_surface_t *surface) { struct ivi_surface *ivisurf = NULL; p_wlCtx->ctx_image = surface; p_wlCtx->id_surface = id_surface; wl_list_init(&p_wlCtx->link); wl_list_insert(&p_wlCtx->cmm->list_wlContextStruct, &p_wlCtx->link); createSurface(p_wlCtx); createShmBuffer(p_wlCtx); ivisurf = ivi_application_surface_create(p_wlCtx->cmm->iviApplication, id_surface, p_wlCtx->wlSurface); if (ivisurf == NULL) { fprintf(stderr, "Failed to create ivi_client_surface\n"); return; } drawImage(p_wlCtx); } static void create_ivisurfaceFromFile(struct wlContextStruct *p_wlCtx, uint32_t id_surface, const char *imageFile) { cairo_surface_t *surface = load_cairo_surface(imageFile); if (NULL == surface) { fprintf(stderr, "Failed to load_cairo_surface %s\n", imageFile); return; } create_ivisurface(p_wlCtx, id_surface, surface); } static void set_hex_color(cairo_t *cr, uint32_t color) { cairo_set_source_rgba(cr, ((color >> 16) & 0xff) / 255.0, ((color >> 8) & 0xff) / 255.0, ((color >> 0) & 0xff) / 255.0, ((color >> 24) & 0xff) / 255.0); } static void create_ivisurfaceFromColor(struct wlContextStruct *p_wlCtx, uint32_t id_surface, uint32_t width, uint32_t height, uint32_t color) { cairo_surface_t *surface = NULL; cairo_t *cr = NULL; surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle(cr, 0, 0, width, height); set_hex_color(cr, color); cairo_fill(cr); cairo_destroy(cr); create_ivisurface(p_wlCtx, id_surface, surface); } static void UI_ready(struct ivi_hmi_controller *controller) { ivi_hmi_controller_UI_ready(controller); } /** * Internal method to set up UI by using ivi-hmi-controller */ static void create_background(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, const char *imageFile) { create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); } static void create_panel(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, const char *imageFile) { create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); } static void create_button(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, const char *imageFile, uint32_t number) { create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); } static void create_home_button(struct wlContextStruct *p_wlCtx, const uint32_t id_surface, const char *imageFile) { create_ivisurfaceFromFile(p_wlCtx, id_surface, imageFile); } static void create_workspace_background(struct wlContextStruct *p_wlCtx, struct hmi_homescreen_srf *srf) { create_ivisurfaceFromColor(p_wlCtx, srf->id, 1, 1, srf->color); } static void create_launchers(struct wlContextCommon *cmm, struct wl_list *launcher_list) { struct hmi_homescreen_launcher **launchers; struct hmi_homescreen_launcher *launcher = NULL; int launcher_count = wl_list_length(launcher_list); int ii = 0; int start = 0; if (0 == launcher_count) return; launchers = xzalloc(launcher_count * sizeof(*launchers)); wl_list_for_each(launcher, launcher_list, link) { launchers[ii] = launcher; ii++; } for (ii = 0; ii < launcher_count; ii++) { int jj = 0; if (ii != launcher_count - 1 && launchers[ii]->workspace_id == launchers[ii + 1]->workspace_id) continue; for (jj = start; jj <= ii; jj++) { struct wlContextStruct *p_wlCtx; p_wlCtx = xzalloc(sizeof(*p_wlCtx)); p_wlCtx->cmm = cmm; create_ivisurfaceFromFile(p_wlCtx, launchers[jj]->icon_surface_id, launchers[jj]->icon); } start = ii + 1; } free(launchers); } /** * Internal method to read out weston.ini to get configuration */ static struct hmi_homescreen_setting * hmi_homescreen_setting_create(void) { const char *config_file; struct weston_config *config = NULL; struct weston_config_section *shellSection = NULL; struct hmi_homescreen_setting *setting = xzalloc(sizeof(*setting)); struct weston_config_section *section = NULL; const char *name = NULL; uint32_t workspace_layer_id; uint32_t icon_surface_id = 0; char *filename; wl_list_init(&setting->workspace_list); wl_list_init(&setting->launcher_list); config_file = weston_config_get_name_from_env(); config = weston_config_parse(config_file); shellSection = weston_config_get_section(config, "ivi-shell", NULL, NULL); weston_config_section_get_string( shellSection, "cursor-theme", &setting->cursor_theme, NULL); weston_config_section_get_int( shellSection, "cursor-size", &setting->cursor_size, 32); weston_config_section_get_uint( shellSection, "workspace-layer-id", &workspace_layer_id, 3000); filename = file_name_with_datadir("background.png"); weston_config_section_get_string( shellSection, "background-image", &setting->background.filePath, filename); free(filename); weston_config_section_get_uint( shellSection, "background-id", &setting->background.id, 1001); filename = file_name_with_datadir("panel.png"); weston_config_section_get_string( shellSection, "panel-image", &setting->panel.filePath, filename); free(filename); weston_config_section_get_uint( shellSection, "panel-id", &setting->panel.id, 1002); filename = file_name_with_datadir("tiling.png"); weston_config_section_get_string( shellSection, "tiling-image", &setting->tiling.filePath, filename); free(filename); weston_config_section_get_uint( shellSection, "tiling-id", &setting->tiling.id, 1003); filename = file_name_with_datadir("sidebyside.png"); weston_config_section_get_string( shellSection, "sidebyside-image", &setting->sidebyside.filePath, filename); free(filename); weston_config_section_get_uint( shellSection, "sidebyside-id", &setting->sidebyside.id, 1004); filename = file_name_with_datadir("fullscreen.png"); weston_config_section_get_string( shellSection, "fullscreen-image", &setting->fullscreen.filePath, filename); free(filename); weston_config_section_get_uint( shellSection, "fullscreen-id", &setting->fullscreen.id, 1005); filename = file_name_with_datadir("random.png"); weston_config_section_get_string( shellSection, "random-image", &setting->random.filePath, filename); free(filename); weston_config_section_get_uint( shellSection, "random-id", &setting->random.id, 1006); filename = file_name_with_datadir("home.png"); weston_config_section_get_string( shellSection, "home-image", &setting->home.filePath, filename); free(filename); weston_config_section_get_uint( shellSection, "home-id", &setting->home.id, 1007); weston_config_section_get_color( shellSection, "workspace-background-color", &setting->workspace_background.color, 0x99000000); weston_config_section_get_uint( shellSection, "workspace-background-id", &setting->workspace_background.id, 2001); weston_config_section_get_uint( shellSection, "surface-id-offset", &setting->surface_id_offset, 10); icon_surface_id = workspace_layer_id + 1; while (weston_config_next_section(config, §ion, &name)) { struct hmi_homescreen_launcher *launcher; if (strcmp(name, "ivi-launcher") != 0) continue; launcher = xzalloc(sizeof(*launcher)); wl_list_init(&launcher->link); weston_config_section_get_string(section, "icon", &launcher->icon, NULL); weston_config_section_get_string(section, "path", &launcher->path, NULL); weston_config_section_get_uint(section, "workspace-id", &launcher->workspace_id, 0); weston_config_section_get_uint(section, "icon-id", &launcher->icon_surface_id, icon_surface_id); icon_surface_id++; wl_list_insert(setting->launcher_list.prev, &launcher->link); } weston_config_destroy(config); return setting; } /** * Main thread * * The basic flow are as followed, * 1/ read configuration from weston.ini by hmi_homescreen_setting_create * 2/ draw png file to surface according to configuration of weston.ini and * set up UI by using ivi-hmi-controller protocol by each create_* method */ int main(int argc, char **argv) { struct wlContextCommon wlCtxCommon; struct wlContextStruct *wlCtx_BackGround; struct wlContextStruct *wlCtx_Panel; struct wlContextStruct wlCtx_Button_1; struct wlContextStruct wlCtx_Button_2; struct wlContextStruct wlCtx_Button_3; struct wlContextStruct wlCtx_Button_4; struct wlContextStruct wlCtx_HomeButton; struct wlContextStruct wlCtx_WorkSpaceBackGround; struct wl_list launcher_wlCtxList; int ret = 0; struct hmi_homescreen_setting *hmi_setting; struct wlContextStruct *pWlCtxSt = NULL; int i = 0; hmi_setting = hmi_homescreen_setting_create(); memset(&wlCtxCommon, 0x00, sizeof(wlCtxCommon)); memset(&wlCtx_Button_1, 0x00, sizeof(wlCtx_Button_1)); memset(&wlCtx_Button_2, 0x00, sizeof(wlCtx_Button_2)); memset(&wlCtx_Button_3, 0x00, sizeof(wlCtx_Button_3)); memset(&wlCtx_Button_4, 0x00, sizeof(wlCtx_Button_4)); memset(&wlCtx_HomeButton, 0x00, sizeof(wlCtx_HomeButton)); memset(&wlCtx_WorkSpaceBackGround, 0x00, sizeof(wlCtx_WorkSpaceBackGround)); wl_list_init(&launcher_wlCtxList); wl_list_init(&wlCtxCommon.list_wlContextStruct); wlCtxCommon.hmi_setting = hmi_setting; wlCtxCommon.wlDisplay = wl_display_connect(NULL); if (NULL == wlCtxCommon.wlDisplay) { printf("Error: wl_display_connect failed.\n"); return -1; } /* get wl_registry */ wlCtxCommon.formats = 0; wlCtxCommon.wlRegistry = wl_display_get_registry(wlCtxCommon.wlDisplay); wl_registry_add_listener(wlCtxCommon.wlRegistry, ®istry_listener, &wlCtxCommon); wl_display_roundtrip(wlCtxCommon.wlDisplay); if (wlCtxCommon.wlShm == NULL) { fprintf(stderr, "No wl_shm global\n"); exit(1); } wl_display_roundtrip(wlCtxCommon.wlDisplay); if (!(wlCtxCommon.formats & (1 << WL_SHM_FORMAT_XRGB8888))) { fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); exit(1); } wlCtx_BackGround = xzalloc(hmi_setting->screen_num * sizeof(struct wlContextStruct)); wlCtx_Panel= xzalloc(hmi_setting->screen_num * sizeof(struct wlContextStruct)); if (wlCtxCommon.hmi_setting->cursor_theme) { create_cursors(&wlCtxCommon); wlCtxCommon.pointer_surface = wl_compositor_create_surface(wlCtxCommon.wlCompositor); wlCtxCommon.current_cursor = CURSOR_LEFT_PTR; } wlCtx_Button_1.cmm = &wlCtxCommon; wlCtx_Button_2.cmm = &wlCtxCommon; wlCtx_Button_3.cmm = &wlCtxCommon; wlCtx_Button_4.cmm = &wlCtxCommon; wlCtx_HomeButton.cmm = &wlCtxCommon; wlCtx_WorkSpaceBackGround.cmm = &wlCtxCommon; /* create desktop widgets */ for (i = 0; i < hmi_setting->screen_num; i++) { wlCtx_BackGround[i].cmm = &wlCtxCommon; create_background(&wlCtx_BackGround[i], hmi_setting->background.id + (i * hmi_setting->surface_id_offset), hmi_setting->background.filePath); wlCtx_Panel[i].cmm = &wlCtxCommon; create_panel(&wlCtx_Panel[i], hmi_setting->panel.id + (i * hmi_setting->surface_id_offset), hmi_setting->panel.filePath); } create_button(&wlCtx_Button_1, hmi_setting->tiling.id, hmi_setting->tiling.filePath, 0); create_button(&wlCtx_Button_2, hmi_setting->sidebyside.id, hmi_setting->sidebyside.filePath, 1); create_button(&wlCtx_Button_3, hmi_setting->fullscreen.id, hmi_setting->fullscreen.filePath, 2); create_button(&wlCtx_Button_4, hmi_setting->random.id, hmi_setting->random.filePath, 3); create_workspace_background(&wlCtx_WorkSpaceBackGround, &hmi_setting->workspace_background); create_launchers(&wlCtxCommon, &hmi_setting->launcher_list); create_home_button(&wlCtx_HomeButton, hmi_setting->home.id, hmi_setting->home.filePath); UI_ready(wlCtxCommon.hmiCtrl); while (ret != -1) ret = wl_display_dispatch(wlCtxCommon.wlDisplay); wl_list_for_each(pWlCtxSt, &wlCtxCommon.list_wlContextStruct, link) { destroyWLContextStruct(pWlCtxSt); } free(wlCtx_BackGround); free(wlCtx_Panel); destroyWLContextCommon(&wlCtxCommon); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.318963 weston-8.0.0/clients/keyboard.c0000644000175000017460000006720500000000000016736 0ustar00simonwheel00000000000000/* * Copyright © 2012 Openismus GmbH * Copyright © 2012 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include "window.h" #include "input-method-unstable-v1-client-protocol.h" #include "text-input-unstable-v1-client-protocol.h" #include "shared/xalloc.h" struct keyboard; struct virtual_keyboard { struct zwp_input_panel_v1 *input_panel; struct zwp_input_method_v1 *input_method; struct zwp_input_method_context_v1 *context; struct display *display; struct output *output; char *preedit_string; uint32_t preedit_style; struct { xkb_mod_mask_t shift_mask; } keysym; uint32_t serial; uint32_t content_hint; uint32_t content_purpose; char *preferred_language; char *surrounding_text; uint32_t surrounding_cursor; struct keyboard *keyboard; bool toplevel; }; enum key_type { keytype_default, keytype_backspace, keytype_enter, keytype_space, keytype_switch, keytype_symbols, keytype_tab, keytype_arrow_up, keytype_arrow_left, keytype_arrow_right, keytype_arrow_down, keytype_style }; struct key { enum key_type key_type; char *label; char *uppercase; char *symbol; unsigned int width; }; struct layout { const struct key *keys; uint32_t count; uint32_t columns; uint32_t rows; const char *language; uint32_t text_direction; }; static const struct key normal_keys[] = { { keytype_default, "q", "Q", "1", 1}, { keytype_default, "w", "W", "2", 1}, { keytype_default, "e", "E", "3", 1}, { keytype_default, "r", "R", "4", 1}, { keytype_default, "t", "T", "5", 1}, { keytype_default, "y", "Y", "6", 1}, { keytype_default, "u", "U", "7", 1}, { keytype_default, "i", "I", "8", 1}, { keytype_default, "o", "O", "9", 1}, { keytype_default, "p", "P", "0", 1}, { keytype_backspace, "<--", "<--", "<--", 2}, { keytype_tab, "->|", "->|", "->|", 1}, { keytype_default, "a", "A", "-", 1}, { keytype_default, "s", "S", "@", 1}, { keytype_default, "d", "D", "*", 1}, { keytype_default, "f", "F", "^", 1}, { keytype_default, "g", "G", ":", 1}, { keytype_default, "h", "H", ";", 1}, { keytype_default, "j", "J", "(", 1}, { keytype_default, "k", "K", ")", 1}, { keytype_default, "l", "L", "~", 1}, { keytype_enter, "Enter", "Enter", "Enter", 2}, { keytype_switch, "ABC", "abc", "ABC", 2}, { keytype_default, "z", "Z", "/", 1}, { keytype_default, "x", "X", "\'", 1}, { keytype_default, "c", "C", "\"", 1}, { keytype_default, "v", "V", "+", 1}, { keytype_default, "b", "B", "=", 1}, { keytype_default, "n", "N", "?", 1}, { keytype_default, "m", "M", "!", 1}, { keytype_default, ",", ",", "\\", 1}, { keytype_default, ".", ".", "|", 1}, { keytype_switch, "ABC", "abc", "ABC", 1}, { keytype_symbols, "?123", "?123", "abc", 1}, { keytype_space, "", "", "", 5}, { keytype_arrow_up, "/\\", "/\\", "/\\", 1}, { keytype_arrow_left, "<", "<", "<", 1}, { keytype_arrow_right, ">", ">", ">", 1}, { keytype_arrow_down, "\\/", "\\/", "\\/", 1}, { keytype_style, "", "", "", 2} }; static const struct key numeric_keys[] = { { keytype_default, "1", "1", "1", 1}, { keytype_default, "2", "2", "2", 1}, { keytype_default, "3", "3", "3", 1}, { keytype_default, "4", "4", "4", 1}, { keytype_default, "5", "5", "5", 1}, { keytype_default, "6", "6", "6", 1}, { keytype_default, "7", "7", "7", 1}, { keytype_default, "8", "8", "8", 1}, { keytype_default, "9", "9", "9", 1}, { keytype_default, "0", "0", "0", 1}, { keytype_backspace, "<--", "<--", "<--", 2}, { keytype_space, "", "", "", 4}, { keytype_enter, "Enter", "Enter", "Enter", 2}, { keytype_arrow_up, "/\\", "/\\", "/\\", 1}, { keytype_arrow_left, "<", "<", "<", 1}, { keytype_arrow_right, ">", ">", ">", 1}, { keytype_arrow_down, "\\/", "\\/", "\\/", 1}, { keytype_style, "", "", "", 2} }; static const struct key arabic_keys[] = { { keytype_default, "ض", "ﹶ", "۱", 1}, { keytype_default, "ص", "ﹰ", "۲", 1}, { keytype_default, "ث", "ﹸ", "۳", 1}, { keytype_default, "ق", "ﹲ", "۴", 1}, { keytype_default, "ف", "ﻹ", "۵", 1}, { keytype_default, "غ", "ﺇ", "۶", 1}, { keytype_default, "ع", "`", "۷", 1}, { keytype_default, "ه", "٪", "۸", 1}, { keytype_default, "خ", ">", "۹", 1}, { keytype_default, "ح", "<", "۰", 1}, { keytype_backspace, "-->", "-->", "-->", 2}, { keytype_tab, "->|", "->|", "->|", 1}, { keytype_default, "ش", "ﹺ", "ﹼ", 1}, { keytype_default, "س", "ﹴ", "!", 1}, { keytype_default, "ي", "[", "@", 1}, { keytype_default, "ب", "]", "#", 1}, { keytype_default, "ل", "ﻷ", "$", 1}, { keytype_default, "ا", "أ", "%", 1}, { keytype_default, "ت", "-", "^", 1}, { keytype_default, "ن", "x", "&", 1}, { keytype_default, "م", "/", "*", 1}, { keytype_default, "ك", ":", "_", 1}, { keytype_default, "د", "\"", "+", 1}, { keytype_enter, "Enter", "Enter", "Enter", 2}, { keytype_switch, "Shift", "Base", "Shift", 2}, { keytype_default, "ئ", "~", ")", 1}, { keytype_default, "ء", "°", "(", 1}, { keytype_default, "ؤ", "{", "\"", 1}, { keytype_default, "ر", "}", "\'", 1}, { keytype_default, "ى", "ﺁ", "؟", 1}, { keytype_default, "ة", "'", "!", 1}, { keytype_default, "و", ",", ";", 1}, { keytype_default, "ﺯ", ".", "\\", 1}, { keytype_default, "ظ", "؟", "=", 1}, { keytype_switch, "Shift", "Base", "Shift", 2}, { keytype_symbols, "؟٣٢١", "؟٣٢١", "Base", 1}, { keytype_default, "ﻻ", "ﻵ", "|", 1}, { keytype_default, ",", "،", "،", 1}, { keytype_space, "", "", "", 6}, { keytype_default, ".", "ذ", "]", 1}, { keytype_default, "ط", "ﺝ", "[", 1}, { keytype_style, "", "", "", 2} }; static const struct layout normal_layout = { normal_keys, sizeof(normal_keys) / sizeof(*normal_keys), 12, 4, "en", ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_LTR }; static const struct layout numeric_layout = { numeric_keys, sizeof(numeric_keys) / sizeof(*numeric_keys), 12, 2, "en", ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_LTR }; static const struct layout arabic_layout = { arabic_keys, sizeof(arabic_keys) / sizeof(*arabic_keys), 13, 4, "ar", ZWP_TEXT_INPUT_V1_TEXT_DIRECTION_RTL }; static const char *style_labels[] = { "default", "none", "active", "inactive", "highlight", "underline", "selection", "incorrect" }; static const double key_width = 60; static const double key_height = 50; enum keyboard_state { KEYBOARD_STATE_DEFAULT, KEYBOARD_STATE_UPPERCASE, KEYBOARD_STATE_SYMBOLS }; struct keyboard { struct virtual_keyboard *keyboard; struct window *window; struct widget *widget; enum keyboard_state state; }; static void __attribute__ ((format (printf, 1, 2))) dbg(const char *fmt, ...) { #ifdef DEBUG va_list argp; va_start(argp, fmt); vfprintf(stderr, fmt, argp); va_end(argp); #endif } static const char * label_from_key(struct keyboard *keyboard, const struct key *key) { if (key->key_type == keytype_style) return style_labels[keyboard->keyboard->preedit_style]; switch(keyboard->state) { case KEYBOARD_STATE_DEFAULT: return key->label; case KEYBOARD_STATE_UPPERCASE: return key->uppercase; case KEYBOARD_STATE_SYMBOLS: return key->symbol; } return ""; } static void draw_key(struct keyboard *keyboard, const struct key *key, cairo_t *cr, unsigned int row, unsigned int col) { const char *label; cairo_text_extents_t extents; cairo_save(cr); cairo_rectangle(cr, col * key_width, row * key_height, key->width * key_width, key_height); cairo_clip(cr); /* Paint frame */ cairo_rectangle(cr, col * key_width, row * key_height, key->width * key_width, key_height); cairo_set_line_width(cr, 3); cairo_stroke(cr); /* Paint text */ label = label_from_key(keyboard, key); cairo_text_extents(cr, label, &extents); cairo_translate(cr, col * key_width, row * key_height); cairo_translate(cr, (key->width * key_width - extents.width) / 2, (key_height - extents.y_bearing) / 2); cairo_show_text(cr, label); cairo_restore(cr); } static const struct layout * get_current_layout(struct virtual_keyboard *keyboard) { switch (keyboard->content_purpose) { case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DIGITS: case ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NUMBER: return &numeric_layout; default: if (keyboard->preferred_language && strcmp(keyboard->preferred_language, "ar") == 0) return &arabic_layout; else return &normal_layout; } } static void redraw_handler(struct widget *widget, void *data) { struct keyboard *keyboard = data; cairo_surface_t *surface; struct rectangle allocation; cairo_t *cr; unsigned int i; unsigned int row = 0, col = 0; const struct layout *layout; layout = get_current_layout(keyboard->keyboard); surface = window_get_surface(keyboard->window); widget_get_allocation(keyboard->widget, &allocation); cr = cairo_create(surface); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_clip(cr); cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cr, 16); cairo_translate(cr, allocation.x, allocation.y); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 1, 1, 1, 0.75); cairo_rectangle(cr, 0, 0, layout->columns * key_width, layout->rows * key_height); cairo_paint(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); for (i = 0; i < layout->count; ++i) { cairo_set_source_rgb(cr, 0, 0, 0); draw_key(keyboard, &layout->keys[i], cr, row, col); col += layout->keys[i].width; if (col >= layout->columns) { row += 1; col = 0; } } cairo_destroy(cr); cairo_surface_destroy(surface); } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { /* struct keyboard *keyboard = data; */ } static char * insert_text(const char *text, uint32_t offset, const char *insert) { int tlen = strlen(text), ilen = strlen(insert); char *new_text = xmalloc(tlen + ilen + 1); memcpy(new_text, text, offset); memcpy(new_text + offset, insert, ilen); memcpy(new_text + offset + ilen, text + offset, tlen - offset); new_text[tlen + ilen] = '\0'; return new_text; } static void virtual_keyboard_commit_preedit(struct virtual_keyboard *keyboard) { char *surrounding_text; if (!keyboard->preedit_string || strlen(keyboard->preedit_string) == 0) return; zwp_input_method_context_v1_cursor_position(keyboard->context, 0, 0); zwp_input_method_context_v1_commit_string(keyboard->context, keyboard->serial, keyboard->preedit_string); if (keyboard->surrounding_text) { surrounding_text = insert_text(keyboard->surrounding_text, keyboard->surrounding_cursor, keyboard->preedit_string); free(keyboard->surrounding_text); keyboard->surrounding_text = surrounding_text; keyboard->surrounding_cursor += strlen(keyboard->preedit_string); } else { keyboard->surrounding_text = strdup(keyboard->preedit_string); keyboard->surrounding_cursor = strlen(keyboard->preedit_string); } free(keyboard->preedit_string); keyboard->preedit_string = strdup(""); } static void virtual_keyboard_send_preedit(struct virtual_keyboard *keyboard, int32_t cursor) { uint32_t index = strlen(keyboard->preedit_string); if (keyboard->preedit_style) zwp_input_method_context_v1_preedit_styling(keyboard->context, 0, strlen(keyboard->preedit_string), keyboard->preedit_style); if (cursor > 0) index = cursor; zwp_input_method_context_v1_preedit_cursor(keyboard->context, index); zwp_input_method_context_v1_preedit_string(keyboard->context, keyboard->serial, keyboard->preedit_string, keyboard->preedit_string); } static const char * prev_utf8_char(const char *s, const char *p) { for (--p; p >= s; --p) { if ((*p & 0xc0) != 0x80) return p; } return NULL; } static void delete_before_cursor(struct virtual_keyboard *keyboard) { const char *start, *end; if (!keyboard->surrounding_text) { dbg("delete_before_cursor: No surrounding text available\n"); return; } start = prev_utf8_char(keyboard->surrounding_text, keyboard->surrounding_text + keyboard->surrounding_cursor); if (!start) { dbg("delete_before_cursor: No previous character to delete\n"); return; } end = keyboard->surrounding_text + keyboard->surrounding_cursor; zwp_input_method_context_v1_delete_surrounding_text(keyboard->context, (start - keyboard->surrounding_text) - keyboard->surrounding_cursor, end - start); zwp_input_method_context_v1_commit_string(keyboard->context, keyboard->serial, ""); /* Update surrounding text */ keyboard->surrounding_cursor = start - keyboard->surrounding_text; keyboard->surrounding_text[keyboard->surrounding_cursor] = '\0'; if (*end) memmove(keyboard->surrounding_text + keyboard->surrounding_cursor, end, strlen(end)); } static char * append(char *s1, const char *s2) { int len1, len2; char *s; len1 = strlen(s1); len2 = strlen(s2); s = xrealloc(s1, len1 + len2 + 1); memcpy(s + len1, s2, len2); s[len1 + len2] = '\0'; return s; } static void keyboard_handle_key(struct keyboard *keyboard, uint32_t time, const struct key *key, struct input *input, enum wl_pointer_button_state state) { const char *label = NULL; switch(keyboard->state) { case KEYBOARD_STATE_DEFAULT : label = key->label; break; case KEYBOARD_STATE_UPPERCASE : label = key->uppercase; break; case KEYBOARD_STATE_SYMBOLS : label = key->symbol; break; } xkb_mod_mask_t mod_mask = keyboard->state == KEYBOARD_STATE_DEFAULT ? 0 : keyboard->keyboard->keysym.shift_mask; uint32_t key_state = (state == WL_POINTER_BUTTON_STATE_PRESSED) ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED; switch (key->key_type) { case keytype_default: if (state != WL_POINTER_BUTTON_STATE_PRESSED) break; keyboard->keyboard->preedit_string = append(keyboard->keyboard->preedit_string, label); virtual_keyboard_send_preedit(keyboard->keyboard, -1); break; case keytype_backspace: if (state != WL_POINTER_BUTTON_STATE_PRESSED) break; if (strlen(keyboard->keyboard->preedit_string) == 0) { delete_before_cursor(keyboard->keyboard); } else { keyboard->keyboard->preedit_string[strlen(keyboard->keyboard->preedit_string) - 1] = '\0'; virtual_keyboard_send_preedit(keyboard->keyboard, -1); } break; case keytype_enter: virtual_keyboard_commit_preedit(keyboard->keyboard); zwp_input_method_context_v1_keysym(keyboard->keyboard->context, display_get_serial(keyboard->keyboard->display), time, XKB_KEY_Return, key_state, mod_mask); break; case keytype_space: if (state != WL_POINTER_BUTTON_STATE_PRESSED) break; keyboard->keyboard->preedit_string = append(keyboard->keyboard->preedit_string, " "); virtual_keyboard_commit_preedit(keyboard->keyboard); break; case keytype_switch: if (state != WL_POINTER_BUTTON_STATE_PRESSED) break; switch(keyboard->state) { case KEYBOARD_STATE_DEFAULT: keyboard->state = KEYBOARD_STATE_UPPERCASE; break; case KEYBOARD_STATE_UPPERCASE: keyboard->state = KEYBOARD_STATE_DEFAULT; break; case KEYBOARD_STATE_SYMBOLS: keyboard->state = KEYBOARD_STATE_UPPERCASE; break; } break; case keytype_symbols: if (state != WL_POINTER_BUTTON_STATE_PRESSED) break; switch(keyboard->state) { case KEYBOARD_STATE_DEFAULT: keyboard->state = KEYBOARD_STATE_SYMBOLS; break; case KEYBOARD_STATE_UPPERCASE: keyboard->state = KEYBOARD_STATE_SYMBOLS; break; case KEYBOARD_STATE_SYMBOLS: keyboard->state = KEYBOARD_STATE_DEFAULT; break; } break; case keytype_tab: virtual_keyboard_commit_preedit(keyboard->keyboard); zwp_input_method_context_v1_keysym(keyboard->keyboard->context, display_get_serial(keyboard->keyboard->display), time, XKB_KEY_Tab, key_state, mod_mask); break; case keytype_arrow_up: virtual_keyboard_commit_preedit(keyboard->keyboard); zwp_input_method_context_v1_keysym(keyboard->keyboard->context, display_get_serial(keyboard->keyboard->display), time, XKB_KEY_Up, key_state, mod_mask); break; case keytype_arrow_left: virtual_keyboard_commit_preedit(keyboard->keyboard); zwp_input_method_context_v1_keysym(keyboard->keyboard->context, display_get_serial(keyboard->keyboard->display), time, XKB_KEY_Left, key_state, mod_mask); break; case keytype_arrow_right: virtual_keyboard_commit_preedit(keyboard->keyboard); zwp_input_method_context_v1_keysym(keyboard->keyboard->context, display_get_serial(keyboard->keyboard->display), time, XKB_KEY_Right, key_state, mod_mask); break; case keytype_arrow_down: virtual_keyboard_commit_preedit(keyboard->keyboard); zwp_input_method_context_v1_keysym(keyboard->keyboard->context, display_get_serial(keyboard->keyboard->display), time, XKB_KEY_Down, key_state, mod_mask); break; case keytype_style: if (state != WL_POINTER_BUTTON_STATE_PRESSED) break; keyboard->keyboard->preedit_style = (keyboard->keyboard->preedit_style + 1) % 8; /* TODO */ virtual_keyboard_send_preedit(keyboard->keyboard, -1); break; } } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct keyboard *keyboard = data; struct rectangle allocation; int32_t x, y; int row, col; unsigned int i; const struct layout *layout; layout = get_current_layout(keyboard->keyboard); if (button != BTN_LEFT) { return; } input_get_position(input, &x, &y); widget_get_allocation(keyboard->widget, &allocation); x -= allocation.x; y -= allocation.y; row = y / key_height; col = x / key_width + row * layout->columns; for (i = 0; i < layout->count; ++i) { col -= layout->keys[i].width; if (col < 0) { keyboard_handle_key(keyboard, time, &layout->keys[i], input, state); break; } } widget_schedule_redraw(widget); } static void touch_handler(struct input *input, uint32_t time, float x, float y, uint32_t state, void *data) { struct keyboard *keyboard = data; struct rectangle allocation; int row, col; unsigned int i; const struct layout *layout; layout = get_current_layout(keyboard->keyboard); widget_get_allocation(keyboard->widget, &allocation); x -= allocation.x; y -= allocation.y; row = (int)y / key_height; col = (int)x / key_width + row * layout->columns; for (i = 0; i < layout->count; ++i) { col -= layout->keys[i].width; if (col < 0) { keyboard_handle_key(keyboard, time, &layout->keys[i], input, state); break; } } widget_schedule_redraw(keyboard->widget); } static void touch_down_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data) { touch_handler(input, time, x, y, WL_POINTER_BUTTON_STATE_PRESSED, data); } static void touch_up_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, void *data) { float x, y; input_get_touch(input, id, &x, &y); touch_handler(input, time, x, y, WL_POINTER_BUTTON_STATE_RELEASED, data); } static void handle_surrounding_text(void *data, struct zwp_input_method_context_v1 *context, const char *text, uint32_t cursor, uint32_t anchor) { struct virtual_keyboard *keyboard = data; free(keyboard->surrounding_text); keyboard->surrounding_text = strdup(text); keyboard->surrounding_cursor = cursor; } static void handle_reset(void *data, struct zwp_input_method_context_v1 *context) { struct virtual_keyboard *keyboard = data; dbg("Reset pre-edit buffer\n"); if (strlen(keyboard->preedit_string)) { free(keyboard->preedit_string); keyboard->preedit_string = strdup(""); } } static void handle_content_type(void *data, struct zwp_input_method_context_v1 *context, uint32_t hint, uint32_t purpose) { struct virtual_keyboard *keyboard = data; keyboard->content_hint = hint; keyboard->content_purpose = purpose; } static void handle_invoke_action(void *data, struct zwp_input_method_context_v1 *context, uint32_t button, uint32_t index) { struct virtual_keyboard *keyboard = data; if (button != BTN_LEFT) return; virtual_keyboard_send_preedit(keyboard, index); } static void handle_commit_state(void *data, struct zwp_input_method_context_v1 *context, uint32_t serial) { struct virtual_keyboard *keyboard = data; const struct layout *layout; keyboard->serial = serial; layout = get_current_layout(keyboard); if (keyboard->surrounding_text) dbg("Surrounding text updated: %s\n", keyboard->surrounding_text); window_schedule_resize(keyboard->keyboard->window, layout->columns * key_width, layout->rows * key_height); zwp_input_method_context_v1_language(context, keyboard->serial, layout->language); zwp_input_method_context_v1_text_direction(context, keyboard->serial, layout->text_direction); widget_schedule_redraw(keyboard->keyboard->widget); } static void handle_preferred_language(void *data, struct zwp_input_method_context_v1 *context, const char *language) { struct virtual_keyboard *keyboard = data; if (keyboard->preferred_language) free(keyboard->preferred_language); keyboard->preferred_language = NULL; if (language) keyboard->preferred_language = strdup(language); } static const struct zwp_input_method_context_v1_listener input_method_context_listener = { handle_surrounding_text, handle_reset, handle_content_type, handle_invoke_action, handle_commit_state, handle_preferred_language }; static void input_method_activate(void *data, struct zwp_input_method_v1 *input_method, struct zwp_input_method_context_v1 *context) { struct virtual_keyboard *keyboard = data; struct wl_array modifiers_map; const struct layout *layout; keyboard->keyboard->state = KEYBOARD_STATE_DEFAULT; if (keyboard->context) zwp_input_method_context_v1_destroy(keyboard->context); if (keyboard->preedit_string) free(keyboard->preedit_string); keyboard->preedit_string = strdup(""); keyboard->content_hint = 0; keyboard->content_purpose = 0; free(keyboard->preferred_language); keyboard->preferred_language = NULL; free(keyboard->surrounding_text); keyboard->surrounding_text = NULL; keyboard->serial = 0; keyboard->context = context; zwp_input_method_context_v1_add_listener(context, &input_method_context_listener, keyboard); wl_array_init(&modifiers_map); keysym_modifiers_add(&modifiers_map, "Shift"); keysym_modifiers_add(&modifiers_map, "Control"); keysym_modifiers_add(&modifiers_map, "Mod1"); zwp_input_method_context_v1_modifiers_map(context, &modifiers_map); keyboard->keysym.shift_mask = keysym_modifiers_get_mask(&modifiers_map, "Shift"); wl_array_release(&modifiers_map); layout = get_current_layout(keyboard); window_schedule_resize(keyboard->keyboard->window, layout->columns * key_width, layout->rows * key_height); zwp_input_method_context_v1_language(context, keyboard->serial, layout->language); zwp_input_method_context_v1_text_direction(context, keyboard->serial, layout->text_direction); widget_schedule_redraw(keyboard->keyboard->widget); } static void input_method_deactivate(void *data, struct zwp_input_method_v1 *input_method, struct zwp_input_method_context_v1 *context) { struct virtual_keyboard *keyboard = data; if (!keyboard->context) return; zwp_input_method_context_v1_destroy(keyboard->context); keyboard->context = NULL; } static const struct zwp_input_method_v1_listener input_method_listener = { input_method_activate, input_method_deactivate }; static void global_handler(struct display *display, uint32_t name, const char *interface, uint32_t version, void *data) { struct virtual_keyboard *keyboard = data; if (!strcmp(interface, "zwp_input_panel_v1")) { keyboard->input_panel = display_bind(display, name, &zwp_input_panel_v1_interface, 1); } else if (!strcmp(interface, "zwp_input_method_v1")) { keyboard->input_method = display_bind(display, name, &zwp_input_method_v1_interface, 1); zwp_input_method_v1_add_listener(keyboard->input_method, &input_method_listener, keyboard); } } static void set_toplevel(struct output *output, struct virtual_keyboard *virtual_keyboard) { struct zwp_input_panel_surface_v1 *ips; struct keyboard *keyboard = virtual_keyboard->keyboard; ips = zwp_input_panel_v1_get_input_panel_surface(virtual_keyboard->input_panel, window_get_wl_surface(keyboard->window)); zwp_input_panel_surface_v1_set_toplevel(ips, output_get_wl_output(output), ZWP_INPUT_PANEL_SURFACE_V1_POSITION_CENTER_BOTTOM); virtual_keyboard->toplevel = true; } static void display_output_handler(struct output *output, void *data) { struct virtual_keyboard *keyboard = data; if (!keyboard->toplevel) set_toplevel(output, keyboard); } static void keyboard_create(struct virtual_keyboard *virtual_keyboard) { struct keyboard *keyboard; const struct layout *layout; layout = get_current_layout(virtual_keyboard); keyboard = xzalloc(sizeof *keyboard); keyboard->keyboard = virtual_keyboard; keyboard->window = window_create_custom(virtual_keyboard->display); keyboard->widget = window_add_widget(keyboard->window, keyboard); virtual_keyboard->keyboard = keyboard; window_set_title(keyboard->window, "Virtual keyboard"); window_set_user_data(keyboard->window, keyboard); widget_set_redraw_handler(keyboard->widget, redraw_handler); widget_set_resize_handler(keyboard->widget, resize_handler); widget_set_button_handler(keyboard->widget, button_handler); widget_set_touch_down_handler(keyboard->widget, touch_down_handler); widget_set_touch_up_handler(keyboard->widget, touch_up_handler); window_schedule_resize(keyboard->window, layout->columns * key_width, layout->rows * key_height); display_set_output_configure_handler(virtual_keyboard->display, display_output_handler); } int main(int argc, char *argv[]) { struct virtual_keyboard virtual_keyboard; memset(&virtual_keyboard, 0, sizeof virtual_keyboard); virtual_keyboard.display = display_create(&argc, argv); if (virtual_keyboard.display == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } display_set_user_data(virtual_keyboard.display, &virtual_keyboard); display_set_global_handler(virtual_keyboard.display, global_handler); if (virtual_keyboard.input_panel == NULL) { fprintf(stderr, "No input panel global\n"); return -1; } keyboard_create(&virtual_keyboard); display_run(virtual_keyboard.display); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.318963 weston-8.0.0/clients/meson.build0000644000175000017460000002251300000000000017125 0ustar00simonwheel00000000000000if get_option('resize-pool') config_h.set('USE_RESIZE_POOL', '1') endif srcs_toytoolkit = [ 'window.c', xdg_shell_client_protocol_h, xdg_shell_protocol_c, text_cursor_position_client_protocol_h, text_cursor_position_protocol_c, relative_pointer_unstable_v1_client_protocol_h, relative_pointer_unstable_v1_protocol_c, pointer_constraints_unstable_v1_client_protocol_h, pointer_constraints_unstable_v1_protocol_c, ivi_application_client_protocol_h, ivi_application_protocol_c, viewporter_client_protocol_h, viewporter_protocol_c, ] deps_toytoolkit = [ dep_wayland_client, dep_lib_cairo_shared, dep_xkbcommon, dependency('wayland-cursor'), cc.find_library('util'), ] lib_toytoolkit = static_library( 'toytoolkit', srcs_toytoolkit, include_directories: common_inc, dependencies: deps_toytoolkit, install: false, ) dep_toytoolkit = declare_dependency( link_with: lib_toytoolkit, dependencies: deps_toytoolkit, ) simple_clients = [ { 'name': 'damage', 'sources': [ 'simple-damage.c', viewporter_client_protocol_h, viewporter_protocol_c, xdg_shell_client_protocol_h, xdg_shell_protocol_c, fullscreen_shell_unstable_v1_client_protocol_h, fullscreen_shell_unstable_v1_protocol_c, ], 'dep_objs': [ dep_wayland_client, dep_libshared ] }, { 'name': 'dmabuf-egl', 'sources': [ 'simple-dmabuf-egl.c', linux_dmabuf_unstable_v1_client_protocol_h, linux_dmabuf_unstable_v1_protocol_c, linux_explicit_synchronization_unstable_v1_client_protocol_h, linux_explicit_synchronization_unstable_v1_protocol_c, xdg_shell_client_protocol_h, xdg_shell_protocol_c, weston_direct_display_client_protocol_h, weston_direct_display_protocol_c, fullscreen_shell_unstable_v1_client_protocol_h, fullscreen_shell_unstable_v1_protocol_c, ], 'dep_objs': [ dep_wayland_client, dep_libdrm, dep_libm ], 'deps': [ 'egl', 'glesv2', 'gbm' ], 'options': [ 'renderer-gl' ] }, { 'name': 'dmabuf-v4l', 'sources': [ 'simple-dmabuf-v4l.c', linux_dmabuf_unstable_v1_client_protocol_h, linux_dmabuf_unstable_v1_protocol_c, xdg_shell_client_protocol_h, xdg_shell_protocol_c, fullscreen_shell_unstable_v1_client_protocol_h, fullscreen_shell_unstable_v1_protocol_c, ], 'dep_objs': [ dep_wayland_client, dep_libdrm_headers ] }, { 'name': 'egl', 'sources': [ 'simple-egl.c', xdg_shell_client_protocol_h, xdg_shell_protocol_c, ivi_application_client_protocol_h, ivi_application_protocol_c, ], 'dep_objs': [ dep_wayland_client, dep_libshared, dep_libm ], 'deps': [ 'egl', 'wayland-egl', 'glesv2', 'wayland-cursor' ], 'options': [ 'renderer-gl' ] }, # weston-simple-im is handled specially separately due to install_dir and odd window.h usage { 'name': 'shm', 'sources': [ 'simple-shm.c', xdg_shell_client_protocol_h, xdg_shell_protocol_c, fullscreen_shell_unstable_v1_client_protocol_h, fullscreen_shell_unstable_v1_protocol_c, ivi_application_client_protocol_h, ivi_application_protocol_c, ], 'dep_objs': [ dep_wayland_client, dep_libshared ] }, { 'name': 'touch', 'sources': [ 'simple-touch.c', ], 'dep_objs': [ dep_wayland_client, dep_libshared ] }, ] simple_clients_enabled = get_option('simple-clients') simple_build_all = simple_clients_enabled.contains('all') foreach t : simple_clients if simple_build_all or simple_clients_enabled.contains(t.get('name')) t_name = 'weston-simple-' + t.get('name') t_deps = t.get('dep_objs', []) foreach depname : t.get('deps', []) dep = dependency(depname, required: false) if not dep.found() error('@0@ requires @1@ which was not found. If you rather not build this, drop "@2@" from simple-clients option.'.format(t_name, depname, t.get('name'))) endif t_deps += dep endforeach foreach optname : t.get('options', []) if not get_option(optname) error('@0@ requires option @1@ which is not enabled. If you rather not build this, drop "@2@" from simple-clients option.'.format(t_name, optname, t.get('name'))) endif endforeach executable( t_name, t.get('sources'), include_directories: common_inc, dependencies: t_deps, install: true ) endif endforeach if simple_build_all or simple_clients_enabled.contains('im') executable( 'weston-simple-im', [ 'simple-im.c', input_method_unstable_v1_client_protocol_h, input_method_unstable_v1_protocol_c, ], include_directories: common_inc, dependencies: [ dep_libshared, dep_wayland_client, dep_xkbcommon, dependency('wayland-cursor'), dependency('cairo') ], install: true, install_dir: dir_libexec ) endif tools_enabled = get_option('tools') tools_list = [ { 'name': 'calibrator', 'sources': [ 'calibrator.c' ], 'deps': [ dep_toytoolkit, dep_matrix_c ], }, { 'name': 'debug', 'sources': [ 'weston-debug.c', weston_debug_client_protocol_h, weston_debug_protocol_c, ], 'deps': [ dep_wayland_client ] }, { 'name': 'info', 'sources': [ 'weston-info.c', presentation_time_client_protocol_h, presentation_time_protocol_c, linux_dmabuf_unstable_v1_client_protocol_h, linux_dmabuf_unstable_v1_protocol_c, tablet_unstable_v2_client_protocol_h, tablet_unstable_v2_protocol_c, xdg_output_unstable_v1_client_protocol_h, xdg_output_unstable_v1_protocol_c, ], 'deps': [ dep_wayland_client, dep_libshared ] }, { 'name': 'terminal', 'sources': [ 'terminal.c' ], 'deps': [ dep_toytoolkit ], }, { 'name': 'touch-calibrator', 'sources': [ 'touch-calibrator.c', weston_touch_calibration_client_protocol_h, weston_touch_calibration_protocol_c, ], 'deps': [ dep_toytoolkit, dep_matrix_c ], }, ] foreach t : tools_list if tools_enabled.contains(t.get('name')) executable( 'weston-@0@'.format(t.get('name')), t.get('sources'), include_directories: common_inc, dependencies: t.get('deps', []), install: true ) endif endforeach demo_clients = [ { 'basename': 'clickdot' }, { 'basename': 'cliptest', 'dep_objs': dep_vertex_clipping }, { 'basename': 'confine' }, { 'basename': 'content_protection', 'add_sources': [ weston_content_protection_client_protocol_h, weston_content_protection_protocol_c, ] }, { 'basename': 'dnd' }, { 'basename': 'editor', 'add_sources': [ text_input_unstable_v1_client_protocol_h, text_input_unstable_v1_protocol_c, ], 'deps': [ 'pangocairo', 'gobject-2.0' ] }, { 'basename': 'eventdemo' }, { 'basename': 'flower' }, { 'basename': 'fullscreen', 'add_sources': [ fullscreen_shell_unstable_v1_client_protocol_h, fullscreen_shell_unstable_v1_protocol_c, ] }, { 'basename': 'image' }, { 'basename': 'multi-resource' }, { 'basename': 'presentation-shm', 'add_sources': [ presentation_time_client_protocol_h, presentation_time_protocol_c, xdg_shell_client_protocol_h, xdg_shell_protocol_c, ] }, { 'basename': 'resizor' }, { 'basename': 'scaler', 'add_sources': [ viewporter_client_protocol_h, viewporter_protocol_c, ] }, { 'basename': 'smoke' }, { 'basename': 'stacking' }, { 'basename': 'subsurfaces', 'deps': [ 'egl', 'glesv2', 'wayland-egl' ] }, { 'basename': 'transformed' }, ] if get_option('demo-clients') foreach t : demo_clients t_name = 'weston-' + t.get('basename') t_srcs = [ t.get('basename') + '.c' ] + t.get('add_sources', []) t_deps = [ dep_toytoolkit, t.get('dep_objs', []) ] foreach depname : t.get('deps', []) dep = dependency(depname, required: false) if not dep.found() error('@0@ requires \'@1@\' which was not found. If you rather not build this, set \'-Ddemo-clients=false\'.'.format(t_name, depname)) endif t_deps += dep endforeach executable( t_name, t_srcs, include_directories: common_inc, dependencies: t_deps, install: true ) endforeach endif if get_option('shell-desktop') exe_keyboard = executable( 'weston-keyboard', 'keyboard.c', text_input_unstable_v1_client_protocol_h, text_input_unstable_v1_protocol_c, input_method_unstable_v1_client_protocol_h, input_method_unstable_v1_protocol_c, include_directories: common_inc, dependencies: dep_toytoolkit, install_dir: get_option('libexecdir'), install: true ) env_modmap += 'weston-keyboard=@0@;'.format(exe_keyboard.full_path()) exe_shooter = executable( 'weston-screenshooter', 'screenshot.c', weston_screenshooter_client_protocol_h, weston_screenshooter_protocol_c, include_directories: common_inc, dependencies: dep_toytoolkit, install_dir: get_option('bindir'), install: true ) env_modmap += 'weston-screenshooter=@0@;'.format(exe_shooter.full_path()) exe_shell_desktop = executable( 'weston-desktop-shell', 'desktop-shell.c', weston_desktop_shell_client_protocol_h, weston_desktop_shell_protocol_c, include_directories: common_inc, dependencies: dep_toytoolkit, install_dir: get_option('libexecdir'), install: true ) env_modmap += 'weston-desktop-shell=@0@;'.format(exe_shell_desktop.full_path()) endif if get_option('shell-ivi') exe_shell_ivi_ui = executable( 'weston-ivi-shell-user-interface', 'ivi-shell-user-interface.c', ivi_hmi_controller_client_protocol_h, ivi_hmi_controller_protocol_c, ivi_application_client_protocol_h, ivi_application_protocol_c, include_directories: common_inc, dependencies: dep_toytoolkit, install: true, install_dir: get_option('libexecdir') ) env_modmap += 'weston-ivi-shell-user-interface=@0@;'.format(exe_shell_ivi_ui.full_path()) endif ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.318963 weston-8.0.0/clients/multi-resource.c0000644000175000017460000003207200000000000020107 0ustar00simonwheel00000000000000/* * Copyright © 2011 Benjamin Franzke * Copyright © 2010, 2013 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shared/os-compatibility.h" #include "shared/xalloc.h" #include struct device { enum { KEYBOARD, POINTER } type; int start_time; int end_time; struct wl_list link; union { struct wl_keyboard *keyboard; struct wl_pointer *pointer; } p; }; struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct wl_shell *shell; struct wl_seat *seat; struct wl_shm *shm; uint32_t formats; struct wl_list devices; }; struct window { struct display *display; int width, height; struct wl_surface *surface; struct wl_shell_surface *shell_surface; }; static void buffer_release(void *data, struct wl_buffer *buffer) { wl_buffer_destroy(buffer); } static const struct wl_buffer_listener buffer_listener = { buffer_release }; static int attach_buffer(struct window *window, int width, int height) { struct wl_shm_pool *pool; struct wl_buffer *buffer; int fd, size, stride; stride = width * 4; size = stride * height; fd = os_create_anonymous_file(size); if (fd < 0) { fprintf(stderr, "creating a buffer file for %d B failed: %s\n", size, strerror(errno)); return -1; } pool = wl_shm_create_pool(window->display->shm, fd, size); buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888); wl_surface_attach(window->surface, buffer, 0, 0); wl_buffer_add_listener(buffer, &buffer_listener, buffer); wl_shm_pool_destroy(pool); close(fd); return 0; } static void handle_ping(void *data, struct wl_shell_surface *shell_surface, uint32_t serial) { wl_shell_surface_pong(shell_surface, serial); } static void handle_configure(void *data, struct wl_shell_surface *shell_surface, uint32_t edges, int32_t width, int32_t height) { } static void handle_popup_done(void *data, struct wl_shell_surface *shell_surface) { } static const struct wl_shell_surface_listener shell_surface_listener = { handle_ping, handle_configure, handle_popup_done }; static struct window * create_window(struct display *display, int width, int height) { struct window *window; window = xzalloc(sizeof *window); window->display = display; window->width = width; window->height = height; window->surface = wl_compositor_create_surface(display->compositor); window->shell_surface = wl_shell_get_shell_surface(display->shell, window->surface); if (window->shell_surface) wl_shell_surface_add_listener(window->shell_surface, &shell_surface_listener, window); wl_shell_surface_set_title(window->shell_surface, "simple-shm"); wl_shell_surface_set_toplevel(window->shell_surface); wl_surface_damage(window->surface, 0, 0, width, height); attach_buffer(window, width, height); wl_surface_commit(window->surface); return window; } static void destroy_window(struct window *window) { wl_shell_surface_destroy(window->shell_surface); wl_surface_destroy(window->surface); free(window); } static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { struct display *d = data; d->formats |= (1 << format); } struct wl_shm_listener shm_listener = { shm_format }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct display *d = data; if (strcmp(interface, "wl_compositor") == 0) { d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); } else if (strcmp(interface, "wl_shell") == 0) { d->shell = wl_registry_bind(registry, id, &wl_shell_interface, 1); } else if (strcmp(interface, "wl_shm") == 0) { d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); wl_shm_add_listener(d->shm, &shm_listener, d); } else if (strcmp(interface, "wl_seat") == 0 && d->seat == NULL) { d->seat = wl_registry_bind(registry, id, &wl_seat_interface, 3); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static struct display * create_display(void) { struct display *display; display = xzalloc(sizeof *display); display->display = wl_display_connect(NULL); assert(display->display); display->formats = 0; display->registry = wl_display_get_registry(display->display); wl_registry_add_listener(display->registry, ®istry_listener, display); wl_display_roundtrip(display->display); if (display->shm == NULL) { fprintf(stderr, "No wl_shm global\n"); exit(1); } wl_display_roundtrip(display->display); if (!(display->formats & (1 << WL_SHM_FORMAT_XRGB8888))) { fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); exit(1); } wl_list_init(&display->devices); return display; } static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx_w, wl_fixed_t sy_w) { } static void pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { } static void pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w) { } static void pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state_w) { } static void pointer_handle_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { } static const struct wl_pointer_listener pointer_listener = { pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis, }; static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { /* Just so we don’t leak the keymap fd */ close(fd); } static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { } static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { } static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state_w) { } static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { } static const struct wl_keyboard_listener keyboard_listener = { keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, }; static void start_device(struct display *display, struct device *device) { if (display->seat == NULL) return; switch (device->type) { case KEYBOARD: if (device->p.keyboard == NULL) { device->p.keyboard = wl_seat_get_keyboard(display->seat); wl_keyboard_add_listener(device->p.keyboard, &keyboard_listener, NULL); } break; case POINTER: if (device->p.pointer == NULL) { device->p.pointer = wl_seat_get_pointer(display->seat); wl_pointer_add_listener(device->p.pointer, &pointer_listener, NULL); } break; } } static void destroy_device(struct device *device) { switch (device->type) { case KEYBOARD: if (device->p.keyboard) wl_keyboard_release(device->p.keyboard); break; case POINTER: if (device->p.pointer) wl_pointer_release(device->p.pointer); break; } wl_list_remove(&device->link); free(device); } static void destroy_devices(struct display *display) { struct device *device, *tmp; wl_list_for_each_safe(device, tmp, &display->devices, link) destroy_device(device); } static void destroy_display(struct display *display) { destroy_devices(display); if (display->shm) wl_shm_destroy(display->shm); if (display->shell) wl_shell_destroy(display->shell); if (display->seat) wl_seat_destroy(display->seat); if (display->compositor) wl_compositor_destroy(display->compositor); wl_registry_destroy(display->registry); wl_display_flush(display->display); wl_display_disconnect(display->display); free(display); } static int running = 1; static void signal_int(int signum) { running = 0; } static int create_device(struct display *display, const char *time_desc, int type) { int start_time; int end_time = -1; char *tail; struct device *device; if (time_desc == NULL) { fprintf(stderr, "missing time description\n"); return -1; } errno = 0; start_time = strtoul(time_desc, &tail, 10); if (errno || tail == time_desc) goto error; if (*tail == ':') { time_desc = tail + 1; end_time = strtoul(time_desc, &tail, 10); if (errno || tail == time_desc || *tail != '\0') goto error; } else if (*tail != '\0') { goto error; } device = xzalloc(sizeof *device); device->type = type; device->start_time = start_time; device->end_time = end_time; wl_list_insert(&display->devices, &device->link); return 0; error: fprintf(stderr, "invalid time description\n"); return -1; } static struct timespec begin_time; static void reset_timer(void) { clock_gettime(CLOCK_MONOTONIC, &begin_time); } static double read_timer(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return (double)(t.tv_sec - begin_time.tv_sec) + 1e-9 * (t.tv_nsec - begin_time.tv_nsec); } static void main_loop(struct display *display) { reset_timer(); while (running) { struct device *device, *tmp; struct pollfd fds[1]; double sleep_time = DBL_MAX; double now; if (wl_display_dispatch_pending(display->display) == -1) break; if (wl_display_flush(display->display) == -1) break; now = read_timer(); wl_list_for_each(device, &display->devices, link) { double next_time = device->start_time - now; if (next_time < 0.0) { sleep_time = 0.0; break; } else if (next_time < sleep_time) { sleep_time = next_time; } next_time = device->end_time - now; if (next_time < 0.0) { sleep_time = 0.0; break; } else if (next_time < sleep_time) { sleep_time = next_time; } } fds[0].fd = wl_display_get_fd(display->display); fds[0].events = POLLIN; fds[0].revents = 0; poll(fds, sizeof fds / sizeof fds[0], sleep_time == DBL_MAX ? -1 : ceil(sleep_time * 1000.0)); if (fds[0].revents && wl_display_dispatch(display->display) == -1) break; now = read_timer(); wl_list_for_each_safe(device, tmp, &display->devices, link) { if (device->start_time <= now) start_device(display, device); if (device->end_time >= 0 && device->end_time <= now) destroy_device(device); } } } int main(int argc, char **argv) { struct sigaction sigint; struct display *display; struct window *window; int i; display = create_display(); window = create_window(display, 250, 250); for (i = 1; i < argc; i++) { if (!strncmp(argv[i], "-p", 2)) { char *arg; if (argv[i][2]) { arg = argv[i] + 2; } else { arg = argv[i + 1]; i++; } if (create_device(display, arg, POINTER) == -1) return 1; } else if (!strncmp(argv[i], "-k", 2)) { char *arg; if (argv[i][2]) { arg = argv[i] + 2; } else { arg = argv[i + 1]; i++; } if (create_device(display, arg, KEYBOARD) == -1) return 1; } else { fprintf(stderr, "unknown argument %s\n", argv[i]); return 1; } } sigint.sa_handler = signal_int; sigemptyset(&sigint.sa_mask); sigint.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sigint, NULL); main_loop(display); fprintf(stderr, "multi-resource exiting\n"); destroy_window(window); destroy_display(display); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.318963 weston-8.0.0/clients/nested-client.c0000644000175000017460000002161100000000000017663 0ustar00simonwheel00000000000000/* * Copyright © 2013 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include "shared/platform.h" struct window; struct seat; struct nested_client { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; EGLDisplay egl_display; EGLContext egl_context; EGLConfig egl_config; EGLSurface egl_surface; struct program *color_program; GLuint vert, frag, program; GLuint rotation; GLuint pos; GLuint col; struct wl_surface *surface; struct wl_egl_window *native; int width, height; }; #define POS 0 #define COL 1 static GLuint create_shader(const char *source, GLenum shader_type) { GLuint shader; GLint status; shader = glCreateShader(shader_type); if (shader == 0) return 0; glShaderSource(shader, 1, (const char **) &source, NULL); glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (!status) { char log[1000]; GLsizei len; glGetShaderInfoLog(shader, 1000, &len, log); fprintf(stderr, "Error: compiling %s: %.*s\n", shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", len, log); return 0; } return shader; } static void create_program(struct nested_client *client, const char *vert, const char *frag) { GLint status; client->vert = create_shader(vert, GL_VERTEX_SHADER); client->frag = create_shader(frag, GL_FRAGMENT_SHADER); client->program = glCreateProgram(); glAttachShader(client->program, client->frag); glAttachShader(client->program, client->vert); glBindAttribLocation(client->program, POS, "pos"); glBindAttribLocation(client->program, COL, "color"); glLinkProgram(client->program); glGetProgramiv(client->program, GL_LINK_STATUS, &status); if (!status) { char log[1000]; GLsizei len; glGetProgramInfoLog(client->program, 1000, &len, log); fprintf(stderr, "Error: linking:\n%.*s\n", len, log); exit(1); } client->rotation = glGetUniformLocation(client->program, "rotation"); } static const char vertex_shader_text[] = "uniform mat4 rotation;\n" "attribute vec4 pos;\n" "attribute vec4 color;\n" "varying vec4 v_color;\n" "void main() {\n" " gl_Position = rotation * pos;\n" " v_color = color;\n" "}\n"; static const char color_fragment_shader_text[] = "precision mediump float;\n" "varying vec4 v_color;\n" "void main() {\n" " gl_FragColor = v_color;\n" "}\n"; static void render_triangle(struct nested_client *client, uint32_t time) { static const GLfloat verts[3][2] = { { -0.5, -0.5 }, { 0.5, -0.5 }, { 0, 0.5 } }; static const GLfloat colors[3][3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; GLfloat angle; GLfloat rotation[4][4] = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } }; static const int32_t speed_div = 5; static uint32_t start_time = 0; if (client->program == 0) create_program(client, vertex_shader_text, color_fragment_shader_text); if (start_time == 0) start_time = time; angle = ((time - start_time) / speed_div) % 360 * M_PI / 180.0; rotation[0][0] = cos(angle); rotation[0][2] = sin(angle); rotation[2][0] = -sin(angle); rotation[2][2] = cos(angle); glClearColor(0.4, 0.4, 0.4, 1.0); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(client->program); glViewport(0, 0, client->width, client->height); glUniformMatrix4fv(client->rotation, 1, GL_FALSE, (GLfloat *) rotation); glVertexAttribPointer(POS, 2, GL_FLOAT, GL_FALSE, 0, verts); glVertexAttribPointer(COL, 3, GL_FLOAT, GL_FALSE, 0, colors); glEnableVertexAttribArray(POS); glEnableVertexAttribArray(COL); glDrawArrays(GL_TRIANGLES, 0, 3); glDisableVertexAttribArray(POS); glDisableVertexAttribArray(COL); glFlush(); } static void frame_callback(void *data, struct wl_callback *callback, uint32_t time); static const struct wl_callback_listener frame_listener = { frame_callback }; static void frame_callback(void *data, struct wl_callback *callback, uint32_t time) { struct nested_client *client = data; if (callback) wl_callback_destroy(callback); callback = wl_surface_frame(client->surface); wl_callback_add_listener(callback, &frame_listener, client); render_triangle(client, time); eglSwapBuffers(client->egl_display, client->egl_surface); } static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct nested_client *client = data; if (strcmp(interface, "wl_compositor") == 0) { client->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static struct nested_client * nested_client_create(void) { static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; static const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 1, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; EGLint major, minor, n; EGLBoolean ret; struct nested_client *client; client = malloc(sizeof *client); if (client == NULL) return NULL; client->width = 250; client->height = 250; client->display = wl_display_connect(NULL); client->registry = wl_display_get_registry(client->display); wl_registry_add_listener(client->registry, ®istry_listener, client); /* get globals */ wl_display_roundtrip(client->display); client->egl_display = weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, client->display, NULL); if (client->egl_display == NULL) return NULL; ret = eglInitialize(client->egl_display, &major, &minor); if (!ret) return NULL; ret = eglBindAPI(EGL_OPENGL_ES_API); if (!ret) return NULL; ret = eglChooseConfig(client->egl_display, config_attribs, &client->egl_config, 1, &n); if (!ret || n != 1) return NULL; client->egl_context = eglCreateContext(client->egl_display, client->egl_config, EGL_NO_CONTEXT, context_attribs); if (!client->egl_context) return NULL; client->surface = wl_compositor_create_surface(client->compositor); client->native = wl_egl_window_create(client->surface, client->width, client->height); client->egl_surface = weston_platform_create_egl_surface(client->egl_display, client->egl_config, client->native, NULL); eglMakeCurrent(client->egl_display, client->egl_surface, client->egl_surface, client->egl_context); wl_egl_window_resize(client->native, client->width, client->height, 0, 0); frame_callback(client, NULL, 0); return client; } static void nested_client_destroy(struct nested_client *client) { eglMakeCurrent(client->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); weston_platform_destroy_egl_surface(client->egl_display, client->egl_surface); wl_egl_window_destroy(client->native); wl_surface_destroy(client->surface); if (client->compositor) wl_compositor_destroy(client->compositor); wl_registry_destroy(client->registry); eglTerminate(client->egl_display); eglReleaseThread(); wl_display_flush(client->display); wl_display_disconnect(client->display); } int main(int argc, char **argv) { struct nested_client *client; int ret = 0; if (getenv("WAYLAND_SOCKET") == NULL) { fprintf(stderr, "must be run by nested, don't run standalone\n"); return EXIT_FAILURE; } client = nested_client_create(); if (client == NULL) return EXIT_FAILURE; while (ret != -1) ret = wl_display_dispatch(client->display); nested_client_destroy(client); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.318963 weston-8.0.0/clients/nested.c0000644000175000017460000007160300000000000016415 0ustar00simonwheel00000000000000/* * Copyright © 2013 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define WL_HIDE_DEPRECATED #include #include "shared/helpers.h" #include "shared/xalloc.h" #include "window.h" #include "shared/weston-egl-ext.h" static bool option_blit; struct nested { struct display *display; struct window *window; struct widget *widget; struct wl_display *child_display; struct task child_task; EGLDisplay egl_display; struct program *texture_program; struct wl_list surface_list; const struct nested_renderer *renderer; }; struct nested_region { struct wl_resource *resource; pixman_region32_t region; }; struct nested_buffer_reference { struct nested_buffer *buffer; struct wl_listener destroy_listener; }; struct nested_buffer { struct wl_resource *resource; struct wl_signal destroy_signal; struct wl_listener destroy_listener; uint32_t busy_count; /* A buffer in the parent compositor representing the same * data. This is created on-demand when the subsurface * renderer is used */ struct wl_buffer *parent_buffer; /* This reference is used to mark when the parent buffer has * been attached to the subsurface. It will be unrefenced when * we receive a buffer release event. That way we won't inform * the client that the buffer is free until the parent * compositor is also finished with it */ struct nested_buffer_reference parent_ref; }; struct nested_surface { struct wl_resource *resource; struct nested *nested; EGLImageKHR *image; struct wl_list link; struct wl_list frame_callback_list; struct { /* wl_surface.attach */ int newly_attached; struct nested_buffer *buffer; struct wl_listener buffer_destroy_listener; /* wl_surface.frame */ struct wl_list frame_callback_list; /* wl_surface.damage */ pixman_region32_t damage; } pending; void *renderer_data; }; /* Data used for the blit renderer */ struct nested_blit_surface { struct nested_buffer_reference buffer_ref; GLuint texture; cairo_surface_t *cairo_surface; }; /* Data used for the subsurface renderer */ struct nested_ss_surface { struct widget *widget; struct wl_surface *surface; struct wl_subsurface *subsurface; struct wl_callback *frame_callback; }; struct nested_frame_callback { struct wl_resource *resource; struct wl_list link; }; struct nested_renderer { void (* surface_init)(struct nested_surface *surface); void (* surface_fini)(struct nested_surface *surface); void (* render_clients)(struct nested *nested, cairo_t *cr); void (* surface_attach)(struct nested_surface *surface, struct nested_buffer *buffer); }; static const struct weston_option nested_options[] = { { WESTON_OPTION_BOOLEAN, "blit", 'b', &option_blit }, }; static const struct nested_renderer nested_blit_renderer; static const struct nested_renderer nested_ss_renderer; static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; static PFNEGLCREATEIMAGEKHRPROC create_image; static PFNEGLDESTROYIMAGEKHRPROC destroy_image; static PFNEGLBINDWAYLANDDISPLAYWL bind_display; static PFNEGLUNBINDWAYLANDDISPLAYWL unbind_display; static PFNEGLQUERYWAYLANDBUFFERWL query_buffer; static PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL create_wayland_buffer_from_image; static void nested_buffer_destroy_handler(struct wl_listener *listener, void *data) { struct nested_buffer *buffer = container_of(listener, struct nested_buffer, destroy_listener); wl_signal_emit(&buffer->destroy_signal, buffer); if (buffer->parent_buffer) wl_buffer_destroy(buffer->parent_buffer); free(buffer); } static struct nested_buffer * nested_buffer_from_resource(struct wl_resource *resource) { struct nested_buffer *buffer; struct wl_listener *listener; listener = wl_resource_get_destroy_listener(resource, nested_buffer_destroy_handler); if (listener) return container_of(listener, struct nested_buffer, destroy_listener); buffer = zalloc(sizeof *buffer); if (buffer == NULL) return NULL; buffer->resource = resource; wl_signal_init(&buffer->destroy_signal); buffer->destroy_listener.notify = nested_buffer_destroy_handler; wl_resource_add_destroy_listener(resource, &buffer->destroy_listener); return buffer; } static void nested_buffer_reference_handle_destroy(struct wl_listener *listener, void *data) { struct nested_buffer_reference *ref = container_of(listener, struct nested_buffer_reference, destroy_listener); assert((struct nested_buffer *)data == ref->buffer); ref->buffer = NULL; } static void nested_buffer_reference(struct nested_buffer_reference *ref, struct nested_buffer *buffer) { if (buffer == ref->buffer) return; if (ref->buffer) { ref->buffer->busy_count--; if (ref->buffer->busy_count == 0) { assert(wl_resource_get_client(ref->buffer->resource)); wl_buffer_send_release(ref->buffer->resource); } wl_list_remove(&ref->destroy_listener.link); } if (buffer) { buffer->busy_count++; wl_signal_add(&buffer->destroy_signal, &ref->destroy_listener); ref->destroy_listener.notify = nested_buffer_reference_handle_destroy; } ref->buffer = buffer; } static void flush_surface_frame_callback_list(struct nested_surface *surface, uint32_t time) { struct nested_frame_callback *nc, *next; wl_list_for_each_safe(nc, next, &surface->frame_callback_list, link) { wl_callback_send_done(nc->resource, time); wl_resource_destroy(nc->resource); } wl_list_init(&surface->frame_callback_list); /* FIXME: toytoolkit need a pre-block handler where we can * call this. */ wl_display_flush_clients(surface->nested->child_display); } static void redraw_handler(struct widget *widget, void *data) { struct nested *nested = data; cairo_surface_t *surface; cairo_t *cr; struct rectangle allocation; widget_get_allocation(nested->widget, &allocation); surface = window_get_surface(nested->window); cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_set_source_rgba(cr, 0, 0, 0, 0.8); cairo_fill(cr); nested->renderer->render_clients(nested, cr); cairo_destroy(cr); cairo_surface_destroy(surface); } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { struct nested *nested = data; window_schedule_redraw(nested->window); } static void handle_child_data(struct task *task, uint32_t events) { struct nested *nested = container_of(task, struct nested, child_task); struct wl_event_loop *loop; loop = wl_display_get_event_loop(nested->child_display); wl_event_loop_dispatch(loop, -1); wl_display_flush_clients(nested->child_display); } struct nested_client { struct wl_client *client; pid_t pid; }; static struct nested_client * launch_client(struct nested *nested, const char *path) { int sv[2]; pid_t pid; struct nested_client *client; client = malloc(sizeof *client); if (client == NULL) return NULL; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { fprintf(stderr, "launch_client: " "socketpair failed while launching '%s': %s\n", path, strerror(errno)); free(client); return NULL; } pid = fork(); if (pid == -1) { close(sv[0]); close(sv[1]); free(client); fprintf(stderr, "launch_client: " "fork failed while launching '%s': %s\n", path, strerror(errno)); return NULL; } if (pid == 0) { int clientfd; char s[32]; /* SOCK_CLOEXEC closes both ends, so we dup the fd to * get a non-CLOEXEC fd to pass through exec. */ clientfd = dup(sv[1]); if (clientfd == -1) { fprintf(stderr, "compositor: dup failed: %s\n", strerror(errno)); exit(-1); } snprintf(s, sizeof s, "%d", clientfd); setenv("WAYLAND_SOCKET", s, 1); execl(path, path, NULL); fprintf(stderr, "compositor: executing '%s' failed: %s\n", path, strerror(errno)); exit(-1); } close(sv[1]); client->client = wl_client_create(nested->child_display, sv[0]); if (!client->client) { close(sv[0]); free(client); fprintf(stderr, "launch_client: " "wl_client_create failed while launching '%s'.\n", path); return NULL; } client->pid = pid; return client; } static void destroy_surface(struct wl_resource *resource) { struct nested_surface *surface = wl_resource_get_user_data(resource); struct nested *nested = surface->nested; struct nested_frame_callback *cb, *next; wl_list_for_each_safe(cb, next, &surface->frame_callback_list, link) wl_resource_destroy(cb->resource); wl_list_for_each_safe(cb, next, &surface->pending.frame_callback_list, link) wl_resource_destroy(cb->resource); pixman_region32_fini(&surface->pending.damage); nested->renderer->surface_fini(surface); wl_list_remove(&surface->link); free(surface); } static void surface_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void surface_attach(struct wl_client *client, struct wl_resource *resource, struct wl_resource *buffer_resource, int32_t sx, int32_t sy) { struct nested_surface *surface = wl_resource_get_user_data(resource); struct nested *nested = surface->nested; struct nested_buffer *buffer = NULL; if (buffer_resource) { int format; if (!query_buffer(nested->egl_display, (void *) buffer_resource, EGL_TEXTURE_FORMAT, &format)) { wl_resource_post_error(buffer_resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "attaching non-egl wl_buffer"); return; } switch (format) { case EGL_TEXTURE_RGB: case EGL_TEXTURE_RGBA: break; default: wl_resource_post_error(buffer_resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "invalid format"); return; } buffer = nested_buffer_from_resource(buffer_resource); if (buffer == NULL) { wl_client_post_no_memory(client); return; } } if (surface->pending.buffer) wl_list_remove(&surface->pending.buffer_destroy_listener.link); surface->pending.buffer = buffer; surface->pending.newly_attached = 1; if (buffer) { wl_signal_add(&buffer->destroy_signal, &surface->pending.buffer_destroy_listener); } } static void nested_surface_attach(struct nested_surface *surface, struct nested_buffer *buffer) { struct nested *nested = surface->nested; if (surface->image != EGL_NO_IMAGE_KHR) destroy_image(nested->egl_display, surface->image); surface->image = create_image(nested->egl_display, NULL, EGL_WAYLAND_BUFFER_WL, buffer->resource, NULL); if (surface->image == EGL_NO_IMAGE_KHR) { fprintf(stderr, "failed to create img\n"); return; } nested->renderer->surface_attach(surface, buffer); } static void surface_damage(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) { struct nested_surface *surface = wl_resource_get_user_data(resource); pixman_region32_union_rect(&surface->pending.damage, &surface->pending.damage, x, y, width, height); } static void destroy_frame_callback(struct wl_resource *resource) { struct nested_frame_callback *callback = wl_resource_get_user_data(resource); wl_list_remove(&callback->link); free(callback); } static void surface_frame(struct wl_client *client, struct wl_resource *resource, uint32_t id) { struct nested_frame_callback *callback; struct nested_surface *surface = wl_resource_get_user_data(resource); callback = malloc(sizeof *callback); if (callback == NULL) { wl_resource_post_no_memory(resource); return; } callback->resource = wl_resource_create(client, &wl_callback_interface, 1, id); wl_resource_set_implementation(callback->resource, NULL, callback, destroy_frame_callback); wl_list_insert(surface->pending.frame_callback_list.prev, &callback->link); } static void surface_set_opaque_region(struct wl_client *client, struct wl_resource *resource, struct wl_resource *region_resource) { fprintf(stderr, "surface_set_opaque_region\n"); } static void surface_set_input_region(struct wl_client *client, struct wl_resource *resource, struct wl_resource *region_resource) { fprintf(stderr, "surface_set_input_region\n"); } static void surface_commit(struct wl_client *client, struct wl_resource *resource) { struct nested_surface *surface = wl_resource_get_user_data(resource); struct nested *nested = surface->nested; /* wl_surface.attach */ if (surface->pending.newly_attached) nested_surface_attach(surface, surface->pending.buffer); if (surface->pending.buffer) { wl_list_remove(&surface->pending.buffer_destroy_listener.link); surface->pending.buffer = NULL; } surface->pending.newly_attached = 0; /* wl_surface.damage */ pixman_region32_clear(&surface->pending.damage); /* wl_surface.frame */ wl_list_insert_list(&surface->frame_callback_list, &surface->pending.frame_callback_list); wl_list_init(&surface->pending.frame_callback_list); /* FIXME: For the subsurface renderer we don't need to * actually redraw the window. However we do want to cause a * commit because the subsurface is synchronized. Ideally we * would just queue the commit */ window_schedule_redraw(nested->window); } static void surface_set_buffer_transform(struct wl_client *client, struct wl_resource *resource, int transform) { fprintf(stderr, "surface_set_buffer_transform\n"); } static const struct wl_surface_interface surface_interface = { surface_destroy, surface_attach, surface_damage, surface_frame, surface_set_opaque_region, surface_set_input_region, surface_commit, surface_set_buffer_transform }; static void surface_handle_pending_buffer_destroy(struct wl_listener *listener, void *data) { struct nested_surface *surface = container_of(listener, struct nested_surface, pending.buffer_destroy_listener); surface->pending.buffer = NULL; } static void compositor_create_surface(struct wl_client *client, struct wl_resource *resource, uint32_t id) { struct nested *nested = wl_resource_get_user_data(resource); struct nested_surface *surface; surface = zalloc(sizeof *surface); if (surface == NULL) { wl_resource_post_no_memory(resource); return; } surface->nested = nested; wl_list_init(&surface->frame_callback_list); wl_list_init(&surface->pending.frame_callback_list); surface->pending.buffer_destroy_listener.notify = surface_handle_pending_buffer_destroy; pixman_region32_init(&surface->pending.damage); display_acquire_window_surface(nested->display, nested->window, NULL); nested->renderer->surface_init(surface); display_release_window_surface(nested->display, nested->window); surface->resource = wl_resource_create(client, &wl_surface_interface, 1, id); wl_resource_set_implementation(surface->resource, &surface_interface, surface, destroy_surface); wl_list_insert(nested->surface_list.prev, &surface->link); } static void destroy_region(struct wl_resource *resource) { struct nested_region *region = wl_resource_get_user_data(resource); pixman_region32_fini(®ion->region); free(region); } static void region_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void region_add(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) { struct nested_region *region = wl_resource_get_user_data(resource); pixman_region32_union_rect(®ion->region, ®ion->region, x, y, width, height); } static void region_subtract(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) { struct nested_region *region = wl_resource_get_user_data(resource); pixman_region32_t rect; pixman_region32_init_rect(&rect, x, y, width, height); pixman_region32_subtract(®ion->region, ®ion->region, &rect); pixman_region32_fini(&rect); } static const struct wl_region_interface region_interface = { region_destroy, region_add, region_subtract }; static void compositor_create_region(struct wl_client *client, struct wl_resource *resource, uint32_t id) { struct nested_region *region; region = malloc(sizeof *region); if (region == NULL) { wl_resource_post_no_memory(resource); return; } pixman_region32_init(®ion->region); region->resource = wl_resource_create(client, &wl_region_interface, 1, id); wl_resource_set_implementation(region->resource, ®ion_interface, region, destroy_region); } static const struct wl_compositor_interface compositor_interface = { compositor_create_surface, compositor_create_region }; static void compositor_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct nested *nested = data; struct wl_resource *resource; resource = wl_resource_create(client, &wl_compositor_interface, MIN(version, 3), id); wl_resource_set_implementation(resource, &compositor_interface, nested, NULL); } static int nested_init_compositor(struct nested *nested) { const char *extensions; struct wl_event_loop *loop; int use_ss_renderer = 0; int fd, ret; wl_list_init(&nested->surface_list); nested->child_display = wl_display_create(); loop = wl_display_get_event_loop(nested->child_display); fd = wl_event_loop_get_fd(loop); nested->child_task.run = handle_child_data; display_watch_fd(nested->display, fd, EPOLLIN, &nested->child_task); if (!wl_global_create(nested->child_display, &wl_compositor_interface, 1, nested, compositor_bind)) return -1; wl_display_init_shm(nested->child_display); nested->egl_display = display_get_egl_display(nested->display); extensions = eglQueryString(nested->egl_display, EGL_EXTENSIONS); if (!weston_check_egl_extension(extensions, "EGL_WL_bind_wayland_display")) { fprintf(stderr, "no EGL_WL_bind_wayland_display extension\n"); return -1; } bind_display = (void *) eglGetProcAddress("eglBindWaylandDisplayWL"); unbind_display = (void *) eglGetProcAddress("eglUnbindWaylandDisplayWL"); create_image = (void *) eglGetProcAddress("eglCreateImageKHR"); destroy_image = (void *) eglGetProcAddress("eglDestroyImageKHR"); query_buffer = (void *) eglGetProcAddress("eglQueryWaylandBufferWL"); image_target_texture_2d = (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); ret = bind_display(nested->egl_display, nested->child_display); if (!ret) { fprintf(stderr, "failed to bind wl_display\n"); return -1; } if (display_has_subcompositor(nested->display)) { const char *func = "eglCreateWaylandBufferFromImageWL"; const char *ext = "EGL_WL_create_wayland_buffer_from_image"; if (weston_check_egl_extension(extensions, ext)) { create_wayland_buffer_from_image = (void *) eglGetProcAddress(func); use_ss_renderer = 1; } } if (option_blit) use_ss_renderer = 0; if (use_ss_renderer) { printf("Using subsurfaces to render client surfaces\n"); nested->renderer = &nested_ss_renderer; } else { printf("Using local compositing with blits to " "render client surfaces\n"); nested->renderer = &nested_blit_renderer; } return 0; } static struct nested * nested_create(struct display *display) { struct nested *nested; nested = zalloc(sizeof *nested); if (nested == NULL) return nested; nested->window = window_create(display); nested->widget = window_frame_create(nested->window, nested); window_set_title(nested->window, "Wayland Nested"); nested->display = display; window_set_user_data(nested->window, nested); widget_set_redraw_handler(nested->widget, redraw_handler); window_set_keyboard_focus_handler(nested->window, keyboard_focus_handler); nested_init_compositor(nested); widget_schedule_resize(nested->widget, 400, 400); return nested; } static void nested_destroy(struct nested *nested) { widget_destroy(nested->widget); window_destroy(nested->window); free(nested); } /*** blit renderer ***/ static void blit_surface_init(struct nested_surface *surface) { struct nested_blit_surface *blit_surface = xzalloc(sizeof *blit_surface); glGenTextures(1, &blit_surface->texture); glBindTexture(GL_TEXTURE_2D, blit_surface->texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); surface->renderer_data = blit_surface; } static void blit_surface_fini(struct nested_surface *surface) { struct nested_blit_surface *blit_surface = surface->renderer_data; nested_buffer_reference(&blit_surface->buffer_ref, NULL); glDeleteTextures(1, &blit_surface->texture); free(blit_surface); } static void blit_frame_callback(void *data, struct wl_callback *callback, uint32_t time) { struct nested *nested = data; struct nested_surface *surface; wl_list_for_each(surface, &nested->surface_list, link) flush_surface_frame_callback_list(surface, time); if (callback) wl_callback_destroy(callback); } static const struct wl_callback_listener blit_frame_listener = { blit_frame_callback }; static void blit_render_clients(struct nested *nested, cairo_t *cr) { struct nested_surface *s; struct rectangle allocation; struct wl_callback *callback; widget_get_allocation(nested->widget, &allocation); wl_list_for_each(s, &nested->surface_list, link) { struct nested_blit_surface *blit_surface = s->renderer_data; display_acquire_window_surface(nested->display, nested->window, NULL); glBindTexture(GL_TEXTURE_2D, blit_surface->texture); image_target_texture_2d(GL_TEXTURE_2D, s->image); display_release_window_surface(nested->display, nested->window); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); cairo_set_source_surface(cr, blit_surface->cairo_surface, allocation.x + 10, allocation.y + 10); cairo_rectangle(cr, allocation.x + 10, allocation.y + 10, allocation.width - 10, allocation.height - 10); cairo_fill(cr); } callback = wl_surface_frame(window_get_wl_surface(nested->window)); wl_callback_add_listener(callback, &blit_frame_listener, nested); } static void blit_surface_attach(struct nested_surface *surface, struct nested_buffer *buffer) { struct nested *nested = surface->nested; struct nested_blit_surface *blit_surface = surface->renderer_data; EGLint width, height; cairo_device_t *device; nested_buffer_reference(&blit_surface->buffer_ref, buffer); if (blit_surface->cairo_surface) cairo_surface_destroy(blit_surface->cairo_surface); query_buffer(nested->egl_display, (void *) buffer->resource, EGL_WIDTH, &width); query_buffer(nested->egl_display, (void *) buffer->resource, EGL_HEIGHT, &height); device = display_get_cairo_device(nested->display); blit_surface->cairo_surface = cairo_gl_surface_create_for_texture(device, CAIRO_CONTENT_COLOR_ALPHA, blit_surface->texture, width, height); } static const struct nested_renderer nested_blit_renderer = { .surface_init = blit_surface_init, .surface_fini = blit_surface_fini, .render_clients = blit_render_clients, .surface_attach = blit_surface_attach }; /*** subsurface renderer ***/ static void ss_surface_init(struct nested_surface *surface) { struct nested *nested = surface->nested; struct wl_compositor *compositor = display_get_compositor(nested->display); struct nested_ss_surface *ss_surface = xzalloc(sizeof *ss_surface); struct rectangle allocation; struct wl_region *region; ss_surface->widget = window_add_subsurface(nested->window, nested, SUBSURFACE_SYNCHRONIZED); widget_set_use_cairo(ss_surface->widget, 0); ss_surface->surface = widget_get_wl_surface(ss_surface->widget); ss_surface->subsurface = widget_get_wl_subsurface(ss_surface->widget); /* The toy toolkit gets confused about the pointer position * when it gets motion events for a subsurface so we'll just * disable input on it */ region = wl_compositor_create_region(compositor); wl_surface_set_input_region(ss_surface->surface, region); wl_region_destroy(region); widget_get_allocation(nested->widget, &allocation); wl_subsurface_set_position(ss_surface->subsurface, allocation.x + 10, allocation.y + 10); surface->renderer_data = ss_surface; } static void ss_surface_fini(struct nested_surface *surface) { struct nested_ss_surface *ss_surface = surface->renderer_data; widget_destroy(ss_surface->widget); if (ss_surface->frame_callback) wl_callback_destroy(ss_surface->frame_callback); free(ss_surface); } static void ss_render_clients(struct nested *nested, cairo_t *cr) { /* The clients are composited by the parent compositor so we * don't need to do anything here */ } static void ss_buffer_release(void *data, struct wl_buffer *wl_buffer) { struct nested_buffer *buffer = data; nested_buffer_reference(&buffer->parent_ref, NULL); } static struct wl_buffer_listener ss_buffer_listener = { ss_buffer_release }; static void ss_frame_callback(void *data, struct wl_callback *callback, uint32_t time) { struct nested_surface *surface = data; struct nested_ss_surface *ss_surface = surface->renderer_data; flush_surface_frame_callback_list(surface, time); if (callback) wl_callback_destroy(callback); ss_surface->frame_callback = NULL; } static const struct wl_callback_listener ss_frame_listener = { ss_frame_callback }; static void ss_surface_attach(struct nested_surface *surface, struct nested_buffer *buffer) { struct nested *nested = surface->nested; struct nested_ss_surface *ss_surface = surface->renderer_data; struct wl_buffer *parent_buffer; const pixman_box32_t *rects; int n_rects, i; if (buffer) { /* Create a representation of the buffer in the parent * compositor if we haven't already */ if (buffer->parent_buffer == NULL) { EGLDisplay *edpy = nested->egl_display; EGLImageKHR image = surface->image; buffer->parent_buffer = create_wayland_buffer_from_image(edpy, image); wl_buffer_add_listener(buffer->parent_buffer, &ss_buffer_listener, buffer); } parent_buffer = buffer->parent_buffer; /* We'll take a reference to the buffer while the parent * compositor is using it so that we won't report the release * event until the parent has also finished with it */ nested_buffer_reference(&buffer->parent_ref, buffer); } else { parent_buffer = NULL; } wl_surface_attach(ss_surface->surface, parent_buffer, 0, 0); rects = pixman_region32_rectangles(&surface->pending.damage, &n_rects); for (i = 0; i < n_rects; i++) { const pixman_box32_t *rect = rects + i; wl_surface_damage(ss_surface->surface, rect->x1, rect->y1, rect->x2 - rect->x1, rect->y2 - rect->y1); } if (ss_surface->frame_callback) wl_callback_destroy(ss_surface->frame_callback); ss_surface->frame_callback = wl_surface_frame(ss_surface->surface); wl_callback_add_listener(ss_surface->frame_callback, &ss_frame_listener, surface); wl_surface_commit(ss_surface->surface); } static const struct nested_renderer nested_ss_renderer = { .surface_init = ss_surface_init, .surface_fini = ss_surface_fini, .render_clients = ss_render_clients, .surface_attach = ss_surface_attach }; int main(int argc, char *argv[]) { struct display *display; struct nested *nested; if (parse_options(nested_options, ARRAY_LENGTH(nested_options), &argc, argv) > 1) { printf("Usage: %s [OPTIONS]\n --blit or -b\n", argv[0]); exit(1); } display = display_create(&argc, argv); if (display == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } nested = nested_create(display); launch_client(nested, "weston-nested-client"); display_run(display); nested_destroy(nested); display_destroy(display); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.318963 weston-8.0.0/clients/presentation-shm.c0000644000175000017460000005445400000000000020440 0ustar00simonwheel00000000000000/* * Copyright © 2011 Benjamin Franzke * Copyright © 2010 Intel Corporation * Copyright © 2014 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "shared/helpers.h" #include #include "shared/timespec-util.h" #include "shared/os-compatibility.h" #include "presentation-time-client-protocol.h" #include "xdg-shell-client-protocol.h" enum run_mode { RUN_MODE_FEEDBACK, RUN_MODE_FEEDBACK_IDLE, RUN_MODE_PRESENT, }; static const char * const run_mode_name[] = { [RUN_MODE_FEEDBACK] = "feedback", [RUN_MODE_FEEDBACK_IDLE] = "feedback-idle", [RUN_MODE_PRESENT] = "low-lat present", }; struct output { struct wl_output *output; uint32_t name; struct wl_list link; }; struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct xdg_wm_base *wm_base; struct wl_shm *shm; uint32_t formats; struct wp_presentation *presentation; clockid_t clk_id; struct wl_list output_list; /* struct output::link */ }; struct feedback { struct window *window; unsigned frame_no; struct wp_presentation_feedback *feedback; struct timespec commit; struct timespec target; uint32_t frame_stamp; struct wl_list link; struct timespec present; }; struct buffer { struct wl_buffer *buffer; void *shm_data; int busy; }; struct window { struct display *display; int width, height; enum run_mode mode; struct wl_surface *surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; uint32_t configure_serial; struct buffer *buffers; int num_buffers; int next; int refresh_nsec; int commit_delay_msecs; struct wl_callback *callback; struct wl_list feedback_list; struct feedback *received_feedback; }; #define NSEC_PER_SEC 1000000000 static void buffer_release(void *data, struct wl_buffer *buffer) { struct buffer *mybuf = data; mybuf->busy = 0; } static const struct wl_buffer_listener buffer_listener = { buffer_release }; static int create_shm_buffers(struct display *display, struct buffer **buffers, int num_buffers, int width, int height, uint32_t format) { struct buffer *bufs; struct wl_shm_pool *pool; int fd, size, stride, offset; void *data; int i; stride = width * 4; size = stride * height * num_buffers; fd = os_create_anonymous_file(size); if (fd < 0) { fprintf(stderr, "creating a buffer file for %d B failed: %s\n", size, strerror(errno)); return -1; } data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return -1; } pool = wl_shm_create_pool(display->shm, fd, size); offset = 0; bufs = calloc(num_buffers, sizeof(*bufs)); assert(bufs); for (i = 0; i < num_buffers; i++) { bufs[i].buffer = wl_shm_pool_create_buffer(pool, offset, width, height, stride, format); assert(bufs[i].buffer); wl_buffer_add_listener(bufs[i].buffer, &buffer_listener, &bufs[i]); bufs[i].shm_data = (char *)data + offset; offset += stride * height; } wl_shm_pool_destroy(pool); close(fd); *buffers = bufs; return 0; } static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); } static const struct xdg_wm_base_listener xdg_wm_base_listener = { .ping = xdg_wm_base_handle_ping, }; static void xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { struct window *window = data; window->configure_serial = serial; } static const struct xdg_surface_listener xdg_surface_listener = { .configure = xdg_surface_handle_configure, }; static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) { /* noop */ } static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) { fprintf(stderr, "presentation-shm exiting\n"); exit(0); } static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = xdg_toplevel_handle_configure, .close = xdg_toplevel_handle_close, }; static struct window * create_window(struct display *display, int width, int height, enum run_mode mode, int commit_delay_msecs) { struct window *window; char title[128]; int ret; snprintf(title, sizeof(title), "presentation-shm: %s [Delay %i msecs]", run_mode_name[mode], commit_delay_msecs); window = zalloc(sizeof *window); if (!window) return NULL; window->commit_delay_msecs = commit_delay_msecs; window->mode = mode; window->callback = NULL; wl_list_init(&window->feedback_list); window->display = display; window->width = width; window->height = height; window->surface = wl_compositor_create_surface(display->compositor); window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); if (!window->xdg_surface) return NULL; window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); if (!window->xdg_toplevel) return NULL; xdg_wm_base_add_listener(display->wm_base, &xdg_wm_base_listener, NULL); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); xdg_toplevel_set_title(window->xdg_toplevel, title); xdg_toplevel_set_min_size(window->xdg_toplevel, width, height); xdg_toplevel_set_max_size(window->xdg_toplevel, width, height); wl_surface_commit(window->surface); wl_display_roundtrip(window->display->display); window->num_buffers = 60; window->refresh_nsec = NSEC_PER_SEC / 60; /* 60 Hz guess */ window->next = 0; ret = create_shm_buffers(window->display, &window->buffers, window->num_buffers, window->width, window->height, WL_SHM_FORMAT_XRGB8888); assert(ret == 0); return window; } static void destroy_feedback(struct feedback *feedback) { if (feedback->feedback) wp_presentation_feedback_destroy(feedback->feedback); wl_list_remove(&feedback->link); free(feedback); } static void destroy_window(struct window *window) { int i; while (!wl_list_empty(&window->feedback_list)) { struct feedback *f; f = wl_container_of(window->feedback_list.next, f, link); printf("clean up feedback %u\n", f->frame_no); destroy_feedback(f); } if (window->callback) wl_callback_destroy(window->callback); xdg_surface_destroy(window->xdg_surface); wl_surface_destroy(window->surface); for (i = 0; i < window->num_buffers; i++) wl_buffer_destroy(window->buffers[i].buffer); /* munmap(window->buffers[0].shm_data, size); */ free(window->buffers); free(window); } static struct buffer * window_next_buffer(struct window *window) { struct buffer *buf = &window->buffers[window->next]; window->next = (window->next + 1) % window->num_buffers; return buf; } static void paint_pixels(void *image, int width, int height, uint32_t phase) { const int halfh = height / 2; const int halfw = width / 2; uint32_t *pixel = image; int y, or; double ang = M_PI * 2.0 / 1000000.0 * phase; double s = sin(ang); double c = cos(ang); /* squared radii thresholds */ or = (halfw < halfh ? halfw : halfh) - 16; or *= or; for (y = 0; y < height; y++) { int x; int oy = y - halfh; int y2 = oy * oy; for (x = 0; x < width; x++) { int ox = x - halfw; uint32_t v = 0xff000000; double rx, ry; if (ox * ox + y2 > or) { if (ox * oy > 0) *pixel++ = 0xff000000; else *pixel++ = 0xffffffff; continue; } rx = c * ox + s * oy; ry = -s * ox + c * oy; if (rx < 0.0) v |= 0x00ff0000; if (ry < 0.0) v |= 0x0000ff00; if ((rx < 0.0) == (ry < 0.0)) v |= 0x000000ff; *pixel++ = v; } } } static void feedback_sync_output(void *data, struct wp_presentation_feedback *presentation_feedback, struct wl_output *output) { /* not interested */ } static char * pflags_to_str(uint32_t flags, char *str, unsigned len) { static const struct { uint32_t flag; char sym; } desc[] = { { WP_PRESENTATION_FEEDBACK_KIND_VSYNC, 's' }, { WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK, 'c' }, { WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION, 'e' }, { WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY, 'z' }, }; unsigned i; *str = '\0'; if (len < ARRAY_LENGTH(desc) + 1) return str; for (i = 0; i < ARRAY_LENGTH(desc); i++) str[i] = flags & desc[i].flag ? desc[i].sym : '_'; str[ARRAY_LENGTH(desc)] = '\0'; return str; } static uint32_t timespec_to_ms(const struct timespec *ts) { return (uint32_t)ts->tv_sec * 1000 + ts->tv_nsec / 1000000; } static int timespec_diff_to_usec(const struct timespec *a, const struct timespec *b) { time_t secs = a->tv_sec - b->tv_sec; long nsec = a->tv_nsec - b->tv_nsec; return secs * 1000000 + nsec / 1000; } static void feedback_presented(void *data, struct wp_presentation_feedback *presentation_feedback, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, uint32_t refresh_nsec, uint32_t seq_hi, uint32_t seq_lo, uint32_t flags) { struct feedback *feedback = data; struct window *window = feedback->window; struct feedback *prev_feedback = window->received_feedback; uint64_t seq = ((uint64_t)seq_hi << 32) + seq_lo; const struct timespec *prevpresent; uint32_t commit, present; uint32_t f2c, c2p, f2p; int p2p, t2p; char flagstr[10]; timespec_from_proto(&feedback->present, tv_sec_hi, tv_sec_lo, tv_nsec); commit = timespec_to_ms(&feedback->commit); present = timespec_to_ms(&feedback->present); if (prev_feedback) prevpresent = &prev_feedback->present; else prevpresent = &feedback->present; f2c = commit - feedback->frame_stamp; c2p = present - commit; f2p = present - feedback->frame_stamp; p2p = timespec_diff_to_usec(&feedback->present, prevpresent); t2p = timespec_diff_to_usec(&feedback->present, &feedback->target); switch (window->mode) { case RUN_MODE_PRESENT: printf("%6u: c2p %4u ms, p2p %5d us, t2p %6d us, [%s] " "seq %" PRIu64 "\n", feedback->frame_no, c2p, p2p, t2p, pflags_to_str(flags, flagstr, sizeof(flagstr)), seq); break; case RUN_MODE_FEEDBACK: case RUN_MODE_FEEDBACK_IDLE: printf("%6u: f2c %2u ms, c2p %2u ms, f2p %2u ms, p2p %5d us, " "t2p %6d, [%s], seq %" PRIu64 "\n", feedback->frame_no, f2c, c2p, f2p, p2p, t2p, pflags_to_str(flags, flagstr, sizeof(flagstr)), seq); } if (window->received_feedback) destroy_feedback(window->received_feedback); window->received_feedback = feedback; } static void feedback_discarded(void *data, struct wp_presentation_feedback *presentation_feedback) { struct feedback *feedback = data; printf("discarded %u\n", feedback->frame_no); destroy_feedback(feedback); } static const struct wp_presentation_feedback_listener feedback_listener = { feedback_sync_output, feedback_presented, feedback_discarded }; static void window_emulate_rendering(struct window *window) { struct timespec delay; int ret; if (window->commit_delay_msecs <= 0) return; delay.tv_sec = window->commit_delay_msecs / 1000; delay.tv_nsec = (window->commit_delay_msecs % 1000) * 1000000; ret = nanosleep(&delay, NULL); if (ret) printf("nanosleep failed: %s\n", strerror(errno)); } static void window_create_feedback(struct window *window, uint32_t frame_stamp) { static unsigned seq; struct wp_presentation *pres = window->display->presentation; struct feedback *feedback; seq++; if (!pres) return; feedback = zalloc(sizeof *feedback); if (!feedback) return; feedback->window = window; feedback->feedback = wp_presentation_feedback(pres, window->surface); wp_presentation_feedback_add_listener(feedback->feedback, &feedback_listener, feedback); feedback->frame_no = seq; clock_gettime(window->display->clk_id, &feedback->commit); feedback->frame_stamp = frame_stamp; feedback->target = feedback->commit; wl_list_insert(&window->feedback_list, &feedback->link); } static void window_commit_next(struct window *window) { struct buffer *buffer; buffer = window_next_buffer(window); assert(buffer); if (window->configure_serial) { xdg_surface_ack_configure(window->xdg_surface, window->configure_serial); window->configure_serial = 0; } wl_surface_attach(window->surface, buffer->buffer, 0, 0); wl_surface_damage(window->surface, 0, 0, window->width, window->height); wl_surface_commit(window->surface); buffer->busy = 1; } static const struct wl_callback_listener frame_listener_mode_feedback; static void redraw_mode_feedback(void *data, struct wl_callback *callback, uint32_t time) { struct window *window = data; if (callback && window->mode == RUN_MODE_FEEDBACK_IDLE) sleep(1); if (callback) wl_callback_destroy(callback); window_emulate_rendering(window); window->callback = wl_surface_frame(window->surface); wl_callback_add_listener(window->callback, &frame_listener_mode_feedback, window); window_create_feedback(window, time); window_commit_next(window); } static const struct wl_callback_listener frame_listener_mode_feedback = { redraw_mode_feedback }; static const struct wp_presentation_feedback_listener feedkick_listener; static void window_feedkick(struct window *window) { struct wp_presentation *pres = window->display->presentation; struct wp_presentation_feedback *fback; fback = wp_presentation_feedback(pres, window->surface); wp_presentation_feedback_add_listener(fback, &feedkick_listener, window); } static void feedkick_presented(void *data, struct wp_presentation_feedback *presentation_feedback, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, uint32_t refresh_nsec, uint32_t seq_hi, uint32_t seq_lo, uint32_t flags) { struct window *window = data; wp_presentation_feedback_destroy(presentation_feedback); window->refresh_nsec = refresh_nsec; switch (window->mode) { case RUN_MODE_PRESENT: window_emulate_rendering(window); window_create_feedback(window, 0); window_feedkick(window); window_commit_next(window); break; case RUN_MODE_FEEDBACK: case RUN_MODE_FEEDBACK_IDLE: assert(0 && "bad mode"); } } static void feedkick_discarded(void *data, struct wp_presentation_feedback *presentation_feedback) { struct window *window = data; wp_presentation_feedback_destroy(presentation_feedback); switch (window->mode) { case RUN_MODE_PRESENT: window_emulate_rendering(window); window_create_feedback(window, 0); window_feedkick(window); window_commit_next(window); break; case RUN_MODE_FEEDBACK: case RUN_MODE_FEEDBACK_IDLE: assert(0 && "bad mode"); } } static const struct wp_presentation_feedback_listener feedkick_listener = { feedback_sync_output, feedkick_presented, feedkick_discarded }; static void firstdraw_mode_burst(struct window *window) { window_emulate_rendering(window); switch (window->mode) { case RUN_MODE_PRESENT: window_create_feedback(window, 0); break; case RUN_MODE_FEEDBACK: case RUN_MODE_FEEDBACK_IDLE: assert(0 && "bad mode"); } window_feedkick(window); window_commit_next(window); } static void window_prerender(struct window *window) { int i; int timefactor = 1000000 / window->num_buffers; for (i = 0; i < window->num_buffers; i++) { struct buffer *buf = &window->buffers[i]; if (buf->busy) fprintf(stderr, "wl_buffer id %u) busy\n", wl_proxy_get_id( (struct wl_proxy *)buf->buffer)); paint_pixels(buf->shm_data, window->width, window->height, i * timefactor); } } static void output_destroy(struct output *o) { wl_output_destroy(o->output); wl_list_remove(&o->link); free(o); } static void display_add_output(struct display *d, uint32_t name, uint32_t version) { struct output *o; o = zalloc(sizeof(*o)); assert(o); o->output = wl_registry_bind(d->registry, name, &wl_output_interface, 1); o->name = name; wl_list_insert(&d->output_list, &o->link); } static void presentation_clock_id(void *data, struct wp_presentation *presentation, uint32_t clk_id) { struct display *d = data; d->clk_id = clk_id; } static const struct wp_presentation_listener presentation_listener = { presentation_clock_id }; static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { struct display *d = data; d->formats |= (1 << format); } static const struct wl_shm_listener shm_listener = { shm_format }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct display *d = data; if (strcmp(interface, "wl_compositor") == 0) { d->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); } else if (strcmp(interface, "xdg_wm_base") == 0) { d->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, version); } else if (strcmp(interface, "wl_shm") == 0) { d->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); wl_shm_add_listener(d->shm, &shm_listener, d); } else if (strcmp(interface, "wl_output") == 0) { display_add_output(d, name, version); } else if (strcmp(interface, wp_presentation_interface.name) == 0) { d->presentation = wl_registry_bind(registry, name, &wp_presentation_interface, 1); wp_presentation_add_listener(d->presentation, &presentation_listener, d); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct display *d = data; struct output *output, *otmp; wl_list_for_each_safe(output, otmp, &d->output_list, link) { if (output->name != name) continue; output_destroy(output); } } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static struct display * create_display(void) { struct display *display; display = malloc(sizeof *display); if (display == NULL) { fprintf(stderr, "out of memory\n"); exit(1); } display->display = wl_display_connect(NULL); assert(display->display); display->formats = 0; display->clk_id = -1; wl_list_init(&display->output_list); display->registry = wl_display_get_registry(display->display); wl_registry_add_listener(display->registry, ®istry_listener, display); wl_display_roundtrip(display->display); if (display->shm == NULL) { fprintf(stderr, "No wl_shm global\n"); exit(1); } wl_display_roundtrip(display->display); if (!(display->formats & (1 << WL_SHM_FORMAT_XRGB8888))) { fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); exit(1); } wl_display_get_fd(display->display); return display; } static void destroy_display(struct display *display) { while (!wl_list_empty(&display->output_list)) { struct output *o; o = wl_container_of(display->output_list.next, o, link); output_destroy(o); } if (display->shm) wl_shm_destroy(display->shm); if (display->wm_base) xdg_wm_base_destroy(display->wm_base); if (display->compositor) wl_compositor_destroy(display->compositor); wl_registry_destroy(display->registry); wl_display_flush(display->display); wl_display_disconnect(display->display); free(display); } static int running = 1; static void signal_int(int signum) { running = 0; } static void usage(const char *prog, int exit_code) { fprintf(stderr, "Usage: %s [mode] [options]\n" "where 'mode' is one of\n" " -f\t\trun in feedback mode (default)\n" " -i\t\trun in feedback-idle mode; sleep 1s between frames\n" " -p\t\trun in low-latency presentation mode\n" "and 'options' may include\n" " -d msecs\temulate the time used for rendering by a delay \n" "\t\tof the given milliseconds before commit\n\n", prog); fprintf(stderr, "Printed timing statistics, depending on mode:\n" " commit sequence number\n" " f2c: time from frame callback timestamp to commit\n" " c2p: time from commit to presentation\n" " f2p: time from frame callback timestamp to presentation\n" " p2p: time from previous presentation to this one\n" " t2p: time from target timestamp to presentation\n" " seq: MSC\n"); exit(exit_code); } int main(int argc, char **argv) { struct sigaction sigint; struct display *display; struct window *window; int ret = 0; enum run_mode mode = RUN_MODE_FEEDBACK; int i; int commit_delay_msecs = 0; for (i = 1; i < argc; i++) { if (strcmp("-f", argv[i]) == 0) mode = RUN_MODE_FEEDBACK; else if (strcmp("-i", argv[i]) == 0) mode = RUN_MODE_FEEDBACK_IDLE; else if (strcmp("-p", argv[i]) == 0) mode = RUN_MODE_PRESENT; else if ((strcmp("-d", argv[i]) == 0) && (i + 1 < argc)) { i++; commit_delay_msecs = atoi(argv[i]); } else usage(argv[0], EXIT_FAILURE); } display = create_display(); window = create_window(display, 250, 250, mode, commit_delay_msecs); if (!window) return 1; sigint.sa_handler = signal_int; sigemptyset(&sigint.sa_mask); sigint.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sigint, NULL); window_prerender(window); switch (mode) { case RUN_MODE_FEEDBACK: case RUN_MODE_FEEDBACK_IDLE: redraw_mode_feedback(window, NULL, 0); break; case RUN_MODE_PRESENT: firstdraw_mode_burst(window); break; } while (running && ret != -1) ret = wl_display_dispatch(display->display); fprintf(stderr, "presentation-shm exiting\n"); destroy_window(window); destroy_display(display); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.318963 weston-8.0.0/clients/resizor.c0000644000175000017460000002630000000000000016622 0ustar00simonwheel00000000000000/* * Copyright © 2010 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "window.h" #include "shared/xalloc.h" struct spring { double current; double target; double previous; }; struct resizor { struct display *display; struct window *window; struct widget *widget; struct window *menu; struct spring width; struct spring height; struct wl_callback *frame_callback; bool pointer_locked; bool locked_frame_callback_registered; struct input *locked_input; float pointer_x; float pointer_y; }; static void spring_update(struct spring *spring) { double current, force; current = spring->current; force = (spring->target - current) / 20.0 + (spring->previous - current); spring->current = current + (current - spring->previous) + force; spring->previous = current; } static int spring_done(struct spring *spring) { return fabs(spring->previous - spring->target) < 0.1; } static const struct wl_callback_listener listener; static void frame_callback(void *data, struct wl_callback *callback, uint32_t time) { struct resizor *resizor = data; assert(!callback || callback == resizor->frame_callback); if (resizor->frame_callback) { wl_callback_destroy(resizor->frame_callback); resizor->frame_callback = NULL; } if (window_is_maximized(resizor->window)) return; spring_update(&resizor->width); spring_update(&resizor->height); widget_schedule_resize(resizor->widget, resizor->width.current + 0.5, resizor->height.current + 0.5); if (!spring_done(&resizor->width) || !spring_done(&resizor->height)) { resizor->frame_callback = wl_surface_frame( window_get_wl_surface(resizor->window)); wl_callback_add_listener(resizor->frame_callback, &listener, resizor); } } static const struct wl_callback_listener listener = { frame_callback }; static void redraw_handler(struct widget *widget, void *data) { struct resizor *resizor = data; cairo_surface_t *surface; cairo_t *cr; struct rectangle allocation; widget_get_allocation(resizor->widget, &allocation); surface = window_get_surface(resizor->window); cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_set_source_rgba(cr, 0, 0, 0, 0.8); cairo_fill(cr); cairo_destroy(cr); cairo_surface_destroy(surface); } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { struct resizor *resizor = data; window_schedule_redraw(resizor->window); } static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data) { struct resizor *resizor = data; struct rectangle allocation; if (state == WL_KEYBOARD_KEY_STATE_RELEASED) return; window_get_allocation(resizor->window, &allocation); resizor->width.current = allocation.width; if (spring_done(&resizor->width)) resizor->width.target = allocation.width; resizor->height.current = allocation.height; if (spring_done(&resizor->height)) resizor->height.target = allocation.height; switch (sym) { case XKB_KEY_Up: if (allocation.height < 400) break; resizor->height.target = allocation.height - 200; break; case XKB_KEY_Down: if (allocation.height > 1000) break; resizor->height.target = allocation.height + 200; break; case XKB_KEY_Left: if (allocation.width < 400) break; resizor->width.target = allocation.width - 200; break; case XKB_KEY_Right: if (allocation.width > 1000) break; resizor->width.target = allocation.width + 200; break; case XKB_KEY_Escape: display_exit(resizor->display); break; } if (!resizor->frame_callback) frame_callback(resizor, NULL, 0); } static void menu_func(void *data, struct input *input, int index) { fprintf(stderr, "picked entry %d\n", index); } static void show_menu(struct resizor *resizor, struct input *input, uint32_t time) { int32_t x, y; static const char *entries[] = { "Roy", "Pris", "Leon", "Zhora" }; input_get_position(input, &x, &y); window_show_menu(resizor->display, input, time, resizor->window, x - 10, y - 10, menu_func, entries, 4); } static void locked_pointer_handle_motion(struct window *window, struct input *input, uint32_t time, float dx, float dy, void *data) { struct resizor *resizor = data; resizor->width.current += dx; resizor->width.previous = resizor->width.current; resizor->width.target = resizor->width.current; resizor->height.current += dy; resizor->height.previous = resizor->height.current; resizor->height.target = resizor->height.current; widget_schedule_resize(resizor->widget, resizor->width.current, resizor->height.current); } static void handle_pointer_locked(struct window *window, struct input *input, void *data) { struct resizor *resizor = data; resizor->pointer_locked = true; input_set_pointer_image(input, CURSOR_BLANK); } static void handle_pointer_unlocked(struct window *window, struct input *input, void *data) { struct resizor *resizor = data; resizor->pointer_locked = false; input_set_pointer_image(input, CURSOR_LEFT_PTR); } static const struct wl_callback_listener locked_pointer_frame_listener; static void locked_pointer_frame_callback(void *data, struct wl_callback *callback, uint32_t time) { struct resizor *resizor = data; struct wl_surface *surface; struct rectangle allocation; float x, y; if (resizor->pointer_locked) { widget_get_allocation(resizor->widget, &allocation); x = resizor->pointer_x + (allocation.width - allocation.x); y = resizor->pointer_y + (allocation.height - allocation.y); widget_set_locked_pointer_cursor_hint(resizor->widget, x, y); } wl_callback_destroy(callback); surface = window_get_wl_surface(resizor->window); callback = wl_surface_frame(surface); wl_callback_add_listener(callback, &locked_pointer_frame_listener, resizor); } static const struct wl_callback_listener locked_pointer_frame_listener = { locked_pointer_frame_callback }; static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct resizor *resizor = data; struct rectangle allocation; struct wl_surface *surface; struct wl_callback *callback; if (button == BTN_RIGHT && state == WL_POINTER_BUTTON_STATE_PRESSED) { show_menu(resizor, input, time); } else if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) { window_get_allocation(resizor->window, &allocation); resizor->width.current = allocation.width; resizor->width.previous = allocation.width; resizor->width.target = allocation.width; resizor->height.current = allocation.height; resizor->height.previous = allocation.height; resizor->height.target = allocation.height; window_lock_pointer(resizor->window, input); window_set_pointer_locked_handler(resizor->window, handle_pointer_locked, handle_pointer_unlocked); resizor->locked_input = input; if (resizor->locked_frame_callback_registered) return; surface = window_get_wl_surface(resizor->window); callback = wl_surface_frame(surface); wl_callback_add_listener(callback, &locked_pointer_frame_listener, resizor); resizor->locked_frame_callback_registered = true; } else if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_RELEASED) { input_set_pointer_image(input, CURSOR_LEFT_PTR); window_unlock_pointer(resizor->window); } } static void set_cursor_inv_offset(struct resizor *resizor, float x, float y) { struct rectangle allocation; widget_get_allocation(resizor->widget, &allocation); resizor->pointer_x = x - (allocation.width - allocation.x); resizor->pointer_y = y - (allocation.height - allocation.y); } static int enter_handler(struct widget *widget, struct input *input, float x, float y, void *data) { struct resizor *resizor = data; set_cursor_inv_offset(resizor, x , y); return CURSOR_LEFT_PTR; } static int motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct resizor *resizor = data; set_cursor_inv_offset(resizor, x , y); return CURSOR_LEFT_PTR; } static struct resizor * resizor_create(struct display *display) { struct resizor *resizor; resizor = xzalloc(sizeof *resizor); resizor->window = window_create(display); resizor->widget = window_frame_create(resizor->window, resizor); window_set_title(resizor->window, "Wayland Resizor"); resizor->display = display; window_set_key_handler(resizor->window, key_handler); window_set_user_data(resizor->window, resizor); widget_set_redraw_handler(resizor->widget, redraw_handler); window_set_keyboard_focus_handler(resizor->window, keyboard_focus_handler); widget_set_enter_handler(resizor->widget, enter_handler); widget_set_motion_handler(resizor->widget, motion_handler); window_set_locked_pointer_motion_handler( resizor->window, locked_pointer_handle_motion); widget_set_button_handler(resizor->widget, button_handler); resizor->height.previous = 400; resizor->height.current = 400; resizor->height.target = 400; resizor->width.previous = 400; resizor->width.current = 400; resizor->width.target = 400; widget_schedule_resize(resizor->widget, 400, 400); return resizor; } static void resizor_destroy(struct resizor *resizor) { if (resizor->frame_callback) wl_callback_destroy(resizor->frame_callback); widget_destroy(resizor->widget); window_destroy(resizor->window); free(resizor); } int main(int argc, char *argv[]) { struct display *display; struct resizor *resizor; display = display_create(&argc, argv); if (display == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } resizor = resizor_create(display); display_run(display); resizor_destroy(resizor); display_destroy(display); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.318963 weston-8.0.0/clients/scaler.c0000644000175000017460000002201400000000000016374 0ustar00simonwheel00000000000000/* * Copyright © 2008 Kristian Høgsberg * Copyright © 2013 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include "window.h" #include "viewporter-client-protocol.h" #define BUFFER_SCALE 2 static const int BUFFER_WIDTH = 421 * BUFFER_SCALE; static const int BUFFER_HEIGHT = 337 * BUFFER_SCALE; static const int SURFACE_WIDTH = 55 * 4; static const int SURFACE_HEIGHT = 77 * 4; static const double RECT_X = 21 * BUFFER_SCALE; /* buffer coords */ static const double RECT_Y = 25 * BUFFER_SCALE; static const double RECT_W = 55 * BUFFER_SCALE; static const double RECT_H = 77 * BUFFER_SCALE; struct box { struct display *display; struct window *window; struct widget *widget; int width, height; struct wp_viewporter *viewporter; struct wp_viewport *viewport; enum { MODE_NO_VIEWPORT, MODE_SRC_ONLY, MODE_DST_ONLY, MODE_SRC_DST } mode; }; static void set_my_viewport(struct box *box) { wl_fixed_t src_x, src_y, src_width, src_height; int32_t dst_width = SURFACE_WIDTH; int32_t dst_height = SURFACE_HEIGHT; if (box->mode == MODE_NO_VIEWPORT) return; /* Cut the green border in half, take white border fully in, * and black border fully out. The borders are 1px wide in buffer. * * The gl-renderer uses linear texture sampling, this means the * top and left edges go to 100% green, bottom goes to 50% blue/black, * right edge has thick white sliding to 50% red. */ src_x = wl_fixed_from_double((RECT_X + 0.5) / BUFFER_SCALE); src_y = wl_fixed_from_double((RECT_Y + 0.5) / BUFFER_SCALE); src_width = wl_fixed_from_double((RECT_W - 0.5) / BUFFER_SCALE); src_height = wl_fixed_from_double((RECT_H - 0.5) / BUFFER_SCALE); switch (box->mode){ case MODE_SRC_ONLY: /* In SRC_ONLY mode we're just cropping - in order * for the surface size to remain an integer, the * compositor will generate an error if we use a * fractional width or height. * * We use fractional width/height for the other cases * to ensure fractional values are still tested. */ src_width = wl_fixed_from_int(RECT_W / BUFFER_SCALE); src_height = wl_fixed_from_int(RECT_H / BUFFER_SCALE); wp_viewport_set_source(box->viewport, src_x, src_y, src_width, src_height); break; case MODE_DST_ONLY: wp_viewport_set_destination(box->viewport, dst_width, dst_height); break; case MODE_SRC_DST: wp_viewport_set_source(box->viewport, src_x, src_y, src_width, src_height); wp_viewport_set_destination(box->viewport, dst_width, dst_height); break; default: assert(!"not reached"); } } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct box *box = data; /* Don't resize me */ widget_set_size(box->widget, box->width, box->height); } static void redraw_handler(struct widget *widget, void *data) { struct box *box = data; cairo_surface_t *surface; cairo_t *cr; surface = window_get_surface(box->window); if (surface == NULL || cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "failed to create cairo egl surface\n"); return; } cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_line_width(cr, 1.0); cairo_translate(cr, RECT_X, RECT_Y); /* red background */ cairo_set_source_rgba(cr, 255, 0, 0, 255); cairo_paint(cr); /* blue box */ cairo_set_source_rgba(cr, 0, 0, 255, 255); cairo_rectangle(cr, 0, 0, RECT_W, RECT_H); cairo_fill(cr); /* black border outside the box */ cairo_set_source_rgb(cr, 0, 0, 0); cairo_move_to(cr, 0, RECT_H + 0.5); cairo_line_to(cr, RECT_W, RECT_H + 0.5); cairo_stroke(cr); /* white border inside the box */ cairo_set_source_rgb(cr, 1, 1, 1); cairo_move_to(cr, RECT_W - 0.5, 0); cairo_line_to(cr, RECT_W - 0.5, RECT_H); cairo_stroke(cr); /* the green border on inside the box, to be split half by crop */ cairo_set_source_rgb(cr, 0, 1, 0); cairo_move_to(cr, 0.5, RECT_H); cairo_line_to(cr, 0.5, 0); cairo_move_to(cr, 0, 0.5); cairo_line_to(cr, RECT_W, 0.5); cairo_stroke(cr); cairo_destroy(cr); /* TODO: buffer_transform */ cairo_surface_destroy(surface); } static void global_handler(struct display *display, uint32_t name, const char *interface, uint32_t version, void *data) { struct box *box = data; if (strcmp(interface, "wp_viewporter") == 0) { box->viewporter = display_bind(display, name, &wp_viewporter_interface, 1); box->viewport = wp_viewporter_get_viewport(box->viewporter, widget_get_wl_surface(box->widget)); set_my_viewport(box); } } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct box *box = data; if (button != BTN_LEFT) return; if (state == WL_POINTER_BUTTON_STATE_PRESSED) { window_move(box->window, input, display_get_serial(box->display)); } } static void touch_down_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data) { struct box *box = data; window_move(box->window, input, display_get_serial(box->display)); } static void usage(const char *progname) { fprintf(stderr, "Usage: %s [mode]\n" "where 'mode' is one of\n" " -b\tset both src and dst in viewport (default)\n" " -d\tset only dst in viewport\n" " -s\tset only src in viewport\n" " -n\tdo not set viewport at all\n\n", progname); fprintf(stderr, "Expected output with output_scale=1:\n"); fprintf(stderr, "Mode -n:\n" " window size %dx%d px\n" " Red box with a blue box in the upper left part.\n" " The blue box has white right edge, black bottom edge,\n" " and thin green left and top edges that can really\n" " be seen only when zoomed in.\n\n", BUFFER_WIDTH / BUFFER_SCALE, BUFFER_HEIGHT / BUFFER_SCALE); fprintf(stderr, "Mode -b:\n" " window size %dx%d px\n" " Blue box with green top and left edge,\n" " thick white right edge with a hint of red,\n" " and a hint of black in bottom edge.\n\n", SURFACE_WIDTH, SURFACE_HEIGHT); fprintf(stderr, "Mode -s:\n" " window size %.0fx%.0f px\n" " The same as mode -b, but scaled a lot smaller.\n\n", RECT_W / BUFFER_SCALE, RECT_H / BUFFER_SCALE); fprintf(stderr, "Mode -d:\n" " window size %dx%d px\n" " This is horizontally squashed version of the -n mode.\n\n", SURFACE_WIDTH, SURFACE_HEIGHT); } int main(int argc, char *argv[]) { struct box box; struct display *d; struct timeval tv; int i; box.mode = MODE_SRC_DST; for (i = 1; i < argc; i++) { if (strcmp("-s", argv[i]) == 0) box.mode = MODE_SRC_ONLY; else if (strcmp("-d", argv[i]) == 0) box.mode = MODE_DST_ONLY; else if (strcmp("-b", argv[i]) == 0) box.mode = MODE_SRC_DST; else if (strcmp("-n", argv[i]) == 0) box.mode = MODE_NO_VIEWPORT; else { usage(argv[0]); exit(1); } } d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } gettimeofday(&tv, NULL); srandom(tv.tv_usec); box.width = BUFFER_WIDTH / BUFFER_SCALE; box.height = BUFFER_HEIGHT / BUFFER_SCALE; box.display = d; box.window = window_create(d); box.widget = window_add_widget(box.window, &box); window_set_title(box.window, "Scaler Test Box"); window_set_buffer_scale(box.window, BUFFER_SCALE); widget_set_resize_handler(box.widget, resize_handler); widget_set_redraw_handler(box.widget, redraw_handler); widget_set_button_handler(box.widget, button_handler); widget_set_default_cursor(box.widget, CURSOR_HAND1); widget_set_touch_down_handler(box.widget, touch_down_handler); window_schedule_resize(box.window, box.width, box.height); display_set_user_data(box.display, &box); display_set_global_handler(box.display, global_handler); display_run(d); widget_destroy(box.widget); window_destroy(box.window); display_destroy(d); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.318963 weston-8.0.0/clients/screenshot.c0000644000175000017460000002075400000000000017311 0ustar00simonwheel00000000000000/* * Copyright © 2008 Kristian Høgsberg * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "weston-screenshooter-client-protocol.h" #include "shared/os-compatibility.h" #include "shared/xalloc.h" #include "shared/file-util.h" /* The screenshooter is a good example of a custom object exposed by * the compositor and serves as a test bed for implementing client * side marshalling outside libwayland.so */ struct screenshooter_output { struct wl_output *output; struct wl_buffer *buffer; int width, height, offset_x, offset_y; void *data; struct wl_list link; }; struct buffer_size { int width, height; int min_x, min_y; int max_x, max_y; }; struct screenshooter_data { struct wl_shm *shm; struct wl_list output_list; struct weston_screenshooter *screenshooter; int buffer_copy_done; }; static void display_handle_geometry(void *data, struct wl_output *wl_output, int x, int y, int physical_width, int physical_height, int subpixel, const char *make, const char *model, int transform) { struct screenshooter_output *output; output = wl_output_get_user_data(wl_output); if (wl_output == output->output) { output->offset_x = x; output->offset_y = y; } } static void display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int width, int height, int refresh) { struct screenshooter_output *output; output = wl_output_get_user_data(wl_output); if (wl_output == output->output && (flags & WL_OUTPUT_MODE_CURRENT)) { output->width = width; output->height = height; } } static const struct wl_output_listener output_listener = { display_handle_geometry, display_handle_mode }; static void screenshot_done(void *data, struct weston_screenshooter *screenshooter) { struct screenshooter_data *sh_data = data; sh_data->buffer_copy_done = 1; } static const struct weston_screenshooter_listener screenshooter_listener = { screenshot_done }; static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { static struct screenshooter_output *output; struct screenshooter_data *sh_data = data; if (strcmp(interface, "wl_output") == 0) { output = xmalloc(sizeof *output); output->output = wl_registry_bind(registry, name, &wl_output_interface, 1); wl_list_insert(&sh_data->output_list, &output->link); wl_output_add_listener(output->output, &output_listener, output); } else if (strcmp(interface, "wl_shm") == 0) { sh_data->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (strcmp(interface, "weston_screenshooter") == 0) { sh_data->screenshooter = wl_registry_bind(registry, name, &weston_screenshooter_interface, 1); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { /* XXX: unimplemented */ } static const struct wl_registry_listener registry_listener = { handle_global, handle_global_remove }; static struct wl_buffer * screenshot_create_shm_buffer(int width, int height, void **data_out, struct wl_shm *shm) { struct wl_shm_pool *pool; struct wl_buffer *buffer; int fd, size, stride; void *data; stride = width * 4; size = stride * height; fd = os_create_anonymous_file(size); if (fd < 0) { fprintf(stderr, "creating a buffer file for %d B failed: %s\n", size, strerror(errno)); return NULL; } data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return NULL; } pool = wl_shm_create_pool(shm, fd, size); close(fd); buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888); wl_shm_pool_destroy(pool); *data_out = data; return buffer; } static void screenshot_write_png(const struct buffer_size *buff_size, struct wl_list *output_list) { int output_stride, buffer_stride, i; cairo_surface_t *surface; void *data, *d, *s; struct screenshooter_output *output, *next; FILE *fp; char filepath[PATH_MAX]; buffer_stride = buff_size->width * 4; data = xmalloc(buffer_stride * buff_size->height); if (!data) return; wl_list_for_each_safe(output, next, output_list, link) { output_stride = output->width * 4; s = output->data; d = data + (output->offset_y - buff_size->min_y) * buffer_stride + (output->offset_x - buff_size->min_x) * 4; for (i = 0; i < output->height; i++) { memcpy(d, s, output_stride); d += buffer_stride; s += output_stride; } free(output); } surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, buff_size->width, buff_size->height, buffer_stride); fp = file_create_dated(getenv("XDG_PICTURES_DIR"), "wayland-screenshot-", ".png", filepath, sizeof(filepath)); if (fp) { fclose (fp); cairo_surface_write_to_png(surface, filepath); } cairo_surface_destroy(surface); free(data); } static int screenshot_set_buffer_size(struct buffer_size *buff_size, struct wl_list *output_list) { struct screenshooter_output *output; buff_size->min_x = buff_size->min_y = INT_MAX; buff_size->max_x = buff_size->max_y = INT_MIN; int position = 0; wl_list_for_each_reverse(output, output_list, link) { output->offset_x = position; position += output->width; } wl_list_for_each(output, output_list, link) { buff_size->min_x = MIN(buff_size->min_x, output->offset_x); buff_size->min_y = MIN(buff_size->min_y, output->offset_y); buff_size->max_x = MAX(buff_size->max_x, output->offset_x + output->width); buff_size->max_y = MAX(buff_size->max_y, output->offset_y + output->height); } if (buff_size->max_x <= buff_size->min_x || buff_size->max_y <= buff_size->min_y) return -1; buff_size->width = buff_size->max_x - buff_size->min_x; buff_size->height = buff_size->max_y - buff_size->min_y; return 0; } int main(int argc, char *argv[]) { struct wl_display *display; struct wl_registry *registry; struct screenshooter_output *output; struct buffer_size buff_size = {}; struct screenshooter_data sh_data = {}; display = wl_display_connect(NULL); if (display == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } wl_list_init(&sh_data.output_list); registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener, &sh_data); wl_display_dispatch(display); wl_display_roundtrip(display); if (sh_data.screenshooter == NULL) { fprintf(stderr, "display doesn't support screenshooter\n"); return -1; } weston_screenshooter_add_listener(sh_data.screenshooter, &screenshooter_listener, &sh_data); if (screenshot_set_buffer_size(&buff_size, &sh_data.output_list)) return -1; wl_list_for_each(output, &sh_data.output_list, link) { output->buffer = screenshot_create_shm_buffer(output->width, output->height, &output->data, sh_data.shm); weston_screenshooter_shoot(sh_data.screenshooter, output->output, output->buffer); sh_data.buffer_copy_done = 0; while (!sh_data.buffer_copy_done) wl_display_roundtrip(display); } screenshot_write_png(&buff_size, &sh_data.output_list); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3222964 weston-8.0.0/clients/simple-damage.c0000644000175000017460000006027300000000000017641 0ustar00simonwheel00000000000000/* * Copyright © 2014 Jason Ekstrand * Copyright © 2011 Benjamin Franzke * Copyright © 2010 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "shared/os-compatibility.h" #include #include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "viewporter-client-protocol.h" int print_debug = 0; struct display { struct wl_display *display; struct wl_registry *registry; int compositor_version; struct wl_compositor *compositor; struct wp_viewporter *viewporter; struct xdg_wm_base *wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct wl_shm *shm; uint32_t formats; }; struct buffer { struct wl_buffer *buffer; uint32_t *shm_data; int busy; }; enum window_flags { WINDOW_FLAG_USE_VIEWPORT = 0x1, WINDOW_FLAG_ROTATING_TRANSFORM = 0x2, WINDOW_FLAG_USE_DAMAGE_BUFFER = 0x4, }; struct window { struct display *display; int width, height, border; struct wl_surface *surface; struct wp_viewport *viewport; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct wl_callback *callback; struct buffer buffers[2]; struct buffer *prev_buffer; bool wait_for_configure; enum window_flags flags; int scale; enum wl_output_transform transform; struct { float x, y; /* position in pixels */ float dx, dy; /* velocity in pixels/second */ int radius; /* radius in pixels */ uint32_t prev_time; } ball; }; static int running = 1; static void redraw(void *data, struct wl_callback *callback, uint32_t time); static void buffer_release(void *data, struct wl_buffer *buffer) { struct buffer *mybuf = data; mybuf->busy = 0; } static const struct wl_buffer_listener buffer_listener = { buffer_release }; static int create_shm_buffer(struct display *display, struct buffer *buffer, int width, int height, uint32_t format) { struct wl_shm_pool *pool; int fd, size, pitch; void *data; pitch = width * 4; size = pitch * height; fd = os_create_anonymous_file(size); if (fd < 0) { fprintf(stderr, "creating a buffer file for %d B failed: %s\n", size, strerror(errno)); return -1; } data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return -1; } pool = wl_shm_create_pool(display->shm, fd, size); buffer->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, pitch, format); wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); wl_shm_pool_destroy(pool); close(fd); buffer->shm_data = data; return 0; } static void xdg_surface_handle_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; xdg_surface_ack_configure(surface, serial); if (window->wait_for_configure) { redraw(window, NULL, 0); window->wait_for_configure = false; } } static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { } static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; static float bounded_randf(float a, float b) { return a + ((float)rand() / (float)RAND_MAX) * (b - a); } static void window_init_game(struct window *window) { int ax1, ay1, ax2, ay2; /* playable arena size */ struct timeval tv; gettimeofday(&tv, NULL); srand(tv.tv_usec); window->ball.radius = 10; ax1 = window->border + window->ball.radius; ay1 = window->border + window->ball.radius; ax2 = window->width - window->border - window->ball.radius; ay2 = window->height - window->border - window->ball.radius; window->ball.x = bounded_randf(ax1, ax2); window->ball.y = bounded_randf(ay1, ay2); window->ball.dx = bounded_randf(0, window->width); window->ball.dy = bounded_randf(0, window->height); window->ball.prev_time = 0; } static void window_advance_game(struct window *window, uint32_t timestamp) { int ax1, ay1, ax2, ay2; /* Arena size */ float dt; if (window->ball.prev_time == 0) { /* first pass, don't do anything */ window->ball.prev_time = timestamp; return; } /* dt in seconds */ dt = (float)(timestamp - window->ball.prev_time) / 1000.0f; ax1 = window->border + window->ball.radius; ay1 = window->border + window->ball.radius; ax2 = window->width - window->border - window->ball.radius; ay2 = window->height - window->border - window->ball.radius; window->ball.x += window->ball.dx * dt; while (window->ball.x < ax1 || ax2 < window->ball.x) { if (window->ball.x < ax1) window->ball.x = 2 * ax1 - window->ball.x; if (ax2 <= window->ball.x) window->ball.x = 2 * ax2 - window->ball.x; window->ball.dx *= -1.0f; } window->ball.y += window->ball.dy * dt; while (window->ball.y < ay1 || ay2 < window->ball.y) { if (window->ball.y < ay1) window->ball.y = 2 * ay1 - window->ball.y; if (ay2 <= window->ball.y) window->ball.y = 2 * ay2 - window->ball.y; window->ball.dy *= -1.0f; } window->ball.prev_time = timestamp; } static struct window * create_window(struct display *display, int width, int height, enum wl_output_transform transform, int scale, enum window_flags flags) { struct window *window; if (display->compositor_version < 2 && (transform != WL_OUTPUT_TRANSFORM_NORMAL || flags & WINDOW_FLAG_ROTATING_TRANSFORM)) { fprintf(stderr, "wl_surface.buffer_transform unsupported in " "wl_surface version %d\n", display->compositor_version); exit(1); } if (display->compositor_version < 3 && (! (flags & WINDOW_FLAG_USE_VIEWPORT)) && scale != 1) { fprintf(stderr, "wl_surface.buffer_scale unsupported in " "wl_surface version %d\n", display->compositor_version); exit(1); } if (display->viewporter == NULL && (flags & WINDOW_FLAG_USE_VIEWPORT)) { fprintf(stderr, "Compositor does not support wp_viewport"); exit(1); } if (display->compositor_version < WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION && (flags & WINDOW_FLAG_USE_DAMAGE_BUFFER)) { fprintf(stderr, "wl_surface.damage_buffer unsupported in " "wl_surface version %d\n", display->compositor_version); exit(1); } window = zalloc(sizeof *window); if (!window) return NULL; window->callback = NULL; window->display = display; window->width = width; window->height = height; window->border = 10; window->flags = flags; window->transform = transform; window->scale = scale; window_init_game(window); window->surface = wl_compositor_create_surface(display->compositor); if (window->flags & WINDOW_FLAG_USE_VIEWPORT) window->viewport = wp_viewporter_get_viewport(display->viewporter, window->surface); if (display->wm_base) { window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); assert(window->xdg_surface); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); assert(window->xdg_toplevel); xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); xdg_toplevel_set_title(window->xdg_toplevel, "simple-damage"); window->wait_for_configure = true; wl_surface_commit(window->surface); } else if (display->fshell) { zwp_fullscreen_shell_v1_present_surface(display->fshell, window->surface, ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, NULL); } else { assert(0); } /* Initialise damage to full surface, so the padding gets painted */ if (window->flags & WINDOW_FLAG_USE_DAMAGE_BUFFER) { wl_surface_damage_buffer(window->surface, 0, 0, INT32_MAX, INT32_MAX); } else { wl_surface_damage(window->surface, 0, 0, INT32_MAX, INT32_MAX); } return window; } static void destroy_window(struct window *window) { if (window->callback) wl_callback_destroy(window->callback); if (window->buffers[0].buffer) wl_buffer_destroy(window->buffers[0].buffer); if (window->buffers[1].buffer) wl_buffer_destroy(window->buffers[1].buffer); if (window->xdg_toplevel) xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) xdg_surface_destroy(window->xdg_surface); if (window->viewport) wp_viewport_destroy(window->viewport); wl_surface_destroy(window->surface); free(window); } static struct buffer * window_next_buffer(struct window *window) { struct buffer *buffer; int ret = 0, bwidth, bheight; if (!window->buffers[0].busy) buffer = &window->buffers[0]; else if (!window->buffers[1].busy) buffer = &window->buffers[1]; else return NULL; switch (window->transform) { default: case WL_OUTPUT_TRANSFORM_NORMAL: case WL_OUTPUT_TRANSFORM_180: case WL_OUTPUT_TRANSFORM_FLIPPED: case WL_OUTPUT_TRANSFORM_FLIPPED_180: bwidth = window->width * window->scale; bheight = window->height * window->scale; break; case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_270: bwidth = window->height * window->scale; bheight = window->width * window->scale; break; } if (!buffer->buffer) { ret = create_shm_buffer(window->display, buffer, bwidth, bheight, WL_SHM_FORMAT_ARGB8888); if (ret < 0) return NULL; } return buffer; } static void paint_box(uint32_t *pixels, int pitch, int x, int y, int width, int height, uint32_t color) { int i, j; for (j = y; j < y + height; ++j) for (i = x; i < x + width; ++i) pixels[i + j * pitch] = color; } static void paint_circle(uint32_t *pixels, int pitch, float x, float y, int radius, uint32_t color) { int i, j; for (j = y - radius; j <= (int)(y + radius); ++j) for (i = x - radius; i <= (int)(x + radius); ++i) if ((j+0.5f-y)*(j+0.5f-y) + (i+0.5f-x)*(i+0.5f-x) <= radius * radius) pixels[i + j * pitch] = color; } static void window_get_transformed_ball(struct window *window, float *bx, float *by) { float wx, wy; wx = window->ball.x; wy = window->ball.y; switch (window->transform) { default: case WL_OUTPUT_TRANSFORM_NORMAL: *bx = wx; *by = wy; break; case WL_OUTPUT_TRANSFORM_90: *bx = window->height - wy; *by = wx; break; case WL_OUTPUT_TRANSFORM_180: *bx = window->width - wx; *by = window->height - wy; break; case WL_OUTPUT_TRANSFORM_270: *bx = wy; *by = window->width - wx; break; case WL_OUTPUT_TRANSFORM_FLIPPED: *bx = window->width - wx; *by = wy; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: *bx = window->height - wy; *by = window->width - wx; break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: *bx = wx; *by = window->height - wy; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: *bx = wy; *by = wx; break; } *bx *= window->scale; *by *= window->scale; if (window->viewport) { /* We're drawing half-size because of the viewport */ *bx /= 2; *by /= 2; } } static const struct wl_callback_listener frame_listener; static void redraw(void *data, struct wl_callback *callback, uint32_t time) { struct window *window = data; struct buffer *buffer; int off_x = 0, off_y = 0; int bwidth, bheight, bborder, bpitch, bradius; float bx, by; buffer = window_next_buffer(window); if (!buffer) { fprintf(stderr, !callback ? "Failed to create the first buffer.\n" : "Both buffers busy at redraw(). Server bug?\n"); abort(); } /* Rotate the damage, but keep the even/odd parity so the * dimensions of the buffers don't change */ if (window->flags & WINDOW_FLAG_ROTATING_TRANSFORM) window->transform = (window->transform + 2) % 8; switch (window->transform) { default: case WL_OUTPUT_TRANSFORM_NORMAL: case WL_OUTPUT_TRANSFORM_180: case WL_OUTPUT_TRANSFORM_FLIPPED: case WL_OUTPUT_TRANSFORM_FLIPPED_180: bwidth = window->width * window->scale; bheight = window->height * window->scale; break; case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_270: bwidth = window->height * window->scale; bheight = window->width * window->scale; break; } bpitch = bwidth; bborder = window->border * window->scale; bradius = window->ball.radius * window->scale; if (window->viewport) { int tx, ty; /* Fill the whole thing with red to detect viewport errors */ paint_box(buffer->shm_data, bpitch, 0, 0, bwidth, bheight, 0xffff0000); /* The buffer is the same size. However, we crop it * and scale it up by a factor of 2 */ bborder /= 2; bradius /= 2; bwidth /= 2; bheight /= 2; /* Offset the drawing region */ tx = (window->width / 3) * window->scale; ty = (window->height / 5) * window->scale; switch (window->transform) { default: case WL_OUTPUT_TRANSFORM_NORMAL: off_y = ty; off_x = tx; break; case WL_OUTPUT_TRANSFORM_90: off_y = tx; off_x = bwidth - ty; break; case WL_OUTPUT_TRANSFORM_180: off_y = bheight - ty; off_x = bwidth - tx; break; case WL_OUTPUT_TRANSFORM_270: off_y = bheight - tx; off_x = ty; break; case WL_OUTPUT_TRANSFORM_FLIPPED: off_y = ty; off_x = bwidth - tx; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: off_y = bheight - tx; off_x = bwidth - ty; break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: off_y = bheight - ty; off_x = tx; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: off_y = tx; off_x = ty; break; } wp_viewport_set_source(window->viewport, wl_fixed_from_int(window->width / 3), wl_fixed_from_int(window->height / 5), wl_fixed_from_int(window->width / 2), wl_fixed_from_int(window->height / 2)); } /* Paint the border */ paint_box(buffer->shm_data, bpitch, off_x, off_y, bwidth, bborder, 0xffffffff); paint_box(buffer->shm_data, bpitch, off_x, off_y, bborder, bheight, 0xffffffff); paint_box(buffer->shm_data, bpitch, off_x + bwidth - bborder, off_y, bborder, bheight, 0xffffffff); paint_box(buffer->shm_data, bpitch, off_x, off_y + bheight - bborder, bwidth, bborder, 0xffffffff); /* fill with translucent */ paint_box(buffer->shm_data, bpitch, off_x + bborder, off_y + bborder, bwidth - 2 * bborder, bheight - 2 * bborder, 0x80000000); /* Damage where the ball was */ if (window->flags & WINDOW_FLAG_USE_DAMAGE_BUFFER) { window_get_transformed_ball(window, &bx, &by); wl_surface_damage_buffer(window->surface, bx - bradius + off_x, by - bradius + off_y, bradius * 2 + 1, bradius * 2 + 1); } else { wl_surface_damage(window->surface, window->ball.x - window->ball.radius, window->ball.y - window->ball.radius, window->ball.radius * 2 + 1, window->ball.radius * 2 + 1); } window_advance_game(window, time); window_get_transformed_ball(window, &bx, &by); /* Paint the ball */ paint_circle(buffer->shm_data, bpitch, off_x + bx, off_y + by, bradius, 0xff00ff00); if (print_debug) { printf("Ball now located at (%f, %f)\n", window->ball.x, window->ball.y); printf("Circle painted at (%f, %f), radius %d\n", bx, by, bradius); printf("Buffer damage rectangle: (%d, %d) @ %dx%d\n", (int)(bx - bradius) + off_x, (int)(by - bradius) + off_y, bradius * 2 + 1, bradius * 2 + 1); } /* Damage where the ball is now */ if (window->flags & WINDOW_FLAG_USE_DAMAGE_BUFFER) { wl_surface_damage_buffer(window->surface, bx - bradius + off_x, by - bradius + off_y, bradius * 2 + 1, bradius * 2 + 1); } else { wl_surface_damage(window->surface, window->ball.x - window->ball.radius, window->ball.y - window->ball.radius, window->ball.radius * 2 + 1, window->ball.radius * 2 + 1); } wl_surface_attach(window->surface, buffer->buffer, 0, 0); if (window->display->compositor_version >= 2 && (window->transform != WL_OUTPUT_TRANSFORM_NORMAL || window->flags & WINDOW_FLAG_ROTATING_TRANSFORM)) wl_surface_set_buffer_transform(window->surface, window->transform); if (window->viewport) wp_viewport_set_destination(window->viewport, window->width, window->height); if (window->scale != 1) wl_surface_set_buffer_scale(window->surface, window->scale); if (callback) wl_callback_destroy(callback); window->callback = wl_surface_frame(window->surface); wl_callback_add_listener(window->callback, &frame_listener, window); wl_surface_commit(window->surface); buffer->busy = 1; } static const struct wl_callback_listener frame_listener = { redraw }; static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { struct display *d = data; d->formats |= (1 << format); } struct wl_shm_listener shm_listener = { shm_format }; static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { xdg_wm_base_pong(shell, serial); } static const struct xdg_wm_base_listener wm_base_listener = { xdg_wm_base_ping, }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct display *d = data; if (strcmp(interface, "wl_compositor") == 0) { if (d->compositor_version > (int)version) { fprintf(stderr, "Compositor does not support " "wl_surface version %d\n", d->compositor_version); exit(1); } if (d->compositor_version < 0) d->compositor_version = version; d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, d->compositor_version); } else if (strcmp(interface, "wp_viewporter") == 0) { d->viewporter = wl_registry_bind(registry, id, &wp_viewporter_interface, 1); } else if (strcmp(interface, "xdg_wm_base") == 0) { d->wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); } else if (strcmp(interface, "wl_shm") == 0) { d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); wl_shm_add_listener(d->shm, &shm_listener, d); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static struct display * create_display(int version) { struct display *display; display = malloc(sizeof *display); if (display == NULL) { fprintf(stderr, "out of memory\n"); exit(1); } display->display = wl_display_connect(NULL); assert(display->display); display->compositor_version = version; display->formats = 0; display->registry = wl_display_get_registry(display->display); wl_registry_add_listener(display->registry, ®istry_listener, display); wl_display_roundtrip(display->display); if (display->shm == NULL) { fprintf(stderr, "No wl_shm global\n"); exit(1); } wl_display_roundtrip(display->display); if (!(display->formats & (1 << WL_SHM_FORMAT_XRGB8888))) { fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); exit(1); } return display; } static void destroy_display(struct display *display) { if (display->shm) wl_shm_destroy(display->shm); if (display->wm_base) xdg_wm_base_destroy(display->wm_base); if (display->fshell) zwp_fullscreen_shell_v1_release(display->fshell); if (display->viewporter) wp_viewporter_destroy(display->viewporter); if (display->compositor) wl_compositor_destroy(display->compositor); wl_registry_destroy(display->registry); wl_display_flush(display->display); wl_display_disconnect(display->display); free(display); } static void signal_int(int signum) { running = 0; } static void print_usage(int retval) { printf( "usage: weston-simple-damage [options]\n\n" "options:\n" " -h, --help\t\tPring this help\n" " --verbose\t\tPrint verbose log information\n" " --version=VERSION\tVersion of wl_surface to use\n" " --width=WIDTH\t\tWidth of the window\n" " --height=HEIGHT\tHeight of the window\n" " --scale=SCALE\t\tScale factor for the surface\n" " --transform=TRANSFORM\tTransform for the surface\n" " --rotating-transform\tUse a different buffer_transform for each frame\n" " --use-viewport\tUse wp_viewport\n" " --use-damage-buffer\tUse damage_buffer to post damage\n" ); exit(retval); } static int parse_transform(const char *str, enum wl_output_transform *transform) { int i; static const struct { const char *name; enum wl_output_transform transform; } names[] = { { "normal", WL_OUTPUT_TRANSFORM_NORMAL }, { "90", WL_OUTPUT_TRANSFORM_90 }, { "180", WL_OUTPUT_TRANSFORM_180 }, { "270", WL_OUTPUT_TRANSFORM_270 }, { "flipped", WL_OUTPUT_TRANSFORM_FLIPPED }, { "flipped-90", WL_OUTPUT_TRANSFORM_FLIPPED_90 }, { "flipped-180", WL_OUTPUT_TRANSFORM_FLIPPED_180 }, { "flipped-270", WL_OUTPUT_TRANSFORM_FLIPPED_270 }, }; for (i = 0; i < 8; i++) { if (strcmp(names[i].name, str) == 0) { *transform = names[i].transform; return 1; } } return 0; } int main(int argc, char **argv) { struct sigaction sigint; struct display *display; struct window *window; int i, ret = 0; int version = -1; int width = 300, height = 200, scale = 1; enum wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; enum window_flags flags = 0; for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { print_usage(0); } else if (sscanf(argv[i], "--version=%d", &version) > 0) { if (version < 1 || version > 4) { fprintf(stderr, "Unsupported wl_surface version: %d\n", version); return 1; } continue; } else if (strcmp(argv[i], "--verbose") == 0) { print_debug = 1; continue; } else if (sscanf(argv[i], "--width=%d", &width) > 0) { continue; } else if (sscanf(argv[i], "--height=%d", &height) > 0) { continue; } else if (strncmp(argv[i], "--transform=", 12) == 0 && parse_transform(argv[i] + 12, &transform) > 0) { continue; } else if (strcmp(argv[i], "--rotating-transform") == 0) { flags |= WINDOW_FLAG_ROTATING_TRANSFORM; continue; } else if (sscanf(argv[i], "--scale=%d", &scale) > 0) { continue; } else if (strcmp(argv[i], "--use-viewport") == 0) { flags |= WINDOW_FLAG_USE_VIEWPORT; continue; } else if (strcmp(argv[i], "--use-damage-buffer") == 0) { flags |= WINDOW_FLAG_USE_DAMAGE_BUFFER; continue; } else { printf("Invalid option: %s\n", argv[i]); print_usage(255); } } display = create_display(version); window = create_window(display, width, height, transform, scale, flags); if (!window) return 1; sigint.sa_handler = signal_int; sigemptyset(&sigint.sa_mask); sigint.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sigint, NULL); if (!window->wait_for_configure) redraw(window, NULL, 0); while (running && ret != -1) ret = wl_display_dispatch(display->display); fprintf(stderr, "simple-shm exiting\n"); destroy_window(window); destroy_display(display); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3222964 weston-8.0.0/clients/simple-dmabuf-egl.c0000644000175000017460000012021600000000000020420 0ustar00simonwheel00000000000000/* * Copyright © 2011 Benjamin Franzke * Copyright © 2010 Intel Corporation * Copyright © 2014,2018 Collabora Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shared/helpers.h" #include "shared/platform.h" #include #include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "weston-direct-display-client-protocol.h" #include "linux-explicit-synchronization-unstable-v1-client-protocol.h" #include #include #include #include #include "shared/weston-egl-ext.h" #ifndef DRM_FORMAT_MOD_INVALID #define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1) #endif /* Possible options that affect the displayed image */ #define OPT_IMMEDIATE (1 << 0) /* create wl_buffer immediately */ #define OPT_IMPLICIT_SYNC (1 << 1) /* force implicit sync */ #define OPT_MANDELBROT (1 << 2) /* render mandelbrot */ #define OPT_DIRECT_DISPLAY (1 << 3) /* direct-display */ #define BUFFER_FORMAT DRM_FORMAT_XRGB8888 #define MAX_BUFFER_PLANES 4 struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct xdg_wm_base *wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct zwp_linux_dmabuf_v1 *dmabuf; struct weston_direct_display_v1 *direct_display; struct zwp_linux_explicit_synchronization_v1 *explicit_sync; uint64_t *modifiers; int modifiers_count; int req_dmabuf_immediate; bool use_explicit_sync; struct { EGLDisplay display; EGLContext context; EGLConfig conf; bool has_dma_buf_import_modifiers; bool has_no_config_context; PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dma_buf_modifiers; PFNEGLCREATEIMAGEKHRPROC create_image; PFNEGLDESTROYIMAGEKHRPROC destroy_image; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; PFNEGLCREATESYNCKHRPROC create_sync; PFNEGLDESTROYSYNCKHRPROC destroy_sync; PFNEGLCLIENTWAITSYNCKHRPROC client_wait_sync; PFNEGLDUPNATIVEFENCEFDANDROIDPROC dup_native_fence_fd; PFNEGLWAITSYNCKHRPROC wait_sync; } egl; struct { int drm_fd; struct gbm_device *device; } gbm; }; struct buffer { struct display *display; struct wl_buffer *buffer; int busy; struct gbm_bo *bo; int width; int height; int format; uint64_t modifier; int plane_count; int dmabuf_fds[MAX_BUFFER_PLANES]; uint32_t strides[MAX_BUFFER_PLANES]; uint32_t offsets[MAX_BUFFER_PLANES]; EGLImageKHR egl_image; GLuint gl_texture; GLuint gl_fbo; struct zwp_linux_buffer_release_v1 *buffer_release; /* The buffer owns the release_fence_fd, until it passes ownership * to it to EGL (see wait_for_buffer_release_fence). */ int release_fence_fd; }; #define NUM_BUFFERS 3 struct window { struct display *display; int width, height; struct wl_surface *surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct zwp_linux_surface_synchronization_v1 *surface_sync; struct buffer buffers[NUM_BUFFERS]; struct wl_callback *callback; bool initialized; bool wait_for_configure; struct { GLuint program; GLuint pos; GLuint color; GLuint offset_uniform; } gl; bool render_mandelbrot; }; static sig_atomic_t running = 1; static void redraw(void *data, struct wl_callback *callback, uint32_t time); static void buffer_release(void *data, struct wl_buffer *buffer) { struct buffer *mybuf = data; mybuf->busy = 0; } static const struct wl_buffer_listener buffer_listener = { buffer_release }; static void buffer_free(struct buffer *buf) { int i; if (buf->release_fence_fd >= 0) close(buf->release_fence_fd); if (buf->buffer_release) zwp_linux_buffer_release_v1_destroy(buf->buffer_release); if (buf->gl_fbo) glDeleteFramebuffers(1, &buf->gl_fbo); if (buf->gl_texture) glDeleteTextures(1, &buf->gl_texture); if (buf->egl_image) { buf->display->egl.destroy_image(buf->display->egl.display, buf->egl_image); } if (buf->buffer) wl_buffer_destroy(buf->buffer); if (buf->bo) gbm_bo_destroy(buf->bo); for (i = 0; i < buf->plane_count; ++i) { if (buf->dmabuf_fds[i] >= 0) close(buf->dmabuf_fds[i]); } } static void create_succeeded(void *data, struct zwp_linux_buffer_params_v1 *params, struct wl_buffer *new_buffer) { struct buffer *buffer = data; buffer->buffer = new_buffer; /* When not using explicit synchronization listen to wl_buffer.release * for release notifications, otherwise we are going to use * zwp_linux_buffer_release_v1. */ if (!buffer->display->use_explicit_sync) { wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); } zwp_linux_buffer_params_v1_destroy(params); } static void create_failed(void *data, struct zwp_linux_buffer_params_v1 *params) { struct buffer *buffer = data; buffer->buffer = NULL; running = 0; zwp_linux_buffer_params_v1_destroy(params); fprintf(stderr, "Error: zwp_linux_buffer_params.create failed.\n"); } static const struct zwp_linux_buffer_params_v1_listener params_listener = { create_succeeded, create_failed }; static bool create_fbo_for_buffer(struct display *display, struct buffer *buffer) { static const int general_attribs = 3; static const int plane_attribs = 5; static const int entries_per_attrib = 2; EGLint attribs[(general_attribs + plane_attribs * MAX_BUFFER_PLANES) * entries_per_attrib + 1]; unsigned int atti = 0; attribs[atti++] = EGL_WIDTH; attribs[atti++] = buffer->width; attribs[atti++] = EGL_HEIGHT; attribs[atti++] = buffer->height; attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; attribs[atti++] = buffer->format; #define ADD_PLANE_ATTRIBS(plane_idx) { \ attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _FD_EXT; \ attribs[atti++] = buffer->dmabuf_fds[plane_idx]; \ attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _OFFSET_EXT; \ attribs[atti++] = (int) buffer->offsets[plane_idx]; \ attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _PITCH_EXT; \ attribs[atti++] = (int) buffer->strides[plane_idx]; \ if (display->egl.has_dma_buf_import_modifiers) { \ attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_LO_EXT; \ attribs[atti++] = buffer->modifier & 0xFFFFFFFF; \ attribs[atti++] = EGL_DMA_BUF_PLANE ## plane_idx ## _MODIFIER_HI_EXT; \ attribs[atti++] = buffer->modifier >> 32; \ } \ } if (buffer->plane_count > 0) ADD_PLANE_ATTRIBS(0); if (buffer->plane_count > 1) ADD_PLANE_ATTRIBS(1); if (buffer->plane_count > 2) ADD_PLANE_ATTRIBS(2); if (buffer->plane_count > 3) ADD_PLANE_ATTRIBS(3); #undef ADD_PLANE_ATTRIBS attribs[atti] = EGL_NONE; assert(atti < ARRAY_LENGTH(attribs)); buffer->egl_image = display->egl.create_image(display->egl.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); if (buffer->egl_image == EGL_NO_IMAGE_KHR) { fprintf(stderr, "EGLImageKHR creation failed\n"); return false; } eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, display->egl.context); glGenTextures(1, &buffer->gl_texture); glBindTexture(GL_TEXTURE_2D, buffer->gl_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); display->egl.image_target_texture_2d(GL_TEXTURE_2D, buffer->egl_image); glGenFramebuffers(1, &buffer->gl_fbo); glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, buffer->gl_texture, 0); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { fprintf(stderr, "FBO creation failed\n"); return false; } return true; } static int create_dmabuf_buffer(struct display *display, struct buffer *buffer, int width, int height, uint32_t opts) { /* Y-Invert the buffer image, since we are going to renderer to the * buffer through a FBO. */ static uint32_t flags = ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; struct zwp_linux_buffer_params_v1 *params; int i; buffer->display = display; buffer->width = width; buffer->height = height; buffer->format = BUFFER_FORMAT; buffer->release_fence_fd = -1; #ifdef HAVE_GBM_MODIFIERS if (display->modifiers_count > 0) { buffer->bo = gbm_bo_create_with_modifiers(display->gbm.device, buffer->width, buffer->height, buffer->format, display->modifiers, display->modifiers_count); if (buffer->bo) buffer->modifier = gbm_bo_get_modifier(buffer->bo); } #endif if (!buffer->bo) { buffer->bo = gbm_bo_create(display->gbm.device, buffer->width, buffer->height, buffer->format, GBM_BO_USE_RENDERING); buffer->modifier = DRM_FORMAT_MOD_INVALID; } if (!buffer->bo) { fprintf(stderr, "create_bo failed\n"); goto error; } #ifdef HAVE_GBM_MODIFIERS buffer->plane_count = gbm_bo_get_plane_count(buffer->bo); for (i = 0; i < buffer->plane_count; ++i) { int ret; union gbm_bo_handle handle; handle = gbm_bo_get_handle_for_plane(buffer->bo, i); if (handle.s32 == -1) { fprintf(stderr, "error: failed to get gbm_bo_handle\n"); goto error; } ret = drmPrimeHandleToFD(display->gbm.drm_fd, handle.u32, 0, &buffer->dmabuf_fds[i]); if (ret < 0 || buffer->dmabuf_fds[i] < 0) { fprintf(stderr, "error: failed to get dmabuf_fd\n"); goto error; } buffer->strides[i] = gbm_bo_get_stride_for_plane(buffer->bo, i); buffer->offsets[i] = gbm_bo_get_offset(buffer->bo, i); } #else buffer->plane_count = 1; buffer->strides[0] = gbm_bo_get_stride(buffer->bo); buffer->dmabuf_fds[0] = gbm_bo_get_fd(buffer->bo); if (buffer->dmabuf_fds[0] < 0) { fprintf(stderr, "error: failed to get dmabuf_fd\n"); goto error; } #endif params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); if ((opts & OPT_DIRECT_DISPLAY) && display->direct_display) { weston_direct_display_v1_enable(display->direct_display, params); /* turn off Y_INVERT otherwise linux-dmabuf will reject it and * we need all dmabuf flags turned off */ flags &= ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; fprintf(stdout, "image is y-inverted as direct-display flag was set, " "dmabuf y-inverted attribute flag was removed\n"); } for (i = 0; i < buffer->plane_count; ++i) { zwp_linux_buffer_params_v1_add(params, buffer->dmabuf_fds[i], i, buffer->offsets[i], buffer->strides[i], buffer->modifier >> 32, buffer->modifier & 0xffffffff); } zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buffer); if (display->req_dmabuf_immediate) { buffer->buffer = zwp_linux_buffer_params_v1_create_immed(params, buffer->width, buffer->height, buffer->format, flags); /* When not using explicit synchronization listen to * wl_buffer.release for release notifications, otherwise we * are going to use zwp_linux_buffer_release_v1. */ if (!buffer->display->use_explicit_sync) { wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); } } else { zwp_linux_buffer_params_v1_create(params, buffer->width, buffer->height, buffer->format, flags); } if (!create_fbo_for_buffer(display, buffer)) goto error; return 0; error: buffer_free(buffer); return -1; } static void xdg_surface_handle_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; xdg_surface_ack_configure(surface, serial); if (window->initialized && window->wait_for_configure) redraw(window, NULL, 0); window->wait_for_configure = false; } static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { } static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; static const char *vert_shader_text = "uniform float offset;\n" "attribute vec4 pos;\n" "attribute vec4 color;\n" "varying vec4 v_color;\n" "void main() {\n" " gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\n" " v_color = color;\n" "}\n"; static const char *frag_shader_text = "precision mediump float;\n" "varying vec4 v_color;\n" "void main() {\n" " gl_FragColor = v_color;\n" "}\n"; static const char *vert_shader_mandelbrot_text = "uniform float offset;\n" "attribute vec4 pos;\n" "varying vec2 v_pos;\n" "void main() {\n" " v_pos = pos.xy;\n" " gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\n" "}\n"; /* Mandelbrot set shader using the escape time algorithm. */ static const char *frag_shader_mandelbrot_text = "precision mediump float;\n" "varying vec2 v_pos;\n" "void main() {\n" " const int max_iteration = 500;\n" " // Scale and translate position to get a nice mandelbrot drawing for\n" " // the used v_pos x and y range (-0.5 to 0.5).\n" " float x0 = 3.0 * v_pos.x - 0.5;\n" " float y0 = 3.0 * v_pos.y;\n" " float x = 0.0;\n" " float y = 0.0;\n" " int iteration = 0;\n" " while (x * x + y * y <= 4.0 && iteration < max_iteration) {\n" " float xtemp = x * x - y * y + x0;\n" " y = 2.0 * x * y + y0;\n" " x = xtemp;\n" " ++iteration;\n" " }\n" " float red = iteration == max_iteration ?\n" " 0.0 : 1.0 - fract(float(iteration) / 20.0);\n" " gl_FragColor = vec4(red, 0.0, 0.0, 1.0);\n" "}\n"; static GLuint create_shader(const char *source, GLenum shader_type) { GLuint shader; GLint status; shader = glCreateShader(shader_type); assert(shader != 0); glShaderSource(shader, 1, (const char **) &source, NULL); glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (!status) { char log[1000]; GLsizei len; glGetShaderInfoLog(shader, 1000, &len, log); fprintf(stderr, "Error: compiling %s: %.*s\n", shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", len, log); return 0; } return shader; } static GLuint create_and_link_program(GLuint vert, GLuint frag) { GLint status; GLuint program = glCreateProgram(); glAttachShader(program, vert); glAttachShader(program, frag); glLinkProgram(program); glGetProgramiv(program, GL_LINK_STATUS, &status); if (!status) { char log[1000]; GLsizei len; glGetProgramInfoLog(program, 1000, &len, log); fprintf(stderr, "Error: linking:\n%.*s\n", len, log); return 0; } return program; } static bool window_set_up_gl(struct window *window) { GLuint vert = create_shader( window->render_mandelbrot ? vert_shader_mandelbrot_text : vert_shader_text, GL_VERTEX_SHADER); GLuint frag = create_shader( window->render_mandelbrot ? frag_shader_mandelbrot_text : frag_shader_text, GL_FRAGMENT_SHADER); window->gl.program = create_and_link_program(vert, frag); glDeleteShader(vert); glDeleteShader(frag); window->gl.pos = glGetAttribLocation(window->gl.program, "pos"); window->gl.color = glGetAttribLocation(window->gl.program, "color"); glUseProgram(window->gl.program); window->gl.offset_uniform = glGetUniformLocation(window->gl.program, "offset"); return window->gl.program != 0; } static void destroy_window(struct window *window) { int i; if (window->gl.program) glDeleteProgram(window->gl.program); if (window->callback) wl_callback_destroy(window->callback); for (i = 0; i < NUM_BUFFERS; i++) { if (window->buffers[i].buffer) buffer_free(&window->buffers[i]); } if (window->xdg_toplevel) xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) xdg_surface_destroy(window->xdg_surface); if (window->surface_sync) zwp_linux_surface_synchronization_v1_destroy(window->surface_sync); wl_surface_destroy(window->surface); free(window); } static struct window * create_window(struct display *display, int width, int height, int opts) { struct window *window; int i; int ret; window = zalloc(sizeof *window); if (!window) return NULL; window->callback = NULL; window->display = display; window->width = width; window->height = height; window->surface = wl_compositor_create_surface(display->compositor); if (display->wm_base) { window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); assert(window->xdg_surface); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); assert(window->xdg_toplevel); xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); xdg_toplevel_set_title(window->xdg_toplevel, "simple-dmabuf-egl"); window->wait_for_configure = true; wl_surface_commit(window->surface); } else if (display->fshell) { zwp_fullscreen_shell_v1_present_surface(display->fshell, window->surface, ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, NULL); } else { assert(0); } if (display->explicit_sync) { window->surface_sync = zwp_linux_explicit_synchronization_v1_get_synchronization( display->explicit_sync, window->surface); assert(window->surface_sync); } for (i = 0; i < NUM_BUFFERS; ++i) { int j; for (j = 0; j < MAX_BUFFER_PLANES; ++j) window->buffers[i].dmabuf_fds[j] = -1; } for (i = 0; i < NUM_BUFFERS; ++i) { ret = create_dmabuf_buffer(display, &window->buffers[i], width, height, opts); if (ret < 0) goto error; } window->render_mandelbrot = opts & OPT_MANDELBROT; if (!window_set_up_gl(window)) goto error; return window; error: if (window) destroy_window(window); return NULL; } static int create_egl_fence_fd(struct window *window) { struct display *d = window->display; EGLSyncKHR sync = d->egl.create_sync(d->egl.display, EGL_SYNC_NATIVE_FENCE_ANDROID, NULL); int fd; assert(sync != EGL_NO_SYNC_KHR); /* We need to flush before we can get the fence fd. */ glFlush(); fd = d->egl.dup_native_fence_fd(d->egl.display, sync); assert(fd >= 0); d->egl.destroy_sync(d->egl.display, sync); return fd; } static struct buffer * window_next_buffer(struct window *window) { int i; for (i = 0; i < NUM_BUFFERS; i++) if (!window->buffers[i].busy) return &window->buffers[i]; return NULL; } static const struct wl_callback_listener frame_listener; /* Renders a square moving from the lower left corner to the * upper right corner of the window. The square's vertices have * the following colors: * * green +-----+ yellow * | | * | | * red +-----+ blue */ static void render(struct window *window, struct buffer *buffer) { /* Complete a movement iteration in 5000 ms. */ static const uint64_t iteration_ms = 5000; static const GLfloat verts[4][2] = { { -0.5, -0.5 }, { -0.5, 0.5 }, { 0.5, -0.5 }, { 0.5, 0.5 } }; static const GLfloat colors[4][3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 }, { 1, 1, 0 } }; GLfloat offset; struct timeval tv; uint64_t time_ms; gettimeofday(&tv, NULL); time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* Split time_ms in repeating windows of [0, iteration_ms) and map them * to offsets in the [-0.5, 0.5) range. */ offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; /* Direct all GL draws to the buffer through the FBO */ glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); glViewport(0, 0, window->width, window->height); glUniform1f(window->gl.offset_uniform, offset); glClearColor(0.0,0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); glVertexAttribPointer(window->gl.color, 3, GL_FLOAT, GL_FALSE, 0, colors); glEnableVertexAttribArray(window->gl.pos); glEnableVertexAttribArray(window->gl.color); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(window->gl.pos); glDisableVertexAttribArray(window->gl.color); } static void render_mandelbrot(struct window *window, struct buffer *buffer) { /* Complete a movement iteration in 5000 ms. */ static const uint64_t iteration_ms = 5000; /* Split drawing in a square grid consisting of grid_side * grid_side * cells. */ static const int grid_side = 4; GLfloat norm_cell_side = 1.0 / grid_side; int num_cells = grid_side * grid_side; GLfloat offset; struct timeval tv; uint64_t time_ms; int i; gettimeofday(&tv, NULL); time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* Split time_ms in repeating windows of [0, iteration_ms) and map them * to offsets in the [-0.5, 0.5) range. */ offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; /* Direct all GL draws to the buffer through the FBO */ glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); glViewport(0, 0, window->width, window->height); glUniform1f(window->gl.offset_uniform, offset); glClearColor(0.6, 0.6, 0.6, 1.0); glClear(GL_COLOR_BUFFER_BIT); for (i = 0; i < num_cells; ++i) { /* Calculate the vertex coordinates of the current grid cell. */ int row = i / grid_side; int col = i % grid_side; GLfloat left = -0.5 + norm_cell_side * col; GLfloat right = left + norm_cell_side; GLfloat top = 0.5 - norm_cell_side * row; GLfloat bottom = top - norm_cell_side; GLfloat verts[4][2] = { { left, bottom }, { left, top }, { right, bottom }, { right, top } }; /* ... and draw it. */ glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); glEnableVertexAttribArray(window->gl.pos); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(window->gl.pos); } } static void buffer_fenced_release(void *data, struct zwp_linux_buffer_release_v1 *release, int32_t fence) { struct buffer *buffer = data; assert(release == buffer->buffer_release); assert(buffer->release_fence_fd == -1); buffer->busy = 0; buffer->release_fence_fd = fence; zwp_linux_buffer_release_v1_destroy(buffer->buffer_release); buffer->buffer_release = NULL; } static void buffer_immediate_release(void *data, struct zwp_linux_buffer_release_v1 *release) { struct buffer *buffer = data; assert(release == buffer->buffer_release); assert(buffer->release_fence_fd == -1); buffer->busy = 0; zwp_linux_buffer_release_v1_destroy(buffer->buffer_release); buffer->buffer_release = NULL; } static const struct zwp_linux_buffer_release_v1_listener buffer_release_listener = { buffer_fenced_release, buffer_immediate_release, }; static void wait_for_buffer_release_fence(struct buffer *buffer) { struct display *d = buffer->display; EGLint attrib_list[] = { EGL_SYNC_NATIVE_FENCE_FD_ANDROID, buffer->release_fence_fd, EGL_NONE, }; EGLSyncKHR sync = d->egl.create_sync(d->egl.display, EGL_SYNC_NATIVE_FENCE_ANDROID, attrib_list); int ret; assert(sync); /* EGLSyncKHR takes ownership of the fence fd. */ buffer->release_fence_fd = -1; if (d->egl.wait_sync) ret = d->egl.wait_sync(d->egl.display, sync, 0); else ret = d->egl.client_wait_sync(d->egl.display, sync, 0, EGL_FOREVER_KHR); assert(ret == EGL_TRUE); ret = d->egl.destroy_sync(d->egl.display, sync); assert(ret == EGL_TRUE); } static void redraw(void *data, struct wl_callback *callback, uint32_t time) { struct window *window = data; struct buffer *buffer; buffer = window_next_buffer(window); if (!buffer) { fprintf(stderr, !callback ? "Failed to create the first buffer.\n" : "All buffers busy at redraw(). Server bug?\n"); abort(); } if (buffer->release_fence_fd >= 0) wait_for_buffer_release_fence(buffer); if (window->render_mandelbrot) render_mandelbrot(window, buffer); else render(window, buffer); if (window->display->use_explicit_sync) { int fence_fd = create_egl_fence_fd(window); zwp_linux_surface_synchronization_v1_set_acquire_fence( window->surface_sync, fence_fd); close(fence_fd); buffer->buffer_release = zwp_linux_surface_synchronization_v1_get_release(window->surface_sync); zwp_linux_buffer_release_v1_add_listener( buffer->buffer_release, &buffer_release_listener, buffer); } else { glFinish(); } wl_surface_attach(window->surface, buffer->buffer, 0, 0); wl_surface_damage(window->surface, 0, 0, window->width, window->height); if (callback) wl_callback_destroy(callback); window->callback = wl_surface_frame(window->surface); wl_callback_add_listener(window->callback, &frame_listener, window); wl_surface_commit(window->surface); buffer->busy = 1; } static const struct wl_callback_listener frame_listener = { redraw }; static void dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { struct display *d = data; switch (format) { case BUFFER_FORMAT: ++d->modifiers_count; d->modifiers = realloc(d->modifiers, d->modifiers_count * sizeof(*d->modifiers)); d->modifiers[d->modifiers_count - 1] = ((uint64_t)modifier_hi << 32) | modifier_lo; break; default: break; } } static void dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) { /* XXX: deprecated */ } static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { dmabuf_format, dmabuf_modifiers }; static void xdg_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) { xdg_wm_base_pong(wm_base, serial); } static const struct xdg_wm_base_listener xdg_wm_base_listener = { xdg_wm_base_ping, }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct display *d = data; if (strcmp(interface, "wl_compositor") == 0) { d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); } else if (strcmp(interface, "xdg_wm_base") == 0) { d->wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { if (version < 3) return; d->dmabuf = wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, 3); zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); } else if (strcmp(interface, "zwp_linux_explicit_synchronization_v1") == 0) { d->explicit_sync = wl_registry_bind( registry, id, &zwp_linux_explicit_synchronization_v1_interface, 1); } else if (strcmp(interface, "weston_direct_display_v1") == 0) { d->direct_display = wl_registry_bind(registry, id, &weston_direct_display_v1_interface, 1); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static void destroy_display(struct display *display) { if (display->gbm.device) gbm_device_destroy(display->gbm.device); if (display->gbm.drm_fd >= 0) close(display->gbm.drm_fd); if (display->egl.context != EGL_NO_CONTEXT) eglDestroyContext(display->egl.display, display->egl.context); if (display->egl.display != EGL_NO_DISPLAY) eglTerminate(display->egl.display); free(display->modifiers); if (display->dmabuf) zwp_linux_dmabuf_v1_destroy(display->dmabuf); if (display->wm_base) xdg_wm_base_destroy(display->wm_base); if (display->fshell) zwp_fullscreen_shell_v1_release(display->fshell); if (display->compositor) wl_compositor_destroy(display->compositor); if (display->registry) wl_registry_destroy(display->registry); if (display->display) { wl_display_flush(display->display); wl_display_disconnect(display->display); } free(display); } static bool display_set_up_egl(struct display *display) { static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; EGLint major, minor, ret, count; const char *egl_extensions = NULL; const char *gl_extensions = NULL; EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 1, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; display->egl.display = weston_platform_get_egl_display(EGL_PLATFORM_GBM_KHR, display->gbm.device, NULL); if (display->egl.display == EGL_NO_DISPLAY) { fprintf(stderr, "Failed to create EGLDisplay\n"); goto error; } if (eglInitialize(display->egl.display, &major, &minor) == EGL_FALSE) { fprintf(stderr, "Failed to initialize EGLDisplay\n"); goto error; } if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { fprintf(stderr, "Failed to bind OpenGL ES API\n"); goto error; } egl_extensions = eglQueryString(display->egl.display, EGL_EXTENSIONS); assert(egl_extensions != NULL); if (!weston_check_egl_extension(egl_extensions, "EGL_EXT_image_dma_buf_import")) { fprintf(stderr, "EGL_EXT_image_dma_buf_import not supported\n"); goto error; } if (!weston_check_egl_extension(egl_extensions, "EGL_KHR_surfaceless_context")) { fprintf(stderr, "EGL_KHR_surfaceless_context not supported\n"); goto error; } if (weston_check_egl_extension(egl_extensions, "EGL_KHR_no_config_context")) { display->egl.has_no_config_context = true; } if (display->egl.has_no_config_context) { display->egl.conf = EGL_NO_CONFIG_KHR; } else { fprintf(stderr, "Warning: EGL_KHR_no_config_context not supported\n"); ret = eglChooseConfig(display->egl.display, config_attribs, &display->egl.conf, 1, &count); assert(ret && count >= 1); } display->egl.context = eglCreateContext(display->egl.display, display->egl.conf, EGL_NO_CONTEXT, context_attribs); if (display->egl.context == EGL_NO_CONTEXT) { fprintf(stderr, "Failed to create EGLContext\n"); goto error; } eglMakeCurrent(display->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, display->egl.context); gl_extensions = (const char *) glGetString(GL_EXTENSIONS); assert(gl_extensions != NULL); if (!weston_check_egl_extension(gl_extensions, "GL_OES_EGL_image")) { fprintf(stderr, "GL_OES_EGL_image not supported\n"); goto error; } if (weston_check_egl_extension(egl_extensions, "EGL_EXT_image_dma_buf_import_modifiers")) { display->egl.has_dma_buf_import_modifiers = true; display->egl.query_dma_buf_modifiers = (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); assert(display->egl.query_dma_buf_modifiers); } display->egl.create_image = (void *) eglGetProcAddress("eglCreateImageKHR"); assert(display->egl.create_image); display->egl.destroy_image = (void *) eglGetProcAddress("eglDestroyImageKHR"); assert(display->egl.destroy_image); display->egl.image_target_texture_2d = (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); assert(display->egl.image_target_texture_2d); if (weston_check_egl_extension(egl_extensions, "EGL_KHR_fence_sync") && weston_check_egl_extension(egl_extensions, "EGL_ANDROID_native_fence_sync")) { display->egl.create_sync = (void *) eglGetProcAddress("eglCreateSyncKHR"); assert(display->egl.create_sync); display->egl.destroy_sync = (void *) eglGetProcAddress("eglDestroySyncKHR"); assert(display->egl.destroy_sync); display->egl.client_wait_sync = (void *) eglGetProcAddress("eglClientWaitSyncKHR"); assert(display->egl.client_wait_sync); display->egl.dup_native_fence_fd = (void *) eglGetProcAddress("eglDupNativeFenceFDANDROID"); assert(display->egl.dup_native_fence_fd); } if (weston_check_egl_extension(egl_extensions, "EGL_KHR_wait_sync")) { display->egl.wait_sync = (void *) eglGetProcAddress("eglWaitSyncKHR"); assert(display->egl.wait_sync); } return true; error: return false; } static bool display_update_supported_modifiers_for_egl(struct display *d) { uint64_t *egl_modifiers = NULL; int num_egl_modifiers = 0; EGLBoolean ret; int i; bool try_modifiers = d->egl.has_dma_buf_import_modifiers; if (try_modifiers) { ret = d->egl.query_dma_buf_modifiers(d->egl.display, BUFFER_FORMAT, 0, /* max_modifiers */ NULL, /* modifiers */ NULL, /* external_only */ &num_egl_modifiers); if (ret == EGL_FALSE) { fprintf(stderr, "Failed to query num EGL modifiers for format\n"); goto error; } } if (!num_egl_modifiers) try_modifiers = false; /* If EGL doesn't support modifiers, don't use them at all. */ if (!try_modifiers) { d->modifiers_count = 0; free(d->modifiers); d->modifiers = NULL; return true; } egl_modifiers = zalloc(num_egl_modifiers * sizeof(*egl_modifiers)); ret = d->egl.query_dma_buf_modifiers(d->egl.display, BUFFER_FORMAT, num_egl_modifiers, egl_modifiers, NULL, /* external_only */ &num_egl_modifiers); if (ret == EGL_FALSE) { fprintf(stderr, "Failed to query EGL modifiers for format\n"); goto error; } /* Poor person's set intersection: d->modifiers INTERSECT * egl_modifiers. If a modifier is not supported, replace it with * DRM_FORMAT_MOD_INVALID in the d->modifiers array. */ for (i = 0; i < d->modifiers_count; ++i) { uint64_t mod = d->modifiers[i]; bool egl_supported = false; int j; for (j = 0; j < num_egl_modifiers; ++j) { if (egl_modifiers[j] == mod) { egl_supported = true; break; } } if (!egl_supported) d->modifiers[i] = DRM_FORMAT_MOD_INVALID; } free(egl_modifiers); return true; error: free(egl_modifiers); return false; } static bool display_set_up_gbm(struct display *display, char const* drm_render_node) { display->gbm.drm_fd = open(drm_render_node, O_RDWR); if (display->gbm.drm_fd < 0) { fprintf(stderr, "Failed to open drm render node %s\n", drm_render_node); return false; } display->gbm.device = gbm_create_device(display->gbm.drm_fd); if (display->gbm.device == NULL) { fprintf(stderr, "Failed to create gbm device\n"); return false; } return true; } static struct display * create_display(char const *drm_render_node, int opts) { struct display *display = NULL; display = zalloc(sizeof *display); if (display == NULL) { fprintf(stderr, "out of memory\n"); goto error; } display->gbm.drm_fd = -1; display->display = wl_display_connect(NULL); assert(display->display); display->req_dmabuf_immediate = opts & OPT_IMMEDIATE; display->registry = wl_display_get_registry(display->display); wl_registry_add_listener(display->registry, ®istry_listener, display); wl_display_roundtrip(display->display); if (display->dmabuf == NULL) { fprintf(stderr, "No zwp_linux_dmabuf global\n"); goto error; } wl_display_roundtrip(display->display); if (!display->modifiers_count) { fprintf(stderr, "format XRGB8888 is not available\n"); goto error; } /* GBM needs to be initialized before EGL, so that we have a valid * render node gbm_device to create the EGL display from. */ if (!display_set_up_gbm(display, drm_render_node)) goto error; if (!display_set_up_egl(display)) goto error; if (!display_update_supported_modifiers_for_egl(display)) goto error; /* We use explicit synchronization only if the user hasn't disabled it, * the compositor supports it, we can handle fence fds. */ display->use_explicit_sync = !(opts & OPT_IMPLICIT_SYNC) && display->explicit_sync && display->egl.dup_native_fence_fd; if (opts & OPT_IMPLICIT_SYNC) { fprintf(stderr, "Warning: Not using explicit sync, disabled by user\n"); } else if (!display->explicit_sync) { fprintf(stderr, "Warning: zwp_linux_explicit_synchronization_v1 not supported,\n" " will not use explicit synchronization\n"); } else if (!display->egl.dup_native_fence_fd) { fprintf(stderr, "Warning: EGL_ANDROID_native_fence_sync not supported,\n" " will not use explicit synchronization\n"); } else if (!display->egl.wait_sync) { fprintf(stderr, "Warning: EGL_KHR_wait_sync not supported,\n" " will not use server-side wait\n"); } return display; error: if (display != NULL) destroy_display(display); return NULL; } static void signal_int(int signum) { running = 0; } static void print_usage_and_exit(void) { printf("usage flags:\n" "\t'-i,--import-immediate=<>'" "\n\t\t0 to import dmabuf via roundtrip, " "\n\t\t1 to enable import without roundtrip\n" "\t'-d,--drm-render-node=<>'" "\n\t\tthe full path to the drm render node to use\n" "\t'-s,--size=<>'" "\n\t\tthe window size in pixels (default: 256)\n" "\t'-e,--explicit-sync=<>'" "\n\t\t0 to disable explicit sync, " "\n\t\t1 to enable explicit sync (default: 1)\n" "\t'-m,--mandelbrot'" "\n\t\trender a mandelbrot set with multiple draw calls\n" "\t'-g,--direct-display'" "\n\t\tenables weston-direct-display extension to attempt " "direct scan-out;\n\t\tnote this will cause the image to be " "displayed inverted as GL uses a\n\t\tdifferent texture " "coordinate system\n"); exit(0); } static int is_true(const char* c) { if (!strcmp(c, "1")) return 1; else if (!strcmp(c, "0")) return 0; else print_usage_and_exit(); return 0; } int main(int argc, char **argv) { struct sigaction sigint; struct display *display; struct window *window; int opts = 0; char const *drm_render_node = "/dev/dri/renderD128"; int c, option_index, ret = 0; int window_size = 256; static struct option long_options[] = { {"import-immediate", required_argument, 0, 'i' }, {"drm-render-node", required_argument, 0, 'd' }, {"size", required_argument, 0, 's' }, {"explicit-sync", required_argument, 0, 'e' }, {"mandelbrot", no_argument, 0, 'm' }, {"direct-display", no_argument, 0, 'g' }, {"help", no_argument , 0, 'h' }, {0, 0, 0, 0} }; while ((c = getopt_long(argc, argv, "hi:d:s:e:mg", long_options, &option_index)) != -1) { switch (c) { case 'i': if (is_true(optarg)) opts |= OPT_IMMEDIATE; break; case 'd': drm_render_node = optarg; break; case 's': window_size = strtol(optarg, NULL, 10); break; case 'e': if (!is_true(optarg)) opts |= OPT_IMPLICIT_SYNC; break; case 'm': opts |= OPT_MANDELBROT; break; case 'g': opts |= OPT_DIRECT_DISPLAY; break; default: print_usage_and_exit(); } } display = create_display(drm_render_node, opts); if (!display) return 1; window = create_window(display, window_size, window_size, opts); if (!window) return 1; sigint.sa_handler = signal_int; sigemptyset(&sigint.sa_mask); sigint.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sigint, NULL); /* Here we retrieve the linux-dmabuf objects if executed without immed, * or error */ wl_display_roundtrip(display->display); if (!running) return 1; window->initialized = true; if (!window->wait_for_configure) redraw(window, NULL, 0); while (running && ret != -1) ret = wl_display_dispatch(display->display); fprintf(stderr, "simple-dmabuf-egl exiting\n"); destroy_window(window); destroy_display(display); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3256297 weston-8.0.0/clients/simple-dmabuf-v4l.c0000644000175000017460000006035000000000000020360 0ustar00simonwheel00000000000000/* * Copyright © 2015 Collabora Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" #define CLEAR(x) memset(&(x), 0, sizeof(x)) static void redraw(void *data, struct wl_callback *callback, uint32_t time); static int xioctl(int fh, int request, void *arg) { int r; do { r = ioctl(fh, request, arg); } while (r == -1 && errno == EINTR); return r; } static uint32_t parse_format(const char fmt[4]) { return fourcc_code(fmt[0], fmt[1], fmt[2], fmt[3]); } static inline const char * dump_format(uint32_t format, char out[4]) { #if BYTE_ORDER == BIG_ENDIAN format = __builtin_bswap32(format); #endif memcpy(out, &format, 4); return out; } struct buffer_format { int width; int height; enum v4l2_buf_type type; uint32_t format; unsigned num_planes; unsigned strides[VIDEO_MAX_PLANES]; }; struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct wl_seat *seat; struct wl_keyboard *keyboard; struct xdg_wm_base *wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct zwp_linux_dmabuf_v1 *dmabuf; bool requested_format_found; int v4l_fd; struct buffer_format format; uint32_t drm_format; }; struct buffer { struct wl_buffer *buffer; struct display *display; int busy; int index; int dmabuf_fds[VIDEO_MAX_PLANES]; int data_offsets[VIDEO_MAX_PLANES]; }; #define NUM_BUFFERS 3 struct window { struct display *display; struct wl_surface *surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct buffer buffers[NUM_BUFFERS]; struct wl_callback *callback; bool wait_for_configure; bool initialized; }; static bool running = true; static int queue(struct display *display, struct buffer *buffer) { struct v4l2_buffer buf; struct v4l2_plane planes[VIDEO_MAX_PLANES]; unsigned i; CLEAR(buf); buf.type = display->format.type; buf.memory = V4L2_MEMORY_MMAP; buf.index = buffer->index; if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { CLEAR(planes); buf.length = VIDEO_MAX_PLANES; buf.m.planes = planes; } if (xioctl(display->v4l_fd, VIDIOC_QUERYBUF, &buf) == -1) { perror("VIDIOC_QUERYBUF"); return 0; } if (xioctl(display->v4l_fd, VIDIOC_QBUF, &buf) == -1) { perror("VIDIOC_QBUF"); return 0; } if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { if (display->format.num_planes != buf.length) { fprintf(stderr, "Wrong number of planes returned by " "QUERYBUF\n"); return 0; } for (i = 0; i < buf.length; ++i) buffer->data_offsets[i] = buf.m.planes[i].data_offset; } return 1; } static void buffer_release(void *data, struct wl_buffer *buffer) { struct buffer *mybuf = data; mybuf->busy = 0; if (!queue(mybuf->display, mybuf)) running = false; } static const struct wl_buffer_listener buffer_listener = { buffer_release }; static unsigned int set_format(struct display *display, uint32_t format) { struct v4l2_format fmt; char buf[4]; CLEAR(fmt); fmt.type = display->format.type; if (xioctl(display->v4l_fd, VIDIOC_G_FMT, &fmt) == -1) { perror("VIDIOC_G_FMT"); return 0; } /* No need to set the format if it already is the one we want */ if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE && fmt.fmt.pix.pixelformat == format) return 1; if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && fmt.fmt.pix_mp.pixelformat == format) return fmt.fmt.pix_mp.num_planes; if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) fmt.fmt.pix.pixelformat = format; else fmt.fmt.pix_mp.pixelformat = format; if (xioctl(display->v4l_fd, VIDIOC_S_FMT, &fmt) == -1) { perror("VIDIOC_S_FMT"); return 0; } if ((display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE && fmt.fmt.pix.pixelformat != format) || (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && fmt.fmt.pix_mp.pixelformat != format)) { fprintf(stderr, "Failed to set format to %.4s\n", dump_format(format, buf)); return 0; } if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) return fmt.fmt.pix_mp.num_planes; return 1; } static int v4l_connect(struct display *display, const char *dev_name) { struct v4l2_capability cap; struct v4l2_requestbuffers req; unsigned int num_planes; display->v4l_fd = open(dev_name, O_RDWR); if (display->v4l_fd < 0) { perror(dev_name); return 0; } if (xioctl(display->v4l_fd, VIDIOC_QUERYCAP, &cap) == -1) { if (errno == EINVAL) { fprintf(stderr, "%s is no V4L2 device\n", dev_name); } else { perror("VIDIOC_QUERYCAP"); } return 0; } if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) display->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; else if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) display->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; else { fprintf(stderr, "%s is no video capture device\n", dev_name); return 0; } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "%s does not support dmabuf i/o\n", dev_name); return 0; } /* Select video input, video standard and tune here */ num_planes = set_format(display, display->format.format); if (num_planes < 1) return 0; CLEAR(req); req.type = display->format.type; req.memory = V4L2_MEMORY_MMAP; req.count = NUM_BUFFERS * num_planes; if (xioctl(display->v4l_fd, VIDIOC_REQBUFS, &req) == -1) { if (errno == EINVAL) { fprintf(stderr, "%s does not support dmabuf\n", dev_name); } else { perror("VIDIOC_REQBUFS"); } return 0; } if (req.count < NUM_BUFFERS * num_planes) { fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name); return 0; } printf("Created %d buffers\n", req.count); return 1; } static void v4l_shutdown(struct display *display) { close(display->v4l_fd); } static void create_succeeded(void *data, struct zwp_linux_buffer_params_v1 *params, struct wl_buffer *new_buffer) { struct buffer *buffer = data; unsigned i; buffer->buffer = new_buffer; wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); zwp_linux_buffer_params_v1_destroy(params); for (i = 0; i < buffer->display->format.num_planes; ++i) close(buffer->dmabuf_fds[i]); } static void create_failed(void *data, struct zwp_linux_buffer_params_v1 *params) { struct buffer *buffer = data; unsigned i; buffer->buffer = NULL; zwp_linux_buffer_params_v1_destroy(params); for (i = 0; i < buffer->display->format.num_planes; ++i) close(buffer->dmabuf_fds[i]); running = false; fprintf(stderr, "Error: zwp_linux_buffer_params.create failed.\n"); } static const struct zwp_linux_buffer_params_v1_listener params_listener = { create_succeeded, create_failed }; static void create_dmabuf_buffer(struct display *display, struct buffer *buffer) { struct zwp_linux_buffer_params_v1 *params; uint64_t modifier; uint32_t flags; unsigned i; modifier = 0; flags = 0; /* XXX: apparently some webcams may actually provide y-inverted images, * in which case we should set * flags = ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT */ params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); for (i = 0; i < display->format.num_planes; ++i) zwp_linux_buffer_params_v1_add(params, buffer->dmabuf_fds[i], i, /* plane_idx */ buffer->data_offsets[i], /* offset */ display->format.strides[i], modifier >> 32, modifier & 0xffffffff); zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buffer); zwp_linux_buffer_params_v1_create(params, display->format.width, display->format.height, display->drm_format, flags); } static int buffer_export(struct display *display, int index, int dmafd[]) { struct v4l2_exportbuffer expbuf; unsigned i; CLEAR(expbuf); for (i = 0; i < display->format.num_planes; ++i) { expbuf.type = display->format.type; expbuf.index = index; expbuf.plane = i; if (xioctl(display->v4l_fd, VIDIOC_EXPBUF, &expbuf) == -1) { perror("VIDIOC_EXPBUF"); while (i) close(dmafd[--i]); return 0; } dmafd[i] = expbuf.fd; } return 1; } static int queue_initial_buffers(struct display *display, struct buffer buffers[NUM_BUFFERS]) { struct buffer *buffer; int index; for (index = 0; index < NUM_BUFFERS; ++index) { buffer = &buffers[index]; buffer->display = display; buffer->index = index; if (!queue(display, buffer)) { fprintf(stderr, "Failed to queue buffer\n"); return 0; } assert(!buffer->buffer); if (!buffer_export(display, index, buffer->dmabuf_fds)) return 0; create_dmabuf_buffer(display, buffer); } return 1; } static int dequeue(struct display *display) { struct v4l2_buffer buf; struct v4l2_plane planes[VIDEO_MAX_PLANES]; CLEAR(buf); buf.type = display->format.type; buf.memory = V4L2_MEMORY_MMAP; buf.length = VIDEO_MAX_PLANES; buf.m.planes = planes; /* This ioctl is blocking until a buffer is ready to be displayed */ if (xioctl(display->v4l_fd, VIDIOC_DQBUF, &buf) == -1) { perror("VIDIOC_DQBUF"); return -1; } return buf.index; } static int fill_buffer_format(struct display *display) { struct v4l2_format fmt; struct v4l2_pix_format *pix; struct v4l2_pix_format_mplane *pix_mp; int i; char buf[4]; CLEAR(fmt); fmt.type = display->format.type; /* Preserve original settings as set by v4l2-ctl for example */ if (xioctl(display->v4l_fd, VIDIOC_G_FMT, &fmt) == -1) { perror("VIDIOC_G_FMT"); return 0; } if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { pix = &fmt.fmt.pix; printf("%d×%d, %.4s\n", pix->width, pix->height, dump_format(pix->pixelformat, buf)); display->format.num_planes = 1; display->format.width = pix->width; display->format.height = pix->height; display->format.strides[0] = pix->bytesperline; } else { pix_mp = &fmt.fmt.pix_mp; display->format.num_planes = pix_mp->num_planes; display->format.width = pix_mp->width; display->format.height = pix_mp->height; for (i = 0; i < pix_mp->num_planes; ++i) display->format.strides[i] = pix_mp->plane_fmt[i].bytesperline; printf("%d×%d, %.4s, %d planes\n", pix_mp->width, pix_mp->height, dump_format(pix_mp->pixelformat, buf), pix_mp->num_planes); } return 1; } static int v4l_init(struct display *display, struct buffer buffers[NUM_BUFFERS]) { if (!fill_buffer_format(display)) { fprintf(stderr, "Failed to fill buffer format\n"); return 0; } if (!queue_initial_buffers(display, buffers)) { fprintf(stderr, "Failed to queue initial buffers\n"); return 0; } return 1; } static int start_capture(struct display *display) { int type = display->format.type; if (xioctl(display->v4l_fd, VIDIOC_STREAMON, &type) == -1) { perror("VIDIOC_STREAMON"); return 0; } return 1; } static void xdg_surface_handle_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; xdg_surface_ack_configure(surface, serial); if (window->initialized && window->wait_for_configure) redraw(window, NULL, 0); window->wait_for_configure = false; } static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { } static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; static struct window * create_window(struct display *display) { struct window *window; window = zalloc(sizeof *window); if (!window) return NULL; window->callback = NULL; window->display = display; window->surface = wl_compositor_create_surface(display->compositor); if (display->wm_base) { window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); assert(window->xdg_surface); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); assert(window->xdg_toplevel); xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); xdg_toplevel_set_title(window->xdg_toplevel, "simple-dmabuf-v4l"); window->wait_for_configure = true; wl_surface_commit(window->surface); } else if (display->fshell) { zwp_fullscreen_shell_v1_present_surface(display->fshell, window->surface, ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, NULL); } else { assert(0); } return window; } static void destroy_window(struct window *window) { int i; unsigned j; if (window->callback) wl_callback_destroy(window->callback); if (window->xdg_toplevel) xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) xdg_surface_destroy(window->xdg_surface); wl_surface_destroy(window->surface); for (i = 0; i < NUM_BUFFERS; i++) { if (!window->buffers[i].buffer) continue; wl_buffer_destroy(window->buffers[i].buffer); for (j = 0; j < window->display->format.num_planes; ++j) close(window->buffers[i].dmabuf_fds[j]); } v4l_shutdown(window->display); free(window); } static const struct wl_callback_listener frame_listener; static void redraw(void *data, struct wl_callback *callback, uint32_t time) { struct window *window = data; struct buffer *buffer; int index, num_busy = 0; /* Check for a deadlock situation where we would block forever trying * to dequeue a buffer while all of them are locked by the compositor. */ for (index = 0; index < NUM_BUFFERS; ++index) if (window->buffers[index].busy) ++num_busy; /* A robust application would just postpone redraw until it has queued * a buffer. */ assert(num_busy < NUM_BUFFERS); index = dequeue(window->display); if (index < 0) { /* We couldn’t get any buffer out of the camera, exiting. */ running = false; return; } buffer = &window->buffers[index]; assert(!buffer->busy); wl_surface_attach(window->surface, buffer->buffer, 0, 0); wl_surface_damage(window->surface, 0, 0, window->display->format.width, window->display->format.height); if (callback) wl_callback_destroy(callback); window->callback = wl_surface_frame(window->surface); wl_callback_add_listener(window->callback, &frame_listener, window); wl_surface_commit(window->surface); buffer->busy = 1; } static const struct wl_callback_listener frame_listener = { redraw }; static void dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format) { struct display *d = data; if (format == d->drm_format) d->requested_format_found = true; } static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { dmabuf_format }; static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { /* Just so we don’t leak the keymap fd */ close(fd); } static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { } static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { } static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { struct display *d = data; if (!d->wm_base) return; if (key == KEY_ESC && state) running = false; } static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { } static const struct wl_keyboard_listener keyboard_listener = { keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, }; static void seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { struct display *d = data; if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !d->keyboard) { d->keyboard = wl_seat_get_keyboard(seat); wl_keyboard_add_listener(d->keyboard, &keyboard_listener, d); } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && d->keyboard) { wl_keyboard_destroy(d->keyboard); d->keyboard = NULL; } } static const struct wl_seat_listener seat_listener = { seat_handle_capabilities, }; static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { xdg_wm_base_pong(shell, serial); } static const struct xdg_wm_base_listener wm_base_listener = { xdg_wm_base_ping, }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct display *d = data; if (strcmp(interface, "wl_compositor") == 0) { d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); } else if (strcmp(interface, "wl_seat") == 0) { d->seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); wl_seat_add_listener(d->seat, &seat_listener, d); } else if (strcmp(interface, "xdg_wm_base") == 0) { d->wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { d->dmabuf = wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, 1); zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, d); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static struct display * create_display(uint32_t requested_format) { struct display *display; display = malloc(sizeof *display); if (display == NULL) { fprintf(stderr, "out of memory\n"); exit(1); } display->display = wl_display_connect(NULL); assert(display->display); display->drm_format = requested_format; display->registry = wl_display_get_registry(display->display); wl_registry_add_listener(display->registry, ®istry_listener, display); wl_display_roundtrip(display->display); if (display->dmabuf == NULL) { fprintf(stderr, "No zwp_linux_dmabuf global\n"); exit(1); } wl_display_roundtrip(display->display); /* XXX: fake, because the compositor does not yet advertise anything */ display->requested_format_found = true; if (!display->requested_format_found) { fprintf(stderr, "DRM_FORMAT_YUYV not available\n"); exit(1); } return display; } static void destroy_display(struct display *display) { if (display->dmabuf) zwp_linux_dmabuf_v1_destroy(display->dmabuf); if (display->wm_base) xdg_wm_base_destroy(display->wm_base); if (display->fshell) zwp_fullscreen_shell_v1_release(display->fshell); if (display->compositor) wl_compositor_destroy(display->compositor); wl_registry_destroy(display->registry); wl_display_flush(display->display); wl_display_disconnect(display->display); free(display); } static void usage(const char *argv0) { printf("Usage: %s [V4L2 device] [V4L2 format] [DRM format]\n" "\n" "The default V4L2 device is /dev/video0\n" "\n" "Both formats are FOURCC values (see http://fourcc.org/)\n" "V4L2 formats are defined in \n" "DRM formats are defined in \n" "The default for both formats is YUYV.\n" "If the V4L2 and DRM formats differ, the data is simply " "reinterpreted rather than converted.\n", argv0); printf("\n" "How to set up Vivid the virtual video driver for testing:\n" "- build your kernel with CONFIG_VIDEO_VIVID=m\n" "- add this to a /etc/modprobe.d/ file:\n" " options vivid node_types=0x1 num_inputs=1 input_types=0x00\n" "- modprobe vivid and check which device was created,\n" " here we assume /dev/video0\n" "- set the pixel format:\n" " $ v4l2-ctl -d /dev/video0 --set-fmt-video=width=640,pixelformat=XR24\n" "- launch the demo:\n" " $ %s /dev/video0 XR24 XR24\n" "You should see a test pattern with color bars, and some text.\n" "\n" "More about vivid: https://www.kernel.org/doc/Documentation/video4linux/vivid.txt\n" "\n", argv0); exit(0); } static void signal_int(int signum) { running = false; } int main(int argc, char **argv) { struct sigaction sigint; struct display *display; struct window *window; const char *v4l_device; uint32_t v4l_format, drm_format; int ret = 0; if (argc < 2) { v4l_device = "/dev/video0"; } else if (!strcmp(argv[1], "--help")) { usage(argv[0]); } else { v4l_device = argv[1]; } if (argc < 3) v4l_format = parse_format("YUYV"); else v4l_format = parse_format(argv[2]); if (argc < 4) drm_format = v4l_format; else drm_format = parse_format(argv[3]); display = create_display(drm_format); display->format.format = v4l_format; window = create_window(display); if (!window) return 1; if (!v4l_connect(display, v4l_device)) return 1; if (!v4l_init(display, window->buffers)) return 1; sigint.sa_handler = signal_int; sigemptyset(&sigint.sa_mask); sigint.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sigint, NULL); /* Here we retrieve the linux-dmabuf objects, or error */ wl_display_roundtrip(display->display); /* In case of error, running will be 0 */ if (!running) return 1; /* We got all of our buffers, we can start the capture! */ if (!start_capture(display)) return 1; window->initialized = true; if (!window->wait_for_configure) redraw(window, NULL, 0); while (running && ret != -1) ret = wl_display_dispatch(display->display); fprintf(stderr, "simple-dmabuf-v4l exiting\n"); destroy_window(window); destroy_display(display); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.328963 weston-8.0.0/clients/simple-egl.c0000644000175000017460000005517200000000000017174 0ustar00simonwheel00000000000000/* * Copyright © 2011 Benjamin Franzke * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xdg-shell-client-protocol.h" #include #include #include "shared/helpers.h" #include "shared/platform.h" #include "shared/weston-egl-ext.h" struct window; struct seat; struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct xdg_wm_base *wm_base; struct wl_seat *seat; struct wl_pointer *pointer; struct wl_touch *touch; struct wl_keyboard *keyboard; struct wl_shm *shm; struct wl_cursor_theme *cursor_theme; struct wl_cursor *default_cursor; struct wl_surface *cursor_surface; struct { EGLDisplay dpy; EGLContext ctx; EGLConfig conf; } egl; struct window *window; PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage; }; struct geometry { int width, height; }; struct window { struct display *display; struct geometry geometry, window_size; struct { GLuint rotation_uniform; GLuint pos; GLuint col; } gl; uint32_t benchmark_time, frames; struct wl_egl_window *native; struct wl_surface *surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; EGLSurface egl_surface; struct wl_callback *callback; int fullscreen, maximized, opaque, buffer_size, frame_sync, delay; bool wait_for_configure; }; static const char *vert_shader_text = "uniform mat4 rotation;\n" "attribute vec4 pos;\n" "attribute vec4 color;\n" "varying vec4 v_color;\n" "void main() {\n" " gl_Position = rotation * pos;\n" " v_color = color;\n" "}\n"; static const char *frag_shader_text = "precision mediump float;\n" "varying vec4 v_color;\n" "void main() {\n" " gl_FragColor = v_color;\n" "}\n"; static int running = 1; static void init_egl(struct display *display, struct window *window) { static const struct { char *extension, *entrypoint; } swap_damage_ext_to_entrypoint[] = { { .extension = "EGL_EXT_swap_buffers_with_damage", .entrypoint = "eglSwapBuffersWithDamageEXT", }, { .extension = "EGL_KHR_swap_buffers_with_damage", .entrypoint = "eglSwapBuffersWithDamageKHR", }, }; static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; const char *extensions; EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 1, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; EGLint major, minor, n, count, i, size; EGLConfig *configs; EGLBoolean ret; if (window->opaque || window->buffer_size == 16) config_attribs[9] = 0; display->egl.dpy = weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, display->display, NULL); assert(display->egl.dpy); ret = eglInitialize(display->egl.dpy, &major, &minor); assert(ret == EGL_TRUE); ret = eglBindAPI(EGL_OPENGL_ES_API); assert(ret == EGL_TRUE); if (!eglGetConfigs(display->egl.dpy, NULL, 0, &count) || count < 1) assert(0); configs = calloc(count, sizeof *configs); assert(configs); ret = eglChooseConfig(display->egl.dpy, config_attribs, configs, count, &n); assert(ret && n >= 1); for (i = 0; i < n; i++) { eglGetConfigAttrib(display->egl.dpy, configs[i], EGL_BUFFER_SIZE, &size); if (window->buffer_size == size) { display->egl.conf = configs[i]; break; } } free(configs); if (display->egl.conf == NULL) { fprintf(stderr, "did not find config with buffer size %d\n", window->buffer_size); exit(EXIT_FAILURE); } display->egl.ctx = eglCreateContext(display->egl.dpy, display->egl.conf, EGL_NO_CONTEXT, context_attribs); assert(display->egl.ctx); display->swap_buffers_with_damage = NULL; extensions = eglQueryString(display->egl.dpy, EGL_EXTENSIONS); if (extensions && weston_check_egl_extension(extensions, "EGL_EXT_buffer_age")) { for (i = 0; i < (int) ARRAY_LENGTH(swap_damage_ext_to_entrypoint); i++) { if (weston_check_egl_extension(extensions, swap_damage_ext_to_entrypoint[i].extension)) { /* The EXTPROC is identical to the KHR one */ display->swap_buffers_with_damage = (PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC) eglGetProcAddress(swap_damage_ext_to_entrypoint[i].entrypoint); break; } } } if (display->swap_buffers_with_damage) printf("has EGL_EXT_buffer_age and %s\n", swap_damage_ext_to_entrypoint[i].extension); } static void fini_egl(struct display *display) { eglTerminate(display->egl.dpy); eglReleaseThread(); } static GLuint create_shader(struct window *window, const char *source, GLenum shader_type) { GLuint shader; GLint status; shader = glCreateShader(shader_type); assert(shader != 0); glShaderSource(shader, 1, (const char **) &source, NULL); glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (!status) { char log[1000]; GLsizei len; glGetShaderInfoLog(shader, 1000, &len, log); fprintf(stderr, "Error: compiling %s: %.*s\n", shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", len, log); exit(1); } return shader; } static void init_gl(struct window *window) { GLuint frag, vert; GLuint program; GLint status; frag = create_shader(window, frag_shader_text, GL_FRAGMENT_SHADER); vert = create_shader(window, vert_shader_text, GL_VERTEX_SHADER); program = glCreateProgram(); glAttachShader(program, frag); glAttachShader(program, vert); glLinkProgram(program); glGetProgramiv(program, GL_LINK_STATUS, &status); if (!status) { char log[1000]; GLsizei len; glGetProgramInfoLog(program, 1000, &len, log); fprintf(stderr, "Error: linking:\n%.*s\n", len, log); exit(1); } glUseProgram(program); window->gl.pos = 0; window->gl.col = 1; glBindAttribLocation(program, window->gl.pos, "pos"); glBindAttribLocation(program, window->gl.col, "color"); glLinkProgram(program); window->gl.rotation_uniform = glGetUniformLocation(program, "rotation"); } static void handle_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; xdg_surface_ack_configure(surface, serial); window->wait_for_configure = false; } static const struct xdg_surface_listener xdg_surface_listener = { handle_surface_configure }; static void handle_toplevel_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { struct window *window = data; uint32_t *p; window->fullscreen = 0; window->maximized = 0; wl_array_for_each(p, states) { uint32_t state = *p; switch (state) { case XDG_TOPLEVEL_STATE_FULLSCREEN: window->fullscreen = 1; break; case XDG_TOPLEVEL_STATE_MAXIMIZED: window->maximized = 1; break; } } if (width > 0 && height > 0) { if (!window->fullscreen && !window->maximized) { window->window_size.width = width; window->window_size.height = height; } window->geometry.width = width; window->geometry.height = height; } else if (!window->fullscreen && !window->maximized) { window->geometry = window->window_size; } if (window->native) wl_egl_window_resize(window->native, window->geometry.width, window->geometry.height, 0, 0); } static void handle_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } static const struct xdg_toplevel_listener xdg_toplevel_listener = { handle_toplevel_configure, handle_toplevel_close, }; static void create_surface(struct window *window) { struct display *display = window->display; EGLBoolean ret; window->surface = wl_compositor_create_surface(display->compositor); window->native = wl_egl_window_create(window->surface, window->geometry.width, window->geometry.height); window->egl_surface = weston_platform_create_egl_surface(display->egl.dpy, display->egl.conf, window->native, NULL); window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); xdg_toplevel_set_title(window->xdg_toplevel, "simple-egl"); window->wait_for_configure = true; wl_surface_commit(window->surface); ret = eglMakeCurrent(window->display->egl.dpy, window->egl_surface, window->egl_surface, window->display->egl.ctx); assert(ret == EGL_TRUE); if (!window->frame_sync) eglSwapInterval(display->egl.dpy, 0); if (!display->wm_base) return; if (window->fullscreen) xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); } static void destroy_surface(struct window *window) { /* Required, otherwise segfault in egl_dri2.c: dri2_make_current() * on eglReleaseThread(). */ eglMakeCurrent(window->display->egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); weston_platform_destroy_egl_surface(window->display->egl.dpy, window->egl_surface); wl_egl_window_destroy(window->native); if (window->xdg_toplevel) xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) xdg_surface_destroy(window->xdg_surface); wl_surface_destroy(window->surface); if (window->callback) wl_callback_destroy(window->callback); } static void redraw(void *data, struct wl_callback *callback, uint32_t time) { struct window *window = data; struct display *display = window->display; static const GLfloat verts[3][2] = { { -0.5, -0.5 }, { 0.5, -0.5 }, { 0, 0.5 } }; static const GLfloat colors[3][3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; GLfloat angle; GLfloat rotation[4][4] = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } }; static const uint32_t speed_div = 5, benchmark_interval = 5; struct wl_region *region; EGLint rect[4]; EGLint buffer_age = 0; struct timeval tv; assert(window->callback == callback); window->callback = NULL; if (callback) wl_callback_destroy(callback); gettimeofday(&tv, NULL); time = tv.tv_sec * 1000 + tv.tv_usec / 1000; if (window->frames == 0) window->benchmark_time = time; if (time - window->benchmark_time > (benchmark_interval * 1000)) { printf("%d frames in %d seconds: %f fps\n", window->frames, benchmark_interval, (float) window->frames / benchmark_interval); window->benchmark_time = time; window->frames = 0; } angle = (time / speed_div) % 360 * M_PI / 180.0; rotation[0][0] = cos(angle); rotation[0][2] = sin(angle); rotation[2][0] = -sin(angle); rotation[2][2] = cos(angle); if (display->swap_buffers_with_damage) eglQuerySurface(display->egl.dpy, window->egl_surface, EGL_BUFFER_AGE_EXT, &buffer_age); glViewport(0, 0, window->geometry.width, window->geometry.height); glUniformMatrix4fv(window->gl.rotation_uniform, 1, GL_FALSE, (GLfloat *) rotation); glClearColor(0.0, 0.0, 0.0, 0.5); glClear(GL_COLOR_BUFFER_BIT); glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); glVertexAttribPointer(window->gl.col, 3, GL_FLOAT, GL_FALSE, 0, colors); glEnableVertexAttribArray(window->gl.pos); glEnableVertexAttribArray(window->gl.col); glDrawArrays(GL_TRIANGLES, 0, 3); glDisableVertexAttribArray(window->gl.pos); glDisableVertexAttribArray(window->gl.col); usleep(window->delay); if (window->opaque || window->fullscreen) { region = wl_compositor_create_region(window->display->compositor); wl_region_add(region, 0, 0, window->geometry.width, window->geometry.height); wl_surface_set_opaque_region(window->surface, region); wl_region_destroy(region); } else { wl_surface_set_opaque_region(window->surface, NULL); } if (display->swap_buffers_with_damage && buffer_age > 0) { rect[0] = window->geometry.width / 4 - 1; rect[1] = window->geometry.height / 4 - 1; rect[2] = window->geometry.width / 2 + 2; rect[3] = window->geometry.height / 2 + 2; display->swap_buffers_with_damage(display->egl.dpy, window->egl_surface, rect, 1); } else { eglSwapBuffers(display->egl.dpy, window->egl_surface); } window->frames++; } static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) { struct display *display = data; struct wl_buffer *buffer; struct wl_cursor *cursor = display->default_cursor; struct wl_cursor_image *image; if (display->window->fullscreen) wl_pointer_set_cursor(pointer, serial, NULL, 0, 0); else if (cursor) { image = display->default_cursor->images[0]; buffer = wl_cursor_image_get_buffer(image); if (!buffer) return; wl_pointer_set_cursor(pointer, serial, display->cursor_surface, image->hotspot_x, image->hotspot_y); wl_surface_attach(display->cursor_surface, buffer, 0, 0); wl_surface_damage(display->cursor_surface, 0, 0, image->width, image->height); wl_surface_commit(display->cursor_surface); } } static void pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { } static void pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { } static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { struct display *display = data; if (!display->window->xdg_toplevel) return; if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) xdg_toplevel_move(display->window->xdg_toplevel, display->seat, serial); } static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { } static const struct wl_pointer_listener pointer_listener = { pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis, }; static void touch_handle_down(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { struct display *d = (struct display *)data; if (!d->wm_base) return; xdg_toplevel_move(d->window->xdg_toplevel, d->seat, serial); } static void touch_handle_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id) { } static void touch_handle_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { } static void touch_handle_frame(void *data, struct wl_touch *wl_touch) { } static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) { } static const struct wl_touch_listener touch_listener = { touch_handle_down, touch_handle_up, touch_handle_motion, touch_handle_frame, touch_handle_cancel, }; static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { /* Just so we don’t leak the keymap fd */ close(fd); } static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { } static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { } static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { struct display *d = data; if (!d->wm_base) return; if (key == KEY_F11 && state) { if (d->window->fullscreen) xdg_toplevel_unset_fullscreen(d->window->xdg_toplevel); else xdg_toplevel_set_fullscreen(d->window->xdg_toplevel, NULL); } else if (key == KEY_ESC && state) running = 0; } static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { } static const struct wl_keyboard_listener keyboard_listener = { keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, }; static void seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { struct display *d = data; if ((caps & WL_SEAT_CAPABILITY_POINTER) && !d->pointer) { d->pointer = wl_seat_get_pointer(seat); wl_pointer_add_listener(d->pointer, &pointer_listener, d); } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && d->pointer) { wl_pointer_destroy(d->pointer); d->pointer = NULL; } if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !d->keyboard) { d->keyboard = wl_seat_get_keyboard(seat); wl_keyboard_add_listener(d->keyboard, &keyboard_listener, d); } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && d->keyboard) { wl_keyboard_destroy(d->keyboard); d->keyboard = NULL; } if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !d->touch) { d->touch = wl_seat_get_touch(seat); wl_touch_set_user_data(d->touch, d); wl_touch_add_listener(d->touch, &touch_listener, d); } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && d->touch) { wl_touch_destroy(d->touch); d->touch = NULL; } } static const struct wl_seat_listener seat_listener = { seat_handle_capabilities, }; static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { xdg_wm_base_pong(shell, serial); } static const struct xdg_wm_base_listener wm_base_listener = { xdg_wm_base_ping, }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct display *d = data; if (strcmp(interface, "wl_compositor") == 0) { d->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, MIN(version, 4)); } else if (strcmp(interface, "xdg_wm_base") == 0) { d->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, d); } else if (strcmp(interface, "wl_seat") == 0) { d->seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); wl_seat_add_listener(d->seat, &seat_listener, d); } else if (strcmp(interface, "wl_shm") == 0) { d->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); d->cursor_theme = wl_cursor_theme_load(NULL, 32, d->shm); if (!d->cursor_theme) { fprintf(stderr, "unable to load default theme\n"); return; } d->default_cursor = wl_cursor_theme_get_cursor(d->cursor_theme, "left_ptr"); if (!d->default_cursor) { fprintf(stderr, "unable to load default left pointer\n"); // TODO: abort ? } } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static void signal_int(int signum) { running = 0; } static void usage(int error_code) { fprintf(stderr, "Usage: simple-egl [OPTIONS]\n\n" " -d \tBuffer swap delay in microseconds\n" " -f\tRun in fullscreen mode\n" " -o\tCreate an opaque surface\n" " -s\tUse a 16 bpp EGL config\n" " -b\tDon't sync to compositor redraw (eglSwapInterval 0)\n" " -h\tThis help text\n\n"); exit(error_code); } int main(int argc, char **argv) { struct sigaction sigint; struct display display = { 0 }; struct window window = { 0 }; int i, ret = 0; window.display = &display; display.window = &window; window.geometry.width = 250; window.geometry.height = 250; window.window_size = window.geometry; window.buffer_size = 32; window.frame_sync = 1; window.delay = 0; for (i = 1; i < argc; i++) { if (strcmp("-d", argv[i]) == 0 && i+1 < argc) window.delay = atoi(argv[++i]); else if (strcmp("-f", argv[i]) == 0) window.fullscreen = 1; else if (strcmp("-o", argv[i]) == 0) window.opaque = 1; else if (strcmp("-s", argv[i]) == 0) window.buffer_size = 16; else if (strcmp("-b", argv[i]) == 0) window.frame_sync = 0; else if (strcmp("-h", argv[i]) == 0) usage(EXIT_SUCCESS); else usage(EXIT_FAILURE); } display.display = wl_display_connect(NULL); assert(display.display); display.registry = wl_display_get_registry(display.display); wl_registry_add_listener(display.registry, ®istry_listener, &display); wl_display_roundtrip(display.display); init_egl(&display, &window); create_surface(&window); init_gl(&window); display.cursor_surface = wl_compositor_create_surface(display.compositor); sigint.sa_handler = signal_int; sigemptyset(&sigint.sa_mask); sigint.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sigint, NULL); /* The mainloop here is a little subtle. Redrawing will cause * EGL to read events so we can just call * wl_display_dispatch_pending() to handle any events that got * queued up as a side effect. */ while (running && ret != -1) { if (window.wait_for_configure) { ret = wl_display_dispatch(display.display); } else { ret = wl_display_dispatch_pending(display.display); redraw(&window, NULL, 0); } } fprintf(stderr, "simple-egl exiting\n"); destroy_surface(&window); fini_egl(&display); wl_surface_destroy(display.cursor_surface); if (display.cursor_theme) wl_cursor_theme_destroy(display.cursor_theme); if (display.wm_base) xdg_wm_base_destroy(display.wm_base); if (display.compositor) wl_compositor_destroy(display.compositor); wl_registry_destroy(display.registry); wl_display_flush(display.display); wl_display_disconnect(display.display); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.328963 weston-8.0.0/clients/simple-im.c0000644000175000017460000003255700000000000017034 0ustar00simonwheel00000000000000/* * Copyright © 2012 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include "window.h" #include "input-method-unstable-v1-client-protocol.h" #include "shared/helpers.h" enum compose_state { state_normal, state_compose }; struct compose_seq { uint32_t keys[4]; const char *text; }; struct simple_im; typedef void (*keyboard_input_key_handler_t)(struct simple_im *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t unicode, enum wl_keyboard_key_state state); struct simple_im { struct zwp_input_method_v1 *input_method; struct zwp_input_method_context_v1 *context; struct wl_display *display; struct wl_registry *registry; struct wl_keyboard *keyboard; enum compose_state compose_state; struct compose_seq compose_seq; struct xkb_context *xkb_context; uint32_t modifiers; struct xkb_keymap *keymap; struct xkb_state *state; xkb_mod_mask_t control_mask; xkb_mod_mask_t alt_mask; xkb_mod_mask_t shift_mask; keyboard_input_key_handler_t key_handler; uint32_t serial; }; static const struct compose_seq compose_seqs[] = { { { XKB_KEY_quotedbl, XKB_KEY_A, 0 }, "Ä" }, { { XKB_KEY_quotedbl, XKB_KEY_O, 0 }, "Ö" }, { { XKB_KEY_quotedbl, XKB_KEY_U, 0 }, "Ü" }, { { XKB_KEY_quotedbl, XKB_KEY_a, 0 }, "ä" }, { { XKB_KEY_quotedbl, XKB_KEY_o, 0 }, "ö" }, { { XKB_KEY_quotedbl, XKB_KEY_u, 0 }, "ü" }, { { XKB_KEY_apostrophe, XKB_KEY_A, 0 }, "Á" }, { { XKB_KEY_apostrophe, XKB_KEY_a, 0 }, "á" }, { { XKB_KEY_slash, XKB_KEY_O, 0 }, "Ø" }, { { XKB_KEY_slash, XKB_KEY_o, 0 }, "ø" }, { { XKB_KEY_less, XKB_KEY_3, 0 }, "♥" }, { { XKB_KEY_A, XKB_KEY_A, 0 }, "Å" }, { { XKB_KEY_A, XKB_KEY_E, 0 }, "Æ" }, { { XKB_KEY_O, XKB_KEY_C, 0 }, "©" }, { { XKB_KEY_O, XKB_KEY_R, 0 }, "®" }, { { XKB_KEY_s, XKB_KEY_s, 0 }, "ß" }, { { XKB_KEY_a, XKB_KEY_e, 0 }, "æ" }, { { XKB_KEY_a, XKB_KEY_a, 0 }, "å" }, }; static const uint32_t ignore_keys_on_compose[] = { XKB_KEY_Shift_L, XKB_KEY_Shift_R }; static void handle_surrounding_text(void *data, struct zwp_input_method_context_v1 *context, const char *text, uint32_t cursor, uint32_t anchor) { fprintf(stderr, "Surrounding text updated: %s\n", text); } static void handle_reset(void *data, struct zwp_input_method_context_v1 *context) { struct simple_im *keyboard = data; fprintf(stderr, "Reset pre-edit buffer\n"); keyboard->compose_state = state_normal; } static void handle_content_type(void *data, struct zwp_input_method_context_v1 *context, uint32_t hint, uint32_t purpose) { } static void handle_invoke_action(void *data, struct zwp_input_method_context_v1 *context, uint32_t button, uint32_t index) { } static void handle_commit_state(void *data, struct zwp_input_method_context_v1 *context, uint32_t serial) { struct simple_im *keyboard = data; keyboard->serial = serial; } static void handle_preferred_language(void *data, struct zwp_input_method_context_v1 *context, const char *language) { } static const struct zwp_input_method_context_v1_listener input_method_context_listener = { handle_surrounding_text, handle_reset, handle_content_type, handle_invoke_action, handle_commit_state, handle_preferred_language }; static void input_method_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { struct simple_im *keyboard = data; char *map_str; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { close(fd); return; } map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (map_str == MAP_FAILED) { close(fd); return; } keyboard->keymap = xkb_keymap_new_from_string(keyboard->xkb_context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap(map_str, size); close(fd); if (!keyboard->keymap) { fprintf(stderr, "Failed to compile keymap\n"); return; } keyboard->state = xkb_state_new(keyboard->keymap); if (!keyboard->state) { fprintf(stderr, "Failed to create XKB state\n"); xkb_keymap_unref(keyboard->keymap); return; } keyboard->control_mask = 1 << xkb_keymap_mod_get_index(keyboard->keymap, "Control"); keyboard->alt_mask = 1 << xkb_keymap_mod_get_index(keyboard->keymap, "Mod1"); keyboard->shift_mask = 1 << xkb_keymap_mod_get_index(keyboard->keymap, "Shift"); } static void input_method_keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state_w) { struct simple_im *keyboard = data; uint32_t code; uint32_t num_syms; const xkb_keysym_t *syms; xkb_keysym_t sym; enum wl_keyboard_key_state state = state_w; if (!keyboard->state) return; code = key + 8; num_syms = xkb_state_key_get_syms(keyboard->state, code, &syms); sym = XKB_KEY_NoSymbol; if (num_syms == 1) sym = syms[0]; if (keyboard->key_handler) (*keyboard->key_handler)(keyboard, serial, time, key, sym, state); } static void input_method_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { struct simple_im *keyboard = data; struct zwp_input_method_context_v1 *context = keyboard->context; xkb_mod_mask_t mask; xkb_state_update_mask(keyboard->state, mods_depressed, mods_latched, mods_locked, 0, 0, group); mask = xkb_state_serialize_mods(keyboard->state, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); keyboard->modifiers = 0; if (mask & keyboard->control_mask) keyboard->modifiers |= MOD_CONTROL_MASK; if (mask & keyboard->alt_mask) keyboard->modifiers |= MOD_ALT_MASK; if (mask & keyboard->shift_mask) keyboard->modifiers |= MOD_SHIFT_MASK; zwp_input_method_context_v1_modifiers(context, serial, mods_depressed, mods_depressed, mods_latched, group); } static const struct wl_keyboard_listener input_method_keyboard_listener = { input_method_keyboard_keymap, NULL, /* enter */ NULL, /* leave */ input_method_keyboard_key, input_method_keyboard_modifiers }; static void input_method_activate(void *data, struct zwp_input_method_v1 *input_method, struct zwp_input_method_context_v1 *context) { struct simple_im *keyboard = data; if (keyboard->context) zwp_input_method_context_v1_destroy(keyboard->context); keyboard->compose_state = state_normal; keyboard->serial = 0; keyboard->context = context; zwp_input_method_context_v1_add_listener(context, &input_method_context_listener, keyboard); keyboard->keyboard = zwp_input_method_context_v1_grab_keyboard(context); wl_keyboard_add_listener(keyboard->keyboard, &input_method_keyboard_listener, keyboard); } static void input_method_deactivate(void *data, struct zwp_input_method_v1 *input_method, struct zwp_input_method_context_v1 *context) { struct simple_im *keyboard = data; if (!keyboard->context) return; zwp_input_method_context_v1_destroy(keyboard->context); keyboard->context = NULL; } static const struct zwp_input_method_v1_listener input_method_listener = { input_method_activate, input_method_deactivate }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct simple_im *keyboard = data; if (!strcmp(interface, "zwp_input_method_v1")) { keyboard->input_method = wl_registry_bind(registry, name, &zwp_input_method_v1_interface, 1); zwp_input_method_v1_add_listener(keyboard->input_method, &input_method_listener, keyboard); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static int compare_compose_keys(const void *c1, const void *c2) { const struct compose_seq *cs1 = c1; const struct compose_seq *cs2 = c2; int i; for (i = 0; cs1->keys[i] != 0 && cs2->keys[i] != 0; i++) { if (cs1->keys[i] != cs2->keys[i]) return cs1->keys[i] - cs2->keys[i]; } if (cs1->keys[i] == cs2->keys[i] || cs1->keys[i] == 0) return 0; return cs1->keys[i] - cs2->keys[i]; } static void simple_im_key_handler(struct simple_im *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state) { struct zwp_input_method_context_v1 *context = keyboard->context; char text[64]; if (sym == XKB_KEY_Multi_key && state == WL_KEYBOARD_KEY_STATE_RELEASED && keyboard->compose_state == state_normal) { keyboard->compose_state = state_compose; memset(&keyboard->compose_seq, 0, sizeof(struct compose_seq)); return; } if (keyboard->compose_state == state_compose) { uint32_t i = 0; struct compose_seq *cs; if (state == WL_KEYBOARD_KEY_STATE_PRESSED) return; for (i = 0; i < ARRAY_LENGTH(ignore_keys_on_compose); i++) { if (sym == ignore_keys_on_compose[i]) { zwp_input_method_context_v1_key(context, keyboard->serial, time, key, state); return; } } for (i = 0; keyboard->compose_seq.keys[i] != 0; i++); keyboard->compose_seq.keys[i] = sym; cs = bsearch (&keyboard->compose_seq, compose_seqs, ARRAY_LENGTH(compose_seqs), sizeof(compose_seqs[0]), compare_compose_keys); if (cs) { if (cs->keys[i + 1] == 0) { zwp_input_method_context_v1_preedit_cursor(keyboard->context, 0); zwp_input_method_context_v1_preedit_string(keyboard->context, keyboard->serial, "", ""); zwp_input_method_context_v1_cursor_position(keyboard->context, 0, 0); zwp_input_method_context_v1_commit_string(keyboard->context, keyboard->serial, cs->text); keyboard->compose_state = state_normal; } else { uint32_t j = 0, idx = 0; for (; j <= i; j++) { idx += xkb_keysym_to_utf8(cs->keys[j], text + idx, sizeof(text) - idx); } zwp_input_method_context_v1_preedit_cursor(keyboard->context, strlen(text)); zwp_input_method_context_v1_preedit_string(keyboard->context, keyboard->serial, text, text); } } else { uint32_t j = 0, idx = 0; for (; j <= i; j++) { idx += xkb_keysym_to_utf8(keyboard->compose_seq.keys[j], text + idx, sizeof(text) - idx); } zwp_input_method_context_v1_preedit_cursor(keyboard->context, 0); zwp_input_method_context_v1_preedit_string(keyboard->context, keyboard->serial, "", ""); zwp_input_method_context_v1_cursor_position(keyboard->context, 0, 0); zwp_input_method_context_v1_commit_string(keyboard->context, keyboard->serial, text); keyboard->compose_state = state_normal; } return; } if (xkb_keysym_to_utf8(sym, text, sizeof(text)) <= 0) { zwp_input_method_context_v1_key(context, serial, time, key, state); return; } if (state == WL_KEYBOARD_KEY_STATE_PRESSED) return; zwp_input_method_context_v1_cursor_position(keyboard->context, 0, 0); zwp_input_method_context_v1_commit_string(keyboard->context, keyboard->serial, text); } int main(int argc, char *argv[]) { struct simple_im simple_im; int ret = 0; memset(&simple_im, 0, sizeof(simple_im)); simple_im.display = wl_display_connect(NULL); if (simple_im.display == NULL) { fprintf(stderr, "Failed to connect to server: %s\n", strerror(errno)); return -1; } simple_im.registry = wl_display_get_registry(simple_im.display); wl_registry_add_listener(simple_im.registry, ®istry_listener, &simple_im); wl_display_roundtrip(simple_im.display); if (simple_im.input_method == NULL) { fprintf(stderr, "No input_method global\n"); return -1; } simple_im.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (simple_im.xkb_context == NULL) { fprintf(stderr, "Failed to create XKB context\n"); return -1; } simple_im.context = NULL; simple_im.key_handler = simple_im_key_handler; while (ret != -1) ret = wl_display_dispatch(simple_im.display); if (ret == -1) { fprintf(stderr, "Dispatch error: %s\n", strerror(errno)); return -1; } return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.328963 weston-8.0.0/clients/simple-shm.c0000644000175000017460000003273100000000000017210 0ustar00simonwheel00000000000000/* * Copyright © 2011 Benjamin Franzke * Copyright © 2010 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "shared/os-compatibility.h" #include #include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct xdg_wm_base *wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct wl_shm *shm; bool has_xrgb; }; struct buffer { struct wl_buffer *buffer; void *shm_data; int busy; }; struct window { struct display *display; int width, height; struct wl_surface *surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct buffer buffers[2]; struct buffer *prev_buffer; struct wl_callback *callback; bool wait_for_configure; }; static int running = 1; static void redraw(void *data, struct wl_callback *callback, uint32_t time); static void buffer_release(void *data, struct wl_buffer *buffer) { struct buffer *mybuf = data; mybuf->busy = 0; } static const struct wl_buffer_listener buffer_listener = { buffer_release }; static int create_shm_buffer(struct display *display, struct buffer *buffer, int width, int height, uint32_t format) { struct wl_shm_pool *pool; int fd, size, stride; void *data; stride = width * 4; size = stride * height; fd = os_create_anonymous_file(size); if (fd < 0) { fprintf(stderr, "creating a buffer file for %d B failed: %s\n", size, strerror(errno)); return -1; } data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return -1; } pool = wl_shm_create_pool(display->shm, fd, size); buffer->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); wl_shm_pool_destroy(pool); close(fd); buffer->shm_data = data; return 0; } static void handle_xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; xdg_surface_ack_configure(surface, serial); if (window->wait_for_configure) { redraw(window, NULL, 0); window->wait_for_configure = false; } } static const struct xdg_surface_listener xdg_surface_listener = { handle_xdg_surface_configure, }; static void handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *state) { } static void handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = 0; } static const struct xdg_toplevel_listener xdg_toplevel_listener = { handle_xdg_toplevel_configure, handle_xdg_toplevel_close, }; static struct window * create_window(struct display *display, int width, int height) { struct window *window; window = zalloc(sizeof *window); if (!window) return NULL; window->callback = NULL; window->display = display; window->width = width; window->height = height; window->surface = wl_compositor_create_surface(display->compositor); if (display->wm_base) { window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); assert(window->xdg_surface); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); assert(window->xdg_toplevel); xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); xdg_toplevel_set_title(window->xdg_toplevel, "simple-shm"); wl_surface_commit(window->surface); window->wait_for_configure = true; } else if (display->fshell) { zwp_fullscreen_shell_v1_present_surface(display->fshell, window->surface, ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT, NULL); } else { assert(0); } return window; } static void destroy_window(struct window *window) { if (window->callback) wl_callback_destroy(window->callback); if (window->buffers[0].buffer) wl_buffer_destroy(window->buffers[0].buffer); if (window->buffers[1].buffer) wl_buffer_destroy(window->buffers[1].buffer); if (window->xdg_toplevel) xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) xdg_surface_destroy(window->xdg_surface); wl_surface_destroy(window->surface); free(window); } static struct buffer * window_next_buffer(struct window *window) { struct buffer *buffer; int ret = 0; if (!window->buffers[0].busy) buffer = &window->buffers[0]; else if (!window->buffers[1].busy) buffer = &window->buffers[1]; else return NULL; if (!buffer->buffer) { ret = create_shm_buffer(window->display, buffer, window->width, window->height, WL_SHM_FORMAT_XRGB8888); if (ret < 0) return NULL; /* paint the padding */ memset(buffer->shm_data, 0xff, window->width * window->height * 4); } return buffer; } static void paint_pixels(void *image, int padding, int width, int height, uint32_t time) { const int halfh = padding + (height - padding * 2) / 2; const int halfw = padding + (width - padding * 2) / 2; int ir, or; uint32_t *pixel = image; int y; /* squared radii thresholds */ or = (halfw < halfh ? halfw : halfh) - 8; ir = or - 32; or *= or; ir *= ir; pixel += padding * width; for (y = padding; y < height - padding; y++) { int x; int y2 = (y - halfh) * (y - halfh); pixel += padding; for (x = padding; x < width - padding; x++) { uint32_t v; /* squared distance from center */ int r2 = (x - halfw) * (x - halfw) + y2; if (r2 < ir) v = (r2 / 32 + time / 64) * 0x0080401; else if (r2 < or) v = (y + time / 32) * 0x0080401; else v = (x + time / 16) * 0x0080401; v &= 0x00ffffff; /* cross if compositor uses X from XRGB as alpha */ if (abs(x - y) > 6 && abs(x + y - height) > 6) v |= 0xff000000; *pixel++ = v; } pixel += padding; } } static const struct wl_callback_listener frame_listener; static void redraw(void *data, struct wl_callback *callback, uint32_t time) { struct window *window = data; struct buffer *buffer; buffer = window_next_buffer(window); if (!buffer) { fprintf(stderr, !callback ? "Failed to create the first buffer.\n" : "Both buffers busy at redraw(). Server bug?\n"); abort(); } paint_pixels(buffer->shm_data, 20, window->width, window->height, time); wl_surface_attach(window->surface, buffer->buffer, 0, 0); wl_surface_damage(window->surface, 20, 20, window->width - 40, window->height - 40); if (callback) wl_callback_destroy(callback); window->callback = wl_surface_frame(window->surface); wl_callback_add_listener(window->callback, &frame_listener, window); wl_surface_commit(window->surface); buffer->busy = 1; } static const struct wl_callback_listener frame_listener = { redraw }; static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { struct display *d = data; if (format == WL_SHM_FORMAT_XRGB8888) d->has_xrgb = true; } struct wl_shm_listener shm_listener = { shm_format }; static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { xdg_wm_base_pong(shell, serial); } static const struct xdg_wm_base_listener xdg_wm_base_listener = { xdg_wm_base_ping, }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct display *d = data; if (strcmp(interface, "wl_compositor") == 0) { d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); } else if (strcmp(interface, "xdg_wm_base") == 0) { d->wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); } else if (strcmp(interface, "wl_shm") == 0) { d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); wl_shm_add_listener(d->shm, &shm_listener, d); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static struct display * create_display(void) { struct display *display; display = malloc(sizeof *display); if (display == NULL) { fprintf(stderr, "out of memory\n"); exit(1); } display->display = wl_display_connect(NULL); assert(display->display); display->has_xrgb = false; display->registry = wl_display_get_registry(display->display); wl_registry_add_listener(display->registry, ®istry_listener, display); wl_display_roundtrip(display->display); if (display->shm == NULL) { fprintf(stderr, "No wl_shm global\n"); exit(1); } wl_display_roundtrip(display->display); /* * Why do we need two roundtrips here? * * wl_display_get_registry() sends a request to the server, to which * the server replies by emitting the wl_registry.global events. * The first wl_display_roundtrip() sends wl_display.sync. The server * first processes the wl_display.get_registry which includes sending * the global events, and then processes the sync. Therefore when the * sync (roundtrip) returns, we are guaranteed to have received and * processed all the global events. * * While we are inside the first wl_display_roundtrip(), incoming * events are dispatched, which causes registry_handle_global() to * be called for each global. One of these globals is wl_shm. * registry_handle_global() sends wl_registry.bind request for the * wl_shm global. However, wl_registry.bind request is sent after * the first wl_display.sync, so the reply to the sync comes before * the initial events of the wl_shm object. * * The initial events that get sent as a reply to binding to wl_shm * include wl_shm.format. These tell us which pixel formats are * supported, and we need them before we can create buffers. They * don't change at runtime, so we receive them as part of init. * * When the reply to the first sync comes, the server may or may not * have sent the initial wl_shm events. Therefore we need the second * wl_display_roundtrip() call here. * * The server processes the wl_registry.bind for wl_shm first, and * the second wl_display.sync next. During our second call to * wl_display_roundtrip() the initial wl_shm events are received and * processed. Finally, when the reply to the second wl_display.sync * arrives, it guarantees we have processed all wl_shm initial events. * * This sequence contains two examples on how wl_display_roundtrip() * can be used to guarantee, that all reply events to a request * have been received and processed. This is a general Wayland * technique. */ if (!display->has_xrgb) { fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); exit(1); } return display; } static void destroy_display(struct display *display) { if (display->shm) wl_shm_destroy(display->shm); if (display->wm_base) xdg_wm_base_destroy(display->wm_base); if (display->fshell) zwp_fullscreen_shell_v1_release(display->fshell); if (display->compositor) wl_compositor_destroy(display->compositor); wl_registry_destroy(display->registry); wl_display_flush(display->display); wl_display_disconnect(display->display); free(display); } static void signal_int(int signum) { running = 0; } int main(int argc, char **argv) { struct sigaction sigint; struct display *display; struct window *window; int ret = 0; display = create_display(); window = create_window(display, 250, 250); if (!window) return 1; sigint.sa_handler = signal_int; sigemptyset(&sigint.sa_mask); sigint.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sigint, NULL); /* Initialise damage to full surface, so the padding gets painted */ wl_surface_damage(window->surface, 0, 0, window->width, window->height); if (!window->wait_for_configure) redraw(window, NULL, 0); while (running && ret != -1) ret = wl_display_dispatch(display->display); fprintf(stderr, "simple-shm exiting\n"); destroy_window(window); destroy_display(display); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.328963 weston-8.0.0/clients/simple-touch.c0000644000175000017460000002113300000000000017535 0ustar00simonwheel00000000000000/* * Copyright © 2011 Benjamin Franzke * Copyright © 2011 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "shared/helpers.h" #include "shared/os-compatibility.h" struct seat { struct touch *touch; struct wl_seat *seat; struct wl_touch *wl_touch; }; struct touch { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct wl_shell *shell; struct wl_shm *shm; struct wl_pointer *pointer; struct wl_keyboard *keyboard; struct wl_surface *surface; struct wl_shell_surface *shell_surface; struct wl_buffer *buffer; int has_argb; int width, height; void *data; }; static void create_shm_buffer(struct touch *touch) { struct wl_shm_pool *pool; int fd, size, stride; stride = touch->width * 4; size = stride * touch->height; fd = os_create_anonymous_file(size); if (fd < 0) { fprintf(stderr, "creating a buffer file for %d B failed: %s\n", size, strerror(errno)); exit(1); } touch->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (touch->data == MAP_FAILED) { fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); exit(1); } pool = wl_shm_create_pool(touch->shm, fd, size); touch->buffer = wl_shm_pool_create_buffer(pool, 0, touch->width, touch->height, stride, WL_SHM_FORMAT_ARGB8888); wl_shm_pool_destroy(pool); close(fd); } static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { struct touch *touch = data; if (format == WL_SHM_FORMAT_ARGB8888) touch->has_argb = 1; } struct wl_shm_listener shm_listener = { shm_format }; static void touch_paint(struct touch *touch, int32_t x, int32_t y, int32_t id) { uint32_t *p, c; static const uint32_t colors[] = { 0xffff0000, 0xffffff00, 0xff0000ff, 0xffff00ff, 0xff00ff00, 0xff00ffff, }; if (id < (int32_t) ARRAY_LENGTH(colors)) c = colors[id]; else c = 0xffffffff; if (x < 2 || x >= touch->width - 2 || y < 2 || y >= touch->height - 2) return; p = (uint32_t *) touch->data + (x - 2) + (y - 2) * touch->width; p[2] = c; p += touch->width; p[1] = c; p[2] = c; p[3] = c; p += touch->width; p[0] = c; p[1] = c; p[2] = c; p[3] = c; p[4] = c; p += touch->width; p[1] = c; p[2] = c; p[3] = c; p += touch->width; p[2] = c; wl_surface_attach(touch->surface, touch->buffer, 0, 0); wl_surface_damage(touch->surface, x - 2, y - 2, 5, 5); /* todo: We could queue up more damage before committing, if there * are more input events to handle. */ wl_surface_commit(touch->surface); } static void touch_handle_down(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { struct touch *touch = data; float x = wl_fixed_to_double(x_w); float y = wl_fixed_to_double(y_w); touch_paint(touch, x, y, id); } static void touch_handle_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id) { } static void touch_handle_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { struct touch *touch = data; float x = wl_fixed_to_double(x_w); float y = wl_fixed_to_double(y_w); touch_paint(touch, x, y, id); } static void touch_handle_frame(void *data, struct wl_touch *wl_touch) { } static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) { } static const struct wl_touch_listener touch_listener = { touch_handle_down, touch_handle_up, touch_handle_motion, touch_handle_frame, touch_handle_cancel, }; static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum wl_seat_capability caps) { struct seat *seat = data; struct touch *touch = seat->touch; if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !seat->wl_touch) { seat->wl_touch = wl_seat_get_touch(wl_seat); wl_touch_set_user_data(seat->wl_touch, touch); wl_touch_add_listener(seat->wl_touch, &touch_listener, touch); } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch) { wl_touch_destroy(seat->wl_touch); seat->wl_touch = NULL; } } static const struct wl_seat_listener seat_listener = { seat_handle_capabilities, }; static void add_seat(struct touch *touch, uint32_t name, uint32_t version) { struct seat *seat; seat = malloc(sizeof *seat); assert(seat); seat->touch = touch; seat->wl_touch = NULL; seat->seat = wl_registry_bind(touch->registry, name, &wl_seat_interface, 1); wl_seat_add_listener(seat->seat, &seat_listener, seat); } static void handle_ping(void *data, struct wl_shell_surface *shell_surface, uint32_t serial) { wl_shell_surface_pong(shell_surface, serial); } static void handle_configure(void *data, struct wl_shell_surface *shell_surface, uint32_t edges, int32_t width, int32_t height) { } static void handle_popup_done(void *data, struct wl_shell_surface *shell_surface) { } static const struct wl_shell_surface_listener shell_surface_listener = { handle_ping, handle_configure, handle_popup_done }; static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct touch *touch = data; if (strcmp(interface, "wl_compositor") == 0) { touch->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); } else if (strcmp(interface, "wl_shell") == 0) { touch->shell = wl_registry_bind(registry, name, &wl_shell_interface, 1); } else if (strcmp(interface, "wl_shm") == 0) { touch->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); wl_shm_add_listener(touch->shm, &shm_listener, touch); } else if (strcmp(interface, "wl_seat") == 0) { add_seat(touch, name, version); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { handle_global, handle_global_remove }; static struct touch * touch_create(int width, int height) { struct touch *touch; touch = malloc(sizeof *touch); if (touch == NULL) { fprintf(stderr, "out of memory\n"); exit(1); } touch->display = wl_display_connect(NULL); assert(touch->display); touch->has_argb = 0; touch->registry = wl_display_get_registry(touch->display); wl_registry_add_listener(touch->registry, ®istry_listener, touch); wl_display_dispatch(touch->display); wl_display_roundtrip(touch->display); if (!touch->has_argb) { fprintf(stderr, "WL_SHM_FORMAT_ARGB32 not available\n"); exit(1); } touch->width = width; touch->height = height; touch->surface = wl_compositor_create_surface(touch->compositor); touch->shell_surface = wl_shell_get_shell_surface(touch->shell, touch->surface); create_shm_buffer(touch); if (touch->shell_surface) { wl_shell_surface_add_listener(touch->shell_surface, &shell_surface_listener, touch); wl_shell_surface_set_toplevel(touch->shell_surface); } wl_surface_set_user_data(touch->surface, touch); wl_shell_surface_set_title(touch->shell_surface, "simple-touch"); memset(touch->data, 64, width * height * 4); wl_surface_attach(touch->surface, touch->buffer, 0, 0); wl_surface_damage(touch->surface, 0, 0, width, height); wl_surface_commit(touch->surface); return touch; } int main(int argc, char **argv) { struct touch *touch; int ret = 0; touch = touch_create(600, 500); while (ret != -1) ret = wl_display_dispatch(touch->display); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.328963 weston-8.0.0/clients/smoke.c0000644000175000017460000001750600000000000016253 0ustar00simonwheel00000000000000/* * Copyright © 2010 Kristian Høgsberg * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "window.h" struct smoke { struct display *display; struct window *window; struct widget *widget; int width, height; int current; struct { float *d, *u, *v; } b[2]; }; static void diffuse(struct smoke *smoke, uint32_t time, float *source, float *dest) { float *s, *d; int x, y, k, stride; float t, a = 0.0002; stride = smoke->width; for (k = 0; k < 5; k++) { for (y = 1; y < smoke->height - 1; y++) { s = source + y * stride; d = dest + y * stride; for (x = 1; x < smoke->width - 1; x++) { t = d[x - 1] + d[x + 1] + d[x - stride] + d[x + stride]; d[x] = (s[x] + a * t) / (1 + 4 * a) * 0.995; } } } } static void advect(struct smoke *smoke, uint32_t time, float *uu, float *vv, float *source, float *dest) { float *s, *d; float *u, *v; int x, y, stride; int i, j; float px, py, fx, fy; stride = smoke->width; for (y = 1; y < smoke->height - 1; y++) { d = dest + y * stride; u = uu + y * stride; v = vv + y * stride; for (x = 1; x < smoke->width - 1; x++) { px = x - u[x]; py = y - v[x]; if (px < 0.5) px = 0.5; if (py < 0.5) py = 0.5; if (px > smoke->width - 1.5) px = smoke->width - 1.5; if (py > smoke->height - 1.5) py = smoke->height - 1.5; i = (int) px; j = (int) py; fx = px - i; fy = py - j; s = source + j * stride + i; d[x] = (s[0] * (1 - fx) + s[1] * fx) * (1 - fy) + (s[stride] * (1 - fx) + s[stride + 1] * fx) * fy; } } } static void project(struct smoke *smoke, uint32_t time, float *u, float *v, float *p, float *div) { int x, y, k, l, s; float h; h = 1.0 / smoke->width; s = smoke->width; memset(p, 0, smoke->height * smoke->width); for (y = 1; y < smoke->height - 1; y++) { l = y * s; for (x = 1; x < smoke->width - 1; x++) { div[l + x] = -0.5 * h * (u[l + x + 1] - u[l + x - 1] + v[l + x + s] - v[l + x - s]); p[l + x] = 0; } } for (k = 0; k < 5; k++) { for (y = 1; y < smoke->height - 1; y++) { l = y * s; for (x = 1; x < smoke->width - 1; x++) { p[l + x] = (div[l + x] + p[l + x - 1] + p[l + x + 1] + p[l + x - s] + p[l + x + s]) / 4; } } } for (y = 1; y < smoke->height - 1; y++) { l = y * s; for (x = 1; x < smoke->width - 1; x++) { u[l + x] -= 0.5 * (p[l + x + 1] - p[l + x - 1]) / h; v[l + x] -= 0.5 * (p[l + x + s] - p[l + x - s]) / h; } } } static void render(struct smoke *smoke, cairo_surface_t *surface) { unsigned char *dest; int x, y, width, height, stride; float *s; uint32_t *d, c, a; dest = cairo_image_surface_get_data(surface); width = cairo_image_surface_get_width(surface); height = cairo_image_surface_get_height(surface); stride = cairo_image_surface_get_stride(surface); for (y = 1; y < height - 1; y++) { s = smoke->b[smoke->current].d + y * smoke->height; d = (uint32_t *) (dest + y * stride); for (x = 1; x < width - 1; x++) { c = (int) (s[x] * 800); if (c > 255) c = 255; a = c; if (a < 0x33) a = 0x33; d[x] = (a << 24) | (c << 16) | (c << 8) | c; } } } static void redraw_handler(struct widget *widget, void *data) { struct smoke *smoke = data; uint32_t time = widget_get_last_time(smoke->widget); cairo_surface_t *surface; diffuse(smoke, time / 30, smoke->b[0].u, smoke->b[1].u); diffuse(smoke, time / 30, smoke->b[0].v, smoke->b[1].v); project(smoke, time / 30, smoke->b[1].u, smoke->b[1].v, smoke->b[0].u, smoke->b[0].v); advect(smoke, time / 30, smoke->b[1].u, smoke->b[1].v, smoke->b[1].u, smoke->b[0].u); advect(smoke, time / 30, smoke->b[1].u, smoke->b[1].v, smoke->b[1].v, smoke->b[0].v); project(smoke, time / 30, smoke->b[0].u, smoke->b[0].v, smoke->b[1].u, smoke->b[1].v); diffuse(smoke, time / 30, smoke->b[0].d, smoke->b[1].d); advect(smoke, time / 30, smoke->b[0].u, smoke->b[0].v, smoke->b[1].d, smoke->b[0].d); surface = window_get_surface(smoke->window); render(smoke, surface); cairo_surface_destroy(surface); widget_schedule_redraw(smoke->widget); } static void smoke_motion_handler(struct smoke *smoke, float x, float y) { int i, i0, i1, j, j0, j1, k, d = 5; if (x - d < 1) i0 = 1; else i0 = x - d; if (i0 + 2 * d > smoke->width - 1) i1 = smoke->width - 1; else i1 = i0 + 2 * d; if (y - d < 1) j0 = 1; else j0 = y - d; if (j0 + 2 * d > smoke->height - 1) j1 = smoke->height - 1; else j1 = j0 + 2 * d; for (i = i0; i < i1; i++) for (j = j0; j < j1; j++) { k = j * smoke->width + i; smoke->b[0].u[k] += 256 - (random() & 512); smoke->b[0].v[k] += 256 - (random() & 512); smoke->b[0].d[k] += 1; } } static int mouse_motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { smoke_motion_handler(data, x, y); return CURSOR_HAND1; } static void touch_motion_handler(struct widget *widget, struct input *input, uint32_t time, int32_t id, float x, float y, void *data) { smoke_motion_handler(data, x, y); } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct smoke *smoke = data; /* Don't resize me */ widget_set_size(smoke->widget, smoke->width, smoke->height); } int main(int argc, char *argv[]) { struct timespec ts; struct smoke smoke; struct display *d; int size; d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } smoke.width = 200; smoke.height = 200; smoke.display = d; smoke.window = window_create(d); smoke.widget = window_add_widget(smoke.window, &smoke); window_set_title(smoke.window, "smoke"); window_set_buffer_type(smoke.window, WINDOW_BUFFER_TYPE_SHM); clock_gettime(CLOCK_MONOTONIC, &ts); srandom(ts.tv_nsec); smoke.current = 0; size = smoke.height * smoke.width; smoke.b[0].d = calloc(size, sizeof(float)); smoke.b[0].u = calloc(size, sizeof(float)); smoke.b[0].v = calloc(size, sizeof(float)); smoke.b[1].d = calloc(size, sizeof(float)); smoke.b[1].u = calloc(size, sizeof(float)); smoke.b[1].v = calloc(size, sizeof(float)); widget_set_motion_handler(smoke.widget, mouse_motion_handler); widget_set_touch_motion_handler(smoke.widget, touch_motion_handler); widget_set_resize_handler(smoke.widget, resize_handler); widget_set_redraw_handler(smoke.widget, redraw_handler); window_set_user_data(smoke.window, &smoke); widget_schedule_resize(smoke.widget, smoke.width, smoke.height); display_run(d); widget_destroy(smoke.widget); window_destroy(smoke.window); display_destroy(d); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.328963 weston-8.0.0/clients/stacking.c0000644000175000017460000001716000000000000016734 0ustar00simonwheel00000000000000/* * Copyright © 2013 Collabora Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "shared/helpers.h" #include "window.h" struct stacking { struct display *display; struct window *root_window; }; static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data); static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data); static void keyboard_focus_handler(struct window *window, struct input *device, void *data); static void fullscreen_handler(struct window *window, void *data); static void redraw_handler(struct widget *widget, void *data); /* Iff parent_window is set, the new window will be transient. */ static struct window * new_window(struct stacking *stacking, struct window *parent_window) { struct window *new_window; struct widget *new_widget; new_window = window_create(stacking->display); window_set_parent(new_window, parent_window); new_widget = window_frame_create(new_window, new_window); window_set_title(new_window, "Stacking Test"); window_set_key_handler(new_window, key_handler); window_set_keyboard_focus_handler(new_window, keyboard_focus_handler); window_set_fullscreen_handler(new_window, fullscreen_handler); widget_set_button_handler(new_widget, button_handler); widget_set_redraw_handler(new_widget, redraw_handler); window_set_user_data(new_window, stacking); window_schedule_resize(new_window, 300, 300); return new_window; } static void show_popup_cb(void *data, struct input *input, int index) { /* Ignore the selected menu item. */ } static void show_popup(struct stacking *stacking, struct input *input, uint32_t time, struct window *window) { int32_t x, y; static const char *entries[] = { "Test Entry", "Another Test Entry", }; input_get_position(input, &x, &y); window_show_menu(stacking->display, input, time, window, x, y, show_popup_cb, entries, ARRAY_LENGTH(entries)); } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct stacking *stacking = data; switch (button) { case BTN_RIGHT: if (state == WL_POINTER_BUTTON_STATE_PRESSED) show_popup(stacking, input, time, widget_get_user_data(widget)); break; case BTN_LEFT: default: break; } } static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data) { struct stacking *stacking = data; if (state != WL_KEYBOARD_KEY_STATE_PRESSED) return; switch (sym) { case XKB_KEY_f: fullscreen_handler(window, data); break; case XKB_KEY_m: window_set_maximized(window, !window_is_maximized(window)); break; case XKB_KEY_n: /* New top-level window. */ new_window(stacking, NULL); break; case XKB_KEY_p: show_popup(stacking, input, time, window); break; case XKB_KEY_q: exit (0); break; case XKB_KEY_t: /* New transient window. */ new_window(stacking, window); break; default: break; } } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { window_schedule_redraw(window); } static void fullscreen_handler(struct window *window, void *data) { window_set_fullscreen(window, !window_is_fullscreen(window)); } static void draw_string(cairo_t *cr, const char *fmt, ...) WL_PRINTF(2, 3); static void draw_string(cairo_t *cr, const char *fmt, ...) { char buffer[4096]; char *p, *end; va_list argp; cairo_text_extents_t text_extents; cairo_font_extents_t font_extents; cairo_save(cr); cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 14); cairo_font_extents(cr, &font_extents); va_start(argp, fmt); vsnprintf(buffer, sizeof(buffer), fmt, argp); p = buffer; while (*p) { end = strchr(p, '\n'); if (end) *end = 0; cairo_show_text(cr, p); cairo_text_extents(cr, p, &text_extents); cairo_rel_move_to(cr, -text_extents.x_advance, font_extents.height); if (end) p = end + 1; else break; } va_end(argp); cairo_restore(cr); } static void set_window_background_colour(cairo_t *cr, struct window *window) { if (window_get_parent(window)) cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 0.4); else if (window_is_maximized(window)) cairo_set_source_rgba(cr, 1.0, 1.0, 0.0, 0.6); else if (window_is_fullscreen(window)) cairo_set_source_rgba(cr, 0.0, 1.0, 1.0, 0.6); else cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); } static void redraw_handler(struct widget *widget, void *data) { struct window *window; struct rectangle allocation; cairo_t *cr; widget_get_allocation(widget, &allocation); window = widget_get_user_data(widget); cr = widget_cairo_create(widget); cairo_translate(cr, allocation.x, allocation.y); /* Draw background. */ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); set_window_background_colour(cr, window); cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); cairo_fill(cr); /* Print the instructions. */ cairo_move_to(cr, 5, 15); cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); draw_string(cr, "Window: %p\n" "Fullscreen? %u\n" "Maximized? %u\n" "Transient? %u\n" "Keys: (f)ullscreen, (m)aximize,\n" " (n)ew window, (p)opup,\n" " (q)uit, (t)ransient window\n", window, window_is_fullscreen(window), window_is_maximized(window), window_get_parent(window) ? 1 : 0); cairo_destroy(cr); } int main(int argc, char *argv[]) { struct stacking stacking; memset(&stacking, 0, sizeof stacking); stacking.display = display_create(&argc, argv); if (stacking.display == NULL) { fprintf(stderr, "Failed to create display: %s\n", strerror(errno)); return -1; } display_set_user_data(stacking.display, &stacking); stacking.root_window = new_window(&stacking, NULL); display_run(stacking.display); window_destroy(stacking.root_window); display_destroy(stacking.display); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.328963 weston-8.0.0/clients/subsurfaces.c0000644000175000017460000005014300000000000017454 0ustar00simonwheel00000000000000/* * Copyright © 2010 Intel Corporation * Copyright © 2011 Benjamin Franzke * Copyright © 2012-2013 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shared/helpers.h" #include "shared/xalloc.h" #include #include "window.h" #if 0 #define DBG(fmt, ...) \ fprintf(stderr, "%d:%s " fmt, __LINE__, __func__, ##__VA_ARGS__) #else #define DBG(...) do {} while (0) #endif static int32_t option_red_mode; static int32_t option_triangle_mode; static bool option_no_triangle; static bool option_help; static const struct weston_option options[] = { { WESTON_OPTION_INTEGER, "red-mode", 'r', &option_red_mode }, { WESTON_OPTION_INTEGER, "triangle-mode", 't', &option_triangle_mode }, { WESTON_OPTION_BOOLEAN, "no-triangle", 'n', &option_no_triangle }, { WESTON_OPTION_BOOLEAN, "help", 'h', &option_help }, }; static enum subsurface_mode int_to_mode(int32_t i) { switch (i) { case 0: return SUBSURFACE_DESYNCHRONIZED; case 1: return SUBSURFACE_SYNCHRONIZED; default: fprintf(stderr, "error: %d is not a valid commit mode.\n", i); exit(1); } } static const char help_text[] = "Usage: %s [options]\n" "\n" " -r, --red-mode=MODE\t\tthe commit mode for the red sub-surface (0)\n" " -t, --triangle-mode=MODE\tthe commit mode for the GL sub-surface (0)\n" " -n, --no-triangle\t\tDo not create the GL sub-surface.\n" "\n" "The MODE is the wl_subsurface commit mode used by default for the\n" "given sub-surface. Valid values are the integers:\n" " 0\tfor desynchronized, i.e. free-running\n" " 1\tfor synchronized\n" "\n" "This program demonstrates sub-surfaces with the toytoolkit.\n" "The main surface contains the decorations, a green canvas, and a\n" "green spinner. One sub-surface is red with a red spinner. These\n" "are rendered with Cairo. The other sub-surface contains a spinning\n" "triangle rendered in EGL/GLESv2, without Cairo, i.e. it is a raw GL\n" "widget.\n" "\n" "The GL widget animates on its own. The spinners follow wall clock\n" "time and update only when their surface is repainted, so you see\n" "which surfaces get redrawn. The red sub-surface animates on its own,\n" "but can be toggled with the spacebar.\n" "\n" "Even though the sub-surfaces attempt to animate on their own, they\n" "are subject to the commit mode. If commit mode is synchronized,\n" "they will need a commit on the main surface to actually display.\n" "You can trigger a main surface repaint, without a resize, by\n" "hovering the pointer over the title bar buttons.\n" "\n" "Resizing will temporarily toggle the commit mode of all sub-surfaces\n" "to guarantee synchronized rendering on size changes. It also forces\n" "a repaint of all surfaces.\n" "\n" "Using -t1 -r1 is especially useful for trying to catch inconsistent\n" "rendering and deadlocks, since free-running sub-surfaces would\n" "immediately hide the problem.\n" "\n" "Key controls:\n" " space - toggle red sub-surface animation loop\n" " up - step window size shorter\n" " down - step window size taller\n" "\n"; struct egl_state { EGLDisplay dpy; EGLContext ctx; EGLConfig conf; }; struct triangle_gl_state { GLuint rotation_uniform; GLuint pos; GLuint col; }; struct triangle { struct egl_state *egl; struct wl_surface *wl_surface; struct wl_egl_window *egl_window; EGLSurface egl_surface; int width; int height; struct triangle_gl_state gl; struct widget *widget; uint32_t time; struct wl_callback *frame_cb; }; /******** Pure EGL/GLESv2/libwayland-client component: ***************/ static const char *vert_shader_text = "uniform mat4 rotation;\n" "attribute vec4 pos;\n" "attribute vec4 color;\n" "varying vec4 v_color;\n" "void main() {\n" " gl_Position = rotation * pos;\n" " v_color = color;\n" "}\n"; static const char *frag_shader_text = "precision mediump float;\n" "varying vec4 v_color;\n" "void main() {\n" " gl_FragColor = v_color;\n" "}\n"; static void egl_print_config_info(struct egl_state *egl) { EGLint r, g, b, a; printf("Chosen EGL config details:\n"); printf("\tRGBA bits"); if (eglGetConfigAttrib(egl->dpy, egl->conf, EGL_RED_SIZE, &r) && eglGetConfigAttrib(egl->dpy, egl->conf, EGL_GREEN_SIZE, &g) && eglGetConfigAttrib(egl->dpy, egl->conf, EGL_BLUE_SIZE, &b) && eglGetConfigAttrib(egl->dpy, egl->conf, EGL_ALPHA_SIZE, &a)) printf(": %d %d %d %d\n", r, g, b, a); else printf(" unknown\n"); printf("\tswap interval range"); if (eglGetConfigAttrib(egl->dpy, egl->conf, EGL_MIN_SWAP_INTERVAL, &a) && eglGetConfigAttrib(egl->dpy, egl->conf, EGL_MAX_SWAP_INTERVAL, &b)) printf(": %d - %d\n", a, b); else printf(" unknown\n"); } static struct egl_state * egl_state_create(struct wl_display *display) { struct egl_state *egl; static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 1, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; EGLint major, minor, n; EGLBoolean ret; egl = zalloc(sizeof *egl); assert(egl); egl->dpy = weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, display, NULL); assert(egl->dpy); ret = eglInitialize(egl->dpy, &major, &minor); assert(ret == EGL_TRUE); ret = eglBindAPI(EGL_OPENGL_ES_API); assert(ret == EGL_TRUE); ret = eglChooseConfig(egl->dpy, config_attribs, &egl->conf, 1, &n); assert(ret && n == 1); egl->ctx = eglCreateContext(egl->dpy, egl->conf, EGL_NO_CONTEXT, context_attribs); assert(egl->ctx); egl_print_config_info(egl); return egl; } static void egl_state_destroy(struct egl_state *egl) { /* Required, otherwise segfault in egl_dri2.c: dri2_make_current() * on eglReleaseThread(). */ eglMakeCurrent(egl->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglTerminate(egl->dpy); eglReleaseThread(); free(egl); } static void egl_make_swapbuffers_nonblock(struct egl_state *egl) { EGLint a = EGL_MIN_SWAP_INTERVAL; EGLint b = EGL_MAX_SWAP_INTERVAL; if (!eglGetConfigAttrib(egl->dpy, egl->conf, a, &a) || !eglGetConfigAttrib(egl->dpy, egl->conf, b, &b)) { fprintf(stderr, "warning: swap interval range unknown\n"); } else if (a > 0) { fprintf(stderr, "warning: minimum swap interval is %d, " "while 0 is required to not deadlock on resize.\n", a); } /* * We rely on the Wayland compositor to sync to vblank anyway. * We just need to be able to call eglSwapBuffers() without the * risk of waiting for a frame callback in it. */ if (!eglSwapInterval(egl->dpy, 0)) { fprintf(stderr, "error: eglSwapInterval() failed.\n"); } } static GLuint create_shader(const char *source, GLenum shader_type) { GLuint shader; GLint status; shader = glCreateShader(shader_type); assert(shader != 0); glShaderSource(shader, 1, (const char **) &source, NULL); glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (!status) { char log[1000]; GLsizei len; glGetShaderInfoLog(shader, 1000, &len, log); fprintf(stderr, "Error: compiling %s: %.*s\n", shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", len, log); exit(1); } return shader; } static void triangle_init_gl(struct triangle_gl_state *trigl) { GLuint frag, vert; GLuint program; GLint status; frag = create_shader(frag_shader_text, GL_FRAGMENT_SHADER); vert = create_shader(vert_shader_text, GL_VERTEX_SHADER); program = glCreateProgram(); glAttachShader(program, frag); glAttachShader(program, vert); glLinkProgram(program); glGetProgramiv(program, GL_LINK_STATUS, &status); if (!status) { char log[1000]; GLsizei len; glGetProgramInfoLog(program, 1000, &len, log); fprintf(stderr, "Error: linking:\n%.*s\n", len, log); exit(1); } glUseProgram(program); trigl->pos = 0; trigl->col = 1; glBindAttribLocation(program, trigl->pos, "pos"); glBindAttribLocation(program, trigl->col, "color"); glLinkProgram(program); trigl->rotation_uniform = glGetUniformLocation(program, "rotation"); } static void triangle_draw(const struct triangle_gl_state *trigl, uint32_t time) { static const GLfloat verts[3][2] = { { -0.5, -0.5 }, { 0.5, -0.5 }, { 0, 0.5 } }; static const GLfloat colors[3][3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; GLfloat angle; GLfloat rotation[4][4] = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } }; static const int32_t speed_div = 5; angle = (time / speed_div) % 360 * M_PI / 180.0; rotation[0][0] = cos(angle); rotation[0][2] = sin(angle); rotation[2][0] = -sin(angle); rotation[2][2] = cos(angle); glUniformMatrix4fv(trigl->rotation_uniform, 1, GL_FALSE, (GLfloat *) rotation); glClearColor(0.0, 0.0, 0.0, 0.5); glClear(GL_COLOR_BUFFER_BIT); glVertexAttribPointer(trigl->pos, 2, GL_FLOAT, GL_FALSE, 0, verts); glVertexAttribPointer(trigl->col, 3, GL_FLOAT, GL_FALSE, 0, colors); glEnableVertexAttribArray(trigl->pos); glEnableVertexAttribArray(trigl->col); glDrawArrays(GL_TRIANGLES, 0, 3); glDisableVertexAttribArray(trigl->pos); glDisableVertexAttribArray(trigl->col); } static void triangle_frame_callback(void *data, struct wl_callback *callback, uint32_t time); static const struct wl_callback_listener triangle_frame_listener = { triangle_frame_callback }; static void triangle_frame_callback(void *data, struct wl_callback *callback, uint32_t time) { struct triangle *tri = data; DBG("%stime %u\n", callback ? "" : "artificial ", time); assert(callback == tri->frame_cb); tri->time = time; if (callback) wl_callback_destroy(callback); eglMakeCurrent(tri->egl->dpy, tri->egl_surface, tri->egl_surface, tri->egl->ctx); glViewport(0, 0, tri->width, tri->height); triangle_draw(&tri->gl, tri->time); tri->frame_cb = wl_surface_frame(tri->wl_surface); wl_callback_add_listener(tri->frame_cb, &triangle_frame_listener, tri); eglSwapBuffers(tri->egl->dpy, tri->egl_surface); } static void triangle_create_egl_surface(struct triangle *tri, int width, int height) { EGLBoolean ret; tri->wl_surface = widget_get_wl_surface(tri->widget); tri->egl_window = wl_egl_window_create(tri->wl_surface, width, height); tri->egl_surface = weston_platform_create_egl_surface(tri->egl->dpy, tri->egl->conf, tri->egl_window, NULL); ret = eglMakeCurrent(tri->egl->dpy, tri->egl_surface, tri->egl_surface, tri->egl->ctx); assert(ret == EGL_TRUE); egl_make_swapbuffers_nonblock(tri->egl); triangle_init_gl(&tri->gl); } /********* The widget code interfacing the toolkit agnostic code: **********/ static void triangle_resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct triangle *tri = data; DBG("to %dx%d\n", width, height); tri->width = width; tri->height = height; if (tri->egl_surface) { wl_egl_window_resize(tri->egl_window, width, height, 0, 0); } else { triangle_create_egl_surface(tri, width, height); triangle_frame_callback(tri, NULL, 0); } } static void triangle_redraw_handler(struct widget *widget, void *data) { struct triangle *tri = data; int w, h; wl_egl_window_get_attached_size(tri->egl_window, &w, &h); DBG("previous %dx%d, new %dx%d\n", w, h, tri->width, tri->height); /* If size is not changing, do not redraw ahead of time. * That would risk blocking in eglSwapbuffers(). */ if (w == tri->width && h == tri->height) return; if (tri->frame_cb) { wl_callback_destroy(tri->frame_cb); tri->frame_cb = NULL; } triangle_frame_callback(tri, NULL, tri->time); } static void set_empty_input_region(struct widget *widget, struct display *display) { struct wl_compositor *compositor; struct wl_surface *surface; struct wl_region *region; compositor = display_get_compositor(display); surface = widget_get_wl_surface(widget); region = wl_compositor_create_region(compositor); wl_surface_set_input_region(surface, region); wl_region_destroy(region); } static struct triangle * triangle_create(struct window *window, struct egl_state *egl) { struct triangle *tri; tri = xzalloc(sizeof *tri); tri->egl = egl; tri->widget = window_add_subsurface(window, tri, int_to_mode(option_triangle_mode)); widget_set_use_cairo(tri->widget, 0); widget_set_resize_handler(tri->widget, triangle_resize_handler); widget_set_redraw_handler(tri->widget, triangle_redraw_handler); set_empty_input_region(tri->widget, window_get_display(window)); return tri; } static void triangle_destroy(struct triangle *tri) { if (tri->egl_surface) weston_platform_destroy_egl_surface(tri->egl->dpy, tri->egl_surface); if (tri->egl_window) wl_egl_window_destroy(tri->egl_window); widget_destroy(tri->widget); free(tri); } /************** The toytoolkit application code: *********************/ struct demoapp { struct display *display; struct window *window; struct widget *widget; struct widget *subsurface; struct egl_state *egl; struct triangle *triangle; int animate; }; static void draw_spinner(cairo_t *cr, const struct rectangle *rect, uint32_t time) { double cx, cy, r, angle; unsigned t; cx = rect->x + rect->width / 2; cy = rect->y + rect->height / 2; r = (rect->width < rect->height ? rect->width : rect->height) * 0.3; t = time % 2000; angle = t * (M_PI / 500.0); cairo_set_line_width(cr, 4.0); if (t < 1000) cairo_arc(cr, cx, cy, r, 0.0, angle); else cairo_arc(cr, cx, cy, r, angle, 0.0); cairo_stroke(cr); } static void sub_redraw_handler(struct widget *widget, void *data) { struct demoapp *app = data; cairo_t *cr; struct rectangle allocation; uint32_t time; widget_get_allocation(app->subsurface, &allocation); cr = widget_cairo_create(widget); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); /* debug: paint whole surface magenta; no magenta should show */ cairo_set_source_rgba(cr, 0.9, 0.0, 0.9, 1.0); cairo_paint(cr); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_clip(cr); cairo_set_source_rgba(cr, 0.8, 0, 0, 0.8); cairo_paint(cr); time = widget_get_last_time(widget); cairo_set_source_rgba(cr, 1.0, 0.5, 0.5, 1.0); draw_spinner(cr, &allocation, time); cairo_destroy(cr); if (app->animate) widget_schedule_redraw(app->subsurface); DBG("%dx%d @ %d,%d, last time %u\n", allocation.width, allocation.height, allocation.x, allocation.y, time); } static void sub_resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { DBG("%dx%d\n", width, height); widget_input_region_add(widget, NULL); } static void redraw_handler(struct widget *widget, void *data) { struct demoapp *app = data; cairo_t *cr; struct rectangle allocation; uint32_t time; widget_get_allocation(app->widget, &allocation); cr = widget_cairo_create(widget); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_set_source_rgba(cr, 0, 0.8, 0, 0.8); cairo_fill(cr); time = widget_get_last_time(widget); cairo_set_source_rgba(cr, 0.5, 1.0, 0.5, 1.0); draw_spinner(cr, &allocation, time); cairo_destroy(cr); DBG("%dx%d @ %d,%d, last time %u\n", allocation.width, allocation.height, allocation.x, allocation.y, time); } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct demoapp *app = data; struct rectangle area; int side, h; widget_get_allocation(widget, &area); side = area.width < area.height ? area.width / 2 : area.height / 2; h = area.height - side; widget_set_allocation(app->subsurface, area.x + area.width - side, area.y, side, h); if (app->triangle) { widget_set_allocation(app->triangle->widget, area.x + area.width - side, area.y + h, side, side); } DBG("green %dx%d, red %dx%d, GL %dx%d\n", area.width, area.height, side, h, side, side); } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { struct demoapp *app = data; window_schedule_redraw(app->window); } static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data) { struct demoapp *app = data; struct rectangle winrect; if (state == WL_KEYBOARD_KEY_STATE_RELEASED) return; switch (sym) { case XKB_KEY_space: app->animate = !app->animate; window_schedule_redraw(window); break; case XKB_KEY_Up: window_get_allocation(window, &winrect); winrect.height -= 100; if (winrect.height < 150) winrect.height = 150; window_schedule_resize(window, winrect.width, winrect.height); break; case XKB_KEY_Down: window_get_allocation(window, &winrect); winrect.height += 100; if (winrect.height > 600) winrect.height = 600; window_schedule_resize(window, winrect.width, winrect.height); break; case XKB_KEY_Escape: display_exit(app->display); break; } } static struct demoapp * demoapp_create(struct display *display) { struct demoapp *app; app = xzalloc(sizeof *app); app->egl = egl_state_create(display_get_display(display)); app->display = display; display_set_user_data(app->display, app); app->window = window_create(app->display); app->widget = window_frame_create(app->window, app); window_set_title(app->window, "Wayland Sub-surface Demo"); window_set_key_handler(app->window, key_handler); window_set_user_data(app->window, app); window_set_keyboard_focus_handler(app->window, keyboard_focus_handler); widget_set_redraw_handler(app->widget, redraw_handler); widget_set_resize_handler(app->widget, resize_handler); app->subsurface = window_add_subsurface(app->window, app, int_to_mode(option_red_mode)); widget_set_redraw_handler(app->subsurface, sub_redraw_handler); widget_set_resize_handler(app->subsurface, sub_resize_handler); if (app->egl && !option_no_triangle) app->triangle = triangle_create(app->window, app->egl); /* minimum size */ widget_schedule_resize(app->widget, 100, 100); /* initial size */ widget_schedule_resize(app->widget, 400, 300); app->animate = 1; return app; } static void demoapp_destroy(struct demoapp *app) { if (app->triangle) triangle_destroy(app->triangle); if (app->egl) egl_state_destroy(app->egl); widget_destroy(app->subsurface); widget_destroy(app->widget); window_destroy(app->window); free(app); } int main(int argc, char *argv[]) { struct display *display; struct demoapp *app; if (parse_options(options, ARRAY_LENGTH(options), &argc, argv) > 1 || option_help) { printf(help_text, argv[0]); return 0; } display = display_create(&argc, argv); if (display == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } if (!display_has_subcompositor(display)) { fprintf(stderr, "compositor does not support " "the subcompositor extension\n"); return -1; } app = demoapp_create(display); display_run(display); demoapp_destroy(app); display_destroy(display); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3322964 weston-8.0.0/clients/terminal.c0000644000175000017460000025052000000000000016743 0ustar00simonwheel00000000000000/* * Copyright © 2008 Kristian Høgsberg * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shared/helpers.h" #include "shared/xalloc.h" #include "window.h" static bool option_fullscreen; static bool option_maximize; static char *option_font; static int option_font_size; static char *option_term; static char *option_shell; static struct wl_list terminal_list; static struct terminal * terminal_create(struct display *display); static void terminal_destroy(struct terminal *terminal); static int terminal_run(struct terminal *terminal, const char *path); #define TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS \ " !\"#$%&'()*+,-./" \ "0123456789" \ ":;<=>?@" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "[\\]^_`" \ "abcdefghijklmnopqrstuvwxyz" \ "{|}~" \ "" #define MOD_SHIFT 0x01 #define MOD_ALT 0x02 #define MOD_CTRL 0x04 #define ATTRMASK_BOLD 0x01 #define ATTRMASK_UNDERLINE 0x02 #define ATTRMASK_BLINK 0x04 #define ATTRMASK_INVERSE 0x08 #define ATTRMASK_CONCEALED 0x10 /* Buffer sizes */ #define MAX_RESPONSE 256 #define MAX_ESCAPE 255 /* Terminal modes */ #define MODE_SHOW_CURSOR 0x00000001 #define MODE_INVERSE 0x00000002 #define MODE_AUTOWRAP 0x00000004 #define MODE_AUTOREPEAT 0x00000008 #define MODE_LF_NEWLINE 0x00000010 #define MODE_IRM 0x00000020 #define MODE_DELETE_SENDS_DEL 0x00000040 #define MODE_ALT_SENDS_ESC 0x00000080 union utf8_char { unsigned char byte[4]; uint32_t ch; }; enum utf8_state { utf8state_start, utf8state_accept, utf8state_reject, utf8state_expect3, utf8state_expect2, utf8state_expect1 }; struct utf8_state_machine { enum utf8_state state; int len; union utf8_char s; uint32_t unicode; }; static void init_state_machine(struct utf8_state_machine *machine) { machine->state = utf8state_start; machine->len = 0; machine->s.ch = 0; machine->unicode = 0; } static enum utf8_state utf8_next_char(struct utf8_state_machine *machine, unsigned char c) { switch(machine->state) { case utf8state_start: case utf8state_accept: case utf8state_reject: machine->s.ch = 0; machine->len = 0; if (c == 0xC0 || c == 0xC1) { /* overlong encoding, reject */ machine->state = utf8state_reject; } else if ((c & 0x80) == 0) { /* single byte, accept */ machine->s.byte[machine->len++] = c; machine->state = utf8state_accept; machine->unicode = c; } else if ((c & 0xC0) == 0x80) { /* parser out of sync, ignore byte */ machine->state = utf8state_start; } else if ((c & 0xE0) == 0xC0) { /* start of two byte sequence */ machine->s.byte[machine->len++] = c; machine->state = utf8state_expect1; machine->unicode = c & 0x1f; } else if ((c & 0xF0) == 0xE0) { /* start of three byte sequence */ machine->s.byte[machine->len++] = c; machine->state = utf8state_expect2; machine->unicode = c & 0x0f; } else if ((c & 0xF8) == 0xF0) { /* start of four byte sequence */ machine->s.byte[machine->len++] = c; machine->state = utf8state_expect3; machine->unicode = c & 0x07; } else { /* overlong encoding, reject */ machine->state = utf8state_reject; } break; case utf8state_expect3: machine->s.byte[machine->len++] = c; machine->unicode = (machine->unicode << 6) | (c & 0x3f); if ((c & 0xC0) == 0x80) { /* all good, continue */ machine->state = utf8state_expect2; } else { /* missing extra byte, reject */ machine->state = utf8state_reject; } break; case utf8state_expect2: machine->s.byte[machine->len++] = c; machine->unicode = (machine->unicode << 6) | (c & 0x3f); if ((c & 0xC0) == 0x80) { /* all good, continue */ machine->state = utf8state_expect1; } else { /* missing extra byte, reject */ machine->state = utf8state_reject; } break; case utf8state_expect1: machine->s.byte[machine->len++] = c; machine->unicode = (machine->unicode << 6) | (c & 0x3f); if ((c & 0xC0) == 0x80) { /* all good, accept */ machine->state = utf8state_accept; } else { /* missing extra byte, reject */ machine->state = utf8state_reject; } break; default: machine->state = utf8state_reject; break; } return machine->state; } static uint32_t get_unicode(union utf8_char utf8) { struct utf8_state_machine machine; int i; init_state_machine(&machine); for (i = 0; i < 4; i++) { utf8_next_char(&machine, utf8.byte[i]); if (machine.state == utf8state_accept || machine.state == utf8state_reject) break; } if (machine.state == utf8state_reject) return 0xfffd; return machine.unicode; } static bool is_wide(union utf8_char utf8) { uint32_t unichar = get_unicode(utf8); return wcwidth(unichar) > 1; } struct char_sub { union utf8_char match; union utf8_char replace; }; /* Set last char_sub match to NULL char */ typedef struct char_sub *character_set; struct char_sub CS_US[] = { {{{0, }}, {{0, }}} }; static struct char_sub CS_UK[] = { {{{'#', 0, }}, {{0xC2, 0xA3, 0, }}}, /* POUND: £ */ {{{0, }}, {{0, }}} }; static struct char_sub CS_SPECIAL[] = { {{{'`', 0, }}, {{0xE2, 0x99, 0xA6, 0}}}, /* diamond: ♦ */ {{{'a', 0, }}, {{0xE2, 0x96, 0x92, 0}}}, /* 50% cell: ▒ */ {{{'b', 0, }}, {{0xE2, 0x90, 0x89, 0}}}, /* HT: ␉ */ {{{'c', 0, }}, {{0xE2, 0x90, 0x8C, 0}}}, /* FF: ␌ */ {{{'d', 0, }}, {{0xE2, 0x90, 0x8D, 0}}}, /* CR: ␍ */ {{{'e', 0, }}, {{0xE2, 0x90, 0x8A, 0}}}, /* LF: ␊ */ {{{'f', 0, }}, {{0xC2, 0xB0, 0, }}}, /* Degree: ° */ {{{'g', 0, }}, {{0xC2, 0xB1, 0, }}}, /* Plus/Minus: ± */ {{{'h', 0, }}, {{0xE2, 0x90, 0xA4, 0}}}, /* NL: ␤ */ {{{'i', 0, }}, {{0xE2, 0x90, 0x8B, 0}}}, /* VT: ␋ */ {{{'j', 0, }}, {{0xE2, 0x94, 0x98, 0}}}, /* CN_RB: ┘ */ {{{'k', 0, }}, {{0xE2, 0x94, 0x90, 0}}}, /* CN_RT: ┐ */ {{{'l', 0, }}, {{0xE2, 0x94, 0x8C, 0}}}, /* CN_LT: ┌ */ {{{'m', 0, }}, {{0xE2, 0x94, 0x94, 0}}}, /* CN_LB: └ */ {{{'n', 0, }}, {{0xE2, 0x94, 0xBC, 0}}}, /* CROSS: ┼ */ {{{'o', 0, }}, {{0xE2, 0x8E, 0xBA, 0}}}, /* Horiz. Scan Line 1: ⎺ */ {{{'p', 0, }}, {{0xE2, 0x8E, 0xBB, 0}}}, /* Horiz. Scan Line 3: ⎻ */ {{{'q', 0, }}, {{0xE2, 0x94, 0x80, 0}}}, /* Horiz. Scan Line 5: ─ */ {{{'r', 0, }}, {{0xE2, 0x8E, 0xBC, 0}}}, /* Horiz. Scan Line 7: ⎼ */ {{{'s', 0, }}, {{0xE2, 0x8E, 0xBD, 0}}}, /* Horiz. Scan Line 9: ⎽ */ {{{'t', 0, }}, {{0xE2, 0x94, 0x9C, 0}}}, /* TR: ├ */ {{{'u', 0, }}, {{0xE2, 0x94, 0xA4, 0}}}, /* TL: ┤ */ {{{'v', 0, }}, {{0xE2, 0x94, 0xB4, 0}}}, /* TU: ┴ */ {{{'w', 0, }}, {{0xE2, 0x94, 0xAC, 0}}}, /* TD: ┬ */ {{{'x', 0, }}, {{0xE2, 0x94, 0x82, 0}}}, /* V: │ */ {{{'y', 0, }}, {{0xE2, 0x89, 0xA4, 0}}}, /* LE: ≤ */ {{{'z', 0, }}, {{0xE2, 0x89, 0xA5, 0}}}, /* GE: ≥ */ {{{'{', 0, }}, {{0xCF, 0x80, 0, }}}, /* PI: π */ {{{'|', 0, }}, {{0xE2, 0x89, 0xA0, 0}}}, /* NEQ: ≠ */ {{{'}', 0, }}, {{0xC2, 0xA3, 0, }}}, /* POUND: £ */ {{{'~', 0, }}, {{0xE2, 0x8B, 0x85, 0}}}, /* DOT: ⋅ */ {{{0, }}, {{0, }}} }; static void apply_char_set(character_set cs, union utf8_char *utf8) { int i = 0; while (cs[i].match.byte[0]) { if ((*utf8).ch == cs[i].match.ch) { *utf8 = cs[i].replace; break; } i++; } } struct key_map { int sym; int num; char escape; char code; }; /* Set last key_sub sym to NULL */ typedef struct key_map *keyboard_mode; static struct key_map KM_NORMAL[] = { { XKB_KEY_Left, 1, '[', 'D' }, { XKB_KEY_Right, 1, '[', 'C' }, { XKB_KEY_Up, 1, '[', 'A' }, { XKB_KEY_Down, 1, '[', 'B' }, { XKB_KEY_Home, 1, '[', 'H' }, { XKB_KEY_End, 1, '[', 'F' }, { 0, 0, 0, 0 } }; static struct key_map KM_APPLICATION[] = { { XKB_KEY_Left, 1, 'O', 'D' }, { XKB_KEY_Right, 1, 'O', 'C' }, { XKB_KEY_Up, 1, 'O', 'A' }, { XKB_KEY_Down, 1, 'O', 'B' }, { XKB_KEY_Home, 1, 'O', 'H' }, { XKB_KEY_End, 1, 'O', 'F' }, { XKB_KEY_KP_Enter, 1, 'O', 'M' }, { XKB_KEY_KP_Multiply, 1, 'O', 'j' }, { XKB_KEY_KP_Add, 1, 'O', 'k' }, { XKB_KEY_KP_Separator, 1, 'O', 'l' }, { XKB_KEY_KP_Subtract, 1, 'O', 'm' }, { XKB_KEY_KP_Divide, 1, 'O', 'o' }, { 0, 0, 0, 0 } }; static int function_key_response(char escape, int num, uint32_t modifiers, char code, char *response) { int mod_num = 0; int len; if (modifiers & MOD_SHIFT_MASK) mod_num |= 1; if (modifiers & MOD_ALT_MASK) mod_num |= 2; if (modifiers & MOD_CONTROL_MASK) mod_num |= 4; if (mod_num != 0) len = snprintf(response, MAX_RESPONSE, "\e[%d;%d%c", num, mod_num + 1, code); else if (code != '~') len = snprintf(response, MAX_RESPONSE, "\e%c%c", escape, code); else len = snprintf(response, MAX_RESPONSE, "\e%c%d%c", escape, num, code); if (len >= MAX_RESPONSE) return MAX_RESPONSE - 1; else return len; } /* returns the number of bytes written into response, * which must have room for MAX_RESPONSE bytes */ static int apply_key_map(keyboard_mode mode, int sym, uint32_t modifiers, char *response) { struct key_map map; int len = 0; int i = 0; while (mode[i].sym) { map = mode[i++]; if (sym == map.sym) { len = function_key_response(map.escape, map.num, modifiers, map.code, response); break; } } return len; } struct terminal_color { double r, g, b, a; }; struct attr { unsigned char fg, bg; char a; /* attributes format: * 76543210 * cilub */ char s; /* in selection */ }; struct color_scheme { struct terminal_color palette[16]; char border; struct attr default_attr; }; static void attr_init(struct attr *data_attr, struct attr attr, int n) { int i; for (i = 0; i < n; i++) { data_attr[i] = attr; } } enum escape_state { escape_state_normal = 0, escape_state_escape, escape_state_dcs, escape_state_csi, escape_state_osc, escape_state_inner_escape, escape_state_ignore, escape_state_special }; #define ESC_FLAG_WHAT 0x01 #define ESC_FLAG_GT 0x02 #define ESC_FLAG_BANG 0x04 #define ESC_FLAG_CASH 0x08 #define ESC_FLAG_SQUOTE 0x10 #define ESC_FLAG_DQUOTE 0x20 #define ESC_FLAG_SPACE 0x40 enum { SELECT_NONE, SELECT_CHAR, SELECT_WORD, SELECT_LINE }; struct terminal { struct window *window; struct widget *widget; struct display *display; char *title; union utf8_char *data; struct task io_task; char *tab_ruler; struct attr *data_attr; struct attr curr_attr; uint32_t mode; char origin_mode; char saved_origin_mode; struct attr saved_attr; union utf8_char last_char; int margin_top, margin_bottom; character_set cs, g0, g1; character_set saved_cs, saved_g0, saved_g1; keyboard_mode key_mode; int data_pitch, attr_pitch; /* The width in bytes of a line */ int width, height, row, column, max_width; uint32_t buffer_height; uint32_t start, end, saved_start, log_size; wl_fixed_t smooth_scroll; int saved_row, saved_column; int scrolling; int send_cursor_position; int fd, master; uint32_t modifiers; char escape[MAX_ESCAPE+1]; int escape_length; enum escape_state state; enum escape_state outer_state; int escape_flags; struct utf8_state_machine state_machine; int margin; struct color_scheme *color_scheme; struct terminal_color color_table[256]; cairo_font_extents_t extents; double average_width; cairo_scaled_font_t *font_normal, *font_bold; uint32_t hide_cursor_serial; int size_in_title; struct wl_data_source *selection; uint32_t click_time; int dragging, click_count; int selection_start_x, selection_start_y; int selection_end_x, selection_end_y; int selection_start_row, selection_start_col; int selection_end_row, selection_end_col; struct wl_list link; int pace_pipe; }; /* Create default tab stops, every 8 characters */ static void terminal_init_tabs(struct terminal *terminal) { int i = 0; while (i < terminal->width) { if (i % 8 == 0) terminal->tab_ruler[i] = 1; else terminal->tab_ruler[i] = 0; i++; } } static void terminal_init(struct terminal *terminal) { terminal->curr_attr = terminal->color_scheme->default_attr; terminal->origin_mode = 0; terminal->mode = MODE_SHOW_CURSOR | MODE_AUTOREPEAT | MODE_ALT_SENDS_ESC | MODE_AUTOWRAP; terminal->row = 0; terminal->column = 0; terminal->g0 = CS_US; terminal->g1 = CS_US; terminal->cs = terminal->g0; terminal->key_mode = KM_NORMAL; terminal->saved_g0 = terminal->g0; terminal->saved_g1 = terminal->g1; terminal->saved_cs = terminal->cs; terminal->saved_attr = terminal->curr_attr; terminal->saved_origin_mode = terminal->origin_mode; terminal->saved_row = terminal->row; terminal->saved_column = terminal->column; if (terminal->tab_ruler != NULL) terminal_init_tabs(terminal); } static void init_color_table(struct terminal *terminal) { int c, r; struct terminal_color *color_table = terminal->color_table; for (c = 0; c < 256; c ++) { if (c < 16) { color_table[c] = terminal->color_scheme->palette[c]; } else if (c < 232) { r = c - 16; color_table[c].b = ((double)(r % 6) / 6.0); r /= 6; color_table[c].g = ((double)(r % 6) / 6.0); r /= 6; color_table[c].r = ((double)(r % 6) / 6.0); color_table[c].a = 1.0; } else { r = (c - 232) * 10 + 8; color_table[c].r = ((double) r) / 256.0; color_table[c].g = color_table[c].r; color_table[c].b = color_table[c].r; color_table[c].a = 1.0; } } } static union utf8_char * terminal_get_row(struct terminal *terminal, int row) { int index; index = (row + terminal->start) & (terminal->buffer_height - 1); return (void *) terminal->data + index * terminal->data_pitch; } static struct attr* terminal_get_attr_row(struct terminal *terminal, int row) { int index; index = (row + terminal->start) & (terminal->buffer_height - 1); return (void *) terminal->data_attr + index * terminal->attr_pitch; } union decoded_attr { struct attr attr; uint32_t key; }; static void terminal_decode_attr(struct terminal *terminal, int row, int col, union decoded_attr *decoded) { struct attr attr; int foreground, background, tmp; decoded->attr.s = 0; if (((row == terminal->selection_start_row && col >= terminal->selection_start_col) || row > terminal->selection_start_row) && ((row == terminal->selection_end_row && col < terminal->selection_end_col) || row < terminal->selection_end_row)) decoded->attr.s = 1; /* get the attributes for this character cell */ attr = terminal_get_attr_row(terminal, row)[col]; if ((attr.a & ATTRMASK_INVERSE) || decoded->attr.s || ((terminal->mode & MODE_SHOW_CURSOR) && window_has_focus(terminal->window) && terminal->row == row && terminal->column == col)) { foreground = attr.bg; background = attr.fg; if (attr.a & ATTRMASK_BOLD) { if (foreground <= 16) foreground |= 0x08; if (background <= 16) background &= 0x07; } } else { foreground = attr.fg; background = attr.bg; } if (terminal->mode & MODE_INVERSE) { tmp = foreground; foreground = background; background = tmp; if (attr.a & ATTRMASK_BOLD) { if (foreground <= 16) foreground |= 0x08; if (background <= 16) background &= 0x07; } } decoded->attr.fg = foreground; decoded->attr.bg = background; decoded->attr.a = attr.a; } static void terminal_scroll_buffer(struct terminal *terminal, int d) { int i; terminal->start += d; if (d < 0) { d = 0 - d; for (i = 0; i < d; i++) { memset(terminal_get_row(terminal, i), 0, terminal->data_pitch); attr_init(terminal_get_attr_row(terminal, i), terminal->curr_attr, terminal->width); } } else { for (i = terminal->height - d; i < terminal->height; i++) { memset(terminal_get_row(terminal, i), 0, terminal->data_pitch); attr_init(terminal_get_attr_row(terminal, i), terminal->curr_attr, terminal->width); } } terminal->selection_start_row -= d; terminal->selection_end_row -= d; } static void terminal_scroll_window(struct terminal *terminal, int d) { int i; int window_height; int from_row, to_row; // scrolling range is inclusive window_height = terminal->margin_bottom - terminal->margin_top + 1; d = d % (window_height + 1); if (d < 0) { d = 0 - d; to_row = terminal->margin_bottom; from_row = terminal->margin_bottom - d; for (i = 0; i < (window_height - d); i++) { memcpy(terminal_get_row(terminal, to_row - i), terminal_get_row(terminal, from_row - i), terminal->data_pitch); memcpy(terminal_get_attr_row(terminal, to_row - i), terminal_get_attr_row(terminal, from_row - i), terminal->attr_pitch); } for (i = terminal->margin_top; i < (terminal->margin_top + d); i++) { memset(terminal_get_row(terminal, i), 0, terminal->data_pitch); attr_init(terminal_get_attr_row(terminal, i), terminal->curr_attr, terminal->width); } } else { to_row = terminal->margin_top; from_row = terminal->margin_top + d; for (i = 0; i < (window_height - d); i++) { memcpy(terminal_get_row(terminal, to_row + i), terminal_get_row(terminal, from_row + i), terminal->data_pitch); memcpy(terminal_get_attr_row(terminal, to_row + i), terminal_get_attr_row(terminal, from_row + i), terminal->attr_pitch); } for (i = terminal->margin_bottom - d + 1; i <= terminal->margin_bottom; i++) { memset(terminal_get_row(terminal, i), 0, terminal->data_pitch); attr_init(terminal_get_attr_row(terminal, i), terminal->curr_attr, terminal->width); } } } static void terminal_scroll(struct terminal *terminal, int d) { if (terminal->margin_top == 0 && terminal->margin_bottom == terminal->height - 1) terminal_scroll_buffer(terminal, d); else terminal_scroll_window(terminal, d); } static void terminal_shift_line(struct terminal *terminal, int d) { union utf8_char *row; struct attr *attr_row; row = terminal_get_row(terminal, terminal->row); attr_row = terminal_get_attr_row(terminal, terminal->row); if ((terminal->width + d) <= terminal->column) d = terminal->column + 1 - terminal->width; if ((terminal->column + d) >= terminal->width) d = terminal->width - terminal->column - 1; if (d < 0) { d = 0 - d; memmove(&row[terminal->column], &row[terminal->column + d], (terminal->width - terminal->column - d) * sizeof(union utf8_char)); memmove(&attr_row[terminal->column], &attr_row[terminal->column + d], (terminal->width - terminal->column - d) * sizeof(struct attr)); memset(&row[terminal->width - d], 0, d * sizeof(union utf8_char)); attr_init(&attr_row[terminal->width - d], terminal->curr_attr, d); } else { memmove(&row[terminal->column + d], &row[terminal->column], (terminal->width - terminal->column - d) * sizeof(union utf8_char)); memmove(&attr_row[terminal->column + d], &attr_row[terminal->column], (terminal->width - terminal->column - d) * sizeof(struct attr)); memset(&row[terminal->column], 0, d * sizeof(union utf8_char)); attr_init(&attr_row[terminal->column], terminal->curr_attr, d); } } static void terminal_resize_cells(struct terminal *terminal, int width, int height) { union utf8_char *data; struct attr *data_attr; char *tab_ruler; int data_pitch, attr_pitch; int i, l, total_rows; uint32_t d, uheight = height; struct rectangle allocation; struct winsize ws; if (uheight > terminal->buffer_height) height = terminal->buffer_height; if (terminal->width == width && terminal->height == height) return; if (terminal->data && width <= terminal->max_width) { d = 0; if (height < terminal->height && height <= terminal->row) d = terminal->height - height; else if (height > terminal->height && terminal->height - 1 == terminal->row) { d = terminal->height - height; if (terminal->log_size < uheight) d = -terminal->start; } terminal->start += d; terminal->row -= d; } else { terminal->max_width = width; data_pitch = width * sizeof(union utf8_char); data = xzalloc(data_pitch * terminal->buffer_height); attr_pitch = width * sizeof(struct attr); data_attr = xmalloc(attr_pitch * terminal->buffer_height); tab_ruler = xzalloc(width); attr_init(data_attr, terminal->curr_attr, width * terminal->buffer_height); if (terminal->data && terminal->data_attr) { if (width > terminal->width) l = terminal->width; else l = width; if (terminal->height > height) { total_rows = height; i = 1 + terminal->row - height; if (i > 0) { terminal->start += i; terminal->row = terminal->row - i; } } else { total_rows = terminal->height; } for (i = 0; i < total_rows; i++) { memcpy(&data[width * i], terminal_get_row(terminal, i), l * sizeof(union utf8_char)); memcpy(&data_attr[width * i], terminal_get_attr_row(terminal, i), l * sizeof(struct attr)); } free(terminal->data); free(terminal->data_attr); free(terminal->tab_ruler); } terminal->data_pitch = data_pitch; terminal->attr_pitch = attr_pitch; terminal->data = data; terminal->data_attr = data_attr; terminal->tab_ruler = tab_ruler; terminal->start = 0; } terminal->margin_bottom = height - (terminal->height - terminal->margin_bottom); terminal->width = width; terminal->height = height; terminal_init_tabs(terminal); /* Update the window size */ ws.ws_row = terminal->height; ws.ws_col = terminal->width; widget_get_allocation(terminal->widget, &allocation); ws.ws_xpixel = allocation.width; ws.ws_ypixel = allocation.height; ioctl(terminal->master, TIOCSWINSZ, &ws); } static void update_title(struct terminal *terminal) { if (window_is_resizing(terminal->window)) { char *p; if (asprintf(&p, "%s — [%dx%d]", terminal->title, terminal->width, terminal->height) > 0) { window_set_title(terminal->window, p); free(p); } } else { window_set_title(terminal->window, terminal->title); } } static void resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct terminal *terminal = data; int32_t columns, rows, m; if (terminal->pace_pipe >= 0) { close(terminal->pace_pipe); terminal->pace_pipe = -1; } m = 2 * terminal->margin; columns = (width - m) / (int32_t) terminal->average_width; rows = (height - m) / (int32_t) terminal->extents.height; if (!window_is_fullscreen(terminal->window) && !window_is_maximized(terminal->window)) { width = columns * terminal->average_width + m; height = rows * terminal->extents.height + m; widget_set_size(terminal->widget, width, height); } terminal_resize_cells(terminal, columns, rows); update_title(terminal); } static void state_changed_handler(struct window *window, void *data) { struct terminal *terminal = data; update_title(terminal); } static void terminal_resize(struct terminal *terminal, int columns, int rows) { int32_t width, height, m; if (window_is_fullscreen(terminal->window) || window_is_maximized(terminal->window)) return; m = 2 * terminal->margin; width = columns * terminal->average_width + m; height = rows * terminal->extents.height + m; window_frame_set_child_size(terminal->widget, width, height); } struct color_scheme DEFAULT_COLORS = { { {0, 0, 0, 1}, /* black */ {0.66, 0, 0, 1}, /* red */ {0 , 0.66, 0, 1}, /* green */ {0.66, 0.33, 0, 1}, /* orange (nicer than muddy yellow) */ {0 , 0 , 0.66, 1}, /* blue */ {0.66, 0 , 0.66, 1}, /* magenta */ {0, 0.66, 0.66, 1}, /* cyan */ {0.66, 0.66, 0.66, 1}, /* light grey */ {0.22, 0.33, 0.33, 1}, /* dark grey */ {1, 0.33, 0.33, 1}, /* high red */ {0.33, 1, 0.33, 1}, /* high green */ {1, 1, 0.33, 1}, /* high yellow */ {0.33, 0.33, 1, 1}, /* high blue */ {1, 0.33, 1, 1}, /* high magenta */ {0.33, 1, 1, 1}, /* high cyan */ {1, 1, 1, 1} /* white */ }, 0, /* black border */ {7, 0, 0, } /* bg:black (0), fg:light gray (7) */ }; static void terminal_set_color(struct terminal *terminal, cairo_t *cr, int index) { cairo_set_source_rgba(cr, terminal->color_table[index].r, terminal->color_table[index].g, terminal->color_table[index].b, terminal->color_table[index].a); } static void terminal_send_selection(struct terminal *terminal, int fd) { int row, col; union utf8_char *p_row; union decoded_attr attr; FILE *fp; int len; fp = fdopen(fd, "w"); if (fp == NULL){ close(fd); return; } for (row = terminal->selection_start_row; row < terminal->height; row++) { p_row = terminal_get_row(terminal, row); for (col = 0; col < terminal->width; col++) { if (p_row[col].ch == 0x200B) /* space glyph */ continue; /* get the attributes for this character cell */ terminal_decode_attr(terminal, row, col, &attr); if (!attr.attr.s) continue; len = strnlen((char *) p_row[col].byte, 4); if (len > 0) fwrite(p_row[col].byte, 1, len, fp); if (len == 0 || col == terminal->width - 1) { fwrite("\n", 1, 1, fp); break; } } } fclose(fp); } struct glyph_run { struct terminal *terminal; cairo_t *cr; unsigned int count; union decoded_attr attr; cairo_glyph_t glyphs[256], *g; }; static void glyph_run_init(struct glyph_run *run, struct terminal *terminal, cairo_t *cr) { run->terminal = terminal; run->cr = cr; run->g = run->glyphs; run->count = 0; run->attr.key = 0; } static void glyph_run_flush(struct glyph_run *run, union decoded_attr attr) { cairo_scaled_font_t *font; if (run->count > ARRAY_LENGTH(run->glyphs) - 10 || (attr.key != run->attr.key)) { if (run->attr.attr.a & (ATTRMASK_BOLD | ATTRMASK_BLINK)) font = run->terminal->font_bold; else font = run->terminal->font_normal; cairo_set_scaled_font(run->cr, font); terminal_set_color(run->terminal, run->cr, run->attr.attr.fg); if (!(run->attr.attr.a & ATTRMASK_CONCEALED)) cairo_show_glyphs (run->cr, run->glyphs, run->count); run->g = run->glyphs; run->count = 0; } run->attr = attr; } static void glyph_run_add(struct glyph_run *run, int x, int y, union utf8_char *c) { int num_glyphs; cairo_scaled_font_t *font; num_glyphs = ARRAY_LENGTH(run->glyphs) - run->count; if (run->attr.attr.a & (ATTRMASK_BOLD | ATTRMASK_BLINK)) font = run->terminal->font_bold; else font = run->terminal->font_normal; cairo_move_to(run->cr, x, y); cairo_scaled_font_text_to_glyphs (font, x, y, (char *) c->byte, 4, &run->g, &num_glyphs, NULL, NULL, NULL); run->g += num_glyphs; run->count += num_glyphs; } static void redraw_handler(struct widget *widget, void *data) { struct terminal *terminal = data; struct rectangle allocation; cairo_t *cr; int top_margin, side_margin; int row, col, cursor_x, cursor_y; union utf8_char *p_row; union decoded_attr attr; int text_x, text_y; cairo_surface_t *surface; double d; struct glyph_run run; cairo_font_extents_t extents; double average_width; double unichar_width; surface = window_get_surface(terminal->window); widget_get_allocation(terminal->widget, &allocation); cr = widget_cairo_create(terminal->widget); cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); cairo_clip(cr); cairo_push_group(cr); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); terminal_set_color(terminal, cr, terminal->color_scheme->border); cairo_paint(cr); cairo_set_scaled_font(cr, terminal->font_normal); extents = terminal->extents; average_width = terminal->average_width; side_margin = (allocation.width - terminal->width * average_width) / 2; top_margin = (allocation.height - terminal->height * extents.height) / 2; cairo_set_line_width(cr, 1.0); cairo_translate(cr, allocation.x + side_margin, allocation.y + top_margin); /* paint the background */ for (row = 0; row < terminal->height; row++) { p_row = terminal_get_row(terminal, row); for (col = 0; col < terminal->width; col++) { /* get the attributes for this character cell */ terminal_decode_attr(terminal, row, col, &attr); if (attr.attr.bg == terminal->color_scheme->border) continue; if (is_wide(p_row[col])) unichar_width = 2 * average_width; else unichar_width = average_width; terminal_set_color(terminal, cr, attr.attr.bg); cairo_move_to(cr, col * average_width, row * extents.height); cairo_rel_line_to(cr, unichar_width, 0); cairo_rel_line_to(cr, 0, extents.height); cairo_rel_line_to(cr, -unichar_width, 0); cairo_close_path(cr); cairo_fill(cr); } } cairo_set_operator(cr, CAIRO_OPERATOR_OVER); /* paint the foreground */ glyph_run_init(&run, terminal, cr); for (row = 0; row < terminal->height; row++) { p_row = terminal_get_row(terminal, row); for (col = 0; col < terminal->width; col++) { /* get the attributes for this character cell */ terminal_decode_attr(terminal, row, col, &attr); glyph_run_flush(&run, attr); text_x = col * average_width; text_y = extents.ascent + row * extents.height; if (attr.attr.a & ATTRMASK_UNDERLINE) { terminal_set_color(terminal, cr, attr.attr.fg); cairo_move_to(cr, text_x, (double)text_y + 1.5); cairo_line_to(cr, text_x + average_width, (double) text_y + 1.5); cairo_stroke(cr); } /* skip space glyph (RLE) we use as a placeholder of the right half of a double-width character, because RLE is not available in every font. */ if (p_row[col].ch == 0x200B) continue; glyph_run_add(&run, text_x, text_y, &p_row[col]); } } attr.key = ~0; glyph_run_flush(&run, attr); if ((terminal->mode & MODE_SHOW_CURSOR) && !window_has_focus(terminal->window)) { d = 0.5; cairo_set_line_width(cr, 1); cairo_move_to(cr, terminal->column * average_width + d, terminal->row * extents.height + d); cairo_rel_line_to(cr, average_width - 2 * d, 0); cairo_rel_line_to(cr, 0, extents.height - 2 * d); cairo_rel_line_to(cr, -average_width + 2 * d, 0); cairo_close_path(cr); cairo_stroke(cr); } cairo_pop_group_to_source(cr); cairo_paint(cr); cairo_destroy(cr); cairo_surface_destroy(surface); if (terminal->send_cursor_position) { cursor_x = side_margin + allocation.x + terminal->column * average_width; cursor_y = top_margin + allocation.y + terminal->row * extents.height; window_set_text_cursor_position(terminal->window, cursor_x, cursor_y); terminal->send_cursor_position = 0; } } static void terminal_write(struct terminal *terminal, const char *data, size_t length) { if (write(terminal->master, data, length) < 0) abort(); terminal->send_cursor_position = 1; } static void terminal_data(struct terminal *terminal, const char *data, size_t length); static void handle_char(struct terminal *terminal, union utf8_char utf8); static void handle_sgr(struct terminal *terminal, int code); static void handle_term_parameter(struct terminal *terminal, int code, int sr) { int i; if (terminal->escape_flags & ESC_FLAG_WHAT) { switch(code) { case 1: /* DECCKM */ if (sr) terminal->key_mode = KM_APPLICATION; else terminal->key_mode = KM_NORMAL; break; case 2: /* DECANM */ /* No VT52 support yet */ terminal->g0 = CS_US; terminal->g1 = CS_US; terminal->cs = terminal->g0; break; case 3: /* DECCOLM */ if (sr) terminal_resize(terminal, 132, 24); else terminal_resize(terminal, 80, 24); /* set columns, but also home cursor and clear screen */ terminal->row = 0; terminal->column = 0; for (i = 0; i < terminal->height; i++) { memset(terminal_get_row(terminal, i), 0, terminal->data_pitch); attr_init(terminal_get_attr_row(terminal, i), terminal->curr_attr, terminal->width); } break; case 5: /* DECSCNM */ if (sr) terminal->mode |= MODE_INVERSE; else terminal->mode &= ~MODE_INVERSE; break; case 6: /* DECOM */ terminal->origin_mode = sr; if (terminal->origin_mode) terminal->row = terminal->margin_top; else terminal->row = 0; terminal->column = 0; break; case 7: /* DECAWM */ if (sr) terminal->mode |= MODE_AUTOWRAP; else terminal->mode &= ~MODE_AUTOWRAP; break; case 8: /* DECARM */ if (sr) terminal->mode |= MODE_AUTOREPEAT; else terminal->mode &= ~MODE_AUTOREPEAT; break; case 12: /* Very visible cursor (CVVIS) */ /* FIXME: What do we do here. */ break; case 25: if (sr) terminal->mode |= MODE_SHOW_CURSOR; else terminal->mode &= ~MODE_SHOW_CURSOR; break; case 1034: /* smm/rmm, meta mode on/off */ /* ignore */ break; case 1037: /* deleteSendsDel */ if (sr) terminal->mode |= MODE_DELETE_SENDS_DEL; else terminal->mode &= ~MODE_DELETE_SENDS_DEL; break; case 1039: /* altSendsEscape */ if (sr) terminal->mode |= MODE_ALT_SENDS_ESC; else terminal->mode &= ~MODE_ALT_SENDS_ESC; break; case 1049: /* rmcup/smcup, alternate screen */ /* Ignore. Should be possible to implement, * but it's kind of annoying. */ break; default: fprintf(stderr, "Unknown parameter: ?%d\n", code); break; } } else { switch(code) { case 4: /* IRM */ if (sr) terminal->mode |= MODE_IRM; else terminal->mode &= ~MODE_IRM; break; case 20: /* LNM */ if (sr) terminal->mode |= MODE_LF_NEWLINE; else terminal->mode &= ~MODE_LF_NEWLINE; break; default: fprintf(stderr, "Unknown parameter: %d\n", code); break; } } } static void handle_dcs(struct terminal *terminal) { } static void handle_osc(struct terminal *terminal) { char *p; int code; terminal->escape[terminal->escape_length++] = '\0'; p = &terminal->escape[2]; code = strtol(p, &p, 10); if (*p == ';') p++; switch (code) { case 0: /* Icon name and window title */ case 1: /* Icon label */ case 2: /* Window title*/ free(terminal->title); terminal->title = strdup(p); window_set_title(terminal->window, p); break; case 7: /* shell cwd as uri */ break; case 777: /* Desktop notifications */ break; default: fprintf(stderr, "Unknown OSC escape code %d, text %s\n", code, p); break; } } static void handle_escape(struct terminal *terminal) { union utf8_char *row; struct attr *attr_row; char *p; int i, count, x, y, top, bottom; int args[10], set[10] = { 0, }; char response[MAX_RESPONSE] = {0, }; struct rectangle allocation; terminal->escape[terminal->escape_length++] = '\0'; i = 0; p = &terminal->escape[2]; while ((isdigit(*p) || *p == ';') && i < 10) { if (*p == ';') { if (!set[i]) { args[i] = 0; set[i] = 1; } p++; i++; } else { args[i] = strtol(p, &p, 10); set[i] = 1; } } switch (*p) { case '@': /* ICH - Insert blank characters */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; terminal_shift_line(terminal, count); break; case 'A': /* CUU - Move cursor up rows */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; if (terminal->row - count >= terminal->margin_top) terminal->row -= count; else terminal->row = terminal->margin_top; break; case 'B': /* CUD - Move cursor down rows */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; if (terminal->row + count <= terminal->margin_bottom) terminal->row += count; else terminal->row = terminal->margin_bottom; break; case 'C': /* CUF - Move cursor right by columns */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; if ((terminal->column + count) < terminal->width) terminal->column += count; else terminal->column = terminal->width - 1; break; case 'D': /* CUB - Move cursor left columns */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; if ((terminal->column - count) >= 0) terminal->column -= count; else terminal->column = 0; break; case 'E': /* CNL - Move cursor down rows, to column 1 */ count = set[0] ? args[0] : 1; if (terminal->row + count <= terminal->margin_bottom) terminal->row += count; else terminal->row = terminal->margin_bottom; terminal->column = 0; break; case 'F': /* CPL - Move cursour up rows, to column 1 */ count = set[0] ? args[0] : 1; if (terminal->row - count >= terminal->margin_top) terminal->row -= count; else terminal->row = terminal->margin_top; terminal->column = 0; break; case 'G': /* CHA - Move cursor to column in current row */ y = set[0] ? args[0] : 1; y = y <= 0 ? 1 : y > terminal->width ? terminal->width : y; terminal->column = y - 1; break; case 'f': /* HVP - Move cursor to */ case 'H': /* CUP - Move cursor to (origin at 1,1) */ x = (set[1] ? args[1] : 1) - 1; x = x < 0 ? 0 : (x >= terminal->width ? terminal->width - 1 : x); y = (set[0] ? args[0] : 1) - 1; if (terminal->origin_mode) { y += terminal->margin_top; y = y < terminal->margin_top ? terminal->margin_top : (y > terminal->margin_bottom ? terminal->margin_bottom : y); } else { y = y < 0 ? 0 : (y >= terminal->height ? terminal->height - 1 : y); } terminal->row = y; terminal->column = x; break; case 'I': /* CHT */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; while (count > 0 && terminal->column < terminal->width) { if (terminal->tab_ruler[terminal->column]) count--; terminal->column++; } terminal->column--; break; case 'J': /* ED - Erase display */ row = terminal_get_row(terminal, terminal->row); attr_row = terminal_get_attr_row(terminal, terminal->row); if (!set[0] || args[0] == 0 || args[0] > 2) { memset(&row[terminal->column], 0, (terminal->width - terminal->column) * sizeof(union utf8_char)); attr_init(&attr_row[terminal->column], terminal->curr_attr, terminal->width - terminal->column); for (i = terminal->row + 1; i < terminal->height; i++) { memset(terminal_get_row(terminal, i), 0, terminal->data_pitch); attr_init(terminal_get_attr_row(terminal, i), terminal->curr_attr, terminal->width); } } else if (args[0] == 1) { memset(row, 0, (terminal->column+1) * sizeof(union utf8_char)); attr_init(attr_row, terminal->curr_attr, terminal->column+1); for (i = 0; i < terminal->row; i++) { memset(terminal_get_row(terminal, i), 0, terminal->data_pitch); attr_init(terminal_get_attr_row(terminal, i), terminal->curr_attr, terminal->width); } } else if (args[0] == 2) { /* Clear screen by scrolling contents out */ terminal_scroll_buffer(terminal, terminal->end - terminal->start); } break; case 'K': /* EL - Erase line */ row = terminal_get_row(terminal, terminal->row); attr_row = terminal_get_attr_row(terminal, terminal->row); if (!set[0] || args[0] == 0 || args[0] > 2) { memset(&row[terminal->column], 0, (terminal->width - terminal->column) * sizeof(union utf8_char)); attr_init(&attr_row[terminal->column], terminal->curr_attr, terminal->width - terminal->column); } else if (args[0] == 1) { memset(row, 0, (terminal->column+1) * sizeof(union utf8_char)); attr_init(attr_row, terminal->curr_attr, terminal->column+1); } else if (args[0] == 2) { memset(row, 0, terminal->data_pitch); attr_init(attr_row, terminal->curr_attr, terminal->width); } break; case 'L': /* IL - Insert blank lines */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; if (terminal->row >= terminal->margin_top && terminal->row < terminal->margin_bottom) { top = terminal->margin_top; terminal->margin_top = terminal->row; terminal_scroll(terminal, 0 - count); terminal->margin_top = top; } else if (terminal->row == terminal->margin_bottom) { memset(terminal_get_row(terminal, terminal->row), 0, terminal->data_pitch); attr_init(terminal_get_attr_row(terminal, terminal->row), terminal->curr_attr, terminal->width); } break; case 'M': /* DL - Delete lines */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; if (terminal->row >= terminal->margin_top && terminal->row < terminal->margin_bottom) { top = terminal->margin_top; terminal->margin_top = terminal->row; terminal_scroll(terminal, count); terminal->margin_top = top; } else if (terminal->row == terminal->margin_bottom) { memset(terminal_get_row(terminal, terminal->row), 0, terminal->data_pitch); } break; case 'P': /* DCH - Delete characters on current line */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; terminal_shift_line(terminal, 0 - count); break; case 'S': /* SU */ terminal_scroll(terminal, set[0] ? args[0] : 1); break; case 'T': /* SD */ terminal_scroll(terminal, 0 - (set[0] ? args[0] : 1)); break; case 'X': /* ECH - Erase characters on current line */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; if ((terminal->column + count) > terminal->width) count = terminal->width - terminal->column; row = terminal_get_row(terminal, terminal->row); attr_row = terminal_get_attr_row(terminal, terminal->row); memset(&row[terminal->column], 0, count * sizeof(union utf8_char)); attr_init(&attr_row[terminal->column], terminal->curr_attr, count); break; case 'Z': /* CBT */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; while (count > 0 && terminal->column >= 0) { if (terminal->tab_ruler[terminal->column]) count--; terminal->column--; } terminal->column++; break; case '`': /* HPA - Move cursor to column in current row */ y = set[0] ? args[0] : 1; y = y <= 0 ? 1 : y > terminal->width ? terminal->width : y; terminal->column = y - 1; break; case 'b': /* REP */ count = set[0] ? args[0] : 1; if (count == 0) count = 1; if (terminal->last_char.byte[0]) for (i = 0; i < count; i++) handle_char(terminal, terminal->last_char); terminal->last_char.byte[0] = 0; break; case 'c': /* Primary DA - Answer "I am a VT102" */ terminal_write(terminal, "\e[?6c", 5); break; case 'd': /* VPA - Move cursor to row, current column */ x = set[0] ? args[0] : 1; x = x <= 0 ? 1 : x > terminal->height ? terminal->height : x; terminal->row = x - 1; break; case 'g': /* TBC - Clear tab stop(s) */ if (!set[0] || args[0] == 0) { terminal->tab_ruler[terminal->column] = 0; } else if (args[0] == 3) { memset(terminal->tab_ruler, 0, terminal->width); } break; case 'h': /* SM - Set mode */ for (i = 0; i < 10 && set[i]; i++) { handle_term_parameter(terminal, args[i], 1); } break; case 'l': /* RM - Reset mode */ for (i = 0; i < 10 && set[i]; i++) { handle_term_parameter(terminal, args[i], 0); } break; case 'm': /* SGR - Set attributes */ for (i = 0; i < 10; i++) { if (i <= 7 && set[i] && set[i + 1] && set[i + 2] && args[i + 1] == 5) { if (args[i] == 38) { handle_sgr(terminal, args[i + 2] + 256); break; } else if (args[i] == 48) { handle_sgr(terminal, args[i + 2] + 512); break; } } if (set[i]) { handle_sgr(terminal, args[i]); } else if (i == 0) { handle_sgr(terminal, 0); break; } else { break; } } break; case 'n': /* DSR - Status report */ i = set[0] ? args[0] : 0; if (i == 0 || i == 5) { terminal_write(terminal, "\e[0n", 4); } else if (i == 6) { snprintf(response, MAX_RESPONSE, "\e[%d;%dR", terminal->origin_mode ? terminal->row+terminal->margin_top : terminal->row+1, terminal->column+1); terminal_write(terminal, response, strlen(response)); } break; case 'r': /* DECSTBM - Set scrolling region */ if (!set[0]) { terminal->margin_top = 0; terminal->margin_bottom = terminal->height-1; terminal->row = 0; terminal->column = 0; } else { top = (set[0] ? args[0] : 1) - 1; top = top < 0 ? 0 : (top >= terminal->height ? terminal->height - 1 : top); bottom = (set[1] ? args[1] : 1) - 1; bottom = bottom < 0 ? 0 : (bottom >= terminal->height ? terminal->height - 1 : bottom); if (bottom > top) { terminal->margin_top = top; terminal->margin_bottom = bottom; } else { terminal->margin_top = 0; terminal->margin_bottom = terminal->height-1; } if (terminal->origin_mode) terminal->row = terminal->margin_top; else terminal->row = 0; terminal->column = 0; } break; case 's': /* Save cursor location */ terminal->saved_row = terminal->row; terminal->saved_column = terminal->column; break; case 't': /* windowOps */ if (!set[0]) break; switch (args[0]) { case 4: /* resize px */ if (set[1] && set[2]) { widget_schedule_resize(terminal->widget, args[2], args[1]); } break; case 8: /* resize ch */ if (set[1] && set[2]) { terminal_resize(terminal, args[2], args[1]); } break; case 13: /* report position */ widget_get_allocation(terminal->widget, &allocation); snprintf(response, MAX_RESPONSE, "\e[3;%d;%dt", allocation.x, allocation.y); terminal_write(terminal, response, strlen(response)); break; case 14: /* report px */ widget_get_allocation(terminal->widget, &allocation); snprintf(response, MAX_RESPONSE, "\e[4;%d;%dt", allocation.height, allocation.width); terminal_write(terminal, response, strlen(response)); break; case 18: /* report ch */ snprintf(response, MAX_RESPONSE, "\e[9;%d;%dt", terminal->height, terminal->width); terminal_write(terminal, response, strlen(response)); break; case 21: /* report title */ snprintf(response, MAX_RESPONSE, "\e]l%s\e\\", window_get_title(terminal->window)); terminal_write(terminal, response, strlen(response)); break; default: if (args[0] >= 24) terminal_resize(terminal, terminal->width, args[0]); else fprintf(stderr, "Unimplemented windowOp %d\n", args[0]); break; } break; case 'u': /* Restore cursor location */ terminal->row = terminal->saved_row; terminal->column = terminal->saved_column; break; default: fprintf(stderr, "Unknown CSI escape: %c\n", *p); break; } } static void handle_non_csi_escape(struct terminal *terminal, char code) { switch(code) { case 'M': /* RI - Reverse linefeed */ terminal->row -= 1; if (terminal->row < terminal->margin_top) { terminal->row = terminal->margin_top; terminal_scroll(terminal, -1); } break; case 'E': /* NEL - Newline */ terminal->column = 0; // fallthrough case 'D': /* IND - Linefeed */ terminal->row += 1; if (terminal->row > terminal->margin_bottom) { terminal->row = terminal->margin_bottom; terminal_scroll(terminal, +1); } break; case 'c': /* RIS - Reset*/ terminal_init(terminal); break; case 'H': /* HTS - Set tab stop at current column */ terminal->tab_ruler[terminal->column] = 1; break; case '7': /* DECSC - Save current state */ terminal->saved_row = terminal->row; terminal->saved_column = terminal->column; terminal->saved_attr = terminal->curr_attr; terminal->saved_origin_mode = terminal->origin_mode; terminal->saved_cs = terminal->cs; terminal->saved_g0 = terminal->g0; terminal->saved_g1 = terminal->g1; break; case '8': /* DECRC - Restore state most recently saved by ESC 7 */ terminal->row = terminal->saved_row; terminal->column = terminal->saved_column; terminal->curr_attr = terminal->saved_attr; terminal->origin_mode = terminal->saved_origin_mode; terminal->cs = terminal->saved_cs; terminal->g0 = terminal->saved_g0; terminal->g1 = terminal->saved_g1; break; case '=': /* DECPAM - Set application keypad mode */ terminal->key_mode = KM_APPLICATION; break; case '>': /* DECPNM - Set numeric keypad mode */ terminal->key_mode = KM_NORMAL; break; default: fprintf(stderr, "Unknown escape code: %c\n", code); break; } } static void handle_special_escape(struct terminal *terminal, char special, char code) { int i, numChars; if (special == '#') { switch(code) { case '8': /* fill with 'E', no cheap way to do this */ memset(terminal->data, 0, terminal->data_pitch * terminal->height); numChars = terminal->width * terminal->height; for (i = 0; i < numChars; i++) { terminal->data[i].byte[0] = 'E'; } break; default: fprintf(stderr, "Unknown HASH escape #%c\n", code); break; } } else if (special == '(' || special == ')') { switch(code) { case '0': if (special == '(') terminal->g0 = CS_SPECIAL; else terminal->g1 = CS_SPECIAL; break; case 'A': if (special == '(') terminal->g0 = CS_UK; else terminal->g1 = CS_UK; break; case 'B': if (special == '(') terminal->g0 = CS_US; else terminal->g1 = CS_US; break; default: fprintf(stderr, "Unknown character set %c\n", code); break; } } else { fprintf(stderr, "Unknown special escape %c%c\n", special, code); } } static void handle_sgr(struct terminal *terminal, int code) { switch(code) { case 0: terminal->curr_attr = terminal->color_scheme->default_attr; break; case 1: terminal->curr_attr.a |= ATTRMASK_BOLD; if (terminal->curr_attr.fg < 8) terminal->curr_attr.fg += 8; break; case 4: terminal->curr_attr.a |= ATTRMASK_UNDERLINE; break; case 5: terminal->curr_attr.a |= ATTRMASK_BLINK; break; case 8: terminal->curr_attr.a |= ATTRMASK_CONCEALED; break; case 2: case 21: case 22: terminal->curr_attr.a &= ~ATTRMASK_BOLD; if (terminal->curr_attr.fg < 16 && terminal->curr_attr.fg >= 8) terminal->curr_attr.fg -= 8; break; case 24: terminal->curr_attr.a &= ~ATTRMASK_UNDERLINE; break; case 25: terminal->curr_attr.a &= ~ATTRMASK_BLINK; break; case 7: case 26: terminal->curr_attr.a |= ATTRMASK_INVERSE; break; case 27: terminal->curr_attr.a &= ~ATTRMASK_INVERSE; break; case 28: terminal->curr_attr.a &= ~ATTRMASK_CONCEALED; break; case 39: terminal->curr_attr.fg = terminal->color_scheme->default_attr.fg; break; case 49: terminal->curr_attr.bg = terminal->color_scheme->default_attr.bg; break; default: if (code >= 30 && code <= 37) { terminal->curr_attr.fg = code - 30; if (terminal->curr_attr.a & ATTRMASK_BOLD) terminal->curr_attr.fg += 8; } else if (code >= 40 && code <= 47) { terminal->curr_attr.bg = code - 40; } else if (code >= 90 && code <= 97) { terminal->curr_attr.fg = code - 90 + 8; } else if (code >= 100 && code <= 107) { terminal->curr_attr.bg = code - 100 + 8; } else if (code >= 256 && code < 512) { terminal->curr_attr.fg = code - 256; } else if (code >= 512 && code < 768) { terminal->curr_attr.bg = code - 512; } else { fprintf(stderr, "Unknown SGR code: %d\n", code); } break; } } /* Returns 1 if c was special, otherwise 0 */ static int handle_special_char(struct terminal *terminal, char c) { union utf8_char *row; struct attr *attr_row; row = terminal_get_row(terminal, terminal->row); attr_row = terminal_get_attr_row(terminal, terminal->row); switch(c) { case '\r': terminal->column = 0; break; case '\n': if (terminal->mode & MODE_LF_NEWLINE) { terminal->column = 0; } /* fallthrough */ case '\v': case '\f': terminal->row++; if (terminal->row > terminal->margin_bottom) { terminal->row = terminal->margin_bottom; terminal_scroll(terminal, +1); } break; case '\t': while (terminal->column < terminal->width) { if (terminal->mode & MODE_IRM) terminal_shift_line(terminal, +1); if (row[terminal->column].byte[0] == '\0') { row[terminal->column].byte[0] = ' '; row[terminal->column].byte[1] = '\0'; attr_row[terminal->column] = terminal->curr_attr; } terminal->column++; if (terminal->tab_ruler[terminal->column]) break; } if (terminal->column >= terminal->width) { terminal->column = terminal->width - 1; } break; case '\b': if (terminal->column >= terminal->width) { terminal->column = terminal->width - 2; } else if (terminal->column > 0) { terminal->column--; } else if (terminal->mode & MODE_AUTOWRAP) { terminal->column = terminal->width - 1; terminal->row -= 1; if (terminal->row < terminal->margin_top) { terminal->row = terminal->margin_top; terminal_scroll(terminal, -1); } } break; case '\a': /* Bell */ break; case '\x0E': /* SO */ terminal->cs = terminal->g1; break; case '\x0F': /* SI */ terminal->cs = terminal->g0; break; case '\0': break; default: return 0; } return 1; } static void handle_char(struct terminal *terminal, union utf8_char utf8) { union utf8_char *row; struct attr *attr_row; if (handle_special_char(terminal, utf8.byte[0])) return; apply_char_set(terminal->cs, &utf8); /* There are a whole lot of non-characters, control codes, * and formatting codes that should probably be ignored, * for example: */ if (strncmp((char*) utf8.byte, "\xEF\xBB\xBF", 3) == 0) { /* BOM, ignore */ return; } /* Some of these non-characters should be translated, e.g.: */ if (utf8.byte[0] < 32) { utf8.byte[0] = utf8.byte[0] + 64; } /* handle right margin effects */ if (terminal->column >= terminal->width) { if (terminal->mode & MODE_AUTOWRAP) { terminal->column = 0; terminal->row += 1; if (terminal->row > terminal->margin_bottom) { terminal->row = terminal->margin_bottom; terminal_scroll(terminal, +1); } } else { terminal->column--; } } row = terminal_get_row(terminal, terminal->row); attr_row = terminal_get_attr_row(terminal, terminal->row); if (terminal->mode & MODE_IRM) terminal_shift_line(terminal, +1); row[terminal->column] = utf8; attr_row[terminal->column++] = terminal->curr_attr; if (terminal->row + terminal->start + 1 > terminal->end) terminal->end = terminal->row + terminal->start + 1; if (terminal->end == terminal->buffer_height) terminal->log_size = terminal->buffer_height; else if (terminal->log_size < terminal->buffer_height) terminal->log_size = terminal->end; /* cursor jump for wide character. */ if (is_wide(utf8)) row[terminal->column++].ch = 0x200B; /* space glyph */ if (utf8.ch != terminal->last_char.ch) terminal->last_char = utf8; } static void escape_append_utf8(struct terminal *terminal, union utf8_char utf8) { int len, i; if ((utf8.byte[0] & 0x80) == 0x00) len = 1; else if ((utf8.byte[0] & 0xE0) == 0xC0) len = 2; else if ((utf8.byte[0] & 0xF0) == 0xE0) len = 3; else if ((utf8.byte[0] & 0xF8) == 0xF0) len = 4; else len = 1; /* Invalid, cannot happen */ if (terminal->escape_length + len <= MAX_ESCAPE) { for (i = 0; i < len; i++) terminal->escape[terminal->escape_length + i] = utf8.byte[i]; terminal->escape_length += len; } else if (terminal->escape_length < MAX_ESCAPE) { terminal->escape[terminal->escape_length++] = 0; } } static void terminal_data(struct terminal *terminal, const char *data, size_t length) { unsigned int i; union utf8_char utf8; enum utf8_state parser_state; for (i = 0; i < length; i++) { parser_state = utf8_next_char(&terminal->state_machine, data[i]); switch(parser_state) { case utf8state_accept: utf8.ch = terminal->state_machine.s.ch; break; case utf8state_reject: /* the unicode replacement character */ utf8.byte[0] = 0xEF; utf8.byte[1] = 0xBF; utf8.byte[2] = 0xBD; utf8.byte[3] = 0x00; break; default: continue; } /* assume escape codes never use non-ASCII characters */ switch (terminal->state) { case escape_state_escape: escape_append_utf8(terminal, utf8); switch (utf8.byte[0]) { case 'P': /* DCS */ terminal->state = escape_state_dcs; break; case '[': /* CSI */ terminal->state = escape_state_csi; break; case ']': /* OSC */ terminal->state = escape_state_osc; break; case '#': case '(': case ')': /* special */ terminal->state = escape_state_special; break; case '^': /* PM (not implemented) */ case '_': /* APC (not implemented) */ terminal->state = escape_state_ignore; break; default: terminal->state = escape_state_normal; handle_non_csi_escape(terminal, utf8.byte[0]); break; } continue; case escape_state_csi: if (handle_special_char(terminal, utf8.byte[0]) != 0) { /* do nothing */ } else if (utf8.byte[0] == '?') { terminal->escape_flags |= ESC_FLAG_WHAT; } else if (utf8.byte[0] == '>') { terminal->escape_flags |= ESC_FLAG_GT; } else if (utf8.byte[0] == '!') { terminal->escape_flags |= ESC_FLAG_BANG; } else if (utf8.byte[0] == '$') { terminal->escape_flags |= ESC_FLAG_CASH; } else if (utf8.byte[0] == '\'') { terminal->escape_flags |= ESC_FLAG_SQUOTE; } else if (utf8.byte[0] == '"') { terminal->escape_flags |= ESC_FLAG_DQUOTE; } else if (utf8.byte[0] == ' ') { terminal->escape_flags |= ESC_FLAG_SPACE; } else { escape_append_utf8(terminal, utf8); if (terminal->escape_length >= MAX_ESCAPE) terminal->state = escape_state_normal; } if (isalpha(utf8.byte[0]) || utf8.byte[0] == '@' || utf8.byte[0] == '`') { terminal->state = escape_state_normal; handle_escape(terminal); } else { } continue; case escape_state_inner_escape: if (utf8.byte[0] == '\\') { terminal->state = escape_state_normal; if (terminal->outer_state == escape_state_dcs) { handle_dcs(terminal); } else if (terminal->outer_state == escape_state_osc) { handle_osc(terminal); } } else if (utf8.byte[0] == '\e') { terminal->state = terminal->outer_state; escape_append_utf8(terminal, utf8); if (terminal->escape_length >= MAX_ESCAPE) terminal->state = escape_state_normal; } else { terminal->state = terminal->outer_state; if (terminal->escape_length < MAX_ESCAPE) terminal->escape[terminal->escape_length++] = '\e'; escape_append_utf8(terminal, utf8); if (terminal->escape_length >= MAX_ESCAPE) terminal->state = escape_state_normal; } continue; case escape_state_dcs: case escape_state_osc: case escape_state_ignore: if (utf8.byte[0] == '\e') { terminal->outer_state = terminal->state; terminal->state = escape_state_inner_escape; } else if (utf8.byte[0] == '\a' && terminal->state == escape_state_osc) { terminal->state = escape_state_normal; handle_osc(terminal); } else { escape_append_utf8(terminal, utf8); if (terminal->escape_length >= MAX_ESCAPE) terminal->state = escape_state_normal; } continue; case escape_state_special: escape_append_utf8(terminal, utf8); terminal->state = escape_state_normal; if (isdigit(utf8.byte[0]) || isalpha(utf8.byte[0])) { handle_special_escape(terminal, terminal->escape[1], utf8.byte[0]); } continue; default: break; } /* this is valid, because ASCII characters are never used to * introduce a multibyte sequence in UTF-8 */ if (utf8.byte[0] == '\e') { terminal->state = escape_state_escape; terminal->outer_state = escape_state_normal; terminal->escape[0] = '\e'; terminal->escape_length = 1; terminal->escape_flags = 0; } else { handle_char(terminal, utf8); } /* if */ } /* for */ window_schedule_redraw(terminal->window); } static void data_source_target(void *data, struct wl_data_source *source, const char *mime_type) { fprintf(stderr, "data_source_target, %s\n", mime_type); } static void data_source_send(void *data, struct wl_data_source *source, const char *mime_type, int32_t fd) { struct terminal *terminal = data; terminal_send_selection(terminal, fd); } static void data_source_cancelled(void *data, struct wl_data_source *source) { wl_data_source_destroy(source); } static void data_source_dnd_drop_performed(void *data, struct wl_data_source *source) { } static void data_source_dnd_finished(void *data, struct wl_data_source *source) { } static void data_source_action(void *data, struct wl_data_source *source, uint32_t dnd_action) { } static const struct wl_data_source_listener data_source_listener = { data_source_target, data_source_send, data_source_cancelled, data_source_dnd_drop_performed, data_source_dnd_finished, data_source_action }; static const char text_mime_type[] = "text/plain;charset=utf-8"; static void data_handler(struct window *window, struct input *input, float x, float y, const char **types, void *data) { int i, has_text = 0; if (!types) return; for (i = 0; types[i]; i++) if (strcmp(types[i], text_mime_type) == 0) has_text = 1; if (!has_text) { input_accept(input, NULL); } else { input_accept(input, text_mime_type); } } static void drop_handler(struct window *window, struct input *input, int32_t x, int32_t y, void *data) { struct terminal *terminal = data; input_receive_drag_data_to_fd(input, text_mime_type, terminal->master); } static void fullscreen_handler(struct window *window, void *data) { struct terminal *terminal = data; window_set_fullscreen(window, !window_is_fullscreen(terminal->window)); } static void close_handler(void *data) { struct terminal *terminal = data; terminal_destroy(terminal); } static void terminal_copy(struct terminal *terminal, struct input *input) { terminal->selection = display_create_data_source(terminal->display); if (!terminal->selection) return; wl_data_source_offer(terminal->selection, "text/plain;charset=utf-8"); wl_data_source_add_listener(terminal->selection, &data_source_listener, terminal); input_set_selection(input, terminal->selection, display_get_serial(terminal->display)); } static void terminal_paste(struct terminal *terminal, struct input *input) { input_receive_selection_data_to_fd(input, "text/plain;charset=utf-8", terminal->master); } static void terminal_new_instance(struct terminal *terminal) { struct terminal *new_terminal; new_terminal = terminal_create(terminal->display); if (terminal_run(new_terminal, option_shell)) terminal_destroy(new_terminal); } static int handle_bound_key(struct terminal *terminal, struct input *input, uint32_t sym, uint32_t time) { switch (sym) { case XKB_KEY_X: /* Cut selection; terminal doesn't do cut, fall * through to copy. */ case XKB_KEY_C: terminal_copy(terminal, input); return 1; case XKB_KEY_V: terminal_paste(terminal, input); return 1; case XKB_KEY_N: terminal_new_instance(terminal); return 1; case XKB_KEY_Up: if (!terminal->scrolling) terminal->saved_start = terminal->start; if (terminal->start == terminal->end - terminal->log_size) return 1; terminal->scrolling = 1; terminal->start--; terminal->row++; terminal->selection_start_row++; terminal->selection_end_row++; widget_schedule_redraw(terminal->widget); return 1; case XKB_KEY_Down: if (!terminal->scrolling) terminal->saved_start = terminal->start; if (terminal->start == terminal->saved_start) return 1; terminal->scrolling = 1; terminal->start++; terminal->row--; terminal->selection_start_row--; terminal->selection_end_row--; widget_schedule_redraw(terminal->widget); return 1; default: return 0; } } static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data) { struct terminal *terminal = data; char ch[MAX_RESPONSE]; uint32_t modifiers, serial; int ret, len = 0, d; bool convert_utf8 = true; modifiers = input_get_modifiers(input); if ((modifiers & MOD_CONTROL_MASK) && (modifiers & MOD_SHIFT_MASK) && state == WL_KEYBOARD_KEY_STATE_PRESSED && handle_bound_key(terminal, input, sym, time)) return; /* Map keypad symbols to 'normal' equivalents before processing */ switch (sym) { case XKB_KEY_KP_Space: sym = XKB_KEY_space; break; case XKB_KEY_KP_Tab: sym = XKB_KEY_Tab; break; case XKB_KEY_KP_Enter: sym = XKB_KEY_Return; break; case XKB_KEY_KP_Left: sym = XKB_KEY_Left; break; case XKB_KEY_KP_Up: sym = XKB_KEY_Up; break; case XKB_KEY_KP_Right: sym = XKB_KEY_Right; break; case XKB_KEY_KP_Down: sym = XKB_KEY_Down; break; case XKB_KEY_KP_Equal: sym = XKB_KEY_equal; break; case XKB_KEY_KP_Multiply: sym = XKB_KEY_asterisk; break; case XKB_KEY_KP_Add: sym = XKB_KEY_plus; break; case XKB_KEY_KP_Separator: /* Note this is actually locale-dependent and should mostly be * a comma. But leave it as period until we one day start * doing the right thing. */ sym = XKB_KEY_period; break; case XKB_KEY_KP_Subtract: sym = XKB_KEY_minus; break; case XKB_KEY_KP_Decimal: sym = XKB_KEY_period; break; case XKB_KEY_KP_Divide: sym = XKB_KEY_slash; break; case XKB_KEY_KP_0: case XKB_KEY_KP_1: case XKB_KEY_KP_2: case XKB_KEY_KP_3: case XKB_KEY_KP_4: case XKB_KEY_KP_5: case XKB_KEY_KP_6: case XKB_KEY_KP_7: case XKB_KEY_KP_8: case XKB_KEY_KP_9: sym = (sym - XKB_KEY_KP_0) + XKB_KEY_0; break; default: break; } switch (sym) { case XKB_KEY_BackSpace: if (modifiers & MOD_ALT_MASK) ch[len++] = 0x1b; ch[len++] = 0x7f; break; case XKB_KEY_Tab: case XKB_KEY_Linefeed: case XKB_KEY_Clear: case XKB_KEY_Pause: case XKB_KEY_Scroll_Lock: case XKB_KEY_Sys_Req: case XKB_KEY_Escape: ch[len++] = sym & 0x7f; break; case XKB_KEY_Return: if (terminal->mode & MODE_LF_NEWLINE) { ch[len++] = 0x0D; ch[len++] = 0x0A; } else { ch[len++] = 0x0D; } break; case XKB_KEY_Shift_L: case XKB_KEY_Shift_R: case XKB_KEY_Control_L: case XKB_KEY_Control_R: case XKB_KEY_Alt_L: case XKB_KEY_Alt_R: case XKB_KEY_Meta_L: case XKB_KEY_Meta_R: case XKB_KEY_Super_L: case XKB_KEY_Super_R: case XKB_KEY_Hyper_L: case XKB_KEY_Hyper_R: break; case XKB_KEY_Insert: len = function_key_response('[', 2, modifiers, '~', ch); break; case XKB_KEY_Delete: if (terminal->mode & MODE_DELETE_SENDS_DEL) { ch[len++] = '\x04'; } else { len = function_key_response('[', 3, modifiers, '~', ch); } break; case XKB_KEY_Page_Up: len = function_key_response('[', 5, modifiers, '~', ch); break; case XKB_KEY_Page_Down: len = function_key_response('[', 6, modifiers, '~', ch); break; case XKB_KEY_F1: len = function_key_response('O', 1, modifiers, 'P', ch); break; case XKB_KEY_F2: len = function_key_response('O', 1, modifiers, 'Q', ch); break; case XKB_KEY_F3: len = function_key_response('O', 1, modifiers, 'R', ch); break; case XKB_KEY_F4: len = function_key_response('O', 1, modifiers, 'S', ch); break; case XKB_KEY_F5: len = function_key_response('[', 15, modifiers, '~', ch); break; case XKB_KEY_F6: len = function_key_response('[', 17, modifiers, '~', ch); break; case XKB_KEY_F7: len = function_key_response('[', 18, modifiers, '~', ch); break; case XKB_KEY_F8: len = function_key_response('[', 19, modifiers, '~', ch); break; case XKB_KEY_F9: len = function_key_response('[', 20, modifiers, '~', ch); break; case XKB_KEY_F10: len = function_key_response('[', 21, modifiers, '~', ch); break; case XKB_KEY_F12: len = function_key_response('[', 24, modifiers, '~', ch); break; default: /* Handle special keys with alternate mappings */ len = apply_key_map(terminal->key_mode, sym, modifiers, ch); if (len != 0) break; if (modifiers & MOD_CONTROL_MASK) { if (sym >= '3' && sym <= '7') sym = (sym & 0x1f) + 8; if (!((sym >= '!' && sym <= '/') || (sym >= '8' && sym <= '?') || (sym >= '0' && sym <= '2'))) sym = sym & 0x1f; else if (sym == '2') sym = 0x00; else if (sym == '/') sym = 0x1F; else if (sym == '8' || sym == '?') sym = 0x7F; } if (modifiers & MOD_ALT_MASK) { if (terminal->mode & MODE_ALT_SENDS_ESC) { ch[len++] = 0x1b; } else { sym = sym | 0x80; convert_utf8 = false; } } if ((sym < 128) || (!convert_utf8 && sym < 256)) { ch[len++] = sym; } else { ret = xkb_keysym_to_utf8(sym, ch + len, MAX_RESPONSE - len); if (ret < 0) fprintf(stderr, "Warning: buffer too small to encode " "UTF8 character\n"); else len += ret; } break; } if (state == WL_KEYBOARD_KEY_STATE_PRESSED && len > 0) { if (terminal->scrolling) { d = terminal->saved_start - terminal->start; terminal->row -= d; terminal->selection_start_row -= d; terminal->selection_end_row -= d; terminal->start = terminal->saved_start; terminal->scrolling = 0; widget_schedule_redraw(terminal->widget); } terminal_write(terminal, ch, len); /* Hide cursor, except if this was coming from a * repeating key press. */ serial = display_get_serial(terminal->display); if (terminal->hide_cursor_serial != serial) { input_set_pointer_image(input, CURSOR_BLANK); terminal->hide_cursor_serial = serial; } } } static void keyboard_focus_handler(struct window *window, struct input *device, void *data) { struct terminal *terminal = data; window_schedule_redraw(terminal->window); } static int wordsep(int ch) { const char extra[] = "-,./?%&#:_=+@~"; if (ch > 127 || ch < 0) return 1; return ch == 0 || !(isalpha(ch) || isdigit(ch) || strchr(extra, ch)); } static int recompute_selection(struct terminal *terminal) { struct rectangle allocation; int col, x, width, height; int start_row, end_row; int word_start, eol; int side_margin, top_margin; int start_x, end_x; int cw, ch; union utf8_char *data = NULL; cw = terminal->average_width; ch = terminal->extents.height; widget_get_allocation(terminal->widget, &allocation); width = terminal->width * cw; height = terminal->height * ch; side_margin = allocation.x + (allocation.width - width) / 2; top_margin = allocation.y + (allocation.height - height) / 2; start_row = (terminal->selection_start_y - top_margin + ch) / ch - 1; end_row = (terminal->selection_end_y - top_margin + ch) / ch - 1; if (start_row < end_row || (start_row == end_row && terminal->selection_start_x < terminal->selection_end_x)) { terminal->selection_start_row = start_row; terminal->selection_end_row = end_row; start_x = terminal->selection_start_x; end_x = terminal->selection_end_x; } else { terminal->selection_start_row = end_row; terminal->selection_end_row = start_row; start_x = terminal->selection_end_x; end_x = terminal->selection_start_x; } eol = 0; if (terminal->selection_start_row < 0) { terminal->selection_start_row = 0; terminal->selection_start_col = 0; } else { x = side_margin + cw / 2; data = terminal_get_row(terminal, terminal->selection_start_row); word_start = 0; for (col = 0; col < terminal->width; col++, x += cw) { if (col == 0 || wordsep(data[col - 1].ch)) word_start = col; if (data[col].ch != 0) eol = col + 1; if (start_x < x) break; } switch (terminal->dragging) { case SELECT_LINE: terminal->selection_start_col = 0; break; case SELECT_WORD: terminal->selection_start_col = word_start; break; case SELECT_CHAR: terminal->selection_start_col = col; break; } } if (terminal->selection_end_row >= terminal->height) { terminal->selection_end_row = terminal->height; terminal->selection_end_col = 0; } else { x = side_margin + cw / 2; data = terminal_get_row(terminal, terminal->selection_end_row); for (col = 0; col < terminal->width; col++, x += cw) { if (terminal->dragging == SELECT_CHAR && end_x < x) break; if (terminal->dragging == SELECT_WORD && end_x < x && wordsep(data[col].ch)) break; } terminal->selection_end_col = col; } if (terminal->selection_end_col != terminal->selection_start_col || terminal->selection_start_row != terminal->selection_end_row) { col = terminal->selection_end_col; if (col > 0 && data[col - 1].ch == 0) terminal->selection_end_col = terminal->width; data = terminal_get_row(terminal, terminal->selection_start_row); if (data[terminal->selection_start_col].ch == 0) terminal->selection_start_col = eol; } return 1; } static void terminal_minimize(struct terminal *terminal) { window_set_minimized(terminal->window); } static void menu_func(void *data, struct input *input, int index) { struct window *window = data; struct terminal *terminal = window_get_user_data(window); fprintf(stderr, "picked entry %d\n", index); switch (index) { case 0: terminal_new_instance(terminal); break; case 1: terminal_copy(terminal, input); break; case 2: terminal_paste(terminal, input); break; case 3: terminal_minimize(terminal); break; } } static void show_menu(struct terminal *terminal, struct input *input, uint32_t time) { int32_t x, y; static const char *entries[] = { "Open Terminal", "Copy", "Paste", "Minimize" }; input_get_position(input, &x, &y); window_show_menu(terminal->display, input, time, terminal->window, x - 10, y - 10, menu_func, entries, ARRAY_LENGTH(entries)); } static void click_handler(struct widget *widget, struct terminal *terminal, struct input *input, int32_t x, int32_t y, uint32_t time) { if (time - terminal->click_time < 500) terminal->click_count++; else terminal->click_count = 1; terminal->click_time = time; terminal->dragging = (terminal->click_count - 1) % 3 + SELECT_CHAR; terminal->selection_end_x = terminal->selection_start_x = x; terminal->selection_end_y = terminal->selection_start_y = y; if (recompute_selection(terminal)) widget_schedule_redraw(widget); } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct terminal *terminal = data; int32_t x, y; switch (button) { case BTN_LEFT: if (state == WL_POINTER_BUTTON_STATE_PRESSED) { input_get_position(input, &x, &y); click_handler(widget, terminal, input, x, y, time); } else { terminal->dragging = SELECT_NONE; } break; case BTN_RIGHT: if (state == WL_POINTER_BUTTON_STATE_PRESSED) show_menu(terminal, input, time); break; } } static int enter_handler(struct widget *widget, struct input *input, float x, float y, void *data) { return CURSOR_IBEAM; } static int motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct terminal *terminal = data; if (terminal->dragging) { input_get_position(input, &terminal->selection_end_x, &terminal->selection_end_y); if (recompute_selection(terminal)) widget_schedule_redraw(widget); } return CURSOR_IBEAM; } /* This magnitude is chosen rather arbitrarily. Really, the scrolling * should happen on a (fractional) pixel basis, not a line basis. */ #define AXIS_UNITS_PER_LINE 256 static void axis_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t axis, wl_fixed_t value, void *data) { struct terminal *terminal = data; int lines; if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) return; terminal->smooth_scroll += value; lines = terminal->smooth_scroll / AXIS_UNITS_PER_LINE; terminal->smooth_scroll -= lines * AXIS_UNITS_PER_LINE; if (lines > 0) { if (terminal->scrolling) { if ((uint32_t)lines > terminal->saved_start - terminal->start) lines = terminal->saved_start - terminal->start; } else { lines = 0; } } else if (lines < 0) { uint32_t neg_lines = -lines; if (neg_lines > terminal->log_size + terminal->start - terminal->end) lines = terminal->end - terminal->log_size - terminal->start; } if (lines) { if (!terminal->scrolling) terminal->saved_start = terminal->start; terminal->scrolling = 1; terminal->start += lines; terminal->row -= lines; terminal->selection_start_row -= lines; terminal->selection_end_row -= lines; widget_schedule_redraw(widget); } } static void output_handler(struct window *window, struct output *output, int enter, void *data) { if (enter) window_set_buffer_transform(window, output_get_transform(output)); window_set_buffer_scale(window, window_get_output_scale(window)); window_schedule_redraw(window); } static void touch_down_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data) { struct terminal *terminal = data; if (id == 0) click_handler(widget, terminal, input, x, y, time); } static void touch_up_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, void *data) { struct terminal *terminal = data; if (id == 0) terminal->dragging = SELECT_NONE; } static void touch_motion_handler(struct widget *widget, struct input *input, uint32_t time, int32_t id, float x, float y, void *data) { struct terminal *terminal = data; if (terminal->dragging && id == 0) { terminal->selection_end_x = (int)x; terminal->selection_end_y = (int)y; if (recompute_selection(terminal)) widget_schedule_redraw(widget); } } #ifndef howmany #define howmany(x, y) (((x) + ((y) - 1)) / (y)) #endif static struct terminal * terminal_create(struct display *display) { struct terminal *terminal; cairo_surface_t *surface; cairo_t *cr; cairo_text_extents_t text_extents; terminal = xzalloc(sizeof *terminal); terminal->color_scheme = &DEFAULT_COLORS; terminal_init(terminal); terminal->margin_top = 0; terminal->margin_bottom = -1; terminal->window = window_create(display); terminal->widget = window_frame_create(terminal->window, terminal); terminal->title = xstrdup("Wayland Terminal"); window_set_title(terminal->window, terminal->title); widget_set_transparent(terminal->widget, 0); init_state_machine(&terminal->state_machine); init_color_table(terminal); terminal->display = display; terminal->margin = 5; terminal->buffer_height = 1024; terminal->end = 1; window_set_user_data(terminal->window, terminal); window_set_key_handler(terminal->window, key_handler); window_set_keyboard_focus_handler(terminal->window, keyboard_focus_handler); window_set_fullscreen_handler(terminal->window, fullscreen_handler); window_set_output_handler(terminal->window, output_handler); window_set_close_handler(terminal->window, close_handler); window_set_state_changed_handler(terminal->window, state_changed_handler); window_set_data_handler(terminal->window, data_handler); window_set_drop_handler(terminal->window, drop_handler); widget_set_redraw_handler(terminal->widget, redraw_handler); widget_set_resize_handler(terminal->widget, resize_handler); widget_set_button_handler(terminal->widget, button_handler); widget_set_enter_handler(terminal->widget, enter_handler); widget_set_motion_handler(terminal->widget, motion_handler); widget_set_axis_handler(terminal->widget, axis_handler); widget_set_touch_up_handler(terminal->widget, touch_up_handler); widget_set_touch_down_handler(terminal->widget, touch_down_handler); widget_set_touch_motion_handler(terminal->widget, touch_motion_handler); surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); cr = cairo_create(surface); cairo_set_font_size(cr, option_font_size); cairo_select_font_face (cr, option_font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); terminal->font_bold = cairo_get_scaled_font (cr); cairo_scaled_font_reference(terminal->font_bold); cairo_select_font_face (cr, option_font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); terminal->font_normal = cairo_get_scaled_font (cr); cairo_scaled_font_reference(terminal->font_normal); cairo_font_extents(cr, &terminal->extents); /* Compute the average ascii glyph width */ cairo_text_extents(cr, TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS, &text_extents); terminal->average_width = howmany (text_extents.width, strlen(TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS)); terminal->average_width = ceil(terminal->average_width); cairo_destroy(cr); cairo_surface_destroy(surface); terminal_resize(terminal, 20, 5); /* Set minimum size first */ terminal_resize(terminal, 80, 25); wl_list_insert(terminal_list.prev, &terminal->link); return terminal; } static void terminal_destroy(struct terminal *terminal) { display_unwatch_fd(terminal->display, terminal->master); window_destroy(terminal->window); close(terminal->master); wl_list_remove(&terminal->link); if (wl_list_empty(&terminal_list)) display_exit(terminal->display); free(terminal->title); free(terminal); } static void io_handler(struct task *task, uint32_t events) { struct terminal *terminal = container_of(task, struct terminal, io_task); char buffer[256]; int len; if (events & EPOLLHUP) { terminal_destroy(terminal); return; } len = read(terminal->master, buffer, sizeof buffer); if (len < 0) terminal_destroy(terminal); else terminal_data(terminal, buffer, len); } static int terminal_run(struct terminal *terminal, const char *path) { int master; pid_t pid; int pipes[2]; /* Awkwardness: There's a sticky race condition here. If * anything prints after the forkpty() but before the window has * a size then we'll segfault. So we make a pipe and wait on * it before actually exec()ing the terminal. The resize * handler closes it in the parent process and the child continues * on to launch a shell. * * The reason we don't just do terminal_run() after the window * has a size is that we'd prefer to perform the fork() before * the process opens a wayland connection. */ if (pipe(pipes) == -1) { fprintf(stderr, "Can't create pipe for pacing.\n"); exit(EXIT_FAILURE); } pid = forkpty(&master, NULL, NULL, NULL); if (pid == 0) { int ret; close(pipes[1]); do { char tmp; ret = read(pipes[0], &tmp, 1); } while (ret == -1 && errno == EINTR); close(pipes[0]); setenv("TERM", option_term, 1); setenv("COLORTERM", option_term, 1); if (execl(path, path, NULL)) { printf("exec failed: %s\n", strerror(errno)); exit(EXIT_FAILURE); } } else if (pid < 0) { fprintf(stderr, "failed to fork and create pty (%s).\n", strerror(errno)); return -1; } close(pipes[0]); terminal->master = master; terminal->pace_pipe = pipes[1]; fcntl(master, F_SETFL, O_NONBLOCK); terminal->io_task.run = io_handler; display_watch_fd(terminal->display, terminal->master, EPOLLIN | EPOLLHUP, &terminal->io_task); if (option_fullscreen) window_set_fullscreen(terminal->window, 1); else if (option_maximize) window_set_maximized(terminal->window, 1); else terminal_resize(terminal, 80, 24); return 0; } static const struct weston_option terminal_options[] = { { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &option_fullscreen }, { WESTON_OPTION_BOOLEAN, "maximized", 'm', &option_maximize }, { WESTON_OPTION_STRING, "font", 0, &option_font }, { WESTON_OPTION_INTEGER, "font-size", 0, &option_font_size }, { WESTON_OPTION_STRING, "shell", 0, &option_shell }, }; int main(int argc, char *argv[]) { struct display *d; struct terminal *terminal; const char *config_file; struct sigaction sigpipe; struct weston_config *config; struct weston_config_section *s; /* as wcwidth is locale-dependent, wcwidth needs setlocale call to function properly. */ setlocale(LC_ALL, ""); option_shell = getenv("SHELL"); if (!option_shell) option_shell = "/bin/bash"; config_file = weston_config_get_name_from_env(); config = weston_config_parse(config_file); s = weston_config_get_section(config, "terminal", NULL, NULL); weston_config_section_get_string(s, "font", &option_font, "mono"); weston_config_section_get_int(s, "font-size", &option_font_size, 14); weston_config_section_get_string(s, "term", &option_term, "xterm"); weston_config_destroy(config); if (parse_options(terminal_options, ARRAY_LENGTH(terminal_options), &argc, argv) > 1) { printf("Usage: %s [OPTIONS]\n" " --fullscreen or -f\n" " --maximized or -m\n" " --font=NAME\n" " --font-size=SIZE\n" " --shell=NAME\n", argv[0]); return 1; } /* Disable SIGPIPE so that paste operations do not crash the program * when the file descriptor provided to receive data is a pipe or * socket whose reading end has been closed */ sigpipe.sa_handler = SIG_IGN; sigemptyset(&sigpipe.sa_mask); sigpipe.sa_flags = 0; sigaction(SIGPIPE, &sigpipe, NULL); d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } wl_list_init(&terminal_list); terminal = terminal_create(d); if (terminal_run(terminal, option_shell)) exit(EXIT_FAILURE); display_run(d); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3322964 weston-8.0.0/clients/touch-calibrator.c0000644000175000017460000005217200000000000020375 0ustar00simonwheel00000000000000/* * Copyright 2012 Intel Corporation * Copyright 2017-2018 Collabora, Ltd. * Copyright 2017-2018 General Electric Company * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "clients/window.h" #include "shared/helpers.h" #include #include "weston-touch-calibration-client-protocol.h" enum exit_code { CAL_EXIT_SUCCESS = 0, CAL_EXIT_ERROR = 1, CAL_EXIT_CANCELLED = 2, }; static int debug_; static int verbose_; #define pr_ver(...) do { \ if (verbose_) \ printf(__VA_ARGS__); \ } while (0) #define pr_dbg(...) do { \ if (debug_) \ fprintf(stderr, __VA_ARGS__); \ } while (0) static void pr_err(const char *fmt, ...) WL_PRINTF(1, 2); /* Our points for the calibration must be not be on a line */ static const struct { float x_ratio, y_ratio; } test_ratios[] = { { 0.15, 0.10 }, /* three points for calibration */ { 0.85, 0.13 }, { 0.20, 0.80 }, { 0.70, 0.75 } /* and one for verification */ }; #define NR_SAMPLES ((int)ARRAY_LENGTH(test_ratios)) struct point { double x; double y; }; struct sample { int ind; struct point drawn; /**< drawn point, pixels */ struct weston_touch_coordinate *pending; struct point drawn_cal; /**< drawn point, converted */ bool conv_done; struct point touched; /**< touch point, normalized */ bool touch_done; }; struct poly { struct color { double r, g, b, a; } color; int n_verts; const struct point *verts; }; /** Touch event handling state machine * * Only a complete down->up->frame sequence should be accepted with user * feedback "right", and anything that deviates from that (invalid_touch, * cancel, multiple touch-downs) needs to undo the current sample and * possibly show user feedback "wrong". * * \ * - \: \ * * IDLE * - touch down: sample, -> DOWN * - touch up: no-op * - frame: no-op * - invalid_touch: (undo), wrong, -> WAIT * - cancel: no-op * DOWN (first touch down) * - touch down: undo, wrong, -> WAIT * - touch up: -> UP * - frame: no-op * - invalid_touch: undo, wrong, -> WAIT * - cancel: undo, -> IDLE * UP (first touch was down and up) * - touch down: undo, wrong, -> WAIT * - touch up: no-op * - frame: right, touch finish, -> WAIT * - invalid_touch: undo, wrong, -> WAIT * - cancel: undo, -> IDLE * WAIT (show user feedback) * - touch down: no-op * - touch up: no-op * - frame, cancel, timer: if num_tp == 0 && timer_done -> IDLE * - invalid_touch: no-op */ enum touch_state { STATE_IDLE, STATE_DOWN, STATE_UP, STATE_WAIT }; struct calibrator { struct sample samples[NR_SAMPLES]; int current_sample; struct display *display; struct weston_touch_calibration *calibration; struct weston_touch_calibrator *calibrator; struct window *window; struct widget *widget; int n_devices_listed; char *match_name; char *device_name; int width; int height; bool cancelled; const struct poly *current_poly; bool exiting; struct toytimer wait_timer; bool timer_pending; enum touch_state state; int num_tp; /* touch points down count */ }; static struct sample * current_sample(struct calibrator *cal) { return &cal->samples[cal->current_sample]; } static void sample_start(struct calibrator *cal, int i) { struct sample *s = &cal->samples[i]; assert(i >= 0 && i < NR_SAMPLES); s->ind = i; s->drawn.x = round(test_ratios[i].x_ratio * cal->width); s->drawn.y = round(test_ratios[i].y_ratio * cal->height); s->pending = NULL; s->conv_done = false; s->touch_done = false; cal->current_sample = i; } static struct point wire_to_point(uint32_t xu, uint32_t yu) { struct point p = { .x = (double)xu / 0xffffffff, .y = (double)yu / 0xffffffff }; return p; } static void sample_touch_down(struct calibrator *cal, uint32_t xu, uint32_t yu) { struct sample *s = current_sample(cal); s->touched = wire_to_point(xu, yu); s->touch_done = true; pr_dbg("Down[%d] (%f, %f)\n", s->ind, s->touched.x, s->touched.y); } static void coordinate_result_handler(void *data, struct weston_touch_coordinate *interface, uint32_t xu, uint32_t yu) { struct sample *s = data; weston_touch_coordinate_destroy(s->pending); s->pending = NULL; s->drawn_cal = wire_to_point(xu, yu); s->conv_done = true; pr_dbg("Conv[%d] (%f, %f)\n", s->ind, s->drawn_cal.x, s->drawn_cal.y); } struct weston_touch_coordinate_listener coordinate_listener = { coordinate_result_handler }; static void sample_undo(struct calibrator *cal) { struct sample *s = current_sample(cal); pr_dbg("Undo[%d]\n", s->ind); s->touch_done = false; s->conv_done = false; if (s->pending) { weston_touch_coordinate_destroy(s->pending); s->pending = NULL; } } static void sample_finish(struct calibrator *cal) { struct sample *s = current_sample(cal); pr_dbg("Finish[%d]\n", s->ind); assert(!s->pending && !s->conv_done); s->pending = weston_touch_calibrator_convert(cal->calibrator, (int32_t)s->drawn.x, (int32_t)s->drawn.y); weston_touch_coordinate_add_listener(s->pending, &coordinate_listener, s); if (cal->current_sample + 1 < NR_SAMPLES) { sample_start(cal, cal->current_sample + 1); } else { pr_dbg("got all touches\n"); cal->exiting = true; } } /* * Calibration algorithm: * * The equation we want to apply at event time where x' and y' are the * calibrated co-ordinates. * * x' = Ax + By + C * y' = Dx + Ey + F * * For example "zero calibration" would be A=1.0 B=0.0 C=0.0, D=0.0, E=1.0, * and F=0.0. * * With 6 unknowns we need 6 equations to find the constants: * * x1' = Ax1 + By1 + C * y1' = Dx1 + Ey1 + F * ... * x3' = Ax3 + By3 + C * y3' = Dx3 + Ey3 + F * * In matrix form: * * x1' x1 y1 1 A * x2' = x2 y2 1 x B * x3' x3 y3 1 C * * So making the matrix M we can find the constants with: * * A x1' * B = M^-1 x x2' * C x3' * * (and similarly for D, E and F) * * For the calibration the desired values x, y are the same values at which * we've drawn at. * */ static int compute_calibration(struct calibrator *cal, float *result) { struct weston_matrix m; struct weston_matrix inverse; struct weston_vector x_calib; struct weston_vector y_calib; int i; assert(NR_SAMPLES >= 3); /* * x1 y1 1 0 * x2 y2 1 0 * x3 y3 1 0 * 0 0 0 1 */ weston_matrix_init(&m); for (i = 0; i < 3; i++) { m.d[i + 0] = cal->samples[i].touched.x; m.d[i + 4] = cal->samples[i].touched.y; m.d[i + 8] = 1.0f; } m.type = WESTON_MATRIX_TRANSFORM_OTHER; if (weston_matrix_invert(&inverse, &m) < 0) { pr_err("non-invertible matrix during computation\n"); return -1; } for (i = 0; i < 3; i++) { x_calib.f[i] = cal->samples[i].drawn_cal.x; y_calib.f[i] = cal->samples[i].drawn_cal.y; } x_calib.f[3] = 0.0f; y_calib.f[3] = 0.0f; /* Multiples into the vector */ weston_matrix_transform(&inverse, &x_calib); weston_matrix_transform(&inverse, &y_calib); for (i = 0; i < 3; i++) result[i] = x_calib.f[i]; for (i = 0; i < 3; i++) result[i + 3] = y_calib.f[i]; return 0; } static int verify_calibration(struct calibrator *cal, const float *r) { double thr = 0.1; /* accepted error radius */ struct point e; /* expected value; error */ const struct sample *s = &cal->samples[3]; /* transform raw touches through the matrix */ e.x = r[0] * s->touched.x + r[1] * s->touched.y + r[2]; e.y = r[3] * s->touched.x + r[4] * s->touched.y + r[5]; /* compute error */ e.x -= s->drawn_cal.x; e.y -= s->drawn_cal.y; pr_dbg("calibration test error: %f, %f\n", e.x, e.y); if (e.x * e.x + e.y * e.y < thr * thr) return 0; pr_err("Calibration verification failed, too large error.\n"); return -1; } static void send_calibration(struct calibrator *cal, float *values) { struct wl_array matrix; float *f; int i; wl_array_init(&matrix); for (i = 0; i < 6; i++) { f = wl_array_add(&matrix, sizeof *f); *f = values[i]; } weston_touch_calibration_save(cal->calibration, cal->device_name, &matrix); wl_array_release(&matrix); } static const struct point cross_verts[] = { { 0.1, 0.2 }, { 0.2, 0.1 }, { 0.5, 0.4 }, { 0.8, 0.1 }, { 0.9, 0.2 }, { 0.6, 0.5 }, { 0.9, 0.8 }, { 0.8, 0.9 }, { 0.5, 0.6 }, { 0.2, 0.9 }, { 0.1, 0.8 }, { 0.4, 0.5 }, }; /* a red cross, for "wrong" */ static const struct poly cross = { .color = { 0.7, 0.0, 0.0, 1.0 }, .n_verts = ARRAY_LENGTH(cross_verts), .verts = cross_verts }; static const struct point check_verts[] = { { 0.5, 0.7 }, { 0.8, 0.1 }, { 0.9, 0.1 }, { 0.55, 0.8 }, { 0.45, 0.8 }, { 0.3, 0.5 }, { 0.4, 0.5 } }; /* a green check mark, for "right" */ static const struct poly check = { .color = { 0.0, 0.7, 0.0, 1.0 }, .n_verts = ARRAY_LENGTH(check_verts), .verts = check_verts }; static void draw_poly(cairo_t *cr, const struct poly *poly) { int i; cairo_set_source_rgba(cr, poly->color.r, poly->color.g, poly->color.b, poly->color.a); cairo_move_to(cr, poly->verts[0].x, poly->verts[0].y); for (i = 1; i < poly->n_verts; i++) cairo_line_to(cr, poly->verts[i].x, poly->verts[i].y); cairo_close_path(cr); cairo_fill(cr); } static void feedback_show(struct calibrator *cal, const struct poly *what) { cal->current_poly = what; widget_schedule_redraw(cal->widget); toytimer_arm_once_usec(&cal->wait_timer, 1000 * 1000); cal->timer_pending = true; } static void feedback_hide(struct calibrator *cal) { cal->current_poly = NULL; widget_schedule_redraw(cal->widget); } static void try_enter_state_idle(struct calibrator *cal) { if (cal->num_tp != 0) return; if (cal->timer_pending) return; cal->state = STATE_IDLE; feedback_hide(cal); if (cal->exiting) display_exit(cal->display); } static void enter_state_wait(struct calibrator *cal) { assert(cal->timer_pending); cal->state = STATE_WAIT; } static void wait_timer_done(struct toytimer *tt) { struct calibrator *cal = container_of(tt, struct calibrator, wait_timer); assert(cal->state == STATE_WAIT); cal->timer_pending = false; try_enter_state_idle(cal); } static void redraw_handler(struct widget *widget, void *data) { struct calibrator *cal = data; struct sample *s = current_sample(cal); struct rectangle allocation; cairo_surface_t *surface; cairo_t *cr; widget_get_allocation(cal->widget, &allocation); assert(allocation.width == cal->width); assert(allocation.height == cal->height); surface = window_get_surface(cal->window); cr = cairo_create(surface); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); cairo_paint(cr); if (!cal->current_poly) { cairo_translate(cr, s->drawn.x, s->drawn.y); cairo_set_line_width(cr, 2.0); cairo_set_source_rgb(cr, 0.7, 0.0, 0.0); cairo_move_to(cr, 0, -10.0); cairo_line_to(cr, 0, 10.0); cairo_stroke(cr); cairo_move_to(cr, -10.0, 0); cairo_line_to(cr, 10.0, 0.0); cairo_stroke(cr); } else { cairo_scale(cr, allocation.width, allocation.height); draw_poly(cr, cal->current_poly); } cairo_destroy(cr); cairo_surface_destroy(surface); } static struct calibrator * calibrator_create(struct display *display, const char *match_name) { struct calibrator *cal; cal = zalloc(sizeof *cal); if (!cal) abort(); cal->match_name = match_name ? strdup(match_name) : NULL; cal->window = window_create_custom(display); cal->widget = window_add_widget(cal->window, cal); window_inhibit_redraw(cal->window); window_set_title(cal->window, "Touchscreen calibrator"); cal->display = display; widget_set_redraw_handler(cal->widget, redraw_handler); toytimer_init(&cal->wait_timer, CLOCK_MONOTONIC, display, wait_timer_done); cal->state = STATE_IDLE; cal->num_tp = 0; return cal; } static void configure_handler(void *data, struct weston_touch_calibrator *interface, int32_t width, int32_t height) { struct calibrator *cal = data; pr_dbg("Configure calibrator window to size %ix%i\n", width, height); cal->width = width; cal->height = height; window_schedule_resize(cal->window, width, height); window_uninhibit_redraw(cal->window); sample_start(cal, 0); widget_schedule_redraw(cal->widget); } static void cancel_calibration_handler(void *data, struct weston_touch_calibrator *interface) { struct calibrator *cal = data; pr_dbg("calibration cancelled by the display server, quitting.\n"); cal->cancelled = true; display_exit(cal->display); } static void invalid_touch_handler(void *data, struct weston_touch_calibrator *interface) { struct calibrator *cal = data; pr_dbg("invalid touch\n"); switch (cal->state) { case STATE_IDLE: case STATE_DOWN: case STATE_UP: sample_undo(cal); feedback_show(cal, &cross); enter_state_wait(cal); break; case STATE_WAIT: /* no-op */ break; } } static void down_handler(void *data, struct weston_touch_calibrator *interface, uint32_t time, int32_t id, uint32_t xu, uint32_t yu) { struct calibrator *cal = data; cal->num_tp++; switch (cal->state) { case STATE_IDLE: sample_touch_down(cal, xu, yu); cal->state = STATE_DOWN; break; case STATE_DOWN: case STATE_UP: sample_undo(cal); feedback_show(cal, &cross); enter_state_wait(cal); break; case STATE_WAIT: /* no-op */ break; } if (cal->current_poly) return; } static void up_handler(void *data, struct weston_touch_calibrator *interface, uint32_t time, int32_t id) { struct calibrator *cal = data; cal->num_tp--; if (cal->num_tp < 0) { pr_dbg("Unmatched touch up.\n"); cal->num_tp = 0; } switch (cal->state) { case STATE_DOWN: cal->state = STATE_UP; break; case STATE_IDLE: case STATE_UP: case STATE_WAIT: /* no-op */ break; } } static void motion_handler(void *data, struct weston_touch_calibrator *interface, uint32_t time, int32_t id, uint32_t xu, uint32_t yu) { /* motion is ignored */ } static void frame_handler(void *data, struct weston_touch_calibrator *interface) { struct calibrator *cal = data; switch (cal->state) { case STATE_IDLE: case STATE_DOWN: /* no-op */ break; case STATE_UP: feedback_show(cal, &check); sample_finish(cal); enter_state_wait(cal); break; case STATE_WAIT: try_enter_state_idle(cal); break; } } static void cancel_handler(void *data, struct weston_touch_calibrator *interface) { struct calibrator *cal = data; cal->num_tp = 0; switch (cal->state) { case STATE_IDLE: /* no-op */ break; case STATE_DOWN: case STATE_UP: sample_undo(cal); try_enter_state_idle(cal); break; case STATE_WAIT: try_enter_state_idle(cal); break; } } struct weston_touch_calibrator_listener calibrator_listener = { configure_handler, cancel_calibration_handler, invalid_touch_handler, down_handler, up_handler, motion_handler, frame_handler, cancel_handler }; static void calibrator_show(struct calibrator *cal) { struct wl_surface *surface = window_get_wl_surface(cal->window); cal->calibrator = weston_touch_calibration_create_calibrator(cal->calibration, surface, cal->device_name); weston_touch_calibrator_add_listener(cal->calibrator, &calibrator_listener, cal); } static void calibrator_destroy(struct calibrator *cal) { toytimer_fini(&cal->wait_timer); if (cal->calibrator) weston_touch_calibrator_destroy(cal->calibrator); if (cal->calibration) weston_touch_calibration_destroy(cal->calibration); if (cal->widget) widget_destroy(cal->widget); if (cal->window) window_destroy(cal->window); free(cal->match_name); free(cal->device_name); free(cal); } static void touch_device_handler(void *data, struct weston_touch_calibration *c, const char *device, const char *head) { struct calibrator *cal = data; cal->n_devices_listed++; if (!cal->match_name) { printf("device \"%s\" - head \"%s\"\n", device, head); return; } if (cal->device_name) return; if (strcmp(cal->match_name, device) == 0 || strcmp(cal->match_name, head) == 0) cal->device_name = strdup(device); } struct weston_touch_calibration_listener touch_calibration_listener = { touch_device_handler }; static void global_handler(struct display *display, uint32_t name, const char *interface, uint32_t version, void *data) { struct calibrator *cal = data; if (strcmp(interface, "weston_touch_calibration") == 0) { cal->calibration = display_bind(display, name, &weston_touch_calibration_interface, 1); weston_touch_calibration_add_listener(cal->calibration, &touch_calibration_listener, cal); } } static int calibrator_run(struct calibrator *cal) { struct wl_display *dpy; struct sample *s; bool wait; int i; int ret; float result[6]; calibrator_show(cal); display_run(cal->display); if (cal->cancelled) return CAL_EXIT_CANCELLED; /* remove the window, no more input events */ widget_destroy(cal->widget); cal->widget = NULL; window_destroy(cal->window); cal->window = NULL; /* wait for all conversions to return */ dpy = display_get_display(cal->display); do { wait = false; for (i = 0; i < NR_SAMPLES; i++) if (cal->samples[i].pending) wait = true; if (wait) { ret = wl_display_roundtrip(dpy); if (ret < 0) return CAL_EXIT_ERROR; } } while (wait); for (i = 0; i < NR_SAMPLES; i++) { s = &cal->samples[i]; if (!s->conv_done || !s->touch_done) return CAL_EXIT_ERROR; } if (compute_calibration(cal, result) < 0) return CAL_EXIT_ERROR; if (verify_calibration(cal, result) < 0) return CAL_EXIT_ERROR; pr_ver("Calibration values:"); for (i = 0; i < 6; i++) pr_ver(" %f", result[i]); pr_ver("\n"); send_calibration(cal, result); ret = wl_display_roundtrip(dpy); if (ret < 0) return CAL_EXIT_ERROR; return CAL_EXIT_SUCCESS; } static void pr_err(const char *fmt, ...) { va_list argp; va_start(argp, fmt); fprintf(stderr, "%s error: ", program_invocation_short_name); vfprintf(stderr, fmt, argp); va_end(argp); } static void help(void) { fprintf(stderr, "Compute a touchscreen calibration matrix for " "a Wayland compositor by\n" "having the user touch points on the screen.\n\n"); fprintf(stderr, "Usage: %s [options...] name\n\n", program_invocation_short_name); fprintf(stderr, "Where 'name' can be a touch device sys path or a head name.\n" "If 'name' is not given, all devices available for " "calibration will be listed.\n" "If 'name' is given, it must be exactly as listed.\n" "Options:\n" " --debug Print messages to help debugging.\n" " -h, --help Display this help message\n" " -v, --verbose Print list header and calibration result.\n"); } int main(int argc, char *argv[]) { struct display *display; struct calibrator *cal; int c; char *match_name = NULL; int exit_code = CAL_EXIT_SUCCESS; static const struct option opts[] = { { "help", no_argument, NULL, 'h' }, { "debug", no_argument, &debug_, 1 }, { "verbose", no_argument, &verbose_, 1 }, { 0, 0, NULL, 0 } }; while ((c = getopt_long(argc, argv, "hv", opts, NULL)) != -1) { switch (c) { case 'h': help(); return CAL_EXIT_SUCCESS; case 'v': verbose_ = 1; break; case 0: break; default: return CAL_EXIT_ERROR; } } if (optind < argc) match_name = argv[optind++]; if (optind < argc) { pr_err("extra arguments given.\n\n"); help(); return CAL_EXIT_ERROR; } display = display_create(&argc, argv); if (!display) return CAL_EXIT_ERROR; cal = calibrator_create(display, match_name); if (!cal) return CAL_EXIT_ERROR; display_set_user_data(display, cal); display_set_global_handler(display, global_handler); if (!match_name) pr_ver("Available touch devices:\n"); /* Roundtrip to get list of available touch devices, * first globals, then touch_device events */ wl_display_roundtrip(display_get_display(display)); wl_display_roundtrip(display_get_display(display)); if (!cal->calibration) { exit_code = CAL_EXIT_ERROR; pr_err("the Wayland server does not expose the calibration interface.\n"); } else if (cal->device_name) { exit_code = calibrator_run(cal); } else if (match_name) { exit_code = CAL_EXIT_ERROR; pr_err("\"%s\" was not found.\n", match_name); } else if (cal->n_devices_listed == 0) { fprintf(stderr, "No devices listed.\n"); } calibrator_destroy(cal); display_destroy(display); return exit_code; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3322964 weston-8.0.0/clients/transformed.c0000644000175000017460000001671500000000000017462 0ustar00simonwheel00000000000000/* * Copyright © 2008 Kristian Høgsberg * Copyright © 2012 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "window.h" struct transformed { struct display *display; struct window *window; struct widget *widget; int width, height; int fullscreen; }; static void draw_stuff(cairo_t *cr, int width, int height) { cairo_matrix_t m; cairo_get_matrix (cr, &m); cairo_translate(cr, width / 2, height / 2); cairo_scale(cr, width / 2, height / 2); cairo_set_source_rgba(cr, 0, 0, 0.3, 1.0); cairo_set_source_rgba(cr, 0, 0, 0, 1.0); cairo_rectangle(cr, -1, -1, 2, 2); cairo_fill(cr); cairo_set_source_rgb(cr, 1, 0, 0); cairo_move_to(cr, 0, 0); cairo_line_to(cr, 0, -1); cairo_save(cr); cairo_set_matrix(cr, &m); cairo_set_line_width(cr, 2.0); cairo_stroke(cr); cairo_restore(cr); cairo_set_source_rgb(cr, 0, 1, 0); cairo_move_to(cr, 0, 0); cairo_line_to(cr, 1, 0); cairo_save(cr); cairo_set_matrix(cr, &m); cairo_set_line_width(cr, 2.0); cairo_stroke(cr); cairo_restore(cr); cairo_set_source_rgb(cr, 1, 1, 1); cairo_move_to(cr, 0, 0); cairo_line_to(cr, 0, 1); cairo_move_to(cr, 0, 0); cairo_line_to(cr, -1, 0); cairo_save(cr); cairo_set_matrix(cr, &m); cairo_set_line_width(cr, 2.0); cairo_stroke(cr); cairo_restore(cr); cairo_destroy(cr); } static void fullscreen_handler(struct window *window, void *data) { struct transformed *transformed = data; transformed->fullscreen ^= 1; window_set_fullscreen(window, transformed->fullscreen); } static void redraw_handler(struct widget *widget, void *data) { struct transformed *transformed = data; struct rectangle allocation; cairo_surface_t *surface; cairo_t *cr; surface = window_get_surface(transformed->window); if (surface == NULL || cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "failed to create cairo egl surface\n"); return; } widget_get_allocation(transformed->widget, &allocation); cr = widget_cairo_create(widget); draw_stuff(cr, allocation.width, allocation.height); cairo_surface_destroy(surface); } static void output_handler(struct window *window, struct output *output, int enter, void *data) { if (!enter) return; window_set_buffer_transform(window, output_get_transform(output)); window_set_buffer_scale(window, output_get_scale(output)); window_schedule_redraw(window); } static void key_handler(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, void *data) { int transform, scale; if (state == WL_KEYBOARD_KEY_STATE_RELEASED) return; transform = window_get_buffer_transform (window); scale = window_get_buffer_scale (window); switch (sym) { case XKB_KEY_Left: if (transform == 0) transform = 3; else if (transform == 4) transform = 7; else transform--; break; case XKB_KEY_Right: if (transform == 3) transform = 0; else if (transform == 7) transform = 4; else transform++; break; case XKB_KEY_space: if (transform >= 4) transform -= 4; else transform += 4; break; case XKB_KEY_z: if (scale == 1) scale = 2; else scale = 1; break; } printf ("setting buffer transform to %d\n", transform); printf ("setting buffer scale to %d\n", scale); window_set_buffer_transform(window, transform); window_set_buffer_scale(window, scale); window_schedule_redraw(window); } static void button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct transformed *transformed = data; switch (button) { case BTN_LEFT: if (state == WL_POINTER_BUTTON_STATE_PRESSED) window_move(transformed->window, input, display_get_serial(transformed->display)); break; case BTN_MIDDLE: if (state == WL_POINTER_BUTTON_STATE_PRESSED) widget_schedule_redraw(widget); break; case BTN_RIGHT: if (state == WL_POINTER_BUTTON_STATE_PRESSED) window_show_frame_menu(transformed->window, input, time); break; } } static void touch_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data) { struct transformed *transformed = data; window_move(transformed->window, input, display_get_serial(transformed->display)); } static void usage(int error_code) { fprintf(stderr, "Usage: transformed [OPTIONS]\n\n" " -d\t\tUse \"driver\" fullscreen method\n" " -w \tSet window width to \n" " -h \tSet window height to \n" " --help\tShow this help text\n\n"); exit(error_code); } int main(int argc, char *argv[]) { struct transformed transformed; struct display *d; int i; transformed.width = 500; transformed.height = 250; transformed.fullscreen = 0; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-w") == 0) { if (++i >= argc) usage(EXIT_FAILURE); transformed.width = atol(argv[i]); } else if (strcmp(argv[i], "-h") == 0) { if (++i >= argc) usage(EXIT_FAILURE); transformed.height = atol(argv[i]); } else if (strcmp(argv[i], "--help") == 0) usage(EXIT_SUCCESS); else usage(EXIT_FAILURE); } d = display_create(&argc, argv); if (d == NULL) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } transformed.display = d; transformed.window = window_create(d); transformed.widget = window_add_widget(transformed.window, &transformed); window_set_title(transformed.window, "Transformed"); widget_set_transparent(transformed.widget, 0); widget_set_default_cursor(transformed.widget, CURSOR_BLANK); widget_set_redraw_handler(transformed.widget, redraw_handler); widget_set_button_handler(transformed.widget, button_handler); widget_set_touch_down_handler(transformed.widget, touch_handler); window_set_key_handler(transformed.window, key_handler); window_set_fullscreen_handler(transformed.window, fullscreen_handler); window_set_output_handler(transformed.window, output_handler); window_set_user_data(transformed.window, &transformed); window_schedule_resize(transformed.window, transformed.width, transformed.height); display_run(d); widget_destroy(transformed.widget); window_destroy(transformed.window); display_destroy(d); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3322964 weston-8.0.0/clients/weston-debug.c0000644000175000017460000002601600000000000017534 0ustar00simonwheel00000000000000/* * Copyright © 2017 Pekka Paalanen * Copyright © 2018 Zodiac Inflight Innovations * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shared/helpers.h" #include #include "weston-debug-client-protocol.h" struct debug_app { struct { bool help; bool list; bool bind_all; char *output; char *outfd; } opt; int out_fd; struct wl_display *dpy; struct wl_registry *registry; struct weston_debug_v1 *debug_iface; struct wl_list stream_list; }; struct debug_stream { struct wl_list link; bool should_bind; char *name; char *desc; struct weston_debug_stream_v1 *obj; }; /** * Called either through stream_find in response to an advertisement * event (see comment on stream_find) when we have all the information, * or directly from option parsing to make a placeholder entry when the * stream was explicitly named on the command line to bind to. */ static struct debug_stream * stream_alloc(struct debug_app *app, const char *name, const char *desc) { struct debug_stream *stream; stream = zalloc(sizeof *stream); if (!stream) return NULL; stream->name = strdup(name); if (!stream->name) { free(stream); return NULL; } if (desc) { stream->desc = strdup(desc); if (!stream->desc) { free(stream->name); free(stream); return NULL; } } stream->should_bind = app->opt.bind_all; wl_list_insert(app->stream_list.prev, &stream->link); return stream; } /** * Called in response to a stream advertisement event. If our stream was * manually specified on the command line, then it will already have a * dummy entry in stream_list: we fill in its description and return. * If there's no entry in the list, we make a new one and return that. */ static struct debug_stream * stream_find(struct debug_app *app, const char *name, const char *desc) { struct debug_stream *stream; wl_list_for_each(stream, &app->stream_list, link) { if (strcmp(stream->name, name) == 0) { assert(stream->desc == NULL); if (desc) stream->desc = strdup(desc); return stream; } } return stream_alloc(app, name, desc); } static void stream_destroy(struct debug_stream *stream) { if (stream->obj) weston_debug_stream_v1_destroy(stream->obj); wl_list_remove(&stream->link); free(stream->name); free(stream); } static void destroy_streams(struct debug_app *app) { struct debug_stream *stream; struct debug_stream *tmp; wl_list_for_each_safe(stream, tmp, &app->stream_list, link) stream_destroy(stream); } static void debug_advertise(void *data, struct weston_debug_v1 *debug, const char *name, const char *desc) { struct debug_app *app = data; (void) stream_find(app, name, desc); } static const struct weston_debug_v1_listener debug_listener = { debug_advertise, }; static void global_handler(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct debug_app *app = data; uint32_t myver; assert(app->registry == registry); if (!strcmp(interface, weston_debug_v1_interface.name)) { if (app->debug_iface) return; myver = MIN(1, version); app->debug_iface = wl_registry_bind(registry, id, &weston_debug_v1_interface, myver); weston_debug_v1_add_listener(app->debug_iface, &debug_listener, app); } } static void global_remove_handler(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { global_handler, global_remove_handler }; static void handle_stream_complete(void *data, struct weston_debug_stream_v1 *obj) { struct debug_stream *stream = data; assert(stream->obj == obj); stream_destroy(stream); } static void handle_stream_failure(void *data, struct weston_debug_stream_v1 *obj, const char *msg) { struct debug_stream *stream = data; assert(stream->obj == obj); fprintf(stderr, "Debug stream '%s' aborted: %s\n", stream->name, msg); stream_destroy(stream); } static const struct weston_debug_stream_v1_listener stream_listener = { handle_stream_complete, handle_stream_failure }; static void start_streams(struct debug_app *app) { struct debug_stream *stream; wl_list_for_each(stream, &app->stream_list, link) { if (!stream->should_bind) continue; stream->obj = weston_debug_v1_subscribe(app->debug_iface, stream->name, app->out_fd); weston_debug_stream_v1_add_listener(stream->obj, &stream_listener, stream); } } static void list_streams(struct debug_app *app) { struct debug_stream *stream; fprintf(stderr, "Available debug streams:\n"); wl_list_for_each(stream, &app->stream_list, link) { if (stream->should_bind && stream->desc) { fprintf(stderr, " %s [will bind]\n", stream->name); fprintf(stderr, " %s\n", stream->desc); } else if (stream->should_bind) { fprintf(stderr, " %s [wanted but not found]\n", stream->name); } else { fprintf(stderr, " %s [will not bind]\n", stream->name); fprintf(stderr, " %s\n", stream->desc); } } } static int setup_out_fd(const char *output, const char *outfd) { int fd = -1; int flags; assert(!(output && outfd)); if (output) { if (strcmp(output, "-") == 0) { fd = STDOUT_FILENO; } else { fd = open(output, O_WRONLY | O_APPEND | O_CREAT, 0644); if (fd < 0) { fprintf(stderr, "Error: opening file '%s' failed: %s\n", output, strerror(errno)); } return fd; } } else if (outfd) { fd = atoi(outfd); } else { fd = STDOUT_FILENO; } flags = fcntl(fd, F_GETFL); if (flags == -1) { fprintf(stderr, "Error: cannot use file descriptor %d: %s\n", fd, strerror(errno)); return -1; } if ((flags & O_ACCMODE) != O_WRONLY && (flags & O_ACCMODE) != O_RDWR) { fprintf(stderr, "Error: file descriptor %d is not writable.\n", fd); return -1; } return fd; } static void print_help(void) { fprintf(stderr, "Usage: weston-debug [options] [names]\n" "Where options may be:\n" " -h, --help\n" " This help text, and exit with success.\n" " -l, --list\n" " Print a list of available debug streams to stderr.\n" " -a, --all-streams\n" " Bind to all available streams.\n" " -o FILE, --output FILE\n" " Direct output to file named FILE. Use - for stdout.\n" " Stdout is the default. Mutually exclusive with -f.\n" " -f FD, --outfd FD\n" " Direct output to the file descriptor FD.\n" " Stdout (1) is the default. Mutually exclusive with -o.\n" "Names are whatever debug stream names the compositor supports.\n" ); } static int parse_cmdline(struct debug_app *app, int argc, char **argv) { static const struct option opts[] = { { "help", no_argument, NULL, 'h' }, { "list", no_argument, NULL, 'l' }, { "all-streams", no_argument, NULL, 'a' }, { "output", required_argument, NULL, 'o' }, { "outfd", required_argument, NULL, 'f' }, { 0 } }; static const char optstr[] = "hlao:f:"; int c; bool failed = false; while (1) { c = getopt_long(argc, argv, optstr, opts, NULL); if (c == -1) break; switch (c) { case 'h': app->opt.help = true; break; case 'l': app->opt.list = true; break; case 'a': app->opt.bind_all = true; break; case 'o': free(app->opt.output); app->opt.output = strdup(optarg); break; case 'f': free(app->opt.outfd); app->opt.outfd = strdup(optarg); break; case '?': failed = true; break; default: fprintf(stderr, "huh? getopt => %c (%d)\n", c, c); failed = true; } } if (failed) return -1; while (optind < argc) { struct debug_stream *stream = stream_alloc(app, argv[optind++], NULL); stream->should_bind = true; } return 0; } int main(int argc, char **argv) { struct debug_app app = {}; int ret = 0; wl_list_init(&app.stream_list); app.out_fd = -1; if (parse_cmdline(&app, argc, argv) < 0) { ret = 1; goto out_parse; } if (app.opt.help) { print_help(); goto out_parse; } if (!app.opt.list && !app.opt.bind_all && wl_list_empty(&app.stream_list)) { fprintf(stderr, "Error: no options given.\n\n"); ret = 1; print_help(); goto out_parse; } if (app.opt.bind_all && !wl_list_empty(&app.stream_list)) { fprintf(stderr, "Error: --all and specific stream names cannot be used simultaneously.\n"); ret = 1; goto out_parse; } if (app.opt.output && app.opt.outfd) { fprintf(stderr, "Error: options --output and --outfd cannot be used simultaneously.\n"); ret = 1; goto out_parse; } app.out_fd = setup_out_fd(app.opt.output, app.opt.outfd); if (app.out_fd < 0) { ret = 1; goto out_parse; } app.dpy = wl_display_connect(NULL); if (!app.dpy) { fprintf(stderr, "Error: Could not connect to Wayland display: %s\n", strerror(errno)); ret = 1; goto out_parse; } app.registry = wl_display_get_registry(app.dpy); wl_registry_add_listener(app.registry, ®istry_listener, &app); wl_display_roundtrip(app.dpy); if (!app.debug_iface) { ret = 1; fprintf(stderr, "The Wayland server does not support %s interface.\n", weston_debug_v1_interface.name); goto out_conn; } wl_display_roundtrip(app.dpy); /* for weston_debug_v1::advertise */ if (app.opt.list) list_streams(&app); start_streams(&app); weston_debug_v1_destroy(app.debug_iface); while (1) { struct debug_stream *stream; bool empty = true; wl_list_for_each(stream, &app.stream_list, link) { if (stream->obj) { empty = false; break; } } if (empty) break; if (wl_display_dispatch(app.dpy) < 0) { ret = 1; break; } } out_conn: destroy_streams(&app); /* Wait for server to close all files */ wl_display_roundtrip(app.dpy); wl_registry_destroy(app.registry); wl_display_disconnect(app.dpy); out_parse: if (app.out_fd != -1) close(app.out_fd); destroy_streams(&app); free(app.opt.output); free(app.opt.outfd); return ret; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3322964 weston-8.0.0/clients/weston-info.c0000644000175000017460000013640000000000000017400 0ustar00simonwheel00000000000000/* * Copyright © 2012 Philipp Brüschweiler * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "shared/helpers.h" #include "shared/os-compatibility.h" #include "shared/xalloc.h" #include #include "presentation-time-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "tablet-unstable-v2-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" typedef void (*print_info_t)(void *info); typedef void (*destroy_info_t)(void *info); struct global_info { struct wl_list link; uint32_t id; uint32_t version; char *interface; print_info_t print; destroy_info_t destroy; }; struct output_mode { struct wl_list link; uint32_t flags; int32_t width, height; int32_t refresh; }; struct output_info { struct global_info global; struct wl_list global_link; struct wl_output *output; int32_t version; struct { int32_t x, y; int32_t scale; int32_t physical_width, physical_height; enum wl_output_subpixel subpixel; enum wl_output_transform output_transform; char *make; char *model; } geometry; struct wl_list modes; }; struct shm_format { struct wl_list link; uint32_t format; }; struct shm_info { struct global_info global; struct wl_shm *shm; struct wl_list formats; }; struct linux_dmabuf_modifier { struct wl_list link; uint32_t format; uint64_t modifier; }; struct linux_dmabuf_info { struct global_info global; struct zwp_linux_dmabuf_v1 *dmabuf; struct wl_list modifiers; }; struct seat_info { struct global_info global; struct wl_list global_link; struct wl_seat *seat; struct weston_info *info; struct wl_keyboard *keyboard; uint32_t capabilities; char *name; int32_t repeat_rate; int32_t repeat_delay; }; struct tablet_v2_path { struct wl_list link; char *path; }; struct tablet_tool_info { struct wl_list link; struct zwp_tablet_tool_v2 *tool; uint64_t hardware_serial; uint64_t hardware_id_wacom; enum zwp_tablet_tool_v2_type type; bool has_tilt; bool has_pressure; bool has_distance; bool has_rotation; bool has_slider; bool has_wheel; }; struct tablet_pad_group_info { struct wl_list link; struct zwp_tablet_pad_group_v2 *group; uint32_t modes; size_t button_count; int *buttons; size_t strips; size_t rings; }; struct tablet_pad_info { struct wl_list link; struct zwp_tablet_pad_v2 *pad; uint32_t buttons; struct wl_list paths; struct wl_list groups; }; struct tablet_info { struct wl_list link; struct zwp_tablet_v2 *tablet; char *name; uint32_t vid, pid; struct wl_list paths; }; struct tablet_seat_info { struct wl_list link; struct zwp_tablet_seat_v2 *seat; struct seat_info *seat_info; struct wl_list tablets; struct wl_list tools; struct wl_list pads; }; struct tablet_v2_info { struct global_info global; struct zwp_tablet_manager_v2 *manager; struct weston_info *info; struct wl_list seats; }; struct xdg_output_v1_info { struct wl_list link; struct zxdg_output_v1 *xdg_output; struct output_info *output; struct { int32_t x, y; int32_t width, height; } logical; char *name, *description; }; struct xdg_output_manager_v1_info { struct global_info global; struct zxdg_output_manager_v1 *manager; struct weston_info *info; struct wl_list outputs; }; struct presentation_info { struct global_info global; struct wp_presentation *presentation; clockid_t clk_id; }; struct weston_info { struct wl_display *display; struct wl_registry *registry; struct wl_list infos; bool roundtrip_needed; /* required for tablet-unstable-v2 */ struct wl_list seats; struct tablet_v2_info *tablet_info; /* required for xdg-output-unstable-v1 */ struct wl_list outputs; struct xdg_output_manager_v1_info *xdg_output_manager_v1_info; }; static void print_global_info(void *data) { struct global_info *global = data; printf("interface: '%s', version: %u, name: %u\n", global->interface, global->version, global->id); } static void init_global_info(struct weston_info *info, struct global_info *global, uint32_t id, const char *interface, uint32_t version) { global->id = id; global->version = version; global->interface = xstrdup(interface); wl_list_insert(info->infos.prev, &global->link); } static void print_output_info(void *data) { struct output_info *output = data; struct output_mode *mode; const char *subpixel_orientation; const char *transform; print_global_info(data); switch (output->geometry.subpixel) { case WL_OUTPUT_SUBPIXEL_UNKNOWN: subpixel_orientation = "unknown"; break; case WL_OUTPUT_SUBPIXEL_NONE: subpixel_orientation = "none"; break; case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: subpixel_orientation = "horizontal rgb"; break; case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: subpixel_orientation = "horizontal bgr"; break; case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: subpixel_orientation = "vertical rgb"; break; case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: subpixel_orientation = "vertical bgr"; break; default: fprintf(stderr, "unknown subpixel orientation %u\n", output->geometry.subpixel); subpixel_orientation = "unexpected value"; break; } switch (output->geometry.output_transform) { case WL_OUTPUT_TRANSFORM_NORMAL: transform = "normal"; break; case WL_OUTPUT_TRANSFORM_90: transform = "90°"; break; case WL_OUTPUT_TRANSFORM_180: transform = "180°"; break; case WL_OUTPUT_TRANSFORM_270: transform = "270°"; break; case WL_OUTPUT_TRANSFORM_FLIPPED: transform = "flipped"; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: transform = "flipped 90°"; break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: transform = "flipped 180°"; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: transform = "flipped 270°"; break; default: fprintf(stderr, "unknown output transform %u\n", output->geometry.output_transform); transform = "unexpected value"; break; } printf("\tx: %d, y: %d,", output->geometry.x, output->geometry.y); if (output->version >= 2) printf(" scale: %d,", output->geometry.scale); printf("\n"); printf("\tphysical_width: %d mm, physical_height: %d mm,\n", output->geometry.physical_width, output->geometry.physical_height); printf("\tmake: '%s', model: '%s',\n", output->geometry.make, output->geometry.model); printf("\tsubpixel_orientation: %s, output_transform: %s,\n", subpixel_orientation, transform); wl_list_for_each(mode, &output->modes, link) { printf("\tmode:\n"); printf("\t\twidth: %d px, height: %d px, refresh: %.3f Hz,\n", mode->width, mode->height, (float) mode->refresh / 1000); printf("\t\tflags:"); if (mode->flags & WL_OUTPUT_MODE_CURRENT) printf(" current"); if (mode->flags & WL_OUTPUT_MODE_PREFERRED) printf(" preferred"); printf("\n"); } } static char bits2graph(uint32_t value, unsigned bitoffset) { int c = (value >> bitoffset) & 0xff; if (isgraph(c) || isspace(c)) return c; return '?'; } static void fourcc2str(uint32_t format, char *str, int len) { int i; assert(len >= 5); for (i = 0; i < 4; i++) str[i] = bits2graph(format, i * 8); str[i] = '\0'; } static void print_shm_info(void *data) { char str[5]; struct shm_info *shm = data; struct shm_format *format; print_global_info(data); printf("\tformats:"); wl_list_for_each(format, &shm->formats, link) switch (format->format) { case WL_SHM_FORMAT_ARGB8888: printf(" ARGB8888"); break; case WL_SHM_FORMAT_XRGB8888: printf(" XRGB8888"); break; case WL_SHM_FORMAT_RGB565: printf(" RGB565"); break; default: fourcc2str(format->format, str, sizeof(str)); printf(" '%s'(0x%08x)", str, format->format); break; } printf("\n"); } static void print_linux_dmabuf_info(void *data) { char str[5]; struct linux_dmabuf_info *dmabuf = data; struct linux_dmabuf_modifier *modifier; print_global_info(data); printf("\tformats:"); wl_list_for_each(modifier, &dmabuf->modifiers, link) { fourcc2str(modifier->format, str, sizeof(str)); printf("\n\t'%s'(0x%08x), modifier: 0x%016"PRIx64, str, modifier->format, modifier->modifier); } printf("\n"); } static void print_seat_info(void *data) { struct seat_info *seat = data; print_global_info(data); printf("\tname: %s\n", seat->name); printf("\tcapabilities:"); if (seat->capabilities & WL_SEAT_CAPABILITY_POINTER) printf(" pointer"); if (seat->capabilities & WL_SEAT_CAPABILITY_KEYBOARD) printf(" keyboard"); if (seat->capabilities & WL_SEAT_CAPABILITY_TOUCH) printf(" touch"); printf("\n"); if (seat->repeat_rate > 0) printf("\tkeyboard repeat rate: %d\n", seat->repeat_rate); if (seat->repeat_delay > 0) printf("\tkeyboard repeat delay: %d\n", seat->repeat_delay); } static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { /* Just so we don’t leak the keymap fd */ close(fd); } static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { } static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { } static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { } static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { } static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay) { struct seat_info *seat = data; seat->repeat_rate = rate; seat->repeat_delay = delay; } static const struct wl_keyboard_listener keyboard_listener = { keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, keyboard_handle_repeat_info, }; static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum wl_seat_capability caps) { struct seat_info *seat = data; seat->capabilities = caps; /* we want listen for repeat_info from wl_keyboard, but only * do so if the seat info is >= 4 and if we actually have a * keyboard */ if (seat->global.version < 4) return; if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { seat->keyboard = wl_seat_get_keyboard(seat->seat); wl_keyboard_add_listener(seat->keyboard, &keyboard_listener, seat); seat->info->roundtrip_needed = true; } } static void seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name) { struct seat_info *seat = data; seat->name = xstrdup(name); } static const struct wl_seat_listener seat_listener = { seat_handle_capabilities, seat_handle_name, }; static void destroy_seat_info(void *data) { struct seat_info *seat = data; wl_seat_destroy(seat->seat); if (seat->name != NULL) free(seat->name); if (seat->keyboard) wl_keyboard_destroy(seat->keyboard); wl_list_remove(&seat->global_link); } static const char * tablet_tool_type_to_str(enum zwp_tablet_tool_v2_type type) { switch (type) { case ZWP_TABLET_TOOL_V2_TYPE_PEN: return "pen"; case ZWP_TABLET_TOOL_V2_TYPE_ERASER: return "eraser"; case ZWP_TABLET_TOOL_V2_TYPE_BRUSH: return "brush"; case ZWP_TABLET_TOOL_V2_TYPE_PENCIL: return "pencil"; case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH: return "airbrush"; case ZWP_TABLET_TOOL_V2_TYPE_FINGER: return "finger"; case ZWP_TABLET_TOOL_V2_TYPE_MOUSE: return "mouse"; case ZWP_TABLET_TOOL_V2_TYPE_LENS: return "lens"; } return "Unknown type"; } static void print_tablet_tool_info(const struct tablet_tool_info *info) { printf("\t\ttablet_tool: %s\n", tablet_tool_type_to_str(info->type)); if (info->hardware_serial) { printf("\t\t\thardware serial: %" PRIx64 "\n", info->hardware_serial); } if (info->hardware_id_wacom) { printf("\t\t\thardware wacom: %" PRIx64 "\n", info->hardware_id_wacom); } printf("\t\t\tcapabilities:"); if (info->has_tilt) { printf(" tilt"); } if (info->has_pressure) { printf(" pressure"); } if (info->has_distance) { printf(" distance"); } if (info->has_rotation) { printf(" rotation"); } if (info->has_slider) { printf(" slider"); } if (info->has_wheel) { printf(" wheel"); } printf("\n"); } static void destroy_tablet_tool_info(struct tablet_tool_info *info) { wl_list_remove(&info->link); zwp_tablet_tool_v2_destroy(info->tool); free(info); } static void print_tablet_pad_group_info(const struct tablet_pad_group_info *info) { size_t i; printf("\t\t\tgroup:\n"); printf("\t\t\t\tmodes: %u\n", info->modes); printf("\t\t\t\tstrips: %zu\n", info->strips); printf("\t\t\t\trings: %zu\n", info->rings); printf("\t\t\t\tbuttons:"); for (i = 0; i < info->button_count; ++i) { printf(" %d", info->buttons[i]); } printf("\n"); } static void destroy_tablet_pad_group_info(struct tablet_pad_group_info *info) { wl_list_remove(&info->link); zwp_tablet_pad_group_v2_destroy(info->group); if (info->buttons) { free(info->buttons); } free(info); } static void print_tablet_pad_info(const struct tablet_pad_info *info) { const struct tablet_v2_path *path; const struct tablet_pad_group_info *group; printf("\t\tpad:\n"); printf("\t\t\tbuttons: %u\n", info->buttons); wl_list_for_each(path, &info->paths, link) { printf("\t\t\tpath: %s\n", path->path); } wl_list_for_each(group, &info->groups, link) { print_tablet_pad_group_info(group); } } static void destroy_tablet_pad_info(struct tablet_pad_info *info) { struct tablet_v2_path *path; struct tablet_v2_path *tmp_path; struct tablet_pad_group_info *group; struct tablet_pad_group_info *tmp_group; wl_list_remove(&info->link); zwp_tablet_pad_v2_destroy(info->pad); wl_list_for_each_safe(path, tmp_path, &info->paths, link) { wl_list_remove(&path->link); free(path->path); free(path); } wl_list_for_each_safe(group, tmp_group, &info->groups, link) { destroy_tablet_pad_group_info(group); } free(info); } static void print_tablet_info(const struct tablet_info *info) { const struct tablet_v2_path *path; printf("\t\ttablet: %s\n", info->name); printf("\t\t\tvendor: %u\n", info->vid); printf("\t\t\tproduct: %u\n", info->pid); wl_list_for_each(path, &info->paths, link) { printf("\t\t\tpath: %s\n", path->path); } } static void destroy_tablet_info(struct tablet_info *info) { struct tablet_v2_path *path; struct tablet_v2_path *tmp; wl_list_remove(&info->link); zwp_tablet_v2_destroy(info->tablet); if (info->name) { free(info->name); } wl_list_for_each_safe(path, tmp, &info->paths, link) { wl_list_remove(&path->link); free(path->path); free(path); } free(info); } static void print_tablet_seat_info(const struct tablet_seat_info *info) { const struct tablet_info *tablet; const struct tablet_pad_info *pad; const struct tablet_tool_info *tool; printf("\ttablet_seat: %s\n", info->seat_info->name); wl_list_for_each(tablet, &info->tablets, link) { print_tablet_info(tablet); } wl_list_for_each(pad, &info->pads, link) { print_tablet_pad_info(pad); } wl_list_for_each(tool, &info->tools, link) { print_tablet_tool_info(tool); } } static void destroy_tablet_seat_info(struct tablet_seat_info *info) { struct tablet_info *tablet; struct tablet_info *tmp_tablet; struct tablet_pad_info *pad; struct tablet_pad_info *tmp_pad; struct tablet_tool_info *tool; struct tablet_tool_info *tmp_tool; wl_list_remove(&info->link); zwp_tablet_seat_v2_destroy(info->seat); wl_list_for_each_safe(tablet, tmp_tablet, &info->tablets, link) { destroy_tablet_info(tablet); } wl_list_for_each_safe(pad, tmp_pad, &info->pads, link) { destroy_tablet_pad_info(pad); } wl_list_for_each_safe(tool, tmp_tool, &info->tools, link) { destroy_tablet_tool_info(tool); } free(info); } static void print_tablet_v2_info(void *data) { struct tablet_v2_info *info = data; struct tablet_seat_info *seat; print_global_info(data); wl_list_for_each(seat, &info->seats, link) { /* Skip tablet_seats without a tablet, they are irrelevant */ if (wl_list_empty(&seat->pads) && wl_list_empty(&seat->tablets) && wl_list_empty(&seat->tools)) { continue; } print_tablet_seat_info(seat); } } static void destroy_tablet_v2_info(void *data) { struct tablet_v2_info *info = data; struct tablet_seat_info *seat; struct tablet_seat_info *tmp; zwp_tablet_manager_v2_destroy(info->manager); wl_list_for_each_safe(seat, tmp, &info->seats, link) { destroy_tablet_seat_info(seat); } } static void handle_tablet_v2_tablet_tool_done(void *data, struct zwp_tablet_tool_v2 *tool) { /* don't bother waiting for this; there's no good reason a * compositor will wait more than one roundtrip before sending * these initial events. */ } static void handle_tablet_v2_tablet_tool_removed(void *data, struct zwp_tablet_tool_v2 *tool) { /* don't bother waiting for this; we never make any request either way. */ } static void handle_tablet_v2_tablet_tool_type(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t tool_type) { struct tablet_tool_info *info = data; info->type = tool_type; } static void handle_tablet_v2_tablet_tool_hardware_serial(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial_hi, uint32_t serial_lo) { struct tablet_tool_info *info = data; info->hardware_serial = ((uint64_t) serial_hi) << 32 | (uint64_t) serial_lo; } static void handle_tablet_v2_tablet_tool_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t id_hi, uint32_t id_lo) { struct tablet_tool_info *info = data; info->hardware_id_wacom = ((uint64_t) id_hi) << 32 | (uint64_t) id_lo; } static void handle_tablet_v2_tablet_tool_capability(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t capability) { struct tablet_tool_info *info = data; enum zwp_tablet_tool_v2_capability cap = capability; switch(cap) { case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT: info->has_tilt = true; break; case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE: info->has_pressure = true; break; case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE: info->has_distance = true; break; case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION: info->has_rotation = true; break; case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER: info->has_slider = true; break; case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL: info->has_wheel = true; break; } } static void handle_tablet_v2_tablet_tool_proximity_in(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) { } static void handle_tablet_v2_tablet_tool_proximity_out(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) { } static void handle_tablet_v2_tablet_tool_down(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial) { } static void handle_tablet_v2_tablet_tool_up(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) { } static void handle_tablet_v2_tablet_tool_motion(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y) { } static void handle_tablet_v2_tablet_tool_pressure(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t pressure) { } static void handle_tablet_v2_tablet_tool_distance(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t distance) { } static void handle_tablet_v2_tablet_tool_tilt(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y) { } static void handle_tablet_v2_tablet_tool_rotation(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees) { } static void handle_tablet_v2_tablet_tool_slider(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, int32_t position) { } static void handle_tablet_v2_tablet_tool_wheel(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees, int32_t clicks) { } static void handle_tablet_v2_tablet_tool_button(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state) { } static void handle_tablet_v2_tablet_tool_frame(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t time) { } static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { .removed = handle_tablet_v2_tablet_tool_removed, .done = handle_tablet_v2_tablet_tool_done, .type = handle_tablet_v2_tablet_tool_type, .hardware_serial = handle_tablet_v2_tablet_tool_hardware_serial, .hardware_id_wacom = handle_tablet_v2_tablet_tool_hardware_id_wacom, .capability = handle_tablet_v2_tablet_tool_capability, .proximity_in = handle_tablet_v2_tablet_tool_proximity_in, .proximity_out = handle_tablet_v2_tablet_tool_proximity_out, .down = handle_tablet_v2_tablet_tool_down, .up = handle_tablet_v2_tablet_tool_up, .motion = handle_tablet_v2_tablet_tool_motion, .pressure = handle_tablet_v2_tablet_tool_pressure, .distance = handle_tablet_v2_tablet_tool_distance, .tilt = handle_tablet_v2_tablet_tool_tilt, .rotation = handle_tablet_v2_tablet_tool_rotation, .slider = handle_tablet_v2_tablet_tool_slider, .wheel = handle_tablet_v2_tablet_tool_wheel, .button = handle_tablet_v2_tablet_tool_button, .frame = handle_tablet_v2_tablet_tool_frame, }; static void add_tablet_v2_tablet_tool_info(void *data, struct zwp_tablet_seat_v2 *tablet_seat_v2, struct zwp_tablet_tool_v2 *tool) { struct tablet_seat_info *tablet_seat = data; struct tablet_tool_info *tool_info = xzalloc(sizeof *tool_info); tool_info->tool = tool; wl_list_insert(&tablet_seat->tools, &tool_info->link); zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, tool_info); } static void handle_tablet_v2_tablet_pad_group_mode_switch(void *data, struct zwp_tablet_pad_group_v2 *zwp_tablet_pad_group_v2, uint32_t time, uint32_t serial, uint32_t mode) { /* This shouldn't ever happen */ } static void handle_tablet_v2_tablet_pad_group_done(void *data, struct zwp_tablet_pad_group_v2 *group) { /* don't bother waiting for this; there's no good reason a * compositor will wait more than one roundtrip before sending * these initial events. */ } static void handle_tablet_v2_tablet_pad_group_modes(void *data, struct zwp_tablet_pad_group_v2 *group, uint32_t modes) { struct tablet_pad_group_info *info = data; info->modes = modes; } static void handle_tablet_v2_tablet_pad_group_buttons(void *data, struct zwp_tablet_pad_group_v2 *group, struct wl_array *buttons) { struct tablet_pad_group_info *info = data; info->button_count = buttons->size / sizeof(int); info->buttons = xzalloc(buttons->size); memcpy(info->buttons, buttons->data, buttons->size); } static void handle_tablet_v2_tablet_pad_group_ring(void *data, struct zwp_tablet_pad_group_v2 *group, struct zwp_tablet_pad_ring_v2 *ring) { struct tablet_pad_group_info *info = data; ++info->rings; zwp_tablet_pad_ring_v2_destroy(ring); } static void handle_tablet_v2_tablet_pad_group_strip(void *data, struct zwp_tablet_pad_group_v2 *group, struct zwp_tablet_pad_strip_v2 *strip) { struct tablet_pad_group_info *info = data; ++info->strips; zwp_tablet_pad_strip_v2_destroy(strip); } static const struct zwp_tablet_pad_group_v2_listener tablet_pad_group_listener = { .buttons = handle_tablet_v2_tablet_pad_group_buttons, .modes = handle_tablet_v2_tablet_pad_group_modes, .ring = handle_tablet_v2_tablet_pad_group_ring, .strip = handle_tablet_v2_tablet_pad_group_strip, .done = handle_tablet_v2_tablet_pad_group_done, .mode_switch = handle_tablet_v2_tablet_pad_group_mode_switch, }; static void handle_tablet_v2_tablet_pad_group(void *data, struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, struct zwp_tablet_pad_group_v2 *pad_group) { struct tablet_pad_info *pad_info = data; struct tablet_pad_group_info *group = xzalloc(sizeof *group); wl_list_insert(&pad_info->groups, &group->link); group->group = pad_group; zwp_tablet_pad_group_v2_add_listener(pad_group, &tablet_pad_group_listener, group); } static void handle_tablet_v2_tablet_pad_path(void *data, struct zwp_tablet_pad_v2 *pad, const char *path) { struct tablet_pad_info *pad_info = data; struct tablet_v2_path *path_elem = xzalloc(sizeof *path_elem); path_elem->path = xstrdup(path); wl_list_insert(&pad_info->paths, &path_elem->link); } static void handle_tablet_v2_tablet_pad_buttons(void *data, struct zwp_tablet_pad_v2 *pad, uint32_t buttons) { struct tablet_pad_info *pad_info = data; pad_info->buttons = buttons; } static void handle_tablet_v2_tablet_pad_done(void *data, struct zwp_tablet_pad_v2 *pad) { /* don't bother waiting for this; there's no good reason a * compositor will wait more than one roundtrip before sending * these initial events. */ } static void handle_tablet_v2_tablet_pad_removed(void *data, struct zwp_tablet_pad_v2 *pad) { /* don't bother waiting for this; We never make any request that's not * allowed to be issued either way. */ } static void handle_tablet_v2_tablet_pad_button(void *data, struct zwp_tablet_pad_v2 *pad, uint32_t time, uint32_t button, uint32_t state) { /* we don't have a surface, so this can't ever happen */ } static void handle_tablet_v2_tablet_pad_enter(void *data, struct zwp_tablet_pad_v2 *pad, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) { /* we don't have a surface, so this can't ever happen */ } static void handle_tablet_v2_tablet_pad_leave(void *data, struct zwp_tablet_pad_v2 *pad, uint32_t serial, struct wl_surface *surface) { /* we don't have a surface, so this can't ever happen */ } static const struct zwp_tablet_pad_v2_listener tablet_pad_listener = { .group = handle_tablet_v2_tablet_pad_group, .path = handle_tablet_v2_tablet_pad_path, .buttons = handle_tablet_v2_tablet_pad_buttons, .done = handle_tablet_v2_tablet_pad_done, .removed = handle_tablet_v2_tablet_pad_removed, .button = handle_tablet_v2_tablet_pad_button, .enter = handle_tablet_v2_tablet_pad_enter, .leave = handle_tablet_v2_tablet_pad_leave, }; static void add_tablet_v2_tablet_pad_info(void *data, struct zwp_tablet_seat_v2 *tablet_seat_v2, struct zwp_tablet_pad_v2 *pad) { struct tablet_seat_info *tablet_seat = data; struct tablet_pad_info *pad_info = xzalloc(sizeof *pad_info); wl_list_init(&pad_info->paths); wl_list_init(&pad_info->groups); pad_info->pad = pad; wl_list_insert(&tablet_seat->pads, &pad_info->link); zwp_tablet_pad_v2_add_listener(pad, &tablet_pad_listener, pad_info); } static void handle_tablet_v2_tablet_name(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, const char *name) { struct tablet_info *tablet_info = data; tablet_info->name = xstrdup(name); } static void handle_tablet_v2_tablet_path(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, const char *path) { struct tablet_info *tablet_info = data; struct tablet_v2_path *path_elem = xzalloc(sizeof *path_elem); path_elem->path = xstrdup(path); wl_list_insert(&tablet_info->paths, &path_elem->link); } static void handle_tablet_v2_tablet_id(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, uint32_t vid, uint32_t pid) { struct tablet_info *tablet_info = data; tablet_info->vid = vid; tablet_info->pid = pid; } static void handle_tablet_v2_tablet_done(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) { /* don't bother waiting for this; there's no good reason a * compositor will wait more than one roundtrip before sending * these initial events. */ } static void handle_tablet_v2_tablet_removed(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) { /* don't bother waiting for this; We never make any request that's not * allowed to be issued either way. */ } static const struct zwp_tablet_v2_listener tablet_listener = { .name = handle_tablet_v2_tablet_name, .id = handle_tablet_v2_tablet_id, .path = handle_tablet_v2_tablet_path, .done = handle_tablet_v2_tablet_done, .removed = handle_tablet_v2_tablet_removed }; static void add_tablet_v2_tablet_info(void *data, struct zwp_tablet_seat_v2 *tablet_seat_v2, struct zwp_tablet_v2 *tablet) { struct tablet_seat_info *tablet_seat = data; struct tablet_info *tablet_info = xzalloc(sizeof *tablet_info); wl_list_init(&tablet_info->paths); tablet_info->tablet = tablet; wl_list_insert(&tablet_seat->tablets, &tablet_info->link); zwp_tablet_v2_add_listener(tablet, &tablet_listener, tablet_info); } static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { .tablet_added = add_tablet_v2_tablet_info, .pad_added = add_tablet_v2_tablet_pad_info, .tool_added = add_tablet_v2_tablet_tool_info, }; static void add_tablet_seat_info(struct tablet_v2_info *tablet_info, struct seat_info *seat) { struct tablet_seat_info *tablet_seat = xzalloc(sizeof *tablet_seat); wl_list_insert(&tablet_info->seats, &tablet_seat->link); tablet_seat->seat = zwp_tablet_manager_v2_get_tablet_seat( tablet_info->manager, seat->seat); zwp_tablet_seat_v2_add_listener(tablet_seat->seat, &tablet_seat_listener, tablet_seat); wl_list_init(&tablet_seat->pads); wl_list_init(&tablet_seat->tablets); wl_list_init(&tablet_seat->tools); tablet_seat->seat_info = seat; tablet_info->info->roundtrip_needed = true; } static void add_tablet_v2_info(struct weston_info *info, uint32_t id, uint32_t version) { struct seat_info *seat; struct tablet_v2_info *tablet = xzalloc(sizeof *tablet); wl_list_init(&tablet->seats); tablet->info = info; init_global_info(info, &tablet->global, id, zwp_tablet_manager_v2_interface.name, version); tablet->global.print = print_tablet_v2_info; tablet->global.destroy = destroy_tablet_v2_info; tablet->manager = wl_registry_bind(info->registry, id, &zwp_tablet_manager_v2_interface, 1); wl_list_for_each(seat, &info->seats, global_link) { add_tablet_seat_info(tablet, seat); } info->tablet_info = tablet; } static void destroy_xdg_output_v1_info(struct xdg_output_v1_info *info) { wl_list_remove(&info->link); zxdg_output_v1_destroy(info->xdg_output); free(info->name); free(info->description); free(info); } static void print_xdg_output_v1_info(const struct xdg_output_v1_info *info) { printf("\txdg_output_v1\n"); printf("\t\toutput: %d\n", info->output->global.id); if (info->name) printf("\t\tname: '%s'\n", info->name); if (info->description) printf("\t\tdescription: '%s'\n", info->description); printf("\t\tlogical_x: %d, logical_y: %d\n", info->logical.x, info->logical.y); printf("\t\tlogical_width: %d, logical_height: %d\n", info->logical.width, info->logical.height); } static void print_xdg_output_manager_v1_info(void *data) { struct xdg_output_manager_v1_info *info = data; struct xdg_output_v1_info *output; print_global_info(data); wl_list_for_each(output, &info->outputs, link) print_xdg_output_v1_info(output); } static void destroy_xdg_output_manager_v1_info(void *data) { struct xdg_output_manager_v1_info *info = data; struct xdg_output_v1_info *output, *tmp; zxdg_output_manager_v1_destroy(info->manager); wl_list_for_each_safe(output, tmp, &info->outputs, link) destroy_xdg_output_v1_info(output); } static void handle_xdg_output_v1_logical_position(void *data, struct zxdg_output_v1 *output, int32_t x, int32_t y) { struct xdg_output_v1_info *xdg_output = data; xdg_output->logical.x = x; xdg_output->logical.y = y; } static void handle_xdg_output_v1_logical_size(void *data, struct zxdg_output_v1 *output, int32_t width, int32_t height) { struct xdg_output_v1_info *xdg_output = data; xdg_output->logical.width = width; xdg_output->logical.height = height; } static void handle_xdg_output_v1_done(void *data, struct zxdg_output_v1 *output) { /* Don't bother waiting for this; there's no good reason a * compositor will wait more than one roundtrip before sending * these initial events. */ } static void handle_xdg_output_v1_name(void *data, struct zxdg_output_v1 *output, const char *name) { struct xdg_output_v1_info *xdg_output = data; xdg_output->name = strdup(name); } static void handle_xdg_output_v1_description(void *data, struct zxdg_output_v1 *output, const char *description) { struct xdg_output_v1_info *xdg_output = data; xdg_output->description = strdup(description); } static const struct zxdg_output_v1_listener xdg_output_v1_listener = { .logical_position = handle_xdg_output_v1_logical_position, .logical_size = handle_xdg_output_v1_logical_size, .done = handle_xdg_output_v1_done, .name = handle_xdg_output_v1_name, .description = handle_xdg_output_v1_description, }; static void add_xdg_output_v1_info(struct xdg_output_manager_v1_info *manager_info, struct output_info *output) { struct xdg_output_v1_info *xdg_output = xzalloc(sizeof *xdg_output); wl_list_insert(&manager_info->outputs, &xdg_output->link); xdg_output->xdg_output = zxdg_output_manager_v1_get_xdg_output( manager_info->manager, output->output); zxdg_output_v1_add_listener(xdg_output->xdg_output, &xdg_output_v1_listener, xdg_output); xdg_output->output = output; manager_info->info->roundtrip_needed = true; } static void add_xdg_output_manager_v1_info(struct weston_info *info, uint32_t id, uint32_t version) { struct output_info *output; struct xdg_output_manager_v1_info *manager = xzalloc(sizeof *manager); wl_list_init(&manager->outputs); manager->info = info; init_global_info(info, &manager->global, id, zxdg_output_manager_v1_interface.name, version); manager->global.print = print_xdg_output_manager_v1_info; manager->global.destroy = destroy_xdg_output_manager_v1_info; manager->manager = wl_registry_bind(info->registry, id, &zxdg_output_manager_v1_interface, version > 2 ? 2 : version); wl_list_for_each(output, &info->outputs, global_link) add_xdg_output_v1_info(manager, output); info->xdg_output_manager_v1_info = manager; } static void add_seat_info(struct weston_info *info, uint32_t id, uint32_t version) { struct seat_info *seat = xzalloc(sizeof *seat); /* required to set roundtrip_needed to true in capabilities * handler */ seat->info = info; init_global_info(info, &seat->global, id, "wl_seat", version); seat->global.print = print_seat_info; seat->global.destroy = destroy_seat_info; seat->seat = wl_registry_bind(info->registry, id, &wl_seat_interface, MIN(version, 4)); wl_seat_add_listener(seat->seat, &seat_listener, seat); seat->repeat_rate = seat->repeat_delay = -1; info->roundtrip_needed = true; wl_list_insert(&info->seats, &seat->global_link); if (info->tablet_info) { add_tablet_seat_info(info->tablet_info, seat); } } static void shm_handle_format(void *data, struct wl_shm *wl_shm, uint32_t format) { struct shm_info *shm = data; struct shm_format *shm_format = xzalloc(sizeof *shm_format); wl_list_insert(&shm->formats, &shm_format->link); shm_format->format = format; } static const struct wl_shm_listener shm_listener = { shm_handle_format, }; static void destroy_shm_info(void *data) { struct shm_info *shm = data; struct shm_format *format, *tmp; wl_list_for_each_safe(format, tmp, &shm->formats, link) { wl_list_remove(&format->link); free(format); } wl_shm_destroy(shm->shm); } static void add_shm_info(struct weston_info *info, uint32_t id, uint32_t version) { struct shm_info *shm = xzalloc(sizeof *shm); init_global_info(info, &shm->global, id, "wl_shm", version); shm->global.print = print_shm_info; shm->global.destroy = destroy_shm_info; wl_list_init(&shm->formats); shm->shm = wl_registry_bind(info->registry, id, &wl_shm_interface, 1); wl_shm_add_listener(shm->shm, &shm_listener, shm); info->roundtrip_needed = true; } static void linux_dmabuf_handle_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format) { /* This is a deprecated event, don’t use it. */ } static void linux_dmabuf_handle_modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { struct linux_dmabuf_info *dmabuf = data; struct linux_dmabuf_modifier *linux_dmabuf_modifier = xzalloc(sizeof *linux_dmabuf_modifier); wl_list_insert(&dmabuf->modifiers, &linux_dmabuf_modifier->link); linux_dmabuf_modifier->format = format; linux_dmabuf_modifier->modifier = ((uint64_t)modifier_hi) << 32 | modifier_lo; } static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_listener = { linux_dmabuf_handle_format, linux_dmabuf_handle_modifier, }; static void destroy_linux_dmabuf_info(void *data) { struct linux_dmabuf_info *dmabuf = data; struct linux_dmabuf_modifier *modifier, *tmp; wl_list_for_each_safe(modifier, tmp, &dmabuf->modifiers, link) { wl_list_remove(&modifier->link); free(modifier); } zwp_linux_dmabuf_v1_destroy(dmabuf->dmabuf); } static void add_linux_dmabuf_info(struct weston_info *info, uint32_t id, uint32_t version) { struct linux_dmabuf_info *dmabuf = xzalloc(sizeof *dmabuf); init_global_info(info, &dmabuf->global, id, "zwp_linux_dmabuf_v1", version); dmabuf->global.print = print_linux_dmabuf_info; dmabuf->global.destroy = destroy_linux_dmabuf_info; wl_list_init(&dmabuf->modifiers); if (version >= 3) { dmabuf->dmabuf = wl_registry_bind(info->registry, id, &zwp_linux_dmabuf_v1_interface, 3); zwp_linux_dmabuf_v1_add_listener(dmabuf->dmabuf, &linux_dmabuf_listener, dmabuf); info->roundtrip_needed = true; } } static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t output_transform) { struct output_info *output = data; output->geometry.x = x; output->geometry.y = y; output->geometry.physical_width = physical_width; output->geometry.physical_height = physical_height; output->geometry.subpixel = subpixel; output->geometry.make = xstrdup(make); output->geometry.model = xstrdup(model); output->geometry.output_transform = output_transform; } static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { struct output_info *output = data; struct output_mode *mode = xmalloc(sizeof *mode); mode->flags = flags; mode->width = width; mode->height = height; mode->refresh = refresh; wl_list_insert(output->modes.prev, &mode->link); } static void output_handle_done(void *data, struct wl_output *wl_output) { /* don't bother waiting for this; there's no good reason a * compositor will wait more than one roundtrip before sending * these initial events. */ } static void output_handle_scale(void *data, struct wl_output *wl_output, int32_t scale) { struct output_info *output = data; output->geometry.scale = scale; } static const struct wl_output_listener output_listener = { output_handle_geometry, output_handle_mode, output_handle_done, output_handle_scale, }; static void destroy_output_info(void *data) { struct output_info *output = data; struct output_mode *mode, *tmp; wl_output_destroy(output->output); if (output->geometry.make != NULL) free(output->geometry.make); if (output->geometry.model != NULL) free(output->geometry.model); wl_list_for_each_safe(mode, tmp, &output->modes, link) { wl_list_remove(&mode->link); free(mode); } } static void add_output_info(struct weston_info *info, uint32_t id, uint32_t version) { struct output_info *output = xzalloc(sizeof *output); init_global_info(info, &output->global, id, "wl_output", version); output->global.print = print_output_info; output->global.destroy = destroy_output_info; output->version = MIN(version, 2); output->geometry.scale = 1; wl_list_init(&output->modes); output->output = wl_registry_bind(info->registry, id, &wl_output_interface, output->version); wl_output_add_listener(output->output, &output_listener, output); info->roundtrip_needed = true; wl_list_insert(&info->outputs, &output->global_link); if (info->xdg_output_manager_v1_info) add_xdg_output_v1_info(info->xdg_output_manager_v1_info, output); } static void destroy_presentation_info(void *info) { struct presentation_info *prinfo = info; wp_presentation_destroy(prinfo->presentation); } static const char * clock_name(clockid_t clk_id) { static const char *names[] = { [CLOCK_REALTIME] = "CLOCK_REALTIME", [CLOCK_MONOTONIC] = "CLOCK_MONOTONIC", [CLOCK_MONOTONIC_RAW] = "CLOCK_MONOTONIC_RAW", [CLOCK_REALTIME_COARSE] = "CLOCK_REALTIME_COARSE", [CLOCK_MONOTONIC_COARSE] = "CLOCK_MONOTONIC_COARSE", #ifdef CLOCK_BOOTTIME [CLOCK_BOOTTIME] = "CLOCK_BOOTTIME", #endif }; if (clk_id < 0 || (unsigned)clk_id >= ARRAY_LENGTH(names)) return "unknown"; return names[clk_id]; } static void print_presentation_info(void *info) { struct presentation_info *prinfo = info; print_global_info(info); printf("\tpresentation clock id: %d (%s)\n", prinfo->clk_id, clock_name(prinfo->clk_id)); } static void presentation_handle_clock_id(void *data, struct wp_presentation *presentation, uint32_t clk_id) { struct presentation_info *prinfo = data; prinfo->clk_id = clk_id; } static const struct wp_presentation_listener presentation_listener = { presentation_handle_clock_id }; static void add_presentation_info(struct weston_info *info, uint32_t id, uint32_t version) { struct presentation_info *prinfo = xzalloc(sizeof *prinfo); init_global_info(info, &prinfo->global, id, wp_presentation_interface.name, version); prinfo->global.print = print_presentation_info; prinfo->global.destroy = destroy_presentation_info; prinfo->clk_id = -1; prinfo->presentation = wl_registry_bind(info->registry, id, &wp_presentation_interface, 1); wp_presentation_add_listener(prinfo->presentation, &presentation_listener, prinfo); info->roundtrip_needed = true; } static void destroy_global_info(void *data) { } static void add_global_info(struct weston_info *info, uint32_t id, const char *interface, uint32_t version) { struct global_info *global = xzalloc(sizeof *global); init_global_info(info, global, id, interface, version); global->print = print_global_info; global->destroy = destroy_global_info; } static void global_handler(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct weston_info *info = data; if (!strcmp(interface, "wl_seat")) add_seat_info(info, id, version); else if (!strcmp(interface, "wl_shm")) add_shm_info(info, id, version); else if (!strcmp(interface, "zwp_linux_dmabuf_v1")) add_linux_dmabuf_info(info, id, version); else if (!strcmp(interface, "wl_output")) add_output_info(info, id, version); else if (!strcmp(interface, wp_presentation_interface.name)) add_presentation_info(info, id, version); else if (!strcmp(interface, zwp_tablet_manager_v2_interface.name)) add_tablet_v2_info(info, id, version); else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)) add_xdg_output_manager_v1_info(info, id, version); else add_global_info(info, id, interface, version); } static void global_remove_handler(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { global_handler, global_remove_handler }; static void print_infos(struct wl_list *infos) { struct global_info *info; wl_list_for_each(info, infos, link) info->print(info); } static void destroy_info(void *data) { struct global_info *global = data; global->destroy(data); wl_list_remove(&global->link); free(global->interface); free(data); } static void destroy_infos(struct wl_list *infos) { struct global_info *info, *tmp; wl_list_for_each_safe(info, tmp, infos, link) destroy_info(info); } int main(int argc, char **argv) { struct weston_info info; info.display = wl_display_connect(NULL); if (!info.display) { fprintf(stderr, "failed to create display: %s\n", strerror(errno)); return -1; } info.tablet_info = NULL; info.xdg_output_manager_v1_info = NULL; wl_list_init(&info.infos); wl_list_init(&info.seats); wl_list_init(&info.outputs); info.registry = wl_display_get_registry(info.display); wl_registry_add_listener(info.registry, ®istry_listener, &info); do { info.roundtrip_needed = false; wl_display_roundtrip(info.display); } while (info.roundtrip_needed); print_infos(&info.infos); destroy_infos(&info.infos); wl_registry_destroy(info.registry); wl_display_disconnect(info.display); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3356297 weston-8.0.0/clients/window.c0000644000175000017460000046505700000000000016454 0ustar00simonwheel00000000000000/* * Copyright © 2008 Kristian Høgsberg * Copyright © 2012-2013 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CAIRO_EGL #include #ifdef USE_CAIRO_GLESV2 #include #include #else #include #endif #include #include #include #elif !defined(ENABLE_EGL) /* platform.h defines these if EGL is enabled */ typedef void *EGLDisplay; typedef void *EGLConfig; typedef void *EGLContext; #define EGL_NO_DISPLAY ((EGLDisplay)0) #endif /* no HAVE_CAIRO_EGL */ #include #ifdef HAVE_XKBCOMMON_COMPOSE #include #endif #include #include #include #include "shared/cairo-util.h" #include "shared/helpers.h" #include "shared/xalloc.h" #include #include "xdg-shell-client-protocol.h" #include "text-cursor-position-client-protocol.h" #include "pointer-constraints-unstable-v1-client-protocol.h" #include "relative-pointer-unstable-v1-client-protocol.h" #include "shared/os-compatibility.h" #include "shared/string-helpers.h" #include "window.h" #include "viewporter-client-protocol.h" #define ZWP_RELATIVE_POINTER_MANAGER_V1_VERSION 1 #define ZWP_POINTER_CONSTRAINTS_V1_VERSION 1 #define DEFAULT_XCURSOR_SIZE 32 struct shm_pool; struct global { uint32_t name; char *interface; uint32_t version; struct wl_list link; }; struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct wl_subcompositor *subcompositor; struct wl_shm *shm; struct wl_data_device_manager *data_device_manager; struct text_cursor_position *text_cursor_position; struct xdg_wm_base *xdg_shell; struct zwp_relative_pointer_manager_v1 *relative_pointer_manager; struct zwp_pointer_constraints_v1 *pointer_constraints; EGLDisplay dpy; EGLConfig argb_config; EGLContext argb_ctx; cairo_device_t *argb_device; uint32_t serial; int display_fd; uint32_t display_fd_events; struct task display_task; int epoll_fd; struct wl_list deferred_list; int running; struct wl_list global_list; struct wl_list window_list; struct wl_list input_list; struct wl_list output_list; struct theme *theme; struct wl_cursor_theme *cursor_theme; struct wl_cursor **cursors; display_output_handler_t output_configure_handler; display_global_handler_t global_handler; display_global_handler_t global_handler_remove; void *user_data; struct xkb_context *xkb_context; /* A hack to get text extents for tooltips */ cairo_surface_t *dummy_surface; void *dummy_surface_data; int data_device_manager_version; struct wp_viewporter *viewporter; }; struct window_output { struct output *output; struct wl_list link; }; struct toysurface { /* * Prepare the surface for drawing. Ensure there is a surface * of the right size available for rendering, and return it. * dx,dy are the x,y of wl_surface.attach. * width,height are the new buffer size. * If flags has SURFACE_HINT_RESIZE set, the user is * doing continuous resizing. * Returns the Cairo surface to draw to. */ cairo_surface_t *(*prepare)(struct toysurface *base, int dx, int dy, int32_t width, int32_t height, uint32_t flags, enum wl_output_transform buffer_transform, int32_t buffer_scale); /* * Post the surface to the server, returning the server allocation * rectangle. The Cairo surface from prepare() must be destroyed * after calling this. */ void (*swap)(struct toysurface *base, enum wl_output_transform buffer_transform, int32_t buffer_scale, struct rectangle *server_allocation); /* * Make the toysurface current with the given EGL context. * Returns 0 on success, and negative on failure. */ int (*acquire)(struct toysurface *base, EGLContext ctx); /* * Release the toysurface from the EGL context, returning control * to Cairo. */ void (*release)(struct toysurface *base); /* * Destroy the toysurface, including the Cairo surface, any * backing storage, and the Wayland protocol objects. */ void (*destroy)(struct toysurface *base); }; struct surface { struct window *window; struct wl_surface *surface; struct wl_subsurface *subsurface; int synchronized; int synchronized_default; struct toysurface *toysurface; struct widget *widget; int redraw_needed; struct wl_callback *frame_cb; uint32_t last_time; struct rectangle allocation; struct rectangle server_allocation; struct wl_region *input_region; struct wl_region *opaque_region; enum window_buffer_type buffer_type; enum wl_output_transform buffer_transform; int32_t buffer_scale; cairo_surface_t *cairo_surface; struct wl_list link; struct wp_viewport *viewport; }; struct window { struct display *display; struct wl_list window_output_list; char *title; struct rectangle saved_allocation; struct rectangle min_allocation; struct rectangle pending_allocation; struct rectangle last_geometry; int x, y; int redraw_inhibited; int redraw_needed; int redraw_task_scheduled; struct task redraw_task; int resize_needed; int custom; int focused; int resizing; int fullscreen; int maximized; window_key_handler_t key_handler; window_keyboard_focus_handler_t keyboard_focus_handler; window_data_handler_t data_handler; window_drop_handler_t drop_handler; window_close_handler_t close_handler; window_fullscreen_handler_t fullscreen_handler; window_output_handler_t output_handler; window_state_changed_handler_t state_changed_handler; window_locked_pointer_motion_handler_t locked_pointer_motion_handler; struct surface *main_surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct xdg_popup *xdg_popup; struct window *parent; struct window *last_parent; struct window_frame *frame; /* struct surface::link, contains also main_surface */ struct wl_list subsurface_list; struct zwp_relative_pointer_v1 *relative_pointer; struct zwp_locked_pointer_v1 *locked_pointer; bool pointer_locked; locked_pointer_locked_handler_t pointer_locked_handler; locked_pointer_unlocked_handler_t pointer_unlocked_handler; confined_pointer_confined_handler_t pointer_confined_handler; confined_pointer_unconfined_handler_t pointer_unconfined_handler; struct zwp_confined_pointer_v1 *confined_pointer; struct widget *confined_widget; bool confined; void *user_data; struct wl_list link; }; struct widget { struct window *window; struct surface *surface; struct tooltip *tooltip; struct wl_list child_list; struct wl_list link; struct rectangle allocation; widget_resize_handler_t resize_handler; widget_redraw_handler_t redraw_handler; widget_enter_handler_t enter_handler; widget_leave_handler_t leave_handler; widget_motion_handler_t motion_handler; widget_button_handler_t button_handler; widget_touch_down_handler_t touch_down_handler; widget_touch_up_handler_t touch_up_handler; widget_touch_motion_handler_t touch_motion_handler; widget_touch_frame_handler_t touch_frame_handler; widget_touch_cancel_handler_t touch_cancel_handler; widget_axis_handler_t axis_handler; widget_pointer_frame_handler_t pointer_frame_handler; widget_axis_source_handler_t axis_source_handler; widget_axis_stop_handler_t axis_stop_handler; widget_axis_discrete_handler_t axis_discrete_handler; void *user_data; int opaque; int tooltip_count; int default_cursor; /* If this is set to false then no cairo surface will be * created before redrawing the surface. This is useful if the * redraw handler is going to do completely custom rendering * such as using EGL directly */ int use_cairo; int viewport_dest_width; int viewport_dest_height; }; struct touch_point { int32_t id; float x, y; struct widget *widget; struct wl_list link; }; struct input { struct display *display; struct wl_seat *seat; struct wl_pointer *pointer; struct wl_keyboard *keyboard; struct wl_touch *touch; struct wl_list touch_point_list; struct window *pointer_focus; struct window *keyboard_focus; struct window *touch_focus; struct window *locked_window; struct window *confined_window; int current_cursor; uint32_t cursor_anim_start; struct wl_callback *cursor_frame_cb; uint32_t cursor_timer_start; uint32_t cursor_anim_current; struct toytimer cursor_timer; bool cursor_timer_running; struct wl_surface *pointer_surface; uint32_t modifiers; uint32_t pointer_enter_serial; uint32_t cursor_serial; float sx, sy; struct wl_list link; struct widget *focus_widget; struct widget *grab; uint32_t grab_button; struct wl_data_device *data_device; struct data_offer *drag_offer; struct data_offer *selection_offer; uint32_t touch_grab; int32_t touch_grab_id; float drag_x, drag_y; struct window *drag_focus; uint32_t drag_enter_serial; struct { struct xkb_keymap *keymap; struct xkb_state *state; #ifdef HAVE_XKBCOMMON_COMPOSE struct xkb_compose_table *compose_table; struct xkb_compose_state *compose_state; #endif xkb_mod_mask_t control_mask; xkb_mod_mask_t alt_mask; xkb_mod_mask_t shift_mask; } xkb; int32_t repeat_rate_sec; int32_t repeat_rate_nsec; int32_t repeat_delay_sec; int32_t repeat_delay_nsec; struct toytimer repeat_timer; uint32_t repeat_sym; uint32_t repeat_key; uint32_t repeat_time; int seat_version; }; struct output { struct display *display; struct wl_output *output; uint32_t server_output_id; struct rectangle allocation; struct wl_list link; int transform; int scale; char *make; char *model; display_output_handler_t destroy_handler; void *user_data; }; struct window_frame { struct widget *widget; struct widget *child; struct frame *frame; uint32_t last_time; uint32_t did_double, double_click; int32_t last_id, double_id; }; struct menu { void *user_data; struct window *window; struct widget *widget; struct input *input; struct frame *frame; const char **entries; uint32_t time; int current; int count; int release_count; menu_func_t func; }; struct tooltip { struct widget *parent; struct widget *widget; char *entry; struct toytimer timer; float x, y; }; struct shm_pool { struct wl_shm_pool *pool; size_t size; size_t used; void *data; }; enum { CURSOR_DEFAULT = 100, CURSOR_UNSET }; static const cairo_user_data_key_t shm_surface_data_key; /* #define DEBUG */ #ifdef DEBUG static void debug_print(void *proxy, int line, const char *func, const char *fmt, ...) __attribute__ ((format (printf, 4, 5))); static void debug_print(void *proxy, int line, const char *func, const char *fmt, ...) { va_list ap; struct timeval tv; gettimeofday(&tv, NULL); fprintf(stderr, "%8ld.%03ld ", (long)tv.tv_sec & 0xffff, (long)tv.tv_usec / 1000); if (proxy) fprintf(stderr, "%s@%d ", wl_proxy_get_class(proxy), wl_proxy_get_id(proxy)); /*fprintf(stderr, __FILE__ ":%d:%s ", line, func);*/ fprintf(stderr, "%s ", func); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } #define DBG(fmt, ...) \ debug_print(NULL, __LINE__, __func__, fmt, ##__VA_ARGS__) #define DBG_OBJ(obj, fmt, ...) \ debug_print(obj, __LINE__, __func__, fmt, ##__VA_ARGS__) #else #define DBG(...) do {} while (0) #define DBG_OBJ(...) do {} while (0) #endif static void surface_to_buffer_size (enum wl_output_transform buffer_transform, int32_t buffer_scale, int32_t *width, int32_t *height) { int32_t tmp; switch (buffer_transform) { case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_270: tmp = *width; *width = *height; *height = tmp; break; default: break; } *width *= buffer_scale; *height *= buffer_scale; } static void buffer_to_surface_size (enum wl_output_transform buffer_transform, int32_t buffer_scale, int32_t *width, int32_t *height) { int32_t tmp; switch (buffer_transform) { case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_270: tmp = *width; *width = *height; *height = tmp; break; default: break; } *width /= buffer_scale; *height /= buffer_scale; } #ifdef HAVE_CAIRO_EGL struct egl_window_surface { struct toysurface base; cairo_surface_t *cairo_surface; struct display *display; struct wl_surface *surface; struct wl_egl_window *egl_window; EGLSurface egl_surface; }; static struct egl_window_surface * to_egl_window_surface(struct toysurface *base) { return container_of(base, struct egl_window_surface, base); } static cairo_surface_t * egl_window_surface_prepare(struct toysurface *base, int dx, int dy, int32_t width, int32_t height, uint32_t flags, enum wl_output_transform buffer_transform, int32_t buffer_scale) { struct egl_window_surface *surface = to_egl_window_surface(base); surface_to_buffer_size (buffer_transform, buffer_scale, &width, &height); wl_egl_window_resize(surface->egl_window, width, height, dx, dy); cairo_gl_surface_set_size(surface->cairo_surface, width, height); return cairo_surface_reference(surface->cairo_surface); } static void egl_window_surface_swap(struct toysurface *base, enum wl_output_transform buffer_transform, int32_t buffer_scale, struct rectangle *server_allocation) { struct egl_window_surface *surface = to_egl_window_surface(base); cairo_gl_surface_swapbuffers(surface->cairo_surface); wl_egl_window_get_attached_size(surface->egl_window, &server_allocation->width, &server_allocation->height); buffer_to_surface_size (buffer_transform, buffer_scale, &server_allocation->width, &server_allocation->height); } static int egl_window_surface_acquire(struct toysurface *base, EGLContext ctx) { struct egl_window_surface *surface = to_egl_window_surface(base); cairo_device_t *device; device = cairo_surface_get_device(surface->cairo_surface); if (!device) return -1; if (!ctx) { if (device == surface->display->argb_device) ctx = surface->display->argb_ctx; else assert(0); } cairo_device_flush(device); cairo_device_acquire(device); if (!eglMakeCurrent(surface->display->dpy, surface->egl_surface, surface->egl_surface, ctx)) fprintf(stderr, "failed to make surface current\n"); return 0; } static void egl_window_surface_release(struct toysurface *base) { struct egl_window_surface *surface = to_egl_window_surface(base); cairo_device_t *device; device = cairo_surface_get_device(surface->cairo_surface); if (!device) return; if (!eglMakeCurrent(surface->display->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) fprintf(stderr, "failed to make context current\n"); cairo_device_release(device); } static void egl_window_surface_destroy(struct toysurface *base) { struct egl_window_surface *surface = to_egl_window_surface(base); struct display *d = surface->display; cairo_surface_destroy(surface->cairo_surface); weston_platform_destroy_egl_surface(d->dpy, surface->egl_surface); wl_egl_window_destroy(surface->egl_window); surface->surface = NULL; free(surface); } static struct toysurface * egl_window_surface_create(struct display *display, struct wl_surface *wl_surface, uint32_t flags, struct rectangle *rectangle) { struct egl_window_surface *surface; if (display->dpy == EGL_NO_DISPLAY) return NULL; surface = zalloc(sizeof *surface); if (!surface) return NULL; surface->base.prepare = egl_window_surface_prepare; surface->base.swap = egl_window_surface_swap; surface->base.acquire = egl_window_surface_acquire; surface->base.release = egl_window_surface_release; surface->base.destroy = egl_window_surface_destroy; surface->display = display; surface->surface = wl_surface; surface->egl_window = wl_egl_window_create(surface->surface, rectangle->width, rectangle->height); surface->egl_surface = weston_platform_create_egl_surface(display->dpy, display->argb_config, surface->egl_window, NULL); surface->cairo_surface = cairo_gl_surface_create_for_egl(display->argb_device, surface->egl_surface, rectangle->width, rectangle->height); return &surface->base; } #else static struct toysurface * egl_window_surface_create(struct display *display, struct wl_surface *wl_surface, uint32_t flags, struct rectangle *rectangle) { return NULL; } #endif struct shm_surface_data { struct wl_buffer *buffer; struct shm_pool *pool; }; struct wl_buffer * display_get_buffer_for_surface(struct display *display, cairo_surface_t *surface) { struct shm_surface_data *data; data = cairo_surface_get_user_data(surface, &shm_surface_data_key); return data->buffer; } static void shm_pool_destroy(struct shm_pool *pool); static void shm_surface_data_destroy(void *p) { struct shm_surface_data *data = p; wl_buffer_destroy(data->buffer); if (data->pool) shm_pool_destroy(data->pool); free(data); } static struct wl_shm_pool * make_shm_pool(struct display *display, int size, void **data) { struct wl_shm_pool *pool; int fd; fd = os_create_anonymous_file(size); if (fd < 0) { fprintf(stderr, "creating a buffer file for %d B failed: %s\n", size, strerror(errno)); return NULL; } *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (*data == MAP_FAILED) { fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return NULL; } pool = wl_shm_create_pool(display->shm, fd, size); close(fd); return pool; } static struct shm_pool * shm_pool_create(struct display *display, size_t size) { struct shm_pool *pool = malloc(sizeof *pool); if (!pool) return NULL; pool->pool = make_shm_pool(display, size, &pool->data); if (!pool->pool) { free(pool); return NULL; } pool->size = size; pool->used = 0; return pool; } static void * shm_pool_allocate(struct shm_pool *pool, size_t size, int *offset) { if (pool->used + size > pool->size) return NULL; *offset = pool->used; pool->used += size; return (char *) pool->data + *offset; } /* destroy the pool. this does not unmap the memory though */ static void shm_pool_destroy(struct shm_pool *pool) { munmap(pool->data, pool->size); wl_shm_pool_destroy(pool->pool); free(pool); } /* Start allocating from the beginning of the pool again */ static void shm_pool_reset(struct shm_pool *pool) { pool->used = 0; } static int data_length_for_shm_surface(struct rectangle *rect) { int stride; stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, rect->width); return stride * rect->height; } static cairo_surface_t * display_create_shm_surface_from_pool(struct display *display, struct rectangle *rectangle, uint32_t flags, struct shm_pool *pool) { struct shm_surface_data *data; uint32_t format; cairo_surface_t *surface; int stride, length, offset; void *map; data = malloc(sizeof *data); if (data == NULL) return NULL; stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, rectangle->width); length = stride * rectangle->height; data->pool = NULL; map = shm_pool_allocate(pool, length, &offset); if (!map) { free(data); return NULL; } surface = cairo_image_surface_create_for_data (map, CAIRO_FORMAT_ARGB32, rectangle->width, rectangle->height, stride); cairo_surface_set_user_data(surface, &shm_surface_data_key, data, shm_surface_data_destroy); if (flags & SURFACE_OPAQUE) format = WL_SHM_FORMAT_XRGB8888; else format = WL_SHM_FORMAT_ARGB8888; data->buffer = wl_shm_pool_create_buffer(pool->pool, offset, rectangle->width, rectangle->height, stride, format); return surface; } static cairo_surface_t * display_create_shm_surface(struct display *display, struct rectangle *rectangle, uint32_t flags, struct shm_pool *alternate_pool, struct shm_surface_data **data_ret) { struct shm_surface_data *data; struct shm_pool *pool; cairo_surface_t *surface; if (alternate_pool) { shm_pool_reset(alternate_pool); surface = display_create_shm_surface_from_pool(display, rectangle, flags, alternate_pool); if (surface) { data = cairo_surface_get_user_data(surface, &shm_surface_data_key); goto out; } } pool = shm_pool_create(display, data_length_for_shm_surface(rectangle)); if (!pool) return NULL; surface = display_create_shm_surface_from_pool(display, rectangle, flags, pool); if (!surface) { shm_pool_destroy(pool); return NULL; } /* make sure we destroy the pool when the surface is destroyed */ data = cairo_surface_get_user_data(surface, &shm_surface_data_key); data->pool = pool; out: if (data_ret) *data_ret = data; return surface; } static int check_size(struct rectangle *rect) { if (rect->width && rect->height) return 0; fprintf(stderr, "tried to create surface of " "width: %d, height: %d\n", rect->width, rect->height); return -1; } cairo_surface_t * display_create_surface(struct display *display, struct wl_surface *surface, struct rectangle *rectangle, uint32_t flags) { if (check_size(rectangle) < 0) return NULL; assert(flags & SURFACE_SHM); return display_create_shm_surface(display, rectangle, flags, NULL, NULL); } struct shm_surface_leaf { cairo_surface_t *cairo_surface; /* 'data' is automatically destroyed, when 'cairo_surface' is */ struct shm_surface_data *data; struct shm_pool *resize_pool; int busy; }; static void shm_surface_leaf_release(struct shm_surface_leaf *leaf) { if (leaf->cairo_surface) cairo_surface_destroy(leaf->cairo_surface); /* leaf->data already destroyed via cairo private */ if (leaf->resize_pool) shm_pool_destroy(leaf->resize_pool); memset(leaf, 0, sizeof *leaf); } #define MAX_LEAVES 3 struct shm_surface { struct toysurface base; struct display *display; struct wl_surface *surface; uint32_t flags; int dx, dy; struct shm_surface_leaf leaf[MAX_LEAVES]; struct shm_surface_leaf *current; }; static struct shm_surface * to_shm_surface(struct toysurface *base) { return container_of(base, struct shm_surface, base); } static void shm_surface_buffer_state_debug(struct shm_surface *surface, const char *msg) { #ifdef DEBUG struct shm_surface_leaf *leaf; char bufs[MAX_LEAVES + 1]; int i; for (i = 0; i < MAX_LEAVES; i++) { leaf = &surface->leaf[i]; if (leaf->busy) bufs[i] = 'b'; else if (leaf->cairo_surface) bufs[i] = 'a'; else bufs[i] = ' '; } bufs[MAX_LEAVES] = '\0'; DBG_OBJ(surface->surface, "%s, leaves [%s]\n", msg, bufs); #endif } static void shm_surface_buffer_release(void *data, struct wl_buffer *buffer) { struct shm_surface *surface = data; struct shm_surface_leaf *leaf; int i; int free_found; shm_surface_buffer_state_debug(surface, "buffer_release before"); for (i = 0; i < MAX_LEAVES; i++) { leaf = &surface->leaf[i]; if (leaf->data && leaf->data->buffer == buffer) { leaf->busy = 0; break; } } assert(i < MAX_LEAVES && "unknown buffer released"); /* Leave one free leaf with storage, release others */ free_found = 0; for (i = 0; i < MAX_LEAVES; i++) { leaf = &surface->leaf[i]; if (!leaf->cairo_surface || leaf->busy) continue; if (!free_found) free_found = 1; else shm_surface_leaf_release(leaf); } shm_surface_buffer_state_debug(surface, "buffer_release after"); } static const struct wl_buffer_listener shm_surface_buffer_listener = { shm_surface_buffer_release }; static cairo_surface_t * shm_surface_prepare(struct toysurface *base, int dx, int dy, int32_t width, int32_t height, uint32_t flags, enum wl_output_transform buffer_transform, int32_t buffer_scale) { int resize_hint = !!(flags & SURFACE_HINT_RESIZE); struct shm_surface *surface = to_shm_surface(base); struct rectangle rect = { 0}; struct shm_surface_leaf *leaf = NULL; int i; surface->dx = dx; surface->dy = dy; /* pick a free buffer, preferably one that already has storage */ for (i = 0; i < MAX_LEAVES; i++) { if (surface->leaf[i].busy) continue; if (!leaf || surface->leaf[i].cairo_surface) leaf = &surface->leaf[i]; } DBG_OBJ(surface->surface, "pick leaf %d\n", (int)(leaf - &surface->leaf[0])); if (!leaf) { fprintf(stderr, "%s: all buffers are held by the server.\n", __func__); exit(1); return NULL; } if (!resize_hint && leaf->resize_pool) { cairo_surface_destroy(leaf->cairo_surface); leaf->cairo_surface = NULL; shm_pool_destroy(leaf->resize_pool); leaf->resize_pool = NULL; } surface_to_buffer_size (buffer_transform, buffer_scale, &width, &height); if (leaf->cairo_surface && cairo_image_surface_get_width(leaf->cairo_surface) == width && cairo_image_surface_get_height(leaf->cairo_surface) == height) goto out; if (leaf->cairo_surface) cairo_surface_destroy(leaf->cairo_surface); #ifdef USE_RESIZE_POOL if (resize_hint && !leaf->resize_pool) { /* Create a big pool to allocate from, while continuously * resizing. Mmapping a new pool in the server * is relatively expensive, so reusing a pool performs * better, but may temporarily reserve unneeded memory. */ /* We should probably base this number on the output size. */ leaf->resize_pool = shm_pool_create(surface->display, 6 * 1024 * 1024); } #endif rect.width = width; rect.height = height; leaf->cairo_surface = display_create_shm_surface(surface->display, &rect, surface->flags, leaf->resize_pool, &leaf->data); if (!leaf->cairo_surface) return NULL; wl_buffer_add_listener(leaf->data->buffer, &shm_surface_buffer_listener, surface); out: surface->current = leaf; return cairo_surface_reference(leaf->cairo_surface); } static void shm_surface_swap(struct toysurface *base, enum wl_output_transform buffer_transform, int32_t buffer_scale, struct rectangle *server_allocation) { struct shm_surface *surface = to_shm_surface(base); struct shm_surface_leaf *leaf = surface->current; server_allocation->width = cairo_image_surface_get_width(leaf->cairo_surface); server_allocation->height = cairo_image_surface_get_height(leaf->cairo_surface); buffer_to_surface_size (buffer_transform, buffer_scale, &server_allocation->width, &server_allocation->height); wl_surface_attach(surface->surface, leaf->data->buffer, surface->dx, surface->dy); wl_surface_damage(surface->surface, 0, 0, server_allocation->width, server_allocation->height); wl_surface_commit(surface->surface); DBG_OBJ(surface->surface, "leaf %d busy\n", (int)(leaf - &surface->leaf[0])); leaf->busy = 1; surface->current = NULL; } static int shm_surface_acquire(struct toysurface *base, EGLContext ctx) { return -1; } static void shm_surface_release(struct toysurface *base) { } static void shm_surface_destroy(struct toysurface *base) { struct shm_surface *surface = to_shm_surface(base); int i; for (i = 0; i < MAX_LEAVES; i++) shm_surface_leaf_release(&surface->leaf[i]); free(surface); } static struct toysurface * shm_surface_create(struct display *display, struct wl_surface *wl_surface, uint32_t flags, struct rectangle *rectangle) { struct shm_surface *surface; DBG_OBJ(wl_surface, "\n"); surface = xzalloc(sizeof *surface); surface->base.prepare = shm_surface_prepare; surface->base.swap = shm_surface_swap; surface->base.acquire = shm_surface_acquire; surface->base.release = shm_surface_release; surface->base.destroy = shm_surface_destroy; surface->display = display; surface->surface = wl_surface; surface->flags = flags; return &surface->base; } /* * The following correspondences between file names and cursors was copied * from: https://bugs.kde.org/attachment.cgi?id=67313 */ static const char *bottom_left_corners[] = { "bottom_left_corner", "sw-resize", "size_bdiag" }; static const char *bottom_right_corners[] = { "bottom_right_corner", "se-resize", "size_fdiag" }; static const char *bottom_sides[] = { "bottom_side", "s-resize", "size_ver" }; static const char *grabbings[] = { "grabbing", "closedhand", "208530c400c041818281048008011002" }; static const char *left_ptrs[] = { "left_ptr", "default", "top_left_arrow", "left-arrow" }; static const char *left_sides[] = { "left_side", "w-resize", "size_hor" }; static const char *right_sides[] = { "right_side", "e-resize", "size_hor" }; static const char *top_left_corners[] = { "top_left_corner", "nw-resize", "size_fdiag" }; static const char *top_right_corners[] = { "top_right_corner", "ne-resize", "size_bdiag" }; static const char *top_sides[] = { "top_side", "n-resize", "size_ver" }; static const char *xterms[] = { "xterm", "ibeam", "text" }; static const char *hand1s[] = { "hand1", "pointer", "pointing_hand", "e29285e634086352946a0e7090d73106" }; static const char *watches[] = { "watch", "wait", "0426c94ea35c87780ff01dc239897213" }; static const char *move_draggings[] = { "dnd-move" }; static const char *copy_draggings[] = { "dnd-copy" }; static const char *forbidden_draggings[] = { "dnd-none", "dnd-no-drop" }; struct cursor_alternatives { const char **names; size_t count; }; static const struct cursor_alternatives cursors[] = { {bottom_left_corners, ARRAY_LENGTH(bottom_left_corners)}, {bottom_right_corners, ARRAY_LENGTH(bottom_right_corners)}, {bottom_sides, ARRAY_LENGTH(bottom_sides)}, {grabbings, ARRAY_LENGTH(grabbings)}, {left_ptrs, ARRAY_LENGTH(left_ptrs)}, {left_sides, ARRAY_LENGTH(left_sides)}, {right_sides, ARRAY_LENGTH(right_sides)}, {top_left_corners, ARRAY_LENGTH(top_left_corners)}, {top_right_corners, ARRAY_LENGTH(top_right_corners)}, {top_sides, ARRAY_LENGTH(top_sides)}, {xterms, ARRAY_LENGTH(xterms)}, {hand1s, ARRAY_LENGTH(hand1s)}, {watches, ARRAY_LENGTH(watches)}, {move_draggings, ARRAY_LENGTH(move_draggings)}, {copy_draggings, ARRAY_LENGTH(copy_draggings)}, {forbidden_draggings, ARRAY_LENGTH(forbidden_draggings)}, }; static void create_cursors(struct display *display) { const char *config_file; struct weston_config *config; struct weston_config_section *s; int size = DEFAULT_XCURSOR_SIZE; char *theme = NULL, *size_str; unsigned int i, j; struct wl_cursor *cursor; theme = getenv("XCURSOR_THEME"); size_str = getenv("XCURSOR_SIZE"); if (size_str) { safe_strtoint(size_str, &size); if (size <= 0) size = DEFAULT_XCURSOR_SIZE; } config_file = weston_config_get_name_from_env(); config = weston_config_parse(config_file); s = weston_config_get_section(config, "shell", NULL, NULL); weston_config_section_get_string(s, "cursor-theme", &theme, theme); weston_config_section_get_int(s, "cursor-size", &size, size); weston_config_destroy(config); display->cursor_theme = wl_cursor_theme_load(theme, size, display->shm); if (!display->cursor_theme) { fprintf(stderr, "could not load theme '%s'\n", theme); return; } free(theme); display->cursors = xmalloc(ARRAY_LENGTH(cursors) * sizeof display->cursors[0]); for (i = 0; i < ARRAY_LENGTH(cursors); i++) { cursor = NULL; for (j = 0; !cursor && j < cursors[i].count; ++j) cursor = wl_cursor_theme_get_cursor( display->cursor_theme, cursors[i].names[j]); if (!cursor) fprintf(stderr, "could not load cursor '%s'\n", cursors[i].names[0]); display->cursors[i] = cursor; } } static void destroy_cursors(struct display *display) { wl_cursor_theme_destroy(display->cursor_theme); free(display->cursors); } struct wl_cursor_image * display_get_pointer_image(struct display *display, int pointer) { struct wl_cursor *cursor = display->cursors[pointer]; return cursor ? cursor->images[0] : NULL; } static void surface_flush(struct surface *surface) { struct widget *widget = surface->widget; if (!surface->cairo_surface) return; if (surface->opaque_region) { wl_surface_set_opaque_region(surface->surface, surface->opaque_region); wl_region_destroy(surface->opaque_region); surface->opaque_region = NULL; } if (surface->input_region) { wl_surface_set_input_region(surface->surface, surface->input_region); wl_region_destroy(surface->input_region); surface->input_region = NULL; } if (surface->viewport) { wp_viewport_set_destination(surface->viewport, widget->viewport_dest_width, widget->viewport_dest_height); } surface->toysurface->swap(surface->toysurface, surface->buffer_transform, surface->buffer_scale, &surface->server_allocation); cairo_surface_destroy(surface->cairo_surface); surface->cairo_surface = NULL; } int window_has_focus(struct window *window) { return window->focused; } static void window_close(struct window *window) { if (window->close_handler) window->close_handler(window->user_data); else display_exit(window->display); } struct display * window_get_display(struct window *window) { return window->display; } static void surface_create_surface(struct surface *surface, uint32_t flags) { struct display *display = surface->window->display; struct rectangle allocation = surface->allocation; if (!surface->toysurface && display->dpy && surface->buffer_type == WINDOW_BUFFER_TYPE_EGL_WINDOW) { surface->toysurface = egl_window_surface_create(display, surface->surface, flags, &allocation); } if (!surface->toysurface) surface->toysurface = shm_surface_create(display, surface->surface, flags, &allocation); surface->cairo_surface = surface->toysurface->prepare( surface->toysurface, 0, 0, allocation.width, allocation.height, flags, surface->buffer_transform, surface->buffer_scale); } static void window_create_main_surface(struct window *window) { struct surface *surface = window->main_surface; uint32_t flags = 0; if (window->resizing) flags |= SURFACE_HINT_RESIZE; surface_create_surface(surface, flags); } int window_get_buffer_transform(struct window *window) { return window->main_surface->buffer_transform; } void window_set_buffer_transform(struct window *window, enum wl_output_transform transform) { window->main_surface->buffer_transform = transform; wl_surface_set_buffer_transform(window->main_surface->surface, transform); } void window_set_buffer_scale(struct window *window, int32_t scale) { window->main_surface->buffer_scale = scale; wl_surface_set_buffer_scale(window->main_surface->surface, scale); } uint32_t window_get_buffer_scale(struct window *window) { return window->main_surface->buffer_scale; } uint32_t window_get_output_scale(struct window *window) { struct window_output *window_output; struct window_output *window_output_tmp; int scale = 1; wl_list_for_each_safe(window_output, window_output_tmp, &window->window_output_list, link) { if (window_output->output->scale > scale) scale = window_output->output->scale; } return scale; } static void window_frame_destroy(struct window_frame *frame); static void surface_destroy(struct surface *surface) { if (surface->frame_cb) wl_callback_destroy(surface->frame_cb); if (surface->input_region) wl_region_destroy(surface->input_region); if (surface->opaque_region) wl_region_destroy(surface->opaque_region); if (surface->subsurface) wl_subsurface_destroy(surface->subsurface); wl_surface_destroy(surface->surface); if (surface->toysurface) surface->toysurface->destroy(surface->toysurface); wl_list_remove(&surface->link); free(surface); } void window_destroy(struct window *window) { struct display *display = window->display; struct input *input; struct window_output *window_output; struct window_output *window_output_tmp; wl_list_remove(&window->redraw_task.link); wl_list_for_each(input, &display->input_list, link) { if (input->touch_focus == window) { struct touch_point *tp, *tmp; wl_list_for_each_safe(tp, tmp, &input->touch_point_list, link) { wl_list_remove(&tp->link); free(tp); } input->touch_focus = NULL; } if (input->pointer_focus == window) input->pointer_focus = NULL; if (input->keyboard_focus == window) input->keyboard_focus = NULL; if (input->locked_window == window) input->locked_window = NULL; if (input->confined_window == window) input->confined_window = NULL; if (input->focus_widget && input->focus_widget->window == window) input->focus_widget = NULL; } wl_list_for_each_safe(window_output, window_output_tmp, &window->window_output_list, link) { free (window_output); } if (window->frame) window_frame_destroy(window->frame); if (window->xdg_toplevel) xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_popup) xdg_popup_destroy(window->xdg_popup); if (window->xdg_surface) xdg_surface_destroy(window->xdg_surface); surface_destroy(window->main_surface); wl_list_remove(&window->link); free(window->title); free(window); } static struct widget * widget_find_widget(struct widget *widget, int32_t x, int32_t y) { struct widget *child, *target; int alloc_x, alloc_y, width, height; double scale; wl_list_for_each(child, &widget->child_list, link) { target = widget_find_widget(child, x, y); if (target) return target; } alloc_x = widget->allocation.x; alloc_y = widget->allocation.y; width = widget->allocation.width; height = widget->allocation.height; if (widget->viewport_dest_width != -1 && widget->viewport_dest_height != -1) { scale = widget->viewport_dest_width / (double) width; alloc_x = alloc_x * scale; width = widget->viewport_dest_width; scale = widget->viewport_dest_height / (double) height; alloc_y = alloc_y * scale; height = widget->viewport_dest_height; } if (alloc_x <= x && x < alloc_x + width && alloc_y <= y && y < alloc_y + height) { return widget; } return NULL; } static struct widget * window_find_widget(struct window *window, int32_t x, int32_t y) { struct surface *surface; struct widget *widget; wl_list_for_each(surface, &window->subsurface_list, link) { widget = widget_find_widget(surface->widget, x, y); if (widget) return widget; } return NULL; } static struct widget * widget_create(struct window *window, struct surface *surface, void *data) { struct widget *widget; widget = xzalloc(sizeof *widget); widget->window = window; widget->surface = surface; widget->user_data = data; widget->allocation = surface->allocation; wl_list_init(&widget->child_list); widget->opaque = 0; widget->tooltip = NULL; widget->tooltip_count = 0; widget->default_cursor = CURSOR_LEFT_PTR; widget->use_cairo = 1; widget->viewport_dest_width = -1; widget->viewport_dest_height = -1; return widget; } struct widget * window_add_widget(struct window *window, void *data) { struct widget *widget; widget = widget_create(window, window->main_surface, data); wl_list_init(&widget->link); window->main_surface->widget = widget; return widget; } struct widget * widget_add_widget(struct widget *parent, void *data) { struct widget *widget; widget = widget_create(parent->window, parent->surface, data); wl_list_insert(parent->child_list.prev, &widget->link); return widget; } void widget_destroy(struct widget *widget) { struct display *display = widget->window->display; struct surface *surface = widget->surface; struct input *input; /* Destroy the sub-surface along with the root widget */ if (surface->widget == widget && surface->subsurface) surface_destroy(widget->surface); if (widget->tooltip) widget_destroy_tooltip(widget); wl_list_for_each(input, &display->input_list, link) { if (input->focus_widget == widget) input->focus_widget = NULL; } wl_list_remove(&widget->link); free(widget); } void widget_set_default_cursor(struct widget *widget, int cursor) { widget->default_cursor = cursor; } void widget_get_allocation(struct widget *widget, struct rectangle *allocation) { *allocation = widget->allocation; } void widget_set_size(struct widget *widget, int32_t width, int32_t height) { widget->allocation.width = width; widget->allocation.height = height; } void widget_set_allocation(struct widget *widget, int32_t x, int32_t y, int32_t width, int32_t height) { widget->allocation.x = x; widget->allocation.y = y; widget_set_size(widget, width, height); } void widget_set_transparent(struct widget *widget, int transparent) { widget->opaque = !transparent; } void * widget_get_user_data(struct widget *widget) { return widget->user_data; } static cairo_surface_t * widget_get_cairo_surface(struct widget *widget) { struct surface *surface = widget->surface; struct window *window = widget->window; assert(widget->use_cairo); if (!surface->cairo_surface) { if (surface == window->main_surface) window_create_main_surface(window); else surface_create_surface(surface, 0); } return surface->cairo_surface; } static void widget_cairo_update_transform(struct widget *widget, cairo_t *cr) { struct surface *surface = widget->surface; double angle; cairo_matrix_t m; enum wl_output_transform transform; int surface_width, surface_height; int translate_x, translate_y; int32_t scale; surface_width = surface->allocation.width; surface_height = surface->allocation.height; transform = surface->buffer_transform; scale = surface->buffer_scale; switch (transform) { case WL_OUTPUT_TRANSFORM_FLIPPED: case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_180: case WL_OUTPUT_TRANSFORM_FLIPPED_270: cairo_matrix_init(&m, -1, 0, 0, 1, 0, 0); break; default: cairo_matrix_init_identity(&m); break; } switch (transform) { case WL_OUTPUT_TRANSFORM_NORMAL: default: angle = 0; translate_x = 0; translate_y = 0; break; case WL_OUTPUT_TRANSFORM_FLIPPED: angle = 0; translate_x = surface_width; translate_y = 0; break; case WL_OUTPUT_TRANSFORM_90: angle = M_PI_2; translate_x = surface_height; translate_y = 0; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: angle = M_PI_2; translate_x = surface_height; translate_y = surface_width; break; case WL_OUTPUT_TRANSFORM_180: angle = M_PI; translate_x = surface_width; translate_y = surface_height; break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: angle = M_PI; translate_x = 0; translate_y = surface_height; break; case WL_OUTPUT_TRANSFORM_270: angle = M_PI + M_PI_2; translate_x = 0; translate_y = surface_width; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: angle = M_PI + M_PI_2; translate_x = 0; translate_y = 0; break; } cairo_scale(cr, scale, scale); cairo_translate(cr, translate_x, translate_y); cairo_rotate(cr, angle); cairo_transform(cr, &m); } cairo_t * widget_cairo_create(struct widget *widget) { struct surface *surface = widget->surface; cairo_surface_t *cairo_surface; cairo_t *cr; cairo_surface = widget_get_cairo_surface(widget); cr = cairo_create(cairo_surface); widget_cairo_update_transform(widget, cr); cairo_translate(cr, -surface->allocation.x, -surface->allocation.y); return cr; } struct wl_surface * widget_get_wl_surface(struct widget *widget) { return widget->surface->surface; } struct wl_subsurface * widget_get_wl_subsurface(struct widget *widget) { return widget->surface->subsurface; } uint32_t widget_get_last_time(struct widget *widget) { return widget->surface->last_time; } void widget_input_region_add(struct widget *widget, const struct rectangle *rect) { struct wl_compositor *comp = widget->window->display->compositor; struct surface *surface = widget->surface; if (!surface->input_region) surface->input_region = wl_compositor_create_region(comp); if (rect) { wl_region_add(surface->input_region, rect->x, rect->y, rect->width, rect->height); } } void widget_set_resize_handler(struct widget *widget, widget_resize_handler_t handler) { widget->resize_handler = handler; } void widget_set_redraw_handler(struct widget *widget, widget_redraw_handler_t handler) { widget->redraw_handler = handler; } void widget_set_enter_handler(struct widget *widget, widget_enter_handler_t handler) { widget->enter_handler = handler; } void widget_set_leave_handler(struct widget *widget, widget_leave_handler_t handler) { widget->leave_handler = handler; } void widget_set_motion_handler(struct widget *widget, widget_motion_handler_t handler) { widget->motion_handler = handler; } void widget_set_button_handler(struct widget *widget, widget_button_handler_t handler) { widget->button_handler = handler; } void widget_set_touch_up_handler(struct widget *widget, widget_touch_up_handler_t handler) { widget->touch_up_handler = handler; } void widget_set_touch_down_handler(struct widget *widget, widget_touch_down_handler_t handler) { widget->touch_down_handler = handler; } void widget_set_touch_motion_handler(struct widget *widget, widget_touch_motion_handler_t handler) { widget->touch_motion_handler = handler; } void widget_set_touch_frame_handler(struct widget *widget, widget_touch_frame_handler_t handler) { widget->touch_frame_handler = handler; } void widget_set_touch_cancel_handler(struct widget *widget, widget_touch_cancel_handler_t handler) { widget->touch_cancel_handler = handler; } void widget_set_axis_handler(struct widget *widget, widget_axis_handler_t handler) { widget->axis_handler = handler; } void widget_set_pointer_frame_handler(struct widget *widget, widget_pointer_frame_handler_t handler) { widget->pointer_frame_handler = handler; } void widget_set_axis_handlers(struct widget *widget, widget_axis_handler_t axis_handler, widget_axis_source_handler_t axis_source_handler, widget_axis_stop_handler_t axis_stop_handler, widget_axis_discrete_handler_t axis_discrete_handler) { widget->axis_handler = axis_handler; widget->axis_source_handler = axis_source_handler; widget->axis_stop_handler = axis_stop_handler; widget->axis_discrete_handler = axis_discrete_handler; } static void window_schedule_redraw_task(struct window *window); void widget_schedule_redraw(struct widget *widget) { DBG_OBJ(widget->surface->surface, "widget %p\n", widget); widget->surface->redraw_needed = 1; window_schedule_redraw_task(widget->window); } void widget_set_use_cairo(struct widget *widget, int use_cairo) { widget->use_cairo = use_cairo; } int widget_set_viewport_destination(struct widget *widget, int width, int height) { struct window *window = widget->window; struct display *display = window->display; struct surface *surface = widget->surface; if (!display->viewporter) return -1; if (width == -1 && height == -1) { if (surface->viewport) { wp_viewport_destroy(surface->viewport); surface->viewport = NULL; } widget->viewport_dest_width = -1; widget->viewport_dest_height = -1; return 0; } if (!surface->viewport) { surface->viewport = wp_viewporter_get_viewport(display->viewporter, surface->surface); if (!surface->viewport) return -1; } widget->viewport_dest_width = width; widget->viewport_dest_height = height; return 0; } cairo_surface_t * window_get_surface(struct window *window) { cairo_surface_t *cairo_surface; cairo_surface = widget_get_cairo_surface(window->main_surface->widget); return cairo_surface_reference(cairo_surface); } struct wl_surface * window_get_wl_surface(struct window *window) { return window->main_surface->surface; } static void tooltip_redraw_handler(struct widget *widget, void *data) { cairo_t *cr; const int32_t r = 3; struct tooltip *tooltip = data; int32_t width, height; cr = widget_cairo_create(widget); cairo_translate(cr, widget->allocation.x, widget->allocation.y); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0); cairo_paint(cr); width = widget->allocation.width; height = widget->allocation.height; rounded_rect(cr, 0, 0, width, height, r); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgba(cr, 0.0, 0.0, 0.4, 0.8); cairo_fill(cr); cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.85); cairo_move_to(cr, 10, 17); cairo_set_font_size(cr, 14); cairo_show_text(cr, tooltip->entry); cairo_destroy(cr); } static cairo_text_extents_t get_text_extents(struct display *display, struct tooltip *tooltip) { cairo_t *cr; cairo_text_extents_t extents; /* Use the dummy_surface because the tooltip's surface was not * created yet, and parent does not have a valid surface * outside repaint, either. */ cr = cairo_create(display->dummy_surface); cairo_set_font_size(cr, 14); cairo_text_extents(cr, tooltip->entry, &extents); cairo_destroy(cr); return extents; } static int window_create_tooltip(struct tooltip *tooltip) { struct widget *parent = tooltip->parent; struct display *display = parent->window->display; const int offset_y = 27; const int margin = 3; cairo_text_extents_t extents; if (tooltip->widget) return 0; tooltip->widget = window_add_subsurface(parent->window, tooltip, SUBSURFACE_DESYNCHRONIZED); extents = get_text_extents(display, tooltip); widget_set_redraw_handler(tooltip->widget, tooltip_redraw_handler); widget_set_allocation(tooltip->widget, tooltip->x, tooltip->y + offset_y, extents.width + 20, 20 + margin * 2); return 0; } void widget_destroy_tooltip(struct widget *parent) { struct tooltip *tooltip = parent->tooltip; parent->tooltip_count = 0; if (!tooltip) return; if (tooltip->widget) { widget_destroy(tooltip->widget); tooltip->widget = NULL; } toytimer_fini(&tooltip->timer); free(tooltip->entry); free(tooltip); parent->tooltip = NULL; } static void tooltip_func(struct toytimer *tt) { struct tooltip *tooltip = container_of(tt, struct tooltip, timer); window_create_tooltip(tooltip); } #define TOOLTIP_TIMEOUT 500 static int tooltip_timer_reset(struct tooltip *tooltip) { toytimer_arm_once_usec(&tooltip->timer, TOOLTIP_TIMEOUT * 1000); return 0; } int widget_set_tooltip(struct widget *parent, char *entry, float x, float y) { struct tooltip *tooltip = parent->tooltip; parent->tooltip_count++; if (tooltip) { tooltip->x = x; tooltip->y = y; tooltip_timer_reset(tooltip); return 0; } /* the handler might be triggered too fast via input device motion, so * we need this check here to make sure tooltip is fully initialized */ if (parent->tooltip_count > 1) return 0; tooltip = malloc(sizeof *tooltip); if (!tooltip) return -1; parent->tooltip = tooltip; tooltip->parent = parent; tooltip->widget = NULL; tooltip->x = x; tooltip->y = y; tooltip->entry = strdup(entry); toytimer_init(&tooltip->timer, CLOCK_MONOTONIC, parent->window->display, tooltip_func); tooltip_timer_reset(tooltip); return 0; } static void frame_resize_handler(struct widget *widget, int32_t width, int32_t height, void *data) { struct window_frame *frame = data; struct widget *child = frame->child; struct rectangle interior; struct rectangle input; struct rectangle opaque; if (widget->window->fullscreen) { interior.x = 0; interior.y = 0; interior.width = width; interior.height = height; } else { frame_resize(frame->frame, width, height); frame_interior(frame->frame, &interior.x, &interior.y, &interior.width, &interior.height); } widget_set_allocation(child, interior.x, interior.y, interior.width, interior.height); if (child->resize_handler) { child->resize_handler(child, interior.width, interior.height, child->user_data); if (widget->window->fullscreen) { width = child->allocation.width; height = child->allocation.height; } else { frame_resize_inside(frame->frame, child->allocation.width, child->allocation.height); width = frame_width(frame->frame); height = frame_height(frame->frame); } } widget_set_allocation(widget, 0, 0, width, height); widget->surface->input_region = wl_compositor_create_region(widget->window->display->compositor); if (!widget->window->fullscreen) { frame_input_rect(frame->frame, &input.x, &input.y, &input.width, &input.height); wl_region_add(widget->surface->input_region, input.x, input.y, input.width, input.height); } else { wl_region_add(widget->surface->input_region, 0, 0, width, height); } widget_set_allocation(widget, 0, 0, width, height); if (child->opaque) { if (!widget->window->fullscreen) { frame_opaque_rect(frame->frame, &opaque.x, &opaque.y, &opaque.width, &opaque.height); wl_region_add(widget->surface->opaque_region, opaque.x, opaque.y, opaque.width, opaque.height); } else { wl_region_add(widget->surface->opaque_region, 0, 0, width, height); } } widget_schedule_redraw(widget); } static void frame_redraw_handler(struct widget *widget, void *data) { cairo_t *cr; struct window_frame *frame = data; struct window *window = widget->window; if (window->fullscreen) return; cr = widget_cairo_create(widget); frame_repaint(frame->frame, cr); cairo_destroy(cr); } static int frame_get_pointer_image_for_location(struct window_frame *frame, enum theme_location location) { struct window *window = frame->widget->window; if (window->custom) return CURSOR_LEFT_PTR; switch (location) { case THEME_LOCATION_RESIZING_TOP: return CURSOR_TOP; case THEME_LOCATION_RESIZING_BOTTOM: return CURSOR_BOTTOM; case THEME_LOCATION_RESIZING_LEFT: return CURSOR_LEFT; case THEME_LOCATION_RESIZING_RIGHT: return CURSOR_RIGHT; case THEME_LOCATION_RESIZING_TOP_LEFT: return CURSOR_TOP_LEFT; case THEME_LOCATION_RESIZING_TOP_RIGHT: return CURSOR_TOP_RIGHT; case THEME_LOCATION_RESIZING_BOTTOM_LEFT: return CURSOR_BOTTOM_LEFT; case THEME_LOCATION_RESIZING_BOTTOM_RIGHT: return CURSOR_BOTTOM_RIGHT; case THEME_LOCATION_EXTERIOR: case THEME_LOCATION_TITLEBAR: default: return CURSOR_LEFT_PTR; } } static void frame_menu_func(void *data, struct input *input, int index) { struct window *window = data; switch (index) { case 0: /* close */ window_close(window); break; case 1: /* fullscreen */ /* we don't have a way to get out of fullscreen for now */ if (window->fullscreen_handler) window->fullscreen_handler(window, window->user_data); break; } } void window_show_frame_menu(struct window *window, struct input *input, uint32_t time) { int32_t x, y; int count; static const char *entries[] = { "Close", "Fullscreen" }; if (window->fullscreen_handler) count = ARRAY_LENGTH(entries); else count = ARRAY_LENGTH(entries) - 1; input_get_position(input, &x, &y); window_show_menu(window->display, input, time, window, x - 10, y - 10, frame_menu_func, entries, count); } static int frame_enter_handler(struct widget *widget, struct input *input, float x, float y, void *data) { struct window_frame *frame = data; enum theme_location location; location = frame_pointer_enter(frame->frame, input, x, y); if (frame_status(frame->frame) & FRAME_STATUS_REPAINT) widget_schedule_redraw(frame->widget); return frame_get_pointer_image_for_location(data, location); } static int frame_motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct window_frame *frame = data; enum theme_location location; location = frame_pointer_motion(frame->frame, input, x, y); if (frame_status(frame->frame) & FRAME_STATUS_REPAINT) widget_schedule_redraw(frame->widget); return frame_get_pointer_image_for_location(data, location); } static void frame_leave_handler(struct widget *widget, struct input *input, void *data) { struct window_frame *frame = data; frame_pointer_leave(frame->frame, input); if (frame_status(frame->frame) & FRAME_STATUS_REPAINT) widget_schedule_redraw(frame->widget); } static void frame_handle_status(struct window_frame *frame, struct input *input, uint32_t time, enum theme_location location) { struct window *window = frame->widget->window; uint32_t status; status = frame_status(frame->frame); if (status & FRAME_STATUS_REPAINT) widget_schedule_redraw(frame->widget); if (status & FRAME_STATUS_MINIMIZE) { window_set_minimized(window); frame_status_clear(frame->frame, FRAME_STATUS_MINIMIZE); } if (status & FRAME_STATUS_MENU) { window_show_frame_menu(window, input, time); frame_status_clear(frame->frame, FRAME_STATUS_MENU); } if (status & FRAME_STATUS_MAXIMIZE) { window_set_maximized(window, !window->maximized); frame_status_clear(frame->frame, FRAME_STATUS_MAXIMIZE); } if (status & FRAME_STATUS_CLOSE) { window_close(window); return; } if ((status & FRAME_STATUS_MOVE) && window->xdg_toplevel) { input_ungrab(input); xdg_toplevel_move(window->xdg_toplevel, input_get_seat(input), window->display->serial); frame_status_clear(frame->frame, FRAME_STATUS_MOVE); } if ((status & FRAME_STATUS_RESIZE) && window->xdg_toplevel) { input_ungrab(input); xdg_toplevel_resize(window->xdg_toplevel, input_get_seat(input), window->display->serial, location); frame_status_clear(frame->frame, FRAME_STATUS_RESIZE); } } #define DOUBLE_CLICK_PERIOD 250 static void frame_button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct window_frame *frame = data; enum theme_location location; frame->double_click = 0; if (state == WL_POINTER_BUTTON_STATE_PRESSED) { if (time - frame->last_time <= DOUBLE_CLICK_PERIOD) { frame->double_click = 1; frame->did_double = 1; } else frame->did_double = 0; frame->last_time = time; } else if (frame->did_double == 1) { frame->double_click = 1; frame->did_double = 0; } if (frame->double_click) location = frame_double_click(frame->frame, input, button, state); else location = frame_pointer_button(frame->frame, input, button, state); frame_handle_status(frame, input, time, location); } static void frame_touch_down_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data) { struct window_frame *frame = data; frame->double_click = 0; if (time - frame->last_time <= DOUBLE_CLICK_PERIOD && frame->last_id == id) { frame->double_click = 1; frame->did_double = 1; frame->double_id = id; } else frame->did_double = 0; frame->last_time = time; frame->last_id = id; if (frame->double_click) frame_double_touch_down(frame->frame, input, id, x, y); else frame_touch_down(frame->frame, input, id, x, y); frame_handle_status(frame, input, time, THEME_LOCATION_CLIENT_AREA); } static void frame_touch_up_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, void *data) { struct window_frame *frame = data; if (frame->double_id == id && frame->did_double) { frame->did_double = 0; frame->double_id = 0; frame_double_touch_up(frame->frame, input, id); } else frame_touch_up(frame->frame, input, id); frame_handle_status(frame, input, time, THEME_LOCATION_CLIENT_AREA); } struct widget * window_frame_create(struct window *window, void *data) { struct window_frame *frame; uint32_t buttons; if (window->custom) { buttons = FRAME_BUTTON_NONE; } else { buttons = FRAME_BUTTON_ALL; } frame = xzalloc(sizeof *frame); frame->frame = frame_create(window->display->theme, 0, 0, buttons, window->title, NULL); frame->widget = window_add_widget(window, frame); frame->child = widget_add_widget(frame->widget, data); widget_set_redraw_handler(frame->widget, frame_redraw_handler); widget_set_resize_handler(frame->widget, frame_resize_handler); widget_set_enter_handler(frame->widget, frame_enter_handler); widget_set_leave_handler(frame->widget, frame_leave_handler); widget_set_motion_handler(frame->widget, frame_motion_handler); widget_set_button_handler(frame->widget, frame_button_handler); widget_set_touch_down_handler(frame->widget, frame_touch_down_handler); widget_set_touch_up_handler(frame->widget, frame_touch_up_handler); window->frame = frame; return frame->child; } void window_frame_set_child_size(struct widget *widget, int child_width, int child_height) { struct display *display = widget->window->display; struct theme *t = display->theme; int decoration_width, decoration_height; int width, height; int margin = widget->window->maximized ? 0 : t->margin; if (!widget->window->fullscreen) { decoration_width = (t->width + margin) * 2; decoration_height = t->width + t->titlebar_height + margin * 2; width = child_width + decoration_width; height = child_height + decoration_height; } else { width = child_width; height = child_height; } window_schedule_resize(widget->window, width, height); } static void window_frame_destroy(struct window_frame *frame) { frame_destroy(frame->frame); /* frame->child must be destroyed by the application */ widget_destroy(frame->widget); free(frame); } static void input_set_focus_widget(struct input *input, struct widget *focus, float x, float y) { struct widget *old, *widget; int cursor; if (focus == input->focus_widget) return; old = input->focus_widget; if (old) { widget = old; if (input->grab) widget = input->grab; if (widget->leave_handler) widget->leave_handler(old, input, widget->user_data); input->focus_widget = NULL; } if (focus) { widget = focus; if (input->grab) widget = input->grab; input->focus_widget = focus; if (widget->enter_handler) cursor = widget->enter_handler(focus, input, x, y, widget->user_data); else cursor = widget->default_cursor; input_set_pointer_image(input, cursor); } } void touch_grab(struct input *input, int32_t touch_id) { input->touch_grab = 1; input->touch_grab_id = touch_id; } void touch_ungrab(struct input *input) { struct touch_point *tp, *tmp; input->touch_grab = 0; wl_list_for_each_safe(tp, tmp, &input->touch_point_list, link) { if (tp->id != input->touch_grab_id) continue; wl_list_remove(&tp->link); free(tp); return; } } void input_grab(struct input *input, struct widget *widget, uint32_t button) { input->grab = widget; input->grab_button = button; input_set_focus_widget(input, widget, input->sx, input->sy); } void input_ungrab(struct input *input) { struct widget *widget; input->grab = NULL; if (input->pointer_focus) { widget = window_find_widget(input->pointer_focus, input->sx, input->sy); input_set_focus_widget(input, widget, input->sx, input->sy); } } static void cursor_delay_timer_reset(struct input *input, uint32_t duration) { if (!duration) input->cursor_timer_running = false; else input->cursor_timer_running = true; toytimer_arm_once_usec(&input->cursor_timer, duration * 1000); } static void cancel_pointer_image_update(struct input *input) { if (input->cursor_timer_running) cursor_delay_timer_reset(input, 0); } static void input_remove_pointer_focus(struct input *input) { struct window *window = input->pointer_focus; if (!window) return; input_set_focus_widget(input, NULL, 0, 0); input->pointer_focus = NULL; input->current_cursor = CURSOR_UNSET; cancel_pointer_image_update(input); } static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx_w, wl_fixed_t sy_w) { struct input *input = data; struct window *window; struct widget *widget; float sx = wl_fixed_to_double(sx_w); float sy = wl_fixed_to_double(sy_w); if (!surface) { /* enter event for a window we've just destroyed */ return; } window = wl_surface_get_user_data(surface); if (surface != window->main_surface->surface) { DBG("Ignoring input event from subsurface %p\n", surface); return; } input->display->serial = serial; input->pointer_enter_serial = serial; input->pointer_focus = window; input->sx = sx; input->sy = sy; widget = window_find_widget(window, sx, sy); input_set_focus_widget(input, widget, sx, sy); } static void pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { struct input *input = data; input->display->serial = serial; input_remove_pointer_focus(input); } static void pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w) { struct input *input = data; struct window *window = input->pointer_focus; struct widget *widget; int cursor; float sx = wl_fixed_to_double(sx_w); float sy = wl_fixed_to_double(sy_w); if (!window) return; input->sx = sx; input->sy = sy; /* when making the window smaller - e.g. after an unmaximise we might * still have a pending motion event that the compositor has picked * based on the old surface dimensions. However, if we have an active * grab, we expect to see input from outside the window anyway. */ if (!input->grab && (sx < window->main_surface->allocation.x || sy < window->main_surface->allocation.y || sx > window->main_surface->allocation.width || sy > window->main_surface->allocation.height)) return; if (!(input->grab && input->grab_button)) { widget = window_find_widget(window, sx, sy); input_set_focus_widget(input, widget, sx, sy); } if (input->grab) widget = input->grab; else widget = input->focus_widget; if (widget) { if (widget->motion_handler) cursor = widget->motion_handler(input->focus_widget, input, time, sx, sy, widget->user_data); else cursor = widget->default_cursor; } else cursor = CURSOR_LEFT_PTR; input_set_pointer_image(input, cursor); } static void pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state_w) { struct input *input = data; struct widget *widget; enum wl_pointer_button_state state = state_w; input->display->serial = serial; if (input->focus_widget && input->grab == NULL && state == WL_POINTER_BUTTON_STATE_PRESSED) input_grab(input, input->focus_widget, button); widget = input->grab; if (widget && widget->button_handler) (*widget->button_handler)(widget, input, time, button, state, input->grab->user_data); if (input->grab && input->grab_button == button && state == WL_POINTER_BUTTON_STATE_RELEASED) input_ungrab(input); } static void pointer_handle_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { struct input *input = data; struct widget *widget; widget = input->focus_widget; if (input->grab) widget = input->grab; if (widget && widget->axis_handler) (*widget->axis_handler)(widget, input, time, axis, value, widget->user_data); } static void pointer_handle_frame(void *data, struct wl_pointer *pointer) { struct input *input = data; struct widget *widget; widget = input->focus_widget; if (input->grab) widget = input->grab; if (widget && widget->pointer_frame_handler) (*widget->pointer_frame_handler)(widget, input, widget->user_data); } static void pointer_handle_axis_source(void *data, struct wl_pointer *pointer, uint32_t source) { struct input *input = data; struct widget *widget; widget = input->focus_widget; if (input->grab) widget = input->grab; if (widget && widget->axis_source_handler) (*widget->axis_source_handler)(widget, input, source, widget->user_data); } static void pointer_handle_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis) { struct input *input = data; struct widget *widget; widget = input->focus_widget; if (input->grab) widget = input->grab; if (widget && widget->axis_stop_handler) (*widget->axis_stop_handler)(widget, input, time, axis, widget->user_data); } static void pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete) { struct input *input = data; struct widget *widget; widget = input->focus_widget; if (input->grab) widget = input->grab; if (widget && widget->axis_discrete_handler) (*widget->axis_discrete_handler)(widget, input, axis, discrete, widget->user_data); } static const struct wl_pointer_listener pointer_listener = { pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis, pointer_handle_frame, pointer_handle_axis_source, pointer_handle_axis_stop, pointer_handle_axis_discrete, }; static void input_remove_keyboard_focus(struct input *input) { struct window *window = input->keyboard_focus; toytimer_disarm(&input->repeat_timer); if (!window) return; if (window->keyboard_focus_handler) (*window->keyboard_focus_handler)(window, NULL, window->user_data); input->keyboard_focus = NULL; } static void keyboard_repeat_func(struct toytimer *tt) { struct input *input = container_of(tt, struct input, repeat_timer); struct window *window = input->keyboard_focus; if (window && window->key_handler) { (*window->key_handler)(window, input, input->repeat_time, input->repeat_key, input->repeat_sym, WL_KEYBOARD_KEY_STATE_PRESSED, window->user_data); } } static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) { struct input *input = data; struct xkb_keymap *keymap; struct xkb_state *state; #ifdef HAVE_XKBCOMMON_COMPOSE struct xkb_compose_table *compose_table; struct xkb_compose_state *compose_state; #endif char *locale; char *map_str; if (!data) { close(fd); return; } if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { close(fd); return; } map_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (map_str == MAP_FAILED) { close(fd); return; } /* Set up XKB keymap */ keymap = xkb_keymap_new_from_string(input->display->xkb_context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, 0); munmap(map_str, size); close(fd); if (!keymap) { fprintf(stderr, "failed to compile keymap\n"); return; } /* Set up XKB state */ state = xkb_state_new(keymap); if (!state) { fprintf(stderr, "failed to create XKB state\n"); xkb_keymap_unref(keymap); return; } /* Look up the preferred locale, falling back to "C" as default */ if (!(locale = getenv("LC_ALL"))) if (!(locale = getenv("LC_CTYPE"))) if (!(locale = getenv("LANG"))) locale = "C"; /* Set up XKB compose table */ #ifdef HAVE_XKBCOMMON_COMPOSE compose_table = xkb_compose_table_new_from_locale(input->display->xkb_context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS); if (compose_table) { /* Set up XKB compose state */ compose_state = xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS); if (compose_state) { xkb_compose_state_unref(input->xkb.compose_state); xkb_compose_table_unref(input->xkb.compose_table); input->xkb.compose_state = compose_state; input->xkb.compose_table = compose_table; } else { fprintf(stderr, "could not create XKB compose state. " "Disabiling compose.\n"); xkb_compose_table_unref(compose_table); compose_table = NULL; } } else { fprintf(stderr, "could not create XKB compose table for locale '%s'. " "Disabiling compose\n", locale); } #endif xkb_keymap_unref(input->xkb.keymap); xkb_state_unref(input->xkb.state); input->xkb.keymap = keymap; input->xkb.state = state; input->xkb.control_mask = 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Control"); input->xkb.alt_mask = 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Mod1"); input->xkb.shift_mask = 1 << xkb_keymap_mod_get_index(input->xkb.keymap, "Shift"); } static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { struct input *input = data; struct window *window; if (!surface) { /* enter event for a window we've just destroyed */ return; } input->display->serial = serial; input->keyboard_focus = wl_surface_get_user_data(surface); window = input->keyboard_focus; if (window->keyboard_focus_handler) (*window->keyboard_focus_handler)(window, input, window->user_data); } static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { struct input *input = data; input->display->serial = serial; input_remove_keyboard_focus(input); } /* Translate symbols appropriately if a compose sequence is being entered */ static xkb_keysym_t process_key_press(xkb_keysym_t sym, struct input *input) { #ifdef HAVE_XKBCOMMON_COMPOSE if (!input->xkb.compose_state) return sym; if (sym == XKB_KEY_NoSymbol) return sym; if (xkb_compose_state_feed(input->xkb.compose_state, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym; switch (xkb_compose_state_get_status(input->xkb.compose_state)) { case XKB_COMPOSE_COMPOSING: return XKB_KEY_NoSymbol; case XKB_COMPOSE_COMPOSED: return xkb_compose_state_get_one_sym(input->xkb.compose_state); case XKB_COMPOSE_CANCELLED: return XKB_KEY_NoSymbol; case XKB_COMPOSE_NOTHING: return sym; default: return sym; } #else return sym; #endif } static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state_w) { struct input *input = data; struct window *window = input->keyboard_focus; uint32_t code, num_syms; enum wl_keyboard_key_state state = state_w; const xkb_keysym_t *syms; xkb_keysym_t sym; struct itimerspec its; input->display->serial = serial; code = key + 8; if (!window || !input->xkb.state) return; /* We only use input grabs for pointer events for now, so just * ignore key presses if a grab is active. We expand the key * event delivery mechanism to route events to widgets to * properly handle key grabs. In the meantime, this prevents * key event delivery while a grab is active. */ if (input->grab && input->grab_button == 0) return; num_syms = xkb_state_key_get_syms(input->xkb.state, code, &syms); sym = XKB_KEY_NoSymbol; if (num_syms == 1) sym = syms[0]; if (sym == XKB_KEY_F5 && input->modifiers == MOD_ALT_MASK) { if (state == WL_KEYBOARD_KEY_STATE_PRESSED) window_set_maximized(window, !window->maximized); } else if (sym == XKB_KEY_F11 && window->fullscreen_handler && state == WL_KEYBOARD_KEY_STATE_PRESSED) { window->fullscreen_handler(window, window->user_data); } else if (sym == XKB_KEY_F4 && input->modifiers == MOD_ALT_MASK && state == WL_KEYBOARD_KEY_STATE_PRESSED) { window_close(window); } else if (window->key_handler) { if (state == WL_KEYBOARD_KEY_STATE_PRESSED) sym = process_key_press(sym, input); (*window->key_handler)(window, input, time, key, sym, state, window->user_data); } if (state == WL_KEYBOARD_KEY_STATE_RELEASED && key == input->repeat_key) { toytimer_disarm(&input->repeat_timer); } else if (state == WL_KEYBOARD_KEY_STATE_PRESSED && xkb_keymap_key_repeats(input->xkb.keymap, code)) { input->repeat_sym = sym; input->repeat_key = key; input->repeat_time = time; its.it_interval.tv_sec = input->repeat_rate_sec; its.it_interval.tv_nsec = input->repeat_rate_nsec; its.it_value.tv_sec = input->repeat_delay_sec; its.it_value.tv_nsec = input->repeat_delay_nsec; toytimer_arm(&input->repeat_timer, &its); } } static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { struct input *input = data; xkb_mod_mask_t mask; /* If we're not using a keymap, then we don't handle PC-style modifiers */ if (!input->xkb.keymap) return; xkb_state_update_mask(input->xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group); mask = xkb_state_serialize_mods(input->xkb.state, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); input->modifiers = 0; if (mask & input->xkb.control_mask) input->modifiers |= MOD_CONTROL_MASK; if (mask & input->xkb.alt_mask) input->modifiers |= MOD_ALT_MASK; if (mask & input->xkb.shift_mask) input->modifiers |= MOD_SHIFT_MASK; } static void set_repeat_info(struct input *input, int32_t rate, int32_t delay) { input->repeat_rate_sec = input->repeat_rate_nsec = 0; input->repeat_delay_sec = input->repeat_delay_nsec = 0; /* a rate of zero disables any repeating, regardless of the delay's * value */ if (rate == 0) return; if (rate == 1) input->repeat_rate_sec = 1; else input->repeat_rate_nsec = 1000000000 / rate; input->repeat_delay_sec = delay / 1000; delay -= (input->repeat_delay_sec * 1000); input->repeat_delay_nsec = delay * 1000 * 1000; } static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay) { struct input *input = data; set_repeat_info(input, rate, delay); } static const struct wl_keyboard_listener keyboard_listener = { keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, keyboard_handle_repeat_info }; static void touch_handle_down(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { struct input *input = data; struct widget *widget; float sx = wl_fixed_to_double(x_w); float sy = wl_fixed_to_double(y_w); if (!surface) { /* down event for a window we've just destroyed */ return; } input->display->serial = serial; input->touch_focus = wl_surface_get_user_data(surface); if (!input->touch_focus) { DBG("Failed to find to touch focus for surface %p\n", surface); return; } if (surface != input->touch_focus->main_surface->surface) { DBG("Ignoring input event from subsurface %p\n", surface); input->touch_focus = NULL; return; } if (input->grab) widget = input->grab; else widget = window_find_widget(input->touch_focus, wl_fixed_to_double(x_w), wl_fixed_to_double(y_w)); if (widget) { struct touch_point *tp = xmalloc(sizeof *tp); if (tp) { tp->id = id; tp->widget = widget; tp->x = sx; tp->y = sy; wl_list_insert(&input->touch_point_list, &tp->link); if (widget->touch_down_handler) (*widget->touch_down_handler)(widget, input, serial, time, id, sx, sy, widget->user_data); } } } static void touch_handle_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id) { struct input *input = data; struct touch_point *tp, *tmp; if (!input->touch_focus) { DBG("No touch focus found for touch up event!\n"); return; } wl_list_for_each_safe(tp, tmp, &input->touch_point_list, link) { if (tp->id != id) continue; if (tp->widget->touch_up_handler) (*tp->widget->touch_up_handler)(tp->widget, input, serial, time, id, tp->widget->user_data); wl_list_remove(&tp->link); free(tp); return; } } static void touch_handle_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { struct input *input = data; struct touch_point *tp; float sx = wl_fixed_to_double(x_w); float sy = wl_fixed_to_double(y_w); DBG("touch_handle_motion: %i %i\n", id, wl_list_length(&input->touch_point_list)); if (!input->touch_focus) { DBG("No touch focus found for touch motion event!\n"); return; } wl_list_for_each(tp, &input->touch_point_list, link) { if (tp->id != id) continue; tp->x = sx; tp->y = sy; if (tp->widget->touch_motion_handler) (*tp->widget->touch_motion_handler)(tp->widget, input, time, id, sx, sy, tp->widget->user_data); return; } } static void touch_handle_frame(void *data, struct wl_touch *wl_touch) { struct input *input = data; struct touch_point *tp, *tmp; DBG("touch_handle_frame\n"); if (!input->touch_focus) { DBG("No touch focus found for touch frame event!\n"); return; } wl_list_for_each_safe(tp, tmp, &input->touch_point_list, link) { if (tp->widget->touch_frame_handler) (*tp->widget->touch_frame_handler)(tp->widget, input, tp->widget->user_data); } } static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) { struct input *input = data; struct touch_point *tp, *tmp; DBG("touch_handle_cancel\n"); if (!input->touch_focus) { DBG("No touch focus found for touch cancel event!\n"); return; } wl_list_for_each_safe(tp, tmp, &input->touch_point_list, link) { if (tp->widget->touch_cancel_handler) (*tp->widget->touch_cancel_handler)(tp->widget, input, tp->widget->user_data); wl_list_remove(&tp->link); free(tp); } } void touch_handle_shape(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) { } void touch_handle_orientation(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t orientation) { } static const struct wl_touch_listener touch_listener = { touch_handle_down, touch_handle_up, touch_handle_motion, touch_handle_frame, touch_handle_cancel, touch_handle_shape, touch_handle_orientation, }; static void seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { struct input *input = data; if ((caps & WL_SEAT_CAPABILITY_POINTER) && !input->pointer) { input->pointer = wl_seat_get_pointer(seat); wl_pointer_set_user_data(input->pointer, input); wl_pointer_add_listener(input->pointer, &pointer_listener, input); } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && input->pointer) { if (input->seat_version >= WL_POINTER_RELEASE_SINCE_VERSION) wl_pointer_release(input->pointer); else wl_pointer_destroy(input->pointer); input->pointer = NULL; } if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->keyboard) { input->keyboard = wl_seat_get_keyboard(seat); wl_keyboard_set_user_data(input->keyboard, input); wl_keyboard_add_listener(input->keyboard, &keyboard_listener, input); } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->keyboard) { if (input->seat_version >= WL_KEYBOARD_RELEASE_SINCE_VERSION) wl_keyboard_release(input->keyboard); else wl_keyboard_destroy(input->keyboard); input->keyboard = NULL; } if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->touch) { input->touch = wl_seat_get_touch(seat); wl_touch_set_user_data(input->touch, input); wl_touch_add_listener(input->touch, &touch_listener, input); } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && input->touch) { if (input->seat_version >= WL_TOUCH_RELEASE_SINCE_VERSION) wl_touch_release(input->touch); else wl_touch_destroy(input->touch); input->touch = NULL; } } static void seat_handle_name(void *data, struct wl_seat *seat, const char *name) { } static const struct wl_seat_listener seat_listener = { seat_handle_capabilities, seat_handle_name }; void input_get_position(struct input *input, int32_t *x, int32_t *y) { *x = input->sx; *y = input->sy; } int input_get_touch(struct input *input, int32_t id, float *x, float *y) { struct touch_point *tp; wl_list_for_each(tp, &input->touch_point_list, link) { if (tp->id != id) continue; *x = tp->x; *y = tp->y; return 0; } return -1; } struct display * input_get_display(struct input *input) { return input->display; } struct wl_seat * input_get_seat(struct input *input) { return input->seat; } uint32_t input_get_modifiers(struct input *input) { return input->modifiers; } struct widget * input_get_focus_widget(struct input *input) { return input->focus_widget; } struct data_offer { struct wl_data_offer *offer; struct input *input; struct wl_array types; int refcount; struct task io_task; int fd; data_func_t func; int32_t x, y; uint32_t dnd_action; uint32_t source_actions; void *user_data; }; static void data_offer_offer(void *data, struct wl_data_offer *wl_data_offer, const char *type) { struct data_offer *offer = data; char **p; p = wl_array_add(&offer->types, sizeof *p); *p = strdup(type); } static void data_offer_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) { struct data_offer *offer = data; offer->source_actions = source_actions; } static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) { struct data_offer *offer = data; offer->dnd_action = dnd_action; } static const struct wl_data_offer_listener data_offer_listener = { data_offer_offer, data_offer_source_actions, data_offer_action }; static void data_offer_destroy(struct data_offer *offer) { char **p; offer->refcount--; if (offer->refcount == 0) { wl_data_offer_destroy(offer->offer); for (p = offer->types.data; *p; p++) free(*p); wl_array_release(&offer->types); free(offer); } } static void data_device_data_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *_offer) { struct data_offer *offer; offer = xmalloc(sizeof *offer); wl_array_init(&offer->types); offer->refcount = 1; offer->input = data; offer->offer = _offer; wl_data_offer_add_listener(offer->offer, &data_offer_listener, offer); } static void data_device_enter(void *data, struct wl_data_device *data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x_w, wl_fixed_t y_w, struct wl_data_offer *offer) { struct input *input = data; struct window *window; void *types_data; float x = wl_fixed_to_double(x_w); float y = wl_fixed_to_double(y_w); char **p; if (!surface) { /* enter event for a window we've just destroyed */ return; } window = wl_surface_get_user_data(surface); input->drag_enter_serial = serial; input->drag_focus = window, input->drag_x = x; input->drag_y = y; if (!input->touch_grab) input->pointer_enter_serial = serial; if (offer) { input->drag_offer = wl_data_offer_get_user_data(offer); p = wl_array_add(&input->drag_offer->types, sizeof *p); *p = NULL; types_data = input->drag_offer->types.data; if (input->display->data_device_manager_version >= WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION) { wl_data_offer_set_actions(offer, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE, WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE); } } else { input->drag_offer = NULL; types_data = NULL; } if (window->data_handler) window->data_handler(window, input, x, y, types_data, window->user_data); } static void data_device_leave(void *data, struct wl_data_device *data_device) { struct input *input = data; if (input->drag_offer) { data_offer_destroy(input->drag_offer); input->drag_offer = NULL; } } static void data_device_motion(void *data, struct wl_data_device *data_device, uint32_t time, wl_fixed_t x_w, wl_fixed_t y_w) { struct input *input = data; struct window *window = input->drag_focus; float x = wl_fixed_to_double(x_w); float y = wl_fixed_to_double(y_w); void *types_data; input->drag_x = x; input->drag_y = y; if (input->drag_offer) types_data = input->drag_offer->types.data; else types_data = NULL; if (window->data_handler) window->data_handler(window, input, x, y, types_data, window->user_data); } static void data_device_drop(void *data, struct wl_data_device *data_device) { struct input *input = data; struct window *window = input->drag_focus; float x, y; x = input->drag_x; y = input->drag_y; if (window->drop_handler) window->drop_handler(window, input, x, y, window->user_data); if (input->touch_grab) touch_ungrab(input); } static void data_device_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *offer) { struct input *input = data; char **p; if (input->selection_offer) data_offer_destroy(input->selection_offer); if (offer) { input->selection_offer = wl_data_offer_get_user_data(offer); p = wl_array_add(&input->selection_offer->types, sizeof *p); *p = NULL; } else { input->selection_offer = NULL; } } static const struct wl_data_device_listener data_device_listener = { data_device_data_offer, data_device_enter, data_device_leave, data_device_motion, data_device_drop, data_device_selection }; static void input_set_pointer_image_index(struct input *input, int index) { struct wl_buffer *buffer; struct wl_cursor *cursor; struct wl_cursor_image *image; if (!input->pointer) return; cursor = input->display->cursors[input->current_cursor]; if (!cursor) return; if (index >= (int) cursor->image_count) { fprintf(stderr, "cursor index out of range\n"); return; } image = cursor->images[index]; buffer = wl_cursor_image_get_buffer(image); if (!buffer) return; wl_surface_attach(input->pointer_surface, buffer, 0, 0); wl_surface_damage(input->pointer_surface, 0, 0, image->width, image->height); wl_surface_commit(input->pointer_surface); wl_pointer_set_cursor(input->pointer, input->pointer_enter_serial, input->pointer_surface, image->hotspot_x, image->hotspot_y); } static const struct wl_callback_listener pointer_surface_listener; static bool input_set_pointer_special(struct input *input) { if (input->current_cursor == CURSOR_BLANK) { wl_pointer_set_cursor(input->pointer, input->pointer_enter_serial, NULL, 0, 0); return true; } if (input->current_cursor == CURSOR_UNSET) return true; return false; } static void schedule_pointer_image_update(struct input *input, struct wl_cursor *cursor, uint32_t duration, bool force_frame) { /* Some silly cursor sets have enormous pauses in them. In these * cases it's better to use a timer even if it results in less * accurate presentation, since it will save us having to set the * same cursor image over and over again. * * This is really not the way we're supposed to time any kind of * animation, but we're pretending it's OK here because we don't * want animated cursors with long delays to needlessly hog CPU. * * We use force_frame to ensure we don't accumulate large timing * errors by running off the wrong clock. */ if (!force_frame && duration > 100) { struct timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); input->cursor_timer_start = tp.tv_sec * 1000 + tp.tv_nsec / 1000000; cursor_delay_timer_reset(input, duration); return; } /* for short durations we'll just spin on frame callbacks for * accurate timing - the way any kind of timing sensitive animation * should really be done. */ input->cursor_frame_cb = wl_surface_frame(input->pointer_surface); wl_callback_add_listener(input->cursor_frame_cb, &pointer_surface_listener, input); } static void pointer_surface_frame_callback(void *data, struct wl_callback *callback, uint32_t time) { struct input *input = data; struct wl_cursor *cursor; int i; uint32_t duration; bool force_frame = true; cancel_pointer_image_update(input); if (callback) { assert(callback == input->cursor_frame_cb); wl_callback_destroy(callback); input->cursor_frame_cb = NULL; force_frame = false; } if (!input->pointer) return; if (input_set_pointer_special(input)) return; cursor = input->display->cursors[input->current_cursor]; if (!cursor) return; /* FIXME We don't have the current time on the first call so we set * the animation start to the time of the first frame callback. */ if (time == 0) input->cursor_anim_start = 0; else if (input->cursor_anim_start == 0) input->cursor_anim_start = time; input->cursor_anim_current = time; if (time == 0 || input->cursor_anim_start == 0) { duration = 0; i = 0; } else i = wl_cursor_frame_and_duration( cursor, time - input->cursor_anim_start, &duration); if (cursor->image_count > 1) schedule_pointer_image_update(input, cursor, duration, force_frame); input_set_pointer_image_index(input, i); } static void cursor_timer_func(struct toytimer *tt) { struct input *input = container_of(tt, struct input, cursor_timer); struct timespec tp; struct wl_cursor *cursor; uint32_t time; if (!input->cursor_timer_running) return; cursor = input->display->cursors[input->current_cursor]; if (!cursor) return; clock_gettime(CLOCK_MONOTONIC, &tp); time = tp.tv_sec * 1000 + tp.tv_nsec / 1000000 - input->cursor_timer_start; pointer_surface_frame_callback(input, NULL, input->cursor_anim_current + time); } static const struct wl_callback_listener pointer_surface_listener = { pointer_surface_frame_callback }; void input_set_pointer_image(struct input *input, int pointer) { int force = 0; if (!input->pointer) return; if (input->pointer_enter_serial > input->cursor_serial) force = 1; if (!force && pointer == input->current_cursor) return; input->current_cursor = pointer; input->cursor_serial = input->pointer_enter_serial; if (!input->cursor_frame_cb) pointer_surface_frame_callback(input, NULL, 0); else if (force && !input_set_pointer_special(input)) { /* The current frame callback may be stuck if, for instance, * the set cursor request was processed by the server after * this client lost the focus. In this case the cursor surface * might not be mapped and the frame callback wouldn't ever * complete. Send a set_cursor and attach to try to map the * cursor surface again so that the callback will finish */ input_set_pointer_image_index(input, 0); } } struct wl_data_device * input_get_data_device(struct input *input) { return input->data_device; } void input_set_selection(struct input *input, struct wl_data_source *source, uint32_t time) { if (input->data_device) wl_data_device_set_selection(input->data_device, source, time); } void input_accept(struct input *input, const char *type) { wl_data_offer_accept(input->drag_offer->offer, input->drag_enter_serial, type); } static void offer_io_func(struct task *task, uint32_t events) { struct data_offer *offer = container_of(task, struct data_offer, io_task); struct display *display = offer->input->display; unsigned int len; char buffer[4096]; len = read(offer->fd, buffer, sizeof buffer); offer->func(buffer, len, offer->x, offer->y, offer->user_data); if (len == 0) { if ((offer != offer->input->selection_offer) && (display->data_device_manager_version >= WL_DATA_OFFER_FINISH_SINCE_VERSION)) wl_data_offer_finish(offer->offer); close(offer->fd); data_offer_destroy(offer); } } static void data_offer_receive_data(struct data_offer *offer, const char *mime_type, data_func_t func, void *user_data) { int p[2]; if (pipe2(p, O_CLOEXEC) == -1) return; wl_data_offer_receive(offer->offer, mime_type, p[1]); close(p[1]); offer->io_task.run = offer_io_func; offer->fd = p[0]; offer->func = func; offer->refcount++; offer->user_data = user_data; display_watch_fd(offer->input->display, offer->fd, EPOLLIN, &offer->io_task); } void input_receive_drag_data(struct input *input, const char *mime_type, data_func_t func, void *data) { data_offer_receive_data(input->drag_offer, mime_type, func, data); input->drag_offer->x = input->drag_x; input->drag_offer->y = input->drag_y; } int input_receive_drag_data_to_fd(struct input *input, const char *mime_type, int fd) { if (input->drag_offer) wl_data_offer_receive(input->drag_offer->offer, mime_type, fd); return 0; } int input_receive_selection_data(struct input *input, const char *mime_type, data_func_t func, void *data) { char **p; if (input->selection_offer == NULL) return -1; for (p = input->selection_offer->types.data; *p; p++) if (strcmp(mime_type, *p) == 0) break; if (*p == NULL) return -1; data_offer_receive_data(input->selection_offer, mime_type, func, data); return 0; } int input_receive_selection_data_to_fd(struct input *input, const char *mime_type, int fd) { if (input->selection_offer) wl_data_offer_receive(input->selection_offer->offer, mime_type, fd); return 0; } void window_move(struct window *window, struct input *input, uint32_t serial) { if (!window->xdg_toplevel) return; xdg_toplevel_move(window->xdg_toplevel, input->seat, serial); } static void surface_set_synchronized(struct surface *surface) { if (!surface->subsurface) return; if (surface->synchronized) return; wl_subsurface_set_sync(surface->subsurface); surface->synchronized = 1; } static void surface_set_synchronized_default(struct surface *surface) { if (!surface->subsurface) return; if (surface->synchronized == surface->synchronized_default) return; if (surface->synchronized_default) wl_subsurface_set_sync(surface->subsurface); else wl_subsurface_set_desync(surface->subsurface); surface->synchronized = surface->synchronized_default; } static void surface_resize(struct surface *surface) { struct widget *widget = surface->widget; struct wl_compositor *compositor = widget->window->display->compositor; if (surface->input_region) { wl_region_destroy(surface->input_region); surface->input_region = NULL; } if (surface->opaque_region) wl_region_destroy(surface->opaque_region); surface->opaque_region = wl_compositor_create_region(compositor); if (widget->resize_handler) widget->resize_handler(widget, widget->allocation.width, widget->allocation.height, widget->user_data); if (surface->subsurface && (surface->allocation.x != widget->allocation.x || surface->allocation.y != widget->allocation.y)) { wl_subsurface_set_position(surface->subsurface, widget->allocation.x, widget->allocation.y); } if (surface->allocation.width != widget->allocation.width || surface->allocation.height != widget->allocation.height) { window_schedule_redraw(widget->window); } surface->allocation = widget->allocation; if (widget->opaque) wl_region_add(surface->opaque_region, 0, 0, widget->allocation.width, widget->allocation.height); } static void window_do_resize(struct window *window) { struct surface *surface; widget_set_allocation(window->main_surface->widget, window->pending_allocation.x, window->pending_allocation.y, window->pending_allocation.width, window->pending_allocation.height); surface_resize(window->main_surface); /* The main surface is in the list, too. Main surface's * resize_handler is responsible for calling widget_set_allocation() * on all sub-surface root widgets, so they will be resized * properly. */ wl_list_for_each(surface, &window->subsurface_list, link) { if (surface == window->main_surface) continue; surface_set_synchronized(surface); surface_resize(surface); } if (!window->fullscreen && !window->maximized) window->saved_allocation = window->pending_allocation; if (window->confined && window->confined_widget) { struct wl_compositor *compositor = window->display->compositor; struct wl_region *region; struct widget *widget = window->confined_widget; region = wl_compositor_create_region(compositor); wl_region_add(region, widget->allocation.x, widget->allocation.y, widget->allocation.width, widget->allocation.height); zwp_confined_pointer_v1_set_region(window->confined_pointer, region); wl_region_destroy(region); } } static void idle_resize(struct window *window) { window->resize_needed = 0; window->redraw_needed = 1; DBG("from %dx%d to %dx%d\n", window->main_surface->server_allocation.width, window->main_surface->server_allocation.height, window->pending_allocation.width, window->pending_allocation.height); window_do_resize(window); } static void undo_resize(struct window *window) { window->pending_allocation.width = window->main_surface->server_allocation.width; window->pending_allocation.height = window->main_surface->server_allocation.height; DBG("back to %dx%d\n", window->main_surface->server_allocation.width, window->main_surface->server_allocation.height); window_do_resize(window); if (window->pending_allocation.width == 0 && window->pending_allocation.height == 0) { fprintf(stderr, "Error: Could not draw a surface, " "most likely due to insufficient disk space in " "%s (XDG_RUNTIME_DIR).\n", getenv("XDG_RUNTIME_DIR")); exit(EXIT_FAILURE); } } void window_schedule_resize(struct window *window, int width, int height) { /* We should probably get these numbers from the theme. */ const int min_width = 200, min_height = 200; window->pending_allocation.x = 0; window->pending_allocation.y = 0; window->pending_allocation.width = width; window->pending_allocation.height = height; if (window->min_allocation.width == 0) { if (width < min_width && window->frame) window->min_allocation.width = min_width; else window->min_allocation.width = width; if (height < min_height && window->frame) window->min_allocation.height = min_height; else window->min_allocation.height = height; } if (window->pending_allocation.width < window->min_allocation.width) window->pending_allocation.width = window->min_allocation.width; if (window->pending_allocation.height < window->min_allocation.height) window->pending_allocation.height = window->min_allocation.height; window->resize_needed = 1; window_schedule_redraw(window); } void widget_schedule_resize(struct widget *widget, int32_t width, int32_t height) { window_schedule_resize(widget->window, width, height); } static int window_get_shadow_margin(struct window *window) { if (window->frame && !window->fullscreen) return frame_get_shadow_margin(window->frame->frame); else return 0; } void window_inhibit_redraw(struct window *window) { window->redraw_inhibited = 1; wl_list_remove(&window->redraw_task.link); wl_list_init(&window->redraw_task.link); window->redraw_task_scheduled = 0; } void window_uninhibit_redraw(struct window *window) { window->redraw_inhibited = 0; if (window->redraw_needed || window->resize_needed) window_schedule_redraw_task(window); } static void xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { struct window *window = data; xdg_surface_ack_configure(window->xdg_surface, serial); if (window->state_changed_handler) window->state_changed_handler(window, window->user_data); window_uninhibit_redraw(window); } static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure }; static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) { struct window *window = data; uint32_t *p; window->maximized = 0; window->fullscreen = 0; window->resizing = 0; window->focused = 0; wl_array_for_each(p, states) { uint32_t state = *p; switch (state) { case XDG_TOPLEVEL_STATE_MAXIMIZED: window->maximized = 1; break; case XDG_TOPLEVEL_STATE_FULLSCREEN: window->fullscreen = 1; break; case XDG_TOPLEVEL_STATE_RESIZING: window->resizing = 1; break; case XDG_TOPLEVEL_STATE_ACTIVATED: window->focused = 1; break; default: /* Unknown state */ break; } } if (window->frame) { if (window->maximized) { frame_set_flag(window->frame->frame, FRAME_FLAG_MAXIMIZED); } else { frame_unset_flag(window->frame->frame, FRAME_FLAG_MAXIMIZED); } if (window->focused) { frame_set_flag(window->frame->frame, FRAME_FLAG_ACTIVE); } else { frame_unset_flag(window->frame->frame, FRAME_FLAG_ACTIVE); } } if (width > 0 && height > 0) { /* The width / height params are for window geometry, * but window_schedule_resize takes allocation. Add * on the shadow margin to get the difference. */ int margin = window_get_shadow_margin(window); window_schedule_resize(window, width + margin * 2, height + margin * 2); } else if (window->saved_allocation.width > 0 && window->saved_allocation.height > 0) { window_schedule_resize(window, window->saved_allocation.width, window->saved_allocation.height); } } static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_surface) { struct window *window = data; window_close(window); } static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; static void window_sync_parent(struct window *window) { struct xdg_toplevel *parent_toplevel; if (!window->xdg_surface) return; if (window->parent == window->last_parent) return; if (window->parent) parent_toplevel = window->parent->xdg_toplevel; else parent_toplevel = NULL; xdg_toplevel_set_parent(window->xdg_toplevel, parent_toplevel); window->last_parent = window->parent; } static void window_get_geometry(struct window *window, struct rectangle *geometry) { if (window->frame && !window->fullscreen) frame_input_rect(window->frame->frame, &geometry->x, &geometry->y, &geometry->width, &geometry->height); else window_get_allocation(window, geometry); } static void window_sync_geometry(struct window *window) { struct rectangle geometry; if (!window->xdg_surface) return; window_get_geometry(window, &geometry); if (geometry.x == window->last_geometry.x && geometry.y == window->last_geometry.y && geometry.width == window->last_geometry.width && geometry.height == window->last_geometry.height) return; xdg_surface_set_window_geometry(window->xdg_surface, geometry.x, geometry.y, geometry.width, geometry.height); window->last_geometry = geometry; } static void window_flush(struct window *window) { struct surface *surface; assert(!window->redraw_inhibited); if (!window->custom) { if (window->xdg_surface) window_sync_geometry(window); if (window->xdg_toplevel) window_sync_parent(window); } wl_list_for_each(surface, &window->subsurface_list, link) { if (surface == window->main_surface) continue; surface_flush(surface); } surface_flush(window->main_surface); } static void menu_destroy(struct menu *menu) { widget_destroy(menu->widget); window_destroy(menu->window); frame_destroy(menu->frame); free(menu); } void window_get_allocation(struct window *window, struct rectangle *allocation) { *allocation = window->main_surface->allocation; } static void widget_redraw(struct widget *widget) { struct widget *child; if (widget->redraw_handler) widget->redraw_handler(widget, widget->user_data); wl_list_for_each(child, &widget->child_list, link) widget_redraw(child); } static void frame_callback(void *data, struct wl_callback *callback, uint32_t time) { struct surface *surface = data; assert(callback == surface->frame_cb); DBG_OBJ(callback, "done\n"); wl_callback_destroy(callback); surface->frame_cb = NULL; surface->last_time = time; if (surface->redraw_needed || surface->window->redraw_needed) { DBG_OBJ(surface->surface, "window_schedule_redraw_task\n"); window_schedule_redraw_task(surface->window); } } static const struct wl_callback_listener listener = { frame_callback }; static int surface_redraw(struct surface *surface) { DBG_OBJ(surface->surface, "begin\n"); if (!surface->window->redraw_needed && !surface->redraw_needed) return 0; /* Whole-window redraw forces a redraw even if the previous has * not yet hit the screen. */ if (surface->frame_cb) { if (!surface->window->redraw_needed) return 0; DBG_OBJ(surface->frame_cb, "cancelled\n"); wl_callback_destroy(surface->frame_cb); } if (surface->widget->use_cairo && !widget_get_cairo_surface(surface->widget)) { DBG_OBJ(surface->surface, "cancelled due to buffer failure\n"); return -1; } surface->frame_cb = wl_surface_frame(surface->surface); wl_callback_add_listener(surface->frame_cb, &listener, surface); DBG_OBJ(surface->frame_cb, "new\n"); surface->redraw_needed = 0; DBG_OBJ(surface->surface, "-> widget_redraw\n"); widget_redraw(surface->widget); DBG_OBJ(surface->surface, "done\n"); return 0; } static void idle_redraw(struct task *task, uint32_t events) { struct window *window = container_of(task, struct window, redraw_task); struct surface *surface; int failed = 0; int resized = 0; DBG(" --------- \n"); wl_list_init(&window->redraw_task.link); window->redraw_task_scheduled = 0; if (window->resize_needed) { /* throttle resizing to the main surface display */ if (window->main_surface->frame_cb) { DBG_OBJ(window->main_surface->frame_cb, "pending\n"); return; } idle_resize(window); resized = 1; } if (surface_redraw(window->main_surface) < 0) { /* * Only main_surface failure will cause us to undo the resize. * If sub-surfaces fail, they will just be broken with old * content. */ failed = 1; } else { wl_list_for_each(surface, &window->subsurface_list, link) { if (surface == window->main_surface) continue; surface_redraw(surface); } } window->redraw_needed = 0; window_flush(window); wl_list_for_each(surface, &window->subsurface_list, link) surface_set_synchronized_default(surface); if (resized && failed) { /* Restore widget tree to correspond to what is on screen. */ undo_resize(window); } } static void window_schedule_redraw_task(struct window *window) { if (window->redraw_inhibited) return; if (!window->redraw_task_scheduled) { window->redraw_task.run = idle_redraw; display_defer(window->display, &window->redraw_task); window->redraw_task_scheduled = 1; } } void window_schedule_redraw(struct window *window) { struct surface *surface; DBG_OBJ(window->main_surface->surface, "window %p\n", window); wl_list_for_each(surface, &window->subsurface_list, link) surface->redraw_needed = 1; window_schedule_redraw_task(window); } int window_is_fullscreen(struct window *window) { return window->fullscreen; } void window_set_fullscreen(struct window *window, int fullscreen) { if (!window->xdg_toplevel) return; if (window->fullscreen == fullscreen) return; if (fullscreen) xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); else xdg_toplevel_unset_fullscreen(window->xdg_toplevel); } int window_is_maximized(struct window *window) { return window->maximized; } void window_set_maximized(struct window *window, int maximized) { if (!window->xdg_toplevel) return; if (window->maximized == maximized) return; if (maximized) xdg_toplevel_set_maximized(window->xdg_toplevel); else xdg_toplevel_unset_maximized(window->xdg_toplevel); } int window_is_resizing(struct window *window) { return window->resizing; } void window_set_minimized(struct window *window) { if (!window->xdg_toplevel) return; xdg_toplevel_set_minimized(window->xdg_toplevel); } void window_set_user_data(struct window *window, void *data) { window->user_data = data; } void * window_get_user_data(struct window *window) { return window->user_data; } void window_set_key_handler(struct window *window, window_key_handler_t handler) { window->key_handler = handler; } void window_set_keyboard_focus_handler(struct window *window, window_keyboard_focus_handler_t handler) { window->keyboard_focus_handler = handler; } void window_set_data_handler(struct window *window, window_data_handler_t handler) { window->data_handler = handler; } void window_set_drop_handler(struct window *window, window_drop_handler_t handler) { window->drop_handler = handler; } void window_set_close_handler(struct window *window, window_close_handler_t handler) { window->close_handler = handler; } void window_set_fullscreen_handler(struct window *window, window_fullscreen_handler_t handler) { window->fullscreen_handler = handler; } void window_set_output_handler(struct window *window, window_output_handler_t handler) { window->output_handler = handler; } void window_set_state_changed_handler(struct window *window, window_state_changed_handler_t handler) { window->state_changed_handler = handler; } void window_set_pointer_locked_handler(struct window *window, locked_pointer_locked_handler_t locked, locked_pointer_unlocked_handler_t unlocked) { window->pointer_unlocked_handler = unlocked; window->pointer_locked_handler = locked; } void window_set_pointer_confined_handler(struct window *window, confined_pointer_confined_handler_t confined, confined_pointer_unconfined_handler_t unconfined) { window->pointer_confined_handler = confined; window->pointer_unconfined_handler = unconfined; } void window_set_locked_pointer_motion_handler(struct window *window, window_locked_pointer_motion_handler_t handler) { window->locked_pointer_motion_handler = handler; } void window_set_title(struct window *window, const char *title) { free(window->title); window->title = strdup(title); if (window->frame) { frame_set_title(window->frame->frame, title); widget_schedule_redraw(window->frame->widget); } if (window->xdg_toplevel) xdg_toplevel_set_title(window->xdg_toplevel, title); } const char * window_get_title(struct window *window) { return window->title; } void window_set_text_cursor_position(struct window *window, int32_t x, int32_t y) { struct text_cursor_position *text_cursor_position = window->display->text_cursor_position; if (!text_cursor_position) return; text_cursor_position_notify(text_cursor_position, window->main_surface->surface, wl_fixed_from_int(x), wl_fixed_from_int(y)); } static void relative_pointer_handle_motion(void *data, struct zwp_relative_pointer_v1 *pointer, uint32_t utime_hi, uint32_t utime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) { struct input *input = data; struct window *window = input->pointer_focus; uint32_t ms = (((uint64_t) utime_hi) << 32 | utime_lo) / 1000; if (window->locked_pointer_motion_handler && window->pointer_locked) { window->locked_pointer_motion_handler( window, input, ms, wl_fixed_to_double(dx), wl_fixed_to_double(dy), window->user_data); } } static const struct zwp_relative_pointer_v1_listener relative_pointer_listener = { relative_pointer_handle_motion, }; static void locked_pointer_locked(void *data, struct zwp_locked_pointer_v1 *locked_pointer) { struct input *input = data; struct window *window = input->locked_window; if (!window) return; window->pointer_locked = true; if (window->pointer_locked_handler) { window->pointer_locked_handler(window, input, window->user_data); } } static void locked_pointer_unlocked(void *data, struct zwp_locked_pointer_v1 *locked_pointer) { struct input *input = data; struct window *window = input->locked_window; if (!window) return; window_unlock_pointer(window); input->locked_window = NULL; if (window->pointer_unlocked_handler) { window->pointer_unlocked_handler(window, input, window->user_data); } } static const struct zwp_locked_pointer_v1_listener locked_pointer_listener = { locked_pointer_locked, locked_pointer_unlocked, }; int window_lock_pointer(struct window *window, struct input *input) { struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = window->display->relative_pointer_manager; struct zwp_pointer_constraints_v1 *pointer_constraints = window->display->pointer_constraints; struct zwp_relative_pointer_v1 *relative_pointer; struct zwp_locked_pointer_v1 *locked_pointer; if (!window->display->relative_pointer_manager) return -1; if (!window->display->pointer_constraints) return -1; if (window->locked_pointer) return -1; if (window->confined_pointer) return -1; if (!input->pointer) return -1; relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer( relative_pointer_manager, input->pointer); zwp_relative_pointer_v1_add_listener(relative_pointer, &relative_pointer_listener, input); locked_pointer = zwp_pointer_constraints_v1_lock_pointer(pointer_constraints, window->main_surface->surface, input->pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); zwp_locked_pointer_v1_add_listener(locked_pointer, &locked_pointer_listener, input); window->locked_pointer = locked_pointer; window->relative_pointer = relative_pointer; input->locked_window = window; return 0; } void window_unlock_pointer(struct window *window) { if (!window->locked_pointer) return; zwp_locked_pointer_v1_destroy(window->locked_pointer); zwp_relative_pointer_v1_destroy(window->relative_pointer); window->locked_pointer = NULL; window->relative_pointer = NULL; window->pointer_locked = false; } void widget_set_locked_pointer_cursor_hint(struct widget *widget, float x, float y) { struct window *window = widget->window; if (!window->locked_pointer) return; zwp_locked_pointer_v1_set_cursor_position_hint(window->locked_pointer, wl_fixed_from_double(x), wl_fixed_from_double(y)); wl_surface_commit(window->main_surface->surface); } static void confined_pointer_confined(void *data, struct zwp_confined_pointer_v1 *confined_pointer) { struct input *input = data; struct window *window = input->confined_window; if (!window) return; window->confined = true; if (window->pointer_confined_handler) { window->pointer_confined_handler(window, input, window->user_data); } } static void confined_pointer_unconfined(void *data, struct zwp_confined_pointer_v1 *confined_pointer) { struct input *input = data; struct window *window = input->confined_window; if (!window) return; window_unconfine_pointer(window); window->confined = false; input->confined_window = NULL; if (window->pointer_unconfined_handler) { window->pointer_unconfined_handler(window, input, window->user_data); } } static const struct zwp_confined_pointer_v1_listener confined_pointer_listener = { confined_pointer_confined, confined_pointer_unconfined, }; int window_confine_pointer_to_rectangles(struct window *window, struct input *input, struct rectangle *rectangles, int num_rectangles) { struct zwp_pointer_constraints_v1 *pointer_constraints = window->display->pointer_constraints; struct zwp_confined_pointer_v1 *confined_pointer; struct wl_compositor *compositor = window->display->compositor; struct wl_region *region = NULL; int i; if (!window->display->pointer_constraints) return -1; if (window->locked_pointer) return -1; if (window->confined_pointer) return -1; if (!input->pointer) return -1; if (num_rectangles >= 1) { region = wl_compositor_create_region(compositor); for (i = 0; i < num_rectangles; i++) { wl_region_add(region, rectangles[i].x, rectangles[i].y, rectangles[i].width, rectangles[i].height); } } confined_pointer = zwp_pointer_constraints_v1_confine_pointer(pointer_constraints, window->main_surface->surface, input->pointer, region, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); if (region) wl_region_destroy(region); zwp_confined_pointer_v1_add_listener(confined_pointer, &confined_pointer_listener, input); window->confined_pointer = confined_pointer; window->confined_widget = NULL; input->confined_window = window; return 0; } void window_update_confine_rectangles(struct window *window, struct rectangle *rectangles, int num_rectangles) { struct wl_compositor *compositor = window->display->compositor; struct wl_region *region; int i; region = wl_compositor_create_region(compositor); for (i = 0; i < num_rectangles; i++) { wl_region_add(region, rectangles[i].x, rectangles[i].y, rectangles[i].width, rectangles[i].height); } zwp_confined_pointer_v1_set_region(window->confined_pointer, region); wl_region_destroy(region); } int window_confine_pointer_to_widget(struct window *window, struct widget *widget, struct input *input) { int ret; if (widget) { ret = window_confine_pointer_to_rectangles(window, input, &widget->allocation, 1); window->confined_widget = widget; return ret; } else { return window_confine_pointer_to_rectangles(window, input, NULL, 0); } } void window_unconfine_pointer(struct window *window) { if (!window->confined_pointer) return; zwp_confined_pointer_v1_destroy(window->confined_pointer); window->confined_pointer = NULL; window->confined = false; } static void surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct window *window = data; struct output *output; struct output *output_found = NULL; struct window_output *window_output; wl_list_for_each(output, &window->display->output_list, link) { if (output->output == wl_output) { output_found = output; break; } } if (!output_found) return; window_output = xmalloc(sizeof *window_output); window_output->output = output_found; wl_list_insert (&window->window_output_list, &window_output->link); if (window->output_handler) window->output_handler(window, output_found, 1, window->user_data); } static void surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *output) { struct window *window = data; struct window_output *window_output; struct window_output *window_output_found = NULL; wl_list_for_each(window_output, &window->window_output_list, link) { if (window_output->output->output == output) { window_output_found = window_output; break; } } if (window_output_found) { wl_list_remove(&window_output_found->link); if (window->output_handler) window->output_handler(window, window_output->output, 0, window->user_data); free(window_output_found); } } static const struct wl_surface_listener surface_listener = { surface_enter, surface_leave }; static struct surface * surface_create(struct window *window) { struct display *display = window->display; struct surface *surface; surface = xzalloc(sizeof *surface); surface->window = window; surface->surface = wl_compositor_create_surface(display->compositor); surface->buffer_scale = 1; wl_surface_add_listener(surface->surface, &surface_listener, window); wl_list_insert(&window->subsurface_list, &surface->link); surface->viewport = NULL; return surface; } static enum window_buffer_type get_preferred_buffer_type(struct display *display) { #ifdef HAVE_CAIRO_EGL if (display->argb_device && !getenv("TOYTOOLKIT_NO_EGL")) return WINDOW_BUFFER_TYPE_EGL_WINDOW; #endif return WINDOW_BUFFER_TYPE_SHM; } static struct window * window_create_internal(struct display *display, int custom) { struct window *window; struct surface *surface; window = xzalloc(sizeof *window); wl_list_init(&window->subsurface_list); window->display = display; surface = surface_create(window); window->main_surface = surface; assert(custom || display->xdg_shell); window->custom = custom; surface->buffer_type = get_preferred_buffer_type(display); wl_surface_set_user_data(surface->surface, window); wl_list_insert(display->window_list.prev, &window->link); wl_list_init(&window->redraw_task.link); wl_list_init (&window->window_output_list); return window; } struct window * window_create(struct display *display) { struct window *window; window = window_create_internal(display, 0); if (window->display->xdg_shell) { window->xdg_surface = xdg_wm_base_get_xdg_surface(window->display->xdg_shell, window->main_surface->surface); fail_on_null(window->xdg_surface, 0, __FILE__, __LINE__); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); fail_on_null(window->xdg_toplevel, 0, __FILE__, __LINE__); xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); window_inhibit_redraw(window); wl_surface_commit(window->main_surface->surface); } return window; } struct window * window_create_custom(struct display *display) { return window_create_internal(display, 1); } void window_set_parent(struct window *window, struct window *parent_window) { window->parent = parent_window; window_sync_parent(window); } struct window * window_get_parent(struct window *window) { return window->parent; } static void menu_set_item(struct menu *menu, int sy) { int32_t x, y, width, height; int next; frame_interior(menu->frame, &x, &y, &width, &height); next = (sy - y) / 20; if (menu->current != next) { menu->current = next; widget_schedule_redraw(menu->widget); } } static int menu_motion_handler(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data) { struct menu *menu = data; if (widget == menu->widget) menu_set_item(data, y); return CURSOR_LEFT_PTR; } static int menu_enter_handler(struct widget *widget, struct input *input, float x, float y, void *data) { struct menu *menu = data; if (widget == menu->widget) menu_set_item(data, y); return CURSOR_LEFT_PTR; } static void menu_leave_handler(struct widget *widget, struct input *input, void *data) { struct menu *menu = data; if (widget == menu->widget) menu_set_item(data, -200); } static void menu_button_handler(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data) { struct menu *menu = data; if (state == WL_POINTER_BUTTON_STATE_RELEASED && (menu->release_count > 0 || time - menu->time > 500)) { /* Either release after press-drag-release or * click-motion-click. */ menu->func(menu->user_data, input, menu->current); input_ungrab(menu->input); menu_destroy(menu); } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { menu->release_count++; } } static void menu_touch_up_handler(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, void *data) { struct menu *menu = data; input_ungrab(input); menu_destroy(menu); } static void menu_redraw_handler(struct widget *widget, void *data) { cairo_t *cr; struct menu *menu = data; int32_t x, y, width, height, i; cr = widget_cairo_create(widget); frame_repaint(menu->frame, cr); frame_interior(menu->frame, &x, &y, &width, &height); theme_set_background_source(menu->window->display->theme, cr, THEME_FRAME_ACTIVE); cairo_rectangle(cr, x, y, width, height); cairo_fill(cr); cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 12); for (i = 0; i < menu->count; i++) { if (i == menu->current) { cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); cairo_rectangle(cr, x, y + i * 20, width, 20); cairo_fill(cr); cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_move_to(cr, x + 10, y + i * 20 + 16); cairo_show_text(cr, menu->entries[i]); } else { cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_move_to(cr, x + 10, y + i * 20 + 16); cairo_show_text(cr, menu->entries[i]); } } cairo_destroy(cr); } static void xdg_popup_handle_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) { } static void xdg_popup_handle_popup_done(void *data, struct xdg_popup *xdg_popup) { struct window *window = data; struct menu *menu = window->main_surface->widget->user_data; input_ungrab(menu->input); menu_destroy(menu); } static const struct xdg_popup_listener xdg_popup_listener = { xdg_popup_handle_configure, xdg_popup_handle_popup_done, }; static struct menu * create_menu(struct display *display, struct input *input, uint32_t time, menu_func_t func, const char **entries, int count, void *user_data) { struct window *window; struct menu *menu; menu = malloc(sizeof *menu); if (!menu) return NULL; window = window_create_internal(display, 0); if (!window) { free(menu); return NULL; } menu->window = window; menu->user_data = user_data; menu->widget = window_add_widget(menu->window, menu); menu->frame = frame_create(window->display->theme, 0, 0, FRAME_BUTTON_NONE, NULL, NULL); fail_on_null(menu->frame, 0, __FILE__, __LINE__); menu->entries = entries; menu->count = count; menu->release_count = 0; menu->current = -1; menu->time = time; menu->func = func; menu->input = input; input_ungrab(input); widget_set_redraw_handler(menu->widget, menu_redraw_handler); widget_set_enter_handler(menu->widget, menu_enter_handler); widget_set_leave_handler(menu->widget, menu_leave_handler); widget_set_motion_handler(menu->widget, menu_motion_handler); widget_set_button_handler(menu->widget, menu_button_handler); widget_set_touch_up_handler(menu->widget, menu_touch_up_handler); input_grab(input, menu->widget, 0); frame_resize_inside(menu->frame, 200, count * 20); frame_set_flag(menu->frame, FRAME_FLAG_ACTIVE); window_schedule_resize(window, frame_width(menu->frame), frame_height(menu->frame)); return menu; } static struct xdg_positioner * create_simple_positioner(struct display *display, int x, int y, int w, int h) { struct xdg_positioner *positioner; positioner = xdg_wm_base_create_positioner(display->xdg_shell); fail_on_null(positioner, 0, __FILE__, __LINE__); xdg_positioner_set_anchor_rect(positioner, x, y, 1, 1); xdg_positioner_set_size(positioner, w, h); xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); xdg_positioner_set_gravity(positioner, XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT); return positioner; } void window_show_menu(struct display *display, struct input *input, uint32_t time, struct window *parent, int32_t x, int32_t y, menu_func_t func, const char **entries, int count) { struct menu *menu; struct window *window; int32_t ix, iy; struct rectangle parent_geometry; struct xdg_positioner *positioner; menu = create_menu(display, input, time, func, entries, count, parent); if (menu == NULL) return; window = menu->window; window_set_buffer_scale (menu->window, window_get_buffer_scale (parent)); window_set_buffer_transform (menu->window, window_get_buffer_transform (parent)); window->x = x; window->y = y; frame_interior(menu->frame, &ix, &iy, NULL, NULL); window_get_geometry(parent, &parent_geometry); if (!display->xdg_shell) return; window->xdg_surface = xdg_wm_base_get_xdg_surface(display->xdg_shell, window->main_surface->surface); fail_on_null(window->xdg_surface, 0, __FILE__, __LINE__); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); positioner = create_simple_positioner(display, window->x - (ix + parent_geometry.x), window->y - (iy + parent_geometry.y), frame_width(menu->frame), frame_height(menu->frame)); window->xdg_popup = xdg_surface_get_popup(window->xdg_surface, parent->xdg_surface, positioner); fail_on_null(window->xdg_popup, 0, __FILE__, __LINE__); xdg_positioner_destroy(positioner); xdg_popup_grab(window->xdg_popup, input->seat, display_get_serial(window->display)); xdg_popup_add_listener(window->xdg_popup, &xdg_popup_listener, window); window_inhibit_redraw(window); wl_surface_commit(window->main_surface->surface); } void window_set_buffer_type(struct window *window, enum window_buffer_type type) { window->main_surface->buffer_type = type; } enum window_buffer_type window_get_buffer_type(struct window *window) { return window->main_surface->buffer_type; } struct widget * window_add_subsurface(struct window *window, void *data, enum subsurface_mode default_mode) { struct widget *widget; struct surface *surface; struct wl_surface *parent; struct wl_subcompositor *subcompo = window->display->subcompositor; surface = surface_create(window); surface->buffer_type = window_get_buffer_type(window); widget = widget_create(window, surface, data); wl_list_init(&widget->link); surface->widget = widget; parent = window->main_surface->surface; surface->subsurface = wl_subcompositor_get_subsurface(subcompo, surface->surface, parent); surface->synchronized = 1; switch (default_mode) { case SUBSURFACE_SYNCHRONIZED: surface->synchronized_default = 1; break; case SUBSURFACE_DESYNCHRONIZED: surface->synchronized_default = 0; break; default: assert(!"bad enum subsurface_mode"); } window->resize_needed = 1; window_schedule_redraw(window); return widget; } static void display_handle_geometry(void *data, struct wl_output *wl_output, int x, int y, int physical_width, int physical_height, int subpixel, const char *make, const char *model, int transform) { struct output *output = data; output->allocation.x = x; output->allocation.y = y; output->transform = transform; if (output->make) free(output->make); output->make = strdup(make); if (output->model) free(output->model); output->model = strdup(model); } static void display_handle_done(void *data, struct wl_output *wl_output) { } static void display_handle_scale(void *data, struct wl_output *wl_output, int32_t scale) { struct output *output = data; output->scale = scale; } static void display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int width, int height, int refresh) { struct output *output = data; struct display *display = output->display; if (flags & WL_OUTPUT_MODE_CURRENT) { output->allocation.width = width; output->allocation.height = height; if (display->output_configure_handler) (*display->output_configure_handler)( output, display->user_data); } } static const struct wl_output_listener output_listener = { display_handle_geometry, display_handle_mode, display_handle_done, display_handle_scale }; static void display_add_output(struct display *d, uint32_t id) { struct output *output; output = xzalloc(sizeof *output); output->display = d; output->scale = 1; output->output = wl_registry_bind(d->registry, id, &wl_output_interface, 2); output->server_output_id = id; wl_list_insert(d->output_list.prev, &output->link); wl_output_add_listener(output->output, &output_listener, output); } static void output_destroy(struct output *output) { if (output->destroy_handler) (*output->destroy_handler)(output, output->user_data); wl_output_destroy(output->output); wl_list_remove(&output->link); free(output); } static void display_destroy_output(struct display *d, uint32_t id) { struct output *output; wl_list_for_each(output, &d->output_list, link) { if (output->server_output_id == id) { output_destroy(output); break; } } } void display_set_global_handler(struct display *display, display_global_handler_t handler) { struct global *global; display->global_handler = handler; if (!handler) return; wl_list_for_each(global, &display->global_list, link) display->global_handler(display, global->name, global->interface, global->version, display->user_data); } void display_set_global_handler_remove(struct display *display, display_global_handler_t remove_handler) { display->global_handler_remove = remove_handler; if (!remove_handler) return; } void display_set_output_configure_handler(struct display *display, display_output_handler_t handler) { struct output *output; display->output_configure_handler = handler; if (!handler) return; wl_list_for_each(output, &display->output_list, link) { if (output->allocation.width == 0 && output->allocation.height == 0) continue; (*display->output_configure_handler)(output, display->user_data); } } void output_set_user_data(struct output *output, void *data) { output->user_data = data; } void * output_get_user_data(struct output *output) { return output->user_data; } void output_set_destroy_handler(struct output *output, display_output_handler_t handler) { output->destroy_handler = handler; /* FIXME: implement this, once we have way to remove outputs */ } void output_get_allocation(struct output *output, struct rectangle *base) { struct rectangle allocation = output->allocation; switch (output->transform) { case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_270: /* Swap width and height */ allocation.width = output->allocation.height; allocation.height = output->allocation.width; break; } *base = allocation; } struct wl_output * output_get_wl_output(struct output *output) { return output->output; } enum wl_output_transform output_get_transform(struct output *output) { return output->transform; } uint32_t output_get_scale(struct output *output) { return output->scale; } const char * output_get_make(struct output *output) { return output->make; } const char * output_get_model(struct output *output) { return output->model; } static void fini_xkb(struct input *input) { xkb_state_unref(input->xkb.state); xkb_keymap_unref(input->xkb.keymap); } static void display_add_input(struct display *d, uint32_t id, int display_seat_version) { struct input *input; int seat_version = MIN(display_seat_version, 7); input = xzalloc(sizeof *input); input->display = d; input->seat = wl_registry_bind(d->registry, id, &wl_seat_interface, seat_version); input->touch_focus = NULL; input->pointer_focus = NULL; input->keyboard_focus = NULL; input->seat_version = seat_version; wl_list_init(&input->touch_point_list); wl_list_insert(d->input_list.prev, &input->link); wl_seat_add_listener(input->seat, &seat_listener, input); wl_seat_set_user_data(input->seat, input); if (d->data_device_manager) { input->data_device = wl_data_device_manager_get_data_device(d->data_device_manager, input->seat); wl_data_device_add_listener(input->data_device, &data_device_listener, input); } input->pointer_surface = wl_compositor_create_surface(d->compositor); toytimer_init(&input->cursor_timer, CLOCK_MONOTONIC, d, cursor_timer_func); set_repeat_info(input, 40, 400); toytimer_init(&input->repeat_timer, CLOCK_MONOTONIC, d, keyboard_repeat_func); } static void display_add_data_device(struct display *d, uint32_t id, int ddm_version) { struct input *input; d->data_device_manager_version = MIN(ddm_version, 3); d->data_device_manager = wl_registry_bind(d->registry, id, &wl_data_device_manager_interface, d->data_device_manager_version); wl_list_for_each(input, &d->input_list, link) { if (!input->data_device) { input->data_device = wl_data_device_manager_get_data_device(d->data_device_manager, input->seat); wl_data_device_add_listener(input->data_device, &data_device_listener, input); } } } static void input_destroy(struct input *input) { input_remove_keyboard_focus(input); input_remove_pointer_focus(input); if (input->drag_offer) data_offer_destroy(input->drag_offer); if (input->selection_offer) data_offer_destroy(input->selection_offer); if (input->data_device) { if (input->display->data_device_manager_version >= 2) wl_data_device_release(input->data_device); else wl_data_device_destroy(input->data_device); } if (input->seat_version >= WL_POINTER_RELEASE_SINCE_VERSION) { if (input->touch) wl_touch_release(input->touch); if (input->pointer) wl_pointer_release(input->pointer); if (input->keyboard) wl_keyboard_release(input->keyboard); } else { if (input->touch) wl_touch_destroy(input->touch); if (input->pointer) wl_pointer_destroy(input->pointer); if (input->keyboard) wl_keyboard_destroy(input->keyboard); } fini_xkb(input); wl_surface_destroy(input->pointer_surface); wl_list_remove(&input->link); wl_seat_destroy(input->seat); toytimer_fini(&input->repeat_timer); toytimer_fini(&input->cursor_timer); free(input); } static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) { xdg_wm_base_pong(shell, serial); } static const struct xdg_wm_base_listener wm_base_listener = { xdg_wm_base_ping, }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct display *d = data; struct global *global; global = xmalloc(sizeof *global); global->name = id; global->interface = strdup(interface); global->version = version; wl_list_insert(d->global_list.prev, &global->link); if (strcmp(interface, "wl_compositor") == 0) { d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 3); } else if (strcmp(interface, "wl_output") == 0) { display_add_output(d, id); } else if (strcmp(interface, "wl_seat") == 0) { display_add_input(d, id, version); } else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0 && version == ZWP_RELATIVE_POINTER_MANAGER_V1_VERSION) { d->relative_pointer_manager = wl_registry_bind(registry, id, &zwp_relative_pointer_manager_v1_interface, 1); } else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0 && version == ZWP_POINTER_CONSTRAINTS_V1_VERSION) { d->pointer_constraints = wl_registry_bind(registry, id, &zwp_pointer_constraints_v1_interface, 1); } else if (strcmp(interface, "wl_shm") == 0) { d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); } else if (strcmp(interface, "wl_data_device_manager") == 0) { display_add_data_device(d, id, version); } else if (strcmp(interface, "xdg_wm_base") == 0) { d->xdg_shell = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(d->xdg_shell, &wm_base_listener, d); } else if (strcmp(interface, "text_cursor_position") == 0) { d->text_cursor_position = wl_registry_bind(registry, id, &text_cursor_position_interface, 1); } else if (strcmp(interface, "wl_subcompositor") == 0) { d->subcompositor = wl_registry_bind(registry, id, &wl_subcompositor_interface, 1); } else if (!strcmp(interface, "wp_viewporter")) { d->viewporter = wl_registry_bind(registry, id, &wp_viewporter_interface, 1); } if (d->global_handler) d->global_handler(d, id, interface, version, d->user_data); } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct display *d = data; struct global *global; struct global *tmp; wl_list_for_each_safe(global, tmp, &d->global_list, link) { if (global->name != name) continue; if (strcmp(global->interface, "wl_output") == 0) display_destroy_output(d, name); /* XXX: Should destroy remaining bound globals */ if (d->global_handler_remove) d->global_handler_remove(d, name, global->interface, global->version, d->user_data); wl_list_remove(&global->link); free(global->interface); free(global); } } void * display_bind(struct display *display, uint32_t name, const struct wl_interface *interface, uint32_t version) { return wl_registry_bind(display->registry, name, interface, version); } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; #ifdef HAVE_CAIRO_EGL static int init_egl(struct display *d) { EGLint major, minor; EGLint n; #ifdef USE_CAIRO_GLESV2 # define GL_BIT EGL_OPENGL_ES2_BIT #else # define GL_BIT EGL_OPENGL_BIT #endif static const EGLint argb_cfg_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 1, EGL_DEPTH_SIZE, 1, EGL_RENDERABLE_TYPE, GL_BIT, EGL_NONE }; #ifdef USE_CAIRO_GLESV2 static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; EGLint api = EGL_OPENGL_ES_API; #else EGLint *context_attribs = NULL; EGLint api = EGL_OPENGL_API; #endif d->dpy = weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, d->display, NULL); if (!eglInitialize(d->dpy, &major, &minor)) { fprintf(stderr, "failed to initialize EGL\n"); return -1; } if (!eglBindAPI(api)) { fprintf(stderr, "failed to bind EGL client API\n"); return -1; } if (!eglChooseConfig(d->dpy, argb_cfg_attribs, &d->argb_config, 1, &n) || n != 1) { fprintf(stderr, "failed to choose argb EGL config\n"); return -1; } d->argb_ctx = eglCreateContext(d->dpy, d->argb_config, EGL_NO_CONTEXT, context_attribs); if (d->argb_ctx == NULL) { fprintf(stderr, "failed to create EGL context\n"); return -1; } d->argb_device = cairo_egl_device_create(d->dpy, d->argb_ctx); if (cairo_device_status(d->argb_device) != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "failed to get cairo EGL argb device\n"); return -1; } return 0; } static void fini_egl(struct display *display) { cairo_device_destroy(display->argb_device); eglMakeCurrent(display->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglTerminate(display->dpy); eglReleaseThread(); } #endif static void init_dummy_surface(struct display *display) { int len; void *data; len = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, 1); data = xmalloc(len); display->dummy_surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, 1, 1, len); display->dummy_surface_data = data; } static void handle_display_data(struct task *task, uint32_t events) { struct display *display = container_of(task, struct display, display_task); struct epoll_event ep; int ret; display->display_fd_events = events; if (events & EPOLLERR || events & EPOLLHUP) { display_exit(display); return; } if (events & EPOLLIN) { ret = wl_display_dispatch(display->display); if (ret == -1) { display_exit(display); return; } } if (events & EPOLLOUT) { ret = wl_display_flush(display->display); if (ret == 0) { ep.events = EPOLLIN | EPOLLERR | EPOLLHUP; ep.data.ptr = &display->display_task; epoll_ctl(display->epoll_fd, EPOLL_CTL_MOD, display->display_fd, &ep); } else if (ret == -1 && errno != EAGAIN) { display_exit(display); return; } } } static void log_handler(const char *format, va_list args) { vfprintf(stderr, format, args); } struct display * display_create(int *argc, char *argv[]) { struct display *d; wl_log_set_handler_client(log_handler); d = zalloc(sizeof *d); if (d == NULL) return NULL; d->display = wl_display_connect(NULL); if (d->display == NULL) { fprintf(stderr, "failed to connect to Wayland display: %s\n", strerror(errno)); free(d); return NULL; } d->xkb_context = xkb_context_new(0); if (d->xkb_context == NULL) { fprintf(stderr, "Failed to create XKB context\n"); free(d); return NULL; } d->epoll_fd = os_epoll_create_cloexec(); d->display_fd = wl_display_get_fd(d->display); d->display_task.run = handle_display_data; display_watch_fd(d, d->display_fd, EPOLLIN | EPOLLERR | EPOLLHUP, &d->display_task); wl_list_init(&d->deferred_list); wl_list_init(&d->input_list); wl_list_init(&d->output_list); wl_list_init(&d->global_list); d->registry = wl_display_get_registry(d->display); wl_registry_add_listener(d->registry, ®istry_listener, d); if (wl_display_roundtrip(d->display) < 0) { fprintf(stderr, "Failed to process Wayland connection: %s\n", strerror(errno)); return NULL; } #ifdef HAVE_CAIRO_EGL if (init_egl(d) < 0) fprintf(stderr, "EGL does not seem to work, " "falling back to software rendering and wl_shm.\n"); #endif create_cursors(d); d->theme = theme_create(); wl_list_init(&d->window_list); init_dummy_surface(d); return d; } static void display_destroy_outputs(struct display *display) { struct output *tmp; struct output *output; wl_list_for_each_safe(output, tmp, &display->output_list, link) output_destroy(output); } static void display_destroy_inputs(struct display *display) { struct input *tmp; struct input *input; wl_list_for_each_safe(input, tmp, &display->input_list, link) input_destroy(input); } void display_destroy(struct display *display) { if (!wl_list_empty(&display->window_list)) fprintf(stderr, "toytoolkit warning: %d windows exist.\n", wl_list_length(&display->window_list)); if (!wl_list_empty(&display->deferred_list)) fprintf(stderr, "toytoolkit warning: deferred tasks exist.\n"); cairo_surface_destroy(display->dummy_surface); free(display->dummy_surface_data); display_destroy_outputs(display); display_destroy_inputs(display); xkb_context_unref(display->xkb_context); theme_destroy(display->theme); destroy_cursors(display); #ifdef HAVE_CAIRO_EGL if (display->argb_device) fini_egl(display); #endif if (display->subcompositor) wl_subcompositor_destroy(display->subcompositor); if (display->xdg_shell) xdg_wm_base_destroy(display->xdg_shell); if (display->shm) wl_shm_destroy(display->shm); if (display->data_device_manager) wl_data_device_manager_destroy(display->data_device_manager); wl_compositor_destroy(display->compositor); wl_registry_destroy(display->registry); close(display->epoll_fd); if (!(display->display_fd_events & EPOLLERR) && !(display->display_fd_events & EPOLLHUP)) wl_display_flush(display->display); wl_display_disconnect(display->display); free(display); } void display_set_user_data(struct display *display, void *data) { display->user_data = data; } void * display_get_user_data(struct display *display) { return display->user_data; } struct wl_display * display_get_display(struct display *display) { return display->display; } int display_has_subcompositor(struct display *display) { if (display->subcompositor) return 1; wl_display_roundtrip(display->display); return display->subcompositor != NULL; } cairo_device_t * display_get_cairo_device(struct display *display) { return display->argb_device; } struct output * display_get_output(struct display *display) { if (wl_list_empty(&display->output_list)) return NULL; return container_of(display->output_list.next, struct output, link); } struct wl_compositor * display_get_compositor(struct display *display) { return display->compositor; } uint32_t display_get_serial(struct display *display) { return display->serial; } EGLDisplay display_get_egl_display(struct display *d) { return d->dpy; } struct wl_data_source * display_create_data_source(struct display *display) { if (display->data_device_manager) return wl_data_device_manager_create_data_source(display->data_device_manager); else return NULL; } EGLConfig display_get_argb_egl_config(struct display *d) { return d->argb_config; } int display_acquire_window_surface(struct display *display, struct window *window, EGLContext ctx) { struct surface *surface = window->main_surface; if (surface->buffer_type != WINDOW_BUFFER_TYPE_EGL_WINDOW) return -1; widget_get_cairo_surface(window->main_surface->widget); return surface->toysurface->acquire(surface->toysurface, ctx); } void display_release_window_surface(struct display *display, struct window *window) { struct surface *surface = window->main_surface; if (surface->buffer_type != WINDOW_BUFFER_TYPE_EGL_WINDOW) return; surface->toysurface->release(surface->toysurface); } void display_defer(struct display *display, struct task *task) { wl_list_insert(&display->deferred_list, &task->link); } void display_watch_fd(struct display *display, int fd, uint32_t events, struct task *task) { struct epoll_event ep; ep.events = events; ep.data.ptr = task; epoll_ctl(display->epoll_fd, EPOLL_CTL_ADD, fd, &ep); } void display_unwatch_fd(struct display *display, int fd) { epoll_ctl(display->epoll_fd, EPOLL_CTL_DEL, fd, NULL); } void display_run(struct display *display) { struct task *task; struct epoll_event ep[16]; int i, count, ret; display->running = 1; while (1) { while (!wl_list_empty(&display->deferred_list)) { task = container_of(display->deferred_list.prev, struct task, link); wl_list_remove(&task->link); task->run(task, 0); } wl_display_dispatch_pending(display->display); if (!display->running) break; ret = wl_display_flush(display->display); if (ret < 0 && errno == EAGAIN) { ep[0].events = EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP; ep[0].data.ptr = &display->display_task; epoll_ctl(display->epoll_fd, EPOLL_CTL_MOD, display->display_fd, &ep[0]); } else if (ret < 0) { break; } count = epoll_wait(display->epoll_fd, ep, ARRAY_LENGTH(ep), -1); for (i = 0; i < count; i++) { task = ep[i].data.ptr; task->run(task, ep[i].events); } } } void display_exit(struct display *display) { display->running = 0; } int display_get_data_device_manager_version(struct display *display) { return display->data_device_manager_version; } void keysym_modifiers_add(struct wl_array *modifiers_map, const char *name) { size_t len = strlen(name) + 1; char *p; p = wl_array_add(modifiers_map, len); if (p == NULL) return; strncpy(p, name, len); } static xkb_mod_index_t keysym_modifiers_get_index(struct wl_array *modifiers_map, const char *name) { xkb_mod_index_t index = 0; char *p = modifiers_map->data; while ((const char *)p < (const char *)(modifiers_map->data + modifiers_map->size)) { if (strcmp(p, name) == 0) return index; index++; p += strlen(p) + 1; } return XKB_MOD_INVALID; } xkb_mod_mask_t keysym_modifiers_get_mask(struct wl_array *modifiers_map, const char *name) { xkb_mod_index_t index = keysym_modifiers_get_index(modifiers_map, name); if (index == XKB_MOD_INVALID) return XKB_MOD_INVALID; return 1 << index; } static void toytimer_fire(struct task *tsk, uint32_t events) { uint64_t e; struct toytimer *tt; tt = container_of(tsk, struct toytimer, tsk); if (events != EPOLLIN) fprintf(stderr, "unexpected timerfd events %x\n", events); if (!(events & EPOLLIN)) return; if (read(tt->fd, &e, sizeof e) != sizeof e) { /* If we change the timer between the fd becoming * readable and getting here, there'll be nothing to * read and we get EAGAIN. */ if (errno != EAGAIN) fprintf(stderr, "timer read failed: %s\n", strerror(errno)); return; } tt->callback(tt); } void toytimer_init(struct toytimer *tt, clockid_t clock, struct display *display, toytimer_cb callback) { memset(tt, 0, sizeof *tt); tt->fd = timerfd_create(clock, TFD_CLOEXEC | TFD_NONBLOCK); if (tt->fd == -1) { fprintf(stderr, "creating timer failed: %s\n", strerror(errno)); abort(); } tt->display = display; tt->callback = callback; tt->tsk.run = toytimer_fire; display_watch_fd(display, tt->fd, EPOLLIN, &tt->tsk); } void toytimer_fini(struct toytimer *tt) { display_unwatch_fd(tt->display, tt->fd); close(tt->fd); tt->fd = -1; } void toytimer_arm(struct toytimer *tt, const struct itimerspec *its) { int ret; ret = timerfd_settime(tt->fd, 0, its, NULL); if (ret < 0) { fprintf(stderr, "timer setup failed: %s\n", strerror(errno)); abort(); } } #define USEC_PER_SEC 1000000 void toytimer_arm_once_usec(struct toytimer *tt, uint32_t usec) { struct itimerspec its; its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 0; its.it_value.tv_sec = usec / USEC_PER_SEC; its.it_value.tv_nsec = (usec % USEC_PER_SEC) * 1000; toytimer_arm(tt, &its); } void toytimer_disarm(struct toytimer *tt) { struct itimerspec its = {}; toytimer_arm(tt, &its); } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3356297 weston-8.0.0/clients/window.h0000644000175000017460000004650400000000000016451 0ustar00simonwheel00000000000000/* * Copyright © 2008 Kristian Høgsberg * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #ifndef _WINDOW_H_ #define _WINDOW_H_ #include "config.h" #include #include #include #include #include #include #include #include "shared/platform.h" struct window; struct widget; struct display; struct input; struct output; struct task { void (*run)(struct task *task, uint32_t events); struct wl_list link; }; struct rectangle { int32_t x; int32_t y; int32_t width; int32_t height; }; struct display * display_create(int *argc, char *argv[]); void display_destroy(struct display *display); void display_set_user_data(struct display *display, void *data); void * display_get_user_data(struct display *display); struct wl_display * display_get_display(struct display *display); int display_has_subcompositor(struct display *display); cairo_device_t * display_get_cairo_device(struct display *display); struct wl_compositor * display_get_compositor(struct display *display); struct output * display_get_output(struct display *display); uint32_t display_get_serial(struct display *display); typedef void (*display_global_handler_t)(struct display *display, uint32_t name, const char *interface, uint32_t version, void *data); void display_set_global_handler(struct display *display, display_global_handler_t handler); void display_set_global_handler_remove(struct display *display, display_global_handler_t remove_handler); void * display_bind(struct display *display, uint32_t name, const struct wl_interface *interface, uint32_t version); typedef void (*display_output_handler_t)(struct output *output, void *data); /* * The output configure handler is called, when a new output is connected * and we know its current mode, or when the current mode changes. * Test and set the output user data in your handler to know, if the * output is new. Note: 'data' in the configure handler is the display * user data. */ void display_set_output_configure_handler(struct display *display, display_output_handler_t handler); struct wl_data_source * display_create_data_source(struct display *display); #ifdef EGL_NO_DISPLAY EGLDisplay display_get_egl_display(struct display *d); EGLConfig display_get_argb_egl_config(struct display *d); int display_acquire_window_surface(struct display *display, struct window *window, EGLContext ctx); void display_release_window_surface(struct display *display, struct window *window); #endif #define SURFACE_OPAQUE 0x01 #define SURFACE_SHM 0x02 #define SURFACE_HINT_RESIZE 0x10 cairo_surface_t * display_create_surface(struct display *display, struct wl_surface *surface, struct rectangle *rectangle, uint32_t flags); struct wl_buffer * display_get_buffer_for_surface(struct display *display, cairo_surface_t *surface); struct wl_cursor_image * display_get_pointer_image(struct display *display, int pointer); void display_defer(struct display *display, struct task *task); void display_watch_fd(struct display *display, int fd, uint32_t events, struct task *task); void display_unwatch_fd(struct display *display, int fd); void display_run(struct display *d); void display_exit(struct display *d); int display_get_data_device_manager_version(struct display *d); enum cursor_type { CURSOR_BOTTOM_LEFT, CURSOR_BOTTOM_RIGHT, CURSOR_BOTTOM, CURSOR_DRAGGING, CURSOR_LEFT_PTR, CURSOR_LEFT, CURSOR_RIGHT, CURSOR_TOP_LEFT, CURSOR_TOP_RIGHT, CURSOR_TOP, CURSOR_IBEAM, CURSOR_HAND1, CURSOR_WATCH, CURSOR_DND_MOVE, CURSOR_DND_COPY, CURSOR_DND_FORBIDDEN, CURSOR_BLANK }; typedef void (*window_key_handler_t)(struct window *window, struct input *input, uint32_t time, uint32_t key, uint32_t unicode, enum wl_keyboard_key_state state, void *data); typedef void (*window_keyboard_focus_handler_t)(struct window *window, struct input *device, void *data); typedef void (*window_data_handler_t)(struct window *window, struct input *input, float x, float y, const char **types, void *data); typedef void (*window_drop_handler_t)(struct window *window, struct input *input, int32_t x, int32_t y, void *data); typedef void (*window_close_handler_t)(void *data); typedef void (*window_fullscreen_handler_t)(struct window *window, void *data); typedef void (*window_output_handler_t)(struct window *window, struct output *output, int enter, void *data); typedef void (*window_state_changed_handler_t)(struct window *window, void *data); typedef void (*window_locked_pointer_motion_handler_t)(struct window *window, struct input *input, uint32_t time, float x, float y, void *data); typedef void (*locked_pointer_locked_handler_t)(struct window *window, struct input *input, void *data); typedef void (*locked_pointer_unlocked_handler_t)(struct window *window, struct input *input, void *data); typedef void (*confined_pointer_confined_handler_t)(struct window *window, struct input *input, void *data); typedef void (*confined_pointer_unconfined_handler_t)(struct window *window, struct input *input, void *data); typedef void (*widget_resize_handler_t)(struct widget *widget, int32_t width, int32_t height, void *data); typedef void (*widget_redraw_handler_t)(struct widget *widget, void *data); typedef int (*widget_enter_handler_t)(struct widget *widget, struct input *input, float x, float y, void *data); typedef void (*widget_leave_handler_t)(struct widget *widget, struct input *input, void *data); typedef int (*widget_motion_handler_t)(struct widget *widget, struct input *input, uint32_t time, float x, float y, void *data); typedef void (*widget_button_handler_t)(struct widget *widget, struct input *input, uint32_t time, uint32_t button, enum wl_pointer_button_state state, void *data); typedef void (*widget_touch_down_handler_t)(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, float x, float y, void *data); typedef void (*widget_touch_up_handler_t)(struct widget *widget, struct input *input, uint32_t serial, uint32_t time, int32_t id, void *data); typedef void (*widget_touch_motion_handler_t)(struct widget *widget, struct input *input, uint32_t time, int32_t id, float x, float y, void *data); typedef void (*widget_touch_frame_handler_t)(struct widget *widget, struct input *input, void *data); typedef void (*widget_touch_cancel_handler_t)(struct widget *widget, struct input *input, void *data); typedef void (*widget_axis_handler_t)(struct widget *widget, struct input *input, uint32_t time, uint32_t axis, wl_fixed_t value, void *data); typedef void (*widget_pointer_frame_handler_t)(struct widget *widget, struct input *input, void *data); typedef void (*widget_axis_source_handler_t)(struct widget *widget, struct input *input, uint32_t source, void *data); typedef void (*widget_axis_stop_handler_t)(struct widget *widget, struct input *input, uint32_t time, uint32_t axis, void *data); typedef void (*widget_axis_discrete_handler_t)(struct widget *widget, struct input *input, uint32_t axis, int32_t discrete, void *data); struct window * window_create(struct display *display); struct window * window_create_custom(struct display *display); void window_set_parent(struct window *window, struct window *parent_window); struct window * window_get_parent(struct window *window); int window_has_focus(struct window *window); typedef void (*menu_func_t)(void *data, struct input *input, int index); void window_show_menu(struct display *display, struct input *input, uint32_t time, struct window *parent, int32_t x, int32_t y, menu_func_t func, const char **entries, int count); void window_show_frame_menu(struct window *window, struct input *input, uint32_t time); int window_get_buffer_transform(struct window *window); void window_set_buffer_transform(struct window *window, enum wl_output_transform transform); uint32_t window_get_buffer_scale(struct window *window); void window_set_buffer_scale(struct window *window, int32_t scale); uint32_t window_get_output_scale(struct window *window); void window_destroy(struct window *window); struct widget * window_add_widget(struct window *window, void *data); enum subsurface_mode { SUBSURFACE_SYNCHRONIZED, SUBSURFACE_DESYNCHRONIZED }; struct widget * window_add_subsurface(struct window *window, void *data, enum subsurface_mode default_mode); typedef void (*data_func_t)(void *data, size_t len, int32_t x, int32_t y, void *user_data); struct display * window_get_display(struct window *window); void window_move(struct window *window, struct input *input, uint32_t time); void window_get_allocation(struct window *window, struct rectangle *allocation); void window_schedule_redraw(struct window *window); void window_schedule_resize(struct window *window, int width, int height); int window_lock_pointer(struct window *window, struct input *input); void window_unlock_pointer(struct window *window); void widget_set_locked_pointer_cursor_hint(struct widget *widget, float x, float y); int window_confine_pointer_to_rectangles(struct window *window, struct input *input, struct rectangle *rectangles, int num_rectangles); void window_update_confine_rectangles(struct window *window, struct rectangle *rectangles, int num_rectangles); int window_confine_pointer_to_widget(struct window *window, struct widget *widget, struct input *input); void window_unconfine_pointer(struct window *window); cairo_surface_t * window_get_surface(struct window *window); struct wl_surface * window_get_wl_surface(struct window *window); struct wl_subsurface * widget_get_wl_subsurface(struct widget *widget); enum window_buffer_type { WINDOW_BUFFER_TYPE_EGL_WINDOW, WINDOW_BUFFER_TYPE_SHM, }; void display_surface_damage(struct display *display, cairo_surface_t *cairo_surface, int32_t x, int32_t y, int32_t width, int32_t height); void window_set_buffer_type(struct window *window, enum window_buffer_type type); enum window_buffer_type window_get_buffer_type(struct window *window); int window_is_fullscreen(struct window *window); void window_set_fullscreen(struct window *window, int fullscreen); int window_is_maximized(struct window *window); void window_set_maximized(struct window *window, int maximized); int window_is_resizing(struct window *window); void window_set_minimized(struct window *window); void window_set_user_data(struct window *window, void *data); void * window_get_user_data(struct window *window); void window_set_key_handler(struct window *window, window_key_handler_t handler); void window_set_keyboard_focus_handler(struct window *window, window_keyboard_focus_handler_t handler); void window_set_data_handler(struct window *window, window_data_handler_t handler); void window_set_drop_handler(struct window *window, window_drop_handler_t handler); void window_set_close_handler(struct window *window, window_close_handler_t handler); void window_set_fullscreen_handler(struct window *window, window_fullscreen_handler_t handler); void window_set_output_handler(struct window *window, window_output_handler_t handler); void window_set_state_changed_handler(struct window *window, window_state_changed_handler_t handler); void window_set_pointer_locked_handler(struct window *window, locked_pointer_locked_handler_t locked, locked_pointer_unlocked_handler_t unlocked); void window_set_pointer_confined_handler(struct window *window, confined_pointer_confined_handler_t confined, confined_pointer_unconfined_handler_t unconfined); void window_set_locked_pointer_motion_handler( struct window *window, window_locked_pointer_motion_handler_t handler); void window_set_title(struct window *window, const char *title); const char * window_get_title(struct window *window); void window_set_text_cursor_position(struct window *window, int32_t x, int32_t y); int widget_set_tooltip(struct widget *parent, char *entry, float x, float y); void widget_destroy_tooltip(struct widget *parent); struct widget * widget_add_widget(struct widget *parent, void *data); void widget_destroy(struct widget *widget); void widget_set_default_cursor(struct widget *widget, int cursor); void widget_get_allocation(struct widget *widget, struct rectangle *allocation); void widget_set_allocation(struct widget *widget, int32_t x, int32_t y, int32_t width, int32_t height); void widget_set_size(struct widget *widget, int32_t width, int32_t height); void widget_set_transparent(struct widget *widget, int transparent); void widget_schedule_resize(struct widget *widget, int32_t width, int32_t height); void * widget_get_user_data(struct widget *widget); cairo_t * widget_cairo_create(struct widget *widget); struct wl_surface * widget_get_wl_surface(struct widget *widget); uint32_t widget_get_last_time(struct widget *widget); void widget_input_region_add(struct widget *widget, const struct rectangle *rect); void widget_set_redraw_handler(struct widget *widget, widget_redraw_handler_t handler); void widget_set_resize_handler(struct widget *widget, widget_resize_handler_t handler); void widget_set_enter_handler(struct widget *widget, widget_enter_handler_t handler); void widget_set_leave_handler(struct widget *widget, widget_leave_handler_t handler); void widget_set_motion_handler(struct widget *widget, widget_motion_handler_t handler); void widget_set_button_handler(struct widget *widget, widget_button_handler_t handler); void widget_set_touch_down_handler(struct widget *widget, widget_touch_down_handler_t handler); void widget_set_touch_up_handler(struct widget *widget, widget_touch_up_handler_t handler); void widget_set_touch_motion_handler(struct widget *widget, widget_touch_motion_handler_t handler); void widget_set_touch_frame_handler(struct widget *widget, widget_touch_frame_handler_t handler); void widget_set_touch_cancel_handler(struct widget *widget, widget_touch_cancel_handler_t handler); void widget_set_axis_handler(struct widget *widget, widget_axis_handler_t handler); void widget_set_pointer_frame_handler(struct widget *widget, widget_pointer_frame_handler_t handler); void widget_set_axis_handlers(struct widget *widget, widget_axis_handler_t axis_handler, widget_axis_source_handler_t axis_source_handler, widget_axis_stop_handler_t axis_stop_handler, widget_axis_discrete_handler_t axis_discrete_handler); void window_inhibit_redraw(struct window *window); void window_uninhibit_redraw(struct window *window); void widget_schedule_redraw(struct widget *widget); void widget_set_use_cairo(struct widget *widget, int use_cairo); /* * Sets the viewport destination for the widget's surface * return 0 on success and -1 on failure. Set width and height to * -1 to reset the viewport. */ int widget_set_viewport_destination(struct widget *widget, int width, int height); struct widget * window_frame_create(struct window *window, void *data); void window_frame_set_child_size(struct widget *widget, int child_width, int child_height); void input_set_pointer_image(struct input *input, int pointer); void input_get_position(struct input *input, int32_t *x, int32_t *y); int input_get_touch(struct input *input, int32_t id, float *x, float *y); #define MOD_SHIFT_MASK 0x01 #define MOD_ALT_MASK 0x02 #define MOD_CONTROL_MASK 0x04 uint32_t input_get_modifiers(struct input *input); void touch_grab(struct input *input, int32_t touch_id); void touch_ungrab(struct input *input); void input_grab(struct input *input, struct widget *widget, uint32_t button); void input_ungrab(struct input *input); struct widget * input_get_focus_widget(struct input *input); struct display * input_get_display(struct input *input); struct wl_seat * input_get_seat(struct input *input); struct wl_data_device * input_get_data_device(struct input *input); void input_set_selection(struct input *input, struct wl_data_source *source, uint32_t time); void input_accept(struct input *input, const char *type); void input_receive_drag_data(struct input *input, const char *mime_type, data_func_t func, void *user_data); int input_receive_drag_data_to_fd(struct input *input, const char *mime_type, int fd); int input_receive_selection_data(struct input *input, const char *mime_type, data_func_t func, void *data); int input_receive_selection_data_to_fd(struct input *input, const char *mime_type, int fd); void output_set_user_data(struct output *output, void *data); void * output_get_user_data(struct output *output); void output_set_destroy_handler(struct output *output, display_output_handler_t handler); void output_get_allocation(struct output *output, struct rectangle *allocation); struct wl_output * output_get_wl_output(struct output *output); enum wl_output_transform output_get_transform(struct output *output); uint32_t output_get_scale(struct output *output); const char * output_get_make(struct output *output); const char * output_get_model(struct output *output); void keysym_modifiers_add(struct wl_array *modifiers_map, const char *name); xkb_mod_mask_t keysym_modifiers_get_mask(struct wl_array *modifiers_map, const char *name); struct toytimer; typedef void (*toytimer_cb)(struct toytimer *); struct toytimer { struct display *display; struct task tsk; int fd; toytimer_cb callback; }; void toytimer_init(struct toytimer *tt, clockid_t clock, struct display *display, toytimer_cb callback); void toytimer_fini(struct toytimer *tt); void toytimer_arm(struct toytimer *tt, const struct itimerspec *its); void toytimer_arm_once_usec(struct toytimer *tt, uint32_t usec); void toytimer_disarm(struct toytimer *tt); #endif ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.338963 weston-8.0.0/compositor/0000755000175000017460000000000000000000000015515 5ustar00simonwheel00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3356297 weston-8.0.0/compositor/cms-colord.c0000644000175000017460000003711200000000000017727 0ustar00simonwheel00000000000000/* * Copyright © 2013 Richard Hughes * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include "weston.h" #include "cms-helper.h" #include "shared/helpers.h" struct cms_colord { struct weston_compositor *ec; CdClient *client; GHashTable *devices; /* key = device-id, value = cms_output */ GHashTable *pnp_ids; /* key = pnp-id, value = vendor */ gchar *pnp_ids_data; GMainLoop *loop; GThread *thread; GList *pending; GMutex pending_mutex; struct wl_event_source *source; int readfd; int writefd; struct wl_listener destroy_listener; struct wl_listener output_created_listener; }; struct cms_output { CdDevice *device; guint32 backlight_value; struct cms_colord *cms; struct weston_color_profile *p; struct weston_output *o; struct wl_listener destroy_listener; }; static gint colord_idle_find_output_cb(gconstpointer a, gconstpointer b) { struct cms_output *ocms = (struct cms_output *) a; struct weston_output *o = (struct weston_output *) b; return ocms->o == o ? 0 : -1; } static void colord_idle_cancel_for_output(struct cms_colord *cms, struct weston_output *o) { GList *l; /* cancel and remove any helpers that match the output */ g_mutex_lock(&cms->pending_mutex); l = g_list_find_custom (cms->pending, o, colord_idle_find_output_cb); if (l) { struct cms_output *ocms = l->data; cms->pending = g_list_remove (cms->pending, ocms); } g_mutex_unlock(&cms->pending_mutex); } static bool edid_value_valid(const char *str) { if (str == NULL) return false; if (str[0] == '\0') return false; if (strcmp(str, "unknown") == 0) return false; return true; } static gchar * get_output_id(struct cms_colord *cms, struct weston_output *o) { struct weston_head *head; const gchar *tmp; GString *device_id; /* XXX: What to do with multiple heads? * This is potentially unstable, if head configuration is changed * while the output is enabled. */ head = weston_output_get_first_head(o); if (wl_list_length(&o->head_list) > 1) { weston_log("colord: WARNING: multiple heads are not supported (output %s).\n", o->name); } /* see https://github.com/hughsie/colord/blob/master/doc/device-and-profile-naming-spec.txt * for format and allowed values */ device_id = g_string_new("xrandr"); if (edid_value_valid(head->make)) { tmp = g_hash_table_lookup(cms->pnp_ids, head->make); if (tmp == NULL) tmp = head->make; g_string_append_printf(device_id, "-%s", tmp); } if (edid_value_valid(head->model)) g_string_append_printf(device_id, "-%s", head->model); if (edid_value_valid(head->serial_number)) g_string_append_printf(device_id, "-%s", head->serial_number); /* no EDID data, so use fallback */ if (strcmp(device_id->str, "xrandr") == 0) g_string_append_printf(device_id, "-drm-%i", o->id); return g_string_free(device_id, FALSE); } static void update_device_with_profile_in_idle(struct cms_output *ocms) { gboolean signal_write = FALSE; ssize_t rc; struct cms_colord *cms = ocms->cms; colord_idle_cancel_for_output(cms, ocms->o); g_mutex_lock(&cms->pending_mutex); if (cms->pending == NULL) signal_write = TRUE; cms->pending = g_list_prepend(cms->pending, ocms); g_mutex_unlock(&cms->pending_mutex); /* signal we've got updates to do */ if (signal_write) { gchar tmp = '\0'; rc = write(cms->writefd, &tmp, 1); if (rc == 0) weston_log("colord: failed to write to pending fd\n"); } } static void colord_update_output_from_device (struct cms_output *ocms) { CdProfile *profile; const gchar *tmp; gboolean ret; GError *error = NULL; gint percentage; /* old profile is no longer valid */ weston_cms_destroy_profile(ocms->p); ocms->p = NULL; ret = cd_device_connect_sync(ocms->device, NULL, &error); if (!ret) { weston_log("colord: failed to connect to device %s: %s\n", cd_device_get_object_path (ocms->device), error->message); g_error_free(error); goto out; } profile = cd_device_get_default_profile(ocms->device); if (!profile) { weston_log("colord: no assigned color profile for %s\n", cd_device_get_id (ocms->device)); goto out; } ret = cd_profile_connect_sync(profile, NULL, &error); if (!ret) { weston_log("colord: failed to connect to profile %s: %s\n", cd_profile_get_object_path (profile), error->message); g_error_free(error); goto out; } /* get the calibration brightness level (only set for some profiles) */ tmp = cd_profile_get_metadata_item(profile, CD_PROFILE_METADATA_SCREEN_BRIGHTNESS); if (tmp != NULL) { percentage = atoi(tmp); if (percentage > 0 && percentage <= 100) ocms->backlight_value = percentage * 255 / 100; } ocms->p = weston_cms_load_profile(cd_profile_get_filename(profile)); if (ocms->p == NULL) { weston_log("colord: warning failed to load profile %s: %s\n", cd_profile_get_object_path (profile), error->message); g_error_free(error); goto out; } out: update_device_with_profile_in_idle(ocms); } static void colord_device_changed_cb(CdDevice *device, struct cms_output *ocms) { weston_log("colord: device %s changed, update output\n", cd_device_get_object_path (ocms->device)); colord_update_output_from_device(ocms); } static void colord_notifier_output_destroy(struct wl_listener *listener, void *data) { struct cms_output *ocms = container_of(listener, struct cms_output, destroy_listener); struct weston_output *o = (struct weston_output *) data; struct cms_colord *cms = ocms->cms; gchar *device_id; device_id = get_output_id(cms, o); g_hash_table_remove (cms->devices, device_id); g_free (device_id); } static void colord_output_created(struct cms_colord *cms, struct weston_output *o) { struct weston_head *head; CdDevice *device; const gchar *tmp; gchar *device_id; GError *error = NULL; GHashTable *device_props; struct cms_output *ocms; /* XXX: What to do with multiple heads? */ head = weston_output_get_first_head(o); /* create device */ device_id = get_output_id(cms, o); weston_log("colord: output added %s\n", device_id); device_props = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_insert (device_props, g_strdup(CD_DEVICE_PROPERTY_KIND), g_strdup(cd_device_kind_to_string (CD_DEVICE_KIND_DISPLAY))); g_hash_table_insert (device_props, g_strdup(CD_DEVICE_PROPERTY_FORMAT), g_strdup("ColorModel.OutputMode.OutputResolution")); g_hash_table_insert (device_props, g_strdup(CD_DEVICE_PROPERTY_COLORSPACE), g_strdup(cd_colorspace_to_string(CD_COLORSPACE_RGB))); if (edid_value_valid(head->make)) { tmp = g_hash_table_lookup(cms->pnp_ids, head->make); if (tmp == NULL) tmp = head->make; g_hash_table_insert (device_props, g_strdup(CD_DEVICE_PROPERTY_VENDOR), g_strdup(tmp)); } if (edid_value_valid(head->model)) { g_hash_table_insert (device_props, g_strdup(CD_DEVICE_PROPERTY_MODEL), g_strdup(head->model)); } if (edid_value_valid(head->serial_number)) { g_hash_table_insert (device_props, g_strdup(CD_DEVICE_PROPERTY_SERIAL), g_strdup(head->serial_number)); } if (head->connection_internal) { g_hash_table_insert (device_props, g_strdup (CD_DEVICE_PROPERTY_EMBEDDED), NULL); } device = cd_client_create_device_sync(cms->client, device_id, CD_OBJECT_SCOPE_TEMP, device_props, NULL, &error); if (g_error_matches (error, CD_CLIENT_ERROR, CD_CLIENT_ERROR_ALREADY_EXISTS)) { g_clear_error(&error); device = cd_client_find_device_sync (cms->client, device_id, NULL, &error); } if (!device) { weston_log("colord: failed to create new or " "find existing device: %s\n", error->message); g_error_free(error); goto out; } /* create object and watch for the output to be destroyed */ ocms = g_slice_new0(struct cms_output); ocms->cms = cms; ocms->o = o; ocms->device = g_object_ref(device); ocms->destroy_listener.notify = colord_notifier_output_destroy; wl_signal_add(&o->destroy_signal, &ocms->destroy_listener); /* add to local cache */ g_hash_table_insert (cms->devices, g_strdup(device_id), ocms); g_signal_connect (ocms->device, "changed", G_CALLBACK (colord_device_changed_cb), ocms); /* get profiles */ colord_update_output_from_device (ocms); out: g_hash_table_unref (device_props); if (device) g_object_unref (device); g_free (device_id); } static void colord_notifier_output_created(struct wl_listener *listener, void *data) { struct weston_output *o = (struct weston_output *) data; struct cms_colord *cms = container_of(listener, struct cms_colord, destroy_listener); weston_log("colord: output %s created\n", o->name); colord_output_created(cms, o); } static gpointer colord_run_loop_thread(gpointer data) { struct cms_colord *cms = (struct cms_colord *) data; struct weston_output *o; /* coldplug outputs */ wl_list_for_each(o, &cms->ec->output_list, link) { weston_log("colord: output %s coldplugged\n", o->name); colord_output_created(cms, o); } g_main_loop_run(cms->loop); return NULL; } static int colord_dispatch_all_pending(int fd, uint32_t mask, void *data) { gchar tmp; GList *l; ssize_t rc; struct cms_colord *cms = data; struct cms_output *ocms; weston_log("colord: dispatching events\n"); g_mutex_lock(&cms->pending_mutex); for (l = cms->pending; l != NULL; l = l->next) { ocms = l->data; /* optionally set backlight to calibration value */ if (ocms->o->set_backlight && ocms->backlight_value != 0) { weston_log("colord: profile calibration backlight to %i/255\n", ocms->backlight_value); ocms->o->set_backlight(ocms->o, ocms->backlight_value); } weston_cms_set_color_profile(ocms->o, ocms->p); } g_list_free (cms->pending); cms->pending = NULL; g_mutex_unlock(&cms->pending_mutex); /* done */ rc = read(cms->readfd, &tmp, 1); if (rc == 0) weston_log("colord: failed to read from pending fd\n"); return 1; } static void colord_load_pnp_ids(struct cms_colord *cms) { gboolean ret = FALSE; gchar *tmp; GError *error = NULL; guint i; const gchar *pnp_ids_fn[] = { "/usr/share/hwdata/pnp.ids", "/usr/share/misc/pnp.ids", NULL }; /* find and load file */ for (i = 0; pnp_ids_fn[i] != NULL; i++) { if (!g_file_test(pnp_ids_fn[i], G_FILE_TEST_EXISTS)) continue; ret = g_file_get_contents(pnp_ids_fn[i], &cms->pnp_ids_data, NULL, &error); if (!ret) { weston_log("colord: failed to load %s: %s\n", pnp_ids_fn[i], error->message); g_error_free(error); return; } break; } if (!ret) { weston_log("colord: no pnp.ids found\n"); return; } /* parse fixed offsets into lines */ tmp = cms->pnp_ids_data; for (i = 0; cms->pnp_ids_data[i] != '\0'; i++) { if (cms->pnp_ids_data[i] != '\n') continue; cms->pnp_ids_data[i] = '\0'; if (tmp[0] && tmp[1] && tmp[2] && tmp[3] == '\t' && tmp[4]) { tmp[3] = '\0'; g_hash_table_insert(cms->pnp_ids, tmp, tmp+4); tmp = &cms->pnp_ids_data[i+1]; } } } static void colord_module_destroy(struct cms_colord *cms) { if (cms->loop) { g_main_loop_quit(cms->loop); g_main_loop_unref(cms->loop); } if (cms->thread) g_thread_join(cms->thread); /* cms->devices must be destroyed before other resources, as * the other resources are needed during output cleanup in * cms->devices unref. */ if (cms->devices) g_hash_table_unref(cms->devices); if (cms->client) g_object_unref(cms->client); if (cms->readfd) close(cms->readfd); if (cms->writefd) close(cms->writefd); g_free(cms->pnp_ids_data); g_hash_table_unref(cms->pnp_ids); wl_list_remove(&cms->destroy_listener.link); free(cms); } static void colord_notifier_destroy(struct wl_listener *listener, void *data) { struct cms_colord *cms = container_of(listener, struct cms_colord, destroy_listener); colord_module_destroy(cms); } static void colord_cms_output_destroy(gpointer data) { struct cms_output *ocms = (struct cms_output *) data; struct cms_colord *cms = ocms->cms; struct weston_output *o = ocms->o; gboolean ret; gchar *device_id; GError *error = NULL; colord_idle_cancel_for_output(cms, o); device_id = get_output_id(cms, o); weston_log("colord: output unplugged %s\n", device_id); wl_list_remove(&ocms->destroy_listener.link); g_signal_handlers_disconnect_by_data(ocms->device, ocms); ret = cd_client_delete_device_sync (cms->client, ocms->device, NULL, &error); if (!ret) { weston_log("colord: failed to delete device: %s\n", error->message); g_error_free(error); } g_object_unref(ocms->device); g_slice_free(struct cms_output, ocms); g_free (device_id); } WL_EXPORT int wet_module_init(struct weston_compositor *ec, int *argc, char *argv[]) { gboolean ret; GError *error = NULL; int fd[2]; struct cms_colord *cms; struct wl_event_loop *loop; weston_log("colord: initialized\n"); /* create local state object */ cms = zalloc(sizeof *cms); if (cms == NULL) return -1; cms->ec = ec; if (!weston_compositor_add_destroy_listener_once(ec, &cms->destroy_listener, colord_notifier_destroy)) { free(cms); return 0; } #if !GLIB_CHECK_VERSION(2,36,0) g_type_init(); #endif cms->client = cd_client_new(); ret = cd_client_connect_sync(cms->client, NULL, &error); if (!ret) { weston_log("colord: failed to contact daemon: %s\n", error->message); g_error_free(error); colord_module_destroy(cms); return -1; } g_mutex_init(&cms->pending_mutex); cms->devices = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, colord_cms_output_destroy); /* devices added */ cms->output_created_listener.notify = colord_notifier_output_created; wl_signal_add(&ec->output_created_signal, &cms->output_created_listener); /* add all the PNP IDs */ cms->pnp_ids = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); colord_load_pnp_ids(cms); /* setup a thread for the GLib callbacks */ cms->loop = g_main_loop_new(NULL, FALSE); cms->thread = g_thread_new("colord CMS main loop", colord_run_loop_thread, cms); /* batch device<->profile updates */ if (pipe2(fd, O_CLOEXEC) == -1) { colord_module_destroy(cms); return -1; } cms->readfd = fd[0]; cms->writefd = fd[1]; loop = wl_display_get_event_loop(ec->wl_display); cms->source = wl_event_loop_add_fd (loop, cms->readfd, WL_EVENT_READABLE, colord_dispatch_all_pending, cms); if (!cms->source) { colord_module_destroy(cms); return -1; } return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3356297 weston-8.0.0/compositor/cms-helper.c0000644000175000017460000000673000000000000017726 0ustar00simonwheel00000000000000/* * Copyright © 2013 Richard Hughes * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #ifdef HAVE_LCMS #include #endif #include #include "cms-helper.h" #ifdef HAVE_LCMS static void weston_cms_gamma_clear(struct weston_output *o) { int i; uint16_t *red; if (!o->set_gamma) return; red = calloc(o->gamma_size, sizeof(uint16_t)); for (i = 0; i < o->gamma_size; i++) red[i] = (uint32_t) 0xffff * (uint32_t) i / (uint32_t) (o->gamma_size - 1); o->set_gamma(o, o->gamma_size, red, red, red); free(red); } #endif void weston_cms_set_color_profile(struct weston_output *o, struct weston_color_profile *p) { #ifdef HAVE_LCMS cmsFloat32Number in; const cmsToneCurve **vcgt; int i; int size; uint16_t *red = NULL; uint16_t *green = NULL; uint16_t *blue = NULL; if (!o->set_gamma) return; if (!p) { weston_cms_gamma_clear(o); return; } weston_log("Using ICC profile %s\n", p->filename); vcgt = cmsReadTag (p->lcms_handle, cmsSigVcgtTag); if (vcgt == NULL || vcgt[0] == NULL) { weston_cms_gamma_clear(o); return; } size = o->gamma_size; red = calloc(size, sizeof(uint16_t)); green = calloc(size, sizeof(uint16_t)); blue = calloc(size, sizeof(uint16_t)); for (i = 0; i < size; i++) { in = (cmsFloat32Number) i / (cmsFloat32Number) (size - 1); red[i] = cmsEvalToneCurveFloat(vcgt[0], in) * (double) 0xffff; green[i] = cmsEvalToneCurveFloat(vcgt[1], in) * (double) 0xffff; blue[i] = cmsEvalToneCurveFloat(vcgt[2], in) * (double) 0xffff; } o->set_gamma(o, size, red, green, blue); free(red); free(green); free(blue); #endif } void weston_cms_destroy_profile(struct weston_color_profile *p) { if (!p) return; #ifdef HAVE_LCMS cmsCloseProfile(p->lcms_handle); #endif free(p->filename); free(p); } struct weston_color_profile * weston_cms_create_profile(const char *filename, void *lcms_profile) { struct weston_color_profile *p; p = zalloc(sizeof(struct weston_color_profile)); p->filename = strdup(filename); p->lcms_handle = lcms_profile; return p; } struct weston_color_profile * weston_cms_load_profile(const char *filename) { struct weston_color_profile *p = NULL; #ifdef HAVE_LCMS cmsHPROFILE lcms_profile; lcms_profile = cmsOpenProfileFromFile(filename, "r"); if (lcms_profile) p = weston_cms_create_profile(filename, lcms_profile); #endif return p; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3356297 weston-8.0.0/compositor/cms-helper.h0000644000175000017460000000555400000000000017736 0ustar00simonwheel00000000000000/* * Copyright © 2013 Richard Hughes * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef _WESTON_CMS_H_ #define _WESTON_CMS_H_ #include "config.h" #include /* General overview on how to be a CMS plugin: * * First, some nomenclature: * * CMF: Color management framework, i.e. "Use foo.icc for device $bar" * CMM: Color management module that converts pixel colors, which is * usually lcms2 on any modern OS. * CMS: Color management system that encompasses both a CMF and CMM. * ICC: International Color Consortium, the people that define the * binary encoding of a .icc file. * VCGT: Video Card Gamma Tag. An Apple extension to the ICC specification * that allows the calibration state to be stored in the ICC profile * Output: Physical port with a display attached, e.g. LVDS1 * * As a CMF is probably something you don't want or need on an embedded install * these functions will not be called if the icc_profile key is set for a * specific [output] section in weston.ini * * Most desktop environments want the CMF to decide what profile to use in * different situations, so that displays can be profiled and also so that * the ICC profiles can be changed at runtime depending on the task or ambient * environment. * * The CMF can be selected using the 'modules' key in the [core] section. */ struct weston_color_profile { char *filename; void *lcms_handle; }; void weston_cms_set_color_profile(struct weston_output *o, struct weston_color_profile *p); struct weston_color_profile * weston_cms_create_profile(const char *filename, void *lcms_profile); struct weston_color_profile * weston_cms_load_profile(const char *filename); void weston_cms_destroy_profile(struct weston_color_profile *p); #endif ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3356297 weston-8.0.0/compositor/cms-static.c0000644000175000017460000000665000000000000017737 0ustar00simonwheel00000000000000/* * Copyright © 2013 Richard Hughes * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include "cms-helper.h" #include "shared/helpers.h" #include "weston.h" struct cms_static { struct weston_compositor *ec; struct wl_listener destroy_listener; struct wl_listener output_created_listener; }; static void cms_output_created(struct cms_static *cms, struct weston_output *o) { struct weston_color_profile *p; struct weston_config_section *s; char *profile; weston_log("cms-static: output %i [%s] created\n", o->id, o->name); if (o->name == NULL) return; s = weston_config_get_section(wet_get_config(cms->ec), "output", "name", o->name); if (s == NULL) return; if (weston_config_section_get_string(s, "icc_profile", &profile, NULL) < 0) return; p = weston_cms_load_profile(profile); if (p == NULL && strlen(profile) > 0) { weston_log("cms-static: failed to load %s\n", profile); } else { weston_log("cms-static: loading %s for %s\n", (p != NULL) ? profile : "identity LUT", o->name); weston_cms_set_color_profile(o, p); } } static void cms_notifier_output_created(struct wl_listener *listener, void *data) { struct weston_output *o = (struct weston_output *) data; struct cms_static *cms = container_of(listener, struct cms_static, output_created_listener); cms_output_created(cms, o); } static void cms_module_destroy(struct cms_static *cms) { free(cms); } static void cms_notifier_destroy(struct wl_listener *listener, void *data) { struct cms_static *cms = container_of(listener, struct cms_static, destroy_listener); cms_module_destroy(cms); } WL_EXPORT int wet_module_init(struct weston_compositor *ec, int *argc, char *argv[]) { struct cms_static *cms; struct weston_output *output; weston_log("cms-static: initialized\n"); /* create local state object */ cms = zalloc(sizeof *cms); if (cms == NULL) return -1; cms->ec = ec; if (!weston_compositor_add_destroy_listener_once(ec, &cms->destroy_listener, cms_notifier_destroy)) { free(cms); return 0; } cms->output_created_listener.notify = cms_notifier_output_created; wl_signal_add(&ec->output_created_signal, &cms->output_created_listener); /* discover outputs */ wl_list_for_each(output, &ec->output_list, link) cms_output_created(cms, output); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.338963 weston-8.0.0/compositor/executable.c0000644000175000017460000000236600000000000020011 0ustar00simonwheel00000000000000/* * Copyright © 2019 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include "weston.h" int main(int argc, char *argv[]) { return wet_main(argc, argv); } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.338963 weston-8.0.0/compositor/main.c0000644000175000017460000025666700000000000016633 0ustar00simonwheel00000000000000/* * Copyright © 2010-2011 Intel Corporation * Copyright © 2008-2011 Kristian Høgsberg * Copyright © 2012-2018 Collabora, Ltd. * Copyright © 2010-2011 Benjamin Franzke * Copyright © 2013 Jason Ekstrand * Copyright © 2017, 2018 General Electric Company * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "weston.h" #include #include "shared/os-compatibility.h" #include "shared/helpers.h" #include "shared/string-helpers.h" #include "git-version.h" #include #include "weston.h" #include #include #include #include #include #include #include #include #include "../remoting/remoting-plugin.h" #include "../pipewire/pipewire-plugin.h" #define WINDOW_TITLE "Weston Compositor" /* flight recorder size (in bytes) */ #define DEFAULT_FLIGHT_REC_SIZE (5 * 1024 * 1024) struct wet_output_config { int width; int height; int32_t scale; uint32_t transform; }; struct wet_compositor; struct wet_layoutput; struct wet_head_tracker { struct wl_listener head_destroy_listener; }; /** User data for each weston_output */ struct wet_output { struct weston_output *output; struct wl_listener output_destroy_listener; struct wet_layoutput *layoutput; struct wl_list link; /**< in wet_layoutput::output_list */ }; #define MAX_CLONE_HEADS 16 struct wet_head_array { struct weston_head *heads[MAX_CLONE_HEADS]; /**< heads to add */ unsigned n; /**< the number of heads */ }; /** A layout output * * Contains wet_outputs that are all clones (independent CRTCs). * Stores output layout information in the future. */ struct wet_layoutput { struct wet_compositor *compositor; struct wl_list compositor_link; /**< in wet_compositor::layoutput_list */ struct wl_list output_list; /**< wet_output::link */ char *name; struct weston_config_section *section; struct wet_head_array add; /**< tmp: heads to add as clones */ }; struct wet_compositor { struct weston_compositor *compositor; struct weston_config *config; struct wet_output_config *parsed_options; bool drm_use_current_mode; struct wl_listener heads_changed_listener; int (*simple_output_configure)(struct weston_output *output); bool init_failed; struct wl_list layoutput_list; /**< wet_layoutput::compositor_link */ }; static FILE *weston_logfile = NULL; static struct weston_log_scope *log_scope; static struct weston_log_scope *protocol_scope; static int cached_tm_mday = -1; static char * weston_log_timestamp(char *buf, size_t len) { struct timeval tv; struct tm *brokendown_time; char datestr[128]; char timestr[128]; gettimeofday(&tv, NULL); brokendown_time = localtime(&tv.tv_sec); if (brokendown_time == NULL) { snprintf(buf, len, "%s", "[(NULL)localtime] "); return buf; } memset(datestr, 0, sizeof(datestr)); if (brokendown_time->tm_mday != cached_tm_mday) { strftime(datestr, sizeof(datestr), "Date: %Y-%m-%d %Z\n", brokendown_time); cached_tm_mday = brokendown_time->tm_mday; } strftime(timestr, sizeof(timestr), "%H:%M:%S", brokendown_time); /* if datestr is empty it prints only timestr*/ snprintf(buf, len, "%s[%s.%03li]", datestr, timestr, (tv.tv_usec / 1000)); return buf; } static void custom_handler(const char *fmt, va_list arg) { char timestr[512]; weston_log_scope_printf(log_scope, "%s libwayland: ", weston_log_timestamp(timestr, sizeof(timestr))); weston_log_scope_vprintf(log_scope, fmt, arg); } static void weston_log_file_open(const char *filename) { wl_log_set_handler_server(custom_handler); if (filename != NULL) { weston_logfile = fopen(filename, "a"); if (weston_logfile) os_fd_set_cloexec(fileno(weston_logfile)); } if (weston_logfile == NULL) weston_logfile = stderr; else setvbuf(weston_logfile, NULL, _IOLBF, 256); } static void weston_log_file_close(void) { if ((weston_logfile != stderr) && (weston_logfile != NULL)) fclose(weston_logfile); weston_logfile = stderr; } static int vlog(const char *fmt, va_list ap) { const char *oom = "Out of memory"; char timestr[128]; int len = 0; char *str; if (weston_log_scope_is_enabled(log_scope)) { int len_va; char *log_timestamp = weston_log_timestamp(timestr, sizeof(timestr)); len_va = vasprintf(&str, fmt, ap); if (len_va >= 0) { len = weston_log_scope_printf(log_scope, "%s %s", log_timestamp, str); free(str); } else { len = weston_log_scope_printf(log_scope, "%s %s", log_timestamp, oom); } } return len; } static int vlog_continue(const char *fmt, va_list argp) { return weston_log_scope_vprintf(log_scope, fmt, argp); } static const char * get_next_argument(const char *signature, char* type) { for(; *signature; ++signature) { switch(*signature) { case 'i': case 'u': case 'f': case 's': case 'o': case 'n': case 'a': case 'h': *type = *signature; return signature + 1; } } *type = '\0'; return signature; } static void protocol_log_fn(void *user_data, enum wl_protocol_logger_type direction, const struct wl_protocol_logger_message *message) { FILE *fp; char *logstr; size_t logsize; char timestr[128]; struct wl_resource *res = message->resource; const char *signature = message->message->signature; int i; char type; if (!weston_log_scope_is_enabled(protocol_scope)) return; fp = open_memstream(&logstr, &logsize); if (!fp) return; weston_log_scope_timestamp(protocol_scope, timestr, sizeof timestr); fprintf(fp, "%s ", timestr); fprintf(fp, "client %p %s ", wl_resource_get_client(res), direction == WL_PROTOCOL_LOGGER_REQUEST ? "rq" : "ev"); fprintf(fp, "%s@%u.%s(", wl_resource_get_class(res), wl_resource_get_id(res), message->message->name); for (i = 0; i < message->arguments_count; i++) { signature = get_next_argument(signature, &type); if (i > 0) fprintf(fp, ", "); switch (type) { case 'u': fprintf(fp, "%u", message->arguments[i].u); break; case 'i': fprintf(fp, "%d", message->arguments[i].i); break; case 'f': fprintf(fp, "%f", wl_fixed_to_double(message->arguments[i].f)); break; case 's': fprintf(fp, "\"%s\"", message->arguments[i].s); break; case 'o': if (message->arguments[i].o) { struct wl_resource* resource; resource = (struct wl_resource*) message->arguments[i].o; fprintf(fp, "%s@%u", wl_resource_get_class(resource), wl_resource_get_id(resource)); } else fprintf(fp, "nil"); break; case 'n': fprintf(fp, "new id %s@", (message->message->types[i]) ? message->message->types[i]->name : "[unknown]"); if (message->arguments[i].n != 0) fprintf(fp, "%u", message->arguments[i].n); else fprintf(fp, "nil"); break; case 'a': fprintf(fp, "array"); break; case 'h': fprintf(fp, "fd %d", message->arguments[i].h); break; } } fprintf(fp, ")\n"); if (fclose(fp) == 0) weston_log_scope_write(protocol_scope, logstr, logsize); free(logstr); } static struct wl_list child_process_list; static struct weston_compositor *segv_compositor; static int sigchld_handler(int signal_number, void *data) { struct weston_process *p; int status; pid_t pid; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { wl_list_for_each(p, &child_process_list, link) { if (p->pid == pid) break; } if (&p->link == &child_process_list) { weston_log("unknown child process exited\n"); continue; } wl_list_remove(&p->link); p->cleanup(p, status); } if (pid < 0 && errno != ECHILD) weston_log("waitpid error %s\n", strerror(errno)); return 1; } static void child_client_exec(int sockfd, const char *path) { int clientfd; char s[32]; sigset_t allsigs; /* do not give our signal mask to the new process */ sigfillset(&allsigs); sigprocmask(SIG_UNBLOCK, &allsigs, NULL); /* Launch clients as the user. Do not launch clients with wrong euid. */ if (seteuid(getuid()) == -1) { weston_log("compositor: failed seteuid\n"); return; } /* SOCK_CLOEXEC closes both ends, so we dup the fd to get a * non-CLOEXEC fd to pass through exec. */ clientfd = dup(sockfd); if (clientfd == -1) { weston_log("compositor: dup failed: %s\n", strerror(errno)); return; } snprintf(s, sizeof s, "%d", clientfd); setenv("WAYLAND_SOCKET", s, 1); if (execl(path, path, NULL) < 0) weston_log("compositor: executing '%s' failed: %s\n", path, strerror(errno)); } WL_EXPORT struct wl_client * weston_client_launch(struct weston_compositor *compositor, struct weston_process *proc, const char *path, weston_process_cleanup_func_t cleanup) { int sv[2]; pid_t pid; struct wl_client *client; weston_log("launching '%s'\n", path); if (os_socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { weston_log("weston_client_launch: " "socketpair failed while launching '%s': %s\n", path, strerror(errno)); return NULL; } pid = fork(); if (pid == -1) { close(sv[0]); close(sv[1]); weston_log("weston_client_launch: " "fork failed while launching '%s': %s\n", path, strerror(errno)); return NULL; } if (pid == 0) { child_client_exec(sv[1], path); _exit(-1); } close(sv[1]); client = wl_client_create(compositor->wl_display, sv[0]); if (!client) { close(sv[0]); weston_log("weston_client_launch: " "wl_client_create failed while launching '%s'.\n", path); return NULL; } proc->pid = pid; proc->cleanup = cleanup; weston_watch_process(proc); return client; } WL_EXPORT void weston_watch_process(struct weston_process *process) { wl_list_insert(&child_process_list, &process->link); } struct process_info { struct weston_process proc; char *path; }; static void process_handle_sigchld(struct weston_process *process, int status) { struct process_info *pinfo = container_of(process, struct process_info, proc); /* * There are no guarantees whether this runs before or after * the wl_client destructor. */ if (WIFEXITED(status)) { weston_log("%s exited with status %d\n", pinfo->path, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { weston_log("%s died on signal %d\n", pinfo->path, WTERMSIG(status)); } else { weston_log("%s disappeared\n", pinfo->path); } free(pinfo->path); free(pinfo); } WL_EXPORT struct wl_client * weston_client_start(struct weston_compositor *compositor, const char *path) { struct process_info *pinfo; struct wl_client *client; pinfo = zalloc(sizeof *pinfo); if (!pinfo) return NULL; pinfo->path = strdup(path); if (!pinfo->path) goto out_free; client = weston_client_launch(compositor, &pinfo->proc, path, process_handle_sigchld); if (!client) goto out_str; return client; out_str: free(pinfo->path); out_free: free(pinfo); return NULL; } static void log_uname(void) { struct utsname usys; uname(&usys); weston_log("OS: %s, %s, %s, %s\n", usys.sysname, usys.release, usys.version, usys.machine); } static struct wet_compositor * to_wet_compositor(struct weston_compositor *compositor) { return weston_compositor_get_user_data(compositor); } static struct wet_output_config * wet_init_parsed_options(struct weston_compositor *ec) { struct wet_compositor *compositor = to_wet_compositor(ec); struct wet_output_config *config; config = zalloc(sizeof *config); if (!config) { perror("out of memory"); return NULL; } config->width = 0; config->height = 0; config->scale = 0; config->transform = UINT32_MAX; compositor->parsed_options = config; return config; } WL_EXPORT struct weston_config * wet_get_config(struct weston_compositor *ec) { struct wet_compositor *compositor = to_wet_compositor(ec); return compositor->config; } static const char xdg_error_message[] = "fatal: environment variable XDG_RUNTIME_DIR is not set.\n"; static const char xdg_wrong_message[] = "fatal: environment variable XDG_RUNTIME_DIR\n" "is set to \"%s\", which is not a directory.\n"; static const char xdg_wrong_mode_message[] = "warning: XDG_RUNTIME_DIR \"%s\" is not configured\n" "correctly. Unix access mode must be 0700 (current mode is %o),\n" "and must be owned by the user (current owner is UID %d).\n"; static const char xdg_detail_message[] = "Refer to your distribution on how to get it, or\n" "http://www.freedesktop.org/wiki/Specifications/basedir-spec\n" "on how to implement it.\n"; static void verify_xdg_runtime_dir(void) { char *dir = getenv("XDG_RUNTIME_DIR"); struct stat s; if (!dir) { weston_log(xdg_error_message); weston_log_continue(xdg_detail_message); exit(EXIT_FAILURE); } if (stat(dir, &s) || !S_ISDIR(s.st_mode)) { weston_log(xdg_wrong_message, dir); weston_log_continue(xdg_detail_message); exit(EXIT_FAILURE); } if ((s.st_mode & 0777) != 0700 || s.st_uid != getuid()) { weston_log(xdg_wrong_mode_message, dir, s.st_mode & 0777, s.st_uid); weston_log_continue(xdg_detail_message); } } static int usage(int error_code) { FILE *out = error_code == EXIT_SUCCESS ? stdout : stderr; fprintf(out, "Usage: weston [OPTIONS]\n\n" "This is weston version " VERSION ", the Wayland reference compositor.\n" "Weston supports multiple backends, and depending on which backend is in use\n" "different options will be accepted.\n\n" "Core options:\n\n" " --version\t\tPrint weston version\n" " -B, --backend=MODULE\tBackend module, one of\n" #if defined(BUILD_DRM_COMPOSITOR) "\t\t\t\tdrm-backend.so\n" #endif #if defined(BUILD_FBDEV_COMPOSITOR) "\t\t\t\tfbdev-backend.so\n" #endif #if defined(BUILD_HEADLESS_COMPOSITOR) "\t\t\t\theadless-backend.so\n" #endif #if defined(BUILD_RDP_COMPOSITOR) "\t\t\t\trdp-backend.so\n" #endif #if defined(BUILD_WAYLAND_COMPOSITOR) "\t\t\t\twayland-backend.so\n" #endif #if defined(BUILD_X11_COMPOSITOR) "\t\t\t\tx11-backend.so\n" #endif " --shell=MODULE\tShell module, defaults to desktop-shell.so\n" " -S, --socket=NAME\tName of socket to listen on\n" " -i, --idle-time=SECS\tIdle time in seconds\n" #if defined(BUILD_XWAYLAND) " --xwayland\t\tLoad the xwayland module\n" #endif " --modules\t\tLoad the comma-separated list of modules\n" " --log=FILE\t\tLog to the given file\n" " -c, --config=FILE\tConfig file to load, defaults to weston.ini\n" " --no-config\t\tDo not read weston.ini\n" " --wait-for-debugger\tRaise SIGSTOP on start-up\n" " --debug\t\tEnable debug extension\n" " -l, --logger-scopes=SCOPE\n\t\t\tSpecify log scopes to " "subscribe to.\n\t\t\tCan specify multiple scopes, " "each followed by comma\n" " -f, --flight-rec-scopes=SCOPE\n\t\t\tSpecify log scopes to " "subscribe to.\n\t\t\tCan specify multiple scopes, " "each followed by comma\n" " -h, --help\t\tThis help message\n\n"); #if defined(BUILD_DRM_COMPOSITOR) fprintf(out, "Options for drm-backend.so:\n\n" " --seat=SEAT\t\tThe seat that weston should run on, instead of the seat defined in XDG_SEAT\n" " --tty=TTY\t\tThe tty to use\n" " --drm-device=CARD\tThe DRM device to use, e.g. \"card0\".\n" " --use-pixman\t\tUse the pixman (CPU) renderer\n" " --current-mode\tPrefer current KMS mode over EDID preferred mode\n\n"); #endif #if defined(BUILD_FBDEV_COMPOSITOR) fprintf(out, "Options for fbdev-backend.so:\n\n" " --tty=TTY\t\tThe tty to use\n" " --device=DEVICE\tThe framebuffer device to use\n" " --seat=SEAT\t\tThe seat that weston should run on, instead of the seat defined in XDG_SEAT\n" "\n"); #endif #if defined(BUILD_HEADLESS_COMPOSITOR) fprintf(out, "Options for headless-backend.so:\n\n" " --width=WIDTH\t\tWidth of memory surface\n" " --height=HEIGHT\tHeight of memory surface\n" " --transform=TR\tThe output transformation, TR is one of:\n" "\tnormal 90 180 270 flipped flipped-90 flipped-180 flipped-270\n" " --use-pixman\t\tUse the pixman (CPU) renderer (default: no rendering)\n" " --use-gl\t\tUse the GL renderer (default: no rendering)\n" " --no-outputs\t\tDo not create any virtual outputs\n" "\n"); #endif #if defined(BUILD_RDP_COMPOSITOR) fprintf(out, "Options for rdp-backend.so:\n\n" " --width=WIDTH\t\tWidth of desktop\n" " --height=HEIGHT\tHeight of desktop\n" " --env-socket\t\tUse socket defined in RDP_FD env variable as peer connection\n" " --address=ADDR\tThe address to bind\n" " --port=PORT\t\tThe port to listen on\n" " --no-clients-resize\tThe RDP peers will be forced to the size of the desktop\n" " --rdp4-key=FILE\tThe file containing the key for RDP4 encryption\n" " --rdp-tls-cert=FILE\tThe file containing the certificate for TLS encryption\n" " --rdp-tls-key=FILE\tThe file containing the private key for TLS encryption\n" "\n"); #endif #if defined(BUILD_WAYLAND_COMPOSITOR) fprintf(out, "Options for wayland-backend.so:\n\n" " --width=WIDTH\t\tWidth of Wayland surface\n" " --height=HEIGHT\tHeight of Wayland surface\n" " --scale=SCALE\t\tScale factor of output\n" " --fullscreen\t\tRun in fullscreen mode\n" " --use-pixman\t\tUse the pixman (CPU) renderer\n" " --output-count=COUNT\tCreate multiple outputs\n" " --sprawl\t\tCreate one fullscreen output for every parent output\n" " --display=DISPLAY\tWayland display to connect to\n\n"); #endif #if defined(BUILD_X11_COMPOSITOR) fprintf(out, "Options for x11-backend.so:\n\n" " --width=WIDTH\t\tWidth of X window\n" " --height=HEIGHT\tHeight of X window\n" " --scale=SCALE\t\tScale factor of output\n" " --fullscreen\t\tRun in fullscreen mode\n" " --use-pixman\t\tUse the pixman (CPU) renderer\n" " --output-count=COUNT\tCreate multiple outputs\n" " --no-input\t\tDont create input devices\n\n"); #endif exit(error_code); } static int on_term_signal(int signal_number, void *data) { struct wl_display *display = data; weston_log("caught signal %d\n", signal_number); wl_display_terminate(display); return 1; } static const char * clock_name(clockid_t clk_id) { static const char *names[] = { [CLOCK_REALTIME] = "CLOCK_REALTIME", [CLOCK_MONOTONIC] = "CLOCK_MONOTONIC", [CLOCK_MONOTONIC_RAW] = "CLOCK_MONOTONIC_RAW", [CLOCK_REALTIME_COARSE] = "CLOCK_REALTIME_COARSE", [CLOCK_MONOTONIC_COARSE] = "CLOCK_MONOTONIC_COARSE", #ifdef CLOCK_BOOTTIME [CLOCK_BOOTTIME] = "CLOCK_BOOTTIME", #endif }; if (clk_id < 0 || (unsigned)clk_id >= ARRAY_LENGTH(names)) return "unknown"; return names[clk_id]; } static const struct { uint32_t bit; /* enum weston_capability */ const char *desc; } capability_strings[] = { { WESTON_CAP_ROTATION_ANY, "arbitrary surface rotation:" }, { WESTON_CAP_CAPTURE_YFLIP, "screen capture uses y-flip:" }, }; static void weston_compositor_log_capabilities(struct weston_compositor *compositor) { unsigned i; int yes; struct timespec res; weston_log("Compositor capabilities:\n"); for (i = 0; i < ARRAY_LENGTH(capability_strings); i++) { yes = compositor->capabilities & capability_strings[i].bit; weston_log_continue(STAMP_SPACE "%s %s\n", capability_strings[i].desc, yes ? "yes" : "no"); } weston_log_continue(STAMP_SPACE "presentation clock: %s, id %d\n", clock_name(compositor->presentation_clock), compositor->presentation_clock); if (clock_getres(compositor->presentation_clock, &res) == 0) weston_log_continue(STAMP_SPACE "presentation clock resolution: %d.%09ld s\n", (int)res.tv_sec, res.tv_nsec); else weston_log_continue(STAMP_SPACE "presentation clock resolution: N/A\n"); } static void handle_primary_client_destroyed(struct wl_listener *listener, void *data) { struct wl_client *client = data; weston_log("Primary client died. Closing...\n"); wl_display_terminate(wl_client_get_display(client)); } static int weston_create_listening_socket(struct wl_display *display, const char *socket_name) { if (socket_name) { if (wl_display_add_socket(display, socket_name)) { weston_log("fatal: failed to add socket: %s\n", strerror(errno)); return -1; } } else { socket_name = wl_display_add_socket_auto(display); if (!socket_name) { weston_log("fatal: failed to add socket: %s\n", strerror(errno)); return -1; } } setenv("WAYLAND_DISPLAY", socket_name, 1); return 0; } WL_EXPORT void * wet_load_module_entrypoint(const char *name, const char *entrypoint) { char path[PATH_MAX]; void *module, *init; size_t len; if (name == NULL) return NULL; if (name[0] != '/') { len = weston_module_path_from_env(name, path, sizeof path); if (len == 0) len = snprintf(path, sizeof path, "%s/%s", MODULEDIR, name); } else { len = snprintf(path, sizeof path, "%s", name); } /* snprintf returns the length of the string it would've written, * _excluding_ the NUL byte. So even being equal to the size of * our buffer is an error here. */ if (len >= sizeof path) return NULL; module = dlopen(path, RTLD_NOW | RTLD_NOLOAD); if (module) { weston_log("Module '%s' already loaded\n", path); } else { weston_log("Loading module '%s'\n", path); module = dlopen(path, RTLD_NOW); if (!module) { weston_log("Failed to load module: %s\n", dlerror()); return NULL; } } init = dlsym(module, entrypoint); if (!init) { weston_log("Failed to lookup init function: %s\n", dlerror()); dlclose(module); return NULL; } return init; } WL_EXPORT int wet_load_module(struct weston_compositor *compositor, const char *name, int *argc, char *argv[]) { int (*module_init)(struct weston_compositor *ec, int *argc, char *argv[]); module_init = wet_load_module_entrypoint(name, "wet_module_init"); if (!module_init) return -1; if (module_init(compositor, argc, argv) < 0) return -1; return 0; } static int wet_load_shell(struct weston_compositor *compositor, const char *name, int *argc, char *argv[]) { int (*shell_init)(struct weston_compositor *ec, int *argc, char *argv[]); shell_init = wet_load_module_entrypoint(name, "wet_shell_init"); if (!shell_init) return -1; if (shell_init(compositor, argc, argv) < 0) return -1; return 0; } static char * wet_get_binary_path(const char *name, const char *dir) { char path[PATH_MAX]; size_t len; len = weston_module_path_from_env(name, path, sizeof path); if (len > 0) return strdup(path); len = snprintf(path, sizeof path, "%s/%s", dir, name); if (len >= sizeof path) return NULL; return strdup(path); } WL_EXPORT char * wet_get_libexec_path(const char *name) { return wet_get_binary_path(name, LIBEXECDIR); } WL_EXPORT char * wet_get_bindir_path(const char *name) { return wet_get_binary_path(name, BINDIR); } static int load_modules(struct weston_compositor *ec, const char *modules, int *argc, char *argv[], bool *xwayland) { const char *p, *end; char buffer[256]; if (modules == NULL) return 0; p = modules; while (*p) { end = strchrnul(p, ','); snprintf(buffer, sizeof buffer, "%.*s", (int) (end - p), p); if (strstr(buffer, "xwayland.so")) { weston_log("Old Xwayland module loading detected: " "Please use --xwayland command line option " "or set xwayland=true in the [core] section " "in weston.ini\n"); *xwayland = true; } else { if (wet_load_module(ec, buffer, argc, argv) < 0) return -1; } p = end; while (*p == ',') p++; } return 0; } static int save_touch_device_calibration(struct weston_compositor *compositor, struct weston_touch_device *device, const struct weston_touch_device_matrix *calibration) { struct weston_config_section *s; struct weston_config *config = wet_get_config(compositor); char *helper = NULL; char *helper_cmd = NULL; int ret = -1; int status; const float *m = calibration->m; s = weston_config_get_section(config, "libinput", NULL, NULL); weston_config_section_get_string(s, "calibration_helper", &helper, NULL); if (!helper || strlen(helper) == 0) { ret = 0; goto out; } if (asprintf(&helper_cmd, "\"%s\" '%s' %f %f %f %f %f %f", helper, device->syspath, m[0], m[1], m[2], m[3], m[4], m[5]) < 0) goto out; status = system(helper_cmd); free(helper_cmd); if (status < 0) { weston_log("Error: failed to run calibration helper '%s'.\n", helper); goto out; } if (!WIFEXITED(status)) { weston_log("Error: calibration helper '%s' possibly killed.\n", helper); goto out; } if (WEXITSTATUS(status) == 0) { ret = 0; } else { weston_log("Calibration helper '%s' exited with status %d.\n", helper, WEXITSTATUS(status)); } out: free(helper); return ret; } static int weston_compositor_init_config(struct weston_compositor *ec, struct weston_config *config) { struct xkb_rule_names xkb_names; struct weston_config_section *s; int repaint_msec; bool cal; /* weston.ini [keyboard] */ s = weston_config_get_section(config, "keyboard", NULL, NULL); weston_config_section_get_string(s, "keymap_rules", (char **) &xkb_names.rules, NULL); weston_config_section_get_string(s, "keymap_model", (char **) &xkb_names.model, NULL); weston_config_section_get_string(s, "keymap_layout", (char **) &xkb_names.layout, NULL); weston_config_section_get_string(s, "keymap_variant", (char **) &xkb_names.variant, NULL); weston_config_section_get_string(s, "keymap_options", (char **) &xkb_names.options, NULL); if (weston_compositor_set_xkb_rule_names(ec, &xkb_names) < 0) return -1; weston_config_section_get_int(s, "repeat-rate", &ec->kb_repeat_rate, 40); weston_config_section_get_int(s, "repeat-delay", &ec->kb_repeat_delay, 400); weston_config_section_get_bool(s, "vt-switching", &ec->vt_switching, true); /* weston.ini [core] */ s = weston_config_get_section(config, "core", NULL, NULL); weston_config_section_get_int(s, "repaint-window", &repaint_msec, ec->repaint_msec); if (repaint_msec < -10 || repaint_msec > 1000) { weston_log("Invalid repaint_window value in config: %d\n", repaint_msec); } else { ec->repaint_msec = repaint_msec; } weston_log("Output repaint window is %d ms maximum.\n", ec->repaint_msec); /* weston.ini [libinput] */ s = weston_config_get_section(config, "libinput", NULL, NULL); weston_config_section_get_bool(s, "touchscreen_calibrator", &cal, 0); if (cal) weston_compositor_enable_touch_calibrator(ec, save_touch_device_calibration); return 0; } static char * weston_choose_default_backend(void) { char *backend = NULL; if (getenv("WAYLAND_DISPLAY") || getenv("WAYLAND_SOCKET")) backend = strdup("wayland-backend.so"); else if (getenv("DISPLAY")) backend = strdup("x11-backend.so"); else backend = strdup(WESTON_NATIVE_BACKEND); return backend; } static const struct { const char *name; uint32_t token; } transforms[] = { { "normal", WL_OUTPUT_TRANSFORM_NORMAL }, { "90", WL_OUTPUT_TRANSFORM_90 }, { "180", WL_OUTPUT_TRANSFORM_180 }, { "270", WL_OUTPUT_TRANSFORM_270 }, { "flipped", WL_OUTPUT_TRANSFORM_FLIPPED }, { "flipped-90", WL_OUTPUT_TRANSFORM_FLIPPED_90 }, { "flipped-180", WL_OUTPUT_TRANSFORM_FLIPPED_180 }, { "flipped-270", WL_OUTPUT_TRANSFORM_FLIPPED_270 }, }; WL_EXPORT int weston_parse_transform(const char *transform, uint32_t *out) { unsigned int i; for (i = 0; i < ARRAY_LENGTH(transforms); i++) if (strcmp(transforms[i].name, transform) == 0) { *out = transforms[i].token; return 0; } *out = WL_OUTPUT_TRANSFORM_NORMAL; return -1; } WL_EXPORT const char * weston_transform_to_string(uint32_t output_transform) { unsigned int i; for (i = 0; i < ARRAY_LENGTH(transforms); i++) if (transforms[i].token == output_transform) return transforms[i].name; return ""; } static int load_configuration(struct weston_config **config, int32_t noconfig, const char *config_file) { const char *file = "weston.ini"; const char *full_path; *config = NULL; if (config_file) file = config_file; if (noconfig == 0) *config = weston_config_parse(file); if (*config) { full_path = weston_config_get_full_path(*config); weston_log("Using config file '%s'\n", full_path); setenv(WESTON_CONFIG_FILE_ENV_VAR, full_path, 1); return 0; } if (config_file && noconfig == 0) { weston_log("fatal: error opening or reading config file" " '%s'.\n", config_file); return -1; } weston_log("Starting with no config file.\n"); setenv(WESTON_CONFIG_FILE_ENV_VAR, "", 1); return 0; } static void handle_exit(struct weston_compositor *c) { wl_display_terminate(c->wl_display); } static void wet_output_set_scale(struct weston_output *output, struct weston_config_section *section, int32_t default_scale, int32_t parsed_scale) { int32_t scale = default_scale; if (section) weston_config_section_get_int(section, "scale", &scale, default_scale); if (parsed_scale) scale = parsed_scale; weston_output_set_scale(output, scale); } /* UINT32_MAX is treated as invalid because 0 is a valid * enumeration value and the parameter is unsigned */ static void wet_output_set_transform(struct weston_output *output, struct weston_config_section *section, uint32_t default_transform, uint32_t parsed_transform) { char *t; uint32_t transform = default_transform; if (section) { weston_config_section_get_string(section, "transform", &t, "normal"); if (weston_parse_transform(t, &transform) < 0) { weston_log("Invalid transform \"%s\" for output %s\n", t, output->name); transform = default_transform; } free(t); } if (parsed_transform != UINT32_MAX) transform = parsed_transform; weston_output_set_transform(output, transform); } static void allow_content_protection(struct weston_output *output, struct weston_config_section *section) { bool allow_hdcp = true; if (section) weston_config_section_get_bool(section, "allow_hdcp", &allow_hdcp, true); weston_output_allow_protection(output, allow_hdcp); } static int wet_configure_windowed_output_from_config(struct weston_output *output, struct wet_output_config *defaults) { const struct weston_windowed_output_api *api = weston_windowed_output_get_api(output->compositor); struct weston_config *wc = wet_get_config(output->compositor); struct weston_config_section *section = NULL; struct wet_compositor *compositor = to_wet_compositor(output->compositor); struct wet_output_config *parsed_options = compositor->parsed_options; int width = defaults->width; int height = defaults->height; assert(parsed_options); if (!api) { weston_log("Cannot use weston_windowed_output_api.\n"); return -1; } section = weston_config_get_section(wc, "output", "name", output->name); if (section) { char *mode; weston_config_section_get_string(section, "mode", &mode, NULL); if (!mode || sscanf(mode, "%dx%d", &width, &height) != 2) { weston_log("Invalid mode for output %s. Using defaults.\n", output->name); width = defaults->width; height = defaults->height; } free(mode); } allow_content_protection(output, section); if (parsed_options->width) width = parsed_options->width; if (parsed_options->height) height = parsed_options->height; wet_output_set_scale(output, section, defaults->scale, parsed_options->scale); wet_output_set_transform(output, section, defaults->transform, parsed_options->transform); if (api->output_set_size(output, width, height) < 0) { weston_log("Cannot configure output \"%s\" using weston_windowed_output_api.\n", output->name); return -1; } return 0; } static int count_remaining_heads(struct weston_output *output, struct weston_head *to_go) { struct weston_head *iter = NULL; int n = 0; while ((iter = weston_output_iterate_heads(output, iter))) { if (iter != to_go) n++; } return n; } static void wet_head_tracker_destroy(struct wet_head_tracker *track) { wl_list_remove(&track->head_destroy_listener.link); free(track); } static void handle_head_destroy(struct wl_listener *listener, void *data) { struct weston_head *head = data; struct weston_output *output; struct wet_head_tracker *track = container_of(listener, struct wet_head_tracker, head_destroy_listener); wet_head_tracker_destroy(track); output = weston_head_get_output(head); /* On shutdown path, the output might be already gone. */ if (!output) return; if (count_remaining_heads(output, head) > 0) return; weston_output_destroy(output); } static struct wet_head_tracker * wet_head_tracker_from_head(struct weston_head *head) { struct wl_listener *lis; lis = weston_head_get_destroy_listener(head, handle_head_destroy); if (!lis) return NULL; return container_of(lis, struct wet_head_tracker, head_destroy_listener); } /* Listen for head destroy signal. * * If a head is destroyed and it was the last head on the output, we * destroy the associated output. * * Do not bother destroying the head trackers on shutdown, the backend will * destroy the heads which calls our handler to destroy the trackers. */ static void wet_head_tracker_create(struct wet_compositor *compositor, struct weston_head *head) { struct wet_head_tracker *track; track = zalloc(sizeof *track); if (!track) return; track->head_destroy_listener.notify = handle_head_destroy; weston_head_add_destroy_listener(head, &track->head_destroy_listener); } static void simple_head_enable(struct wet_compositor *wet, struct weston_head *head) { struct weston_output *output; int ret = 0; output = weston_compositor_create_output_with_head(wet->compositor, head); if (!output) { weston_log("Could not create an output for head \"%s\".\n", weston_head_get_name(head)); wet->init_failed = true; return; } if (wet->simple_output_configure) ret = wet->simple_output_configure(output); if (ret < 0) { weston_log("Cannot configure output \"%s\".\n", weston_head_get_name(head)); weston_output_destroy(output); wet->init_failed = true; return; } if (weston_output_enable(output) < 0) { weston_log("Enabling output \"%s\" failed.\n", weston_head_get_name(head)); weston_output_destroy(output); wet->init_failed = true; return; } wet_head_tracker_create(wet, head); /* The weston_compositor will track and destroy the output on exit. */ } static void simple_head_disable(struct weston_head *head) { struct weston_output *output; struct wet_head_tracker *track; track = wet_head_tracker_from_head(head); if (track) wet_head_tracker_destroy(track); output = weston_head_get_output(head); assert(output); weston_output_destroy(output); } static void simple_heads_changed(struct wl_listener *listener, void *arg) { struct weston_compositor *compositor = arg; struct wet_compositor *wet = to_wet_compositor(compositor); struct weston_head *head = NULL; bool connected; bool enabled; bool changed; bool non_desktop; while ((head = weston_compositor_iterate_heads(wet->compositor, head))) { connected = weston_head_is_connected(head); enabled = weston_head_is_enabled(head); changed = weston_head_is_device_changed(head); non_desktop = weston_head_is_non_desktop(head); if (connected && !enabled && !non_desktop) { simple_head_enable(wet, head); } else if (!connected && enabled) { simple_head_disable(head); } else if (enabled && changed) { weston_log("Detected a monitor change on head '%s', " "not bothering to do anything about it.\n", weston_head_get_name(head)); } weston_head_reset_device_changed(head); } } static void wet_set_simple_head_configurator(struct weston_compositor *compositor, int (*fn)(struct weston_output *)) { struct wet_compositor *wet = to_wet_compositor(compositor); wet->simple_output_configure = fn; wet->heads_changed_listener.notify = simple_heads_changed; weston_compositor_add_heads_changed_listener(compositor, &wet->heads_changed_listener); } static void configure_input_device_accel(struct weston_config_section *s, struct libinput_device *device) { char *profile_string = NULL; int is_a_profile = 1; uint32_t profiles; enum libinput_config_accel_profile profile; double speed; if (weston_config_section_get_string(s, "accel-profile", &profile_string, NULL) == 0) { if (strcmp(profile_string, "flat") == 0) profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; else if (strcmp(profile_string, "adaptive") == 0) profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; else { weston_log("warning: no such accel-profile: %s\n", profile_string); is_a_profile = 0; } profiles = libinput_device_config_accel_get_profiles(device); if (is_a_profile && (profile & profiles) != 0) { weston_log(" accel-profile=%s\n", profile_string); libinput_device_config_accel_set_profile(device, profile); } } if (weston_config_section_get_double(s, "accel-speed", &speed, 0) == 0 && speed >= -1. && speed <= 1.) { weston_log(" accel-speed=%.3f\n", speed); libinput_device_config_accel_set_speed(device, speed); } free(profile_string); } static void configure_input_device_scroll(struct weston_config_section *s, struct libinput_device *device) { bool natural; char *method_string = NULL; uint32_t methods; enum libinput_config_scroll_method method; char *button_string = NULL; int button; if (libinput_device_config_scroll_has_natural_scroll(device) && weston_config_section_get_bool(s, "natural-scroll", &natural, false) == 0) { weston_log(" natural-scroll=%s\n", natural ? "true" : "false"); libinput_device_config_scroll_set_natural_scroll_enabled( device, natural); } if (weston_config_section_get_string(s, "scroll-method", &method_string, NULL) != 0) goto done; if (strcmp(method_string, "two-finger") == 0) method = LIBINPUT_CONFIG_SCROLL_2FG; else if (strcmp(method_string, "edge") == 0) method = LIBINPUT_CONFIG_SCROLL_EDGE; else if (strcmp(method_string, "button") == 0) method = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; else if (strcmp(method_string, "none") == 0) method = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; else { weston_log("warning: no such scroll-method: %s\n", method_string); goto done; } methods = libinput_device_config_scroll_get_methods(device); if (method != LIBINPUT_CONFIG_SCROLL_NO_SCROLL && (method & methods) == 0) goto done; weston_log(" scroll-method=%s\n", method_string); libinput_device_config_scroll_set_method(device, method); if (method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) { if (weston_config_section_get_string(s, "scroll-button", &button_string, NULL) != 0) goto done; button = libevdev_event_code_from_name(EV_KEY, button_string); if (button == -1) { weston_log(" Bad scroll-button: %s\n", button_string); goto done; } weston_log(" scroll-button=%s\n", button_string); libinput_device_config_scroll_set_button(device, button); } done: free(method_string); free(button_string); } static void configure_input_device(struct weston_compositor *compositor, struct libinput_device *device) { struct weston_config_section *s; struct weston_config *config = wet_get_config(compositor); bool has_enable_tap = false; bool enable_tap; bool disable_while_typing; bool middle_emulation; bool tap_and_drag; bool tap_and_drag_lock; bool left_handed; unsigned int rotation; weston_log("libinput: configuring device \"%s\".\n", libinput_device_get_name(device)); s = weston_config_get_section(config, "libinput", NULL, NULL); if (libinput_device_config_tap_get_finger_count(device) > 0) { if (weston_config_section_get_bool(s, "enable_tap", &enable_tap, false) == 0) { weston_log("!!DEPRECATION WARNING!!: In weston.ini, " "enable_tap is deprecated in favour of " "enable-tap. Support for it may be removed " "at any time!"); has_enable_tap = true; } if (weston_config_section_get_bool(s, "enable-tap", &enable_tap, false) == 0) has_enable_tap = true; if (has_enable_tap) { weston_log(" enable-tap=%s.\n", enable_tap ? "true" : "false"); libinput_device_config_tap_set_enabled(device, enable_tap); } if (weston_config_section_get_bool(s, "tap-and-drag", &tap_and_drag, false) == 0) { weston_log(" tap-and-drag=%s.\n", tap_and_drag ? "true" : "false"); libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); } if (weston_config_section_get_bool(s, "tap-and-drag-lock", &tap_and_drag_lock, false) == 0) { weston_log(" tap-and-drag-lock=%s.\n", tap_and_drag_lock ? "true" : "false"); libinput_device_config_tap_set_drag_lock_enabled( device, tap_and_drag_lock); } } if (libinput_device_config_dwt_is_available(device) && weston_config_section_get_bool(s, "disable-while-typing", &disable_while_typing, false) == 0) { weston_log(" disable-while-typing=%s.\n", disable_while_typing ? "true" : "false"); libinput_device_config_dwt_set_enabled(device, disable_while_typing); } if (libinput_device_config_middle_emulation_is_available(device) && weston_config_section_get_bool(s, "middle-button-emulation", &middle_emulation, false) == 0) { weston_log(" middle-button-emulation=%s\n", middle_emulation ? "true" : "false"); libinput_device_config_middle_emulation_set_enabled( device, middle_emulation); } if (libinput_device_config_left_handed_is_available(device) && weston_config_section_get_bool(s, "left-handed", &left_handed, false) == 0) { weston_log(" left-handed=%s\n", left_handed ? "true" : "false"); libinput_device_config_left_handed_set(device, left_handed); } if (libinput_device_config_rotation_is_available(device) && weston_config_section_get_uint(s, "rotation", &rotation, false) == 0) { weston_log(" rotation=%u\n", rotation); libinput_device_config_rotation_set_angle(device, rotation); } if (libinput_device_config_accel_is_available(device)) configure_input_device_accel(s, device); configure_input_device_scroll(s, device); } static int drm_backend_output_configure(struct weston_output *output, struct weston_config_section *section) { struct wet_compositor *wet = to_wet_compositor(output->compositor); const struct weston_drm_output_api *api; enum weston_drm_backend_output_mode mode = WESTON_DRM_BACKEND_OUTPUT_PREFERRED; char *s; char *modeline = NULL; char *gbm_format = NULL; char *seat = NULL; api = weston_drm_output_get_api(output->compositor); if (!api) { weston_log("Cannot use weston_drm_output_api.\n"); return -1; } weston_config_section_get_string(section, "mode", &s, "preferred"); if (strcmp(s, "off") == 0) { assert(0 && "off was supposed to be pruned"); return -1; } else if (wet->drm_use_current_mode || strcmp(s, "current") == 0) { mode = WESTON_DRM_BACKEND_OUTPUT_CURRENT; } else if (strcmp(s, "preferred") != 0) { modeline = s; s = NULL; } free(s); if (api->set_mode(output, mode, modeline) < 0) { weston_log("Cannot configure an output using weston_drm_output_api.\n"); free(modeline); return -1; } free(modeline); wet_output_set_scale(output, section, 1, 0); wet_output_set_transform(output, section, WL_OUTPUT_TRANSFORM_NORMAL, UINT32_MAX); weston_config_section_get_string(section, "gbm-format", &gbm_format, NULL); api->set_gbm_format(output, gbm_format); free(gbm_format); weston_config_section_get_string(section, "seat", &seat, ""); api->set_seat(output, seat); free(seat); allow_content_protection(output, section); return 0; } /* Find the output section to use for configuring the output with the * named head. If an output section with the given name contains * a "same-as" key, ignore all other settings in the output section and * instead find an output section named by the "same-as". Do this * recursively. */ static struct weston_config_section * drm_config_find_controlling_output_section(struct weston_config *config, const char *head_name) { struct weston_config_section *section; char *same_as; int depth = 0; same_as = strdup(head_name); do { section = weston_config_get_section(config, "output", "name", same_as); if (!section && depth > 0) weston_log("Configuration error: " "output section referred to with " "'same-as=%s' not found.\n", same_as); free(same_as); if (!section) return NULL; if (++depth > 10) { weston_log("Configuration error: " "'same-as' nested too deep for output '%s'.\n", head_name); return NULL; } weston_config_section_get_string(section, "same-as", &same_as, NULL); } while (same_as); return section; } static struct wet_layoutput * wet_compositor_create_layoutput(struct wet_compositor *compositor, const char *name, struct weston_config_section *section) { struct wet_layoutput *lo; lo = zalloc(sizeof *lo); if (!lo) return NULL; lo->compositor = compositor; wl_list_insert(compositor->layoutput_list.prev, &lo->compositor_link); wl_list_init(&lo->output_list); lo->name = strdup(name); lo->section = section; return lo; } static void wet_layoutput_destroy(struct wet_layoutput *lo) { wl_list_remove(&lo->compositor_link); assert(wl_list_empty(&lo->output_list)); free(lo->name); free(lo); } static void wet_output_handle_destroy(struct wl_listener *listener, void *data) { struct wet_output *output; output = wl_container_of(listener, output, output_destroy_listener); assert(output->output == data); output->output = NULL; wl_list_remove(&output->output_destroy_listener.link); } static struct wet_output * wet_layoutput_create_output(struct wet_layoutput *lo, const char *name) { struct wet_output *output; output = zalloc(sizeof *output); if (!output) return NULL; output->output = weston_compositor_create_output(lo->compositor->compositor, name); if (!output->output) { free(output); return NULL; } output->layoutput = lo; wl_list_insert(lo->output_list.prev, &output->link); output->output_destroy_listener.notify = wet_output_handle_destroy; weston_output_add_destroy_listener(output->output, &output->output_destroy_listener); return output; } static struct wet_output * wet_output_from_weston_output(struct weston_output *base) { struct wl_listener *lis; lis = weston_output_get_destroy_listener(base, wet_output_handle_destroy); if (!lis) return NULL; return container_of(lis, struct wet_output, output_destroy_listener); } static void wet_output_destroy(struct wet_output *output) { if (output->output) { /* output->output destruction may be deferred in some cases (see * drm_output_destroy()), so we need to forcibly trigger the * destruction callback now, or otherwise would later access * data that we are about to free */ struct weston_output *save = output->output; wet_output_handle_destroy(&output->output_destroy_listener, save); weston_output_destroy(save); } wl_list_remove(&output->link); free(output); } static struct wet_layoutput * wet_compositor_find_layoutput(struct wet_compositor *wet, const char *name) { struct wet_layoutput *lo; wl_list_for_each(lo, &wet->layoutput_list, compositor_link) if (strcmp(lo->name, name) == 0) return lo; return NULL; } static void wet_compositor_layoutput_add_head(struct wet_compositor *wet, const char *output_name, struct weston_config_section *section, struct weston_head *head) { struct wet_layoutput *lo; lo = wet_compositor_find_layoutput(wet, output_name); if (!lo) { lo = wet_compositor_create_layoutput(wet, output_name, section); if (!lo) return; } if (lo->add.n + 1 >= ARRAY_LENGTH(lo->add.heads)) return; lo->add.heads[lo->add.n++] = head; } static void wet_compositor_destroy_layout(struct wet_compositor *wet) { struct wet_layoutput *lo, *lo_tmp; struct wet_output *output, *output_tmp; wl_list_for_each_safe(lo, lo_tmp, &wet->layoutput_list, compositor_link) { wl_list_for_each_safe(output, output_tmp, &lo->output_list, link) { wet_output_destroy(output); } wet_layoutput_destroy(lo); } } static void drm_head_prepare_enable(struct wet_compositor *wet, struct weston_head *head) { const char *name = weston_head_get_name(head); struct weston_config_section *section; char *output_name = NULL; char *mode = NULL; section = drm_config_find_controlling_output_section(wet->config, name); if (section) { /* skip outputs that are explicitly off, or non-desktop and not * explicitly enabled. The backend turns them off automatically. */ weston_config_section_get_string(section, "mode", &mode, NULL); if (mode && strcmp(mode, "off") == 0) { free(mode); return; } if (!mode && weston_head_is_non_desktop(head)) return; free(mode); weston_config_section_get_string(section, "name", &output_name, NULL); assert(output_name); wet_compositor_layoutput_add_head(wet, output_name, section, head); free(output_name); } else { wet_compositor_layoutput_add_head(wet, name, NULL, head); } } static bool drm_head_should_force_enable(struct wet_compositor *wet, struct weston_head *head) { const char *name = weston_head_get_name(head); struct weston_config_section *section; bool force; section = drm_config_find_controlling_output_section(wet->config, name); if (!section) return false; weston_config_section_get_bool(section, "force-on", &force, false); return force; } static void drm_try_attach(struct weston_output *output, struct wet_head_array *add, struct wet_head_array *failed) { unsigned i; /* try to attach all heads, this probably succeeds */ for (i = 0; i < add->n; i++) { if (!add->heads[i]) continue; if (weston_output_attach_head(output, add->heads[i]) < 0) { assert(failed->n < ARRAY_LENGTH(failed->heads)); failed->heads[failed->n++] = add->heads[i]; add->heads[i] = NULL; } } } static int drm_try_enable(struct weston_output *output, struct wet_head_array *undo, struct wet_head_array *failed) { /* Try to enable, and detach heads one by one until it succeeds. */ while (!output->enabled) { if (weston_output_enable(output) == 0) return 0; /* the next head to drop */ while (undo->n > 0 && undo->heads[--undo->n] == NULL) ; /* No heads left to undo and failed to enable. */ if (undo->heads[undo->n] == NULL) return -1; assert(failed->n < ARRAY_LENGTH(failed->heads)); /* undo one head */ weston_head_detach(undo->heads[undo->n]); failed->heads[failed->n++] = undo->heads[undo->n]; undo->heads[undo->n] = NULL; } return 0; } static int drm_try_attach_enable(struct weston_output *output, struct wet_layoutput *lo) { struct wet_head_array failed = {}; unsigned i; assert(!output->enabled); drm_try_attach(output, &lo->add, &failed); if (drm_backend_output_configure(output, lo->section) < 0) return -1; if (drm_try_enable(output, &lo->add, &failed) < 0) return -1; /* For all successfully attached/enabled heads */ for (i = 0; i < lo->add.n; i++) if (lo->add.heads[i]) wet_head_tracker_create(lo->compositor, lo->add.heads[i]); /* Push failed heads to the next round. */ lo->add = failed; return 0; } static int drm_process_layoutput(struct wet_compositor *wet, struct wet_layoutput *lo) { struct wet_output *output, *tmp; char *name = NULL; int ret; /* * For each existing wet_output: * try attach * While heads left to enable: * Create output * try attach, try enable */ wl_list_for_each_safe(output, tmp, &lo->output_list, link) { struct wet_head_array failed = {}; if (!output->output) { /* Clean up left-overs from destroyed heads. */ wet_output_destroy(output); continue; } assert(output->output->enabled); drm_try_attach(output->output, &lo->add, &failed); lo->add = failed; if (lo->add.n == 0) return 0; } if (!weston_compositor_find_output_by_name(wet->compositor, lo->name)) name = strdup(lo->name); while (lo->add.n > 0) { if (!wl_list_empty(&lo->output_list)) { weston_log("Error: independent-CRTC clone mode is not implemented.\n"); return -1; } if (!name) { ret = asprintf(&name, "%s:%s", lo->name, weston_head_get_name(lo->add.heads[0])); if (ret < 0) return -1; } output = wet_layoutput_create_output(lo, name); free(name); name = NULL; if (!output) return -1; if (drm_try_attach_enable(output->output, lo) < 0) { wet_output_destroy(output); return -1; } } return 0; } static int drm_process_layoutputs(struct wet_compositor *wet) { struct wet_layoutput *lo; int ret = 0; wl_list_for_each(lo, &wet->layoutput_list, compositor_link) { if (lo->add.n == 0) continue; if (drm_process_layoutput(wet, lo) < 0) { lo->add = (struct wet_head_array){}; ret = -1; } } return ret; } static void drm_head_disable(struct weston_head *head) { struct weston_output *output_base; struct wet_output *output; struct wet_head_tracker *track; track = wet_head_tracker_from_head(head); if (track) wet_head_tracker_destroy(track); output_base = weston_head_get_output(head); assert(output_base); output = wet_output_from_weston_output(output_base); assert(output && output->output == output_base); weston_head_detach(head); if (count_remaining_heads(output->output, NULL) == 0) wet_output_destroy(output); } static void drm_heads_changed(struct wl_listener *listener, void *arg) { struct weston_compositor *compositor = arg; struct wet_compositor *wet = to_wet_compositor(compositor); struct weston_head *head = NULL; bool connected; bool enabled; bool changed; bool forced; /* We need to collect all cloned heads into outputs before enabling the * output. */ while ((head = weston_compositor_iterate_heads(compositor, head))) { connected = weston_head_is_connected(head); enabled = weston_head_is_enabled(head); changed = weston_head_is_device_changed(head); forced = drm_head_should_force_enable(wet, head); if ((connected || forced) && !enabled) { drm_head_prepare_enable(wet, head); } else if (!(connected || forced) && enabled) { drm_head_disable(head); } else if (enabled && changed) { weston_log("Detected a monitor change on head '%s', " "not bothering to do anything about it.\n", weston_head_get_name(head)); } weston_head_reset_device_changed(head); } if (drm_process_layoutputs(wet) < 0) wet->init_failed = true; } static int drm_backend_remoted_output_configure(struct weston_output *output, struct weston_config_section *section, char *modeline, const struct weston_remoting_api *api) { char *gbm_format = NULL; char *seat = NULL; char *host = NULL; char *pipeline = NULL; int port, ret; ret = api->set_mode(output, modeline); if (ret < 0) { weston_log("Cannot configure an output \"%s\" using " "weston_remoting_api. Invalid mode\n", output->name); return -1; } wet_output_set_scale(output, section, 1, 0); wet_output_set_transform(output, section, WL_OUTPUT_TRANSFORM_NORMAL, UINT32_MAX); weston_config_section_get_string(section, "gbm-format", &gbm_format, NULL); api->set_gbm_format(output, gbm_format); free(gbm_format); weston_config_section_get_string(section, "seat", &seat, ""); api->set_seat(output, seat); free(seat); weston_config_section_get_string(section, "gst-pipeline", &pipeline, NULL); if (pipeline) { api->set_gst_pipeline(output, pipeline); free(pipeline); return 0; } weston_config_section_get_string(section, "host", &host, NULL); weston_config_section_get_int(section, "port", &port, 0); if (!host || port <= 0 || 65533 < port) { weston_log("Cannot configure an output \"%s\". " "Need to specify gst-pipeline or " "host and port (1-65533).\n", output->name); } api->set_host(output, host); free(host); api->set_port(output, port); return 0; } static void remoted_output_init(struct weston_compositor *c, struct weston_config_section *section, const struct weston_remoting_api *api) { struct weston_output *output = NULL; char *output_name, *modeline = NULL; int ret; weston_config_section_get_string(section, "name", &output_name, NULL); if (!output_name) return; weston_config_section_get_string(section, "mode", &modeline, "off"); if (strcmp(modeline, "off") == 0) goto err; output = api->create_output(c, output_name); if (!output) { weston_log("Cannot create remoted output \"%s\".\n", output_name); goto err; } ret = drm_backend_remoted_output_configure(output, section, modeline, api); if (ret < 0) { weston_log("Cannot configure remoted output \"%s\".\n", output_name); goto err; } if (weston_output_enable(output) < 0) { weston_log("Enabling remoted output \"%s\" failed.\n", output_name); goto err; } free(modeline); free(output_name); weston_log("remoted output '%s' enabled\n", output->name); return; err: free(modeline); free(output_name); if (output) weston_output_destroy(output); } static void load_remoting(struct weston_compositor *c, struct weston_config *wc) { const struct weston_remoting_api *api = NULL; int (*module_init)(struct weston_compositor *ec); struct weston_config_section *section = NULL; const char *section_name; /* read remote-output section in weston.ini */ while (weston_config_next_section(wc, §ion, §ion_name)) { if (strcmp(section_name, "remote-output")) continue; if (!api) { char *module_name; struct weston_config_section *core_section = weston_config_get_section(wc, "core", NULL, NULL); weston_config_section_get_string(core_section, "remoting", &module_name, "remoting-plugin.so"); module_init = weston_load_module(module_name, "weston_module_init"); free(module_name); if (!module_init) { weston_log("Can't load remoting-plugin\n"); return; } if (module_init(c) < 0) { weston_log("Remoting-plugin init failed\n"); return; } api = weston_remoting_get_api(c); if (!api) return; } remoted_output_init(c, section, api); } } static int drm_backend_pipewire_output_configure(struct weston_output *output, struct weston_config_section *section, char *modeline, const struct weston_pipewire_api *api) { char *seat = NULL; int ret; ret = api->set_mode(output, modeline); if (ret < 0) { weston_log("Cannot configure an output \"%s\" using " "weston_pipewire_api. Invalid mode\n", output->name); return -1; } wet_output_set_scale(output, section, 1, 0); wet_output_set_transform(output, section, WL_OUTPUT_TRANSFORM_NORMAL, UINT32_MAX); weston_config_section_get_string(section, "seat", &seat, ""); api->set_seat(output, seat); free(seat); return 0; } static void pipewire_output_init(struct weston_compositor *c, struct weston_config_section *section, const struct weston_pipewire_api *api) { struct weston_output *output = NULL; char *output_name, *modeline = NULL; int ret; weston_config_section_get_string(section, "name", &output_name, NULL); if (!output_name) return; weston_config_section_get_string(section, "mode", &modeline, "off"); if (strcmp(modeline, "off") == 0) goto err; output = api->create_output(c, output_name); if (!output) { weston_log("Cannot create pipewire output \"%s\".\n", output_name); goto err; } ret = drm_backend_pipewire_output_configure(output, section, modeline, api); if (ret < 0) { weston_log("Cannot configure pipewire output \"%s\".\n", output_name); goto err; } if (weston_output_enable(output) < 0) { weston_log("Enabling pipewire output \"%s\" failed.\n", output_name); goto err; } free(modeline); free(output_name); weston_log("pipewire output '%s' enabled\n", output->name); return; err: free(modeline); free(output_name); if (output) weston_output_destroy(output); } static void load_pipewire(struct weston_compositor *c, struct weston_config *wc) { const struct weston_pipewire_api *api = NULL; int (*module_init)(struct weston_compositor *ec); struct weston_config_section *section = NULL; const char *section_name; /* read pipewire-output section in weston.ini */ while (weston_config_next_section(wc, §ion, §ion_name)) { if (strcmp(section_name, "pipewire-output")) continue; if (!api) { char *module_name; struct weston_config_section *core_section = weston_config_get_section(wc, "core", NULL, NULL); weston_config_section_get_string(core_section, "pipewire", &module_name, "pipewire-plugin.so"); module_init = weston_load_module(module_name, "weston_module_init"); free(module_name); if (!module_init) { weston_log("Can't load pipewire-plugin\n"); return; } if (module_init(c) < 0) { weston_log("Pipewire-plugin init failed\n"); return; } api = weston_pipewire_get_api(c); if (!api) return; } pipewire_output_init(c, section, api); } } static int load_drm_backend(struct weston_compositor *c, int *argc, char **argv, struct weston_config *wc) { struct weston_drm_backend_config config = {{ 0, }}; struct weston_config_section *section; struct wet_compositor *wet = to_wet_compositor(c); int ret = 0; wet->drm_use_current_mode = false; section = weston_config_get_section(wc, "core", NULL, NULL); weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, false); const struct weston_option options[] = { { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, { WESTON_OPTION_STRING, "drm-device", 0, &config.specific_device }, { WESTON_OPTION_BOOLEAN, "current-mode", 0, &wet->drm_use_current_mode }, { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, }; parse_options(options, ARRAY_LENGTH(options), argc, argv); section = weston_config_get_section(wc, "core", NULL, NULL); weston_config_section_get_string(section, "gbm-format", &config.gbm_format, NULL); weston_config_section_get_uint(section, "pageflip-timeout", &config.pageflip_timeout, 0); weston_config_section_get_bool(section, "pixman-shadow", &config.use_pixman_shadow, true); config.base.struct_version = WESTON_DRM_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_drm_backend_config); config.configure_device = configure_input_device; wet->heads_changed_listener.notify = drm_heads_changed; weston_compositor_add_heads_changed_listener(c, &wet->heads_changed_listener); ret = weston_compositor_load_backend(c, WESTON_BACKEND_DRM, &config.base); /* remoting */ load_remoting(c, wc); /* pipewire */ load_pipewire(c, wc); free(config.gbm_format); free(config.seat_id); return ret; } static int headless_backend_output_configure(struct weston_output *output) { struct wet_output_config defaults = { .width = 1024, .height = 640, .scale = 1, .transform = WL_OUTPUT_TRANSFORM_NORMAL }; return wet_configure_windowed_output_from_config(output, &defaults); } static int load_headless_backend(struct weston_compositor *c, int *argc, char **argv, struct weston_config *wc) { const struct weston_windowed_output_api *api; struct weston_headless_backend_config config = {{ 0, }}; struct weston_config_section *section; bool no_outputs; int ret = 0; char *transform = NULL; struct wet_output_config *parsed_options = wet_init_parsed_options(c); if (!parsed_options) return -1; section = weston_config_get_section(wc, "core", NULL, NULL); weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, false); weston_config_section_get_bool(section, "use-gl", &config.use_gl, false); const struct weston_option options[] = { { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, { WESTON_OPTION_BOOLEAN, "use-gl", 0, &config.use_gl }, { WESTON_OPTION_STRING, "transform", 0, &transform }, { WESTON_OPTION_BOOLEAN, "no-outputs", 0, &no_outputs }, }; parse_options(options, ARRAY_LENGTH(options), argc, argv); if (transform) { if (weston_parse_transform(transform, &parsed_options->transform) < 0) { weston_log("Invalid transform \"%s\"\n", transform); parsed_options->transform = UINT32_MAX; } free(transform); } config.base.struct_version = WESTON_HEADLESS_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_headless_backend_config); wet_set_simple_head_configurator(c, headless_backend_output_configure); /* load the actual wayland backend and configure it */ ret = weston_compositor_load_backend(c, WESTON_BACKEND_HEADLESS, &config.base); if (ret < 0) return ret; if (!no_outputs) { api = weston_windowed_output_get_api(c); if (!api) { weston_log("Cannot use weston_windowed_output_api.\n"); return -1; } if (api->create_head(c, "headless") < 0) return -1; } return 0; } static int rdp_backend_output_configure(struct weston_output *output) { struct wet_compositor *compositor = to_wet_compositor(output->compositor); struct wet_output_config *parsed_options = compositor->parsed_options; const struct weston_rdp_output_api *api = weston_rdp_output_get_api(output->compositor); int width = 640; int height = 480; assert(parsed_options); if (!api) { weston_log("Cannot use weston_rdp_output_api.\n"); return -1; } if (parsed_options->width) width = parsed_options->width; if (parsed_options->height) height = parsed_options->height; weston_output_set_scale(output, 1); weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); if (api->output_set_size(output, width, height) < 0) { weston_log("Cannot configure output \"%s\" using weston_rdp_output_api.\n", output->name); return -1; } return 0; } static void weston_rdp_backend_config_init(struct weston_rdp_backend_config *config) { config->base.struct_version = WESTON_RDP_BACKEND_CONFIG_VERSION; config->base.struct_size = sizeof(struct weston_rdp_backend_config); config->bind_address = NULL; config->port = 3389; config->rdp_key = NULL; config->server_cert = NULL; config->server_key = NULL; config->env_socket = 0; config->no_clients_resize = 0; config->force_no_compression = 0; } static int load_rdp_backend(struct weston_compositor *c, int *argc, char *argv[], struct weston_config *wc) { struct weston_rdp_backend_config config = {{ 0, }}; int ret = 0; struct wet_output_config *parsed_options = wet_init_parsed_options(c); if (!parsed_options) return -1; weston_rdp_backend_config_init(&config); const struct weston_option rdp_options[] = { { WESTON_OPTION_BOOLEAN, "env-socket", 0, &config.env_socket }, { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, { WESTON_OPTION_STRING, "address", 0, &config.bind_address }, { WESTON_OPTION_INTEGER, "port", 0, &config.port }, { WESTON_OPTION_BOOLEAN, "no-clients-resize", 0, &config.no_clients_resize }, { WESTON_OPTION_STRING, "rdp4-key", 0, &config.rdp_key }, { WESTON_OPTION_STRING, "rdp-tls-cert", 0, &config.server_cert }, { WESTON_OPTION_STRING, "rdp-tls-key", 0, &config.server_key }, { WESTON_OPTION_BOOLEAN, "force-no-compression", 0, &config.force_no_compression }, }; parse_options(rdp_options, ARRAY_LENGTH(rdp_options), argc, argv); wet_set_simple_head_configurator(c, rdp_backend_output_configure); ret = weston_compositor_load_backend(c, WESTON_BACKEND_RDP, &config.base); free(config.bind_address); free(config.rdp_key); free(config.server_cert); free(config.server_key); return ret; } static int fbdev_backend_output_configure(struct weston_output *output) { struct weston_config *wc = wet_get_config(output->compositor); struct weston_config_section *section; section = weston_config_get_section(wc, "output", "name", "fbdev"); wet_output_set_transform(output, section, WL_OUTPUT_TRANSFORM_NORMAL, UINT32_MAX); weston_output_set_scale(output, 1); return 0; } static int load_fbdev_backend(struct weston_compositor *c, int *argc, char **argv, struct weston_config *wc) { struct weston_fbdev_backend_config config = {{ 0, }}; int ret = 0; const struct weston_option fbdev_options[] = { { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, { WESTON_OPTION_STRING, "device", 0, &config.device }, { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, }; parse_options(fbdev_options, ARRAY_LENGTH(fbdev_options), argc, argv); config.base.struct_version = WESTON_FBDEV_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_fbdev_backend_config); config.configure_device = configure_input_device; wet_set_simple_head_configurator(c, fbdev_backend_output_configure); /* load the actual wayland backend and configure it */ ret = weston_compositor_load_backend(c, WESTON_BACKEND_FBDEV, &config.base); free(config.device); return ret; } static int x11_backend_output_configure(struct weston_output *output) { struct wet_output_config defaults = { .width = 1024, .height = 600, .scale = 1, .transform = WL_OUTPUT_TRANSFORM_NORMAL }; return wet_configure_windowed_output_from_config(output, &defaults); } static int load_x11_backend(struct weston_compositor *c, int *argc, char **argv, struct weston_config *wc) { char *default_output; const struct weston_windowed_output_api *api; struct weston_x11_backend_config config = {{ 0, }}; struct weston_config_section *section; int ret = 0; int option_count = 1; int output_count = 0; char const *section_name; int i; struct wet_output_config *parsed_options = wet_init_parsed_options(c); if (!parsed_options) return -1; section = weston_config_get_section(wc, "core", NULL, NULL); weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, false); const struct weston_option options[] = { { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &config.fullscreen }, { WESTON_OPTION_INTEGER, "output-count", 0, &option_count }, { WESTON_OPTION_BOOLEAN, "no-input", 0, &config.no_input }, { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, }; parse_options(options, ARRAY_LENGTH(options), argc, argv); config.base.struct_version = WESTON_X11_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_x11_backend_config); wet_set_simple_head_configurator(c, x11_backend_output_configure); /* load the actual backend and configure it */ ret = weston_compositor_load_backend(c, WESTON_BACKEND_X11, &config.base); if (ret < 0) return ret; api = weston_windowed_output_get_api(c); if (!api) { weston_log("Cannot use weston_windowed_output_api.\n"); return -1; } section = NULL; while (weston_config_next_section(wc, §ion, §ion_name)) { char *output_name; if (output_count >= option_count) break; if (strcmp(section_name, "output") != 0) { continue; } weston_config_section_get_string(section, "name", &output_name, NULL); if (output_name == NULL || output_name[0] != 'X') { free(output_name); continue; } if (api->create_head(c, output_name) < 0) { free(output_name); return -1; } free(output_name); output_count++; } default_output = NULL; for (i = output_count; i < option_count; i++) { if (asprintf(&default_output, "screen%d", i) < 0) { return -1; } if (api->create_head(c, default_output) < 0) { free(default_output); return -1; } free(default_output); } return 0; } static int wayland_backend_output_configure(struct weston_output *output) { struct wet_output_config defaults = { .width = 1024, .height = 640, .scale = 1, .transform = WL_OUTPUT_TRANSFORM_NORMAL }; return wet_configure_windowed_output_from_config(output, &defaults); } static int load_wayland_backend(struct weston_compositor *c, int *argc, char **argv, struct weston_config *wc) { struct weston_wayland_backend_config config = {{ 0, }}; struct weston_config_section *section; const struct weston_windowed_output_api *api; const char *section_name; char *output_name = NULL; int count = 1; int ret = 0; int i; struct wet_output_config *parsed_options = wet_init_parsed_options(c); if (!parsed_options) return -1; config.cursor_size = 32; config.cursor_theme = NULL; config.display_name = NULL; section = weston_config_get_section(wc, "core", NULL, NULL); weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, false); const struct weston_option wayland_options[] = { { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, { WESTON_OPTION_STRING, "display", 0, &config.display_name }, { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, { WESTON_OPTION_INTEGER, "output-count", 0, &count }, { WESTON_OPTION_BOOLEAN, "fullscreen", 0, &config.fullscreen }, { WESTON_OPTION_BOOLEAN, "sprawl", 0, &config.sprawl }, }; parse_options(wayland_options, ARRAY_LENGTH(wayland_options), argc, argv); section = weston_config_get_section(wc, "shell", NULL, NULL); weston_config_section_get_string(section, "cursor-theme", &config.cursor_theme, NULL); weston_config_section_get_int(section, "cursor-size", &config.cursor_size, 32); config.base.struct_size = sizeof(struct weston_wayland_backend_config); config.base.struct_version = WESTON_WAYLAND_BACKEND_CONFIG_VERSION; /* load the actual wayland backend and configure it */ ret = weston_compositor_load_backend(c, WESTON_BACKEND_WAYLAND, &config.base); free(config.cursor_theme); free(config.display_name); if (ret < 0) return ret; api = weston_windowed_output_get_api(c); if (api == NULL) { /* We will just assume if load_backend() finished cleanly and * windowed_output_api is not present that wayland backend is * started with --sprawl or runs on fullscreen-shell. * In this case, all values are hardcoded, so nothing can be * configured; simply create and enable an output. */ wet_set_simple_head_configurator(c, NULL); return 0; } wet_set_simple_head_configurator(c, wayland_backend_output_configure); section = NULL; while (weston_config_next_section(wc, §ion, §ion_name)) { if (count == 0) break; if (strcmp(section_name, "output") != 0) { continue; } weston_config_section_get_string(section, "name", &output_name, NULL); if (output_name == NULL) continue; if (output_name[0] != 'W' || output_name[1] != 'L') { free(output_name); continue; } if (api->create_head(c, output_name) < 0) { free(output_name); return -1; } free(output_name); --count; } for (i = 0; i < count; i++) { if (asprintf(&output_name, "wayland%d", i) < 0) return -1; if (api->create_head(c, output_name) < 0) { free(output_name); return -1; } free(output_name); } return 0; } static int load_backend(struct weston_compositor *compositor, const char *backend, int *argc, char **argv, struct weston_config *config) { if (strstr(backend, "headless-backend.so")) return load_headless_backend(compositor, argc, argv, config); else if (strstr(backend, "rdp-backend.so")) return load_rdp_backend(compositor, argc, argv, config); else if (strstr(backend, "fbdev-backend.so")) return load_fbdev_backend(compositor, argc, argv, config); else if (strstr(backend, "drm-backend.so")) return load_drm_backend(compositor, argc, argv, config); else if (strstr(backend, "x11-backend.so")) return load_x11_backend(compositor, argc, argv, config); else if (strstr(backend, "wayland-backend.so")) return load_wayland_backend(compositor, argc, argv, config); weston_log("Error: unknown backend \"%s\"\n", backend); return -1; } static char * copy_command_line(int argc, char * const argv[]) { FILE *fp; char *str = NULL; size_t size = 0; int i; fp = open_memstream(&str, &size); if (!fp) return NULL; fprintf(fp, "%s", argv[0]); for (i = 1; i < argc; i++) fprintf(fp, " %s", argv[i]); fclose(fp); return str; } #if !defined(BUILD_XWAYLAND) int wet_load_xwayland(struct weston_compositor *comp) { return -1; } #endif static void weston_log_setup_scopes(struct weston_log_context *log_ctx, struct weston_log_subscriber *subscriber, const char *names) { assert(log_ctx); assert(subscriber); char *tokenize = strdup(names); char *token = strtok(tokenize, ","); while (token) { weston_log_subscribe(log_ctx, subscriber, token); token = strtok(NULL, ","); } free(tokenize); } static void flight_rec_key_binding_handler(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct weston_log_subscriber *flight_rec = data; weston_log_subscriber_display_flight_rec(flight_rec); } static void weston_log_subscribe_to_scopes(struct weston_log_context *log_ctx, struct weston_log_subscriber *logger, struct weston_log_subscriber *flight_rec, const char *log_scopes, const char *flight_rec_scopes) { if (log_scopes) weston_log_setup_scopes(log_ctx, logger, log_scopes); else weston_log_subscribe(log_ctx, logger, "log"); if (flight_rec_scopes) { weston_log_setup_scopes(log_ctx, flight_rec, flight_rec_scopes); } else { /* by default subscribe to 'log', and 'drm-backend' */ weston_log_subscribe(log_ctx, flight_rec, "log"); weston_log_subscribe(log_ctx, flight_rec, "drm-backend"); } } WL_EXPORT int wet_main(int argc, char *argv[]) { int ret = EXIT_FAILURE; char *cmdline; struct wl_display *display; struct wl_event_source *signals[4]; struct wl_event_loop *loop; int i, fd; char *backend = NULL; char *shell = NULL; bool xwayland = false; char *modules = NULL; char *option_modules = NULL; char *log = NULL; char *log_scopes = NULL; char *flight_rec_scopes = NULL; char *server_socket = NULL; int32_t idle_time = -1; int32_t help = 0; char *socket_name = NULL; int32_t version = 0; int32_t noconfig = 0; int32_t debug_protocol = 0; bool numlock_on; char *config_file = NULL; struct weston_config *config = NULL; struct weston_config_section *section; struct wl_client *primary_client; struct wl_listener primary_client_destroyed; struct weston_seat *seat; struct wet_compositor wet = { 0 }; struct weston_log_context *log_ctx = NULL; struct weston_log_subscriber *logger = NULL; struct weston_log_subscriber *flight_rec = NULL; sigset_t mask; bool wait_for_debugger = false; struct wl_protocol_logger *protologger = NULL; const struct weston_option core_options[] = { { WESTON_OPTION_STRING, "backend", 'B', &backend }, { WESTON_OPTION_STRING, "shell", 0, &shell }, { WESTON_OPTION_STRING, "socket", 'S', &socket_name }, { WESTON_OPTION_INTEGER, "idle-time", 'i', &idle_time }, #if defined(BUILD_XWAYLAND) { WESTON_OPTION_BOOLEAN, "xwayland", 0, &xwayland }, #endif { WESTON_OPTION_STRING, "modules", 0, &option_modules }, { WESTON_OPTION_STRING, "log", 0, &log }, { WESTON_OPTION_BOOLEAN, "help", 'h', &help }, { WESTON_OPTION_BOOLEAN, "version", 0, &version }, { WESTON_OPTION_BOOLEAN, "no-config", 0, &noconfig }, { WESTON_OPTION_STRING, "config", 'c', &config_file }, { WESTON_OPTION_BOOLEAN, "wait-for-debugger", 0, &wait_for_debugger }, { WESTON_OPTION_BOOLEAN, "debug", 0, &debug_protocol }, { WESTON_OPTION_STRING, "logger-scopes", 'l', &log_scopes }, { WESTON_OPTION_STRING, "flight-rec-scopes", 'f', &flight_rec_scopes }, }; wl_list_init(&wet.layoutput_list); os_fd_set_cloexec(fileno(stdin)); cmdline = copy_command_line(argc, argv); parse_options(core_options, ARRAY_LENGTH(core_options), &argc, argv); if (help) { free(cmdline); usage(EXIT_SUCCESS); } if (version) { printf(PACKAGE_STRING "\n"); free(cmdline); return EXIT_SUCCESS; } log_ctx = weston_log_ctx_compositor_create(); if (!log_ctx) { fprintf(stderr, "Failed to initialize weston debug framework.\n"); return EXIT_FAILURE; } log_scope = weston_compositor_add_log_scope(log_ctx, "log", "Weston and Wayland log\n", NULL, NULL, NULL); weston_log_file_open(log); weston_log_set_handler(vlog, vlog_continue); logger = weston_log_subscriber_create_log(weston_logfile); flight_rec = weston_log_subscriber_create_flight_rec(DEFAULT_FLIGHT_REC_SIZE); weston_log_subscribe_to_scopes(log_ctx, logger, flight_rec, log_scopes, flight_rec_scopes); weston_log("%s\n" STAMP_SPACE "%s\n" STAMP_SPACE "Bug reports to: %s\n" STAMP_SPACE "Build: %s\n", PACKAGE_STRING, PACKAGE_URL, PACKAGE_BUGREPORT, BUILD_ID); weston_log("Command line: %s\n", cmdline); free(cmdline); log_uname(); verify_xdg_runtime_dir(); display = wl_display_create(); if (display == NULL) { weston_log("fatal: failed to create display\n"); goto out_display; } loop = wl_display_get_event_loop(display); signals[0] = wl_event_loop_add_signal(loop, SIGTERM, on_term_signal, display); signals[1] = wl_event_loop_add_signal(loop, SIGINT, on_term_signal, display); signals[2] = wl_event_loop_add_signal(loop, SIGQUIT, on_term_signal, display); wl_list_init(&child_process_list); signals[3] = wl_event_loop_add_signal(loop, SIGCHLD, sigchld_handler, NULL); if (!signals[0] || !signals[1] || !signals[2] || !signals[3]) goto out_signals; /* Xwayland uses SIGUSR1 for communicating with weston. Since some weston plugins may create additional threads, set up any necessary signal blocking early so that these threads can inherit the settings when created. */ sigemptyset(&mask); sigaddset(&mask, SIGUSR1); pthread_sigmask(SIG_BLOCK, &mask, NULL); if (load_configuration(&config, noconfig, config_file) < 0) goto out_signals; wet.config = config; wet.parsed_options = NULL; section = weston_config_get_section(config, "core", NULL, NULL); if (!wait_for_debugger) { weston_config_section_get_bool(section, "wait-for-debugger", &wait_for_debugger, false); } if (wait_for_debugger) { weston_log("Weston PID is %ld - " "waiting for debugger, send SIGCONT to continue...\n", (long)getpid()); raise(SIGSTOP); } if (!backend) { weston_config_section_get_string(section, "backend", &backend, NULL); if (!backend) backend = weston_choose_default_backend(); } wet.compositor = weston_compositor_create(display, log_ctx, &wet); if (wet.compositor == NULL) { weston_log("fatal: failed to create compositor\n"); goto out; } segv_compositor = wet.compositor; protocol_scope = weston_compositor_add_log_scope(log_ctx, "proto", "Wayland protocol dump for all clients.\n", NULL, NULL, NULL); protologger = wl_display_add_protocol_logger(display, protocol_log_fn, NULL); if (debug_protocol) weston_compositor_enable_debug_protocol(wet.compositor); weston_compositor_add_debug_binding(wet.compositor, KEY_D, flight_rec_key_binding_handler, flight_rec); if (weston_compositor_init_config(wet.compositor, config) < 0) goto out; weston_config_section_get_bool(section, "require-input", &wet.compositor->require_input, true); if (load_backend(wet.compositor, backend, &argc, argv, config) < 0) { weston_log("fatal: failed to create compositor backend\n"); goto out; } weston_compositor_flush_heads_changed(wet.compositor); if (wet.init_failed) goto out; if (idle_time < 0) weston_config_section_get_int(section, "idle-time", &idle_time, -1); if (idle_time < 0) idle_time = 300; /* default idle timeout, in seconds */ wet.compositor->idle_time = idle_time; wet.compositor->default_pointer_grab = NULL; wet.compositor->exit = handle_exit; weston_compositor_log_capabilities(wet.compositor); server_socket = getenv("WAYLAND_SERVER_SOCKET"); if (server_socket) { weston_log("Running with single client\n"); if (!safe_strtoint(server_socket, &fd)) fd = -1; } else { fd = -1; } if (fd != -1) { primary_client = wl_client_create(display, fd); if (!primary_client) { weston_log("fatal: failed to add client: %s\n", strerror(errno)); goto out; } primary_client_destroyed.notify = handle_primary_client_destroyed; wl_client_add_destroy_listener(primary_client, &primary_client_destroyed); } else if (weston_create_listening_socket(display, socket_name)) { goto out; } if (!shell) weston_config_section_get_string(section, "shell", &shell, "desktop-shell.so"); if (wet_load_shell(wet.compositor, shell, &argc, argv) < 0) goto out; weston_config_section_get_string(section, "modules", &modules, ""); if (load_modules(wet.compositor, modules, &argc, argv, &xwayland) < 0) goto out; if (load_modules(wet.compositor, option_modules, &argc, argv, &xwayland) < 0) goto out; if (!xwayland) { weston_config_section_get_bool(section, "xwayland", &xwayland, false); } if (xwayland) { if (wet_load_xwayland(wet.compositor) < 0) goto out; } section = weston_config_get_section(config, "keyboard", NULL, NULL); weston_config_section_get_bool(section, "numlock-on", &numlock_on, false); if (numlock_on) { wl_list_for_each(seat, &wet.compositor->seat_list, link) { struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); if (keyboard) weston_keyboard_set_locks(keyboard, WESTON_NUM_LOCK, WESTON_NUM_LOCK); } } for (i = 1; i < argc; i++) weston_log("fatal: unhandled option: %s\n", argv[i]); if (argc > 1) goto out; weston_compositor_wake(wet.compositor); wl_display_run(display); /* Allow for setting return exit code after * wl_display_run returns normally. This is * useful for devs/testers and automated tests * that want to indicate failure status to * testing infrastructure above */ ret = wet.compositor->exit_code; out: wet_compositor_destroy_layout(&wet); /* free(NULL) is valid, and it won't be NULL if it's used */ free(wet.parsed_options); if (protologger) wl_protocol_logger_destroy(protologger); weston_compositor_log_scope_destroy(protocol_scope); protocol_scope = NULL; weston_compositor_tear_down(wet.compositor); weston_compositor_log_scope_destroy(log_scope); log_scope = NULL; weston_log_ctx_compositor_destroy(wet.compositor); weston_compositor_destroy(wet.compositor); weston_log_subscriber_destroy_log(logger); weston_log_subscriber_destroy_flight_rec(flight_rec); out_signals: for (i = ARRAY_LENGTH(signals) - 1; i >= 0; i--) if (signals[i]) wl_event_source_remove(signals[i]); wl_display_destroy(display); out_display: weston_log_file_close(); if (config) weston_config_destroy(config); free(config_file); free(backend); free(shell); free(socket_name); free(option_modules); free(log); free(modules); return ret; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.338963 weston-8.0.0/compositor/meson.build0000644000175000017460000001141700000000000017663 0ustar00simonwheel00000000000000srcs_weston = [ git_version_h, 'main.c', 'text-backend.c', 'weston-screenshooter.c', text_input_unstable_v1_server_protocol_h, text_input_unstable_v1_protocol_c, input_method_unstable_v1_server_protocol_h, input_method_unstable_v1_protocol_c, weston_screenshooter_server_protocol_h, weston_screenshooter_protocol_c, ] deps_weston = [ dep_libshared, dep_libweston_public, dep_libinput, dep_libevdev, dep_libdl, dep_threads, ] if get_option('xwayland') config_h.set('BUILD_XWAYLAND', '1') srcs_weston += 'xwayland.c' config_h.set_quoted('XSERVER_PATH', get_option('xwayland-path')) endif libexec_weston = shared_library( 'exec_weston', sources: srcs_weston, include_directories: common_inc, dependencies: deps_weston, install_dir: dir_module_weston, install: true, version: '0.0.0', soversion: 0 ) dep_libexec_weston = declare_dependency( link_with: libexec_weston, include_directories: [ include_directories('.'), public_inc ], dependencies: dep_libweston_public ) exe_weston = executable( 'weston', 'executable.c', include_directories: common_inc, dependencies: dep_libexec_weston, install_rpath: dir_module_weston, install: true ) install_headers('weston.h', subdir: 'weston') pkgconfig.generate( filebase: 'weston', name: 'Weston Plugin API', version: version_weston, description: 'Header files for Weston plugin development', requires_private: [ lib_weston ], variables: [ 'libexecdir=' + join_paths('${prefix}', get_option('libexecdir')), 'pkglibexecdir=${libexecdir}/weston' ], subdirs: 'weston' ) install_data( 'weston.desktop', install_dir: join_paths(dir_data, 'wayland-sessions') ) if get_option('screenshare') srcs_screenshare = [ 'screen-share.c', fullscreen_shell_unstable_v1_client_protocol_h, fullscreen_shell_unstable_v1_protocol_c, ] deps_screenshare = [ dep_libexec_weston, dep_libshared, dep_libweston_public, dep_libweston_private_h, # XXX: https://gitlab.freedesktop.org/wayland/weston/issues/292 dep_wayland_client, ] plugin_screenshare = shared_library( 'screen-share', srcs_screenshare, include_directories: common_inc, dependencies: deps_screenshare, name_prefix: '', install: true, install_dir: dir_module_weston ) env_modmap += 'screen-share.so=@0@;'.format(plugin_screenshare.full_path()) endif if get_option('color-management-lcms') config_h.set('HAVE_LCMS', '1') srcs_lcms = [ 'cms-static.c', 'cms-helper.c', ] dep_lcms2 = dependency('lcms2', required: false) if not dep_lcms2.found() error('cms-static requires lcms2 which was not found. Or, you can use \'-Dcolor-management-lcms=false\'.') endif plugin_lcms = shared_library( 'cms-static', srcs_lcms, include_directories: common_inc, dependencies: [ dep_libexec_weston, dep_libweston_public, dep_lcms2 ], name_prefix: '', install: true, install_dir: dir_module_weston ) env_modmap += 'cms-static.so=@0@;'.format(plugin_lcms.full_path()) endif if get_option('color-management-colord') if not get_option('color-management-lcms') error('LCMS must be enabled to support colord') endif srcs_colord = [ 'cms-colord.c', 'cms-helper.c', ] dep_colord = dependency('colord', version: '>= 0.1.27', required: false) if not dep_colord.found() error('cms-colord requires colord >= 0.1.27 which was not found. Or, you can use \'-Dcolor-management-colord=false\'.') endif plugin_colord_deps = [ dep_libweston_public, dep_colord ] foreach depname : [ 'glib-2.0', 'gobject-2.0' ] dep = dependency(depname, required: false) if not dep.found() error('cms-colord requires \'@0@\' which was not found. If you rather not build this, set \'-Dcolor-management-colord=false\'.'.format(depname)) endif plugin_colord_deps += dep endforeach plugin_colord = shared_library( 'cms-colord', srcs_colord, include_directories: common_inc, dependencies: plugin_colord_deps, name_prefix: '', override_options: [ 'b_lundef=false' ], install: true, install_dir: dir_module_weston ) env_modmap += 'cms-colord.so=@0@;'.format(plugin_colord.full_path()) endif if get_option('systemd') dep_libsystemd = dependency('libsystemd', required: false) if not dep_libsystemd.found() error('systemd-notify requires libsystemd which was not found. Or, you can use \'-Dsystemd=false\'.') endif plugin_systemd_notify = shared_library( 'systemd-notify', 'systemd-notify.c', include_directories: common_inc, dependencies: [ dep_libweston_public, dep_libsystemd ], name_prefix: '', install: true, install_dir: dir_module_weston ) env_modmap += 'systemd-notify.so=@0@;'.format(plugin_systemd_notify.full_path()) endif weston_ini_config = configuration_data() weston_ini_config.set('bindir', dir_bin) weston_ini_config.set('libexecdir', dir_libexec) configure_file( input: '../weston.ini.in', output: 'weston.ini', configuration: weston_ini_config ) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.338963 weston-8.0.0/compositor/screen-share.c0000644000175000017460000007344600000000000020256 0ustar00simonwheel00000000000000/* * Copyright © 2008-2011 Kristian Høgsberg * Copyright © 2014 Jason Ekstrand * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backend.h" #include "libweston-internal.h" #include "weston.h" #include "shared/helpers.h" #include "shared/os-compatibility.h" #include "shared/timespec-util.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" struct shared_output { struct weston_output *output; struct wl_listener output_destroyed; struct wl_list seat_list; struct { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct wl_shm *shm; uint32_t shm_formats; struct zwp_fullscreen_shell_v1 *fshell; struct wl_output *output; struct wl_surface *surface; struct wl_callback *frame_cb; struct zwp_fullscreen_shell_mode_feedback_v1 *mode_feedback; } parent; struct wl_event_source *event_source; struct wl_listener frame_listener; struct { int32_t width, height; struct wl_list buffers; struct wl_list free_buffers; } shm; int cache_dirty; pixman_image_t *cache_image; uint32_t *tmp_data; size_t tmp_data_size; }; struct ss_seat { struct weston_seat base; struct shared_output *output; struct wl_list link; uint32_t id; struct { struct wl_seat *seat; struct wl_pointer *pointer; struct wl_keyboard *keyboard; } parent; enum weston_key_state_update keyboard_state_update; uint32_t key_serial; }; struct ss_shm_buffer { struct shared_output *output; struct wl_list link; struct wl_list free_link; struct wl_buffer *buffer; void *data; size_t size; pixman_region32_t damage; pixman_image_t *pm_image; }; struct screen_share { struct weston_compositor *compositor; /* XXX: missing compositor destroy listener * https://gitlab.freedesktop.org/wayland/weston/issues/298 */ char *command; }; static void ss_seat_handle_pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y) { struct ss_seat *seat = data; /* No transformation of input position is required here because we are * always receiving the input in the same coordinates as the output. */ notify_pointer_focus(&seat->base, NULL, 0, 0); } static void ss_seat_handle_pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { struct ss_seat *seat = data; notify_pointer_focus(&seat->base, NULL, 0, 0); } static void ss_seat_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { struct ss_seat *seat = data; struct timespec ts; timespec_from_msec(&ts, time); /* No transformation of input position is required here because we are * always receiving the input in the same coordinates as the output. */ notify_motion_absolute(&seat->base, &ts, wl_fixed_to_double(x), wl_fixed_to_double(y)); notify_pointer_frame(&seat->base); } static void ss_seat_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { struct ss_seat *seat = data; struct timespec ts; timespec_from_msec(&ts, time); notify_button(&seat->base, &ts, button, state); notify_pointer_frame(&seat->base); } static void ss_seat_handle_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { struct ss_seat *seat = data; struct weston_pointer_axis_event weston_event; struct timespec ts; weston_event.axis = axis; weston_event.value = wl_fixed_to_double(value); weston_event.has_discrete = false; timespec_from_msec(&ts, time); notify_axis(&seat->base, &ts, &weston_event); notify_pointer_frame(&seat->base); } static const struct wl_pointer_listener ss_seat_pointer_listener = { ss_seat_handle_pointer_enter, ss_seat_handle_pointer_leave, ss_seat_handle_motion, ss_seat_handle_button, ss_seat_handle_axis, }; static void ss_seat_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int fd, uint32_t size) { struct ss_seat *seat = data; struct xkb_keymap *keymap; char *map_str; if (!data) goto error_no_seat; if (format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (map_str == MAP_FAILED) { weston_log("mmap failed: %s\n", strerror(errno)); goto error; } keymap = xkb_keymap_new_from_string(seat->base.compositor->xkb_context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, 0); munmap(map_str, size); if (!keymap) { weston_log("failed to compile keymap\n"); goto error; } seat->keyboard_state_update = STATE_UPDATE_NONE; } else if (format == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { weston_log("No keymap provided; falling back to default\n"); keymap = NULL; seat->keyboard_state_update = STATE_UPDATE_AUTOMATIC; } else { weston_log("Invalid keymap\n"); goto error; } close(fd); if (seat->base.keyboard_device_count) weston_seat_update_keymap(&seat->base, keymap); else weston_seat_init_keyboard(&seat->base, keymap); xkb_keymap_unref(keymap); return; error: wl_keyboard_release(seat->parent.keyboard); error_no_seat: close(fd); } static void ss_seat_handle_keyboard_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { struct ss_seat *seat = data; /* XXX: If we get a modifier event immediately before the focus, * we should try to keep the same serial. */ notify_keyboard_focus_in(&seat->base, keys, STATE_UPDATE_AUTOMATIC); } static void ss_seat_handle_keyboard_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { struct ss_seat *seat = data; notify_keyboard_focus_out(&seat->base); } static void ss_seat_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { struct ss_seat *seat = data; struct timespec ts; timespec_from_msec(&ts, time); seat->key_serial = serial; notify_key(&seat->base, &ts, key, state ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED, seat->keyboard_state_update); } static void ss_seat_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial_in, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { struct ss_seat *seat = data; struct weston_compositor *c = seat->base.compositor; struct weston_keyboard *keyboard; uint32_t serial_out; /* If we get a key event followed by a modifier event with the * same serial number, then we try to preserve those semantics by * reusing the same serial number on the way out too. */ if (serial_in == seat->key_serial) serial_out = wl_display_get_serial(c->wl_display); else serial_out = wl_display_next_serial(c->wl_display); keyboard = weston_seat_get_keyboard(&seat->base); xkb_state_update_mask(keyboard->xkb_state.state, mods_depressed, mods_latched, mods_locked, 0, 0, group); notify_modifiers(&seat->base, serial_out); } static const struct wl_keyboard_listener ss_seat_keyboard_listener = { ss_seat_handle_keymap, ss_seat_handle_keyboard_enter, ss_seat_handle_keyboard_leave, ss_seat_handle_key, ss_seat_handle_modifiers, }; static void ss_seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { struct ss_seat *ss_seat = data; if ((caps & WL_SEAT_CAPABILITY_POINTER) && !ss_seat->parent.pointer) { ss_seat->parent.pointer = wl_seat_get_pointer(seat); wl_pointer_set_user_data(ss_seat->parent.pointer, ss_seat); wl_pointer_add_listener(ss_seat->parent.pointer, &ss_seat_pointer_listener, ss_seat); weston_seat_init_pointer(&ss_seat->base); } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && ss_seat->parent.pointer) { wl_pointer_destroy(ss_seat->parent.pointer); ss_seat->parent.pointer = NULL; } if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !ss_seat->parent.keyboard) { ss_seat->parent.keyboard = wl_seat_get_keyboard(seat); wl_keyboard_set_user_data(ss_seat->parent.keyboard, ss_seat); wl_keyboard_add_listener(ss_seat->parent.keyboard, &ss_seat_keyboard_listener, ss_seat); } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && ss_seat->parent.keyboard) { wl_keyboard_destroy(ss_seat->parent.keyboard); ss_seat->parent.keyboard = NULL; } } static const struct wl_seat_listener ss_seat_listener = { ss_seat_handle_capabilities, }; static struct ss_seat * ss_seat_create(struct shared_output *so, uint32_t id) { struct ss_seat *seat; seat = zalloc(sizeof *seat); if (seat == NULL) return NULL; weston_seat_init(&seat->base, so->output->compositor, "default"); seat->output = so; seat->id = id; seat->parent.seat = wl_registry_bind(so->parent.registry, id, &wl_seat_interface, 1); wl_list_insert(so->seat_list.prev, &seat->link); wl_seat_add_listener(seat->parent.seat, &ss_seat_listener, seat); wl_seat_set_user_data(seat->parent.seat, seat); return seat; } static void ss_seat_destroy(struct ss_seat *seat) { if (seat->parent.pointer) wl_pointer_release(seat->parent.pointer); if (seat->parent.keyboard) wl_keyboard_release(seat->parent.keyboard); wl_seat_destroy(seat->parent.seat); wl_list_remove(&seat->link); weston_seat_release(&seat->base); free(seat); } static void ss_shm_buffer_destroy(struct ss_shm_buffer *buffer) { pixman_image_unref(buffer->pm_image); wl_buffer_destroy(buffer->buffer); munmap(buffer->data, buffer->size); pixman_region32_fini(&buffer->damage); wl_list_remove(&buffer->link); wl_list_remove(&buffer->free_link); free(buffer); } static void buffer_release(void *data, struct wl_buffer *buffer) { struct ss_shm_buffer *sb = data; if (sb->output) { wl_list_insert(&sb->output->shm.free_buffers, &sb->free_link); } else { ss_shm_buffer_destroy(sb); } } static const struct wl_buffer_listener buffer_listener = { buffer_release }; static struct ss_shm_buffer * shared_output_get_shm_buffer(struct shared_output *so) { struct ss_shm_buffer *sb, *bnext; struct wl_shm_pool *pool; int width, height, stride; int fd; unsigned char *data; width = so->output->width; height = so->output->height; stride = width * 4; /* If the size of the output changed, we free the old buffers and * make new ones. */ if (so->shm.width != width || so->shm.height != height) { /* Destroy free buffers */ wl_list_for_each_safe(sb, bnext, &so->shm.free_buffers, free_link) ss_shm_buffer_destroy(sb); /* Orphan in-use buffers so they get destroyed */ wl_list_for_each(sb, &so->shm.buffers, link) sb->output = NULL; so->shm.width = width; so->shm.height = height; } if (!wl_list_empty(&so->shm.free_buffers)) { sb = container_of(so->shm.free_buffers.next, struct ss_shm_buffer, free_link); wl_list_remove(&sb->free_link); wl_list_init(&sb->free_link); return sb; } fd = os_create_anonymous_file(height * stride); if (fd < 0) { weston_log("os_create_anonymous_file: %s\n", strerror(errno)); return NULL; } data = mmap(NULL, height * stride, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { weston_log("mmap: %s\n", strerror(errno)); goto out_close; } sb = zalloc(sizeof *sb); if (!sb) goto out_unmap; sb->output = so; wl_list_init(&sb->free_link); wl_list_insert(&so->shm.buffers, &sb->link); pixman_region32_init_rect(&sb->damage, 0, 0, width, height); sb->data = data; sb->size = height * stride; pool = wl_shm_create_pool(so->parent.shm, fd, sb->size); sb->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); wl_buffer_add_listener(sb->buffer, &buffer_listener, sb); wl_shm_pool_destroy(pool); close(fd); fd = -1; memset(data, 0, sb->size); sb->pm_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, (uint32_t *)data, stride); if (!sb->pm_image) goto out_pixman_error; return sb; out_pixman_error: pixman_region32_fini(&sb->damage); out_unmap: munmap(data, height * stride); out_close: if (fd != -1) close(fd); return NULL; } static void output_compute_transform(struct weston_output *output, pixman_transform_t *transform) { pixman_fixed_t fw, fh; pixman_transform_init_identity(transform); fw = pixman_int_to_fixed(output->width); fh = pixman_int_to_fixed(output->height); switch (output->transform) { case WL_OUTPUT_TRANSFORM_FLIPPED: case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_FLIPPED_180: case WL_OUTPUT_TRANSFORM_FLIPPED_270: pixman_transform_scale(transform, NULL, pixman_int_to_fixed (-1), pixman_int_to_fixed (1)); pixman_transform_translate(transform, NULL, fw, 0); } switch (output->transform) { default: case WL_OUTPUT_TRANSFORM_NORMAL: case WL_OUTPUT_TRANSFORM_FLIPPED: break; case WL_OUTPUT_TRANSFORM_90: case WL_OUTPUT_TRANSFORM_FLIPPED_90: pixman_transform_rotate(transform, NULL, 0, pixman_fixed_1); pixman_transform_translate(transform, NULL, fh, 0); break; case WL_OUTPUT_TRANSFORM_180: case WL_OUTPUT_TRANSFORM_FLIPPED_180: pixman_transform_rotate(transform, NULL, -pixman_fixed_1, 0); pixman_transform_translate(transform, NULL, fw, fh); break; case WL_OUTPUT_TRANSFORM_270: case WL_OUTPUT_TRANSFORM_FLIPPED_270: pixman_transform_rotate(transform, NULL, 0, -pixman_fixed_1); pixman_transform_translate(transform, NULL, 0, fw); break; } pixman_transform_scale(transform, NULL, pixman_fixed_1 * output->current_scale, pixman_fixed_1 * output->current_scale); } static void shared_output_destroy(struct shared_output *so); static int shared_output_ensure_tmp_data(struct shared_output *so, pixman_region32_t *region) { pixman_box32_t *ext; size_t size; if (!pixman_region32_not_empty(region)) return 0; ext = pixman_region32_extents(region); /* Damage is in output coordinates. * * We are multiplying by 4 because the temporary data needs to be able * to store an 32 bit-per-pixel buffer. */ size = 4 * (ext->x2 - ext->x1) * (ext->y2 - ext->y1) * so->output->current_scale * so->output->current_scale; if (so->tmp_data != NULL && size <= so->tmp_data_size) return 0; free(so->tmp_data); so->tmp_data = malloc(size); if (so->tmp_data == NULL) { so->tmp_data_size = 0; errno = ENOMEM; return -1; } so->tmp_data_size = size; return 0; } static void shared_output_update(struct shared_output *so); static void shared_output_frame_callback(void *data, struct wl_callback *cb, uint32_t time) { struct shared_output *so = data; if (cb != so->parent.frame_cb) return; wl_callback_destroy(cb); so->parent.frame_cb = NULL; shared_output_update(so); } static const struct wl_callback_listener shared_output_frame_listener = { shared_output_frame_callback }; static void shared_output_update(struct shared_output *so) { struct ss_shm_buffer *sb; pixman_box32_t *r; int i, nrects; pixman_transform_t transform; /* Only update if we need to */ if (!so->cache_dirty || so->parent.frame_cb) return; sb = shared_output_get_shm_buffer(so); if (sb == NULL) { shared_output_destroy(so); return; } output_compute_transform(so->output, &transform); pixman_image_set_transform(so->cache_image, &transform); pixman_image_set_clip_region32(sb->pm_image, &sb->damage); if (so->output->current_scale == 1) { pixman_image_set_filter(so->cache_image, PIXMAN_FILTER_NEAREST, NULL, 0); } else { pixman_image_set_filter(so->cache_image, PIXMAN_FILTER_BILINEAR, NULL, 0); } pixman_image_composite32(PIXMAN_OP_SRC, so->cache_image, /* src */ NULL, /* mask */ sb->pm_image, /* dest */ 0, 0, /* src_x, src_y */ 0, 0, /* mask_x, mask_y */ 0, 0, /* dest_x, dest_y */ so->output->width, /* width */ so->output->height /* height */); pixman_image_set_transform(sb->pm_image, NULL); pixman_image_set_clip_region32(sb->pm_image, NULL); r = pixman_region32_rectangles(&sb->damage, &nrects); for (i = 0; i < nrects; ++i) wl_surface_damage(so->parent.surface, r[i].x1, r[i].y1, r[i].x2 - r[i].x1, r[i].y2 - r[i].y1); wl_surface_attach(so->parent.surface, sb->buffer, 0, 0); so->parent.frame_cb = wl_surface_frame(so->parent.surface); wl_callback_add_listener(so->parent.frame_cb, &shared_output_frame_listener, so); wl_surface_commit(so->parent.surface); wl_callback_destroy(wl_display_sync(so->parent.display)); wl_display_flush(so->parent.display); /* Clear the buffer damage */ pixman_region32_fini(&sb->damage); pixman_region32_init(&sb->damage); } static void shm_handle_format(void *data, struct wl_shm *wl_shm, uint32_t format) { struct shared_output *so = data; so->parent.shm_formats |= (1 << format); } struct wl_shm_listener shm_listener = { shm_handle_format }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct shared_output *so = data; if (strcmp(interface, "wl_compositor") == 0) { so->parent.compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); } else if (strcmp(interface, "wl_output") == 0 && !so->parent.output) { so->parent.output = wl_registry_bind(registry, id, &wl_output_interface, 1); } else if (strcmp(interface, "wl_seat") == 0) { ss_seat_create(so, id); } else if (strcmp(interface, "wl_shm") == 0) { so->parent.shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); wl_shm_add_listener(so->parent.shm, &shm_listener, so); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { so->parent.fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct shared_output *so = data; struct ss_seat *seat, *next; wl_list_for_each_safe(seat, next, &so->seat_list, link) if (seat->id == name) ss_seat_destroy(seat); } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static int shared_output_handle_event(int fd, uint32_t mask, void *data) { struct shared_output *so = data; int count = 0; if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { shared_output_destroy(so); return 0; } if (mask & WL_EVENT_READABLE) count = wl_display_dispatch(so->parent.display); if (mask & WL_EVENT_WRITABLE) wl_display_flush(so->parent.display); if (mask == 0) { count = wl_display_dispatch_pending(so->parent.display); wl_display_flush(so->parent.display); } return count; } static void output_destroyed(struct wl_listener *l, void *data) { struct shared_output *so; so = container_of(l, struct shared_output, output_destroyed); shared_output_destroy(so); } static void mode_feedback_ok(void *data, struct zwp_fullscreen_shell_mode_feedback_v1 *fb) { struct shared_output *so = data; zwp_fullscreen_shell_mode_feedback_v1_destroy(so->parent.mode_feedback); } static void mode_feedback_failed(void *data, struct zwp_fullscreen_shell_mode_feedback_v1 *fb) { struct shared_output *so = data; zwp_fullscreen_shell_mode_feedback_v1_destroy(so->parent.mode_feedback); weston_log("Screen share failed: present_surface_for_mode failed\n"); shared_output_destroy(so); } struct zwp_fullscreen_shell_mode_feedback_v1_listener mode_feedback_listener = { mode_feedback_ok, mode_feedback_failed, mode_feedback_ok, }; static void shared_output_repainted(struct wl_listener *listener, void *data) { struct shared_output *so = container_of(listener, struct shared_output, frame_listener); pixman_region32_t damage; pixman_region32_t *current_damage = data; struct ss_shm_buffer *sb; int32_t x, y, width, height, stride; int i, nrects, do_yflip, y_orig; pixman_box32_t *r; pixman_image_t *damaged_image; pixman_transform_t transform; width = so->output->current_mode->width; height = so->output->current_mode->height; stride = width; if (!so->cache_image || pixman_image_get_width(so->cache_image) != width || pixman_image_get_height(so->cache_image) != height) { if (so->cache_image) pixman_image_unref(so->cache_image); so->cache_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, NULL, stride); if (!so->cache_image) goto err_shared_output; pixman_region32_init_rect(&damage, 0, 0, width, height); } else { /* Damage in output coordinates */ pixman_region32_init(&damage); pixman_region32_intersect(&damage, &so->output->region, current_damage); pixman_region32_translate(&damage, -so->output->x, -so->output->y); } /* Apply damage to all buffers */ wl_list_for_each(sb, &so->shm.buffers, link) pixman_region32_union(&sb->damage, &sb->damage, &damage); /* Transform to buffer coordinates */ weston_transformed_region(so->output->width, so->output->height, so->output->transform, so->output->current_scale, &damage, &damage); if (shared_output_ensure_tmp_data(so, &damage) < 0) goto err_pixman_init; do_yflip = !!(so->output->compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP); r = pixman_region32_rectangles(&damage, &nrects); for (i = 0; i < nrects; ++i) { x = r[i].x1; y = r[i].y1; width = r[i].x2 - r[i].x1; height = r[i].y2 - r[i].y1; if (do_yflip) y_orig = so->output->current_mode->height - r[i].y2; else y_orig = y; so->output->compositor->renderer->read_pixels( so->output, PIXMAN_a8r8g8b8, so->tmp_data, x, y_orig, width, height); damaged_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, so->tmp_data, (PIXMAN_FORMAT_BPP(PIXMAN_a8r8g8b8) / 8) * width); if (!damaged_image) goto err_pixman_init; if (do_yflip) { pixman_transform_init_scale(&transform, pixman_fixed_1, pixman_fixed_minus_1); pixman_transform_translate(&transform, NULL, 0, pixman_int_to_fixed(height)); pixman_image_set_transform(damaged_image, &transform); } pixman_image_composite32(PIXMAN_OP_SRC, damaged_image, NULL, so->cache_image, 0, 0, 0, 0, x, y, width, height); pixman_image_unref(damaged_image); } so->cache_dirty = 1; pixman_region32_fini(&damage); shared_output_update(so); return; err_pixman_init: pixman_region32_fini(&damage); err_shared_output: shared_output_destroy(so); } static struct shared_output * shared_output_create(struct weston_output *output, int parent_fd) { struct shared_output *so; struct wl_event_loop *loop; struct ss_seat *seat, *tmp; int epoll_fd; so = zalloc(sizeof *so); if (so == NULL) goto err_close; wl_list_init(&so->seat_list); so->parent.display = wl_display_connect_to_fd(parent_fd); if (!so->parent.display) goto err_alloc; so->parent.registry = wl_display_get_registry(so->parent.display); if (!so->parent.registry) goto err_display; wl_registry_add_listener(so->parent.registry, ®istry_listener, so); wl_display_roundtrip(so->parent.display); if (so->parent.shm == NULL) { weston_log("Screen share failed: No wl_shm found\n"); goto err_display; } if (so->parent.fshell == NULL) { weston_log("Screen share failed: " "Parent does not support wl_fullscreen_shell\n"); goto err_display; } if (so->parent.compositor == NULL) { weston_log("Screen share failed: No wl_compositor found\n"); goto err_display; } /* Get SHM formats */ wl_display_roundtrip(so->parent.display); if (!(so->parent.shm_formats & (1 << WL_SHM_FORMAT_XRGB8888))) { weston_log("Screen share failed: " "WL_SHM_FORMAT_XRGB8888 not available\n"); goto err_display; } so->parent.surface = wl_compositor_create_surface(so->parent.compositor); if (!so->parent.surface) { weston_log("Screen share failed: %s\n", strerror(errno)); goto err_display; } so->parent.mode_feedback = zwp_fullscreen_shell_v1_present_surface_for_mode(so->parent.fshell, so->parent.surface, so->parent.output, output->current_mode->refresh); if (!so->parent.mode_feedback) { weston_log("Screen share failed: %s\n", strerror(errno)); goto err_display; } zwp_fullscreen_shell_mode_feedback_v1_add_listener(so->parent.mode_feedback, &mode_feedback_listener, so); loop = wl_display_get_event_loop(output->compositor->wl_display); epoll_fd = wl_display_get_fd(so->parent.display); so->event_source = wl_event_loop_add_fd(loop, epoll_fd, WL_EVENT_READABLE, shared_output_handle_event, so); if (!so->event_source) { weston_log("Screen share failed: %s\n", strerror(errno)); goto err_display; } /* Ok, everything's created. We should be good to go */ wl_list_init(&so->shm.buffers); wl_list_init(&so->shm.free_buffers); so->output = output; so->output_destroyed.notify = output_destroyed; wl_signal_add(&so->output->destroy_signal, &so->output_destroyed); so->frame_listener.notify = shared_output_repainted; wl_signal_add(&output->frame_signal, &so->frame_listener); weston_output_disable_planes_incr(output); weston_output_damage(output); return so; err_display: wl_list_for_each_safe(seat, tmp, &so->seat_list, link) ss_seat_destroy(seat); wl_display_disconnect(so->parent.display); err_alloc: free(so); err_close: close(parent_fd); return NULL; } static void shared_output_destroy(struct shared_output *so) { struct ss_shm_buffer *buffer, *bnext; weston_output_disable_planes_decr(so->output); wl_list_for_each_safe(buffer, bnext, &so->shm.buffers, link) ss_shm_buffer_destroy(buffer); wl_list_for_each_safe(buffer, bnext, &so->shm.free_buffers, free_link) ss_shm_buffer_destroy(buffer); wl_display_disconnect(so->parent.display); wl_event_source_remove(so->event_source); wl_list_remove(&so->output_destroyed.link); wl_list_remove(&so->frame_listener.link); pixman_image_unref(so->cache_image); free(so->tmp_data); free(so); } static struct shared_output * weston_output_share(struct weston_output *output, const char* command) { int sv[2]; char str[32]; pid_t pid; sigset_t allsigs; char *const argv[] = { "/bin/sh", "-c", (char*)command, NULL }; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { weston_log("weston_output_share: socketpair failed: %s\n", strerror(errno)); return NULL; } pid = fork(); if (pid == -1) { close(sv[0]); close(sv[1]); weston_log("weston_output_share: fork failed: %s\n", strerror(errno)); return NULL; } if (pid == 0) { /* do not give our signal mask to the new process */ sigfillset(&allsigs); sigprocmask(SIG_UNBLOCK, &allsigs, NULL); /* Launch clients as the user. Do not launch clients with * wrong euid. */ if (seteuid(getuid()) == -1) { weston_log("weston_output_share: setuid failed: %s\n", strerror(errno)); abort(); } sv[1] = dup(sv[1]); if (sv[1] == -1) { weston_log("weston_output_share: dup failed: %s\n", strerror(errno)); abort(); } snprintf(str, sizeof str, "%d", sv[1]); setenv("WAYLAND_SERVER_SOCKET", str, 1); execv(argv[0], argv); weston_log("weston_output_share: exec failed: %s\n", strerror(errno)); abort(); } else { close(sv[1]); return shared_output_create(output, sv[0]); } return NULL; } static struct weston_output * weston_output_find(struct weston_compositor *c, int32_t x, int32_t y) { struct weston_output *output; wl_list_for_each(output, &c->output_list, link) { if (x >= output->x && y >= output->y && x < output->x + output->width && y < output->y + output->height) return output; } return NULL; } static void share_output_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct weston_output *output; struct weston_pointer *pointer; struct screen_share *ss = data; pointer = weston_seat_get_pointer(keyboard->seat); if (!pointer) { weston_log("Cannot pick output: Seat does not have pointer\n"); return; } output = weston_output_find(pointer->seat->compositor, wl_fixed_to_int(pointer->x), wl_fixed_to_int(pointer->y)); if (!output) { weston_log("Cannot pick output: Pointer not on any output\n"); return; } weston_output_share(output, ss->command); } WL_EXPORT int wet_module_init(struct weston_compositor *compositor, int *argc, char *argv[]) { struct screen_share *ss; struct weston_config *config = wet_get_config(compositor); struct weston_config_section *section; ss = zalloc(sizeof *ss); if (ss == NULL) return -1; ss->compositor = compositor; section = weston_config_get_section(config, "screen-share", NULL, NULL); weston_config_section_get_string(section, "command", &ss->command, ""); weston_compositor_add_key_binding(compositor, KEY_S, MODIFIER_CTRL | MODIFIER_ALT, share_output_binding, ss); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.338963 weston-8.0.0/compositor/systemd-notify.c0000644000175000017460000001062200000000000020660 0ustar00simonwheel00000000000000/* * Copyright (c) 2015 General Electric Company. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include "shared/helpers.h" #include "shared/string-helpers.h" #include #include #include "weston.h" struct systemd_notifier { int watchdog_time; struct wl_event_source *watchdog_source; struct wl_listener compositor_destroy_listener; }; static int add_systemd_sockets(struct weston_compositor *compositor) { int fd; int cnt_systemd_sockets; int current_fd = 0; cnt_systemd_sockets = sd_listen_fds(1); if (cnt_systemd_sockets < 0) { weston_log("sd_listen_fds failed with: %d\n", cnt_systemd_sockets); return -1; } /* socket-based activation not used, return silently */ if (cnt_systemd_sockets == 0) return 0; while (current_fd < cnt_systemd_sockets) { fd = SD_LISTEN_FDS_START + current_fd; if (sd_is_socket(fd, AF_UNIX, SOCK_STREAM,1) <= 0) { weston_log("invalid socket provided from systemd\n"); return -1; } if (wl_display_add_socket_fd(compositor->wl_display, fd)) { weston_log("wl_display_add_socket_fd failed" "for systemd provided socket\n"); return -1; } current_fd++; } weston_log("info: add %d socket(s) provided by systemd\n", current_fd); return current_fd; } static int watchdog_handler(void *data) { struct systemd_notifier *notifier = data; wl_event_source_timer_update(notifier->watchdog_source, notifier->watchdog_time); sd_notify(0, "WATCHDOG=1"); return 1; } static void weston_compositor_destroy_listener(struct wl_listener *listener, void *data) { struct systemd_notifier *notifier; sd_notify(0, "STOPPING=1"); notifier = container_of(listener, struct systemd_notifier, compositor_destroy_listener); if (notifier->watchdog_source) wl_event_source_remove(notifier->watchdog_source); wl_list_remove(¬ifier->compositor_destroy_listener.link); free(notifier); } WL_EXPORT int wet_module_init(struct weston_compositor *compositor, int *argc, char *argv[]) { char *watchdog_time_env; struct wl_event_loop *loop; int32_t watchdog_time_conv; struct systemd_notifier *notifier; notifier = zalloc(sizeof *notifier); if (notifier == NULL) return -1; if (!weston_compositor_add_destroy_listener_once(compositor, ¬ifier->compositor_destroy_listener, weston_compositor_destroy_listener)) { free(notifier); return 0; } if (add_systemd_sockets(compositor) < 0) return -1; sd_notify(0, "READY=1"); /* 'WATCHDOG_USEC' is environment variable that is set * by systemd to transfer 'WatchdogSec' watchdog timeout * setting from service file.*/ watchdog_time_env = getenv("WATCHDOG_USEC"); if (!watchdog_time_env) return 0; if (!safe_strtoint(watchdog_time_env, &watchdog_time_conv)) return 0; /* Convert 'WATCHDOG_USEC' to milliseconds and notify * systemd every half of that time.*/ watchdog_time_conv /= 1000 * 2; if (watchdog_time_conv <= 0) return 0; notifier->watchdog_time = watchdog_time_conv; loop = wl_display_get_event_loop(compositor->wl_display); notifier->watchdog_source = wl_event_loop_add_timer(loop, watchdog_handler, notifier); wl_event_source_timer_update(notifier->watchdog_source, notifier->watchdog_time); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.338963 weston-8.0.0/compositor/text-backend.c0000644000175000017460000007210000000000000020232 0ustar00simonwheel00000000000000/* * Copyright © 2012 Openismus GmbH * Copyright © 2012 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include "weston.h" #include "text-input-unstable-v1-server-protocol.h" #include "input-method-unstable-v1-server-protocol.h" #include "shared/helpers.h" #include "shared/timespec-util.h" struct text_input_manager; struct input_method; struct input_method_context; struct text_backend; struct text_input { struct wl_resource *resource; struct weston_compositor *ec; struct wl_list input_methods; struct weston_surface *surface; pixman_box32_t cursor_rectangle; bool input_panel_visible; struct text_input_manager *manager; }; struct text_input_manager { struct wl_global *text_input_manager_global; struct wl_listener destroy_listener; struct text_input *current_text_input; struct weston_compositor *ec; }; struct input_method { struct wl_resource *input_method_binding; struct wl_global *input_method_global; struct wl_listener destroy_listener; struct weston_seat *seat; struct text_input *input; struct wl_list link; struct wl_listener keyboard_focus_listener; bool focus_listener_initialized; struct input_method_context *context; struct text_backend *text_backend; }; struct input_method_context { struct wl_resource *resource; struct text_input *input; struct input_method *input_method; struct wl_resource *keyboard; }; struct text_backend { struct weston_compositor *compositor; struct { char *path; struct wl_client *client; unsigned deathcount; struct timespec deathstamp; } input_method; struct wl_listener client_listener; struct wl_listener seat_created_listener; }; static void input_method_context_create(struct text_input *input, struct input_method *input_method); static void input_method_context_end_keyboard_grab(struct input_method_context *context); static void input_method_init_seat(struct weston_seat *seat); static void deactivate_input_method(struct input_method *input_method) { struct text_input *text_input = input_method->input; struct weston_compositor *ec = text_input->ec; if (input_method->context && input_method->input_method_binding) { input_method_context_end_keyboard_grab(input_method->context); zwp_input_method_v1_send_deactivate( input_method->input_method_binding, input_method->context->resource); input_method->context->input = NULL; } wl_list_remove(&input_method->link); input_method->input = NULL; input_method->context = NULL; if (wl_list_empty(&text_input->input_methods) && text_input->input_panel_visible && text_input->manager->current_text_input == text_input) { wl_signal_emit(&ec->hide_input_panel_signal, ec); text_input->input_panel_visible = false; } if (text_input->manager->current_text_input == text_input) text_input->manager->current_text_input = NULL; zwp_text_input_v1_send_leave(text_input->resource); } static void destroy_text_input(struct wl_resource *resource) { struct text_input *text_input = wl_resource_get_user_data(resource); struct input_method *input_method, *next; wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) deactivate_input_method(input_method); free(text_input); } static void text_input_set_surrounding_text(struct wl_client *client, struct wl_resource *resource, const char *text, uint32_t cursor, uint32_t anchor) { struct text_input *text_input = wl_resource_get_user_data(resource); struct input_method *input_method, *next; wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { if (!input_method->context) continue; zwp_input_method_context_v1_send_surrounding_text( input_method->context->resource, text, cursor, anchor); } } static void text_input_activate(struct wl_client *client, struct wl_resource *resource, struct wl_resource *seat, struct wl_resource *surface) { struct text_input *text_input = wl_resource_get_user_data(resource); struct weston_seat *weston_seat = wl_resource_get_user_data(seat); struct input_method *input_method; struct weston_compositor *ec = text_input->ec; struct text_input *current; if (!weston_seat) return; input_method = weston_seat->input_method; if (input_method->input == text_input) return; if (input_method->input) deactivate_input_method(input_method); input_method->input = text_input; wl_list_insert(&text_input->input_methods, &input_method->link); input_method_init_seat(weston_seat); text_input->surface = wl_resource_get_user_data(surface); input_method_context_create(text_input, input_method); current = text_input->manager->current_text_input; if (current && current != text_input) { current->input_panel_visible = false; wl_signal_emit(&ec->hide_input_panel_signal, ec); } if (text_input->input_panel_visible) { wl_signal_emit(&ec->show_input_panel_signal, text_input->surface); wl_signal_emit(&ec->update_input_panel_signal, &text_input->cursor_rectangle); } text_input->manager->current_text_input = text_input; zwp_text_input_v1_send_enter(text_input->resource, text_input->surface->resource); } static void text_input_deactivate(struct wl_client *client, struct wl_resource *resource, struct wl_resource *seat) { struct weston_seat *weston_seat = wl_resource_get_user_data(seat); if (weston_seat && weston_seat->input_method->input) deactivate_input_method(weston_seat->input_method); } static void text_input_reset(struct wl_client *client, struct wl_resource *resource) { struct text_input *text_input = wl_resource_get_user_data(resource); struct input_method *input_method, *next; wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { if (!input_method->context) continue; zwp_input_method_context_v1_send_reset( input_method->context->resource); } } static void text_input_set_cursor_rectangle(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) { struct text_input *text_input = wl_resource_get_user_data(resource); struct weston_compositor *ec = text_input->ec; text_input->cursor_rectangle.x1 = x; text_input->cursor_rectangle.y1 = y; text_input->cursor_rectangle.x2 = x + width; text_input->cursor_rectangle.y2 = y + height; wl_signal_emit(&ec->update_input_panel_signal, &text_input->cursor_rectangle); } static void text_input_set_content_type(struct wl_client *client, struct wl_resource *resource, uint32_t hint, uint32_t purpose) { struct text_input *text_input = wl_resource_get_user_data(resource); struct input_method *input_method, *next; wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { if (!input_method->context) continue; zwp_input_method_context_v1_send_content_type( input_method->context->resource, hint, purpose); } } static void text_input_invoke_action(struct wl_client *client, struct wl_resource *resource, uint32_t button, uint32_t index) { struct text_input *text_input = wl_resource_get_user_data(resource); struct input_method *input_method, *next; wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { if (!input_method->context) continue; zwp_input_method_context_v1_send_invoke_action( input_method->context->resource, button, index); } } static void text_input_commit_state(struct wl_client *client, struct wl_resource *resource, uint32_t serial) { struct text_input *text_input = wl_resource_get_user_data(resource); struct input_method *input_method, *next; wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { if (!input_method->context) continue; zwp_input_method_context_v1_send_commit_state( input_method->context->resource, serial); } } static void text_input_show_input_panel(struct wl_client *client, struct wl_resource *resource) { struct text_input *text_input = wl_resource_get_user_data(resource); struct weston_compositor *ec = text_input->ec; text_input->input_panel_visible = true; if (!wl_list_empty(&text_input->input_methods) && text_input == text_input->manager->current_text_input) { wl_signal_emit(&ec->show_input_panel_signal, text_input->surface); wl_signal_emit(&ec->update_input_panel_signal, &text_input->cursor_rectangle); } } static void text_input_hide_input_panel(struct wl_client *client, struct wl_resource *resource) { struct text_input *text_input = wl_resource_get_user_data(resource); struct weston_compositor *ec = text_input->ec; text_input->input_panel_visible = false; if (!wl_list_empty(&text_input->input_methods) && text_input == text_input->manager->current_text_input) wl_signal_emit(&ec->hide_input_panel_signal, ec); } static void text_input_set_preferred_language(struct wl_client *client, struct wl_resource *resource, const char *language) { struct text_input *text_input = wl_resource_get_user_data(resource); struct input_method *input_method, *next; wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { if (!input_method->context) continue; zwp_input_method_context_v1_send_preferred_language( input_method->context->resource, language); } } static const struct zwp_text_input_v1_interface text_input_implementation = { text_input_activate, text_input_deactivate, text_input_show_input_panel, text_input_hide_input_panel, text_input_reset, text_input_set_surrounding_text, text_input_set_content_type, text_input_set_cursor_rectangle, text_input_set_preferred_language, text_input_commit_state, text_input_invoke_action }; static void text_input_manager_create_text_input(struct wl_client *client, struct wl_resource *resource, uint32_t id) { struct text_input_manager *text_input_manager = wl_resource_get_user_data(resource); struct text_input *text_input; text_input = zalloc(sizeof *text_input); if (text_input == NULL) return; text_input->resource = wl_resource_create(client, &zwp_text_input_v1_interface, 1, id); wl_resource_set_implementation(text_input->resource, &text_input_implementation, text_input, destroy_text_input); text_input->ec = text_input_manager->ec; text_input->manager = text_input_manager; wl_list_init(&text_input->input_methods); }; static const struct zwp_text_input_manager_v1_interface manager_implementation = { text_input_manager_create_text_input }; static void bind_text_input_manager(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct text_input_manager *text_input_manager = data; struct wl_resource *resource; /* No checking for duplicate binding necessary. */ resource = wl_resource_create(client, &zwp_text_input_manager_v1_interface, 1, id); if (resource) wl_resource_set_implementation(resource, &manager_implementation, text_input_manager, NULL); } static void text_input_manager_notifier_destroy(struct wl_listener *listener, void *data) { struct text_input_manager *text_input_manager = container_of(listener, struct text_input_manager, destroy_listener); wl_list_remove(&text_input_manager->destroy_listener.link); wl_global_destroy(text_input_manager->text_input_manager_global); free(text_input_manager); } static void text_input_manager_create(struct weston_compositor *ec) { struct text_input_manager *text_input_manager; text_input_manager = zalloc(sizeof *text_input_manager); if (text_input_manager == NULL) return; text_input_manager->ec = ec; text_input_manager->text_input_manager_global = wl_global_create(ec->wl_display, &zwp_text_input_manager_v1_interface, 1, text_input_manager, bind_text_input_manager); text_input_manager->destroy_listener.notify = text_input_manager_notifier_destroy; wl_signal_add(&ec->destroy_signal, &text_input_manager->destroy_listener); } static void input_method_context_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } static void input_method_context_commit_string(struct wl_client *client, struct wl_resource *resource, uint32_t serial, const char *text) { struct input_method_context *context = wl_resource_get_user_data(resource); if (context->input) zwp_text_input_v1_send_commit_string(context->input->resource, serial, text); } static void input_method_context_preedit_string(struct wl_client *client, struct wl_resource *resource, uint32_t serial, const char *text, const char *commit) { struct input_method_context *context = wl_resource_get_user_data(resource); if (context->input) zwp_text_input_v1_send_preedit_string(context->input->resource, serial, text, commit); } static void input_method_context_preedit_styling(struct wl_client *client, struct wl_resource *resource, uint32_t index, uint32_t length, uint32_t style) { struct input_method_context *context = wl_resource_get_user_data(resource); if (context->input) zwp_text_input_v1_send_preedit_styling(context->input->resource, index, length, style); } static void input_method_context_preedit_cursor(struct wl_client *client, struct wl_resource *resource, int32_t cursor) { struct input_method_context *context = wl_resource_get_user_data(resource); if (context->input) zwp_text_input_v1_send_preedit_cursor(context->input->resource, cursor); } static void input_method_context_delete_surrounding_text(struct wl_client *client, struct wl_resource *resource, int32_t index, uint32_t length) { struct input_method_context *context = wl_resource_get_user_data(resource); if (context->input) zwp_text_input_v1_send_delete_surrounding_text( context->input->resource, index, length); } static void input_method_context_cursor_position(struct wl_client *client, struct wl_resource *resource, int32_t index, int32_t anchor) { struct input_method_context *context = wl_resource_get_user_data(resource); if (context->input) zwp_text_input_v1_send_cursor_position(context->input->resource, index, anchor); } static void input_method_context_modifiers_map(struct wl_client *client, struct wl_resource *resource, struct wl_array *map) { struct input_method_context *context = wl_resource_get_user_data(resource); if (context->input) zwp_text_input_v1_send_modifiers_map(context->input->resource, map); } static void input_method_context_keysym(struct wl_client *client, struct wl_resource *resource, uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) { struct input_method_context *context = wl_resource_get_user_data(resource); if (context->input) zwp_text_input_v1_send_keysym(context->input->resource, serial, time, sym, state, modifiers); } static void unbind_keyboard(struct wl_resource *resource) { struct input_method_context *context = wl_resource_get_user_data(resource); input_method_context_end_keyboard_grab(context); context->keyboard = NULL; } static void input_method_context_grab_key(struct weston_keyboard_grab *grab, const struct timespec *time, uint32_t key, uint32_t state_w) { struct weston_keyboard *keyboard = grab->keyboard; struct wl_display *display; uint32_t serial; uint32_t msecs; if (!keyboard->input_method_resource) return; display = wl_client_get_display( wl_resource_get_client(keyboard->input_method_resource)); serial = wl_display_next_serial(display); msecs = timespec_to_msec(time); wl_keyboard_send_key(keyboard->input_method_resource, serial, msecs, key, state_w); } static void input_method_context_grab_modifier(struct weston_keyboard_grab *grab, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { struct weston_keyboard *keyboard = grab->keyboard; if (!keyboard->input_method_resource) return; wl_keyboard_send_modifiers(keyboard->input_method_resource, serial, mods_depressed, mods_latched, mods_locked, group); } static void input_method_context_grab_cancel(struct weston_keyboard_grab *grab) { weston_keyboard_end_grab(grab->keyboard); } static const struct weston_keyboard_grab_interface input_method_context_grab = { input_method_context_grab_key, input_method_context_grab_modifier, input_method_context_grab_cancel, }; static void input_method_context_grab_keyboard(struct wl_client *client, struct wl_resource *resource, uint32_t id) { struct input_method_context *context = wl_resource_get_user_data(resource); struct wl_resource *cr; struct weston_seat *seat = context->input_method->seat; struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); cr = wl_resource_create(client, &wl_keyboard_interface, 1, id); wl_resource_set_implementation(cr, NULL, context, unbind_keyboard); context->keyboard = cr; weston_keyboard_send_keymap(keyboard, cr); if (keyboard->grab != &keyboard->default_grab) { weston_keyboard_end_grab(keyboard); } weston_keyboard_start_grab(keyboard, &keyboard->input_method_grab); keyboard->input_method_resource = cr; } static void input_method_context_key(struct wl_client *client, struct wl_resource *resource, uint32_t serial, uint32_t time, uint32_t key, uint32_t state_w) { struct input_method_context *context = wl_resource_get_user_data(resource); struct weston_seat *seat = context->input_method->seat; struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); struct weston_keyboard_grab *default_grab = &keyboard->default_grab; struct timespec ts; timespec_from_msec(&ts, time); default_grab->interface->key(default_grab, &ts, key, state_w); } static void input_method_context_modifiers(struct wl_client *client, struct wl_resource *resource, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { struct input_method_context *context = wl_resource_get_user_data(resource); struct weston_seat *seat = context->input_method->seat; struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); struct weston_keyboard_grab *default_grab = &keyboard->default_grab; default_grab->interface->modifiers(default_grab, serial, mods_depressed, mods_latched, mods_locked, group); } static void input_method_context_language(struct wl_client *client, struct wl_resource *resource, uint32_t serial, const char *language) { struct input_method_context *context = wl_resource_get_user_data(resource); if (context->input) zwp_text_input_v1_send_language(context->input->resource, serial, language); } static void input_method_context_text_direction(struct wl_client *client, struct wl_resource *resource, uint32_t serial, uint32_t direction) { struct input_method_context *context = wl_resource_get_user_data(resource); if (context->input) zwp_text_input_v1_send_text_direction(context->input->resource, serial, direction); } static const struct zwp_input_method_context_v1_interface context_implementation = { input_method_context_destroy, input_method_context_commit_string, input_method_context_preedit_string, input_method_context_preedit_styling, input_method_context_preedit_cursor, input_method_context_delete_surrounding_text, input_method_context_cursor_position, input_method_context_modifiers_map, input_method_context_keysym, input_method_context_grab_keyboard, input_method_context_key, input_method_context_modifiers, input_method_context_language, input_method_context_text_direction }; static void destroy_input_method_context(struct wl_resource *resource) { struct input_method_context *context = wl_resource_get_user_data(resource); if (context->keyboard) wl_resource_destroy(context->keyboard); if (context->input_method && context->input_method->context == context) context->input_method->context = NULL; free(context); } static void input_method_context_create(struct text_input *input, struct input_method *input_method) { struct input_method_context *context; struct wl_resource *binding; if (!input_method->input_method_binding) return; context = zalloc(sizeof *context); if (context == NULL) return; binding = input_method->input_method_binding; context->resource = wl_resource_create(wl_resource_get_client(binding), &zwp_input_method_context_v1_interface, 1, 0); wl_resource_set_implementation(context->resource, &context_implementation, context, destroy_input_method_context); context->input = input; context->input_method = input_method; input_method->context = context; zwp_input_method_v1_send_activate(binding, context->resource); } static void input_method_context_end_keyboard_grab(struct input_method_context *context) { struct weston_keyboard_grab *grab; struct weston_keyboard *keyboard; keyboard = weston_seat_get_keyboard(context->input_method->seat); if (!keyboard) return; grab = &keyboard->input_method_grab; keyboard = grab->keyboard; if (!keyboard) return; if (keyboard->grab == grab) weston_keyboard_end_grab(keyboard); keyboard->input_method_resource = NULL; } static void unbind_input_method(struct wl_resource *resource) { struct input_method *input_method = wl_resource_get_user_data(resource); input_method->input_method_binding = NULL; input_method->context = NULL; } static void bind_input_method(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct input_method *input_method = data; struct text_backend *text_backend = input_method->text_backend; struct wl_resource *resource; resource = wl_resource_create(client, &zwp_input_method_v1_interface, 1, id); if (input_method->input_method_binding != NULL) { wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "interface object already bound"); return; } if (text_backend->input_method.client != client) { wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "permission to bind " "input_method denied"); return; } wl_resource_set_implementation(resource, NULL, input_method, unbind_input_method); input_method->input_method_binding = resource; } static void input_method_notifier_destroy(struct wl_listener *listener, void *data) { struct input_method *input_method = container_of(listener, struct input_method, destroy_listener); if (input_method->input) deactivate_input_method(input_method); wl_global_destroy(input_method->input_method_global); wl_list_remove(&input_method->destroy_listener.link); free(input_method); } static void handle_keyboard_focus(struct wl_listener *listener, void *data) { struct weston_keyboard *keyboard = data; struct input_method *input_method = container_of(listener, struct input_method, keyboard_focus_listener); struct weston_surface *surface = keyboard->focus; if (!input_method->input) return; if (!surface || input_method->input->surface != surface) deactivate_input_method(input_method); } static void input_method_init_seat(struct weston_seat *seat) { struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); if (seat->input_method->focus_listener_initialized) return; if (keyboard) { seat->input_method->keyboard_focus_listener.notify = handle_keyboard_focus; wl_signal_add(&keyboard->focus_signal, &seat->input_method->keyboard_focus_listener); keyboard->input_method_grab.interface = &input_method_context_grab; } seat->input_method->focus_listener_initialized = true; } static void launch_input_method(struct text_backend *text_backend); static void respawn_input_method_process(struct text_backend *text_backend) { struct timespec time; int64_t tdiff; /* if input_method dies more than 5 times in 10 seconds, give up */ weston_compositor_get_time(&time); tdiff = timespec_sub_to_msec(&time, &text_backend->input_method.deathstamp); if (tdiff > 10000) { text_backend->input_method.deathstamp = time; text_backend->input_method.deathcount = 0; } text_backend->input_method.deathcount++; if (text_backend->input_method.deathcount > 5) { weston_log("input_method disconnected, giving up.\n"); return; } weston_log("input_method disconnected, respawning...\n"); launch_input_method(text_backend); } static void input_method_client_notifier(struct wl_listener *listener, void *data) { struct text_backend *text_backend; text_backend = container_of(listener, struct text_backend, client_listener); text_backend->input_method.client = NULL; respawn_input_method_process(text_backend); } static void launch_input_method(struct text_backend *text_backend) { if (!text_backend->input_method.path) return; if (strcmp(text_backend->input_method.path, "") == 0) return; text_backend->input_method.client = weston_client_start(text_backend->compositor, text_backend->input_method.path); if (!text_backend->input_method.client) { weston_log("not able to start %s\n", text_backend->input_method.path); return; } text_backend->client_listener.notify = input_method_client_notifier; wl_client_add_destroy_listener(text_backend->input_method.client, &text_backend->client_listener); } static void text_backend_seat_created(struct text_backend *text_backend, struct weston_seat *seat) { struct input_method *input_method; struct weston_compositor *ec = seat->compositor; input_method = zalloc(sizeof *input_method); if (input_method == NULL) return; input_method->seat = seat; input_method->input = NULL; input_method->focus_listener_initialized = false; input_method->context = NULL; input_method->text_backend = text_backend; input_method->input_method_global = wl_global_create(ec->wl_display, &zwp_input_method_v1_interface, 1, input_method, bind_input_method); input_method->destroy_listener.notify = input_method_notifier_destroy; wl_signal_add(&seat->destroy_signal, &input_method->destroy_listener); seat->input_method = input_method; } static void handle_seat_created(struct wl_listener *listener, void *data) { struct weston_seat *seat = data; struct text_backend *text_backend = container_of(listener, struct text_backend, seat_created_listener); text_backend_seat_created(text_backend, seat); } static void text_backend_configuration(struct text_backend *text_backend) { struct weston_config *config = wet_get_config(text_backend->compositor); struct weston_config_section *section; char *client; section = weston_config_get_section(config, "input-method", NULL, NULL); client = wet_get_libexec_path("weston-keyboard"); weston_config_section_get_string(section, "path", &text_backend->input_method.path, client); free(client); } WL_EXPORT void text_backend_destroy(struct text_backend *text_backend) { wl_list_remove(&text_backend->seat_created_listener.link); if (text_backend->input_method.client) { /* disable respawn */ wl_list_remove(&text_backend->client_listener.link); wl_client_destroy(text_backend->input_method.client); } free(text_backend->input_method.path); free(text_backend); } WL_EXPORT struct text_backend * text_backend_init(struct weston_compositor *ec) { struct text_backend *text_backend; struct weston_seat *seat; text_backend = zalloc(sizeof(*text_backend)); if (text_backend == NULL) return NULL; text_backend->compositor = ec; text_backend_configuration(text_backend); wl_list_for_each(seat, &ec->seat_list, link) text_backend_seat_created(text_backend, seat); text_backend->seat_created_listener.notify = handle_seat_created; wl_signal_add(&ec->seat_created_signal, &text_backend->seat_created_listener); text_input_manager_create(ec); launch_input_method(text_backend); return text_backend; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.338963 weston-8.0.0/compositor/weston-screenshooter.c0000644000175000017460000001325200000000000022064 0ustar00simonwheel00000000000000/* * Copyright © 2008-2011 Kristian Høgsberg * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include "weston.h" #include "weston-screenshooter-server-protocol.h" #include "shared/helpers.h" #include struct screenshooter { struct weston_compositor *ec; struct wl_global *global; struct wl_client *client; struct weston_process process; struct wl_listener destroy_listener; struct weston_recorder *recorder; }; static void screenshooter_done(void *data, enum weston_screenshooter_outcome outcome) { struct wl_resource *resource = data; switch (outcome) { case WESTON_SCREENSHOOTER_SUCCESS: weston_screenshooter_send_done(resource); break; case WESTON_SCREENSHOOTER_NO_MEMORY: wl_resource_post_no_memory(resource); break; default: break; } } static void screenshooter_shoot(struct wl_client *client, struct wl_resource *resource, struct wl_resource *output_resource, struct wl_resource *buffer_resource) { struct weston_output *output = weston_head_from_resource(output_resource)->output; struct weston_buffer *buffer = weston_buffer_from_resource(buffer_resource); if (buffer == NULL) { wl_resource_post_no_memory(resource); return; } weston_screenshooter_shoot(output, buffer, screenshooter_done, resource); } struct weston_screenshooter_interface screenshooter_implementation = { screenshooter_shoot }; static void bind_shooter(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct screenshooter *shooter = data; struct wl_resource *resource; bool debug_enabled = weston_compositor_is_debug_protocol_enabled(shooter->ec); resource = wl_resource_create(client, &weston_screenshooter_interface, 1, id); if (!debug_enabled && !shooter->client) { wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "screenshooter failed: permission denied. "\ "Debug protocol must be enabled"); return; } else if (!debug_enabled && client != shooter->client) { wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "screenshooter failed: permission denied."); return; } wl_resource_set_implementation(resource, &screenshooter_implementation, data, NULL); } static void screenshooter_sigchld(struct weston_process *process, int status) { struct screenshooter *shooter = container_of(process, struct screenshooter, process); shooter->client = NULL; } static void screenshooter_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct screenshooter *shooter = data; char *screenshooter_exe; screenshooter_exe = wet_get_bindir_path("weston-screenshooter"); if (!screenshooter_exe) { weston_log("Could not construct screenshooter path.\n"); return; } if (!shooter->client) shooter->client = weston_client_launch(shooter->ec, &shooter->process, screenshooter_exe, screenshooter_sigchld); free(screenshooter_exe); } static void recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct weston_compositor *ec = keyboard->seat->compositor; struct weston_output *output; struct screenshooter *shooter = data; struct weston_recorder *recorder = shooter->recorder;; static const char filename[] = "capture.wcap"; if (recorder) { weston_recorder_stop(recorder); shooter->recorder = NULL; } else { if (keyboard->focus && keyboard->focus->output) output = keyboard->focus->output; else output = container_of(ec->output_list.next, struct weston_output, link); shooter->recorder = weston_recorder_start(output, filename); } } static void screenshooter_destroy(struct wl_listener *listener, void *data) { struct screenshooter *shooter = container_of(listener, struct screenshooter, destroy_listener); wl_list_remove(&shooter->destroy_listener.link); wl_global_destroy(shooter->global); free(shooter); } WL_EXPORT void screenshooter_create(struct weston_compositor *ec) { struct screenshooter *shooter; shooter = zalloc(sizeof *shooter); if (shooter == NULL) return; shooter->ec = ec; shooter->global = wl_global_create(ec->wl_display, &weston_screenshooter_interface, 1, shooter, bind_shooter); weston_compositor_add_key_binding(ec, KEY_S, MODIFIER_SUPER, screenshooter_binding, shooter); weston_compositor_add_key_binding(ec, KEY_R, MODIFIER_SUPER, recorder_binding, shooter); shooter->destroy_listener.notify = screenshooter_destroy; wl_signal_add(&ec->destroy_signal, &shooter->destroy_listener); } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.338963 weston-8.0.0/compositor/weston.desktop0000644000175000017460000000013600000000000020427 0ustar00simonwheel00000000000000[Desktop Entry] Name=Weston Comment=The reference Wayland server Exec=weston Type=Application ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.338963 weston-8.0.0/compositor/weston.h0000644000175000017460000000542200000000000017210 0ustar00simonwheel00000000000000/* * Copyright © 2016 Giulio Camuffo * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef WESTON_H #define WESTON_H #ifdef __cplusplus extern "C" { #endif #include #include void screenshooter_create(struct weston_compositor *ec); struct weston_process; typedef void (*weston_process_cleanup_func_t)(struct weston_process *process, int status); struct weston_process { pid_t pid; weston_process_cleanup_func_t cleanup; struct wl_list link; }; struct wl_client * weston_client_launch(struct weston_compositor *compositor, struct weston_process *proc, const char *path, weston_process_cleanup_func_t cleanup); struct wl_client * weston_client_start(struct weston_compositor *compositor, const char *path); void weston_watch_process(struct weston_process *process); struct weston_config * wet_get_config(struct weston_compositor *compositor); void * wet_load_module_entrypoint(const char *name, const char *entrypoint); int wet_shell_init(struct weston_compositor *ec, int *argc, char *argv[]); int wet_module_init(struct weston_compositor *ec, int *argc, char *argv[]); int wet_load_module(struct weston_compositor *compositor, const char *name, int *argc, char *argv[]); int module_init(struct weston_compositor *compositor, int *argc, char *argv[]); char * wet_get_libexec_path(const char *name); char * wet_get_bindir_path(const char *name); int wet_load_xwayland(struct weston_compositor *comp); struct text_backend; struct text_backend * text_backend_init(struct weston_compositor *ec); void text_backend_destroy(struct text_backend *text_backend); int wet_main(int argc, char *argv[]); #ifdef __cplusplus } #endif #endif ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3422964 weston-8.0.0/compositor/xwayland.c0000644000175000017460000001336400000000000017517 0ustar00simonwheel00000000000000/* * Copyright © 2011 Intel Corporation * Copyright © 2016 Giulio Camuffo * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include "compositor/weston.h" #include #include "shared/helpers.h" struct wet_xwayland { struct weston_compositor *compositor; const struct weston_xwayland_api *api; struct weston_xwayland *xwayland; struct wl_event_source *sigusr1_source; struct wl_client *client; int wm_fd; struct weston_process process; }; static int handle_sigusr1(int signal_number, void *data) { struct wet_xwayland *wxw = data; /* We'd be safer if we actually had the struct * signalfd_siginfo from the signalfd data and could verify * this came from Xwayland.*/ wxw->api->xserver_loaded(wxw->xwayland, wxw->client, wxw->wm_fd); wl_event_source_remove(wxw->sigusr1_source); return 1; } static pid_t spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd) { struct wet_xwayland *wxw = user_data; pid_t pid; char s[12], abstract_fd_str[12], unix_fd_str[12], wm_fd_str[12]; int sv[2], wm[2], fd; char *xserver = NULL; struct weston_config *config = wet_get_config(wxw->compositor); struct weston_config_section *section; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { weston_log("wl connection socketpair failed\n"); return 1; } if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm) < 0) { weston_log("X wm connection socketpair failed\n"); return 1; } pid = fork(); switch (pid) { case 0: /* SOCK_CLOEXEC closes both ends, so we need to unset * the flag on the client fd. */ fd = dup(sv[1]); if (fd < 0) goto fail; snprintf(s, sizeof s, "%d", fd); setenv("WAYLAND_SOCKET", s, 1); fd = dup(abstract_fd); if (fd < 0) goto fail; snprintf(abstract_fd_str, sizeof abstract_fd_str, "%d", fd); fd = dup(unix_fd); if (fd < 0) goto fail; snprintf(unix_fd_str, sizeof unix_fd_str, "%d", fd); fd = dup(wm[1]); if (fd < 0) goto fail; snprintf(wm_fd_str, sizeof wm_fd_str, "%d", fd); section = weston_config_get_section(config, "xwayland", NULL, NULL); weston_config_section_get_string(section, "path", &xserver, XSERVER_PATH); /* Ignore SIGUSR1 in the child, which will make the X * server send SIGUSR1 to the parent (weston) when * it's done with initialization. During * initialization the X server will round trip and * block on the wayland compositor, so avoid making * blocking requests (like xcb_connect_to_fd) until * it's done with that. */ signal(SIGUSR1, SIG_IGN); if (execl(xserver, xserver, display, "-rootless", "-listen", abstract_fd_str, "-listen", unix_fd_str, "-wm", wm_fd_str, "-terminate", NULL) < 0) weston_log("exec of '%s %s -rootless " "-listen %s -listen %s -wm %s " "-terminate' failed: %s\n", xserver, display, abstract_fd_str, unix_fd_str, wm_fd_str, strerror(errno)); fail: _exit(EXIT_FAILURE); default: close(sv[1]); wxw->client = wl_client_create(wxw->compositor->wl_display, sv[0]); close(wm[1]); wxw->wm_fd = wm[0]; wxw->process.pid = pid; weston_watch_process(&wxw->process); break; case -1: weston_log("Failed to fork to spawn xserver process\n"); break; } return pid; } static void xserver_cleanup(struct weston_process *process, int status) { struct wet_xwayland *wxw = container_of(process, struct wet_xwayland, process); struct wl_event_loop *loop = wl_display_get_event_loop(wxw->compositor->wl_display); wxw->api->xserver_exited(wxw->xwayland, status); wxw->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1, handle_sigusr1, wxw); wxw->client = NULL; } int wet_load_xwayland(struct weston_compositor *comp) { const struct weston_xwayland_api *api; struct weston_xwayland *xwayland; struct wet_xwayland *wxw; struct wl_event_loop *loop; if (weston_compositor_load_xwayland(comp) < 0) return -1; api = weston_xwayland_get_api(comp); if (!api) { weston_log("Failed to get the xwayland module API.\n"); return -1; } xwayland = api->get(comp); if (!xwayland) { weston_log("Failed to get the xwayland object.\n"); return -1; } wxw = zalloc(sizeof *wxw); if (!wxw) return -1; wxw->compositor = comp; wxw->api = api; wxw->xwayland = xwayland; wxw->process.cleanup = xserver_cleanup; if (api->listen(xwayland, wxw, spawn_xserver) < 0) return -1; loop = wl_display_get_event_loop(comp->wl_display); wxw->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1, handle_sigusr1, wxw); return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/0000755000175000017460000000000000000000000014230 5ustar00simonwheel00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3422964 weston-8.0.0/data/COPYING0000644000175000017460000000356000000000000015267 0ustar00simonwheel00000000000000For the DMZ cursors: (c) 2007-2010 Novell, Inc. This work is licenced under the Creative Commons Attribution-Share Alike 3.0 United States License. To view a copy of this licence, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA. The terminal icon is taken from the gnome-icon-theme collection which is also distributed under the Creative Commons BY-SA 3.0 license. (C) 2013 DENSO CORPORATION Permission to use, copy, modify, distribute, and sell following listed images for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the images without specific, written prior permission. The copyright holders make no representations about the suitability of these images for any purpose. It is provided "as is" without express or implied warranty. background.png tiling.png fullscreen.png panel.png random.png sidebyside.png home.png icon_ivi_clickdot.png icon_ivi_flower.png icon_ivi_simple-egl.png icon_ivi_simple-shm.png icon_ivi_smoke.png For the SVG icons: © 2016 Samsung Electronics Co., Ltd This work is dual-licenced under both the MIT "Expat" License and the Creative Commons Attribution-Share Alike 3.0 United States License, and may be redistributed under either (or both) licenses as desired. See Weston's COPYING for details of the MIT license. To view a copy of the CC-SA-3.0 licence, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA. icons.svg icon_terminal.png icon_editor.png icon_flower.png ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/background.png0000644000175000017460000041051500000000000017063 0ustar00simonwheel00000000000000PNG  IHDR5؂ZIDATxQE Nj!\ vߒr2@bHXї$ˢh&. jeۿjOǻOc0 `0ӗ`0 ݧi֬Yf͚5+s֬Yϳ^?\QO!wY1!Ϫ'0 ah8@ft7QTW Je~OBx}#6׍`żsU1L0]ե))P4*Y}˺C"^D[:̊)~fnxK^ i9+Ě\9v},|mdۭwsn8'ʊnglLKehW^v)ǎ׉|p)rVs|9'~ȱޖlS.%ȉTM-~JTad uկ15nq&$oH^>cw:uk$ӵbxv7Л |9}R| 0+ ^d9p('EOJ6R$ܥ~ m2H9ICN/mpjRP ٫'јrTxH$T(u_R~z0*p"0) >c}Z_  sϴ5 `0 oWz ܈X4^IVsa c6>sDTرc p_̴ѢGu ?F5m|G? a#~ϿT ȝPrGc)+R,"p"KwȐ1s9>ajٔk۴_;LQE*y}ݩb#21qN,| b4NxQFExKK\4Wxyhx-½ JsI>PaR̯+߀joq+¾f@5@;ݍ&lcCo޳|H$5#f5sK˒^58{zM!,,\y~uNmoEd .@Fu9c:ygk28B^<|9!18sQ`n|MIg1CDU)>,k%jkF8?ۂ9L5g߸1X1%dVABDik`G.am*t uIUR0¶q&2x?.ËK(ZG1Xh37[@&]>GB֦tܽ~@?tr;u)cZ [,G]NXNUO1wyXf ג+ ,s4vLGY"b>УZai1*w 9E4sr;M09^N\k)_oQނF;'@NEʀFe_7='Mzż 0 0 Ír12HA K R ?3j2(f!Y[#7++*duu(ȯx՘9UͲf}Ӱߢm2YWAv~{d7eI>\/gU+$x! 3 N8]PSΰ 7@)ɢB Kn4O>jPeZ}g:|1yz1SSꉝ,v zL]oPP]iuWvc$`s~+*g=Md^nw_A6b s3%ցY9ڙ09o993 fVH t-ۓ#ddg:py84F%WVrPRdu7X_V">j P?Y@}kreL*Y7'sdIffs~ءtc$Yzمwn0 0 a w0 wAAy8fl3 A J: '%k+>^-,/\n856̼gTcǪڢ ߪLw|Jeo㖽 0ٵehܖ%oQJMz}o/oaaanϯ'@   @AAy  <oQkPx_6ZɰEc gy" Vv>9YcðP+ ?UpdKh65PL:v RfΊ)A'N.ҳ6¤SEƒvj?=3Jm`W,Р,_O (D?Ir^kN<{ҥ__hd.ЯO؜#|ÜTIzsxJD $=Ǚϯ/n[Ѵyn9PnHu1y2űcs..w4<,] m~28b#<6-f-qy0 0 pS}   )@AAAT   p޵}$'F2'9|)fV NeUi=MNS4C %~2l-+lTX Occ-iĂ3f 4h(UPA%TVp[#;G[98tD50 9!KM NK$P}|5AbK̑ /d|Cd-7p@)A IWgX3qT975?(K*JœF4L$_G ?GM\C2ejTHnȔ@X/P8Ec dWa"92#x 8Y*t*pb0EG&Bǰg1DovaIi(2f43ɰY';##NKq8lb$b06ֲم "CV-:zVM^H(vS900t岇?|WC]^< ^xf]foToh" :kryOٳ2 Э.=p[~6~ _P@8"gaѿҦ+0E5{bN R]q %qeLv@ZT [I >{)ZJ?^ޓiz7amK_ VT)q/'k^i9>BU*nnM|sz> 4ȲoanA|o\y&Ox xssl.s}%btYŰ>7"\/Ԇqvk,z@Wg%N &Ń;~xq} zl3 NJf'eD-$@Q' y?V(7.N|TѭJ݋Ad*{ܭ0"AzCڇb=uݠWNGD86mtbWE T^s wFb"EP%iyv"h$m2"¥`$4~zyqET*ЏJ-\݅yh{HfVV<;: 2>OlW2U %, =K$! 1 TY U3]w -Y>UU'|Ȫ%x-(EqPUZ8 ߉]UıK}# +BLma?}QUx g..$E) Kx\6^}Dh1&=pHU+mTI. w+jݠkL \.t7:cQSm֞LL4}h٫{ŀ_}*Y4%1*yy#`T(7|gb=ܾe陳ͷ)m®8ߤ6Eh6F?bs:;Q;#d2a7n)z'Mn[{}Tv4/,,,,,,,,,,?"HZ\>[>ӈRu1k3\qqE_*Nsѧ T٩Wx]iWq3_&c!hwrW-\ˣX9|EBן}i ?(& eo:F`l @{b~)?ߚxgn>;X(Ώ x]w!>s}ž,̿7|fOzsO2=?~9_K==0#Z#PN 3r8w)C!x5JHKRVb12dU22Ԕ,Gr8p;zD*˒"m_5DЕ)&*ꑌ;4 v1E%wp Yh#AJ \q[>6 eHi&;]YdU"TXz)'d6|hg?[$7#3t8/(-cƥƸ(ZpЦ P )ȀkA.|i|4cCņ?P1RkL*6lƳ2hb| mT]FҢ\$Y hĭ׺e-oL|-MlIUa;3"%^bAኣ"胇} B;fSkLr bj~e\ϳoAZ+bVp1qyi;$-P1  ÿم+(`&pcVUUf26 oF4Khs yc|wn(tm )7slq(:ab0^8]ᐼww@h ̕2-ψcSߟt>L(0ʬ歗ʭO2kT7(Rnv”_~:(oo_'hoKɏX?δ o}Q3k0 `0 /o_W8Ac;)#99 ;zRI(NU5y?Xu4l `0 7SmBFPKo3L>J Q- $>սoڧ2|[Aܦ;zJKўE~ _E(l1NɦCfqMI[8j4ug*6Lj4{·Em{גs !ƷfTkT^01nVXg֤v#>n[Dҷ[KQ#7`0 `04444444444*h~ j܉&9w>?ZӍ514~j7š!xS~==o ^6 NٗE+.,x v2nxkQ-2suUgP/7@ʑq`ܡks^/1AFLeTAf*TrdŲ8OmƄŪvz_qN ֞qPLTaTb:)6\Քm3RXph ZLuW{"d*nMpF~ B Mќ(WƐLqM{ I/L6 2Xiy|Sc =$9wHSr?1Z&U}$[E=O?>/֗ĈY./hLȦ+6P͇4#vAeD|uR#sGPW< *AZS-hL+-\hU#L)Iy0DvܶcEaJyPCa>8vD#(j!VܷzH=^@g)̱/qg -Ri `XwE^f6Ӡ#(8Mcra aCyqc&}@[ᕌ˫+[s9 8 ]ioFzTeҏ>㈏{pm%Fؾ_~+K:|^Z#I9j0 `|L`R% TG1g=YGc#|Ll0G>QgnMQ6aAExnEH-os3Lxۭ@&OkhGK:XZ< #7ɯz}ڸ8pfi f#dS:w\}|^p0 {3c#I_\c{ lT@@U#zWoɤ⛸8 F^YF `BB @>5>25ab6Zx~ahkzО*E˒>ji_H0ggAL@lq#J )7jL6aeʴȂَYS&xe%i=s6ݗZ͋փ`̵/-ysFj;X0uESf MTB}y `0 _04444444444$ zS?YF)&^Un =8QiR9ܑl`K65ۅ>yݚ7wv@8MYr> 8̵!-t'e#rT`|5d{w- ԟuZF|62h4*w 95*^޾գQL78 WLzEy8\Ϲ$2Q|%29`,8GOZ^ o\Ru@~nʼ[omvcb*XaұeYygvT~c18+7_ޏw)W}!}{d)xDƸoגC?=̈7?VqN0 `0 n0hhhhhhhhhhh Pi54444?fӫ:F9B >:+n38CS5=5?|1U XomH ֒< RѾ8 1KVW_˄8Ge:Sk;h)ҧd(TIaiJ$83#p^TTID}*#zDjTE/ a @9vam֢pX!M]j'.nġ*>3 :b W⬐\A̠S2B)k7Qk(AE,Zj_v}[E1Y'QpukTl[ R%[LHVmQZC]ȈsM5{hVz(5 n^^S8s,L','!~br[)t9KY{ufNS07o)Ʒ ?zyH8?a=^:A< ?u}xCMbv#3v!=L>5/^<3r䭗_yhɠ1':#M.K1_&/˧{wDtGN ě L4Oh Y#VuU#Xa ϬDN"dokR5ȨD@%?n\lt2ju󚰊,L-"H 'aIS k)l1lOƯ'S~@1wU6M͢$Idk "ŵ2/u8JE8+zڶ7&T'yeKFHw&#Spd9!) QVI9L:H62]ҹC; >a]%6r#webqrz5H'!8hh"I3)xM6'61h5d'(o!@t^~6k(fd,^`ɢv΍XHMH6溓; (LdV,D97.@[FVvrrJ;OJ61|a[t7u[7d,5qGZ* r-Pɷze?YMAƌ>;OZIfDd˘ >vp7I9!ƺZWكz8ԗ&>+P퀊iænuo&enJcȈbqp;r3CؑUJ,eγ-8让c:x<>={tkI]r}n<~|y޾^6Fh4i-mq.-YBmms]#֏hmmCG%mZƑ-wsh.}M?2~}#>f}eRaPH\a{$4):x#u9 k}bsS=QqLC@Izn2&HVW Wx{4Q4;X `rĭ6έYJԼɒ e8D4&rG<7)6 [|nE33@S*7//Ќ(nf$# & S(# ;zvZ&aʼ1#Y١F p}Aqv`+`bNjמL 姇l4v[~uh1\ Ӊp0f]v sL?τAn]#u y{ICu'###########/=Ӛ5k֬Yf͚54fy}N)7L.Jhpu 4ى4E5a @*5QZ,ʅSP9CHr:&}sVa,f(VtB2ap[')6zsSyqC un֕ ($C1bP S44cx]_/|eWqbGFPp %վzL:9Lǽ_ ^0s&qbFx9va~5)Κ-jY #aL'!vn&q5pBSQ 旙fsS!ֹ5ȶn: '诫Úm60O(O `o "Ol ]kH"yBVRC{wt]ߜ/^FFFFFFFFFFF~Yf͚5k֬YO>SYf͚5k֬Y+@}mяr}ò M)cnM>}[A;oRʥ.s;}uT:B[{ϻy|gflB->0 NHʩ~"fͬb2A.K*>Uπ?wI>ȿ})ÒR|#IPڠ]yL.a|EӅ$/$F<k1ϑ.*Lv&yJsMeX5s9#E=ZĞ~6y`" +Ѱ}1*o^1Gw&Ynl:+`FӛUҸQY vIG+P FBv [\~rvfs[r=/0 0 0 \C`AAAΤ@(aR xd;%Q¥7 ;p*0@?J{ɣ@?p(5W=BKsPŀ<4'q!$NįJÁBvccL?Ƒ}39Q_۳1 0Sa~?o f= 'j(FX*Ro?y*8& 6Je ~䤄J'ѳ0o|?]~8Iϔv W\'yw"%T9%NS+Whj*Nи*hK`Th|mB)|Vrݠ4dʞråU 7(݂xV[]tb  A;4z鵚&^#U&d|.{R>|8_?J`u(MImT[yˈ}j~,#*Ep4WQ$pď+L1XIp!DkEWR/[m+5TŠ\֍ѨS!(&PwiGJݐd8Ԥ*77& lZ;WnX%x+Z 7e2n7P Jя\^+.:K`^.OZkVU7TUU[3qFsz=oVPV}I[Y=l`E+_ DԶ ư 2M\ Ԟ끖5iQBYU<ֵ8Ԓʥ1*bUR+jdJ (srSCWp_%Θېjɬھ0AC堤ى׵| zQ \bRJճf]Ҩ&PWׂ3Jݍv]B+'_JuFq%I$d, -;k2-J=Ua'E$'3 tq]* S& ]6'=UMSnG*u{:s 7aaa2  p#  w)w ќaZ&]ӷ]3e9>.ڟ́n$cz@4?[7^b#o?~0RDAu"ɑ/N 7|?:Z1QwS`!g}+̺rk烾#q\ ̺WPO꾓NELYʵB͢?!n3}ԍ'uP3v4 zo\S"aDuxZÑpYߪ_߾Sȷ$s:,ɗ̢ n*U={Æ9bv^#:ci7*,b!NzȜ+VlgJD d IE7S>77) )ŨB41 <4`4(rN淳s5 lÍ9E^/EQEQEQ=W*JR4T*}x=8rVqNqs8`=zBpх\`SXʣG%^!+#,^Mi_+8* \,T^o -bP )0_p$9T87، ϴ y$P+HB'K7ݽ4r鈧bSckfj\r?z;8G );pY(ݿPnHP'Ad,tgu$4"GdY0>trO J:9U0c~W:z!5Rlpɉ̒Itayۑ'M'dzdsKh {xt1O"T*JRTRT*JRېJR VJ{۔OnCĦrN0YpF,qaymf-fJ׼r8\%đw{xv 0t=l{lہmPD w(((^n!t֟jaDے_*XUfig`}I`揗~WPK8/yݦeY\YtG=4/:N2|SN_oqAtQBKwzn8KT\ML>Np?{FraI7dt}&Jd[͇B!3a=U׹EXs1&@5"u;2bYw|lr~xZ·?wྫAył#xu;t&=/_)eff6Z@gΐ/m]:x䑺GL2.]OKW7N2B3oPEE$KhPKY~l8 &i}VO}h4CF2mOx,<`NSc~4IGIx -;txp'NҘ` cΚ,h~S5O񘪊q%CHUq~ϷHew q7IɊi./ ʗS[YWnJs_;V6Qo- FRQ<[6s5\C u85ЊrPD;!:mc$ .ߊppd9Dۭ18X"1I/,هw<"ji4.L" q%ɏģprp[!U@aY0Y!KwRi nc ܭVrMY8rE<7E #( 7S7%+v9. >Tx#Pg)A*A όj B.;Vu**Ҧ(4&$2ob|!'?wj3k}d[gqKeju>s@_g@sOͬ./5F:}NJh4=o߿{o`?-|7fX.² #VI(Xxȸ;ݩ(  Lg 6oN;Vagzu?w?h@^Nh4Fh4~zիW^zӬЫW^zիO>%p/_;@JDVG2XY !G iăIa# aΙ*0 W.ݒ;+f9fK9%6? k^^Ä<508y{|[).Z΍F'Fh4;zTm2{zիGJ01F">]D.BAN9a ÃOʝŸ[`rvr騯QԙHh~jKW# !*$4Bw1+flr/O>Jq@H,\'wKB6[{S ctE8tZa⚌fyE% yó ܹV}Bxr<. @~Ly F2ag ()2ܦ$V.5]^H @uZ*ؑza T,pxb"%snN7_q/SEW{|6-$"((I^d^R~9"6Sl+VT $#䥂avҹΎ YSܡxIKAucY+<*^ RV7 ԗBY` i?'*ԏv ˅XC{\Ȇz XC;ճ1dY{"e.*Uk1,DnW8%)J%˧vlS%Dz闧$ݞJ #˳S@}Jw%g[UJ2+ ICjY"} psG/bDW/]4| P,>_wMxY!_HS68cXaOXš!&,"Gx{$i 9D4kOؤaCPDj(AqЅk%dzk޲P#xn {VjZYT0@ӉNKP\q+E*|kMdREpF8%%|,V\^F&xDqa ^ iYtSɒB I%\1*szkx,Dԋ|a0BxH*W%+=~+@a t؍(j?(X!֞eq (սجp83$7|^itv+pEԆYm؄i{~'@~w?Y,UN. -պ͔qcؐW%C.qt;)ΟJoYP+Qlh4E}% 4Ʈl&6o'}WG]Xă)O\Khry{ҏ/Ltg*4E gWD~BٵOaar?<AAAp78gAA#AAAp_3   3Um }VJs33`;ӜstU4?yǾOgޑZv^%e͎:<( U,1Ƽp ^oFNM`//o:  {cOLy'5.β0 tP'4Uҗ@\ 떰cmrGynS!~[+zL VR. i${n 'yyx:a ?vaa}U +~gSAp"W__/"T NElPAwQCẶJm>$ܻWGv^=+nĿf0cdf^ UWCۘ׮ >="m3ˋ2efaG3!x)l*4Y8)whZ̟]~__ URߪv.UZ2@Wda4ي i!mEIZ0R gF&bL$utbyJdpz؉%X $Kd'MZ{+)i) 7}>4Ex%F~+`2iyB&O6qI>==͏H>nOaV]ɩBo:|v.NѮVZȁi-4oyX_LD8p)^_'~>a69@;"TУGN&bǤ")ëu-zDrU"Z@z4]狇2܃$7^2Ccwޑ&Nx*uN)Z %{VE~\<v.MʆkUj1.e+fB\y}{޻>sEk=L6  /' 0Uop{;|8NBoy>^YNQ RH" AIC1LU|OWuc,\9aveT]?J ^뽖0VOH>5{{x1L ͇#:t8os,Ն!(\4^Rl5~gXkz%+mo+N :}*u0 ؤp^X>_~ Ӽb>r"S-C O#@UT_cg'*7I1Ay2[J@YRꀻG>4Iglb{ ~勴d,p+JJCDP!@1@4XVi*qyxXQÎDp4^Ⱥ-gX%|\<bs#vBUfcbosQuAy c]. ɯWk}w+t1LŃS ӰIَ^ܥP6;֊NxZ'VNiz[{WIleyk/WTIhKz+W [JīTϰkkhezza4OԞZ{mJ-ݙBdtoN_@dW腶<` (POW! YɃ-y=kfC4~Z \t#9X`5 ̟# M߁2C'$ڶF$6b uÛ< @bP!lh6f$'"-iDFg?:}WlEmeːh jS^8˥Uh{6;@y&yEb5 xvCd,7WqϤAA<UAA +  .6<   o ~1/}6PO'H7j˃?0`rb;S0We.gq) 8]F>9~1Z*"C43$s0.r%S'++IkU#.vdglcz=4Hl( > ˊŸYf5x7~p-^Lؖ8GllAq1W޵Sh>M><.Ұylinm5&Ӈ%s&a odt|Efۃpq wR_Ӯﲭz4Ryw}XNzX0L##AnsF s!i x}襄a/U8@-Xcjzk쀓[3QڽDUpRA*5VCB./KTe]d5 AYMf R%S& Pٴ +DA $CY=rDaATd)aACƙi[G–U֙P|MH~$BC?l[g:@y@FoR㽸~ ġnɈ/= g$܆D^)x׾}ZN&\o;UV]qkԬg7!0gL9EL&U3mefh;9Lx ;h:4Eo"$gf{`DwS-dlgJn<6=:2$BFSa*6F}]*~a/Gc za.rU>7a(izleN:P/:;|^)t2ۭӱernLZL,E=<#Fe(aǐ]9..p"N<v a}'^1[l+8 puu H:\CVA 9"6}ˡAo{ ihG@R-G\Сr0Y҃iF-uߡI8\a(Ǎwrpx#À2  x0q #]@iB -5mulِ ǀaK6U6b,wXX0wO 4^4[9% otJ0`7 ;01|mkC``HR!!25Ƅjԅ󌳭g|#P+5V@HM Q!m%ĥhm#Fh_` 80:VoϏ_ 0L<r!qɡˇt^IȰ<օ֎B$ ,<ՎVRVOt?!q skp<(A|>ß_鏿_)=\H9Y"iHeɗ`3RIv8|~Y#uȱQ2#m1ґFx,X(qE'//|qk念v K*b@帤RF5tl Pi{g!{ I*)07]w8[iZ̯:"|{0J0]} h=l]jɶ[di EdtBOMsUDGR5F΅ƘJiEۊ._?Q~4qڤchf- g4(a8.xerHbA';H,QQ5Db 1g?S&aOx@RpBN>2Q.FJ?ɲIHK {*aCG`JrxӹY󓝦8!Cۘ9bx_?ws\Aeϟ=h] ҡ^;{=,PM41xfNvC/c80p&z€ z8.. @Ez>2pI }/tU##{?DA-D+KL]@C9 DrsQ[yXJZBObQ(λ^vAY8I.+8ɹA4oh2@Z[8( $c . *YeiH'3A=9SuE/Mf֋ !۱(εq2324`'*ȞZI؇ꉥ1U",Hz7a0pte@խhQ@ǞEhZP,>1"|BP(ſBP(46~n(**********ڈMc v>ފ"`V6HQٞL ;|[bMr033cv-V%c1}էJ.}4m9[Ac2NOuQ( 1}@Pxz ?ТMF.s5Xr6Yz-WoAh#GcOj9VW)w7<ה,SN-v/S]A%= ű=#h*:x?xmH Wfο<OXyRO'$EV4xo7k>Im0:"^ޥ%3V!E6FĆ݇OAtƑ ȣÑЪw햲~c펩ȇO3usL N|ÊM35ƾ~SSHmiZnAQ'nv؄B4N g{E(ufꑷru$J9 & MCX}myąury2#sl/kzg073>{O[da33F5 9{ _USD]J&V[<:~VE0LhEl9~-vY?WP)xn #%0<3Ƞo 9ZgS'wSFY$U> r96D @<>Nڢ_%?Md ewB:< FsVȸYWxNE|^"Z.p%2kxw&t:Q@zf`:6=-53^(9 DZ/ O( —qE ***ߌ[ SQQQQQQQQİK(**a=WvT)0e 1($oFD↮V6s"KFUXߌv%͍ظj xB_,a7oHv+g# 2{>O2O Lf5u -%ca݈ qi|#P(<P( _ A/LoEi\P!g0dVH6Rp0^̂ $Vz(AH6?DlC:Rh0 /3`t?8J$P>\tѥ(DGZ=-Kl0pOsn:7L*G{ H5d!I60:>D #8Ӊ\͇ʕh#''bՉ&N1RmY_-D"?1$6D.D"H~RL'ޅjnzIyOhI* @ V e;CeNV /[x•[v$JDLd˔z%re7/SHn˿!HD$OA"H$ކT{$Ei /!j")) )))$&?Jbhx{a!EJ#3OuroD8B`vm5XD.~N ״p#^hjtOl&vQe#X0ɚ&0qq)CD 68J IHl BPkllSk~ʈ-JP{ݓUm>ZTWųtmNLEYӉbCr5 כzKe)@2^ ~)4E+ty@p؎F8>|w s wD=. 0KVW2}| CWpk'mXNᠯS V&}Е( _hv, Rw5z~#k@IkjR<5ɹ k֞r0Ţ+7]}cn9r}| 1jn34E% FQ z sF.4SAyW=fnLb\qsn' r2;{Sʼn97MKl-~]P~qAzf ,օ&#xHQ7]:-BI2ƻvGC6Ӹ!VJ2w:os [7"ť%9IDn*<55|?Kk1p(7L-yL*Xzh-TwN NX:'6XH?W4i/Ƚ,rZU$n_Q V'9BU2O 2Ozgp a%fkEк?|j3InsO_ m4k+s" K@~ po3.t &%듒63%g:eR!Rus0=J"S6kǿ5OW^cDQK?@4EYD 3BNE߀Zc|Ԣƕ77o$_B^/M `Z4vʬmCm ;S|xE4jhkwDfl`ӼueG bmUCCH35se 4ĕ?u, x m+B/ZeZ9:2ڮ!X\ ON=ܫn %7ǹ8Z_e\u#W(aWnjjnG S|:r|R>fJ[aUZ6[ޖ^ }a{OS)@; 1|/' 70SC(S@dLZ`{3&aE$,½u";X{# hyH$<?Z1,P3yIyijmF黁9Eir.@ e!oS'zK9X; vlY0A г,ve7ճ`2qmBr]\Dz"s+; j;dkҋ²<{Qmok *@*k\sB:yL{@@U6juI#J t PM;YwM*o&Sf:LPpm]"νpU}δvkڽ {;VmzNCrB^dv^Z +ѠezÜ+ldoi >0JUFFBEn[t> Kt'O@#tSWݟ'ѦBaϪ:Tx PN}F'0AS'Էۑ- zWXɯ$aJμ.tC%ڠC7StfVa^*hFKdpբp! W$i}h TU` .W"ӀZ;:4$VS ŷ)Hj+}iUw'݉JTe:oC+IAWj#kx%c" d uI!H=H:4BRA)g>$)/QHZD7!{*p*g|I"-s]]6ZR3OUDq`;Cy['?3"8;tZZh@bCTQ˜;}j"Xe X3ڀ omn;&%%%ﹿL|V=pE}4Rxc4p#.uW)F͔*= a ^vq(5h ? Oj6ߧu}cqqU9^ \ʈ.*C%Bw1M`O GSlryvkD"'Y%*FTP HHxU0^X+BR!UQF0:(Kw/Tޙ63[X~}oy!@Ƈp ХYd'k-#>]PEK$>3]2azo{ +,|m^ٖze}yam_;MxVUSjj! %‹72f9qg:Jmz9*M;'ꗖ1ET`lR?A%mDL >*C4$GXb,=t=]5XvK7V@ Լ %Wfde87`|yטȿ'vOY*&6]L$ТSGU|ź[X8z\S&MY^'wzҡ}X0_˪)pXAN>+skͩz!If̒e v$"גOj"c&&% Z%{WoPbޠ7K^b9~]: 5 `̮]_ >T2-|<|;]ԲuLt?8Cd )q+Gqh=?>j -fmrY)qԼ&Ot͎o&|W?)_F.8|>>p/=6M IrT4pυ} 6B(/T?Y@yaY?;&s,0/Ub̀ͻ #x)*DvQLJ'NPU'}(011绂">fD>$Y=<&&&iGeҤǢa_Z117+BGja߾?1:/zAbba=](ܚ\&?[QˤI۶ Pt~psATd[=xgU:8b]&{XqHM6ti(H%͗GSͤ'RgF;y1<Ċ!ޖS#l 15z۫Jk5fkii_7|ݎ%jvdNxb?D0 |("&&x9_ eҤ;P[C{dq8_.#%zY>#äIv1y5`b[.PL*~J?b<;kW+6V);owQ ywfbsh^saiY\~2yqL0mmm%C]([`"uK֘xbdZ9gд(oT@0ǝǸ~=RX"KXpo t$x*8UqW &YN)S> ˾Z[v`:[J3U4T/azp@4YCٔHr' <U8\YO>섩! #?ȣ8jQ D_zb]mnPm{F 2IG_m&.S| V[= B `j[fFhgJSWi|X m%ij'9Ӑ#t f}Ne^%jGM?`4 2HvB`:B᚟c {I@sOΜE6@U aO96_+K`j߆2<:kml"j1<ڷp'EHKCbnd nsai mݙ5.ےLh\nFHLIiINdl>8j4؄ARS8Tfʎ{ڵv$al2Mxo2Y#Bd ~MfEM3:L^Kap]h@.2U[)Yr:`?CcUL%Eމ~h LS_7³zGݑ[FL b>ĵ;x7 n|Ύ+\i"6h'|v*?keʓۘæe-]~? ?q~s2o6lmqN]:kmM]W/%ɑك9Mi,uك)$d8fHk @X;G2;u9Hu~Y`}Ĕh G3|h~TL(c6÷+_Mˎ:9WIBX̯PFˌ0S9g p`4$jV]Dz=Aޘ} ha~ XfII(/Gy=K>#Baa|%˚GepC[7`|pCzy§٨ ؾ׏ G80b%k @`b~~xuc\+zxfQ+-8֗J1\)t_V 8˷&it1s#::Ow}mq{RXd债EG31h +B5LKO{1)s>_/TRR^^c<Λso1RG6~E}{Qkg*8!iN-p2{ Db`8_ع{Sm3u{Y/XN u%6r2Z1Q ׉jr7'< nStR!mC@*_X#Pg]G(l["5:s'K`л0+E%(!Ҫ#q[yxӒdwcr᥆vV9l)Pi:Te.iHh/ξ҂OjxP#45Jsuy2T%q/_^W< .Y ŭT*F\Bfi7nJRcV ١b+Ag Cƶ12KaS3۹zfW6 W=Epgt sU80<"cߪHau咥O$C\x:|w>AgHNaF*,>>Q~6-(A?[ &ʐ cjbhno~Wd9S|[nn 7A 2Yf "1XR} dammK\6]1O_ry1e_*Xuēf`W&2pgQ  Ze䕄`avYRhwI鸜L N;yrLz3߼=iv݇ط-WFw6W/C*Qx[Gu]ij-cǜmxA­L|HmbIubb"_./<׶) DbyEO IgJWD|{ gcYNAEI,XLh4޸WsN]MnRi4 e'2jT'cƪ4Jy&lpV1/|z8 dgV{0tw)2mq!4M QC,*\PL/咈jDqsbp=.&כK. 6k AVm_ XlRʗ3Og\ H%8"~(#]uӕ^N r*c!Z S?- >f.hP(;Pwñ.qڑ%^1lP>2t{|IJ87>@$[)(vEW+@ڄ+*$r/? 15wkz=Ў2'69-H6"6(kJQZȬH 6 ܺ+nPj|C?&vE:aAv 2xD_/\#+vQ@^RVI _I b.J({r |\?2Fk(e #Cёh8{ɞYDJS13?ٞW=)?j!Ƕ)RFej-#k2.@L!r>;zh&9ς(Fc&a9 OnZ2kOET#XX{pGIZF bAl9m̃l..<-y2pcx 3oS~WJ[I"f"ݻwE?Zbnd5<'&76]Q`Ly(Z(Jk\1aN&/ AnW!36Պ42M0efV|r&P Cd-!lp6f)e5º[7&/mMV>Jybq 3Zn&٠G=FXggp94IOEQ4=PE(b` LpyVxٗMC`[u'Yqķqot?Y$ *EL(~0!LE‡#r ̄0MӺ9VeDa~Awv2BC! Sp%,T W)T H<K.:勅)zMGh}8R6^D(n#^B|Ktcy71/8Jn|+MKi]+uɔk[n@rg2fC$ -b#R'0s‹V"5 $&s S!tIyO*FR}P&-!M\c6&EZⵍ+ݕQY#27p ZOir.l$7UH/O @@̂rxn4/ΩhZ92-T램s<N@;~]d_g5@ΦMp>3ULm>tg1yHXp*3-P `9o=Ҽ)?ЦjM DPf]r`A>f1"P-/;d )ʊodFӅq^Q_XfM9u<[~O dTҍ0T?;LST}h>EW?2ҋˮΤLqmNȤ};]uS嵮"@!LQt1)}/Tﷂn1u|~袋.袋.袋9 #hcmOvCt\4"[:hpW1,&iS9C"R)okL)sNgGhVeq}i~EdcVDڧ#`6e@ L_KCd Gv\ b**>nXw_<kSu$TWH(lGY \kS?H+J>J9|g@JIVAp܎050 {4ie×BE-N2@a@ڸ7U-'_gM,& %yAڟU/*Mjd!;"DMA6ڪõUtLj1/ uqn ZQd,9 m/{d?EŒ LLeXuI*8(T-pn*8CȳRFuxC|.-BF R H9/*5m?Ϡ*%ٮί¿X2)q@;"~{SɄY2;z$"CbBV녧_t4F(/S i\D2m#Qbך@;YnJ֣FçgZoQuKdmCG9TO?o gc~Ze1)J!DfyW3iD4Z'A3רּyJ(6vhCHu7v 2Md>?H]p?ѵǽ.Z62g;7d#h~a".8JdIf.s9Z#%|9Ab|XfR_, 5.yF^f4d":A ^:a&#QJ>Cz: w7 b$Мq횏 Y^\ugFP>zEX`!rjP rHf#-n(T-d%쾼||5r6`;,Qv_?gDKcB,5"oy N$5-4$1htAsQFOQ-=Q] 7uBd+QtJ$rDD|F~IrmryK; hq8~e0]Lҍufw;NExl~ՁmOGfyv RvYG{i%,$G @*T?6W:}>&z G~_e1K3e/EuZE xA%ILX2PSl3ãXF</p`8Ĩk τYtӶ>nxĶPR5}$bMlЦHqފ]7!(gˉAԓn^:b c=9Re(*p.v ']*59/cSE9bAoܿ8Sm;ͻEĪܸ%,W<H[?1I(),'ytK R>lє\7*F9("g8/PetL[x]z kLh-&*Liu^;7y@oWجk֤fP KY i/QTݲB)UZSM{؝^z^zѬ6km9*MNz]x b]4SM7^0;~v>K Y<)EYgѾNRchCv ܉36)ޏ(`=q2γOCIoc_շtitg[1~]Ztf}44u_!EZm2)&ִ^ ֧G^|;\IZCetꍤ嵾 %t)yLa6̇`#'㫿Hg̼\ߢ#ƴ5xq6+|:lx=h:/c 13xsɯӔ5HSJ>*$m8S>p_5{gBTmUI=ɑp}BToHȫ@:/dujwٓN(44CJ$,nCL;s]9K|.c@Np9@z (*|DuE 1?jyO+X"ONdk\2a:#x7/#m7z&|o.NB8WrmϿ:ڵu?$ؚEMqH*zX-@P&&s'PR >=~ni11itK]L0' 4MA}?Pa;TYxDmf%7۞z|bcrE&= #Y;vI~rXػw;ayx9xXZ킾;&ӹY%0gȉ*ő'|Q J ,2.>U"GA/Uj ~pݳ>XWjEYG6`s{n7z 0ݪV;u[F[ztU>hr8\#YaB̜2I;+@:ZdL8?t&_ߧrT/FafH{_Xwyr&D]avKQ ծۦ"ssKn9%VTO.T^K i;qHܮf6 U'@9Jm9JJ4S>; Tz h T@16<,,٧amI|~5]gYg: ?"{VK^hS*%RCgUa'H( @9x *As-SM3' H؂j, sQT&Awݎ9bHq+֡RY3S-]dxH@CN}Yo$bv;_U=4q#q 7!?~L`bV Fğb;Wjzj"/as*sٝǶferъ*"E)/iIPbXvӟ@yBvζ) ~/0g=G zH_s^~ľE&; q5N2/ъUp+)!h]OV}lTYHt*%s"N6 SB,r8lR\~s}W8Nd;=iA/KUiԧo՜AXSI v/gk,QM^s{&yOKBKZTQsqK 7(wg5ײy]G^m6;j[?#๥[ ppFMW'sqB(%q%(ɞI3X,rVCb6"h#Х{՜iI)'* my%kZO1uLZ3AZA>xITt14a(V|9yHTx}(P]U2B{, ɚs;oeޗ[b@3 u.ˮ"K v܆t9fIsק2;enL ;pB\Ɵ &w}| q#>|܏ .{FPDEVϖ}E +z&ܟ5G`.O(1ΟZ@z(U_ʮuC6?=MFOIA$jqBۉv-r'!)ݺ {g zJNh-Cp3s#'=*yM?S"f{X>Ea;'}R'*( 1:uE3E7m#"Ƃ22 +v%wg̻6۱B3~>fyQ5T^ECiOsg5mc.Iҹdn? coUjsǚuSz')MB o>N?aVqV4M DQqR4ӃOZHm7)lTϗ*(PH[w>k,G{׃4~CHfͿxA/ U)8ٞqƣ u<+ }^0 tD%p%?=6oxj'g:}ү!:Uu2ZF.QN;әh :_]~gg-EfQ̡۵@"I+sVZ> f*鉧!s/oEm{9!{g0eCkEq d6F;QYħMcߛ;B+i{|N| JgGۇ9g OޓK f]aSÑļ@/@J$WKxC즐=%ABeu7:4rkkhnlOJeY^ǯt+vN̢7-s[/:kreӁ!H@Ht7 ]w'Q\ EDs 0D؀ၝ%9?K^f hYAP?eM aᚓOBһYYIl_H/kBpz>7l [:E񻌋H{=}d Ij:~r?2]wϔjr- IE4yES0I.j2LH84LLrd{=1=LZ6!xHrZr8D>'E=9c(EL{OW^ { SPA ]h2ͳ~)/z_D"n$X7ginY/"g4 G"dvn|v?؟lBգ:;>XM<3F9X⋪"V]Bxx YՒ=݂`M&UHEn`; R 4\̜$ y*0u%ߠmN=0`Y]'j'u91lV {s?UIEeOL\|_tzj:Y:n!VTrcnj._3 ӽ.G[ɞֻ֣k9H1e$ZK%ĪVx 9ȭ^{KL\0v;ydRM0GgXJQ~Ȉ/dI^G|@xఛqbFufJ(5,\GRxyNl%=KR%_VDžx ]DҌ2l#h@a3?JH P@\hA~gÂvxdN6RfGP&n bQmI 1Ò]0tRqв:ЉSɔT!%HzDe6t3{aΐI~;𻓱Vxwo}phaU4 ݜ}%+sy}iazXR_[mCȭW1g6MuH*Ths8Fzf"b9#i *;MX+9caE'2=o_/^\CJ 'aqY2 p,RGΐ"+EH;:9cw)Ͻ$ 7Q, !J"B(zLc&9ٔX_Fb..ڠ}BtĴ$HM57 Wk?͊CcB :nI@;nAd.;Ҽ&uq wW.{";\X'uS2vYBsk}! d޼`6O՘dF~ d %{%$:$@{:7uRddBPb$\|s;qRYWkoQ Dc@'c*$i/.@dtųhL_ -a/ȊX)o RX?.RSXOPɯ 9đN•+ E! l/S6iihkG4㼲KΙIR]d_q,>-7f3=@rQ@$~2P©L .;]G$F:KTqU 4g=uf\s6o9ii9 4K}U0Oɬgҡbd $x.fzĭ5wF؋Kum\ ])3?ЭҊ#wcx L,~q??NTqĽJ8٨v{2Mсl7QBj1CWNNm4M=Hs&cJX>,"Sussi:ySKoLJ N.]>#5⟊vWàE|)K? 3$@$:x|7^¶oLɻ>7BXv| ~DqVW{< Tnxǧ2 7z~yX' C꼦;-"(MݘTh,=JzqN6#@Pg0#)9'8;FPmN5em C))xc6QEmBs=/3ɽE5g雟 5^y%DTRJ)S|RBɫŒxjŜCzi3; z?_UO)AFE?)A G 0[wbǯ_$<1vru(x 'egHwQ&:!ʵh?큗q" *GpJ@LUI˳;8^qJσMQ 0;pcΥˎf?{v %4}./j {"R+~\^덌%4KXܣl'2'>s {8KI<%mOW/U6^~ *@ 'N; oYoGm`^%9I}$1+&E?!@1tmIw^:s[useص+g%Aq hmu׷Ԩ^*WCvS8F:ӜICRuψ|o4`:22)5u?jI;E[|4N5?0ҬhWr HSqڠq:ojڲ`)?9zz ,#:L?'~4#{_QM5n<TUI6SQOg/JwNv|$N-PSId"c?U``y0G0t `["?nt tW{PA8/G@嶾*0S|Ⱦ[HK$\--9<򹴪χd 6pu: E*9zEFXS<هNhnhL*K+OT5I*oʂW|Bsh(WSjx!s&74gB?g rI:lD/{ڐIm>O4 m"iPg!3#>}H{ +L5廤ANy= g0kإO˾'4Mv%/v=A21U H`,!݉neAƫ[7G ȔdJxg<ƕNe/V2qClUn/4*Y= ~OHIʻjWUN|5Xa`ѷ(\d܍p%sᅓfq÷bCXtt&å2Nしc^  9o=WQ~IؕiiY<tuo0 K~VRBahBVD- _!^uR3Cx x!Ҫצ;'{ۑ'@2cCyx^b?Jzkڙc/ GK>H -=9Vl]\H(u ]Z:u&$g&p`_<{=`& XSa#d8T* _y3V_FP5;x=T%n=~X1Y3čA\L0̮nW `~6 K":49jZ|ϰ84a:.^SeBn:!F PW@ބӥ} &%8Av%+ChJir{y:$(L K|>72) 0f&ܽKj|"#ֵ$c}V낸”Mt \']T*q`[<=Wyy9W_oȣ*zQEGIyҮ)eGM {b&1̥?jJ,Fյ¥G wRč+<})մ3YHAV^|TuvŋNkL{>(moxy!4e J:ƣՅxs*"Ae[ *s Z\],_#u)t$,CȎY%' pHdJ+@Tq?iv/2i~D8O"R6A4#R^1(c0bh o uZ{{-h\O $Gd$ c'+JW㢷-Ʊ~u?/ݣ\eosӔ'ik݃2J˻{=_ ѵTp7wC*ֈ ?up )9<G435)t#_ ^ 2ᣀ'>e)SLʕ%@|WtƁ:ኑyٴis&6oM)" {'ؕt@|z+ǟ:Ц!:b n1,DdBrg 3PqNa_eyrW[èdiI\>/$s@jFr\9dIq+peͽȴPLso_0"K\Q)cRdOn $!qS=kCokp#kAUGUw )wUw*{}o !? oSx/cBq)A<sm1#si &*$cPR4ߵ$S~2,;}ZqPBsݟRXNeg^ʚc0KLCc\02%!bM\fLj7~^m~)_}V3@REw_xU$4O!aɺ!j;u,cA$':PSГ@zgvF_2k_U1V (m{?,vh=la/Y̠: '_i8|(KIjk~*HPH>"IB5d˝H#t|ᜓۛ\,q/o4JT+j:YMVԊ!3PZ\Yx d[SenI"L|[bB]}k־̃ܕb%R6>zU2 ~xҮg#sw$"D 55a*դ2ZjHwYdq.q7d($@.p'euLtW]8=I[BÍɤ6tQ":s{}ܪZޟBS~%b:"bB RȖCQ!L."Y*: DRF. ɡDI ڂe`@\pb%u`mvUj(z9Otbvћ͋m"+'OQLPR' ѽ9Iλc<5V%i!AXnh~|Py(@em$H494aהp$."Bi gpR2`CE qݍ;=TM>7F#jd5?}T}Ź-H-toT~|U>H oY_Qngb> E"3> jN;C!qnI|^Hߗ/Ns4.\ڂmC 4F2_w`vNg xNeڳbR5"\͠ŴV"W ΅ot.b(L S K7NB76l#G_/ն81:ll"bsEhܯp"T hG45)[ǑgP`P`oAylef-m9eHE߶kIkQJlj zY]8Ȭˆ!7Y9 d1.7Na4Bh x>&԰T%?iE$8GL7$iL|PEJ:hVakTqK8 xm) %1mZ]LHpo wҐөAtZJe /?v./||7%(\ܑ|vy{p%RN?MF`O8v?o \BQwA4hʛES5|zjXNY;7'=aFA/ǼEeƏ4Q0ж4K$Fj&,CHN=g7Yv[-{ኋ*fTTUϒTˠ31E-& <5zMw Sl>}MxEK%ҟ¸;HWm/J޿JˁO|֧0N'\2]$VӜ)=:ՃJ0|LCc|єX+óZH@Xm_ 8R8'-ѮUC-SVbKyDӞBh؎o!ϛpc&ԝIV\8tX"wV ~g аs>i4Dh4h<17dlyҩ5h\*շ:%œ[y*3JE(4ɳ:J,eBE4y@ !\4[kwM;N oB +$ZQJ$2mlg=|3G)dW5p(u5BQ^kH4EeA>RyaxxU+, lC1MuB)L R '*|篞Ȫth]ٲ}V%03O,#t2EiGx^^ 3)#tcC뢚EAVe&$;C9F#БL4tx)!I>Xzf^('8@)ִ0BiּݩG٩d9q=,:J@FbYiXD9'3&(suʝ$y&+.iަt}[P8)hЬ0uiMm%w9Jv\~lDg2hƉ:Q9Q&xɓj$7Vk~7-+ZϾ&]^)>rм>Q7]#GqJgCqv6OӱCn1SfRtInz[,@^s%YbU"הh|/&(\ԽAolnpxg96oãNqNjMOǻfÌnWwŬ 2ؔfiR&(xfIr2dSs=LW]s"Tj궫%=)-4/ = $ SO=&JQs:(2;GAV l3m5MBR$2b՛jaU$H{qGc}ֺ׺';ʝ@_UJ.;Z <?0Z8^8SPHp{ ݦ''3ltyRul9P֬Ÿ3rI?3Aq8lM .[Uo+ϝoJؼ;ͤ!&O09>A<$V0àwJW{r%-w) x(x!`96z;Z A\tN,C;u/!8{+88* fŒR)F=<=*;'>]O35j-+ӧK:CJa;L mgsܘ|&z<lkvUk!dVEʚЕ(8tD<2~F.gE"÷/yrZ&N!dk3tܐc,<$|JR ){S=蔊㊱++Rh-ר 4pFl0 o3nm :` #,7Z1qjU}ZÇ}"bcљp튷#C;NRcJ(@ҩ=.Uҗ1#ibEnV"2p@ɏ'~J_.C{ 0rSޑ)~7ofe ˾Rm`"쐫ITUA.Uy˻*~ tEPx:K(*'4Xh__ČtӲ [+ ytPg#)e:4j\ 2z[_hB7X|_UOZy/"W0_q+D mHK^jn`PE ꡬ' YV{AiƔc$̬+BVt3&cӋbN6NX>R6B8~iϮ**{'bl{^'!ՠ|tʉrp,21dz eT @z0Șs%4.ϿAO`A ILIipQLCnQk,Fனɼ?ѯ۳̫Seer GG, q>eMoXVqj)[Q;49ZWK.f1ӿ /M&ԑ뤫nb}.Su.6II܂Ix->^"bTvTKֿbG cNizw$01n&,8BKxR|gy3\b ,pJ45~AN}z'ʝgQE" _Ձs첬\OV_Vu%Gp'`t\')EگU0ٵ\a:AkaGpyS:Q_Uxg`V-ኩg!y%ϻH+Cf)QˌʙؠiމDF2G4k FT,%Ǚ/ N6y[G1gk|f%ʌ }7~"\vKxb _0M@8Q%xeL B a PUJE"@r_ed~185~0w`T5=༩9cx'.G٘Rf a!{v$tma}tSl@H`5ߋ*RT\Pǩ{oq؟qUT`0:/>9 7+jDf+֪>b45Ay `lޗ/kL!$hdՓ1oq*xq){ݭBPV &Ts۳q+q!9W1sݶfaޝr=*Y;u[ L|8]L3.IE4IwU I Je#mvɆmދal殄`%h PP*ڬ%;*O}OV T+,@ V;o4֏nj?O@) T=CWI#lUu (.&57|q,WoFc } ʤs/Pg e)uuXߋ~=Mr<$ȧto'de sYwW32jQxL6p>2|2X4FZRS,_,vO`+-F>~׵tvn.J']Thhs s=.N }vNp"F%X)GBF)m.B}IYt;,UdzO)Xh }rDG{@N2U]ʲ  '"L_GevgYPy9ٸK":#8;2Y) D !ȌxGSV4`hH r>Wt D|CŢA,@ggx 󶃨sk#6D1F}] /Sgۃm% 8Kܛcv'AmT> әs  hTEz+`"ͪ m4?΃JFǟ$uf[@+i <w6rC5!p_0|F\ }Ux'Oe0*m ?~j˻%cU6}Ü cR7ni)7C𘯥5&a ]y;tY_4\\zNqyj~ #ޘϺu5%ofם`sykJyS?-u=LBh f*\)+ xb້l$OˎL%̤F} :gZ{8vi^0 ~j*-tG{mN,]>Qv$zzW _Lo>3:nl z!?m84c;!*^$]k}O$QlJ=~^ӷ*Jǫ9EOeeK6_ _/ΤU~^ͅ ğ?uf:2oPFt GyzPd}(6 utbȄd'i7)h(2UV# D)"] $`rp>GeD}67Uڥ!VueztMdJ40~W_uI|,/f[=jܷcʒlzs<0(0H:$8҈̙rw 6Wt!j,O~/E ϴ:rB>yEƸ?ğo?sfwowtδc=S S`N]^u.]eD*=y65MVev_9>_K9w㨊jN]^\)6nLʣ>x.!8:Cˏ&BXкP)xQ(I!B29?sLV?z+a9oi_jQ=j HiiUGZV|usrĆjt=N+w!#>;)IWsC~5L5n&晴u.i9=F/d6s{%5!%KbUAXnYq2 ;]6V~ut&;ϓZ*28]T.` 8T[B"g|CyS d>51ZU*"%clSӮn*q_[ E`f4j8RvbR>]Rd._].D޹,5ͧK҅+K6GWBH5 4C(H9H%D6s˅Zak8 kVY8 ꞏs+=B9JvU1R0񾯠t 喜럲p\Frˠk"Ozf,FyX[+Mz"k8=C陳X{كS1ӁcT1i 8r}8WOs >J[wCV/V%0[7ܰ4x*o:b9Y$ k( 9mkkQ[DG8Z ?sz?V熁̓Ε9>K_&QFM!g,9|tR!'Umz"ůhw’7A hl沗^ăo{Lr5YU"H$f)ȭ*9{KyGl}ṿNcjqd=Y֔]ڀ EtJ{|ztUZ!<靂Ev @*4 8WtA>7vex>;^_oQdkgHn}ǵ`@Q++u 6ǀY$Crty"MEu~$Y=()kH z2\9a`Jt?C{-47Srgpѕhd[S!CIˣ(]y|Kȇs:bbJw͐LiG< ij);mZz:(.e%yC? ~O62y,)TޡQ׳K;JT{@Ulڏgy|oޑygYNhXaрu"fR߫$[h!ny++_ %<2\kECX %>-A#.јEd ϋH^xM|HEDlwZU%hWFVBfsʸz.}^}-t9P 8- gq/^U'iTW5|U!+sBve,z, K٨[7S~xr&^>f ƥV %,nIzR;XM Ydrq&s,2(baOU}sBz.d>_d$U3B Lg􇕥`$0׿4)UD?!+@xeYq8g+Y&O m5aGLYb7%Yd// ~Ygqfgt@ ʱ69+|j \1\n\T'bT9Iy4ujgwZ 'wlfD_" y{ :5Wr-Ͷg9bg۰/7|풻éiZYeָ32U0HUV8.q./(s/`*jƦ `iO<%\޼nqy:֪UR-a0ע_eE^VEI.chA3#HSC({y "NI, '&R&'uf& z-,.3KݫS.ܴ, K8-SJu +ziX&cEjҼ [}C¥/*e9=RH.FU?y6mhRomq~X1y %(.PWStlYHuk;0y|T2cGI5 C_[o!]mVÅ50ɚ͚;TV?3??Bh"rQ凝Ϗr|7hspׄF2NcĮtO (8P6 %PN1r `InA뇑@rԡ5i{WLj|xG%?3ٵ2kU߯d=g+x.lSy &@'&7|{ȼ's~.-}A;՞."]J8qte+ +#O/Hۂy !_ F:h3/,V6iȭ"*(| a6W n R{ Kt];4^xxP.HD1% Pw>/ZStZ C%>YT B >]1h\Xl+8Б:{9I,[`AleT%ۿq98esH>}1މ,^~㋒Q̩YEg/~DfNo@u\\}n_h><.PUۋXJM avIN2u 3D/uppSlwW:@w;SWt o1]}^ QTe Y4{3? whռ |д} T#ǯrɒVEmb9 .>Y}`bzǍ;BanۼӋ~$fǙ}CZRkS&?rϚz{1%oR,Mc58kRC+ Ͼ%Rѡ) 0egZryTDWZI|hK&X@Ϛ1! f8Pϡ2R/ݧt\i e^S2,Zz`0H(NxݟiqbCB _0:f_wZ+xt>bZu{V=d&<;z&\Z''cwA\n;n=kq|tuZ/&ninVf]VE _.vН5I~6(\ # $wvC-bz ,@5m{c56+"ݐlsؑk4+ֲջF8zf2*rPjB@Lgr[T0N?WrƯz$2:~J[@x כ:8=8a4&M!?]Y, ? @H J1lA1 kMjk{L'?v}e79aQ%gݡeJۣЛ=Bq~2YUr"2Y$ z(J3ehnbY<3 К\^dS4CUv'WmZ@;B Ƹjm5/1NFyn{co(}f.u^ ;"N u+Cmʽ+Rs7l[~{P{!Rlbhv`u"j5nʭYV4K+lʬKxx,l1ʡ9Vi3*;SY(T>;*,,(e:uPxn-Nۥ˙ws+ 7zȫso.H3\lօl]>a~9öbOLJ +Fit'K%lKD}ZGHhB1 Y_`ؿXV)FT!Fvp[Ɩ=2䘀,/Ý_|cn}-t6~?yPĎhƘW\O^ءU5܈z2GJr PQVG3-t4 b-)y" M"]Ԫyf8(My[FGT@0ǎcp|SPVHt= 6)tױNʃL;Tׁ ɝK;:3ǯLnLӁbdM#z.A7~`JW2k$jδ*%nDFR`9}=M;'n2|0 Qǽ3$XMxm^ O%` yPξ "I rKOq 9 Vt( ӊѫy'&1͎WZ|V(偤 r6[x_S̎8DcouH)u|U)Sd׉ apڇp"N% rd)PO{pC~K78 )򉊦tǦ7 ]"&+x>m3.ő 2{{Vo v(eS)(Ïq3`~W.aUO_y`=s'.(ѷGksI]_"Ja?|OkL "F=&DIj7G"D |j[h~_o%ؕh>ӂ(VVLG%K Eq% |2m FPM* >jV#ΚWٲ4aN3*˞aD*y5fF*s&S ;-G wX2|hVBʍE% ON֜:~zMra5hBN8iKnXh` ~-4 ~29o[C TMOu Ɇ%W "Βf H(4t1zh'ЀA߀x>4zOLF7?t IS9`:u:g˕vYwF(hH$<=cB<O|#\1m#Da]Qy5M^q۸OZM[*[j{"ވ8"wn:Ϗhm z|e\dMד䬌u<0LJ(3Ims2}Jmz^aQP܊mTf8=7/μ(/#B/ҜTENknkOFTՎNQY K)hx5[ ;t!\ɷy#0e%g79]XR=w0a(xt'mZ~6-^d! ;3NI&Ԯy) ^4o~ YS+&Sv '[ R% #13333333343FᴪG;❈gl;UfUgGdp - k5ݲW%Q7t<9ɱnMDI|nT5@PJ_89F=*_oz]\Rg-P,}\me,-eD(xV 6]>lE<MM2'vBQ=(P%ӣRukژ񽈮(l?P.6Ru|fAGG?jXW H W?-a4qr6ozZk#$ynT{0v2WC4'x|e3oj{ 2P=qUJp 9B b{]dn hT@bP)=6рY(L*⪾;WSvי+ ؕ3^ _fuW 傠>[Gph>{m}\Ki.Xq=:sL}Kj+=  11=g*?=2_r\xZ_\uC*-`#|:Y醻: }%71UP81`N1s$󘀳e??,4]Ke)hZ%j^m5/:`Udi"codP.f iX(6 6{{M?ђxLLǟN'RqSy륽d$II`Bk>/덶͹w ]&4̲Q*`Qƭ V[oby[[d*uz$ԯ*+C8ٰ(zT&YMĦXڽJxˋZ$a_{oMrKqԔٴm껶^n. Zs|0EEwkM [&V׮ֵ `g}QNױ[Zܰqvp*5|>%,gG&ߵI0E|sV'ζ)?RHނ<6^CR}+J:V4ҧ6lUILtA9ւ6~'bsjԟhw,nk*sL%]2YV:vWu=l_1mPmbXi7YgXbjPf}H<'8>Ā|8g1}lDbػo.̕|{ C=¶/,~x}!|Xu,]DV&rf27&7 $Iͥ}h O ' A`0A=#hÛ90<7}̛ o_VoP&Ǎ,)BbO'J9|+`M(GޓtYom#;;2 j0q(UC9tqբDX(!HyzoH"Q)Vd_e9*;Zgrd.(2P9T W±, >FpOҽ)|=m8[ifRX$)ֿȥ.22f\3*H3@>`1z^jDi dubFgi~_\2u">[HfD] c45r2)( )Gr%KEU?02v7^s.o 3g7x )'dB")\B11uzX!$hYKrnAdP(u ke.m^ wD`.p83>&3~ߟgad0 [HXB+"ղL*383[qmv%}/ [)sb]) \ũ#dA^d~Q` i ϑc̊f2!gG Q޷EItЏՠ'6i=}n ,*t߯7v?0Q\+KCGQB`⛿:h[KG4 S OT!}'C .1d4] Itީ7A 1U{Sh#dQpoK=^SּkȀyEca ̲8T @Bv)OSa-}kšbޛꁳP8Fr+;_5[aj˚3ȗu3M#[  ,qCg7 쭏"<85Ǹ"LpYd5o*/%|+`QqgEH<+cK%F'2\pNKd|h;S:~uazy|xC(;9<$d8/33L[VxCq85LNzLxo}‰(Z41.\}1TleƳԋ-e +$J3ӝӬS˺H/ZZ=^KeB>D}+r .Gm4r^{l3;U,;iأ.nvԎ$xzoGwe{: ,yPވJꠓ*i~|2cj<%bc !`W^2G@@F}\tcrr_ A5;'N_W\opvuxTDX@,-}F)÷Ѷ|~Q8iAE|Y;9Rӊs @%*( @ʣ>N$456qL\J _2hR}G>r҂!ZW@%ݏ~~_+?<Ռ }ܟg`Hޢ旫q~~~N,?hza>sGM59(n}1-gPSSGNu<>d^ &PM[!/M‡_`]-zKFWK's=3ovBua[/Ң˞ip]ÇS綴Yk@Mmaa#QG WJ;>Oˎځo E#Xw*"Qӱ!LZ!jj[;G Q׳j.^l?QVοMׅ`XPE@x^]N4/V#n[hց9NuzC: 1(pCP=J83zwu=vK?Q/ZY?mO$飈<& V4خ{6.ig"o >?No.,(^)5ysF0e|ң:6jP^{r"ԭF'ܝnړn`~z >}aNћl図S*_gU[-̽"Qx!֛qK2/XT~f/~ 8A|鍣P]^/T|.u ϐqFa:"17X<`sh@RcPh;*hv#% %00'bItY-,+>U?윘 C۹̾+ 8&]B| a1dr~evuV'/H]g5Icg0mu T*ۖ$'l**eG|(뜄Q8 zV!0C@sWHzoMތޔ zl7yqB)(2(ҏʞWEТ5hSHU¤= qd p?׆!RfgLmy2xf^*^Kt [H" y2M?Er];d방8-9Ȫq7" d-HbΤ*t ͑F[%_ m%ዴ]E\UTs),Le%ٙ$  r< WѹCx pde4w\ l^DS(N=sξFZGEkƚ=1$'dYU![յlV_ҹPFVȬ w-K;wo@l ux<W \:7*.pmTŦImNu7#{?RLPcq6W47F$^&Q$xRλ20KkP2gf8 dܼ}eD$ZtO DL>?l~Zs.|L5Q&y54jiF(R4*NbCJ8伆ZRȲr78ЯϨzܾ*9'U^\cOB^Gה`ֺre!+Bf˂bJ.:2m`FfjIdY3;E1d (rj*WivPr l`&rDk[VAqYu$.a+0#e X8eX Otz"[&Dz%QzaE*Ƹ*OD󟪔4\H[tQJozz NUNd/bk (q=d [/›ckI{Ynt9 $+@X3̘pI6M6Ϫm8$4o*\+*+FT 0s;#9gcA8ُc`1.<q~: a]dx0 = ,ʒSXy+g6Za ۵*L^de[k9H -N3 6.F y2HoyLq_vڨ0(Za5Fz' Ɏ) aBq{T"#988LU~0j,e:ksiE 8|g}j'x6v inJOĦw(F68l̉ᴱ\&)-gsZUOu읺Kd*J|{@`JE\^\f՛-Y&k&BX ; 7$>6- :>Yy,~s`?\I)pW/6֊4s\Q|HfJÅ$iU}D] sR@9#/H2 &[o@l \.z8/,O~ pCLPN^uU %ƹkhFZEnW_Kk=\'d2aG_ba;8tSu񴪄|4kV;T\aS:yXt_qð+q_O[U7 E$:f&+gbCŤn/?`NT#|n3G3xHS˯,Zx09vPAæ.Hy!/T3eڌ/9 UFQk-ue imC_?ڿEXno3f0F.}9vpP"Ǘ>|JηS?\$p4㏯JP$o TD+)3l鋽Xr>3^YAlxrm/'5rCa^;uԢ5Δ ~C!f"w64#t|xffࢬrw# SoF_\7>R"`ola[t?"^h23s =d Q#r+Β|xgz4w>4Dt1'۬*q$ q+nś+y{8;6~;Г۞,sTF^Q(ITj/ އ|'{,7^F/>:?[ɲ́NY8/8 DUpo,!6lc[zmJ0 !B-g <5 vBZ aS6keJK⇳=gtK҉_}h?36%E4 e+*&-At `Pi$NvN=Gf֝JoN.YӍ])猷$ r&>tK(ϓ\7׮,醦 rJ$3礖ۓw>T00wqP6Wg\|fXcS׻^$h)I-EmyZf3HxLwԪ WaS"hǛGքZ@д6Jp ^>"W,En!0v.S~;g-`Z@vuU%_7 _Mn;]N+C]9cL1ouTǬ O IdLKWX( $R%rt'lBU7g W($szŏvn+dR \,)1n5ŏ@U1>SZ!>Ki8U6GeJaVoY3]dՎxi9[1SfnlC [4.%vS~nu0x1x'|jNЙ\inQg ǝUm,^6P.:*$.wC-ژ ;K | t&R5*SoM 6ٚ@1^#|*KOms=l}f&!zT7Ply-TMnn ]LM4&5c1tU^C0BtP/ ?jsĽ2{'2~e 6\^Р-*o:(,!]H+aqK-P" qNU<` ㅫF&ծSpՑ1ǽRWqY"5)E2U*τck0e7=Ta{aG#_TaNp%$g{"\pYf0ӣI"W s@VKSk$i~\,qH0Y ܐ%F0-Ft$DO @آmS(f pk);\pTYs r#}qÞ3;{Iʯ=}1 _q}p?oSQLiP Yߗxt}CyO3]0GKː#51+¡Y: WX jUkykynP @)CW8A߀ %ŋmL7"6pyo~ ;SعR2ӸFGe& B紽 $pe'tfyMa&_쿗*kC lEINAZY6*qM-'TUo+fCpAh*nų}NfohбK,9ڼ,pyy pn'b_l5v[7JmpuUsOyi 8oYsPyc smY}nQwfDt%.G57,GK YSFzgzfᔀetYPdJ200B%K >4*7 5Ǣ Ց9y?}wl3X@I@h PląHG5)|}?V."Oy`Ddag6h j!(ʩz?"<8\QgKc?XJ _*2c clg8Bpf7ϜUl( .έrt(3o| UiSr񄮑5 SK΅jtOuS5kGqJU[k3o-2bWe@,b,qJ1B- a7“Gcy涷ԑhVw0 %knqp&;5{z~ykL⅚- {̀naPJuLn)-kuCUE-mb7YVF/DVØ /g k0"P994 2dV VY$ZS3pJ~mS!+q[t5 .;1%l\?8ZW*@ѣIm #5Ўe&M*`4A| 6pҌzS73vTps7ғs}WoGg!ƚa tN e49.lHJlCٜZ{U"_B&mb!ǣEOI羣DWx8JLZ*5R=Y@ 3toz#.Ls;ڒ/#3c}jxge]nLF54ik0DA+`mdo#7 Tϑy|@DG%xT,5XK_Űʞ![:7v/6lV%HV|4;76hV]/"_h4WU5朦zŵ.1/~*>O5&H>-$٫epQl_{I}` Laԋ% <{>E񉑉v}@\}# ZiQSx܈:kEQyPtU ٪Y.$W(|*8 nKHC'Afɑc:/{xaVz\3[?11g L^!:6 >p2i꺇bo1ş\SץA,{jf"kf$H]W\ ]81>MtϤJN30Db715K1.m;?O)RmAew􊊟L4.fYPYٯGQ۲A*FC:`N&A,<| DÄ?1/IH'z"JbuizZq+f,H}לR8B&9Ku KlQwfZYY.KK8):YoWi=]vq'X8]%gM9C?"ڈZIĭt'0wRߊ^=ɦYq*a8D-E]&!oEygC6=@ h ٣e0)=$o㜔S~;it)P\[ _>z R2ٳzO VGP_Le2՞/)sAfry RIa>+A}}VYP}/rHmYjyk+pa:kHqW7 ^ek<)/w%wo~d"_i?"z 4}>U ((]tmV4n Aldڟ"Eñݑ Yc `+ՔA1(V)\lPzQ* v'D*4p>Ʈvʆ#֌? C´(hKwzϸ/mZH{m-=֏D.i.0S lݓ0GP[3xP5ZbZ6KK҆ugFQ5:Y6@:,IC8("loi4-۞.YYA }[CϸZp\$#3o(^Bw`(Ny!M-ӷePJt_r`o!gl3%5˫gSs|60i)CE?h$XvX>{|;Tط pn^Fw_^z9BU)[39}}V>*pu8\j#Un$wDи$%'AUn/]mk͟/|{dQDnT)x"DBG/eZTPXV'T)Y[s8e"d Mrþՙc63>$~ujq©x-Viցk:Ǎl3~=yKkI72ݳVOmsQ3,0<>Ac)q2HLTF&&Rzf1Mǥ=Ŗ ǰGяl}6Q=H(6Zz8riV:C>m|qDnB^yuo~Ei8 Kn+YǞns fLg[+]QE&62gn6EWWr.Njua#|7JVO5UjQT_m9)9qcBU;@mOr}=Cro}FuphQzw,\[/_:>ék 6ڕ+$23jTׅ&$Hq& Y<@#u[GkՋk9 <ųAK{x*RBkrے -DD=Q:9oTπkDV`5skŀ'ؠj@ e5N]E7Xk9q66h~0Z۳{k<`r PǂNBL<@ igG:x}Be#sgК"iOhnyC Gѿ~6Cڐ~93}sq> BG4cWz\Y.*ܟSfwAv5 ES]ΩtM${SU m2PDOh xuvLxm*A>'cH裢dݟ0rtE3V.lق*ǵCqSU4u )dyP^-UC]V c0]NbVĴ5)ͲW'w N8?#ɡw{{lTD 1բȳU෽3ȖJUPҥrͻ^v+]C$hd>([;w@EE4ͪIo>n1=r8Os$3{{YrBcd?S{CWiuIk/ ,}KIMO<8]`$FēHЮLj2 ޜ2 ʀ #P99Q˒t{F9[Easrvi|=V(wi@gu u^?0m!"M/[DZxsYfYρVD~RX6IevxT0Fp]yJ y~{u@n,潚vÃkv˵ rA&"Ƙ;4t`JL<[J>9P8@~CrϠ^{:ϣqfTHLNQA6A,[|YT7"zS^´4$+T&0eۘrOѩZ(m ZӘJکO|۹ڒp=-6E_̤s D*#Ze}7 brR8 vڭkO1{&n;y]_[Vg둍ҥBnOfs)iiq00iPvg"iITvLbMpy`=쫢l1b =`h+h%x.8>xDM^ Ãի\l (Dnhiu;#r_ @RSz*D[l:E3$}}ac5#  eSP*6#O^ q#?$9M%Plax-+.cV˺(fYO_#W& n6^2ޢؐ(hM1H'˴) 뼠lcW# Y,U ؒZ3(KTg{G{wq}618r5<|eN29 "(VS6 ILK l_ ,0Z̬_4>^5|2߭=f*gq8r*?G Q+I]!CavpdL~x;_рw~j$O#sAIt5SJޕT ̥}w[JL M:R3F#{An H!CнKׅO' !2N;# ڻht/7u"U}N_ VF7e];M' /ЅIYBރ&UDeb K㕷~TAѦSMHS-pu =Ϩ ;T}L4DyqpEe<0!FT"x!43zȀѪo=#<3V`;pP5O.HhQ D:uoؖo˒Ė!^ǭpgF ](B Ȟs(UJ2B߃!}2.>EKQIY-҆42`Aaс˪[]Tԧv֣LmI;4ڒtѢ aO'=mdY]MQWI-bɔ9:5vdi& 5w|( kجIh;A֚LוIju-O4kjG$( uX| 򾵳0`9 `怵׬b8*>wzU˚t##P^_UM+lݩg^'std=&7%($ A1״F o@/8*z|yR+̒z{UP Ck#[:ApʌI%zfҺ>j!҆J8흂xE@+[njui2%nCo1h_S|RKծI_3 l^-dKh柀xR9&̷OP*J@k3܏5x,G@;ZDKbaα95t=Tc|F~Wۮ!77K0Ӕ<"<.7u;{-λ8k;" rO;bReE7sǠN?[ups޺bRߌAPݦ*SzH(,I\ܰ1Qf:2Ol_U^pR!Hn͆G,%*# )nu:fZ *,?_U@0#L~;lk־c26ieSeDHD~LnU'Yhv1c F<8{vDy$2Tl\k\d_F\HSbٮ[Ϝ?գ 8u"%hmhď9&|܇;l>Y"N9!VBи]EIZ,X PR ,ȵ0sʈ~>M@CHd8&K4Ū -y@SU"ӬQ\X{ \IflWFj}9Sul Lt#q_ssN&Xn!w5ehًޫB4xAUW%[(rWY"K5VÅΰJ_:j=A_-U9U06pfgO^Ys|9Adk/6r5zW= RZ'KA&>wåCmrertV`X@~\'}@O6e}N$'jNֳ;;Z+TW k7(qCoS "<;}nuKmϴi5M%ʘ3yv6}_["(j׌EPEmʌ])dqeeB %B "ShLCX@wG{Hg;kQLfǩ8F]XK|.9'nE!!Κ Ë .ǐkג}?"qQ͢ k\kBBE# ?[7Hfjrpш-$N(|jXl(hMU",u^2 5mX oɧ]2|o깖{pv?$Ad+)GAL/]hVD3É5ch9J"iO'N+0o ]]:786D^BF GV!HJR{3/:qөǩSE\%шihaAB+H611-2k cb!^ ?ӳ#o7IJy"1>XLM|Q*hW}|lkRP߯ry]wGk/MdJR5WrbRaCA W >oIQJ0(Lx)X5F9 Ou9Cc!0 j( qc49KCM~MmwWm[4gQ$+IxjO`]9Sf{+Xދl쥻i-.EAٶItFr[Lq *P);ytkDc.|[%']\DNٸ-K9ӭd=}k/`JK*cOBZ- o; FI.ŕ3r0Ag5ٕCyOsg1H|q;v",lA 6ջ(u|[*/Զe?Ʈ#`O8? 㚬~kj ,?߯"SM*L.-Y꘶ˌA;Yly9H,!Utu <^{%KEZ|EBiJAJS{ s"H`%~KP%qnŦ=q!kX3H:(;^J2Q3FrYg{&q3IG/]e: $ q=CQg%Gìʫ~CoɟC`o2LP3n zh"3آ ؆vKu\{y !&`9zC=mL7¼q.h| `?ɰyρ( T6 =\n ?֌Yq4it01AXe57E` @0ώNBPh^XkњY#Sb wh1*F~r9H4c[u6-P\K_*_y+S'K.>v7=8X9vh nyZeyh9_Z̺˙8fxĒG[C]>hTKVk[D E>jQ뢟Z'זngչ芔9y 3?X>ķ7 7*tr sdrev[UwB󜛧  eG |{܋|G GvY8<5A]5R ^p-4LMU'ww+ s gE846ʮ i˚3*ˇTf8/1]܏C3"R5Նa u`4aQ;31\g up5RW/ *zf{r+O8 l ؗyn9kOvd4o#.&j.4{j:$K9Ku-Ma:=Ԅ{Hx8OuG0DwR6DS8Y-ZгVS;]Y0!PI.9Td@A<.0(ɅrIS~L-~g;G8L#n~^FtSEcN+w2D1DՏkvOYptJU0!V{#e H1nrH2־}\GnD"~Gј>T؏,}q+?<"5Z'[]pOUxO1ˉPct>$`*`GL1 O~6K< 1{ 3cd]o>C eR,^E%5wC[G=xȟg(UwML(b@`ٓ:06' G3𨸨dKmm7?Q`?џj fԫ"3B&cMj ,{~Uuʀ64U7Lawh2䁐{v:9jViuځP6~GHMc^;>t{HT=-{S~Ip_; 㓆knEkJ -XZӥW5wveE-Jd=|&PW_~+[>P*p|6V SүuT+=[#5zBn]JB/ سXiOq'~&.P\F^2HVLlTC"fwCzԮYM<狎ZMsʐ9 1#Q[۪dI# dU:;eOL%F<4?ϔgI YeP_Jx͏~߭J@+>W\Et2LKuu4ڰWKmo:kE!A5D{* r*Ҁ596b۹n%1jO roٯ1%+tE`");. :eJգ j9?nrKB؈n-Mowǿ4!AjW+08u#sOF+@%C =ɬ$19o3?y˕}e@MJ+l |N3j[Dt14\ V$N rkQtl|wڡhzLӌR%/wc,'vf-@]u}!P WO0)1/&Ҏ֋Y\huk+"狺cbD&f-![œڰrHa^<\ iJS:%9蔍8`{i[74H I\cʯ"$vVjȉ@A?MP {289 yڋ!7 ++m tw/_uAԉOR dpjlQ?ۡBPgrE} XdPQX4*MW2k/gLMmjGvTecdgȹ|}ژgAQX<^Bg6l]9ή_%!PQ<4]#ʍN\%.Vᰊ{q`+HMIz: : vST7KsE*vkQYCMƾ⭍jzW9OoS弑w.`tɟTWǂ,?ͅ5!Po@liw>{@0ǭ7җش\fD%U㉓)= ]f=qoTK׎i 8[u<k/.4*`曖]vZVWrngee,VeI%w^NmFFIV9}uu9Fhrp+LiC(t`B-Bs7W]H".&-f3a2ZEujni 'V'[@e3J~>OY…MNe Z,iLoȯ7Gm;0uF)XŇǨmQ<>[YWY_iNn ^73~@HCNjgym^IF彦 :R+]|h.QৡQjb_Ġ t{S)~~#~3+Wf{a6k Q4鈃ͲaR0tFg#OhK'ά= cw2//_)``3c\Vܷ5q*ӯsu'Dq~/;m^O\t=g^TʭcPYH:}Lx48H4iUkQs.b|8)jYgשP]oG|pZpk\׼SkJeC_m5$-46;¸) IۿqEl\n ZԒV[d/V&\L)Y2$]1$Lcdt䲩YWU0^NO*#hG?exN)No?r见eyMX30Dk2xn!JxW?SrS Zŀ'ʂjSEe~#a?xSO[͡*mm.ب D.۸C?$\4w{BGp3_iTJo+G8ctg_} nۭX_ A2lYa#I">lk*_(> i@ [ןVxsA^Xyl7 '&<^po]D!գYVthǰ?&@{"TWU}a&3Sf: 隷ֲd7X(h{V{&]h^2R3q}^oϺ?63oިq犒mRkFQm\Vfc#JY^4s49,Pcōt+jAX0!ݳ4ExDWjEmC~sbQWoU1-b%λ~ UDJ`CDr/-p|q8X9&"cq/A1:+@ mכg: Pc ΠUΩͦ·prޑCoMo_Lةs0tP6R ۺgĆ"E4=.1 eyrDػ١ RRan9}i =(zR+7rFMwGj)A\iu6kd=D uDO@r޳{:#lf!J_rܾȜݬ0̀^nP ^ YfFls0QSh25Bl;uԆ@ fbqC$3FZpZ>wq2I/]:UO8qn ɼ d&yL=2qd]Sp%YVpn? D-ǰ18pHӓ^ȻR_VcqN.t?A s0hVWhd_ RA0 {?>Gz Ys ~ /oh;ql:KeMКr+36D%M=dDZ) T,VqHsOkX\^ %!Z]B"$w&)jX1U:Pw)}EBJG?A_īփ"遼ceES)%?ߙAk[JN-Z:sXt/FB[RnFݷ" /TE%*@OҐgvI`3oPnYk :R7T#V)'U6֮q) Z/eLk;A!| 0NrWf̜ M+4UI.6]uhrv9gcL[C{(AgX;eO /,Br79xE(٘G F^*V%_x jiS vGu;$9``8TnSM')kaۇWnA)Z u<ګ+7**4hf،&i6$٤:JNP-C{vT<#_ofuO%D3yD8k ^ 3+E&'X#w5Hcw21*ѭK)bŁn fIju9d|ySDߋevd;kT8[,7 ŖY3NPN!5S]ё>Se [Px)2jYADaAP"n|ټ" b?{4F딴䷆g"$0TUu vx DA@`f4YxI7G#%׬mX {P0Z 2RlKDSinT=#MGxTJK#iQ3`]eLAN)^Qd:?aV>f7;Vd5SzS yQ.5DϯMعU(%^4d\n@^n*׉l-VcW|6>=_k^L+e(㸾4(S&}AOJЇGg;{ټs+> -tݹg8{lcftnփ72Pi4=h{̯6B +{8{  ?t%;4y5b[{t"_mApy技UʸMJ)p0!7++Ié2qxϓȄ5d0Y A)mN5Pۋ$jdR8]J[^.Q zBTeTR\)30*/Dͤk?P4:GؑzR*MqDz"A(.G?*)6>BSy`>#'%WĐR,!.OzJ+j=if HS[8HRPiC;q 5(Բil^M']JZOSm8J@mP=K CMu/哕0Lxg@UO_*d.A4> tS{V,TvzKlTG]q=3g*!Fcv+X &IϞ9{TE0z+l,P~\nH/ABJZRȠZE2dT*tU SU-xξm%zi"/p{Zp'L>H㜟@{Hi'e2P `> cJe6e-||a&Fם1]s<0f6WG*#f؈:UAܒ,Df1᤿ڔ:w:^nWdr#oɬOk?NٯY(< {~/ BG*\|1&vtzo暨2 t`0j&cN!TR!Y؍P 1KeKdeピxr;Wc$Je Vsr8b}$H0_/5"9yELl̏Qty<*AٱB#[c`c`<=# ]_,`KqHOg"l^y%z(܉=! U 'BHb-k|JNi[EVsC)=ciKMd-F$.Hisai![EmWt8?!`;\~|FsLq.dOhi7x1oڿSBfkmEGHyqjh-HZѡAKg*r O5NN~YsRJ2<eh.;䶊}}cB;<ωuoM ,zKj&NL^=Tw 3d3CL,Q2FcKg.SAq1&+!G`< Z#_!GjGG~欕.X-cKAlztۧ5N۩7HRm'Lv"kUnцa)`jEsLikH#N_pjmNh{kx*SE?v㘏rm續8%9uKHlǫde  mH{#slL*%Gq45K,RB4pDx"c#gٹ>jd‘QZZ2`T4:>Fe~RHQ K߅McΜZ#욝 m|ƹ@D:Ѫsyv c8=.ҊBeGZG5ԓdU"7oNpXM0ڌgѦdDCBPME!O CcfLf!l5~S{{ô(del/"ؘc~ Ԋ^M^ܺ,wLjL e {L irjWL}xZ( xzpaBU`r쇴;wHW-ԩ؆ib9g`(ٯGڏކ7jkkw/.k̵_-j,hR_t쌾l5.-3JeβZG: ڿ3xD,>fM/ിh'W,V[|D2Y5 B|{ꄕs^iKLשL&q^3'Ch(:4:MK|sJ3tR;j jnZ;{Cªftպ50-꧝X#3yVb}טu l[Z@ɰ͉J l?m}d_hwra֠m4|u1d9.ߑ:L2X,,ib}15F׆}l?f *;j0e`~!>4]XosWLI\)ޏxFvT589= G)JqW+5Z4 ⡪1vrLj~Wب%%`V4<5E4t/#%ZXSX)|gfqq kF[sax(>߷=u( J.@5[U3Yi> j^JQ/ Wo?,3M1yqD$2>pt9M[av6nw&^<ڇ!3TQѥumz(W_ɡGN;9'ҎJH[͹^j-bVI]+7||@辨$ZkY5EWҔHDXX@(X!әMy(aPNx#9!!] MBFi3l-\9,WS4>+A91dL~a c u!*t(oncT4<^>wWSx><{0: ⩵D΅>2vfW'D~B ^2V{-7kVRkTTP35ݾLVGS=-5/kڴz9paM.U\UKKk(zU. Cy7l m2(%=QǝN쟰3D&1Rȑשzj)uYkIq a 3l7ʲx@lCyc{AGeG7; b1Ҏ6AqO|'@/LOZ<(i_LcH.Tw[<1r8eׁTѵdCfWX;uJ3VK2,^Y)d"#I~..-7/FR?UYwfcbHRwAMY4W :დUp@q]ۏx;-kc_ցu"bAF"uq)\{7dTB3Nw|WZX0;2Zf !\+xMT_6KA/=U؅Jdta S w lį4U+@h nn(VAHi6re?J:n2s$}*ݫFho wsr >!hOe"x3e:zFY Of(2K@aX k_,M ӜQWT$ B 8ϛK,m9{ ZM܉$^s4/hBH!m!ͅC~9Vr\~[V5Sy2Qd}:k$ :-⥫\>$NuWgc}0vO\bB NU-(c;k_?V-Dh~߶#T>EY/:m R0}<ǀaߑB 9$_Q{.l˰jL2): rAQ@q,>eQqneHT^pb l m]. %(8Lbe _/~w343ŀ/A܉K3=ׅ;~W}έDۨpofIhݤoDi-G~NaAG &U~$|T̥\(J> -6Aɺ:kNne%P0 EҘU\G̀DKƽOj䓥Zj  6ZTU.؏~ׇ/?)eQ$ʂ貮 `t)얨3#࣒l&ļ[tfu&P]T>_M|E 3,/hX dm$!iмKrZܡF!H {P_0ˠ ]Ev .5\B\vD kU1,#(\n7Wϫ ԗ5_z |זLl1f oܒ ˽xV.}[QE^3ƀaB^eƥԂQ+^4:SԠ!ـV ŝjX܃V/ߣ0=H/l ep0w$ѦܿMnrP֧PmNY~i+\wq,~,qj?;.S_R'Vo)o=@M0&criϐ_sq|J X/?_~&<(qSLj' U ӽ* M;Xw3]v4BL8בТ3F˟r k4:1HC1Tbl-~l*?5\n~~߬ڪrOfY!"Nrs^SQбO{P9oI)fC3hMVtlq֖HUV)gU!|@9ul,SWbD)5-*q•ƿZQg;gZh*5]S/0j)Z(;2G=P]dSugُs9=f +dNm{h;XT]cn@*mv3kyC2ȉ!_31)-el'+tkYD<Pj T"ZW/Ă޵.d_͏dԟ^ + s?r}Ai(i*rCP l_ ]pq)pkK>i)끽77 Gu>fA2%#͖by8i<I6s഻dLOTî諑}:sW񝬟yfå{ޚ*E6kMQ_Y~%_ENYb)Dخ/q(a,E*k1 ݼs`.³BB 3oߵϷCWqsxD-w|4(Omm{R J +]*uDb8>9>S$\I ! ZA*˂N.sayn@J?mj_'+| D2gozRMwQ>ݘrM$"J7ܱ,ئMg>)Y?C m^((}wa;(o<BMw3DZo^"iArU '/P:&[9XkZ;ڼfV-@VOR,#vU!+$x8yI@?yf^n@R*cϘۨ0eNdS+(êZk>WbO=BW#R`RiZ?$+fekPJo>l}F&'滂;ȅtT@u@c*3G)vÚCC5RU6 EsYqJ8Ori}To܁ȚU|bD8J"dQ'"=67/ܯĂˊLKdcACTʞ]s(34@1 uȸ/L%+r"@Cplp4~ff7Ta/0^ʓ߶$[~o ECfS>AQ(:QHPS٘R ]Y+|fWJ\,^HC1(9}m%\rLy$P}};$e՗r3Y_ *8AAcKfOfkNHeؕ҃/^lp^3EF#^șf'큋PȄ- o4?{y)r?3{xDH0g<iUIwӬR Yd!5Ϥ,Yq;nsA0nI9r2^w^lk)C9Lgg,K-ˎ0_.s6#eyAg)4"Si!ϤlKGO G}1KT>ʯP}[ U6)hQO(Mwp/tL{Iֵ-,}n<ًupy@Ǻiߠ:CAN*e?R)xAt63DO k Mhe2]pRT"16"&/Tݒ?i-r9|s7&H{i>*:&J3y[lDq& ExMXAzMQ-I`@/3G81eoBM.q%8mQ.`|TZRe'jd-kEI5GΚ~S-qxQ鋯ׁm8VL)3d=jb`ͫ~YàXYw6rRWA3 B)"49k.>^U$b-r^Y1s, ;Lx}=őW.)*wG[K7rSǙD<.Ә<*ifVHbl܅q8ԛgf9g ٍNԚ^3:Ŵ]Җ-6 zI{o:9,9DJDℙ듨q!6pw4>d8H^#u4~Oi7E5L8fu&(}:X~Ȫsb";TNdL}&l qpyq>KvMkm'aHSQFa9+ekzTFyqm*g##Su[lЯ㖵"q=ʐ4~) uu\(._97Պ? *+h:WflxCQB2)|vbyhR]3 H)z\Y/x>uA7=Qs%U_5zXunb',\Fl'Pk|DqNoE-2~øvmAlRgB{_Ȁma#7f0S7c:Q{\ԽG r[JteƗF;@1LN)@bௗG(,HOIUKf" R[˞b`y"c#j ɧc{nKS`*4|Z/ cZL\+dY_u ٜ:KP8?i '$De~yǖs:$)g 嬘 d.|jh.r~x"TC@| "'F0E^͢l+źMT[nnM_ki*28S:mJ\cCbE: 9U=K~sN~|عW2=N %[-Àܳvu':ާ5 ZդW*;/k"L2$ \ t=@O.RS\$]Ἷ'pnU(Ng2ؘS*.# cJ>z ̂j8nHP);*R3Tײ7eh?ek>3hOThFfg`2`5@y{@5UJFiuSP J\VWmԢ[y> #eƪd6WKw9OT'ryxyO(sHS+Qi[7@'pmMs`[8`-UQ-ͪbL :'/p᜷(&"+hHͩ_ߩհ8a-.?g'h6Jw5IzȜMj>pY d6y0ɴ9ld"0>Nd R0m-ΔbW>ni3"n7~JEU<)|[Hqs~={P A6蛑&PA> \;=y@DH<-%u4*CvLsU_9 2?A<$UYjz.zLtgF)kE/2qIg@=Q$p~P-bfh傽}2T5ļ2MiwvOd5èu:(B|50!/KZ5J5ZPY7fŸ[%a =teoye ]҇~veVsVOz X)Z'Y5)k8M(9p>-.6i?x>T}~Ak92n;-ώ6nm@_\x9 dUk&T8OпM H3AizE(qL0 B.:T#^ExC  0L:pr-[-cVspF5Ly9-|2ɠBY?N[ār>xJjzRhY‹NxXr:HP(Bfצ-.]smY>تnI@|:XBCXg㓊Iޒׄ?/"). 枚Iq MQ_D-%-_; Ti$I[](Ae3 FA, u&JbW e;ʉ JYJrc7qM%}T`k)_sYLxEڹP i3̸[{#`gq.Cj'Kե#JE]QɒK~箋h"OBvWxbw#)5wiku5u"5@_?^VnsR,曇sFM$v${*ss.F} nc pߒ#M?ǢA`?|&C˯Vq}~ ZIQ s2n#qR\3@oZP&^Ɋ (zܠ ,i j3sYI nqxeFނ9ǩ~폭+~ϗixT}OdK.3DzB3ni3u!F׎v2Y~2 bM$̺33S*ʂTIDЦ5Q:v|ۙծvǏЦ,BncBop몪Uh7dfqh7Uz4Nj6#x_b\ng: %5AGPVV8N08: tƢܱ*#*&H~ W(#]37'"k,hÕ;XXG-TI+hG\Ҁ]X\I7?QN.km9?/? GTS|qD3pPu ^vQ.VԎVϏ*J@>d"8SRye>o#7n[W¯]^*)s MݺlE;R=Uk"¸U{UeLkZQCsm]@Lj ѷTSVM[Â%_͕?Q@8]f8 ι| #R⸑^ڕ\5*~)d{4m8]S=s/SeUCF0 #*$uS>]qk,՛:'$;rS!m 7tqJ@3d`lM:3`w\ U͋ >}L}{yW 8;/75hh8^祽VU2H \P"sȩv~^il$$`|;< '5),V=K|,n >4i|K,6ӲD.B9;Uu?v(N5W?ga~e |#9=Y}^|;bPgSϞ?aQzqz^߯Z(g*8owǸL~LJɷhy?ҶX)46g0Em&,uʣkQ"Xa6 M9EJ u ]b4Cr^%UjG:Rݡ)l[pCO3 `b}#h.j'5j p`ccoribt] -j6(y8v"@ZoE%|~4] NOI_`f#F/QUoQ49VANAkQ9C4 rG, 2NO^{$m1b(qk0ŦXT.=,.(N0$ٵq:Ѹߺ}x2`\CW *p w|SG0\p 䓮պ\*-CE/+%f(CYO|A-u( ӵ)\flU^߹^Y}u]_N>+ ;~v@7H~7A2O9MG:lQO{*|W]!+3Ofe2\Ƴc9asO 7x+1 rmٖ;Ӊ\Ljulz;tÍA P2׮%N{} e0BMTFgxɔzܐS6ߞ2 nhM8e]ep<ӏkN+''e*^2_3Ae> # >f]d : ]T"+T_ӚVjp !$'<*+7aso<r!3zT'DR~[&#}SͤdxNF$-+@שkhWyubmo s`gB8އ==lhy^.Smҽ^ "¾? rk$.yyhvS3qz@j n%~H2+lp( Jׅωnـ4!l/'J%0f7JʾP>y"9DfP{<} 4q>D;=cisOʹ&~7 xahm>*]3]cSc+?'IOdiT$}gFrއW`㕹}I<2ۦqgPnS "aF&2_h=Ҫg7Ig'-=x^?=۸2?#'JK3põǃX;ъβ K;\.* ~l6d.ьwrj]l36[c7™a=?3%C_-70:9 o侫P%dIB$A0{ a2J]fdq~R|ʪvcA5s5cJrTJ<1薏 ϳDPK̩!'iQN=uj]~ ? MG5CѲʿ1N%r@.7irW{n̷^_pTK#MD?u_3-n c#$=FT!?ExJ^drC@X E`տ>*V/qwFڊu$HGa9GBWi~e$WS6A0߷\W" S?>,ggE"Rj.A֨;W.|}fp7PTe3#G58 kM̶v;'A<™p 'ti[ɫ =Zn.ڳVs8IFjZ9tWCjn:V#q " aY283^.T}l IzA6[~6T6 ȧ?#,pVN:~רeUE хE/dz:KY=.)W aNi ƗZiQ[THwqÜ קp9qB3ӣtwaȷ}W2;|Hvħ$b/_X2kYb]Tߍy~>h璙9lN=7CR]o 8Sljiey>Y\k֞T{Y}D6I\44pg`ħ:?*ќ}y^S?¥)wf|mܫ&ja o{n+HI1h{ G @4~tȞZ@~b|xKXQ$k K8./kmh)Nlk{"o5q>O!AHS\|#MV+M>jl7 KUT |)B7ې/ȩ?5dg!v{_5_I3Y0!ܒ Si{ W:e$ nfUFETGe tWk.﮵.EhɀM$[Zd{a5%z. C5)b{ [hC$ #{CzG5 b'BrE$GG%R;3H+,L>[Bo2gTc[co*6(JkE/vwl6mW zB=H}Gߴ«b`"ü8\7>5]t(!^puXc[QR.xQx%kOOyHp2B\\sFs,ں/ny׎/JYd0d +S翷:ð•sg궧ȟY5XPY iʅNj7:}_pM;O]=o.*ֈvft~p}HMA嫃?T+8FfD +h7x.ffd"#Z64Ƶ)gPԦ0f܂Uk]puG>6`7 ^$~sQeO,uj N/=ӨᥱҶExPzxhψ?$=/Gv6uh9tb 3@/S ´u:2VDӐ zjCAF*m;aΡ"S0#1@/ME[tiqe9fgDE,cUɀ2R.tP^}4u$B?h|ag#@f[O?sF|u;mb> % QS[b_ΫUݬina_92N~A;2<˓?͉ یsq>5o l0ԫhDE>@<}^N%DiP`]?WLPn@}= Cdr/ȺS!+-&xu/FJtC|c]6p>)C5 <>ݓ[ԡA'NOg*6J9-`ǪL_m@ij2KlL!<|UMt E'g21O6U%]Bi߾]['hhq s[FCKxiWJ6t_ω/*ˎ`XA 9Z<~9|Q\MbNehAj̏Fn#/Py,1 DAe<=P~f&Ҫdї(cgVUK&^}nqc< S8bo٥?=vSro~mSвpf5[P$]jYU{0\5m=\vT;g qQՍtV\U6uR$%\ (lXq^hel)dufejp>Y s"H z7)3|MNp}'=sgǾ 'Y>HyL XNUltx}z`^Q{w[:ݨٶjfeyϡd>l;2衒os1g7-'(8`[@yە"v-QA0w5$4 %)(jZ⬮=yk¶" ! '8QNh'QŰ\B_8+QZ72WG<Jq7FE٢RwKn|f|3[`!cСqB7_1Tf1C>ȮnU !o$;Õțѧ8>yÚm/fADS>{NE6\)d<drM/= Ǿ~X*[idl?g3@Ĝ,ΊGtk j3;=IOdcϕ;?S Ɖy] 19YTWݦr''I|Y߳NF>-1_" ^у.9k݆q뒁Đʱ!L) ![陻 +h.ôW[&ӧ i5: lnѶXrJuvn?s%+) #8paI#<DrOǼ5¢+ k/yZa\Gpi}>8QMW 8͛һZ.aK[iAHǘ@`Ugu 1ƷBzX=#XFy45ފ+1l:kdU{kO)щ:mȟK\M[=gTT솈3)Q&FH;RrMU*pI'I@,odB(NY}>a!l)eo?Z(<(׈v1T»ETAm+7/E>;AX9MdI4`u/Yn?գ3WDk|yAg#?Bj8k]tWg?4*=u|(x)s@ |x|F: }OAn168e)X ʧvQ@rN B^QKdJ?MuXYeQQ2|pd x)nCUSD?EP&{q&“ƅPWl]}hl 8ק͡;ìqYus=0Xўg`;~Y%;ObaUęB{= B/J,ֹlZsY!j=x)I"i̳l PzwҳxGJxr#[Q&m(<\Y^! g~5edRl+bY["gF]Tm 9S>19-kn݇e30Tb h[L)C)|y"w?KvAe^y?0XYk?ʹ!c^c8w$(}#ۀ1lc гpFqľ'Ev\?z>zۼu377`E}k1ɯ+ݸ0-rvۜ< DvSQ6!6Z#{Us};Gt"y=+^KK2wv5ԴԾLgRR2[GH1~&'eϠpBj'(2pt.) e/KYc"#x+&ˍ:9.l}jW-Ej=|jEw.L,?B+cܬpuՁ{ܴȣ2m8l 4'Ub9tf#Oc3O/&6};gv㣴ӵr551ۆXdNP=AL#x ؄ ׉.%$ J9VX߉vձѨMi:JpKD;\1YlyPg[?+jƚ@np(?ӎ3l{aݝb3Ql]]F^l!FȑW)aQ%ݨo6\ TSU8HHAj A 1^advFGy@Nz)7ަcU`@T :Kqxtכ>24kboֳաcڐ6sp|\sy 5mid܀bcvV:g+ 7U9 F5rEL [c<}T=`o1-v4ޠg@uXS/7! |DlH% l7Gң)3JU\Z~3wCTobnL9b90hˀB@NY]\F%X3TJ2nh-:{!4Ktߏi=3lQVK d_Ad!siK j匊O.MS5%gI˯ޕĖVP0X5laQsL2B% dg-o.e/0j\u+f .[VfyF)EDžfnUN;?nLI  ,&14 *,[1[LP)*f2%Kdx+ٴP7P#fTIU2#ć-zt+ix>r^8/FQu˞glD.oNf% `}ePEB` nX=iHMGQ}^qZUa%&8Rq2n&|ݚT4N8/i{*FplvU*^״t\Zǃ#` VMO8=KC1gme tY᷻ 6)8[_$i1rRNe+5PeE(zhlTmj%Fe/B$ǰ*:\ڪ.VTG7ӱGSq}˽zXpN'TcE]"Ũ usTb;OPyz"Qw$#-JpNOl\H!rWxZ;/i]᪙҂2JbƵ!Cېʹhk$TRg$KXx9Zu4YG6>+Ҍ׆n-[#f̖ζsv-2g\bMYO.W+^ (L1<۞PgjP}Vǭs DHcWa{2mX 0r[~ %phDSPeg,CO09c#1xW-nkYi-Rwu< :q )R0N~K-[J.z"%UP[^/\ԍ xhq1s#zr⚮]v/@k 'r0D&NxH0_eĒ8 [ ZK i0)dtAҍO[򥂜zō$yw Mi|UEBI@bLpi[#A( 4Cڥ2\/X eooy_ҕ6!y rNOti;VM!8Cx_y]DOQ/<\@xWlӕeFJ?FE#e ,+9SA 긭5͘j(f}y;*4!i:!*Q|EBFHc杸ni(a:qPKT3H[=r% -O{t|mbiJ&;VG@^-^2.2nUP09L %e=f1dr[`=V3@ȜS.WzST;DuIt~*l^$d)pyOQ hйX9nnJ?Q6;K x~lO1*zfS_*xmaă 6gyA3Vg F+G%l?@DUXē!#W/c) 7 +ɢs$,B}?㨦̥n  +"_ D*#0M>D[ ?R9}{u Mᎊy1/ t"Ǡ׍I:>DZi5481g$G2U@#lD]ӘH\OuvB(ryދҞpǞvCϼ8HxAlCz+(!yR(W/'prTAKklfs(TJBZQ8 ^4V;)ŤH+L9[=M'F.$z{FȀň.$DLC3@R\ͧ, 8 >H@250{v ByЇs 7ݚX^ cf2p5A5QGl䶜J\Dhl0\id lÎ6g *oUv̯wb 6B!GUmb{$_4h_Y s2)CI$ROmӱ))]YAүߘgk_Q\"ڮ[fžTDYƖwq)EgEK8Xo8ţ*.yHidkC²/E*dz{ T*d(8,PĽ!=b8.`;;|vD3K5cԑaBy''|OuoO}޿7O2IEoYcLǕq8 |EYK:e<6M,rxB3 =l0ۺq NwHEЊsVJ:z΄ݭMݔ'C޻I tYFYVqXd!>#Cj<ο'Bc-<>;/l ćRNϖ89D M"NCxAlJ?$k:kjHmobew9W\*JYt}BO%3L@d޺wyrzփXYL ߉_=b&8>/Tx PJ;}C$!PY(ʠ7~oxdbMyiD2AgN{9#:vJY kغyaTC/'Zt.y dv~`cV_{ ˌ*7oXtR!,@U ?Th*2:J RF".IC~E^%,ՉnzS4أO~nӟ14QDmmyH SʣeގdAҮW$5f j & OlHy{S2p+)ReuM}Q^#71Wj\s(*Xg7OҶ?G-U*:\ZoF]7AG??FdM2}.+<O:CNR>qW8ZРA4=mo Xe(v| OSsGRkEV*6yH3q 1ǫdįykXZgjU.=kў=x%<8?]Wqj<yjXGe4F28*)x ߸2+~`nQMwULj8^?9JEEQTI@OiqG ydClT8k.)q|5qsIAG oTE"iUէj[OH& MzLZ_$Pˁ.hU( ETkX=yX[#C˯釯kLAV/W7?wxNi|F"?Λ#2зK,u>c" FB n`%܍>J\:>(߭;[TZӬ3ċ|m/aA\g#Iq;R3V#DcJF \-%O**ɋfg;.۪agF X#0(Bh2> x"w v͞u/\5p$yQlѕ&UQVlX__|_uݪWs"1=沩4J/_ܪo[wvY E1YgbpBz(.,9xZ c耞]@KɆP;j"|V >tyCERj*cƟAxuM:pz%I- [h2S^1E{]&&B`]́|).[uE-1E2逶xL ]hxև 9`W!1URD_E|)}؟xgr#"f(z~qq5<~-#|GD^nA4^Q}_XRi.Wk5gM4 ;by:x%LtU9->Zn؏I:7k}5l$؜jmTHFUGbְ~V5;KP"WūF<]R!,]EIxH @AW-bټOU52&sX%0F҅ȫր5UÕ*_4߉RI:#BՏ>#ͦ YreiJLDIa{ 8۠S(e`oݏE:P؁WUHAmw%`4i_X1P20}c2@gbp s'`WE&^/#E.*Q3?"-@kwn+k먢a'E%M`tn =GRwC3*7W.Fz'(U2wUO3W] ; +} vVl'5nfx EQ)d{1"\/^k6;Ɲ~ hF[`5Re\+WBpauZ+2_KU.fRZx o NBō2p6 ٠:(Fk:,ف>f DV/&GkCU<S@=q^tRe7O6a#MZV߬COOARtww|B'pF>I8㋿UӈTo>̀[R#$R@X8W0li.,OHHuޢJ)4W SFBXxSE &@(nzxz~*l.`.nMرo@z>(.XQ lQx,zW"Rj>r`ڀ*=Ҵv'q"cerSJu*VUXU[tv}nO .)TsJ)a{͟mpDnc^I0 {( PХIYƪNV>:Q(HLPc= l9o~ܬexeIndSe(q(KU/cyTр_8K,:1e->~ |1X5)#1aZgv4K5I"!x^㢾a=5qȺ-jl=EZte*eBfj+cW+sAbLHF#7X/]jϒzNFoVHA `FU:?Sq]hiZ"+mN6b7K":x'Ff/*ycSJƱWӇq_JBG[E8ё9 zedcT>FoWv8bٛf*ARwvkf`S\*hpCX)[9XAW$2~G~Ax6h?z$G<!dԾv}fZ7|_]PnE5~"M>CS:j_֛ى~5y(P.JS{L'qS4ׅ;j ;G`Դu:»Vύ|K3h\(FQCu_O? 'O9wj! C5=SBUu;eO5%(l˳\WYTJ2"Mvȟj X vg]ɋ:;Y87=S!=@ND aǥ,(gM&xPFY R]1tUq_G}Z+{M B[=5}rñL}yOҸ$7i *XJYTǘ(ڪkZ2籠R*QQYuu>a&ڸ1FTl7RC+ &B؜ p c8?Ug41˽_[u.#Fk4uý0o.*q?5 { 7L< |?b<2sf=*$/K k~wp|*rgdlm,D͒mʺ[;eJ8e+w[91,TCp U{6eVlfq4>V\m]ڙ1Ou?-/g\ p -F| @l#"[%.h2\TՌ*lKu *#̿{?B2Pmg1m[b3Cƌ RDh=e{K+OÝ|7`9𜰞l4̝ "$Cx j̠vdyS/vdU*TW\ vr#挧cAqNRt@ aƿ=X>|!]{^IUR5f!۬mc b@BsR Uҁ:Tv|r M{V8?x5 w1[ݹ2==y5+c#Ì}fh)&Y0rwAZƝcrE=oPV\Pii<2¾Gt\o#XĴ5`XWơB^|-Y_o(yu*;zC: _̬4aqExhI>[qnMrأdTcOB.d`,bL/ŵKxX>e>;n+㟦"[#: Ex56i W gǩ(u{S@Ong}G6U"}k3k,;s0v :~N(!J4΄ElRDpk %"vCar&w3~bIjhC={H"m "!eKZF oae^~vJsEAAAP>"S@x> rUbP5G^hHn(~db*~I"AnOk#qc1{ͬ>x?]C=RR1L!dd$t>fBe= x)Sq#Ѳ5EsQ%If٭1="jm</6[IM_V$% =7;?Ջ`𶸹yc/t/44[V*<+";;;a  ˜N}_SEwwa\~L&?)\|yZzwbSS$ѣ賝痗CfyX,~Al k‚~" L(Z^#c^YaƦIF,KixHZcQ>`?"^jFRh* ]B!ȍBEho{g"7 DQȩ},^{"Q,ç]Y Kn{@ Kuukduy5Nu RQ.ͺ2;c]ڞPR}Ndbx:ϗ-/˶me]WvV/y虑B&vu8cSrgJkSJ`qo4PWrJ_ ЄW!^)J+ #TO'1&@&ms\J@S 4@SF 434UT0{Л*ucz,PC;&,,$Zٙef*~;9PSm}㑨~M,[* OOOƷfsiZivөCp($KnUDja"Kh.{C1ܭ-XHjј)[h^SA.% BE~͹u{TʜY<[o(;Mps=Js@A87D7B0{^,q<5BgP2 96͘Ì"LEdޕs_^f`P;6 BP[WcX6wCe&r>s)!IENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/fullscreen.png0000644000175000017460000000612000000000000017077 0ustar00simonwheel00000000000000PNG  IHDR``mo IDATx]w>O̰T8ff"g8{p ]*I֠6d'r,~}/H Y~NϿ=. <&_ T>/R_b\\O㘈F"CF^EG`_W&rf~euO(c|(]Ykn׿zXSMlw]Y+DDG"EqdY;'h-/]\p92>]9n8ZJ\?~ ݝ:stv[Qd7F.A!+O,o.OMn.̹ 2EsD`Wz#B~{AC>+Kcd`Nw*_Uܕݢz={z*58@ j4C5$;y:Љn2 ZH@4ѥ6 X?r4lj= 0 #JJȀwbb-ԜU6!n=-xNZ` =< 1Fz.!8 >Fj4T}Us-P]:l1dͷsrz xU`,aBKI0Kg_Z"knX J=GKJ3gm$ޑDq\#s80Sҧ!Dk<;?َ x,"?\yÐ> HV 01+` ,*Y9i_`:(sJRINW~s`L,}p{{iii|| NQv{{c4(U"|;eQQԋ*(SW(BLk#㭭iqov&+9OD=~W53Y~CN'sHl{XE:νOډFrVDPz_I|6 3OyТOY'[{xeI44#nw|}Z W0荱:Ip"Q&@q?I$;V7 ^qJQ"GV YՊ,.ۣ(l d|,Y {յc:`KaibqV, dfRah%ZA+htxm,҃ f­iS) Ns WT4Sbz~2Zr[JzI5>7Du)% aΙ,`A'v#9@EB\HZ9nYK\Z% Eʟ)rFPc3o]]T]9y)F` +Oh\[{0@uohAL2 ɼ,p1 Y9Z'-w5:r_G,Hif=?_%@e07ۓ] d̋SWz֓VD5շ䘊sBa?:ႾO% [2'M# 5>WA\1ͨU7)MYx*ŭi oa,llz@+t)Sx-FV^ŠLFJ Ck'dCx ɑA|BX`hp B$xȂZ{XtQApq'I|A9?S?}߰~;@|!~V+g8ryybu>7;{|t"Aq/-e8=9dn >OOK# y,;gE6d ~w/TIT?Ń NtQ>@Y(EPD1ڒZV>93eޓ}n= `QvɄ딆jdH'@!ύkXзX7-([-H~4=H~h= 'd]=۩ @+Ec>{cP RZ8pW^,p}< _S8M4qEaCHև*Ừq6"N"OQ4Oϲ|koN!KȒfH/ I%YȄX\8;9ޘtIbag׿U:9t`H sUm`#ʨd]*b.4Fsrpɣv.n6(Cץ rF`IlH3y*ݻٙ?;+ /=B%@joUwQl7D䒤19ʋltv:mf}f+7>I qƜ9:??u܆\IENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/home.png0000644000175000017460000001021200000000000015662 0ustar00simonwheel00000000000000PNG  IHDR``moQIDATxSB@tm~n:@7im7c= OLN<+O%Balmƀ,u5{,AUĒ^SpxnV %F>^_h€iC@y"#"OP;4@Eɨik/_d#(x4RAߢObm_/+RƣQ]B{w/RH6hm*.8kDeZ,v? Pj~vv>_t:F'9v~vѡUyZFŵ٬j"$4o_Sg/p(..ٟٗ?}HHǢQY˗',$?<9(!RZ-߼ZVZ 2$>%ӖK5niTV*۫nwh y&S򞈲\))޽4LH54 Vmps6&D.1hiك9M,UU,ʲ\8x-4j<Ć:4FU\ݍ$q}P'CnV'S>Ҷ=C+y/f"DOذ&8!E#m4I}Irg4nDIw ra߈w59!A'q3qɋ*gIvmp=B I;i4!AN7w EZ ѐ&GD"4B!qd(lRQc"G#I6i,i%BT%|nf ":9u2Z+XJ, P̗7j!E'OGKx"7 ( !PȢ"&]j(-M44`HjPVeUU޹".Rkݹ>(4Rc}>}{5DKj\+<(KAxYgV RKk緧?O[h{6x}< Izè?~=MUoZc>8?zX>-Mڞ?U #/WQY <؜ jϳ}OuC˸ڂiU/VU5W=_ȓc HѺDoCuht^tq3&k+ƁY4fZh}džV5&vmHkS -C飇mH\T'F̓rօ(﫢' jWH-d>ACv^Z]Hp4v6*TiO%DTaBhy,yXZ~XL㬞ZЈdV8ޢ hbZHN6 Fۇf˓7)NblKR,FۭiKB(ӺB--6z:EUK $hki  @>XX7 mCx3ѰBt޾zu^DQvp!SÇӨ{ BiB3mH MUGnmYƖN:YS^HS˂ u/5XUi2Cmni#Jiôܤ% U> )'VԺa"˵hzRLioIۘ}XlkmjbEg ZЩއ`H Oy ^kpʨ=œGtQ$4>˭hcP'/b,o3LpdPǓ쬾 "˕mJS4Ǵ^. vV!}Љԍ(6iDd*S=Omi#B6nh 2nKcmFX:A7Ɍ1A-L(eYGg'th{< ؕioGcmnHWׅ7yGٓ0l( JHewXv>wWQ$v$Uǣ1X4l4l/!mcz2@񳅻8߉#zW o@8*@ %6b‘";l(o4؛|)mя]@uM:ۻPycw 7xk^se.mѮs?ѐeSsyj-}j×B-LޙіAE#M<^gfHa%4tDd\ 8 l.% l"T2#i:4Bwh@JŶL6/4n:2kpB*hMG)rKԯUZ<MGZV&ڈ+uF Rgy\"Av/ӟcV6ܥ 8%L"i[}ZRmEZ @ }i ]=ƽ|u:e?Kh }uчuKBBZGLKԖvA{-Vs>in_Kŗ.v+KG# u} y* o %'J#ݻ/s*^eҐY8s!a;Rmڍ͕5Y/^hř~Z ;DnցSJ{tԤH(E 1 D6wI64RĬ}#!js U%B}w\4qUdr9f3PZJS(B07HCQD!4O\ӺЦ1jDm1EYWeU紘v!N)ĵ{?I [e;b8!;l HsCdN<7oWk݇ kq%k4tF ۖdTO'4`/է担P/8TA yf("}}w."sûo^u$HPH 9[M'~׿qe++eJo?_{2X跰QDBܐsJkLgucmM(ߗ_ɩIENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/icon_editor.png0000644000175000017460000000134100000000000017233 0ustar00simonwheel00000000000000PNG  IHDR}\IDATxbPfr01`ժUB&L 'h)Iv k+\۶mͱm۶m3wo*muS]tSakds0'~ 4VW޽DIKm{sN@gM Eێ+5_/OOC窙ʕ "'II4 ..NBII V.pEE4,XЎ;`eU### /@&\9Q#'/+$junjCyti[) 066S=L$sI >>Fwe0dH5*+`*$!48jСVug Bzz:Ν;ClQL'CeH T*Mr}p`(Ν;X)G7" y?ԩ%ptt)޾}&P1R/.Fi$'' DٷKW_%c}Qps᫓AXXi",UUUψT݀pKHINDQuu ݧ~׿ϵmKl#x nvww%3>[1IENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/icon_flower.png0000644000175000017460000000221000000000000017237 0ustar00simonwheel00000000000000PNG  IHDRw=OIDATx͕c[erc[UP7l3<׶ڶm۶lεfu5563sea=VoQەup'. MOxnNĬS , 퀛$:ֆ'aYkpu-,]Y FV}sf`;45x1 ;7~Yf}ꫠi(@!rCnhaYs}ӳn~@ǓW;@0'V Xen - 5b͆&16<|~(QTR҂+|J'2^ ;#`K ,t4gFƒ{UtLy9ãoһب]^% Z큍a?,1Vqqs-PAAUd*$5u޹QחTbf%$"\tv`G,qxʇG@mUv`h蔊-*!ar y ]jk[ afTwt ,!Zղod^lSii;E7^"#7i2@F6T=ni`&70"b܂vh5l8qwzN!R!~=AArhqӳ$)i߱X]qk}*Zܭ4YuɵkU  RdôhV5=>@Էy5<|ru%կas,{u_@}l\/\KF9n!;}㓿Gю aw-$(MBMl+'A6_SC#nTj6~bx 6$hkjO)ߔGLTN5BhGhJP-dh- \dK"9yii n m a`( vրeh(]L̔/U]AΒA2} mFh/2ӓmOb-dHC#aIMX+Auk?A󿩞Q=>IENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/icon_ivi_clickdot.png0000644000175000017460000007435300000000000020425 0ustar00simonwheel00000000000000PNG  IHDR?1xIDATxǁA DZ #Bn<> I""^^lrqwo@ tBHPMUU~ `a>%1DQy-8aNTe6Mm[Ƕ4I .s~~'g"`Dt9O*h<5@ĀZ^^ba>kmmE86Q1W8AX AXTMOOdҎ#XX qÃp])T/J rsH: 2f"iB{,x@!W`"8\4V :xV'=kM=F$L@!9CthnnF?<Ԩ+h OUȅLyD"}}f!Jcc#ԊТUL F $4gZ%@F J>k8?ґ^ %a&KJA? K>ǯK UM)RUIHH =l noJ w ?03>Fn۶=y2qo.6ʻ3:= jv]WQ k}G0 躮:hTUbEK9:b& PLϴxw<4S eY(Qlۆ?ȲyPnH-ߴ,eYz'WP4ޏvG~b |5SK@͞2:ퟥu[(tQ MWa$Ig@䓳~jT9 fk~/hL(39q(:\4*O$\{wDB қ=D(RJplO.kp]NY̬MDFjOgF@\{"+>MG?2||}}W;> oGq}._SvvvH5*1";AWV766/(QP5J9 ס51hg2S r(in}I,0DɆE٭-៻#?S@dP1XA='+98fEN43D_^^jlR1Ԭss)aKOvk /~H|'05xf m0Nwb)pkZ甍IFaLNmu po}it@K!aEowl_I^@Ջts x=_mFZ 6YqB+#<㋦U^b-i-89?R_)uj`HQJl>V,%h (s;+שʤ(0l|Y[f{~:#fq}7a.A-ž@O.AbWWc$ ,n".̛n`~/;by}}:+S%&5l ;ur *"< -#bUia#kK, T]>>?~8zYfNH6(J, XyjS?XT/Eі9V,k{q ٕDO J.4e!-䡋mV9JVeQ. ;jDo=b*==c1ڃas\K6tᨗGdMЅfi@I6VaaQkU4o@5 Jgf\jnYqٝ^  &[]ꆽ=Ԩ?l:wtUJn.FHSsgGZXr5f%!jYYs B Ṿ~zzRT0:xGH 㯥?Z +Ymmm(Lzڤ{qv[P369! smVç`RJQu؄4#\mw T{quUf 8()r}E34P$0 ʀ/@(U@=o1 5r}5;ⅻ]񊹾zQW 3s9z":jSمעVK5,5ꍨl;JRhdSL}~UrkF,j~s&Tzv K)4${cL$Kj 4xp7*N 8+$@\G(S[, IeH͜;*oŹ>$T5P\ QНh5LqT9f':r M?hdXM>'r?6&ympF9( =::j%/y;; 6=UWlԀ0mלΧ嵄 ;f$y`oɼ/..T`>@;SnTiq,N{zzJF&H+hgZKj&!hE1'U@AhuW+"~,U[}B{܏gQ8:5M#ţ.A#76p]E[wVI0` hB(YA&~H5*@G<JH;x%AqATPTOCI&$ t꯿-0BOP~ f1Uj]䘌- F)?sVʄ!AV)Dm]2`G d!jU؊WR?חOshVɰH?l\$G1Jb ٭ëwi$mʾ/e [:BVK|Aۈy^E_pH=Oq2@7$뭑,HhbbTG"vy)+x/ ((pB5M{Ӌ$vm Nw@EJ] 9c6Hqz "t*|d`<2iS&VΣBu3o$Ӕ_(6yap,߆</Fng?5ao@|\Vu Q2(O!8LѦ/!߿"1-Bb&`*|dUhnsc8C+,dN xqLLYJ& vWSvs 8I`HEg=~{ٯXޮőw{#W j/U0G.$z%6G򮮮b0 cs?[\ E(s{젟!@p8"&ejNvCy2f-#ɮdZm7N> @#t'>ӝL:VVmw/8j8A_!6q'7$l~?̪}N_'1?džv5TY!50ܑ|֛lOBX JKs"heFEgÃS$Q?vS66$xj?VJ *Jonn(!ۓh~\B}7{: ,^h l>?#P bu@T4i >7doXĘ=e}(r]5@pPgK!7woL,O/VtGBhs=?4pN=vl1)RN[} sՓȚ{@_/^a Z)Xa[9;o937rh`dP"굼8 RS`g!zͪ#""^k@ettÀK d#|TE&fx:p3~x̖:9yDmLz:dcRa4c!k8z1Թj.#Oe1.AF@(U˟=/d | RgQT-)5)t6>bh2 HA?3Nt[c`*娥>`kM0S pkSᲄP ޴s)qG 0dRF3"`EG%y%v&B6cBvt4P[If#=o^ dye'#޽t_Bg 8~sBՃq<_ Χmg|_]`EfRṺ#+ZCK5AiþĽonjY+D}^ @) V >"ƌS#],}mtFfHK4VG鍫` >5צ~Ӎ ʩ>g/^ &j=Hz;߉=&Cꃲ^yށX`NX0;HVp̈" 3~r*{/:8FQ])&ꄐ(l*Y4mQ[S |Ws'__<-5U ȲwL)xWp0 ͼ>*ً㼛z&u>|h(jtx2is4Yh NhON|ޓg$ꬶo L؃X7 o=z"_(fuR.%>;P2g:D/lG=g_^"4"ҡ%)97 HDs*ނh\;p{ʷv3%h?B3+$ .—N}Lμ'~mh^Jj)!e6ha?7 7V( 6{$hL1Oa$ք=&O5bGTޚ,Rj@JE\fbG걩EI:C)5Ծr0~,R˻;,wrI4 ,zc Ҟt.oGnӂϏLiD.˘q5P6ÿ%Yضo6Ƕmfm ֶ'{w雼T3mխ[97@SB^y7{ 駟~BH4qa]3pc(<N-UWDÅ VNvhw#vY?mDIIq:ۺ_~yE"C66?:jgxA-Tz ZM/?D++[kNqӦ[ܱB(Be, 1$7 @I 21(JKj?ϰ %믿644PS;kkۯ^a!Ç ܸqy55fnkW?\^}"Mpm0buLІNDDj8"׏?.N?#?VSj꼪Doܘ|**OˮV"?Iu,JRdw  `<V ڡU)4TX@ Yt>*}Fhl]7l7ztyJŘ1;^y ]ǎQQ P4o9H8V"fN!bŪ[7#u4F)LƕTUU-F F 5kQ0eJ;i:v'>i"g ǫ]$ 5x㍈*kfb%"П zċ6 "-$+OlGjpNw_~ 7="~W3Yi\(~x4_]2wr =@Je a" eo@ܿ2pʔGY>Kf?[~}zw_AȀ"M. @)89 u} bߗ+7AY`sbI.Χ ]Ր)Sz:jU5,yG|UqibFS V{̮0O<;hlcL@[ Io 3%#x7&j").$1. =ɗW]u? ,I zUHF=@O+c%yb$ hzl 'Nmc|ҠOakJX%!gOu„%RRW?ᅬ!ScvZ"?05\Dyq4қ/r, d\ N I8@eso~K_C?4{~F}ԩɫ ^yk h+zuϺ:->ɓe9a@8>5O/.E1 n 4o}Q6.ߊ߆ܠaDʺ'[Dw*pCuFdI9sy4??Ѳ1= .:U-ǟړҥqS[u?A(՛\DړϗqkE[%Þ|?;Fdw(~~EA$ذ"{}wW4th[$ͻ佅wzѽѐTwJ"Nl,g+.{6:EVzeuUӁ}`$Us53OfFMDZ54|NCxV%.?VAL죔Q9L;3.3Ǖ| OPCABb" Jk B?EXߒ"x=VQ冻L"=.N),l?,vtuM蘖?\V؊ØT<ȣ$t ,,=æM5ѣ5`؂mxBvE.{|ձsPG !Bvڥ'SS"=N1uEEH?{1DvPo2j0?)pU۲{z~gS*u |ԩ?omѢ_ܼf A-C䋯w}cNSpL%4a+źs&7 a|*EFZל706H 5UPRy9C .[&RRV^n3dl,FUC(ˍKu ]Gal|B~ /i,"|P{X sHՊEH?w|^~uw le{< zp@~"e*/ E y5>3x'9 ce /0`f::dqq} 9|5M~XW4We騫+,,it%WY~ p]HQ//0dQP 'h@Di$#@5m-00%=!y`<]M;U(OB` ǹ+A dl\̐ }f͋JJ)+'~NZz:0³z[Y(m+C*\݉<}&\EEE҂1ڻ5atҥ~ &*03xQYO&8xVT!!ovl5T*F}^z5yJܨmfw{૑# $H1:mV\ٱo({)r_~ÆY R3gΚUF쫇VTYY9&5j>dg&r<\7n{6X1eɯoC> BXrr kpqVo97e @P[WzX.^}e:fKؚjl@bYAz ԟ'\OQq zn EdFV@N^ބxSĻԉl@ͬYW^=~?PdjSDMMCni٤M/ed Pd%T._ي1yІ~iw}we:zŖ>xwvs[ZcdgAz%PgS}$ ޢ]m.-+ӟIʤK^)z"hdB+$ ߻uP';7׌jȤ>޽J>}^NHJ޷?inia`2֭oe^VZ"Μ= Ŕ dݩx +7:'W>Cw"gV@3hRrm8ʤ/^ ךּ:IlK΃ts=1Hv{P 5= BU3M0 WR3 /($ы jK.c$w.G /gǗC :]n#X\oQU_ot8**HP#>+onztȑd?B[}$wWcY9ZG*pE+i@< X^ȨeWP`k646dha b*f[hї_}})kZp@4rr`ƞ={fmVc7uhH,A |֓rtk2/ۡ~5IHImikO?o{?>]t Zh`LWw80ؑ_Xdtťe 8s?$b#e]7hX[# ?#>XhzBzVCV^c.dvL~pET,^DZ㍂أY+̡jPU;kk_O7ꫯ8-|2baW']/Ülܴ GP=AN0FB%nCCcfItc_c3MER*kj)_?Kybb9 xm Txܼ|I> ,}(eo9C As-%0V E&FෂIH^ҽ陙yN pf75A6ے/Hʪj8J;;:;!gRR"OHy&(Hv !l9 $[\Z^N YOl8[P=Uni DYrWP H"DTlYx[09/G x9p*L-[Ee$Nk`P %<v .ZȑfBkj<{Nñ'8nbJCC">TJ tɉkDAy-yMjKKGlʏ-"{t!`,[\fuI4ڻM5AHe=*IZug-$EWtUM kE$:&ҫA(3T2Ń/(.Wwuݼ&"*.N`u x|K퓉:t _w3v=|6 &f$Xڑ!(5VZ !%ޓ!QZ^asH00 J+EV?NIo\r%E6Ye Bۻo?>%e|QowQ('G~0m { K$Z"qF@aXF۸16)#:w/\;nٔksEr`5P[3{CȨbX&}YEKB:#~׈$9| ^{_\VQ/\x}-6'yze4f1,Ј0ٳqOkkqYAXj*? 745D0tecr?o[G H3&C']oQy^'OZuc8hIWx~-[Ӳş+@=>C\ }ͭmy7(\gX,W? Rc Z+C@¢\T>\hSm;j8';?c/[sJN{( }>|m޸1^._US_?TtUpşt^o\U״iyk E:e;!'!q) _ KJwNmjG[6,]. v?FA 2OjaĔSj &"}$din ~"x>s[ۈPZ$Uʭ-m;v?n\JuWmpP>[|"sTYCa7lLHK`ac8;a.1%3kC{Wf9Fj3OBa [9-|P3V )F HgZDaO[ @PA~Ƚh pQu)] 76?NAb)o~SRQ fcT 314R9>*R.Q)1{c$+`nkI0O9SRY okQ7n{s_wOAKmc1̕D({\ܺs; Gz^F Re-}8bMX"Tk!|dS3%[<F!qwtSOLIhewb$oݒӟK lńjlm#ÅP;9j͍nSrVΓgvٛL|2 5O vl١*@p*ƐuY_{?bln DGdNƗ9Dy늓j[-=?0@2RU Z4gL7l0 4H]uJ艋cҿy˖0us)3/|ɧt̏NNGn;]7~s%yNjF] 3+PvM@NF 35 efԢ|twm>s\jv<(|9#jQ1Rpˠ}9^Gsjcc}Ziq ~GzS-2i3 lih3 _\l(EC, PZ3İ0?鮏}_mdAv(ީCȴmOX 2>q٪J;<,x͡cJP;KϜ;ƍ@?M_ѡEmgdCjX{P{KČiKaHA8|CĜZlrܶ#ǎAнl*|"\ 9H~6b~Ѐ `b^nL 2 t Zy4s[SݾMwU7Z9jr?}mc$Sܒv{ZF Q0ibi~ @;*JK1% !)@i)q&.&WV#چƼ2򰒫C9 DةKw [ q®,#Q!EiW%׭/ÑggnFt*!#kX)BBwޣ~])Ji\H"ϡpqy޼a\`0OK_ZJpuAӵH@l ejFJOcGv =Mx=[2 a)iA{P*nS gX;@zBiXu7n9q¥Kϖ~ תZXQuQh mO<8 ߺs[oNH N iǎNPHoUǏ/Y*13{)0`$iu.0OI3Sm7Oljmǿ%A>cT̪#.7G.gy4╫)~O ^F\;قXpx뭭۷ k1I=tsX#anOZ^>%FTx-cf4@RRqԢ H6qvbV 'ݲ˘tOyu,X;۴Lru8R)XiF#Bbrx|A…L]JHܵw&*  ^\rKWQ-'la)AB:%؁?|rqe6S(iwd=~iܼWpBt56|;:z,9;gzfhC mhmEp$*9evsˡcIo#}muk M/-)gx)0fY;>-{q2d}NS۽o?RsT{b5P9NC|ni.P{u6y4Ц400>ư1boڠMH$ۅ_d \nvc,i9s[W |i ,&@I={λbj-:[DzS-X|71 _ HbJq'HzR(Y ;jz^G_>/58yc$#M(įK(~[Tl`-ҳyxHc!9;`Yt0̂ _sTԋwMγN>M=ՠN} \`yù6JVݑY_ݢ2'#;]p-1~۲i``e]\ޡ[\S#nwJz:O0^R5Q?L`.>%#2=q oE5|>]^]AEhcW [XHOo=}1__f/ʵkOL#^MI\ _uNc;&&gmA # KJew+m$(_Vf(_Mr  bc0ȓpҲ}GB3^kjl}w%1-}~. s_~o;h B  Vxevf?A~X@A`+6NhRyc&1wdL3n6zm}l`BAwHApfAiR*? !:B%h>LIً'<+׮Oa&(,([MI%)-M YQ1Le gxk yfw&ش χ8;*>'۸Z|d6w9ߺwЅ4?iA4l4JDK0$D[耀ׇO{]d\㎲6s}WRJud-tLqi PLz+<2*' ?"՘b`8EQsj㤮jDLz*##s%?G~vsMW)hn!\X^5W\<0Y^Dɕ>δCl`9 C ]@5p3K"-7x}Rx/%A)kqU ' wFu*,R¤Es#K}A\"P<8&ULĢ$?%d$p\b3:_CI9_1ܑ-t) :0`zH~TO95jI}%Ct7UˎsI:ȊʛoMk?|HJN7/SX @ $sQIJQ^,,:FC9[S,ܻwi^Vd6Cn$,dlEV#!Ș+TJx GOb8˲àHnۿkðs1e֭H? 7>UK*+B2Lj9ҮcCAncm tz7m_\2"@kV4QeEvl~Z欦;%kR1}מ斨)bp{PEǑܰi'9_Ǎ0HT=@[Sr_@FkzZf RNne].L nG2nivQ`ŠJZg0+ͬ1JGRle\$ \9Q538~mMr8SMw빁hڑS+u& 0جcEE Op+~ݡ<@C}j({s6mt ?)'WNϧbVA;<$(B94Wh2 +6nD2m6M #~}%B&gg"}<@޷2QWtFi43*7m뫬1Uss2:nxSqYɅG&kׁVax}jot] 7p˟<: ׮[,ۄxD#s5U}Z -'^~$2r*&3ΌpEF=(^}jE;ǫnm'vz7+*yCaCmye_>b{nzIPWVCUB{ܻ:BX\QE(LA?A'Xס M NNHw { ÖS (!BFsL&]TN@ 4:P6(Mq{8ѹox̶Z:&Mwy,q]єVwuS:G,ˤ@auqg04.((ڵ6t$uTۓ۶c'4«(U% igՊU$_7g#rƒ4 L9Dx_sܒRF7n$$j\orj٬z2hЉOdy񅔻*GRpv'۵qJETv[S_c+n6c*7I*un"Gc[;bRUUj> W~wTb"o$F/\II)+. 惡% Z:]îlTռ my,ҙP؈ { V={z`8t`S7:`jEv \`DVY `Y  x_EcaB 5֫G=ìZEA9"BnT-*wG E,^mE Ժʳ6fD)OUIx 4ӌ1]F ۵gO6,XH'!L GUYi1Ah g@'9II0;9 s2Y@aD o&JMn]r: ClVŧtt`e%^d. 2N"qW8%bgY/h}uxxϲk"dᩒib;O~K׮h0ͭj:K;wQ&HtȍV@ dhKlZy2ܖn</ZEuVYH!{J+(%X+ dD=`* hE' aTd^ko0sdXα|M +*h9rxTRtctl]MXvK{L6Zc<7"KWҮ="] ҆iAfCS!0OMQdXq*Uq $pCc|hR[e˿Kbv.'kMES f4]_5ŰWn .@'eC@6s6n)H7vi!:2eA^|i8} c cf{< ۾7DAlUecپknu<̅)Zfڿ;K1O~@珁2,h3<ѧϝ~'l>: htGYeb@U lqop `4Fgƥ9c}UTIP5p- r"eƠNL'//f!ؼ?!'/"cG S;Xmv?xhE?i~ mzdG^F'y|=ƙNa_q;tq!5I0޻tAva1&͝A oD@d2m5e-x+Gls癊^PqM$-Pz}.P&q+|0 C-W-hSO~pN΅D|)Ȫ'Ϟ ?l1B%>cȽaĜ)+"&v*֍Ux瞟SX+ hrۃ͚4spܣ(H+W_<}gD\HZ:hg4ۂdvFsci|fO7_[_YLͪZA7XN 'kICK#VNv?w ;]<B+@m|PpYR yΔlǕ*8侺XnSe-֧ڠ?qy3"4©~wpv70Q`=kpr-վpo+Q\:SMla 0/SaCΗ2_ 9laFAvt-= 3O#9ѮIv/ܤ9ZqS} E"ƀkn,cABE=#IKT0)~@Gljk U/ݍ`y3<~`(.'c ):/ ]X=lë N`44rj)Ƽ&fzV7 =0NHʙi "SؘymmQb{ѸVnۉUCccx\ U(IvLYùqKaB͚-=s׮]?яJjvn 1)@S}D49mΝMU[O#d"HFIB03TPP`T{Blqgԍ`O`^{~%Ȥ{&9chij&7rZ=ZMXA Vls0w]o Xcwu9.>GZȀ- gϜURÃ|A TBP0>%w 9鎂o̹̚atũjjk1 hj36+e/CQfܶɧۈQwB$$z̲-[8M`Zۿ_]Xnr*e**b悊OlӃ))MfXmWoL HUV/ۻd_s837[ZCGoUn;1jϡØJ?j4+!C3Ҽhᘟ]"P1D .t+zz{C 1@`5Q$g_3q`ݻ͌J\gT~޺gv=dj`{zf~rzpe課q7xbMIłq鍍uc((P +&'_ Фb2-@Y`>Y +#/xXd{Wms5X#xlA?*ٞ^P2,%/_K3+61 -"{DrY\za},+˚aJ*6! iيNK/vZ?ow0<>  !P)i_Vm ?㊀ah kHS|h"aT:{jN v1K>Lg&;#$$9"Y mϰUTU$`bcʖõΑ(pgGGaJvnŚUTz^c::H/""!ܥW_Oʣ'lt}CӺW&NTYT{Nvtkн1qtL"9υI/.:f1ֻz>()Y0R%bA$/JBrVvKGGK[lQxl|qRt]̽LHQ%gN$%L:.^]Wnx{*#0Ż;XeehWԺr .@{nhh阏٠7 0q䕡 g-C,cQEo?~:C-ɨfH("XZ3.6,I\SY\.]"">)' "c"0[ba0/셋=WZRӠl~S@ .z =[Y*`6WfgDѻ8 Pܞ瘁&0z{ ӚWRՍnuluObe* Cl&n _ *nVWwj9[vy'xx+!Hqc ąj=\ꍷWL<Bc#GeXSEt%K fq1sO]P-*y@0K)ʅ\AbY+6l|w=ZTQqϿU\j]xجCEida{A%Q! j?DhW 퍐#/8z/ Pґ*P8CKLcd負 [\vI);^jm[qӱ.?70g~Fh@SفaOB fR=k7U+[n'ВS Rt(P]E41;/wӛ%ÚϐlڲÇ_rmr7Dh8R:~NǶd8u}_sO]8n VL(w!d,,`6hsö96c$$LU DI\4A( _Yq=;\-] m0^^d|I%%+%>4O!DDr=PR[A&+͑s@Zi"!& F-gYkcVUV8#5BP82xF'mܵE8gSHL2"G"%C/Jq[+`^'Hzu Y p r f9.[1G֝×N fFi @MF)1W?U@ѣRT䃏>~>4Zا)NB4kؐ?U 6^ujkWWX!` WH"d.x3Gwj D S% -k*n}rG<f:*]teۇ.56u>(%ϡ߅#7f]U% Y>D,I0)&<LG!xdQ~࢟I8xI۴{B<]ܓx$09 TIu`E,+,O>lӬq$Pm =`JI=}[I,u@mdO6p h6 Rg1fGa1$cK@}jťoc`w_/}+f6ՔPSԯ &Mf*}1D _|_޻0k y-meaҌ79=Ԝ|u as"J%+ @F &3"Gz*m@$㐎9YP?}K4R&`, ]HkhPP^;5환 @#8?XbExr_8b[\%@TD:&1&xjDq} <lf3YDᔉD(%~n·~j__FA10HR E;p3]K$̗(SJ3 M)عYpnզ>T]{Zof|Q,{ņ,t/&n=6dtp3\ǀُy7,DadºU Vf.7"\j&#+4/>8/?Z16#( 'subcKa?,}OE͍`rs;z/tGaNw;:O9sxW=Uk,!9wmNɞ13!iau==@/'8:|>)EuixDF;wRsD5+qӊUfO8{OJ P DF1b?"G+h d~@n٥eէN_}ZZʡں--\pQll,[ȵu=Y\qoC 9Y?{δ>@s^pXS{?ػX( Bl[ضmc4,-m6,v%?9hY*!Z#]3h^i?A+Rm:8RAV;Еޒ 4ѣt zќmF&80b3g7IإD0ur G8J"yq'1Wb$yf}4O7*e(e KNG4OQ伾 T=kc&,oIMD @SLAa'4ZWnU̾Vrϗ+vVf2ƖSR;a0Naal^b {-fi!4aJLعΨB ˝x"uxHWFQa+o`3`@-M*O=y8RFrRb6#])N_sиZcn܏컡y;4n!]AlwD#mb5I+гjk V]+S"[` f)V܄M^)_oU]ﲄ^ʉ /#vBƎ7Mp9nӵx·Ģ] mk=Od[}[c&{~6c@`'dKs2yN''Lrʤflf2K>sk@EZ-1ZJfMЯuӼ?]WdUDP@H:md ̮J,} =`S٫ҮyX=x@+flTj\&r]otC##!!31ʄd^U#kqy_(wșezQ;3g2+縪UmRG녶M?N׻/~fBS2LOh1 ly]q)ˁlIz 9=3#9е]".\V*Lכ݄0q9BTXR,W j%o~bŦu彲v5JRImbw(tQ ˙75? "}pSW{|sd,A텦֒l\&۵RJcZ91F`s0[y}:m%Xn\[X>G`2krF=NL6q:e/4^iψRn&,= #k,CQU@n:p*x€b0iMe>GYPi4^chߚy{>ȑrd.|LX#[dʐF"J4> f`@Nuɍ8/O ,p6i8n6wj{saF=R}sV'ܫDO=r;Y/:O{_4o?Ns[߻ }D^džOl/|HڈY틆nFIx%.&C#:N@j*O[Pb~?Cz!2/z|"E6#e0Sd)`H2! ë,׉U8m&m*`A=o(P qh{ W#a2Sǻ=L`a)H6[|J>ț/ZNiĸΉO]Ⲅ y_S~h'R_˚>m\0@`> m׏[_6|ӯa=ۑ&4*JoxgwT"D种qj\,)hS7 l>{g4G Ȣ4qrƞ&#ju0aE=I9 zncHb^FXy,9= !W<0$G &NK硽EFS7 +˶wkzz2L_|1CsXvi˛GW#rO[vI8G }Ax PyNK'-s+E?loѷdg>0 bpnvٸd-1 8my3iaK%}ک1v~=}zFGq4ן+3\-.wXz.R6;A@lQ?.<٩3tq$Z"$ܔպa5Gw(էKyO#!U|{Rb| B`dE?otɼ<, 6*pDdK]fgUäEӐ6p~7GzbA HP>spm./{l/c䇶E1J^}$#?]oZ^M$`]jq3koEcc@vC(8M$e@]o)u"a /Du]'2}36ӖNBk\dT鴍}s 'I6&sq}mo{&ДO;_ad(_#]0>\yɬad&J\%M=+7Pm`U莋#@~Fw;~a817 +u=+J0iDA@`7@#ې@t7/Z;!ϒoʪO`#1lOƞweS#4"=<H<8*-eqUG |WpBt8ޡbd4 2nFА@NrIdӆ(&VtG¬yg@~ў Z#tC0b/2Oc޻O=~Xzx~H6E?8 "FHTs1)-͚VD=у&{ |cpgߋ&!;bH=P 얆`MDe7/w::qעLc Ԟwg߫I˱"k{ǯ\gQ&@aK@(Öry_^-GM`c4⏑cyw}V=MlLBqP@JxYJF'P9:'K'!72ϝ}u?XAe Da;/7o:~Tw8RZ**|ݳaZ<}x<ŒQ([#8F!}W9Eq!w Y#ؕ}꫽(&@Y = qlx}$aTr6[ȗ ^h!S7_?8jл Ye>޴{Sy,AFHT!sp5r6$xEb D_kp2ϚmW~WK1AVv`ɇ<]o|J FxɍaEd34KOCهțab"! )h;f Q *hH!rF2F2c/c V& ='Qy0@חAێu5hmRCٌƞw Kz ?E^ 0CB"^FItWZE3/o=fN?/tl< $3!̊ʨ\\' .׾IOLLmn7<1L%&WWK*]U[Ė,0 $6ćw]ln |EO&EO-7:tXV^Xn3w^ayyH+?^\T^bT 0_e&!!CwOdZ"ꛩ|H'bQxoMͲ2٦*)n{~}sE[@P aٺ5c5"pqO`+˞ y $x`S '3$0\a7 h3 ^  O| BT* !|U'4PW*l$vڶ=%;@2+6"]Z_ -ۘOWJeR|߳ 2g#Emak.@`Ύ"M#zlN&χf:.f֌2xsں]g+A"ic%k0 /i>6 S=ιFh!xsDJ%/NNˆ36tGnI1;0oRiL`flrf1ޞM{;6w;D·u欥fls7ۙs<1}H-b; qroCF˖-o딱'pwlq|!z3~zjqÏ/[i =J p4 rq-\ĪOdЀΌmL:k)oZHAR[,?yŶ7ÞsSP6M,-1/埧M,>@~f¿qʫv W}χya5X('ȏPl̊2$[灟^O9F_t|dO4Jhxu~ \tѸag WǀsǏoMhAD@f81ltfd3恣&mt"2qN.ޅO~"A|X#g˓!LDq]&1 ;C$30 !ulx& ^吉2)f $Zf<lxN.kz_2QMGynzs1@ 簛T4UI >'Hhsul12 r!0Q3r((`>RfYF)K)/ė7JX]uͬ70sIHxm 1$X`rgUڻsrm39S|'jg4 O1'>@4aRgt&3gΧI+ډ(69qUJ$Ɵц? sM&do3WgWɅ@JQLZ?N GtDё)P\nX{_AR6+wd ن3kGs*X5 tXy  H{5Q HLb|pg D,rqt XɅ vwY%̪rS5@P1o^?~S:;z;a\/cm|MG gc> ؖ̾ f L$Bʸ}AZeeް[>y\taǙxoE؃(ki0byxwKt pui~y#9_W"HWp \w⡗Ǒ^Ny}0_d.F2On; 'Ild̻yfk2>y@7::RQb\kb1ڈ`aAPѓ~xEqdvQ#YU~ |ZDrݫ۲ f;Do !=*ߢܥU :~^?kK]#-sWDć; ik뵱t[M.W:z0Z}Iٹg1Lg0ؖ\ *W/z.G_&e{񿽙eKnEJ,* Ef Wי>t|p5ˤ!ItG ЕXhoedAb,0D fzmto_q NErUfy3Rf`"૾m*'sO%-lF%NMyOAS)~c睇=n߻'r9(!då$a(@Ad930RMRy ?nyq{VtE#-|7} ;qp_t~禮<.ٌqg; 3Fg`Įs|:g;tdI D`k"C'10p#? أ"Ս?a=le\u9+q)b>?^~t[71z_t=Kޤ'φ;?]S4#0+;4(F=~Cf"7 8i̋kWpeYÌwϢώg@Ǖg>Co7GAH'<Nj}'Sa@qrLň 'fWrɱlt%;3.DfgRxTj$#DˎgmpSAKBh⪾ޖr=K}.v;pSvVΰ^ol^̆d y,@l2 s"+绋ηRX7[sm@;;m 0Dll #xZ\IV5b7 o_4^f ldn_v~a-BP}},A35/~VFD 0@І xk|i9NB &gVC:R? !(5Y2n+yd_-C@<j)f|KlHwN&h&cC@rNc9DP\ݟ2oו|8.J 39k#hDAo #DA Ŷ gI:E[yu]Y!Jl$4$y?OnVd3UYOsp0a6 &WϽǣ_f|.sSlW_xܩf2f\o"O3ֳ-bCO&Ё˥7x~Tr6k(z44H֙K/wfr`7M6w{afDD)vT-3.ߘѻ& WaR֌ϐlz{/ !!}.Yz(5;sH ZD> JDeK|@.e֛6i>|<*L%+Z*͝ek'?$WԻ]g4SA.o   ٓ 2\,{/cy<'Ϝ˥3J@й9IzL0 \ɧP6sA`*B0PNxu@`H^(B9ޭ h0>æ$سYJR2 mQ -]oHR9E%CfFJ!q``soNƷNRqƊD0!ĀnvLMJ0Rbd4YAECT*a=b@c>vH?R怨T*D9`-0P^d#e_Tr ] ADluJÁ>3""vCT*XlB pT* $vƜٝ R;s  S(ART' G r` $ ER1+& v!"ŷ|צ^gCXTx90 @l06حe;ǵvw8#14caw|ύ?=}5xgƏ0ٰc}fQtǞ<=¹]_=?U>fqI:]]l`Vҁn$0aQT,CΥɭ 32`J`vejʿfh`R)BhȌlMHRk Dc@|e;JmN6FC~-QJņa ÃЩT*X?@åR >FsjT @;p ;KJR1c4Jr038bJb`G4L`@*J #p+ jJ2n2POF;N 0aRT̠J?C*x90 v qhT* B(C*nv\GRU˷ANj9`Re8dHpT*b.`|sP00HäRHBw `ސxNT* +B@bJS ?GRaݺR? _*!P=H" vZFR僽5HTj*! `=1CRA0;4̊T*C1_ PCR2w #ۃ;PTj@>60>!J ns] b1"bvT`U1@8JAĚ4vTD BA slCP~BER,[AC&AF84*&@F;@ÓA@T*A>}H t@@M>CRT`fC{O;QQăJ&ۍ| $J(atT`2B6Jr}H83 BR 펆  N1DY`R& Mo7v I#B|T*]x^ ;;/"cچFqHe=1j0{1?r;d 4TcX$N&%%Em`1-"G G+wDܷu4Ӆ n7Cz vHv؝ú0k!'|N"i`1 `\>MIxTbIt9EaK,7zXo2.a*l;f`0U90Êp>)D+Ab>:׷S՟go?N󛖿/ GSm8^DfӡX`613۾#gxnTTBc/qM ,ezӧoW;S˷/]TP+*?v^#ߋ Wx{:/o\떓Ei#I@O&h'?#{{͹y๏6h'$G/.NN~z횣GoZu_Bpm}P39<( O+`f Q/x8v'HLjOZC!et|s4r1r0~@F&luy9zppG_OGb>&gYFmf!1[/xZH0i} l޾A͈nKD H7W㴑{r߼hy}9:.%ن&* k7hϛ_/>3,1jL$oa$:g=kM?* d14J jŤrYÛ/ZN>3ٌz\^zьujșgH%ec\/4,3ٸlF&Ƕ9q {;=g`7qz@aPi<y&/O"z3Oo'<`Ez'( 6[UϧϏJJx/. ZwqE"fWg yWw*گ_0D_5٢$$Yr/(N&xh)a> >S&d4jg WmJ&u3!<7i;'2#@36& 6S䲣Y~PWmӈBJ|(6_MqG~޾lX֓1n$g7W~u/d$~ǎ:CoB`x*0,a(ᇛMN]ôqDdA $Fɢ\p3`toI򛊭H;m$Bd\..z>ތzcu' L&dD ~$vc$<*qS\'o/| w `fXLע[O(Zqp'kZcty' hP  6d.{7j5F"zWٵL&YUAneƒ}">%qSGlo;$KPVۧK\yW2C#:3f7B#Ѫr&Wol_>t߷tk\,3)AN0<=oS2 /VYOF)8z=q$D02{%(4OȏxSnB|/NJV(z J4A>HWי:Q0 2*\CO@۩1:{ fM o}g "9=.'O}1)y,H@*J4 !{ySǻUb.*lK~orFƛ ȟ)|Rjh e0~_E,)2;f m+}.W/PZ}?,&ܹ̾Sϻ$ H> P.\%W?]Gjٸ'${N:Ŭh6,g:x38^D wy3܆ק5H@6A؇2a}Av_Ӆh${}r|2zlʅm$"Uڸ\f76ݦI'rtcH 0#H@`4֝ I`ܛ&Z~xq#""qT^@]T >_}P졗^7 YX`Q8˥{ݶ ˵n*g 6[%rP<$aCw6xu%񣕹k?yqi x|1 $ZB6"!s=锋eIIFM !^0#&gumz\UKt=Uկ<)ٌmw3_|Gө|QT6_`Q,jufs<tzܸm1>owl_Eܓ0LTrOn.e( .!Da*DD0|?tW|)2J A_YoAٱ Oҙ4d*PBvO jK0{:;="a# G bBWmnC#6aP D  R|ҿ*=+7-\=ʙoJ%ґYAN27D0dtɏ@. _?` d$TpÉ0͉eӥgq]蓁Q^@OMܳƛԼ8q'"dGuu!3Ə&0s8\ B)>g`מzS]+)4(@,pIu-Gs@&K/V8mSb(w>xUkinS/㫖|̐1[si(v $Jb9nroB{{J +m26[%}im{qIv`,ٖo9ovX(V]d%>j%9wYiZ!`0)Af2\"0܈ = {a">lg@WQ"m!#Qo:#їUmѬJ,Жxi!j^<3X4j8?dFztỠ /sP) 2h)fjRs[AvM -WN-|$ `{QE.n–0Di26ύs3DJwXDu-|OrfD ?-էLI832W%;hP9zb1w&TĎߍ b /8b'z\ƟɮQ> Cm#g6Eh糘X3`Y;{;(ҁ5;Nr1:6>OO%?Lav_xbvd` f:Q 8D>5QC=ʆ)O,`u.3LQ-ݗlMvEZ 0fp(93~:uݾXK222DK1 yzcD}t Y{Tf^ <1:sL^;Pv֨?W֍ww<>Ek'ՙ 螶#aAA98RDOm`Y>yB{ayBcN~ ҡ0z {Z#dHD38Ť0FYѩPXEO8{D t mlEI1̬= .%R ,;poK0=41IQdP:D˲W5\cg3?82ΎLN"0XIE]髎J\gUiq:+Dz"0s Cԕ=TEBUw4˄ƣ(ި<{aL+#qϙ]vmr=2D=D\[IY$c TVl9=PWqe># f pI7=ZbZ+!LY]9taG].Vx|wRUGƮaݰ|Rg /NB !ÏhpzxCdT=TpXC'd3v _=b_:#jgCR䨖x1S34AܾBp"I er>.GM,²{=76Py rpVG݀O Vm\;F M_AyfCϿBRZ.6?>eQt#R .gw % V#cWp<-ˢ,sG+O8 hI.fBU,qr៯I ږ'!zt#cGuU/ CjA^a6?:cp&K^+PmRN "a ^IZ]n*}u^~J֑MpEtCD߻}Mٱ,?OW~zV#7:pN~_?fb€ { ;&v %(7WP$Q% r)! xAzE m1` dG7/2Sjk{Jo'pԩA{Ų#I53ħ~>yv+L1ԇ٦E6- Ŭ©e6U/; VkFت{h)$z@ K;f5 eqhaM_y9xݶjyq_Q촲,L}wyQSi*LJX1E[&eAeN+g$,feNu)%ё@Fpj8u*c~9Ű~gvR]&=0TmLo&X5GWYbq-s HKUdɿvdg48 =WKi9@$}ryt""0s:* j%,uu>o@UjS0$͚}/x W+0 rQ` R ϕ]t&z hI%8$Eh=HmbaߎiI RMdNqo%TkTH'/ 8^EJA5-E&z"H'w@,DQu)j1 +~^8cXZwq-ك[J [Gc~)s\ (,KbZ7EۅPbY*Ï/Dd[* {r$`jBe<0UK#~5z­hg[xtᨵl*.,fJR ̌ 0)v W+|? =e|/X5NLka 8$V` /=#ak>|scdh`}uގ?S4"knm͸)>N ?]7"ly5p"'Rŧuz3׵Zu_8#ÏtDoȊ \nsh.=`@ˁTM*ݫ}YKeZT?$߾u(̯ 5'DZVy ܴhVqƦQ/*_̵Ǥa6qڀpM~.+)eȔO~(#1Nj[lT{$!l6rYމвx]o'~\A@b ZbƯ~75~{"vhd|3J%dxTcJ7k`)l*AuiT~xŢ̢.g@U(' %޽S0Y3p6ՒuaHf&(1xwn=ժY٩cR>^7x|ݲXxQ>)L0@뒨 JGp| }b.$LCd~` Q(e x{^ojmmŬ+:>?0h/NK5㜲]ɺ?_W D6'P%I)"w~}CYYmQrR#+/6tI'n72!PC]^\Zw;$2!5ph4Kίˊgxucߗ;(/^m#Ө`@%uF.Qvd$ 'odDU!-^i5בOTEK߇q&Vǽ`!)-m 9cJ]RYwTͫ ~$)$kQtxLJsۻ_o(*mql0쩹'hV/'Bk%xbU:+&e4WX}㕌%OgS+ ۑxs `_;>V]:CV SfJS婘met "c">AʭsD^lTcRSF?( {1蟸KcUsi(oR_rZl{.%aUIԵQ,PMpZSzN+|[-Y}ԅ݇U~DbUY歼2 duqTQ:Z# Ó3h t4 PS"M= sM #%EjmI%gudr%00^ߪؠT& __v`ø ?@xt]{ Q;W%NDI*@Z@[z=P7 C_zN_よ:/` @/ T N>l{ܣn{{ƌM8%pO@M@5QX?F}~-~38g=e9Q.~;Ǒ|=_8.'i~]u3(UO;NJ'+8ȏ;_xSk&xqa\Kⷣ2 D*pIG^G;q@`p[p57ŕosD6X7a>s~X\Έ>ȩ''gO+t^_οrehGo߾?xV;]M "NI*]>E'ڃ;ꋡkdP.s%:E0L"؎,zHt[x.aLukD̵+3tR0wVwڹ2n~.:% m3| (}Dڿ~Y(*[q[厹u>?J򾟆d1~O}_7ؖ\(C>'sFY/!@υHY\w鼾'[۶owu:soc孽{Ufmя7}9GO3|A{N;iDz28ܵyӗ]_M^+קyۧ'`띭Gnp>6("Ne޼ya{vvz]d&xW>;]nIFC|Là򕽫c՟8 ^\2333YffiPm7z 78\ ˑ—;W~]ˮz_ =o^xi8OW H.AWdiꮌ$7-J^E{"շF#cZ~GG%_-/;\ _Yư,>w>y~(48P`5nk `h\%G9n贪c0y GQNy:naw9w84`5Fco@U.'1.a >cSO\+O#1 VaAgN5e[ TA}  ^ڔ禎 :%GA._{B]QѽvZk6cd{T)>'ň,C#%gˑ']y9 fW_R2J9ry{W^dc-',.mƞQv%Bz4V71:CU>DIBc OAQ`ծbeQw<fKx2ҖOq7uy_M߀?%U9IJO"Xc1n{.y J9P~l>? Q/l `QZ5=p_3oqӬ`aAӚqG[!@008¸°ҬcGp9D|C8~#^`#È{`qe zbXOR8tќ<rFr *8}r55?q1Nq=ݨ)JYw^WI> 9D>~h yuHe<6{W.JSYobY Moi:HW"]:Iʁ?LyB}S3Ơȡ' 4<xv_1u9n^0~)@0ALX%y6 Lӷ< N!e< iNDYQF>s!y%@cE[< f{5\sp_~=e?6 apbiDxq D4l vUʾgM^*| u# q'8Dr'`vl@0 tr>d'N:9q*<|9a`U02 (RNL"_0F% HM+SdWGAb[bOLo>&(Y [-@V wod,+ɴw*_=0~P]ztIQ)?C[MSa{~kcqO1zr/&? 23y>$hK3-A4Z3QۅFWy|Eh훂aNJ04&.E!wmO`UP2_v|! TYx|J[H-=rfR/\1ȳ@xB/.*XsʡxqH%hݵ#!3THSXPSJgwsE;0ka.9Py" sx_=m12="{9b bJ @Nvd6 p֞HEZlEw420fխ CHkr ǫe0+vp^(!xFáv5@Pu}lDž9,s ~ 9'59E *!?Rf:1Tp0~.C0%+* q#Yڵ` W k`odۀ3P|Ex d+gT!&X>*\C\xo7t@ؑY%6T+ tP8:.}|;fdTpCg6P;XW._^//YDel `(*y[ob|Kdv]GdtHe*Q!8U݀kK+(ʺRp:Tnj!a`[dy$ne>.1Yp]V'2ŇEI#;7Jz /l`PĜ]a3n5Ch7-f#/FV[4%fazGOZ|_(t08F<{a3.A'T2+{ {\$ 0 EϽƄR P)2:Iq2iɰ j;.a+],wFA=(D4q(r ]{ԣ<\dW| Pۣ< 1}53Z7HS)|kfHSo4Gϛ.+\y![}do BvF>=o/]=*s9giPP(f&S3y@9۾K8MPYK@[ M NQx]|H& 섩PΣ$ 9@h./(JK&JRӥA| <35ǁRN|HA}#w8Tz"2;~؇^-oW\Kzr,E񂉺>a]>JUqXB2Hc#?~ϩ&vxo?0q. S<_zJ'\֟D#mkt>ظ8(0pլ}=xHNd 1,+r0,rT{+ĕb xzw+)kkL9`z0hHϧ hHGy;rK  GH0T:X)?Ǜ 8=Ke'"yNvFv2.D`I@C9y`!/DyAo~&P{[%0d1:/g *L j5a!Ou)d:0X``ܶe'jM@ ~%mqM2iX` q`J)<ZVO)3VomM{C͋y  :Lq(wta;T|"EQnڶz .䇏>#&rLaH 8-܃ZBuQ)bPNB\^}-JܠؖSA ̛VeJй6E@ía%o,X[naS]u9FĥI' rSA <#0S!Ú|=Hpk1ĵ 92됡g0X`G,e1K/xB*:`j~QNzxJoz°S<``5#AQN#w9<סK!(L/gċ1<\_.v>P P;}?ru}͋̃"%uӀWCq1\l"h[ C SZ"OX1G++˼=v?[`ɳMNo LHw)|Xx|eFrpoGVnC[jLNw5Td-Cw6sBYs)uk~/=D:B8!H%uCM?Aa-"8?_:w/D"ͯ!`ҿD"J́Dr|q 89^3m}; WCB|1B%JddͿϧ 0'/v2 TFp68O?ãC|Y}xaFf}xyN_[PC @+܇|ͯ>%yb Nvq'Hy|cnߢo<1Àࠣ*Ġ!qW0vbg !;VJ$yo}~Ƨ=gdh'џۙ0?{0wBk"%۽E( ;XeMj"<F#ƎsiE!f3J=?ڀT.G`nmݳl@O;4 ~OД}] 5/r?5񗜪k{x.K2xA',Zp8,E" v~Mp?kƴ<^Zzwѿ̥> emownUqnW d|}ϑ1o=QϜҏC@ߠ븀/tZk!~o^:[QK?nct <:%gZI,zOBAm[bWA0 @ @ opIENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/icon_ivi_simple-shm.png0000644000175000017460000011534200000000000020701 0ustar00simonwheel00000000000000PNG  IHDR\rfIDATx00pC^Eػhanq9?~,ĉqv#f#h\񑳩Sod/b7q_W,+;uA ?c Hg$"@ @xs$蹅ͱm۶8xm۶mmQzf+Ԥ;$'ϯuCZ@Uk$K3"Y='k%.3=9hYj{#aBuMЉt1ĶGŒ\,8; 'b8U={H)f~urZtW[Sw#gP|յi~Zfs1;N} m\_!C~PTW|eiƥ{ ۇn>r7Mw.vnP|k^.6Wc#ǹDh8|>zk_Gu%7&,D^~]'$;I.'\0\}(Q\s]Ύ+xɷ~U,\Ξ=ト733+C4+! f~D}EЊlpëvQ{cX=Ѕyh[/d4)w2XczCI}\6~e;bpBZ3 k!*J(8!pU}#7gsiџ7vx 3fъ V(/ӷM?a&lG ž-#?_-[9XDۜet-T-vb\XuF@:V0lHjZ*-+&:p3/] MMfq!`mOgޅ;؞=3^ܤ7]13Qޙ/SGk Hb &W"|[0Vn7E~r?~i[˙0TʑJRb8C7 :ԛVp0\\ND!;m].s^c8 ):vQ\6?;hHrF{nqfQo/FKw6(|ǯçbn.eU̝[6Q XhF5D$>v +Klqɐxnxs!lp0.1%̍.珊F V~FƺIЎQƽM<lm9Os?KO9 b;͞&qvH  F5e׸/6 Qz[(/`R7ϐ_%kƏ[CMRH%}@c{aNh`L;xȮ X~^1n~ JOpW~֌88D=/m@r]"5o"J N2R _ Һ-l{~E%IQGY%- &k*bPbL]"I`h ]^&щHщҲV]& pu=N%4DL}5_٘-bIBIA7vͣ?lIf' SpB1333333N,˒%,=Jd9ϙY}F ~rJޢvtayr>~nZSU;w]B-s;T] :`/q@wT1 { _g1a;GZ2}C^f|\}joΌLu9!}ER[FHNJz#펉.󕪤j'=o7<-1&mjpxĩ~쁄J1p6쇷TJKgfr~m#.Xks}f Tm1F1 :#m/*qpE:\'j.Mɚ~ĢG9*YҦ-*_[E+*pFu&58QqPBM] ;zJ t5o|`;<9?v|Ni%eU6gkf̌9` քc҉J)hUJߘPKERV I*2Wt*`Fk6ehʂTiio\"IM{ 9O@L4y#8-?U~'Й,g,Ѩ/36k5Ęv5ms2K\ξC҉H6۪S9l(=U*%שּׂ٠ݨtЋǭY;rNȫSN(* `>7iv!}j9y8ma \apr%YԗȰO5Rs1s[2s)8O!'C0쒾"j|]Z+wrx@) KJpF%@OBt+eG0d,=dGBP@S >K[ >&2voERB7ʱ!4]+挹+%lX#s Z -o.Jx/䵲mzxTAlm/#w@\'}/#Cx4dn]?AEzd eF/:@]}$e39hr:Hc颞AzG_v!3fH՛do{Jms8XSr%`*q8_Oz'K?L-pc<%rMmn-# ?'>p4 Gwa{Է@2=7{c L~4[O!!A"  9U/\ۍd^7<ʶNٙKt|>~Y{J /a',VF˹9o]4EϹ]%Op[f,fco Ƶ pn9KN X*Wf <wS.n78 !nbvf&u*')fu5 Hf~C)X UP03r:ƚI9᭔F}1I [ZHKV|٘[昹'=#'[ןx ~7˪ 6Eӧ8QV/U-Ȍ}: yLM5l'1@w CQ8' + }: 廠c:A]2pFgm76GZؑj|WSWyc;eC4oDr6T3s;2z1ysg?&󟟕Rl+ʿ*4޾mZ}nc.Psb "N5W}-)/(c.3 B4EHеP4ĕ.vK"mO93G3.m~To_f P p?OR֌ ! a1GZ3Cm{s}Ӯt{9wEߐu>.Fk-JU[Uҁ_RX<kP{9lbs{=K7}?N|rʨ[wãp5P^ )1x.tW^DZ9AdgLz-<_d`CL]y _CiGhEf.n9-sg/S|>kQ7kRXlR8<R|lM(uvAc>5;5bS*}[H;X;<hn>Ȕ .B>MP d\Cqv >4PD] >7æs@arSMy,с/Gos\m>oT\ظH{ 9>@Sbr+,ב6̹Z>}gܦTI#k7xP>'¿H)$۝Mp۔NUgݭ:bkґKlC>ټ3Nl:P$E:G~){ C9G.1lX5&(k48$^]GsXYe햫:~ J5\4[ Ԟu56oC:@ؔ:N-&`O sszoe^U91:a|y ȧ?@}|(+Ydl W.Cߞ;R꿉܎,%=ࠡF: s;hخE~9Bh'$Qܮ&Vaulnڗ@2osvCsMat't(V?V߭u{4P@8c"rN38[E޵N_VoUP<*DS4,P'%M/Ε9zʟE :j6,x)[C{ॆ0R>^FoƯs Mŀ>$?qU3i,1HnƯ.bsRҍqL{$[ƷP ( K^QlUh!ko˽'ળ 6Wӣ^|ma/{V5v^hRA"%/͵&=xja3a-xn rny Fx?u)}p 0zDs=8P{`7Lʚձv.eΤ k仲׷LQbU|5c;.=^n$,p1bU0턗 Q:Xqd6,?Wl/ ^P0n\؜RؙʸV9~ ,\0Eop`c 7ӻ,>&=눬pإ !=f_*o/}럱:[2B=bMk+/ZZ;! T*Xljq8yRVw0å΢*.`MpBgm!pKџ:{pAM!bvA{=h& ۔ӡd NHk8MAi O>+vrz񁢤wWێC!0m/%Wx6MH\/^Si$was$Ce(Z)5vvvHAY#(LX ^Vۘl9:]߸6PlpȇBl{V /P>sGWf=2Ɔg&M*BGsx(H߽PE$t,䣔}> @Cg:059frЗ2@k0k>736t<\ lPG?&B:ʼEf#i}t~!$݉. dgg=Y荪ƯV?~aG*C7 go )-Rf&M@;$5Cr F6lwM7@7:nѶht2B B /7}{5^ޫ{bOa eeaElfmSƇ dƐw0˩?ey[)kBhcogЛCپwA@{6kvAudIH? W),)y+C.<J65\}LT[{JCaiJ(&Avn0CM4۸ef#CCbރ\C· ד~8q"BG}62'C0`wSFPnd:i?+ZWGv:ʨ^CH\r'M>%aPǡS40fOYHJGz E)9nݏl+8bp4OLʐʌb+[*pШlL̊K}L?>'RjP$a 1O!pcR29:b^)^-6.b cMvvXV#ѵwؓ jyGő߇i"k F ^Y~] Cgs Էǫ~t\LǑu$aM",7I07ӏͣ4H<3F?!h  <|r#$<0n3~Cô_ [Ma0=YܷS d17!S~ \FWje6 ~붫黛R)eʁcrF/RdЗٌpI`BLlQ_3BOO IXCIu"mPFwQrHV`0ccfĎ)fN72 >ffǃ>/{ ?SRUtUҽm7vϐ諄Ot#}9^2'qyeh]y웱~#Ҕg@/$^ miڞw~e1}'9 !3&iG2fwBx _ȭzOu!&}b{_ޘ{×}\`vYa7L6`R-x}E̊>ӕny'cysD@$ >lG~ʝv1h* i8ϗmOko\k뮃$Kؼ >g<\?@iqc)عc:lEG?NVKb"?a7p;lu pqJl$g1vaG-cwnw?[XP>} ƦMgzYY:hȕz] 2~S5/kLgfIwgani2O)H:er ZNzؿS>^Av5 p #i]%~| C2ԓIeZwwm2\+)}e9n4qѵ6L'0]Abd ,'Gb1;ƫ~yhA2ZEȒja\<rcp·AcǾԝݟ(FGf=`ǔCk  k5نi{=]!7`VDa9!%pk6S]gvg aF\dw_"u:AWQw@?7loqV҇/E}5߇VBmN#&X3z(ѕOcrV-,P8&c{pVAb8zB"W bXLrJ[l61>`OG܈'[ɕAr>aY 4K;];#rҽʂ,vڿDBѾ{o ~HurzOcMVN>͔ǭ^!}=m#sY}% mp ޥ ߧ3#'HÂ- O|[* 8wĮco_'م—,A{J@1|0E(~nSd}ؐ[ǹo?r% 6qU.<~o_zP#&o31-=x&FʗaB=X'%Gy#H竢 \_CO]C/eHy NEa2lA=4J45) ~HX: ]{B ހ+20r8WG6Ԍ-8uȻ0Sȷy{ovÁ ;,xl ?k|7#zo ~ μwJcXN=M01gh[̺E1]?6 =$I(Q8 ez>ßY補?b1B i~tP_$ϧȘ~X #Fm4>?I6_! ޡ.EgE =%12;onbuq/> ɽ.2q;{^BG=e\t:9G=mlԗܦ>Izɥi_O:/緙/3̄/~yʃ z IevAiD^ <?nG=ޅM7A@ 9h5]>t$}Q#*KB/bs+x*$vס6Ƅ_~5[.~{!4\ /~ ;􊯋;4, s05uAK뀷޿W_l{V&ڑdt6TJ j$q=XI, gЅIARw~t1teb# warEvMڌX z] wոP(z٤,[m8>x.tqʏ{c0Qs <.XCXo~0;0 hG'DL44Ү&ri~E<Ri^k KTSiKW8 f`iLAKoR m=\02%$w>ߤr>{Rw4TЍج@哰vYܥ[0D& ԩ=0І'ZѺ^t-"u)+)7I 8g.㏇p-6r9>cb0kIju ́|%cupr>C ?oܮ!PR _kxk$L$oE*"8=O gI`&J,~92#)biCn2o;ϛ-xz(v$ׂ$]{mOPG}(#^XO_L|b3^ <1<';iCQ&.;y'MބCp+ _Fw$ѝ >kLYȳ;.Q՛I@'A\vn9 (.&? b$ )'̘"UY;%s1+:?Ƒ:o.@A#ilS2$&qtۜ9y1=C+{ŀ.1q /2{^Jn7&b2MMf~;1ҍ?"ExJ^69 ~Aړ27ߧNֱ{"msnF&4416Ǹ\_56N\᫄$ȱFh π楜gNWOLyD4&_ r 5@d˓8f<[;6LȽ:~D~IHy/ke'~[w{tft;'.4g]JU>F[Ih1qs7#u̝؃oY{>2%1؋,na$!%o/CkFd c Aۇ ՋMs֕s2V21v&i} etG\`$i&t\/m !dgeUҮ,a5$Yڗ2ڽƂ_k)1*I x#w"ZWT%6j,3C O=LK(ssA>ޛ;й aWع fcй |0i) S3R2QyLR^B$z&i} ?3?LDseD-/`% m͐~=>hYjʤy)kS^*v H+p&꧝T/в؂؋0uG:@fԾh_ s׻)WAm[=A|Yj׾G<#/E3{u =& p[tDırQ`Vyc}{hCuvlObU"= H 1oE \?z3mQF΃l#)t6xHy;k#%Fo"a/Ҿq+r4q vnߩ;Oy/u*(qb6L$f}oh]`9&|K%1 gOri_-ssaEL3b&>g˦hغF56olAuf\A74cW$LRCNA ad De?tgvK}/'1t֏I#gV?cz7\ApNJO RvF-ӖA]0Jk{^Bg=:V 4rc3m_aL3bdyB#o.dl" RġihR/GdIBGڅ/2 }@ϖ%YA9us-1(Y j M~nDžF"!K ʮH C&,6*u>g) 4E|e@6-fvGfm΅W2GԵA:'q=z>jE5/>Mȓt$"2ɝn1yx 3֜O=fg$ڂ&l1[TL*,{Hc^4)tȌ-MH5x6ÒGw$$2v,lE;8}ZL]B$8j g$أj/}+諔Eb:D8) p-v@,cYd]%5=!b>K$^w+`UIk@/}!>u*9XHc D굖.W4asP^,YeG9Wގw4uPfuއYP_nA[44? v0̘kL|B2h>dju`>$"h}N(S" gC]0xA ʬg]2'q>ŋtH }8a>^|fxO}>x!l]Ytx`Bt\d+y#|Fǻ g֊N}ts C,Z,, P׾ٰ{ݟʏ?GYJrcqط23O9y?rx?m v"]|ehnk?Vܰlq*ߍ3}J=[]<Ы җnޣev0e=ZȷB7豇0ܫwc:poN\E~h@zl#3AK9Vp[wO\¯b>#rҫG|ۊcLi6Ѣ[r(W}/݋d]i ]ib?hyw ?r-J }|Are) ).Oi}5v\o)+4TΘP,:Dvtar__90>=PuĿogn2uTL /wp!^-Z6 E`c}lk3m^#:QZ߈d*|C:H xZơLalrԗgZܐ{op+OY0?c]7,܀r)Yc $FR8iMA+ˋhݐRa rd}VB8ڹG%rj!|orIwR-` n6@:=meW1]AG2ʱ؃dnC:¥Pɟܴ/:1ԶQǶ-NP'2^OQ\9P}K6Xfqp#NP\9[Y]<~kS;\}nvu%t2v>BD=0t,"/Uѭx?7+ ;_h,~ڠ]]r3>/2]8+նrKO\;7k yjs 6qw|r)Edzlk\0jK>aO X?/uZp:1?=C, c"lD;7'9 W<6ЙjZB+.J wPC?,L'/Qߑa?B);WsH+Hv&*p3  e+4ud;~"9\n3xB>쁝.&׭3(>p sLy/͈[PgS33RECkqXYlKvV9(axQm|SLpUnyxp I*`ar=MpC#_a@k]?Ay8LY6F }X cQxWЪWĿNFy.8]YG.lNyxJ.+sZ}plg}YYZboWy4h*` ӽQd,-x8k:z=J=:!v ;awC?Vp&G~!+= NmקAZw̳'co^8;'^Gy\ }qٚo{%l@;V :HF;A to5>&-8O9P\ynco̡%WaU ;w Hb&sٖә̯_"ƺ]o`F'cv A y-9s{DW=bmq=xнNΗv@GQb`Auϔfo<+PA]VP3l/lq2۶ɟzkcdȷ t/\؉ss0ϠT&җ/Q>ւ` ەCb!Wd^| W}d$ b,i3c.;@3${?nja%҅AZy1+MLK z+Nۤg~4pU=!~Ӡ(g^&TyuK.nCy (Lz"F&۸~LHccQdfa'!ڵc&;KNqa*$~(ʡ }52M`sZ: CS?.s ]1s}~ǾQ?^_jnsA3 A)@s-4V3M֡P+k :^m瞸8踨8jcE"ΐi}2 ؆Iu Xk[OJr5ĉ>WЖWׇ{ 0*y1.1baᗀ1{hxX09 (}r Q[}Air;?ZSTGO^_kh qWJDZATb6S鵋[%> yJ\7w@.FC1?hG;t](hѹְms}+|K|ϓhE=p\õsĥn|oc/oc(::0qbx24X 4\@'@I(qĝMvw2/cK+l)˂[%6P`NǕ؊Y;t;ᦒJ7r2͚o9!'+HgHc4$Yox`;w-8*q[pyG@u=9E|C_Nȟ;I?aKաw30Z`_~1ggqK?v" xl7Մ}GHhps?I C mIC~*.#;:1kwkč\ dXqp95h 8'#4g ?Ùϖ{ 1 ̇Ac> +q6'σsLKw{rk%/&AV-ÿWdy01W` $? 'ۿ40yq VuFCׅd:OowL EC#C9$1>_03v4 9%f|o!ď 5hWn?)UbTŸ˼ѵBG$Ň۬7J79?x=H.p 2 W_1(rO,M5( wh5&hTT }/ΣuE>j<=>8 >$&KkE$V,1}P. ouhE(|䪽W(H|n~ޤ= %߃ cs!KEkN?N^S7rkO4t*?|'cbiهcV^$6WDkY aIs>x]Nǁؙ4?=U@0q*.'?9z2ٿ; ɑ$ ;vc߽1333323063}UMuSn4djЇnͺN9+i+NdC4 C\  A^< }FL0rJaq?y &l#asPR0I?p/lz&Cl ]Fܾ A&TE[i+n(4,#7ѮfD{~ ]|l!SL䜃4nZ$ 9 joTYfbvAWD@"NLvv[4c7/~_:9MCUgQw]2;yײu('Ec)~(iq{#rz^g7 t=i66`ݱ_ poPgEh_KE#+|i$$k3{I׈? k6QԵw@ Es~m gt29l7Nc4!Gvc J$"rλٗ)rPg?B 7s3pBE߀s(2[`[}2Ml7E;t6_ /ovUͳߵ@,bW?$yWc`]iМSAR 7R>*>װAyV?$o[4bXT*h*/F}Iɿduy8..+O4xtԩw_RV bڄ*=:[(#1^C>d?ِ0?XjoΫ)>[ v& qxφ;JA>? >P]z:jvOb?[t|]]:-H_"o"tԯb3 CP""Ӊ^y vJH嬅]CW~>(} uCm㘱 _&6;:mt՝Q >})ԿZPyu9,6yf>mDzLa#_7#'lã嘹z9;h{[Sp_IgIn@N)8)/!7:->7^6^v Am GANH"o$ |hx.S>~8t gB{_zAnNHmkl/H[9u/9bi3<| <j4y#ƦkTV1;!]$LL.,@a3F}WON+M&CpܱGC/~˫Udcp1;\ƫ \Uo4Y|9q:w5)m¦u]/ ]Omb~T&?b^']@e(Ww r}eo[*mz5b} 1;#&h-]h-n0ƷvHOcPixAqPO>[\`xMX¿#VpXMry`$vB2Iەƕ6,>e;/{g%ǑjIez133wMkfX1ؒ +3d13X4= JYsv?NdeFDFeFfeUcJk^NCm%3)O2h֕[c]mnumII;c֨(ĭ p\>|Y?v~Dy/6(Wu2^{n=ᜎ^(Arɶ~㳰h̅? 3c.5Sw6繞 vlab@$)?I F#7ɦ~{ЫDЏϫg`Poc ;*}L| v nA~ *'C{UxzI1 K_L`>b_Qڝ: Fm>b Z]Wt2 dVc K~%.NPaCVRX9yU,PCfxd򳐷E 7|f''I/1xYŀ@{SEVC奛"&AIc!'H ˞~ۢb usb8bs/y?B1dG niA6*Tøkw:N_] O[Š?B'֯ {DԘ+,tP D۴Ou;yŏz љ@&i)'΁z>A M @wDڄ"l?COd\@ F`?i[gl3J'Bh1-yڬ>P4ZODs)o,*_8vkG_%OvoѝM5C1d1Ŕ @bvUx ޅ\>*\X91x^1d)f:ƨ^[rIC">)prHse/\¹۸>I-R@f-f%MZY\[ieX9|>g"/\E(6+Ȫ6$PA=O!\\~ "RZw7Bm{4~0< 3٦=qgF1t |?I M+nt^^h>l/|?Jgqd:Kcb\(7);IzXyb[R'߅.ԝG?U%3W 7"tւ ]ᄎ09c`~9_. ~. *z0;-A~'Jհ. =|ـ>$CړAڬz(cmlyO|k6PT@ -tp@>}BAOI}W?5Eۿt@w/{uZOHžmk{>|\sӡ~4 >tM)Ns/_ڵm+ar!g1ztMΠ??.MZާa.2{,\tڕHLFy݂lu,b/It3Vy( KQ3M͏E\D9ږSly`^osC\qwA^5c7Ihs,dgWN8xkKNSqiK@&l?F;K=tZ[3IhBnGgr)ƓGݴSHKw\lI7dDk)M U,&tв8/c:ڤ*)Q ~a) Ko yuͶ9o&Z,CQCX!Ǒ{l ;*my}y!߅Cf HaWs̝"rU'Gg= mAgo躯L:qI@G&]>:PC#EI'Nyխn%qWކ @d_џ0? 6B$ƣ/jڎs?I鵨x"az8Dy/”mQq@N,ˆk RV>,70j1+1xES*pZq^69܈4d:ty>f~@'f9H]N*, 1Ħ\00徰¬&cNq?caO%2z@%&&k < @gl< avye?,a{&3p/p^EPVى 2/>/\ rc ˿V}F=#OM8ہJ?[?d`]/Gn:!{2xnv'>'!X=\LSDNn#نm}f'? 1Mji@ p?Vu]iSZD%`r ـR@ jT /(%6 n$_%om[prF?Iy3QJfR~; wNŘ 6--onDq(hS1i&t5U@8#Er%\˽k3s;Yo/:>{vu% iհr :2ah Ka8b!0#=; tuޢ 2_ ga24uMBAA>|=zPD< l7Խwh3< y" wIôw}2Cpt35=qb6O =@pRz@|>4 v~`E6mq-ۑ "uΐ?@ya2I ^?'!)/6zaI"ߞ*xyP>?"R?ɴ:!@ %3Cs4>vWk\ iz8缘YvHt V.I-Nj hG43ôł/H[5 B]GP} pQ0ǰ{J_ ^)I˩Uw[x3q}>Lyl{ðFR"< {ahnFBHI/'"˯"2 3C+{=9(h:yf_?;>S:;*1 $$i>SP&a?:@%md0gh[/B : >5*y?K O'$l1o^,V.tmCpr]=iCzV' PzsEisC]Z~{NWNٴd]<1ؓY_f' `#9 S>Uwo"_SЋ_b!Q< }*OCHtB]lt<i~nz/r2!?ZQ1*~!{^1w!żOk8r=\y?PI:U.3`L?d>9YsݱwO.My^IFvi.mf=n*ł%-z˕ wz@H^3;dJC9XKMZ\Xi>7p Vߩsp߄?ζ\) Alļ&o0N-u}a>^Iq]R]$sHp?L?6b Ps`6Ɨn6} D}r YBZ{9(8yж6)F 6;X,Ylx|jzSs< dM\WY{TXd\+I2GVX" $ x99A}$d`V`ˢ !Ʉo4T IP &WB~HZßZ݋AoE[=-aӞy`: URo}m7ƁJ@r` ޤgSAXfؽ6$*7(.K珧?1#Ay"Zމ[ (O[lEt_\渭[#:};O}>7pir2{=rLOq(n>BBRB.8cȝt^%X x@,mz^,FwZت4}X+ 8mS{m*ݲBg'V[\ |8k`6G`,P>Ħ8E.\tB?o G˰l}b{>S6?9sp=Mn?Pi|w=VMou+;`05Ss']v>Svؤ|9@DAAb:z,lfB> <&N25әE.Cy=^^@≸'ET78\x ◲s0@Y1h=k~x]c"ծ3 W @NNfАЩ8vʛ(Fr|B.a'5|%8s,ȱ 7U}<9>'(hؐLq Mh7L.'1ص:Ӑ}\n̘(~,iWR݆x؜e% @uk@P'mC04v{{q^ }lLZyo'ۙw8}H?G6'oX_T}hO1Uu3W']Zعã~ qD^~ʅ:f`f .H[]^?@L4l(+ _edte q5xAR圿Z> QCGj!}N6O=+RE*A{|W!c}^A[ep'm.ͅ!Knڟ<Cdy`sn\6Ὼ(.|Rw+1տx=8n/قa((Oh1LK}DD,=;k!{XiJODS*me8 E #K`y< ѓ͠+{݃͠hS"<}doPԗo _#u14`~Ǩdm<^hQCC4{_SUG^mq|`Qw1FW3^)aX.Uyt`c8HڿˮT]!VwE&r22aRL{$IwIIĽ$Bڻd?cmGOEd<8VӾAȲ)D|"IeH`_AR7_ vWYtB+g ʲ! \hhOY+ +̢79n%}<1cr ~Б\m}p?{^spal#0W9퐴X;m%~KSǖ7AWx*vl.a9JO#)o}72Hx9vBI ?=!x8t~0>@)* yMI'>E@З^M7ћս-da{:k6"߯½h~CPk.hK\NHcB >57rlНo|`\oO:" +pJVwy +QG5x+` L| .8 ¯i ǠһB?S P{G - U`vKO;ojn}-R\Q?]pVVXrrh[ 擬7\0:J|ɳm A'|;uOA(Xb^אҳE<+A$%$r;t݄\<$n|!lyl$$_Fli /+0zI4(y,K:N^@1E7kNFauʧ9=9>J[?W^xN]^sɑi˥[ ʯ Vrq x15,\=h{Y`+/oh\EB$cpPm &8,;؊:4?8lr8kPjkq8}?Wm0 [N95XCE|Ϝ} Tec/s{ m9ې_qn["pH([L/ԅaWq 6Oup0 qXZ H5|5J n|vʾ\*g UٟUp<`o`LGTnk|n\Lȧ~p^հz}c01[aS|k~5B.arOsz mRY6jq#CH>ρ0rɒCn RK|4;8聲4}#;U JڏHcn8,`$'o3O7_*2pTe@Wq=$'o` Σt?9ugg?X:f+|Mþ̓vb%fŞ@ dQ>H/Sނ~-_Fd>e\'ؘ|ز7_4/>x:X2p}8k9p |4R-iH'/tKk@qre2xc96ES~2~$6ZRD?ag!)do ?|,%ȃ~Ƕ"3G("F~7l{ 59X!nXX}*O`c,{gJߥ~k q?clz͸( qyt/A9G>,WB9 o_ 3Rqv-[ٳCnv,Y!ɾ®"huEk. 5;Κ>X<| )Ƕͻ6o{; ?OLt?? 5$Xǧ!bou@I{PG?>LW1&6ʃoQIᛐqu٩` |5 ;.;: 6OO8J/tϕwm$ ,dȞI9I8)l_Iwqg9f>pw{{ '`ʃ[06HHe6%~vm !T@@9#2^wAE :&,$ɫ],`!~)lƒ6-{vRVՉNOa# cx8k[vpeQ!o6a+i=c XH[pmrKP\Q'V ծ~ PTvK]6n'|kvJS4v;SBw0L\YBA%j,jL:ÆvK[.tKmJM,ك8CQ7i9٩mXȻ!mzˢJ˿9>k]MR~!yJc p3P@{0MW3To(Ǩ'˘jJx{H kOJ#_L)CR#t,c/p~VRIyb5E;RXL)<:^O%'Sp1^eR6 aoiwbeঊnidG8 2 Ui&,G?*6|| O02|UIͮ&3HըWa-wWRK+kA%eSn{)SQ! _H7zNΝƗ %hް{!OK혉/&Zl ?A9NɳX[Qe@}GʓFYKá ᘄ.^^+֊CD@?}m%l#M1:J ?\j+GCAvj;Dc=:od:F?^α0 DPyQPJ'g~T c%05Z#Z_CK^O7<Ϋ;>q4N!G_(u%LMU µU!&E_KV;ddHk }mj׆rSv:3L G X'{]BsI94~̬iš\?@f} y2gdL& 1Ϻ(ǠK ccHťo1 nVǑDB'8\0QG^I,3h-.5A}9eŋhZ/A3x9hJ{2Љ_`j?ig7c5{zAZ׋꿈LjWuYDMGiC-.#&0%YgZ3E;\dV%9@?V6I=$"*f>'FJjYzPӰ|,\Űݔ)0V d wbLKU8-es4t2c! eope[eB@.&p#T\: 4=No=AjtҶh;UZ^HoÝ%L|0[wGCz]_=؂S;xf =E<\Kv_R)C[|\ӻe{8O0EJ~#)ڤ_FNaSY&25dO9xP..E!Miq./頩u<_H7?!N!' I'5y=MZ!++F^"wn,'xK;!DžLڬƹ7q+6TEx ?ԷḜMp L4Hg1#)_bcƐX%^s / %uWVaK7zw| ?Ô?/.eeFan*O6&-aX' (lg)|A~!e%NV *\N=h=auhC!B8?v &PoѨmOÍär:;ҋ Kd1T#!^K K%}x&+IyFQ2b~t*]esqfAsg٘|1|!vu\f! ttdž Xʔc%؈W S*][!\DKo8l qU҆2K K !}e&nE( - SfsZkaMZA|õL ScqoXrNt\ڗ/Mx|-;$<#G73+)W]o LJݲҷn+@jTC`ikfB0 ~~.Һ[es׌q}NC*c[[)s«pav~x8sH9 p-ǔS8^CqMo%oG6\[H.Sː57=(ڒ}+/`~Д41!πEg% hT%3m^tX%x'`غKY*ba >(vOJ@% E/b $&t` .p c-ZMS(:pK_[Hrf„[W|&-4πh]yp+.%?n3ug`2!ܳ]`^?Ѕ/N,I~$;%^Agg>Q!O-M}HA~ սrr&-Qe,rĤEψ-h;\\r!qJۂ{_G7c젽=D1~3nL\`4|[QӀzJm<^;yOrև=ݾ?y=Dډr}7󚴯\"|Ab&>g1$ x<(qa唴N7N-Ӥ]2 1lF/|kh2_6 cz}3!`ׅU7\ ^5BcC [c I%M1+uL&rVwdMS#=5Nyv&I[Đ~J;o}+ ? {{XC#@Q8W]'[ @i"_Fθ H,ʐ=;)jȳe"LN80Me&Ÿ-NKrN 5t'ۇ!/2:hkވA4NNh#|@^z3 /-X:UgL'=F2KîxykeR%{T4E r'/pFHIW >ֈS&ȫ$țC'o,.%~N Bu^.g}0g1PVqplBkچ6 e!"{J U9}EI?}w>/hC&9&H[޿e $t}T"P0O<+]J6A܆a#Ky+>&baL*0QwY V8! sk e,u ssa'/'qLȉ@a u[ ܪ'h>JI}EI;}Gqq8$$%؏2Z=|zF &6dTY8" RNJ& !Nmxue&zFB>AwI#E@iYdؿu4sWmrD"}7VgK_}S}U3n?&K*^qk7ocw3W>d_JH c"$++ҵ>! )7g% *LvW o$|-L=K LU騆zɩ o9YUb Zh^mT _&4 ]>CGR=[>8B_ ' }$*vKB12Z`2Y)?xXvc_nwS]qS\{Rٞ_VRVjJI 73ߊV D *1 %7LjmUȂ6؆qJ ,CFʷż/g=}"3Əx)RqdR;Sɩ }ۤTRRs\*1?-GߋymZ*mRCƕ?5F yN8kL2acR)Gɑp8 mosm*6][c6ĔM>McGQ I˩OF%i;e$^bpiFyg},݆L(ѝZ¯P|&e~޻a$ nChw03W'RbY\[/(²񘗙홺*jtd>ӓsy=Sֲ۰ H.(gjV>pS_XDBl!63,jXE x" t)Q G&|ѱ@s|Fu Pz+?'-C 4)0V,2e >@HRb "Km{E 1TX\&1rpˆ?!!+4=pW DEa䩊N!G}C**g!Fp[_q:C-Ɠ<:JP(#)=uJc8 #Br:@('y S(aF=Kn5ptjCx3CVBs̔+?gx(I,ssBgIxS?pn!"N0:p0n:y{?r'tA@TFzO3?N6SC@GLC ]={Oqfl O\-,G}NB# Cр$:eq&OڸRxxBjrFDI<#w^C,p:@)'#F/$C.Iqbj uZ҇f@4Xfz_7$Zj-آ!Yo ̈́0^| ;L'Trr6A(DI LÍ0/u~™pϵM)fdH7#я8^1YDTyAWݨ+!mK͠Vx'\7jj˾ْ۟ 63T msj7#O^DAŤ>x~پGΖڼMyw=o~͗u(_@HK^E _y$cSy`H" z}@ j;Eٚ%PDϣ*;|ڬ;Bx&<6vyez-'}~e?' \ن[F=nh7zx޾-Ey B@c5J.e$0$Iwiz8 1I\bp"C & Oj- ħ5؎}~>'Kt8-Jj`gI9^{7Z =e77#[$п޾V.V5O+wcygwR6rNr~b8$V}y.i|!=4 c'L>'H)Q1kܯnEÏړgyd?-9X(9WL| 9 Ұa @=ev9 -0 츥!X"SOX&s1 bpn}+U'?㜖Eum[ʶֱvX8.\7S/PG?}@"5.]u ca^UcdcVy<hl- D ' ^[e =|r[pp7wnӸ&7\I#Ve6 =i^ pKz\6.22*}4m B\@?r8 c1Aӊ%# deV̀oA.~CK:]Vx/¯P7MiM}G /ǫPG"g}teTvde' x%cXW˸,絋oi-+J@}YtJ'qImti2F9`}_V/k^(V|stz[+]c+nho>vۀٲ뙠ཀྵ}@y#+>cK+9YO[8@f ?[ep_ 1HUM~Y*<`T,rNo剀tqZݿ4Jx 2u7LmnWanۅArq4@UHgX 7Ccy_㿵| *<1+k#e&!K-B<7/31g?%RX=FaL౞yӥ3n^ d,V h巹 }X¨Af^ ,>lkkǟJ`JB(O1x*򭕀Q".A=SѝieFy  ||.a`%Z_Đr% %[yhh 64oxVX7t,-/zcuNKhlN֢~!gQ |vo@ϘiʙQFz`e Ai-{v."H_B13DLO"x<=3;Ox$~C;mްmd0@^.z k,5.CxɠB/xL0%i[@ $"h4UK%qx<׷!~$:[7vxx"1E݀ Hg4hP:J-q LRr `O^jCx%0@eKNG(TY~d=mLWР} -'Ne`߷P'm >ެ O4,_cX2ZN^AY3g}{ 3l)\2OYV]__~dܨ#ʌm@x҄,?$d}~74m=wF|/Â牋.ļ^ZTBۍj/3)QR韪NUbbP^ BEٞP~/VזnAȗw3&/Y&h<[Řg{2E؏Jm6V%|=n讆;J!{Ż/0qz[9h{2e^yU7fڋj4=( ߮'ih#=D 1:e{nX*3.( h~~Y)D8/ JXܓd7e n q ]ˏ\G ߘ􍕮W0[~I:߁o@8wrOeOnJ)^vǧ)2+ʢUWKY x?PyOI wxh ZB?ih㣀} Z BOAlPّM|O'+ɊO WX)`2@s9~Orv+l:wH~bMcwHCn])U0;c(#Ix= ? CDޫez~&W>Y=x6mG3br~a:їW8ۇp`LD*J+7s\:[ d ?x}U<$>dnKc.Ԯ#kFN%ho7 Uݹ#@%ȖmY$euTLF?Eno%gFCڡ([*z_`mI)83 SvMW?sQ|sƒ}{6 zkcL{{(͸6a F@0'ð]~l,JK"0&[rw"#Bw8rK81-2\l3&iUU% AB\Y8*m|>=)}i P',"M]ϵeOϚ*-!\Cya0MK?]E^Gg #N0>ZCvtHLeVBl+}e@קr_y$퐩9 ;l"Ҩ1tNpT_n@G{< 6Pa?Pأ`P AT3BCM ?'-$LY (jނW(Wـ՗kjiv+Ksa#LUUq_>@,@x:j;G i'1 t,3Fci #X9c5 jԢDCzawQ߿9n}"s21?}1#N;Rvah3@%dcbߘXqmfnNJ,JDNYLHu-?HSX<s^7j}8q,0͏)OI[QWX|? , 7,(@)!Q""nxJ0<!@S}~mO x|xm mn{RR`7T]/eSB&`-/N<8|{-\q$d:0Ǐc0zʚ1aF]4%&D#ݭU{,CKW710f-4vo3Gtx}":0ʇ^swFQot/kN:h)=M=8꣩wnOS: `~F"@%ZjܓzA]o݆jp\ofFHz!ّ'=pu~}>cabOZbAX8CgA`e0L/uBĈHO4 ځ|- \'E[Yr5>.iJb;$4[ ~q htaW}$hވhZ5סQ{8\ s!() BkQ&ZQ#&#L®)>.? X0bf&x*A__&~Q>0s>&=,6P41Ex~~JkPs&LÉ/W16F\{Yk.,w 3{n:ic3{[UR&C/էE\~X3yڼVR:3{#-ߖ7v6={ө?}SZ5$FMf4f,v_t:`OϦfg5nZ!nxV{vYu*SR;=G\XN46ly9/ڿ",wAL {Wnb:-3dUGVTFZ+vIe,cǥ;'L -^k/45;9p \wS>[$|$8 ?h/ta/{j/v:,oQ]zM ,GsYzАnFiJQѼ޸W/Li?c3j8i7IoӓM|ul߷Gdvq_+i3/pSZw_}0ݨ5N Wv1?>>nۇ߇;žaG7n{yT4͙*Q_(~QCALi ʚ77ac@ 0$rTUğ, ||<||&nev'{ݾ+r=feEDiʳU]gxZ/wX/HItGؽ_ݏ:MJxLz: T4-x ⏩ bC?lGwE|$h~4ӨGntVn߸ָ;o(ׯ\ —-|yR~5#r Lt{e3* /T'P੷ 4e9tozΰ?lVVv@xJV/ϖ}ukk`s-^Tie@F sі\a4RoOA4sɬIM.[ TGx瞳=e!6$)_ țxΨbN'[a'x/)zt!^Gx?zqvR岝AW#bNrf_~ngntRvr:  #Mb-~fF"U*bLx0pax #b@?pip5vYWL䣺/JUrOY|aJX&yTw8۾Oymd.7GœoqCQeZZ94֜㣻6c_rS?g?ȘOvW#1"xZmgI9k#4wk~+yrj?NF{7f %N ukPCxuq^/J~ _䞂&=f5 |? ~n׏=`ۘ)?ʓ3/帧[~͟VDBQ B^,WbN3ڑYL+.O)*] ק/ց,iS/lӪ+8?lߔnJN9]gbp$7~8˛ED|i!e`?^p^UM3#L۷x^"_c΍YgW~_,홌5rh\Gϳ䴙Gxm: @48$0ͬZ3j" a+ŏwm(9-v2vg~ DYo2̍_ɟkwM ntDMDs ||Y==-}y]ihΒIuG0@ -Bϊg(Thc(t`dNT]v1QH$NG@3> @#txٹQw1wDmyc*O;po?A?fwg@qv T#;`ڮjc!w\.L>_ִqLh{OnSDAZFg-clg#Nܴ O/%E,Q*m=r+5ԁbj+skV$5emob.UӼ5Z1OO|_ğ.9jy˰ciZ148Gn{/}#?:Lo?:IN?ڸWa s˝74$MQl_V\o"nUcwTϕs'ǿsh3  Pp7=@"~s濧+F}/+?F@: wǎME~w% 2;Bv@ۗQ]{;V֖}%!17;6q\H(lvmqaUx6aoY.]thwMseb5$@܀#,w(acgrq`3!|AY: S ^63ʵGaBƝ9g{]Y+sx m^E F8s8䘽,|={* (M]ωv]yF*C"f4}]]Մ_!Gd[-Q?rr& XW񏬆\{ev+7#@ztN:heEvB/+3` `7?v{dA5|ml9;sixBڟSz!ț6cȅ%t,u;Ʀ*T~,?CrV/clS@|"|jY Bt:&;^ Lx|Ɩn@y)=kY j'ql+Kd+MA`W4., +^X-\@Lsf =dئ{D yٶNhA.I(xHoFc` Xx&0 )0 GQ(r|ng1 s"ݼh &Efg6aOdg#9A|P*_AE")m y90Vn0r֟DxE0>mIy8Ret͜M3g~|f%F 0w` z~`oŢ.0)3ai=M ~Q':bƲW"oau5o)lYS=ԞߨxsmM~%ߋvHFS߰OXz7ebmMyLUftC@7p[6f\[midkO8q"Ez#\qζƒ7.ߕ+=X!;KFhTkzZNyu%@jLm aV}󬱛nuD.ݔJn*e;oŋb?I[Y4`~1I<ꃝQP:ƒA@bEx~Dh:mX:6s{eɮQ@QNSs"Q x]BR}~pt+nJ^z;c}N K| h?["|~nj;a-sKĂJ,T==֧@.0~`dzAp0+#fl_! ܞ{{5۫ܗlԒg!Bu៥3& %tτu96ȇ3$X/tPpX^ep:1c)ǹO)z`.!S!EH˖! .lFV=e34Ս^ >)HSp=#҃H"s7 $eQTfG|gJacS6Д{coP륎&!M@\ t2>q;\R|!߂! y 4۟˱愩Y_.n^v߆i0NdB:\9dPoDݻwoބڲt j P㖱u.tDnDT+lpMTIv?K-W0m |]PF 03+d) Euw⯏m<$ՌAIȨolnz_6]TRcOB1M[xxxЯ0B##Mnt:N`TOD|;a6; sZ (S:=r6 CؘߋSo4F.F7龜vZV= C01pk9 `xKߪβ_CO><E1}ָ=i}6+"|w4v&U﹨>W&[2}ٶ7/:TnAJSA+7ˮĿzNK @w X92)8ܙP/7g*Eyƺqsn:gWøv~`,W~űS6 MO!Kr.F˲db!LA3r^?`زw{ыIv"<믤U. KAѣ'EY~0 guY4[Wʜ~g%@}7gg\sÓ>?ṵJ2o|4:.;f}5v@ zQOG> ]RMqL˲;: YA]6¸dd8 {(K,Y?޼?y'8Y`C]j缻w6Ӈa[:I䞲vZo%!Տ N?~~˟(YF S>.@L^߾l,t72u8ן#p΁\:pe9< jᾬxzڇgo;n2p" T&}R}߂#.. 1 mD_& DPҳn_-$q,?| G yfu) +P' _橀Ez~Fhj݄Qu=e P=YpmBf[0No#-/8Hn+eB133*N ݬw)e]gq2LX"+[Sz7vKu>E+4ѝ;Q^a_%?W[[p o7=o=]^#>;|ۻwoYx)eQNz"xu?sW{K{7WJS.tj2˿z Luμ__h 8smoc 'rY,: BkMhl =k><o]ngPFsў0gJ'mgכ}`쒗H*tЯpDYDT8 !/P cM0>?w %p˯rg<0@ȿ/=_zxP#7,R/ğ-잱^S-\s:9?ةݳ?c43л2#Įpv_+ЫoWu#uVSu H;˱aEٮ(~[<gm^aMEXloߞX?۾p؃ rkv\ ,Uႛ+)s]a$[\Js,0[˴x^7og.ꎍᇁa{g"ͼuO}y<1#vIZNoQ$L- DPN0 uuxKli]h*+)o,TN'`0+nM214і m3D*ԻwZoq ~۟7Kkہ1Ɨ_|[ U5o9Jq1XTy?nj).?LYWZ\V_ia<:>#bS{mc7@ h)1-~2ıv|cDSǼf:[®ug&W#;m>I_so+u޾JDp{҅(DLrS,~zkW<Ϭ;2=3 /Pv@1Ha([(ۊ9 U$O|ݣ@UM5%|'҃a?`Ḁwĝ w?K0_i0@8P (f_~u`#W;C.ҷu٬&}c|H8C symxY,m_1Fؙ) v{1k"*l#mhiRRH>ϝ]nPE>3؊0P\AB/w)rBMߚbc|VS޸lҧoYQhrKμTe"4P)j=YF=QugǴ REcwwQ_Cni&F'P&Ѫz~sμT6;!lRg-/ |oƔr\ac?ߏ{d^0~5 JnT_G#jC㌤ ~  X Qջ,myFvu; aUgev ~bFq?l_|2E̕%0]ϋ[u:}NMJ$7|Jә.)L3lw ]hT磱wbTWJhpo!J^N'D8l0;#'mw^\d҆n=óZ (Wzө34tt.RZO=Tv&pٔ8/?!3+k^G%ͬ_Wt`#=1)+15~l*t!:̶U(uYTyl>}7:+rt ҕPjٽl>qN>Fwq~Kϑ%h'{?P dZM~FDW&_'uY!$I"Tt3^T2|?ifY/XW\_u78QħNz,AvҊvہB;z]v &cbsN qzD4պ/A}18[4#e2i}NndžE1Np!-k> T]qnnl#!W;,:B\[T> o\^S\k} W heR' " <~x8hU1,;Er 1ۊnsExg' #崹||^+3W޲JGXSʲfP# q -i2d(^NE+l;),'%F@o[  ۓ(mo]Bnu Bًb}%~TALS`)ֻ?6<_^ vm*ܱ|wUџaG `H㑮ڞq܀G4 lM^xK=Ne߇;UզUsnKQPJJOng3D\smVz\OvjP͝XCg>>Rju/ zsQ3)-곣0iyPЀ` ~p~ ! Yv/m)o?y<"mȯ8 kT(JdÌ/َ21695h;~1\#djNlƴBXΐy;'>?~AOk ͳzǿ@3<Ӌ |M},B@-cIyj{"'n=:ZȦϬ|3cpz\"a!]"{0e5Km/tNkO#~%yѨI<Ѵfg~hmtOp?n릹!X\*zz8d:Mb30tF`x>kQ;ӏxC_makuj?^W5s j3Z~ζ)K2@ bC ?ށfa˽+z ~+P[|b5.\"[-Ӻ5/lK) @k;pXI5e0k}wD^t/A_5=roe{'mg- }1km[v1OJF,qa=ܿgtze7⭟޴lw\lϏ&kj%(BGHDuKtKG&kjB Lި1ZEx'?ZW7&?gr [Dn^yo߯9/u’3~6\nm<9\C :oqe 1_zǞx߄=#Ыf`[kky{Ȭ7ŵ"NImѓ?Y)Οwĵ:Nƶx@{ a;f07e0>5ߕ |s◳&z,SĮ;Ҝ4*/6{bl$pG?[*_>qn&/ce"Bx֎4gdy9坍~>6Ӯ z?/jS'5;|f(Sa jВF|퇣r?keK<+iSs:m}γOtG<T<Xjd-IP*j^7i*w7,g|>v\Lf^U7J=fW3(d K.0}i7UP'Ϳ7cpk=` tIϳWtHhk>w&i'53š blX</6Q_8% 큗eJ6a_bePQgSt&c (J_6wVu<_l;,&uN" iϜ~_e[C|S`ʔI9Ț|j=gG_ğ!qf^vc(etV:HcUyEVxDW7FbF|YSLx9{ ;{1Y=O"Ouㅯ"Us |)e9+Aa\ЋL?'%: NOYR>3sA,|A*L"|i0+~nj-;:oO|fY٪RG{~Wr*0/Hnl ޣnάUZn nKR-> JmڌIq{ ߚճixwI// /k N]ٍ?=v!}U"W5a,%e=7;>Qw:#QjnKn0g> S]4Us- Q?[!g={z1vKa9<0(LEbU翅b (T]6kOZ3io4f6D2|YiBul"d<(Ρ{&$/Dw-:*-w vF0p [+ެ?oP"~{i^Ctى2i}=Ȓ {Fܤ( :N\a O#lBĤUJҟʂ?ދΌi4nS^v,h#GJe-6l6>evcK ~DӯX3!Mp2o&bT^埫 n:*dѿ6Q)5_\U 1ԁ\[n 2u~};1 /#UYvD?( t6'ܴ_KmR8c1嘽o83kR `oʓ?)q fǀnN_]7*3#&)!%ܠY?kEQ=u Ev*8DG󧓝{ps0_2vW%8?V~S~;kMtWa:5\Qk ; Hpf-s-Bxm*&( *ӳy殟EwҬ"_Q ˤ_erYIeԠ1HBJe,nYrS嶑#P6-,۟ƨ%>NmIpSrCy,3A^xIRסXR&q„fè/=4՘Z<.G+h9%mT?'G}QQW(~K`2-y0$p]|h%G7ٚJ3v߾ <LS_D6b!(y@''}=NC~F玷uǞBuo$P;?P*/$fYB';~rCYN 35V݋%)=g>`;8``fy ~~//G7kr<`9,p.f D"V iI;Dz @f :lG<7n8Aaz:Y9Ĉ#@, q>Ef2a*\RB[ Ubwo$Nq[0c'$bdÖ-3+AAN~phlr}{Yhx/"Li$= ~!:/x~@ϡ_-w&r8;N棆^Z0n'ߘd@=*֥vydH{G^ޜz/;˻l8aQyZyMcny3\;f4cWCM!Zn,t#k哭}G~ E]5K^oV$#5 &N7ߗq]>fOmYKr^/t>{T[ Gڳ`hFu[&i7!˚DSfv_jlVH 뛚./OWN)8rOHdUXH&eaFY־&|JLY0 yO@a#W~saoDhCl:7Ğ`7Aؿdv/mȲb3=7$߁vN520X>!3 q]5 _Ao^$}ߏ_W6g2'[4>ҌS'1_gO ߬^vrq|koEW G T2cLk ]tnuةs׷ߋTW#:V}>)=۽1&_K3 l bB/Wͽn]_5>GL3z&՜EimOYeg"`cO S:Ay<@#'7i?S1i]FIR:Y fd %[&Cϟ.K9z.??"}_(Fu^.~D\ -L:Aabw>rygx[<)1Yٝ~?mG ꩛!^)JZ5VJ wގrbbςdxS"B֝y?/z)w]xa?u3GB ӿ^<rYk:ikRl*z02lfDV03Jo8T'-k|>˅.+>W'0t%A@$ *H?PD?^ctbS/r5Uć;CFgӥ [!߮/u3 ɣ@v.[H[G)7 oBNځg7 {uj_01/Ɖov|Ï Aٷ-i,VgX-w?!DӾW>;Wh_xec@{F?FU`@ K&Ù cS$rϚy<(̺҇nc2}!aM۳&gN2rK1M{Pamȟ Y{y߁xQZy gw#nV:p' #M0<xTrQY#oW}_lB%&l6Mgi LgXs31*geӑh*\>P'7Թ? y { AXrpᬱ V4[_T=;_ԇ@Rtq}·X=QUmgV{៸@N𒐭9pp|+M4̫Y |~? ^׿9a' ָu^_,\D(+?͐?=o73!]Y7;؋|w.3^q63a]W-ǝTϯ&/]}vVr˽Yn= jT68\m^u se`*==&;SL#7~~gϰ2eL?jᾧa?`,qsGw}mycwm>֯%oQ7X@tj̹;󖦕$D_I1*2 LË+؛豅w]乶1ɧoQ˟R ](/fu{׿_z ~_8eXATOwrd0<.r;E+V\"b~>Ite5(WT8t~~wnSufr-* A@hZ#R;/{( F->_96õJn{j]sǚ~k !ZELJ:LzJӿNW ד @#Ab{>_N+Igוx{(mBn??>ğ{YwUu*5붽Y2 $?[!3oC%nk7ўSN~deRI7Y.Q8f.i/Di:^ğXLc֘e@v &K*;On$\<w凵;LL<,WoWKapM5#zp4y:lv4-;g|wCM7o?|d/'0)>Dt}wJYM-l+ X;+.Z6~^2J6;qwn_T0 7jlEh闏^ W'i] z8٨'>>7ku⽎d_}(xO 0><<<#SNԯ5/x3v!gLs'@?|b".No8H]}al,~m\+3@* <-nnJ\|S>S0\[ʻYw~w݀v{ˀ6.r#ǐ=I96'%[?:Wc'~+tKSl c;R'pݎkl(2 |y#{,} LjZx[X+o_XeY0PӼ` fr:ݿymwNd.081OIӱ21^x)0ўdouFX1Eg"4u kѿ={N{ {x7G16ތ6L~n >% Y<Nǀ`6 ֹP*BAO8j00 J{+Md{~w͏ e=?%vށu5~朗y~LORM5tV`KK.l'y3Yd6g 6@>}Z>l?<ß/SڐT3 b"#?DZŖc[] 2[Ol:)p$"Al ^j̉gmJxp 6pE+f3[ k09o}\H':_hXH7[nmABƮK%'q(x 4y[-,k%YS*o))e"?g.Q//~:wj)JQ3nmog9~l1 )•f{u.ꛗ5yZK;Ö밵G;__xL.S4+n`DunNEX^@x*@fyKS\~A&hb0L%4d$̧M^Y,=Gi1PM`]"cVK7?v8:GGrݱ6:hRyOCIQ2洔ea+t ;pO|R Ywf߁OkD-L=? h^A_)zaW8UnQGC3>5py}?eB{gjI0t5>P$-ZLLa`6bzl<_ǔAi%c&&' #IlYJW>i& f`7|BeZD8dG$С`A;v/ڟa9B l5y'& ^ L:&|Gh#ӿIo`E=XNlye=KoBwK}lnK/>> 3epMsb4x p,`o2A^IENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/icon_terminal.png0000644000175000017460000000101500000000000017556 0ustar00simonwheel00000000000000PNG  IHDR;IDATxU@=$Y;up_.]K*]G"nьC{ tUý9Nt:^<`0xyLQ 1(E9>!joW4ym8[.8IBոv5?8Xk1`_[<h *,2=[\xH9F#c4@+x tLa3D&r҄;Wїt"un 0 O'41#4Moc3EY5O*JX:k%Z%\m hp7:̻QԙYREu,dUՍFvzzo^#?Eˤ`lnJ։Ty5/6_ 89L(-rH(|O&^yw˻$yBjh?|||IENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/icon_window.png0000644000175000017460000000012600000000000017254 0ustar00simonwheel00000000000000PNG  IHDRaIDATxcO . P 5`0RWơIENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/icons.svg0000644000175000017460000013555000000000000016075 0ustar00simonwheel00000000000000 image/svg+xml $ weston W ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/meson.build0000644000175000017460000000106000000000000016367 0ustar00simonwheel00000000000000install_data( [ 'background.png', 'border.png', 'fullscreen.png', 'home.png', 'icon_editor.png', 'icon_flower.png', 'icon_ivi_clickdot.png', 'icon_ivi_flower.png', 'icon_ivi_simple-egl.png', 'icon_ivi_simple-shm.png', 'icon_ivi_smoke.png', 'icon_terminal.png', 'icon_window.png', 'panel.png', 'pattern.png', 'random.png', 'sidebyside.png', 'sign_close.png', 'sign_maximize.png', 'sign_minimize.png', 'terminal.png', 'tiling.png', 'wayland.png', 'wayland.svg', ], install_dir: join_paths(dir_data, 'weston') ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/panel.png0000644000175000017460000006616100000000000016047 0ustar00simonwheel00000000000000PNG  IHDRF1ql8IDATx=[; ao1*J4J? c^F189e))?ϟOڟw7=:3^32kg/9=ou I(gߑbbvazMXٛNVK>qj[AqGL0iDXj#3%/ubfó3۪bOD7Dnvgr警ɰP\Eڣs`ՑNNJ:O5CB"*pmtY] OFeڠeÞ7s6aYCB\e | :TG/W5cy,J{ סH`X|2 :/ EgUQQHׄJܳ[¤ߖ\h: @I0ԩr˘m#2빒0` `$ݢwS~J1RI`J+XGjL&9;''W+y[])Ƅ[Dnlz]Px*kZPA_??}Bj<-!Lȸ^-baF.G!{ [S-yؽqiYyRhCɅeʀa1\``;sg80[8ð|z9S dFвK:O. 'Axh x0)Nw5wП} 3܁nu-"V/mM^\!؍>7t8B{7xm3yؙѯ_> \\x|"[سFEت,n$"NM~7̠Y8F"*Z9q溊7pdbgm"si/N$]Y`Xo1kl:$͓7r{awMY.Z]m{BX[yYӯl0 Ieߖ$_e|!Nna4$_NsdmmWAW'7@$ݥ!m29 (zKIGarMf58b-D\ӻQGs\'kv%0 0a 4{!C~I+Un/45-]@nd*KSᎍ7H7oP* : L/ yV텅?/][kLN`P _ laP%{ .NGA{*V v[1g!%#^BW2x5Ou4ΫZs;EFO Ӷw?M1p+I^؇wekAxyTx;L)*W_'3?~{~R I),聆0i֤M# tT!]v/QZ`~.tnUu9*DjnLj#4I8`1 &8)Z LR,s\憃Y֏ad۞ ߪ"t+V{VcKvs: W؈w c+[n;\ƚA|g t k+o{{x +fN\C828Ʃ x5yuśp-''E 8 `s`YH|# U#va ?ڹ%oFe(` :j5U$KEi'>y?<3=!|*3ɏ d`$P|Um|@|聕/k(%Tyk$c1 z9 lT1 RY'='33=dFѦ m&A4]'kxxFIg)a|HISAW q?Xb3Y (ܤ~d[l$@i>\jސ,DJ@u棖+G%Gx'#EZ,\qlr2y !Ѫ(Mu(a?w:C2y;H)ahܝ(-cP#[C씒þ H+-3g2Aڢxu[zka ]Ga!|97?j]pc9X+nԒUQmS Xnjw9UzPg:P?&v>TJ8UΙd>񷹂JRQn/vω$A?DγI FcyYvH03`n1A^Li'*R^Mm@].]!|Ju6= x.V6Wi|hBn""?1f=_K@d.k-_Ģ€s^a$\ Sx&YpjXygT-4()م=ҚYkE1lF5%DR/OxhpȞ^ۤd-u:a`̺6)5TA 8fDM{|vӏ=tKfkÌ@,)məoZؽB1F!Y9WwM{vc[85~??8|cY5/k3cՅ@5{UD<2x牞ɸ]MՉ§Ki/ H#AXV_n'D$O،ћrpp/xpI䌙w5y];eI"aLE+Jdl9KcKn0Cz3Uq,TGw"OE:B0忢眸#+) ńOmbEkySuXD1NqC.ꖰ'4\!DbU*.8ig*t%K.4>^ui62ğH#YP’B]nXED^p}rVL8.L%fV ۱79%;N9'mt5߲,֖eed3@H*u2/23&Ei$tXpD}MyJҨuɇ93cYvY5Ԉ1\2fm\2mo̯~_oQaͼ?yx-I= <1 8H + @NLP*:f^JJ7EFe+L6'@V`i,AB4"mM"d3LM`mz6" ~jdZPbH|(cYv^Z3B`d;tʜQkEW ;\'ZrBTšyɘ`X5ዷD qRpHSRc}1Ry8@&1 Oyr(ǓmKN& (cr:Hg0!AɄ짺X|j)UE[] l>L@"ZGB#J@K!r+LB I/ ɀK)㼆d!:kOLD'9%ɿ{P%GD4,yZl':#-4>_kg?]u@^̭*+Q^,z$7f--RGAS_I\8HZhoQ{I Tny;v4noɊDN=ذ޺bKLgM IlR{ sq$pf8:Roc(ǥCiX#4̈qэșg9fBMtnQC v٭N)͢]%fbavirG]5%do]t"'m#cH"+n4KZ fзsa(9~josORI#EB-P5|Ͽbos$^#<-Xi瘠ϳ@*4_y .R1n,~^&<[GjLohWAW 5CalY>+~-*(v;=B\ .{r-MaCd,W, xu , ҁیRh5@&u,3sƒ}vOA0 j>#"@l֩e>}%F'RJv T9\ЎϱiYuX.^,Ywmdk ȣrtsOtpWJ3>ѬV{)Q䫢kx%.Ia{.-]6p`u /g䂚}5DE/\|3!~!R1:yU |cWt'9hYz٥LWiMkQP5#>R=?ނr=I5@ӥSg#k?S 8Ὶkt|t,:et^jc 8ͽ.m #G_ 4 `,hze1쒐 $=%.hx $-%#G84r r+vuX7qV8;滙+j'X!uOᏀ%x:Rc->k?՚v#^ o# ؤm %kCb虑 \e%˿CH;OIf Y,VeZ|$mp-FO#e@O\3!:$:z8k3S0%ԾD}۞`-:;ZL]ZF)ev>C"L*OY?_0d'ޤ ~$8pM"ekُw:.䴇 V-Xd):qw4NZ_2}mwSWa4u9itfF`s,vseM3 /;ȡ 倳S0} y[OFP si6״;+脕{RO$,s[s6Rsb6,CwdQH^YC:*8orz]AYaQH&,l!dDpL8gfyX( G̢g o}ISXk\.{ht>Vi%nXx3/$T OD5(Ma^!'cGjز*$m9_].~撈۬ uӬhtƎS/3u_Wg0ؼ%h^ DG!:ڙWJHqL4]RE &אy> .KÇNu=+A;,{K} ļ>;#kg5m!=^s+`G,놰/dX Pxy.8B.Ȝ Hys,Ǣy7y"/d;t3ڃ{l9$ncȍrwzӄ2‰R5~,*H`ki"UKbеn),ؔn%O+l_ݒ'B>nsw !ƚQg6 c%eo.y8jkOv5 5{$fCvD폖>vP5:A-NB3ke'c\aٶd|$ AK NxjV+Ngщ FtF%^[C8|p*?m<` * Un\ljA̺e/_uD^aMm"?םuCSKm~H\îI?Y,akؽ˛gI I}d_˗ {?)&gkѭEpC%̓"["Kĝ ب%cYZ6Ol_)r,XT'/^ n PDKk]Qa/fv1S/1 ^(/d&H.NwZ縢ߟt{.t 5݀uҹoG8Ѹ[nkU׽\_6k.7 {B;{{v,Vn#P`9`+H$E*;܋N%m*Ws?E}ͿXOnuǸG,.rf)%>Bά:KzZxQ >맘u@c "L2~l \M}|+ .AxhoRe9WGVjzo#Ѭaf:tc5n"\8`m=YeC;T1,1r\x;C'sũ|u'~SqۥcC{զSQNj۾6kM^PaSR(P0)c{X4,7Rk9)HlRm3JfeR6v-]W|]c; ڟ& Р1Mq(\7gϦf:mI<ol7Egt0b֯J]7sY6DŞi\ҙ#o`)K8h@-(Q p2r7[ vu.B? _u"!)xz^ߦ)|z]lw_MLjeor[6J ~or͌6ށ/w o\w _ DS)Qax(B8ȵSNӊff8 Ѧs$7[L^n;J/+8VTdZ8ћ({ލ䂩V;>=4 ]ʚ+"xAr&%&+}|tcvyqnwdKi+I3Y_zyezѧO-Z_=U";S4R:q]mXNrvCNY+N;i̶M̊zt:lOLԃ0յՅՖJduϿRd$5P{[QdqZF-6}vŷrוh/b@DZ(ʐ 3^D|/1&;)ŗ% 18$g,2)N1!&H@k\p_W~$C*ڌ5_j2KJlpvPk h?> ͼ?UD(ʂ4,-C بWԫ<~F.sE:GnljnҮCn]]Ms9qM\a{'qzd^ ~_kflpmp?O-ς^뢴%mnWז0kgZE"N!?Oh!4gڈ7p'*,y[yZ,q@R'Ew:~Qɮ-o'x;4ͪSm-jBp2<&v-Ύ mZgvr=,q1z)|hv⤯ԮOʡWZKwLXv_shyثoݧIe!rf ε Hnqֿ 6 ϭsuk^Ͼ^ߟ%}ʬLsE| 3P˨LeDD6b? $LZe6m:L*'%M#'Jͽ6ҥťܜδKrU $$ ~!D_7S8v-^˻I)ʕ^4XEJeG:toe+վzJ_Yv@>lޖ e{]Li=")'QI;9WNӰP`cY5\A8P?P%7E^(fk3+G};wF8(S|Z )e~7Z'] `C]E(AUB\q;am@>;rqGL;Ku~#8:]*4)j來MqMS4%OY d 'Y>O)G~ـa_)$x zP%8q/yx,^\?;acҐ<1,ذ8N"+f5>w҇,mKZ4 H2JfY +6؁]l}rᜈ˴D%Gԟ z>{s><4=|94;Oӹֽ~;qq`{}y(dpȶtQuJg᲋$A"(C5%.I]XЀd8R^Uͅ#&'-&=|ɧ&Գ8rEN9 wszf5z#+dE9QTs00.< *G^ÎL9c+s)Ʉn?JٍaetDOG#TYuã%dS(9мW۸D ݗ({b_d 7JHtG1)hj>OjkCgLd|ZOs(>~UGC~v:P M ut+>PP& fZsZ2˭dgOڰdYբ[ݐ/4kW`Kr!l,˻TUN*_^,gH#R 9֚nTn=ߚ*%&|iC[?K$fSs'&P9gڱo&Mu uXRj+իINgW~A^h(Y=tȈ?&Œ#~ ֢83P% wZ| Ys6_7 =}ޅzm_gs?uC`&b,=Xh@se6wQK[~L>p Mme$ &@ľ}i%}FVV'+u5|)|-ub@PbRdE4^Mr`E?LynYC &([AEqɋŜ}w&ALO &C(ykO ch(+䀵c栗:QjnyHzk >BS[}r[bRAIT٠d0q~77Ti˱lbďW@CSDc6_AWRlwL D,!O5 ͯ0,hNjΘ<%+b49JO$JLWB6κ{w7M:@.&b9 ++/3Fx8ct >dB ^87i,dѣNI>OQh}?j܉qϤa<>, ?uwsM:s|\ߍwd s-.ϞqSV9َw攓1VtQG2V&":} f9|Nϥ0W׋|ծY\E& 9l}զ0G=_T-ig)gt$٢ו] TOY!~)Jxe턃dKLl"Q¯*'Q ݎP o2(g9@mb`W Ұye%txάd+y+ uۄ(PB3PEg%Iopnz>J Zс%59ɪ7lGw%|~ "͊ƖԾMKúA&3c8 ڄgP]&OeiS\u%n)"&g09"MYx&sԓKءa6) urrNAOFV}9X+&F;5W6B9=}]dRq2f ?ʄ;y8P_JٮtJxϙULRV4% X/!3a VƵZG簠ϐÀm7,hԾwAi,}-@v\O5!yܐ6IV| MyϨ<ܪLO[ӛW_Uy^hP:MnMuGpҝm45%-{­2q*WVUM&FGZVό=CO? eP4؍@Imr(=uZ8-NTXYi귁:? M} %Pխ]dcYӛnߦVp,=KaI%׵bE kf8@'s;Uա Et{x5zs^ٝhʟ=S2sXHsف־>n,#ԭkhYG=O9]6g+GE>q1եuLMrF4<x: wLgxXr8GtH(kHQ;AU~ofwaw0ɞ°腣}6#̢%5'-H6,̪B9ȔN,0F (s+QIQ'X2<,0y$5fZ|NHRcu0ؼO#h')]&qFF`hؒ߅%G`) 0Aǵ2Ȇ]w{%z%HǙWqZJ\vŻ@k 4U ŌZan8xj -/nJi<9EMtFfTk1pnBo(B.t;F|*E-.uSa\1g L qÈ|I= A^ww|ᠼf}ʞԽͩ фWC71bY#k*M `"k 7^@Xn)<]Ya,2;a3BfÆT1m"M%/4$:)hI">r~mH6MLzcDc8<ʷ {;8;WEDk #%@Cu%Svv^Yy%Gh8P(36̋ɿJ%Mg+gmGGl)?U[^ݒ/3(X޲ϟ;kT+Ϛ~[3JBO&ҤbIHXo(l| mqJYYF2O!ųcv,kC| 5󚬲C밻8|4,r=JP{4Ⱦ6ͫl_![ro>{+AeLSݻ"H 07zy Qz(!PGѧw 1o j@DZ7Un3'# ڢq)[0e3OBJBi IEuN&z5>Ro%W~{흚?{¾Ue@@8,Q$3j,u6o߿j?zsyάzl2𱿞p*IE{^hWUH{l@Tt4yc`70+d&T Ϗn[x;I`+djTZgۡeL$b;}ùWT' OW*𿞆ѳMt_V{~x.A2GyvW6 X -ז%0ßu"z_j]r˄.Fw2K-M8qW'H0y8^+5 &PJ+0QSނWw@Cݺ Bz7:bjk\E'A{dn[)|_rZth?ńN5h VS"׍OW_hI*@5U 2yNkQi[EmM.IfDb^BWBs@ ;=kl4nJFaW}DsS)*rM4 rj| A; x%q@M@5d/lB3^צ>P݌=pjo!72h=n1j]!Es*i+o׾q3~r~qFSv8; 1|9evn9FyB80 믊jxB٬SJ( ^3n 9Ϲ[H$]5M<jkyMѩh*SQ>=3ETPPbG$؁'۔<9\HNvFV9lÑ|63Eq\參n{Ě 1SOG;=bAF˘zZS7Oտ޷_mB*{c78 M%m`wm,Q+-XR+nxIfOLfܢRyMo5uՊbl lLXf$CiB-Vhk~,~fGʓOG~7\"yG.56!2E}vcUw CG 8)up;uV" !cM6-. ' Ęsk#0R3 E(c,{ZsS ݲ>T49N81nrUE?^BH-6*B 5l؞eGk;+aucGS5A'ͩAZ)Y}84淮nMfyWn$C(N[R4;GꟖ4E[PW )>}TF1ӟ:UE*׬on{hڝq\m]&|ty/?AicX]8@u$p7u}}]}5ؙy9瞚?I'Y7v&cqfq[&zET`>*թpq0SRxpDZGHH/ofOMaDdWP ]3+65wΎBiVxf BH]Pȷ|g7.C" $F6PۋՃN*)O šD씓_޻Q:v݌R Z,xʤ4"}7 fQ{s7]JS9v#QMM= 2TK l G;PZ./F _y!k ؀>hj]SA?RwGN $_ _2#i@ZH Z u펦@HZ޺-öp.#ZPvU ڐs>- f\, fP [Fy9:[/Y` _/u*9BaSWrA.KN|%3alw|_i ͘-8^eq58*&(gQ4vl9Kڮ'~aeDqK 4zr@m#'na7yf O$o<|: ܸ#]e,=,MGݹ?v+.9{T84 N= [[?,&;?uܪq#j}57*=;0IJE af1]+* ..*zQbFn'{!H;)~bj,jO 5 ~.37]./65JM{$u%L{>N9w^oߗUOƱΖܾx#:fgl,9ؚTmx!5koōyv0#13Gמ(_}5w~By,u_bw C2_بo=9ٔRʦgج 9psxA6U#2iz%>3֑yÄ;J4ozmC`/ MfNR 7j;A{A\iV dFZlk;dyع(]n\28h>)ödKEƦ3x8gsJy9pTjQvZ3v*CĢ_e3X4ߦ8D9%W;j*`i.MkI/W聄;T{QdFѸ>?KʮPp̝Ǻ+vG\nO.XdP^4ݵ&.AK۵li7=g:!$c5!5[o؀ƍ3 %zX%k7\&kݖ9Ü9g^ᯠr໥6 KȭemF|R yuElQdPvQҬe!WЁS7'BhQWz;2?4J_u/^}0[hU9M~%mBnusA[9F+ycm8"n dbu(w~:^!O4~=(Jsب%V38ۃ O&Xݦ<=o3#߄p&jK]Y3I_ĝ a!&s>IoEcXI?" ef]j8}6c`}U_箃:|+X 敀~ ُNjH\/m8! cZbdA@SQb3#2O5(ڌ^@+'%5pK F1:N?mwAa s|Rtj(W-;|r ̐޴|BU҈I^%NLfJ1 LBOImz ʱ2:~m;n` 9i5Ί_foƯ-$$7J;-^)YJ ݓޯW'u%uWyO$^W{Bip=c+,M{]w 6J8_Lf)=ӼX/f|i9 <Ι&,Qѽ_i&b~L2gVˀ,.ur#{1(ibς=JS]5!w ^he)6fl1==IәvX];44cg}!i,;J qI?7bANIo*z3 F\l~C5`-A b}&;$`>d_ C5| P;3.+s+4o4U&3_CAO|^AOŒ:(Z $c-aʫ/ Yc \iSL9uz."<^X׋ cx,>i~(=lfwsT 긃sY͛U72)θq`(HҺ/ %.]_VÌ4TJVT6+'-C2*tG\/`೧K'f .Q =vKj>]W7'<~s^1"JA35f5dy,Bcqګ30x p'VH0$EJ;4{s8ŒR @XEX#PgcSBiJZf,*^U cz.Nn,$ÄnOnS$v9*gUSe?q|JA8%W JEάt0vQiK~vKogq|%dbrxr6xZ'p^}7)G=T|BٱH0QtFmWCTdYuլ֓7«8A "\l bϨF3kX[R38Jn-:/EY?`F= $d>e0kD!?ZY*PsfWe_9__)y)-g7n;aryW2!,n/QdŎE|+*Hi/Jo5`Z=7eb;aˇ*]m(G$|rh솒 [6oî0C<{e~7\|Ψ:oͽ]wzt-snBd$~_7HJ>hIﲟt謞uYLp:>bC{MM1~wx}bnoOw?ˇyٟ]Ks5>A|XXvY*TK}婖o2~ l(KS_&Ea4"~Jl0(TQjjcyY*]hPsⷚQ/3ƒeKsA qHc'*akRp7$4v[{H~IW!BH%`ClLY$ <%7NY-XitLYqI%MqRk] ԻʷcȄ&RTcf(vX'w;(|],hjܸls@"&@]?F'EMeO\>~m'gHSo;)R.KD8ZD`{z >dCNШdFHst46Z0T!4+rN.U  QK~ Uz c H˜1p^ ab 'V"#9|K6tDuw\1K#|r*SGF ;Hfh'b[08jˈRuĐ诶'!PeϪvV\s't-꒖&zgQվ3T'Ի%U{3$f֝LZC;"ok\c~Cåum߷4a9sD[.UN#if=F(jq|K5h`&tѢͼIFTßq݄K]˺2<= t~WF-aa8§_a e!%8X+Q O@tIgy(dAXlAp斾Jfsb,gˀCۋɥq[aOy Ct7I=J_هhW͒(JJ#19hȰd>buʞwE*ͻ1to_+g{bY}dɭ?w??/?>AH;!g:p~@g|;ޯ_r+S9s븍}~Rr%,GwU)kGj (Jnh )UR\UވvYղT@Dt%BEU-Sk8vۡ&"f'RJg@ʸT^-r]Uլ(P5kFHTmTOA!sr<1XSXF9X.y˼$ JGbYiMVm'2ZrG;)]:ŧ ؕ 1>)QQD;}.-}>(YLou{؂EDdqTǷ,ñ+X?^+nXׁoܔ#!?n!qRhK|bn<|ÿK=>Gvi$gr"[*IT $LGl5^QL$_Ef#ÁdtF䍬9()h^iY"eT":jeOن8j-蜨R*&0? FǢR=X 1)"q7h1Oyt ^S̜Ytc6jTɬ8w- zRk6tIa&˟#1M o*t&8+9O8AC7]󰖊/1~Ns Th&pˬ"CpKΔs3r(G5yǫs,m>yrlwq#7If4of4_II4YY(=`tY>ʟ1ޜpvmk MÖt 1 CYeZz,MDO. 0~ߒ0/3KJ8] ͳVaVw|^x[D(Wcʣ(JsjmA4™pa?y*WKUWnLgE 3(shfVXW-FpOTъOd8{LVmv“H-3҅,z+b^1k\cw+b*Bs8s1ouoK//?~zϫY֪9*R<Z;ؽ:H /?o#9  -sZ s-iKbB,dϓ9ML*` 28[d^՟,8? =Gۨ0l*33'8e}يٟ3߄yioh^gu%Î6+3< q__gZT9B^n-IENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/pattern.png0000644000175000017460000000147100000000000016416 0ustar00simonwheel00000000000000PNG  IHDR;""QNPLTE|urxu}z~|y~|}vsyv~wt{x~}{zw}wu{y|zzxwtxv|z}{yw5IDATx* Eg]tl%D R1N-5I h.5R"kw)(bpCEHTKAhFٽq&jNiUqhTؠ qN*S_s]F+&n#MT@b'/SdGEQ ncڽPp?/X)_E,kq$qO{\+6P mAmǚmШ{e }O[YG-ڞR5rJfRb5|W^>^p ^cW4b69aG urI9gh77P[?{0yR+~n܅jI}sGdIq6.؂cC%"kMp?7h-\qͻI@R1o[w~TNbP\Y0N)g*)16q:Pxn r©,u#}{arNM^È_@lŨ.l]d/[8/pn%Wvܖyroћ? hI=ן.]SJk1s)3V*?Ҧ]3ƲcW^k?_r7Wگ ǐ<13fdVpuD(-TA [IENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/random.png0000644000175000017460000001071100000000000016216 0ustar00simonwheel00000000000000PNG  IHDR``moIDATx\zۺTsId{=AN{/\eK fAf2Z|; )`x{x`DB7/CeZ]ߤ,Lq9=5`ՕQ/IK@^?/brhbMPzSۿ+)KC]̈x@ˁJ)u~/^;>zaa!..XNT'ieƃǽ ۣo>}{t@7SGĕ[&AIۣܹT߾fuǢ3>ıDw$@tx:;GtU41,:1Wy~t T%>@d11&+K]*ⱅW_>ʽE-77dJcj*CWh h L .+sfqtw9eG"4K6k&P1f~2ƔTDT"&  ͐ud@gLJ<#ڒp Jt0Zi}9@Nm_޽+eB *k7O_ڊ_?Ӱ "B<,:;77!,olvO~Atvy@H&_pG N/c6ŋeIƐzG}ꏾ.׮\[_{3a͌w]2c+mr$,Uy S Ôȭʨf>c$qH+J?J^/3#Y1)WW޽{ynY\:WNtež+@,2V  8:k*3 *JoĈn93/tЙy pxONgy6Y1KMzw|~Kt,1뽚h 0BdxvQysH|;d%Q]Zh,~S1fk=C!'3RqPS ϋckNO ]M@RX7|vq D`*٫=+y̘$락3C̽~]#d<pr3Dt[f^8^K \r1a%iP!VaȹCޑR^9t ",֗lęP58Pm؉  '0F7CAD= Cۆ 0si,eFFCHc 1 „@f`p.&1#p!Z|/CnڴdSOl \R`x8`999& j|zA;lb+R`FMGED*I &} ջjOW JOstIMaZν|*hsPXl쏗'-{q-7^eEzw҄4lK @o_Ācٓu7oN}er(޵pO (R7K"2Is]XTE(M8ĭ^jѱaAPh9E$]<;)pt5<VOn_]K&/K{;(RȢsy^\( ":IToq9p W$PA42oZ^wLfgTZ ]wmbҎ!D njvQD|?(hZFgl+NG",4HN0EGst\E)vNheÌ^r1џ2de:hYv'glԥLE,5:r5I*7{qm^̵ "ÉXtQcjtw)9 'fEu֏R0hT [J"*Xtvoll\4ĜV*rfI?p0=; Ow"H`VlL/ny:rHTeX^j-f_P;t#}G7DÕ& glPIR(M8KIhx' A B"@҃ ZS*΅88Yǘe3>Nc-Lۣd}ͰEҬ"ǔ:Imų79:^DN7yP'Kѩ@hO- 0*V3 pB'CU__oxʒ?;*XMoCcV,K"T& Ͳ<~@$RYy{(+?)&<:rx-S`n$S'VLhL~{:xj[ht!<&l'B > YDK(xmB D"eJ ZVKðKaACݧ xN ?&=t=jRmE o,tbRdBJ_xQ7"bU7sB@\!.m!A_k& ]@ 9?\OmKy+~B0r` xyGr}טn[9OB< Yl~p~P.8%6FGJz' L 0 Wkԓ Nc [Z3Y*:!Xʕ_S>l8y7GhXI& ł ɋFo9q ݫ.Q^Ժj7SDh 35 ,]_偯N0tD$ԁ&OD]#n$/eW{(Uמ(V܄k( W5χ'a:|d'3c\99MƘ鋚Cny1DTÕg?=nQC]WdEit!&CAuFP:)E5c99A{Ҁ rńp`*Ϳ}'uHrGPE'/Kɡ2HFi5TX)ƘYY@$w D j&tb| SkR] }w$zHח3_GwW IR\:V:&cʍm ;wYN|Y\)6-e+ %s8p_[i?j~xfe9*qs~+d~>  O?jbzea8Rִՠpݛbrx}Ч(<B/h)@Xg!yG|PQZݱl߹=ɋe6t}ُX4DሆUV Dtft,+vZ]_vGw/~NtAIQU>=޷?La>IENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/sidebyside.png0000644000175000017460000000705600000000000017072 0ustar00simonwheel00000000000000PNG  IHDR``mo IDATx]r:ARrvs9!s w*L9tt,(N Fi,kjSK /N`D&7c*yd*]W͕,Iddw D"&kUQvMJ*mU녩*#71"nqE:>x~rjiELJݝg/_m.d i 7kMqy^~݋'ϳ-]{ewtvfNn D-VA:I!KH;#v/;qx^ݜPo : y][}uO>)*f*>0,"U0-ɫJ7R1FFN-)HF|Sl8*6/Z <^9 p=W :~N cV+Za>bӏAV:, ߦEe>ɣN7'rU%.rÜ6,3<@W_*/~?PpiW7ȁ1T-.,MM] 5CQG1[GqMŗ4=ty!!!~|A'ɥC@[ 0"p]=6OXay4 cp0`1R5cLQWi͕7#9* kÀ<1&˲xF*IҼ,45j ealDr 8: j#" U .y{xr鮴[  48>l8BkN`#yQ9bBr]']GŌa0h!4{Wx}F&Ҕ;e~[жaIQ~o86:ͰX25na><8E{t+ݠ= ,mr(䑂y OpÍG1ۋ t\N pBص0\@nsY*O;)e2Ҵ敏ʹ{-@m H1ȴ{1q J`1¼E9nB, @tkFbTKL` D&a.737Y YP6łb(,xp҂=5?W9\$5(47GxTƆկLX zϗq Bq4CjA"hgXj5a $j^2A).$ ܇$8 >Xl*g-Ŝڭ}i_> :}|ܚ1߼hcڳlstLJDlpw%}, Elc, Z[I#PŘ*wUSx0.֜:MSC]=VC\T ak1 A T2zine5 .ڰƵtH_︄zy1*4YM;S Åe3\YxNY\4gz0J 遷6 ݁0Q ?0[k1 g3&0I{ 5%̎U\2'f\k*b- nF=CvGJ{ Giz5jpU5V)w$I& nA02Y$Zk( 5 )K~ukdypomd(޼)Ig8ԄF0mr< OwWjZ wo ǧ DD #kC51i<fi7 efK9F윟nmn'hj ǓFpa1=~0Ͳai-ǀe0b/n:J4ѣ,˪ݻ" D Sl0$V?zj$UYnn?zg+YZIt[nVVz?G^,ˋ c +$IzgՕ,rD026xgmu9Ko D)DWW1%UTUj,uH-4syȌYi 0 ~Ն,MrqUc۹e- ^B\ץwš5@iq=g zƈrND] &Z@I <@I &/ hU)6~Ts&sؙa!э {-ቼa&!-?¶.| ̩f6jg.PYZ E06m%mzh"+x (L!ԕ2 3ZLYFFD'Yd.KZtA{سMQy4HXbT4?U>x8̋7'Lϛ:>H #`10'BM ! uxt˭5 ?Wë~+EjjƄBG/,-&̐k-׏5j{FwӣNWzeDH0g]K:8)|> )ڈNYLWzwoߡ SӰmX dRh{葫O:Ef J܄1B7DJHd:>zLL FQn)~t܂4SYvIfH5K3ȡOau6;aZDD!a #Ӷby߿?็& oҗj$XoF,lM J JJЀoߢUY>Bf Σ3P# 4\D0jUD}}oh>xׅH3וyX5^E2Pf޺w 6E3|(ﱬAzuR.; u[A=얁j@ o1\g\&AJJ6(^K6runwTY`} ߖeZ{WP偑bˬE00MY67QV=2TlYv|_Ĕ chnj KO{KդSO8۹6/=G5jGAX M6CCw66.s+Kd ڪG>`(hv@JrBt~S"= 4`,)h;$ʕUC%c8SITbN3gm+ ]圇w۸5`,%.mK^~-qȒ %yM1~<9C@.ѬXfZMVD4=})0j!U (k.&Bl{ TJa=kzFP9BTO0,J;LC_ch$ Tggg )Zi9`gry-u?܉,g SK4s;ql@x0no{ova8N'?O_/tONߔX! NCس#@` B @Qʳlpx||o5QA`V b8;<:D9%\քãm"x/NxpҰE($QzApcEG?YYH o}kIp7+Pքgʲ5%0%M + pP~ahO0fn?g/'yk`2Q^UqNe~{KAn, /_Nf $@YUtl^@*鴟$yY:mY4 h"lj0jNpe `p$ET@AsO^Zd^S^MIc lAltX! #"P+Hpx93 JHu(oGq|6Yf ˖$<> \857.-a>D`a]{4H|EP+ C+#Tv]O[VvB@tðĕ 8  NK iBgġA.IVlwdEipݿ}]V$atzZvW}VUfjPAK!Q$=z'yDc`'(8R"~ "g6'PrٜiJ?vt2&^*R"H!Z#F穻rD_'"ߡ,/t.@] Z~̒8BDX# t4!#'_4A9onAQ(k \r&6)Ki@^BV!m+``.7PGhJ@i*,ՠW|de+XtE.n7(+WVƶ 8U7\H4#4",l'iH>:h'$'RҚseP @|Qp47ųTqyz|(Io t+˵R觸&DE@hMrYw8YFSyNOAzSȖGruMq< n]yv`s7A.tô}O&im>i vqm؛J rA͓"Bc\8lp}6Eazm`='M dX H*, 6; mM`c@ [S$ F`]lV "Z9V0 8(Лs| kM!\bM0Ej"J:"AM R Qql q{+ @HBK EGyYdK9鳷E.=,yQWD0w7E:0M (.5!M]CSA85(CZNi!]:h|4׾t{:?f˷;v\LJAan % T%A~ ѝ}DhrFgpq>>GBJ&Irp"ˢ8VvAPA%Kz; z9!NRͨ(@@D*-wӱnE,\T 'cPm+B$ЯO J݆de? Pq!bGãxs,[.y!`ㄪBrI4/5?҈)pgg'Vܯe\U+׹{=34ޜw7#t^q\ȣhPt_5VcR5Yqzvޜy=`ݬ(0G!sw-_/y";)KΚɉD eYL shB6Y#_?P84> }:keeʼn)ުX;BQq%{:Њt`5ib2qgEi~bbXgx!K^Ӧ7in"˻5Tp 84%)#|>>w ~"m̓@~$ >Q]DDp`mA0HZyx:Po@ R>@o "AEk Fe ^˃08L5' o AP>VqB( M@Q %yTIrb6iQt4|n=es4U V"ysh?*_T"E<=$ pb5hiیkiNO=4nE 5aE'2JS7_JOgi+1ZkUk8(c="X$(aAzeC,aA{NHA@a(#N{YljFTPv7=UeF2AZ)"#?<_G8[a2y,݃ 8ؒ :F8ԄcK`9tQx ,iQ֮}}=SYۈ9"jBEa@Enؕh22 ?ȝ90Ml̊B a]k`3"PpIP5| Hޏey ey$: @,'>W7f9%?1n AR!;dyyKv^<Qͅ [ lsTS@aЂ@*?`Y'x/j([ d΋$ǧJ/_rUEѩ00ϾIaQ( )y<)@Bi5롧Oo3TXUST{ Yz| k(L+*:ve/@(>иt؋ V3XA'l6'W%Ay{Bk$:~!p5}[}utNGPnvy?*&0v{]67S@qnzqF-'{=s4My;::=eQٽO^{*+Sx_Ihkl:Pǽ G4IJ4`wpg=˲+v:vQ޼|k:]1iHVIENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/wayland.png0000644000175000017460000001076400000000000016405 0ustar00simonwheel00000000000000PNG  IHDR>aIDATx+Yom۶m۶mֶm;϶mcl75M:9UOr%\rɥ.V(k۞8rX&~dzWdЁj$oŲ.Kc2P,4ISm;l$V2AnKVo',o#t}+K)XCL}YBv;P θNK //J)Be#m-<{FU;|]F<1wkD2*r Enaރz;l`m8AہqZ9\bNtb $_oWyKޠf!m`#0<~ʿF  @}}oɯΉoÜaK0Lo't%d[-|EܟLgeL|yMdb a  Z迳@??Fy.X'%γS"sX|HAo[ϞFn$^b`3rK2_EAAZi/#ϙ19 Ccqin)KXX}ӏ.p#n#"'RN, ,$dG/▵-yrXk#z0(: RNm7p\S=,@a : 3{uSp$-.‹g[qH/@S)tY&z'fu61!;L&M4:x*BE_dp 0`OsIp g ڟ2䩏Üwn+k0xsX0 mg GB x&9l.+Ź_ 6$4_g_&ޅcJu%X}/χZʨ NHϖ0X3T- $fN"ƙYv}?P93C :?A M\Ä DMAk 1S? >VNs @^==;NyA4qNyo'/l+4!Iwp^>V剥_3qJk3bE<{AhI8%^7970d=kY۶m۶m۶m۶'ٹ[+=Gd Hd#M5}7s?\u 8TL.m}aG22vC eW% 5α[{]KFck-U1{ s7]>q[co:?f$|f ]_K@;&] 5EM#ӛ t4Jڒ< -RZ3!5&<}MmP?X 枢j M˷}YpV.COU"K!%'ؐE0dMۉbm@/1">ؚ.jZn՛{{,E#jI'\-(qG"_.ձkM_/H5lfȧ'0TPfYG#)E3l>. NU~_I'e`8+f3 ڟ E@ݨnEVMge ̽G,f7obYÈԩ}9Ȅf*m\d4V1Z;e>gɬwqDIKѣN@t~ hg22:B)Nӫz[EQe him⪅ZXH+⮶Z^ok% Vvi/a˺1m& 87̘IV A~P#W|OxB-[]9rLSX#OROE3AYoWЈN麇{t(}Y<%225#YQ" QW07#t Q'{bMxsLX'd3}'FN5VIg`Q.w^漀$I@a5Lw{G^'Oj|Np>\p &pOEfTPkR!A 'pP{J=9A,-5|h" T)o#t+B=J2`6үԡ/nh0 4@leu+F+ O. , ^3ר+{:iVgCFC[ا/Q~)T'V콮jAR˻З=f!I,OP c.?t҅$bbTpȣyV_b̹,'5rK82aa`qƙŴ$佟B "IGޭsU҅grm,AvBd.'I:EVɍA٣vks~Sk]@JǑ^Et <%^+i _X1mGYWz_-&J >$ rǠͯ~u_!5a9"ސ'$abpDkMhӾ_׏TLE :!18%DJu!E@{:cg_ v!1Bbd ?}QKYNAg_bc>_˝0Ė"1*zCcbQUY<qWCĺ AwGu!I<CѦ!~'I1CexT.|j3&.7>5u_S]1I3D՝=>R;v%hMNrх~ CߴJIQ!3*\nPbERvz+vN Q}'); 1t`©O;q>AG oߠux}:rHGG}z=qf)n3B0}gsvBR'!.h M[2ڂFBE+n5z1IENDB`././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/data/wayland.svg0000644000175000017460000002251000000000000016410 0ustar00simonwheel00000000000000 image/svg+xml ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.348963 weston-8.0.0/desktop-shell/0000755000175000017460000000000000000000000016075 5ustar00simonwheel00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/desktop-shell/exposay.c0000644000175000017460000005016200000000000017735 0ustar00simonwheel00000000000000/* * Copyright © 2010-2012 Intel Corporation * Copyright © 2011-2012 Collabora, Ltd. * Copyright © 2013 Raspberry Pi Foundation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "shell.h" #include "shared/helpers.h" struct exposay_surface { struct desktop_shell *shell; struct exposay_output *eoutput; struct weston_surface *surface; struct weston_view *view; struct wl_listener view_destroy_listener; struct wl_list link; int x; int y; int width; int height; double scale; int row; int column; /* The animations only apply a transformation for their own lifetime, * and don't have an option to indefinitely maintain the * transformation in a steady state - so, we apply our own once the * animation has finished. */ struct weston_transform transform; }; static void exposay_set_state(struct desktop_shell *shell, enum exposay_target_state state, struct weston_seat *seat); static void exposay_check_state(struct desktop_shell *shell); static void exposay_surface_destroy(struct exposay_surface *esurface) { wl_list_remove(&esurface->link); wl_list_remove(&esurface->view_destroy_listener.link); if (esurface->shell->exposay.focus_current == esurface->view) esurface->shell->exposay.focus_current = NULL; if (esurface->shell->exposay.focus_prev == esurface->view) esurface->shell->exposay.focus_prev = NULL; free(esurface); } static void exposay_in_flight_inc(struct desktop_shell *shell) { shell->exposay.in_flight++; } static void exposay_in_flight_dec(struct desktop_shell *shell) { if (--shell->exposay.in_flight > 0) return; exposay_check_state(shell); } static void exposay_animate_in_done(struct weston_view_animation *animation, void *data) { struct exposay_surface *esurface = data; wl_list_insert(&esurface->view->geometry.transformation_list, &esurface->transform.link); weston_matrix_init(&esurface->transform.matrix); weston_matrix_scale(&esurface->transform.matrix, esurface->scale, esurface->scale, 1.0f); weston_matrix_translate(&esurface->transform.matrix, esurface->x - esurface->view->geometry.x, esurface->y - esurface->view->geometry.y, 0); weston_view_geometry_dirty(esurface->view); weston_compositor_schedule_repaint(esurface->view->surface->compositor); exposay_in_flight_dec(esurface->shell); } static void exposay_animate_in(struct exposay_surface *esurface) { exposay_in_flight_inc(esurface->shell); weston_move_scale_run(esurface->view, esurface->x - esurface->view->geometry.x, esurface->y - esurface->view->geometry.y, 1.0, esurface->scale, 0, exposay_animate_in_done, esurface); } static void exposay_animate_out_done(struct weston_view_animation *animation, void *data) { struct exposay_surface *esurface = data; struct desktop_shell *shell = esurface->shell; exposay_surface_destroy(esurface); exposay_in_flight_dec(shell); } static void exposay_animate_out(struct exposay_surface *esurface) { exposay_in_flight_inc(esurface->shell); /* Remove the static transformation set up by * exposay_transform_in_done(). */ wl_list_remove(&esurface->transform.link); weston_view_geometry_dirty(esurface->view); weston_move_scale_run(esurface->view, esurface->x - esurface->view->geometry.x, esurface->y - esurface->view->geometry.y, 1.0, esurface->scale, 1, exposay_animate_out_done, esurface); } static void exposay_highlight_surface(struct desktop_shell *shell, struct exposay_surface *esurface) { struct weston_view *view = esurface->view; if (shell->exposay.focus_current == view) return; shell->exposay.row_current = esurface->row; shell->exposay.column_current = esurface->column; shell->exposay.cur_output = esurface->eoutput; activate(shell, view, shell->exposay.seat, WESTON_ACTIVATE_FLAG_NONE); shell->exposay.focus_current = view; } static int exposay_is_animating(struct desktop_shell *shell) { if (shell->exposay.state_cur == EXPOSAY_LAYOUT_INACTIVE || shell->exposay.state_cur == EXPOSAY_LAYOUT_OVERVIEW) return 0; return (shell->exposay.in_flight > 0); } static void exposay_pick(struct desktop_shell *shell, int x, int y) { struct exposay_surface *esurface; if (exposay_is_animating(shell)) return; wl_list_for_each(esurface, &shell->exposay.surface_list, link) { if (x < esurface->x || x > esurface->x + esurface->width) continue; if (y < esurface->y || y > esurface->y + esurface->height) continue; exposay_highlight_surface(shell, esurface); return; } } static void handle_view_destroy(struct wl_listener *listener, void *data) { struct exposay_surface *esurface = container_of(listener, struct exposay_surface, view_destroy_listener); exposay_surface_destroy(esurface); } /* Pretty lame layout for now; just tries to make a square. Should take * aspect ratio into account really. Also needs to be notified of surface * addition and removal and adjust layout/animate accordingly. */ static enum exposay_layout_state exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) { struct workspace *workspace = shell->exposay.workspace; struct weston_output *output = shell_output->output; struct exposay_output *eoutput = &shell_output->eoutput; struct weston_view *view; struct exposay_surface *esurface, *highlight = NULL; int w, h; int i; int last_row_removed = 0; eoutput->num_surfaces = 0; wl_list_for_each(view, &workspace->layer.view_list.link, layer_link.link) { if (!get_shell_surface(view->surface)) continue; if (view->output != output) continue; eoutput->num_surfaces++; } if (eoutput->num_surfaces == 0) { eoutput->grid_size = 0; eoutput->hpadding_outer = 0; eoutput->vpadding_outer = 0; eoutput->padding_inner = 0; eoutput->surface_size = 0; return EXPOSAY_LAYOUT_OVERVIEW; } /* Lay the grid out as square as possible, losing surfaces from the * bottom row if required. Start with fixed padding of a 10% margin * around the outside and 80px internal padding between surfaces, and * maximise the area made available to surfaces after this, but only * to a maximum of 1/3rd the total output size. * * If we can't make a square grid, add one extra row at the bottom * which will have a smaller number of columns. * * XXX: Surely there has to be a better way to express this maths, * right?! */ eoutput->grid_size = floor(sqrtf(eoutput->num_surfaces)); if (pow(eoutput->grid_size, 2) != eoutput->num_surfaces) eoutput->grid_size++; last_row_removed = pow(eoutput->grid_size, 2) - eoutput->num_surfaces; eoutput->hpadding_outer = (output->width / 10); eoutput->vpadding_outer = (output->height / 10); eoutput->padding_inner = 80; w = output->width - (eoutput->hpadding_outer * 2); w -= eoutput->padding_inner * (eoutput->grid_size - 1); w /= eoutput->grid_size; h = output->height - (eoutput->vpadding_outer * 2); h -= eoutput->padding_inner * (eoutput->grid_size - 1); h /= eoutput->grid_size; eoutput->surface_size = (w < h) ? w : h; if (eoutput->surface_size > (output->width / 2)) eoutput->surface_size = output->width / 2; if (eoutput->surface_size > (output->height / 2)) eoutput->surface_size = output->height / 2; i = 0; wl_list_for_each(view, &workspace->layer.view_list.link, layer_link.link) { int pad; pad = eoutput->surface_size + eoutput->padding_inner; if (!get_shell_surface(view->surface)) continue; if (view->output != output) continue; esurface = malloc(sizeof(*esurface)); if (!esurface) { exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, shell->exposay.seat); break; } wl_list_insert(&shell->exposay.surface_list, &esurface->link); esurface->shell = shell; esurface->eoutput = eoutput; esurface->view = view; esurface->row = i / eoutput->grid_size; esurface->column = i % eoutput->grid_size; esurface->x = output->x + eoutput->hpadding_outer; esurface->x += pad * esurface->column; esurface->y = output->y + eoutput->vpadding_outer; esurface->y += pad * esurface->row; if (esurface->row == eoutput->grid_size - 1) esurface->x += (eoutput->surface_size + eoutput->padding_inner) * last_row_removed / 2; if (view->surface->width > view->surface->height) esurface->scale = eoutput->surface_size / (float) view->surface->width; else esurface->scale = eoutput->surface_size / (float) view->surface->height; esurface->width = view->surface->width * esurface->scale; esurface->height = view->surface->height * esurface->scale; if (shell->exposay.focus_current == esurface->view) highlight = esurface; exposay_animate_in(esurface); /* We want our destroy handler to be after the animation * destroy handler in the list, this way when the view is * destroyed, the animation can safely call the animation * completion callback before we free the esurface in our * destroy handler. */ esurface->view_destroy_listener.notify = handle_view_destroy; wl_signal_add(&view->destroy_signal, &esurface->view_destroy_listener); i++; } if (highlight) { shell->exposay.focus_current = NULL; exposay_highlight_surface(shell, highlight); } weston_compositor_schedule_repaint(shell->compositor); return EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW; } static void exposay_focus(struct weston_pointer_grab *grab) { } static void exposay_motion(struct weston_pointer_grab *grab, const struct timespec *time, struct weston_pointer_motion_event *event) { struct desktop_shell *shell = container_of(grab, struct desktop_shell, exposay.grab_ptr); weston_pointer_move(grab->pointer, event); exposay_pick(shell, wl_fixed_to_int(grab->pointer->x), wl_fixed_to_int(grab->pointer->y)); } static void exposay_button(struct weston_pointer_grab *grab, const struct timespec *time, uint32_t button, uint32_t state_w) { struct desktop_shell *shell = container_of(grab, struct desktop_shell, exposay.grab_ptr); struct weston_seat *seat = grab->pointer->seat; enum wl_pointer_button_state state = state_w; if (button != BTN_LEFT) return; /* Store the surface we clicked on, and don't do anything if we end up * releasing on a different surface. */ if (state == WL_POINTER_BUTTON_STATE_PRESSED) { shell->exposay.clicked = shell->exposay.focus_current; return; } if (shell->exposay.focus_current == shell->exposay.clicked) exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat); else shell->exposay.clicked = NULL; } static void exposay_axis(struct weston_pointer_grab *grab, const struct timespec *time, struct weston_pointer_axis_event *event) { } static void exposay_axis_source(struct weston_pointer_grab *grab, uint32_t source) { } static void exposay_frame(struct weston_pointer_grab *grab) { } static void exposay_pointer_grab_cancel(struct weston_pointer_grab *grab) { struct desktop_shell *shell = container_of(grab, struct desktop_shell, exposay.grab_ptr); exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, shell->exposay.seat); } static const struct weston_pointer_grab_interface exposay_ptr_grab = { exposay_focus, exposay_motion, exposay_button, exposay_axis, exposay_axis_source, exposay_frame, exposay_pointer_grab_cancel, }; static int exposay_maybe_move(struct desktop_shell *shell, int row, int column) { struct exposay_surface *esurface; wl_list_for_each(esurface, &shell->exposay.surface_list, link) { if (esurface->eoutput != shell->exposay.cur_output || esurface->row != row || esurface->column != column) continue; exposay_highlight_surface(shell, esurface); return 1; } return 0; } static void exposay_key(struct weston_keyboard_grab *grab, const struct timespec *time, uint32_t key, uint32_t state_w) { struct weston_seat *seat = grab->keyboard->seat; struct desktop_shell *shell = container_of(grab, struct desktop_shell, exposay.grab_kbd); enum wl_keyboard_key_state state = state_w; if (state != WL_KEYBOARD_KEY_STATE_RELEASED) return; switch (key) { case KEY_ESC: exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat); break; case KEY_ENTER: exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat); break; case KEY_UP: exposay_maybe_move(shell, shell->exposay.row_current - 1, shell->exposay.column_current); break; case KEY_DOWN: /* Special case for trying to move to the bottom row when it * has fewer items than all the others. */ if (!exposay_maybe_move(shell, shell->exposay.row_current + 1, shell->exposay.column_current) && shell->exposay.row_current < (shell->exposay.cur_output->grid_size - 1)) { exposay_maybe_move(shell, shell->exposay.row_current + 1, (shell->exposay.cur_output->num_surfaces % shell->exposay.cur_output->grid_size) - 1); } break; case KEY_LEFT: exposay_maybe_move(shell, shell->exposay.row_current, shell->exposay.column_current - 1); break; case KEY_RIGHT: exposay_maybe_move(shell, shell->exposay.row_current, shell->exposay.column_current + 1); break; case KEY_TAB: /* Try to move right, then down (and to the leftmost column), * then if all else fails, to the top left. */ if (!exposay_maybe_move(shell, shell->exposay.row_current, shell->exposay.column_current + 1) && !exposay_maybe_move(shell, shell->exposay.row_current + 1, 0)) exposay_maybe_move(shell, 0, 0); break; default: break; } } static void exposay_modifier(struct weston_keyboard_grab *grab, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { struct desktop_shell *shell = container_of(grab, struct desktop_shell, exposay.grab_kbd); struct weston_seat *seat = (struct weston_seat *) grab->keyboard->seat; /* We want to know when mod has been pressed and released. * FIXME: There is a problem here: if mod is pressed, then a key * is pressed and released, then mod is released, we will treat that * as if only mod had been pressed and released. */ if (seat->modifier_state) { if (seat->modifier_state == shell->binding_modifier) { shell->exposay.mod_pressed = true; } else { shell->exposay.mod_invalid = true; } } else { if (shell->exposay.mod_pressed && !shell->exposay.mod_invalid) exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat); shell->exposay.mod_invalid = false; shell->exposay.mod_pressed = false; } return; } static void exposay_cancel(struct weston_keyboard_grab *grab) { struct desktop_shell *shell = container_of(grab, struct desktop_shell, exposay.grab_kbd); exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, shell->exposay.seat); } static const struct weston_keyboard_grab_interface exposay_kbd_grab = { exposay_key, exposay_modifier, exposay_cancel, }; /** * Called when the transition from overview -> inactive has completed. */ static enum exposay_layout_state exposay_set_inactive(struct desktop_shell *shell) { struct weston_seat *seat = shell->exposay.seat; struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); struct weston_pointer *pointer = weston_seat_get_pointer(seat); if (pointer) weston_pointer_end_grab(pointer); if (keyboard) { weston_keyboard_end_grab(keyboard); if (keyboard->input_method_resource) keyboard->grab = &keyboard->input_method_grab; } return EXPOSAY_LAYOUT_INACTIVE; } /** * Begins the transition from overview to inactive. */ static enum exposay_layout_state exposay_transition_inactive(struct desktop_shell *shell, int switch_focus) { struct exposay_surface *esurface; /* Call activate() before we start the animations to avoid * animating back the old state and then immediately transitioning * to the new. */ if (switch_focus && shell->exposay.focus_current) activate(shell, shell->exposay.focus_current, shell->exposay.seat, WESTON_ACTIVATE_FLAG_CONFIGURE); else if (shell->exposay.focus_prev) activate(shell, shell->exposay.focus_prev, shell->exposay.seat, WESTON_ACTIVATE_FLAG_CONFIGURE); wl_list_for_each(esurface, &shell->exposay.surface_list, link) exposay_animate_out(esurface); weston_compositor_schedule_repaint(shell->compositor); return EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE; } static enum exposay_layout_state exposay_transition_active(struct desktop_shell *shell) { struct weston_seat *seat = shell->exposay.seat; struct weston_pointer *pointer = weston_seat_get_pointer(seat); struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); struct shell_output *shell_output; bool animate = false; shell->exposay.workspace = get_current_workspace(shell); shell->exposay.focus_prev = get_default_view(keyboard->focus); shell->exposay.focus_current = get_default_view(keyboard->focus); shell->exposay.clicked = NULL; wl_list_init(&shell->exposay.surface_list); lower_fullscreen_layer(shell, NULL); shell->exposay.grab_kbd.interface = &exposay_kbd_grab; weston_keyboard_start_grab(keyboard, &shell->exposay.grab_kbd); weston_keyboard_set_focus(keyboard, NULL); shell->exposay.grab_ptr.interface = &exposay_ptr_grab; if (pointer) { weston_pointer_start_grab(pointer, &shell->exposay.grab_ptr); weston_pointer_clear_focus(pointer); } wl_list_for_each(shell_output, &shell->output_list, link) { enum exposay_layout_state state; state = exposay_layout(shell, shell_output); if (state == EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW) animate = true; } return animate ? EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW : EXPOSAY_LAYOUT_OVERVIEW; } static void exposay_check_state(struct desktop_shell *shell) { enum exposay_layout_state state_new = shell->exposay.state_cur; int do_switch = 0; /* Don't do anything whilst animations are running, just store up * target state changes and only act on them when the animations have * completed. */ if (exposay_is_animating(shell)) return; switch (shell->exposay.state_target) { case EXPOSAY_TARGET_OVERVIEW: switch (shell->exposay.state_cur) { case EXPOSAY_LAYOUT_OVERVIEW: goto out; case EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW: state_new = EXPOSAY_LAYOUT_OVERVIEW; break; default: state_new = exposay_transition_active(shell); break; } break; case EXPOSAY_TARGET_SWITCH: do_switch = 1; /* fallthrough */ case EXPOSAY_TARGET_CANCEL: switch (shell->exposay.state_cur) { case EXPOSAY_LAYOUT_INACTIVE: goto out; case EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE: state_new = exposay_set_inactive(shell); break; default: state_new = exposay_transition_inactive(shell, do_switch); break; } break; } out: shell->exposay.state_cur = state_new; } static void exposay_set_state(struct desktop_shell *shell, enum exposay_target_state state, struct weston_seat *seat) { shell->exposay.state_target = state; shell->exposay.seat = seat; exposay_check_state(shell); } void exposay_binding(struct weston_keyboard *keyboard, enum weston_keyboard_modifier modifier, void *data) { struct desktop_shell *shell = data; exposay_set_state(shell, EXPOSAY_TARGET_OVERVIEW, keyboard->seat); } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/desktop-shell/input-panel.c0000644000175000017460000002715300000000000020505 0ustar00simonwheel00000000000000/* * Copyright © 2010-2012 Intel Corporation * Copyright © 2011-2012 Collabora, Ltd. * Copyright © 2013 Raspberry Pi Foundation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include "shell.h" #include "input-method-unstable-v1-server-protocol.h" #include "shared/helpers.h" struct input_panel_surface { struct wl_resource *resource; struct wl_signal destroy_signal; struct desktop_shell *shell; struct wl_list link; struct weston_surface *surface; struct weston_view *view; struct wl_listener surface_destroy_listener; struct weston_view_animation *anim; struct weston_output *output; uint32_t panel; }; static void input_panel_slide_done(struct weston_view_animation *animation, void *data) { struct input_panel_surface *ipsurf = data; ipsurf->anim = NULL; } static void show_input_panel_surface(struct input_panel_surface *ipsurf) { struct desktop_shell *shell = ipsurf->shell; struct weston_seat *seat; struct weston_surface *focus; float x, y; wl_list_for_each(seat, &shell->compositor->seat_list, link) { struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); if (!keyboard || !keyboard->focus) continue; focus = weston_surface_get_main_surface(keyboard->focus); if (!focus) continue; ipsurf->output = focus->output; x = ipsurf->output->x + (ipsurf->output->width - ipsurf->surface->width) / 2; y = ipsurf->output->y + ipsurf->output->height - ipsurf->surface->height; weston_view_set_position(ipsurf->view, x, y); } weston_layer_entry_insert(&shell->input_panel_layer.view_list, &ipsurf->view->layer_link); weston_view_geometry_dirty(ipsurf->view); weston_view_update_transform(ipsurf->view); ipsurf->surface->is_mapped = true; ipsurf->view->is_mapped = true; weston_surface_damage(ipsurf->surface); if (ipsurf->anim) weston_view_animation_destroy(ipsurf->anim); ipsurf->anim = weston_slide_run(ipsurf->view, ipsurf->surface->height * 0.9, 0, input_panel_slide_done, ipsurf); } static void show_input_panels(struct wl_listener *listener, void *data) { struct desktop_shell *shell = container_of(listener, struct desktop_shell, show_input_panel_listener); struct input_panel_surface *ipsurf, *next; shell->text_input.surface = (struct weston_surface*)data; if (shell->showing_input_panels) return; shell->showing_input_panels = true; if (!shell->locked) weston_layer_set_position(&shell->input_panel_layer, WESTON_LAYER_POSITION_TOP_UI); wl_list_for_each_safe(ipsurf, next, &shell->input_panel.surfaces, link) { if (ipsurf->surface->width == 0) continue; show_input_panel_surface(ipsurf); } } static void hide_input_panels(struct wl_listener *listener, void *data) { struct desktop_shell *shell = container_of(listener, struct desktop_shell, hide_input_panel_listener); struct weston_view *view, *next; if (!shell->showing_input_panels) return; shell->showing_input_panels = false; if (!shell->locked) weston_layer_unset_position(&shell->input_panel_layer); wl_list_for_each_safe(view, next, &shell->input_panel_layer.view_list.link, layer_link.link) weston_view_unmap(view); } static void update_input_panels(struct wl_listener *listener, void *data) { struct desktop_shell *shell = container_of(listener, struct desktop_shell, update_input_panel_listener); memcpy(&shell->text_input.cursor_rectangle, data, sizeof(pixman_box32_t)); } static int input_panel_get_label(struct weston_surface *surface, char *buf, size_t len) { return snprintf(buf, len, "input panel"); } static void input_panel_committed(struct weston_surface *surface, int32_t sx, int32_t sy) { struct input_panel_surface *ip_surface = surface->committed_private; struct desktop_shell *shell = ip_surface->shell; struct weston_view *view; float x, y; if (surface->width == 0) return; if (ip_surface->panel) { view = get_default_view(shell->text_input.surface); if (view == NULL) return; x = view->geometry.x + shell->text_input.cursor_rectangle.x2; y = view->geometry.y + shell->text_input.cursor_rectangle.y2; } else { x = ip_surface->output->x + (ip_surface->output->width - surface->width) / 2; y = ip_surface->output->y + ip_surface->output->height - surface->height; } weston_view_set_position(ip_surface->view, x, y); if (!weston_surface_is_mapped(surface) && shell->showing_input_panels) show_input_panel_surface(ip_surface); } static void destroy_input_panel_surface(struct input_panel_surface *input_panel_surface) { wl_signal_emit(&input_panel_surface->destroy_signal, input_panel_surface); wl_list_remove(&input_panel_surface->surface_destroy_listener.link); wl_list_remove(&input_panel_surface->link); input_panel_surface->surface->committed = NULL; weston_surface_set_label_func(input_panel_surface->surface, NULL); weston_view_destroy(input_panel_surface->view); free(input_panel_surface); } static struct input_panel_surface * get_input_panel_surface(struct weston_surface *surface) { if (surface->committed == input_panel_committed) { return surface->committed_private; } else { return NULL; } } static void input_panel_handle_surface_destroy(struct wl_listener *listener, void *data) { struct input_panel_surface *ipsurface = container_of(listener, struct input_panel_surface, surface_destroy_listener); if (ipsurface->resource) { wl_resource_destroy(ipsurface->resource); } else { destroy_input_panel_surface(ipsurface); } } static struct input_panel_surface * create_input_panel_surface(struct desktop_shell *shell, struct weston_surface *surface) { struct input_panel_surface *input_panel_surface; input_panel_surface = calloc(1, sizeof *input_panel_surface); if (!input_panel_surface) return NULL; surface->committed = input_panel_committed; surface->committed_private = input_panel_surface; weston_surface_set_label_func(surface, input_panel_get_label); input_panel_surface->shell = shell; input_panel_surface->surface = surface; input_panel_surface->view = weston_view_create(surface); wl_signal_init(&input_panel_surface->destroy_signal); input_panel_surface->surface_destroy_listener.notify = input_panel_handle_surface_destroy; wl_signal_add(&surface->destroy_signal, &input_panel_surface->surface_destroy_listener); wl_list_init(&input_panel_surface->link); return input_panel_surface; } static void input_panel_surface_set_toplevel(struct wl_client *client, struct wl_resource *resource, struct wl_resource *output_resource, uint32_t position) { struct input_panel_surface *input_panel_surface = wl_resource_get_user_data(resource); struct desktop_shell *shell = input_panel_surface->shell; struct weston_head *head; wl_list_insert(&shell->input_panel.surfaces, &input_panel_surface->link); head = weston_head_from_resource(output_resource); input_panel_surface->output = head->output; input_panel_surface->panel = 0; } static void input_panel_surface_set_overlay_panel(struct wl_client *client, struct wl_resource *resource) { struct input_panel_surface *input_panel_surface = wl_resource_get_user_data(resource); struct desktop_shell *shell = input_panel_surface->shell; wl_list_insert(&shell->input_panel.surfaces, &input_panel_surface->link); input_panel_surface->panel = 1; } static const struct zwp_input_panel_surface_v1_interface input_panel_surface_implementation = { input_panel_surface_set_toplevel, input_panel_surface_set_overlay_panel }; static void destroy_input_panel_surface_resource(struct wl_resource *resource) { struct input_panel_surface *ipsurf = wl_resource_get_user_data(resource); destroy_input_panel_surface(ipsurf); } static void input_panel_get_input_panel_surface(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface_resource) { struct weston_surface *surface = wl_resource_get_user_data(surface_resource); struct desktop_shell *shell = wl_resource_get_user_data(resource); struct input_panel_surface *ipsurf; if (get_input_panel_surface(surface)) { wl_resource_post_error(surface_resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "wl_input_panel::get_input_panel_surface already requested"); return; } ipsurf = create_input_panel_surface(shell, surface); if (!ipsurf) { wl_resource_post_error(surface_resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "surface->committed already set"); return; } ipsurf->resource = wl_resource_create(client, &zwp_input_panel_surface_v1_interface, 1, id); wl_resource_set_implementation(ipsurf->resource, &input_panel_surface_implementation, ipsurf, destroy_input_panel_surface_resource); } static const struct zwp_input_panel_v1_interface input_panel_implementation = { input_panel_get_input_panel_surface }; static void unbind_input_panel(struct wl_resource *resource) { struct desktop_shell *shell = wl_resource_get_user_data(resource); shell->input_panel.binding = NULL; } static void bind_input_panel(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct desktop_shell *shell = data; struct wl_resource *resource; resource = wl_resource_create(client, &zwp_input_panel_v1_interface, 1, id); if (shell->input_panel.binding == NULL) { wl_resource_set_implementation(resource, &input_panel_implementation, shell, unbind_input_panel); shell->input_panel.binding = resource; return; } wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "interface object already bound"); } void input_panel_destroy(struct desktop_shell *shell) { wl_list_remove(&shell->show_input_panel_listener.link); wl_list_remove(&shell->hide_input_panel_listener.link); } int input_panel_setup(struct desktop_shell *shell) { struct weston_compositor *ec = shell->compositor; shell->show_input_panel_listener.notify = show_input_panels; wl_signal_add(&ec->show_input_panel_signal, &shell->show_input_panel_listener); shell->hide_input_panel_listener.notify = hide_input_panels; wl_signal_add(&ec->hide_input_panel_signal, &shell->hide_input_panel_listener); shell->update_input_panel_listener.notify = update_input_panels; wl_signal_add(&ec->update_input_panel_signal, &shell->update_input_panel_listener); wl_list_init(&shell->input_panel.surfaces); if (wl_global_create(shell->compositor->wl_display, &zwp_input_panel_v1_interface, 1, shell, bind_input_panel) == NULL) return -1; return 0; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3456297 weston-8.0.0/desktop-shell/meson.build0000644000175000017460000000142300000000000020237 0ustar00simonwheel00000000000000if get_option('shell-desktop') config_h.set_quoted('WESTON_SHELL_CLIENT', get_option('desktop-shell-client-default')) srcs_shell_desktop = [ 'shell.c', 'exposay.c', 'input-panel.c', weston_desktop_shell_server_protocol_h, weston_desktop_shell_protocol_c, input_method_unstable_v1_server_protocol_h, input_method_unstable_v1_protocol_c, ] deps_shell_desktop = [ dep_libm, dep_libexec_weston, dep_libshared, dep_lib_desktop, dep_libweston_public, ] plugin_shell_desktop = shared_library( 'desktop-shell', srcs_shell_desktop, include_directories: common_inc, dependencies: deps_shell_desktop, name_prefix: '', install: true, install_dir: dir_module_weston ) env_modmap += 'desktop-shell.so=@0@;'.format(plugin_shell_desktop.full_path()) endif ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.348963 weston-8.0.0/desktop-shell/shell.c0000644000175000017460000043044500000000000017362 0ustar00simonwheel00000000000000/* * Copyright © 2010-2012 Intel Corporation * Copyright © 2011-2012 Collabora, Ltd. * Copyright © 2013 Raspberry Pi Foundation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "shell.h" #include "compositor/weston.h" #include "weston-desktop-shell-server-protocol.h" #include #include "shared/helpers.h" #include "shared/timespec-util.h" #include #define DEFAULT_NUM_WORKSPACES 1 #define DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH 200 struct focus_state { struct desktop_shell *shell; struct weston_seat *seat; struct workspace *ws; struct weston_surface *keyboard_focus; struct wl_list link; struct wl_listener seat_destroy_listener; struct wl_listener surface_destroy_listener; }; /* * Surface stacking and ordering. * * This is handled using several linked lists of surfaces, organised into * ‘layers’. The layers are ordered, and each of the surfaces in one layer are * above all of the surfaces in the layer below. The set of layers is static and * in the following order (top-most first): * • Lock layer (only ever displayed on its own) * • Cursor layer * • Input panel layer * • Fullscreen layer * • Panel layer * • Workspace layers * • Background layer * * The list of layers may be manipulated to remove whole layers of surfaces from * display. For example, when locking the screen, all layers except the lock * layer are removed. * * A surface’s layer is modified on configuring the surface, in * set_surface_type() (which is only called when the surface’s type change is * _committed_). If a surface’s type changes (e.g. when making a window * fullscreen) its layer changes too. * * In order to allow popup and transient surfaces to be correctly stacked above * their parent surfaces, each surface tracks both its parent surface, and a * linked list of its children. When a surface’s layer is updated, so are the * layers of its children. Note that child surfaces are *not* the same as * subsurfaces — child/parent surfaces are purely for maintaining stacking * order. * * The children_link list of siblings of a surface (i.e. those surfaces which * have the same parent) only contains weston_surfaces which have a * shell_surface. Stacking is not implemented for non-shell_surface * weston_surfaces. This means that the following implication does *not* hold: * (shsurf->parent != NULL) ⇒ !wl_list_is_empty(shsurf->children_link) */ struct shell_surface { struct wl_signal destroy_signal; struct weston_desktop_surface *desktop_surface; struct weston_view *view; int32_t last_width, last_height; struct desktop_shell *shell; struct wl_list children_list; struct wl_list children_link; int32_t saved_x, saved_y; bool saved_position_valid; bool saved_rotation_valid; int unresponsive, grabbed; uint32_t resize_edges; struct { struct weston_transform transform; struct weston_matrix rotation; } rotation; struct { struct weston_transform transform; /* matrix from x, y */ struct weston_view *black_view; } fullscreen; struct weston_transform workspace_transform; struct weston_output *fullscreen_output; struct weston_output *output; struct wl_listener output_destroy_listener; struct surface_state { bool fullscreen; bool maximized; bool lowered; } state; struct { bool is_set; int32_t x; int32_t y; } xwayland; int focus_count; bool destroying; }; struct shell_grab { struct weston_pointer_grab grab; struct shell_surface *shsurf; struct wl_listener shsurf_destroy_listener; }; struct shell_touch_grab { struct weston_touch_grab grab; struct shell_surface *shsurf; struct wl_listener shsurf_destroy_listener; struct weston_touch *touch; }; struct weston_move_grab { struct shell_grab base; wl_fixed_t dx, dy; bool client_initiated; }; struct weston_touch_move_grab { struct shell_touch_grab base; int active; wl_fixed_t dx, dy; }; struct rotate_grab { struct shell_grab base; struct weston_matrix rotation; struct { float x; float y; } center; }; struct shell_seat { struct weston_seat *seat; struct wl_listener seat_destroy_listener; struct weston_surface *focused_surface; struct wl_listener caps_changed_listener; struct wl_listener pointer_focus_listener; struct wl_listener keyboard_focus_listener; }; static struct desktop_shell * shell_surface_get_shell(struct shell_surface *shsurf); static void set_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer); static void surface_rotate(struct shell_surface *surface, struct weston_pointer *pointer); static void shell_fade_startup(struct desktop_shell *shell); static void shell_fade(struct desktop_shell *shell, enum fade_type type); static struct shell_seat * get_shell_seat(struct weston_seat *seat); static void get_output_panel_size(struct desktop_shell *shell, struct weston_output *output, int *width, int *height); static void shell_surface_update_child_surface_layers(struct shell_surface *shsurf); static int shell_surface_get_label(struct weston_surface *surface, char *buf, size_t len) { const char *t, *c; struct weston_desktop_surface *desktop_surface = weston_surface_get_desktop_surface(surface); t = weston_desktop_surface_get_title(desktop_surface); c = weston_desktop_surface_get_app_id(desktop_surface); return snprintf(buf, len, "%s window%s%s%s%s%s", "top-level", t ? " '" : "", t ?: "", t ? "'" : "", c ? " of " : "", c ?: ""); } static void destroy_shell_grab_shsurf(struct wl_listener *listener, void *data) { struct shell_grab *grab; grab = container_of(listener, struct shell_grab, shsurf_destroy_listener); grab->shsurf = NULL; } struct weston_view * get_default_view(struct weston_surface *surface) { struct shell_surface *shsurf; struct weston_view *view; if (!surface || wl_list_empty(&surface->views)) return NULL; shsurf = get_shell_surface(surface); if (shsurf) return shsurf->view; wl_list_for_each(view, &surface->views, surface_link) if (weston_view_is_mapped(view)) return view; return container_of(surface->views.next, struct weston_view, surface_link); } static void shell_grab_start(struct shell_grab *grab, const struct weston_pointer_grab_interface *interface, struct shell_surface *shsurf, struct weston_pointer *pointer, enum weston_desktop_shell_cursor cursor) { struct desktop_shell *shell = shsurf->shell; weston_seat_break_desktop_grabs(pointer->seat); grab->grab.interface = interface; grab->shsurf = shsurf; grab->shsurf_destroy_listener.notify = destroy_shell_grab_shsurf; wl_signal_add(&shsurf->destroy_signal, &grab->shsurf_destroy_listener); shsurf->grabbed = 1; weston_pointer_start_grab(pointer, &grab->grab); if (shell->child.desktop_shell) { weston_desktop_shell_send_grab_cursor(shell->child.desktop_shell, cursor); weston_pointer_set_focus(pointer, get_default_view(shell->grab_surface), wl_fixed_from_int(0), wl_fixed_from_int(0)); } } static void get_panel_size(struct desktop_shell *shell, struct weston_view *view, int *width, int *height) { float x1, y1; float x2, y2; weston_view_to_global_float(view, 0, 0, &x1, &y1); weston_view_to_global_float(view, view->surface->width, view->surface->height, &x2, &y2); *width = (int)(x2 - x1); *height = (int)(y2 - y1); } static void get_output_panel_size(struct desktop_shell *shell, struct weston_output *output, int *width, int *height) { struct weston_view *view; *width = 0; *height = 0; if (!output) return; wl_list_for_each(view, &shell->panel_layer.view_list.link, layer_link.link) { if (view->surface->output == output) { get_panel_size(shell, view, width, height); return; } } /* the correct view wasn't found */ } static void get_output_work_area(struct desktop_shell *shell, struct weston_output *output, pixman_rectangle32_t *area) { int32_t panel_width = 0, panel_height = 0; if (!output) { area->x = 0; area->y = 0; area->width = 0; area->height = 0; return; } area->x = output->x; area->y = output->y; get_output_panel_size(shell, output, &panel_width, &panel_height); switch (shell->panel_position) { case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: default: area->y += panel_height; /* fallthrough */ case WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM: area->width = output->width; area->height = output->height - panel_height; break; case WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT: area->x += panel_width; /* fallthrough */ case WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT: area->width = output->width - panel_width; area->height = output->height; break; } } static void shell_grab_end(struct shell_grab *grab) { if (grab->shsurf) { wl_list_remove(&grab->shsurf_destroy_listener.link); grab->shsurf->grabbed = 0; if (grab->shsurf->resize_edges) { grab->shsurf->resize_edges = 0; } } weston_pointer_end_grab(grab->grab.pointer); } static void shell_touch_grab_start(struct shell_touch_grab *grab, const struct weston_touch_grab_interface *interface, struct shell_surface *shsurf, struct weston_touch *touch) { struct desktop_shell *shell = shsurf->shell; weston_seat_break_desktop_grabs(touch->seat); grab->grab.interface = interface; grab->shsurf = shsurf; grab->shsurf_destroy_listener.notify = destroy_shell_grab_shsurf; wl_signal_add(&shsurf->destroy_signal, &grab->shsurf_destroy_listener); grab->touch = touch; shsurf->grabbed = 1; weston_touch_start_grab(touch, &grab->grab); if (shell->child.desktop_shell) weston_touch_set_focus(touch, get_default_view(shell->grab_surface)); } static void shell_touch_grab_end(struct shell_touch_grab *grab) { if (grab->shsurf) { wl_list_remove(&grab->shsurf_destroy_listener.link); grab->shsurf->grabbed = 0; } weston_touch_end_grab(grab->touch); } static void center_on_output(struct weston_view *view, struct weston_output *output); static enum weston_keyboard_modifier get_modifier(char *modifier) { if (!modifier) return MODIFIER_SUPER; if (!strcmp("ctrl", modifier)) return MODIFIER_CTRL; else if (!strcmp("alt", modifier)) return MODIFIER_ALT; else if (!strcmp("super", modifier)) return MODIFIER_SUPER; else if (!strcmp("none", modifier)) return 0; else return MODIFIER_SUPER; } static enum animation_type get_animation_type(char *animation) { if (!animation) return ANIMATION_NONE; if (!strcmp("zoom", animation)) return ANIMATION_ZOOM; else if (!strcmp("fade", animation)) return ANIMATION_FADE; else if (!strcmp("dim-layer", animation)) return ANIMATION_DIM_LAYER; else return ANIMATION_NONE; } static void shell_configuration(struct desktop_shell *shell) { struct weston_config_section *section; char *s, *client; bool allow_zap; section = weston_config_get_section(wet_get_config(shell->compositor), "shell", NULL, NULL); client = wet_get_libexec_path(WESTON_SHELL_CLIENT); weston_config_section_get_string(section, "client", &s, client); free(client); shell->client = s; weston_config_section_get_bool(section, "allow-zap", &allow_zap, true); shell->allow_zap = allow_zap; weston_config_section_get_string(section, "binding-modifier", &s, "super"); shell->binding_modifier = get_modifier(s); free(s); weston_config_section_get_string(section, "exposay-modifier", &s, "none"); shell->exposay_modifier = get_modifier(s); free(s); weston_config_section_get_string(section, "animation", &s, "none"); shell->win_animation_type = get_animation_type(s); free(s); weston_config_section_get_string(section, "close-animation", &s, "fade"); shell->win_close_animation_type = get_animation_type(s); free(s); weston_config_section_get_string(section, "startup-animation", &s, "fade"); shell->startup_animation_type = get_animation_type(s); free(s); if (shell->startup_animation_type == ANIMATION_ZOOM) shell->startup_animation_type = ANIMATION_NONE; weston_config_section_get_string(section, "focus-animation", &s, "none"); shell->focus_animation_type = get_animation_type(s); free(s); weston_config_section_get_uint(section, "num-workspaces", &shell->workspaces.num, DEFAULT_NUM_WORKSPACES); } struct weston_output * get_default_output(struct weston_compositor *compositor) { if (wl_list_empty(&compositor->output_list)) return NULL; return container_of(compositor->output_list.next, struct weston_output, link); } static int focus_surface_get_label(struct weston_surface *surface, char *buf, size_t len) { return snprintf(buf, len, "focus highlight effect for output %s", (surface->output ? surface->output->name : "NULL")); } /* no-op func for checking focus surface */ static void focus_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) { } static struct focus_surface * get_focus_surface(struct weston_surface *surface) { if (surface->committed == focus_surface_committed) return surface->committed_private; else return NULL; } static bool is_focus_surface (struct weston_surface *es) { return (es->committed == focus_surface_committed); } static bool is_focus_view (struct weston_view *view) { return is_focus_surface (view->surface); } static struct focus_surface * create_focus_surface(struct weston_compositor *ec, struct weston_output *output) { struct focus_surface *fsurf = NULL; struct weston_surface *surface = NULL; fsurf = malloc(sizeof *fsurf); if (!fsurf) return NULL; fsurf->surface = weston_surface_create(ec); surface = fsurf->surface; if (surface == NULL) { free(fsurf); return NULL; } surface->committed = focus_surface_committed; surface->output = output; surface->is_mapped = true; surface->committed_private = fsurf; weston_surface_set_label_func(surface, focus_surface_get_label); fsurf->view = weston_view_create(surface); if (fsurf->view == NULL) { weston_surface_destroy(surface); free(fsurf); return NULL; } weston_view_set_output(fsurf->view, output); fsurf->view->is_mapped = true; weston_surface_set_size(surface, output->width, output->height); weston_view_set_position(fsurf->view, output->x, output->y); weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1.0); pixman_region32_fini(&surface->opaque); pixman_region32_init_rect(&surface->opaque, output->x, output->y, output->width, output->height); pixman_region32_fini(&surface->input); pixman_region32_init(&surface->input); wl_list_init(&fsurf->workspace_transform.link); return fsurf; } static void focus_surface_destroy(struct focus_surface *fsurf) { weston_surface_destroy(fsurf->surface); free(fsurf); } static void focus_animation_done(struct weston_view_animation *animation, void *data) { struct workspace *ws = data; ws->focus_animation = NULL; } static void focus_state_destroy(struct focus_state *state) { wl_list_remove(&state->seat_destroy_listener.link); wl_list_remove(&state->surface_destroy_listener.link); free(state); } static void focus_state_seat_destroy(struct wl_listener *listener, void *data) { struct focus_state *state = container_of(listener, struct focus_state, seat_destroy_listener); wl_list_remove(&state->link); focus_state_destroy(state); } static void focus_state_surface_destroy(struct wl_listener *listener, void *data) { struct focus_state *state = container_of(listener, struct focus_state, surface_destroy_listener); struct weston_surface *main_surface; struct weston_view *next; struct weston_view *view; main_surface = weston_surface_get_main_surface(state->keyboard_focus); next = NULL; wl_list_for_each(view, &state->ws->layer.view_list.link, layer_link.link) { if (view->surface == main_surface) continue; if (is_focus_view(view)) continue; if (!get_shell_surface(view->surface)) continue; next = view; break; } /* if the focus was a sub-surface, activate its main surface */ if (main_surface != state->keyboard_focus) next = get_default_view(main_surface); if (next) { if (state->keyboard_focus) { wl_list_remove(&state->surface_destroy_listener.link); wl_list_init(&state->surface_destroy_listener.link); } state->keyboard_focus = NULL; activate(state->shell, next, state->seat, WESTON_ACTIVATE_FLAG_CONFIGURE); } else { if (state->shell->focus_animation_type == ANIMATION_DIM_LAYER) { if (state->ws->focus_animation) weston_view_animation_destroy(state->ws->focus_animation); state->ws->focus_animation = weston_fade_run( state->ws->fsurf_front->view, state->ws->fsurf_front->view->alpha, 0.0, 300, focus_animation_done, state->ws); } wl_list_remove(&state->link); focus_state_destroy(state); } } static struct focus_state * focus_state_create(struct desktop_shell *shell, struct weston_seat *seat, struct workspace *ws) { struct focus_state *state; state = malloc(sizeof *state); if (state == NULL) return NULL; state->shell = shell; state->keyboard_focus = NULL; state->ws = ws; state->seat = seat; wl_list_insert(&ws->focus_list, &state->link); state->seat_destroy_listener.notify = focus_state_seat_destroy; state->surface_destroy_listener.notify = focus_state_surface_destroy; wl_signal_add(&seat->destroy_signal, &state->seat_destroy_listener); wl_list_init(&state->surface_destroy_listener.link); return state; } static struct focus_state * ensure_focus_state(struct desktop_shell *shell, struct weston_seat *seat) { struct workspace *ws = get_current_workspace(shell); struct focus_state *state; wl_list_for_each(state, &ws->focus_list, link) if (state->seat == seat) break; if (&state->link == &ws->focus_list) state = focus_state_create(shell, seat, ws); return state; } static void focus_state_set_focus(struct focus_state *state, struct weston_surface *surface) { if (state->keyboard_focus) { wl_list_remove(&state->surface_destroy_listener.link); wl_list_init(&state->surface_destroy_listener.link); } state->keyboard_focus = surface; if (surface) wl_signal_add(&surface->destroy_signal, &state->surface_destroy_listener); } static void restore_focus_state(struct desktop_shell *shell, struct workspace *ws) { struct focus_state *state, *next; struct weston_surface *surface; struct wl_list pending_seat_list; struct weston_seat *seat, *next_seat; /* Temporarily steal the list of seats so that we can keep * track of the seats we've already processed */ wl_list_init(&pending_seat_list); wl_list_insert_list(&pending_seat_list, &shell->compositor->seat_list); wl_list_init(&shell->compositor->seat_list); wl_list_for_each_safe(state, next, &ws->focus_list, link) { struct weston_keyboard *keyboard = weston_seat_get_keyboard(state->seat); wl_list_remove(&state->seat->link); wl_list_insert(&shell->compositor->seat_list, &state->seat->link); if (!keyboard) continue; surface = state->keyboard_focus; weston_keyboard_set_focus(keyboard, surface); } /* For any remaining seats that we don't have a focus state * for we'll reset the keyboard focus to NULL */ wl_list_for_each_safe(seat, next_seat, &pending_seat_list, link) { struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); wl_list_insert(&shell->compositor->seat_list, &seat->link); if (!keyboard) continue; weston_keyboard_set_focus(keyboard, NULL); } } static void replace_focus_state(struct desktop_shell *shell, struct workspace *ws, struct weston_seat *seat) { struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); struct focus_state *state; wl_list_for_each(state, &ws->focus_list, link) { if (state->seat == seat) { focus_state_set_focus(state, keyboard->focus); return; } } } static void drop_focus_state(struct desktop_shell *shell, struct workspace *ws, struct weston_surface *surface) { struct focus_state *state; wl_list_for_each(state, &ws->focus_list, link) if (state->keyboard_focus == surface) focus_state_set_focus(state, NULL); } static void animate_focus_change(struct desktop_shell *shell, struct workspace *ws, struct weston_view *from, struct weston_view *to) { struct weston_output *output; bool focus_surface_created = false; /* FIXME: Only support dim animation using two layers */ if (from == to || shell->focus_animation_type != ANIMATION_DIM_LAYER) return; output = get_default_output(shell->compositor); if (ws->fsurf_front == NULL && (from || to)) { ws->fsurf_front = create_focus_surface(shell->compositor, output); if (ws->fsurf_front == NULL) return; ws->fsurf_front->view->alpha = 0.0; ws->fsurf_back = create_focus_surface(shell->compositor, output); if (ws->fsurf_back == NULL) { focus_surface_destroy(ws->fsurf_front); return; } ws->fsurf_back->view->alpha = 0.0; focus_surface_created = true; } else { weston_layer_entry_remove(&ws->fsurf_front->view->layer_link); weston_layer_entry_remove(&ws->fsurf_back->view->layer_link); } if (ws->focus_animation) { weston_view_animation_destroy(ws->focus_animation); ws->focus_animation = NULL; } if (to) weston_layer_entry_insert(&to->layer_link, &ws->fsurf_front->view->layer_link); else if (from) weston_layer_entry_insert(&ws->layer.view_list, &ws->fsurf_front->view->layer_link); if (focus_surface_created) { ws->focus_animation = weston_fade_run( ws->fsurf_front->view, ws->fsurf_front->view->alpha, 0.4, 300, focus_animation_done, ws); } else if (from) { weston_layer_entry_insert(&from->layer_link, &ws->fsurf_back->view->layer_link); ws->focus_animation = weston_stable_fade_run( ws->fsurf_front->view, 0.0, ws->fsurf_back->view, 0.4, focus_animation_done, ws); } else if (to) { weston_layer_entry_insert(&ws->layer.view_list, &ws->fsurf_back->view->layer_link); ws->focus_animation = weston_stable_fade_run( ws->fsurf_front->view, 0.0, ws->fsurf_back->view, 0.4, focus_animation_done, ws); } } static void workspace_destroy(struct workspace *ws) { struct focus_state *state, *next; wl_list_for_each_safe(state, next, &ws->focus_list, link) focus_state_destroy(state); if (ws->fsurf_front) focus_surface_destroy(ws->fsurf_front); if (ws->fsurf_back) focus_surface_destroy(ws->fsurf_back); free(ws); } static void seat_destroyed(struct wl_listener *listener, void *data) { struct weston_seat *seat = data; struct focus_state *state, *next; struct workspace *ws = container_of(listener, struct workspace, seat_destroyed_listener); wl_list_for_each_safe(state, next, &ws->focus_list, link) if (state->seat == seat) wl_list_remove(&state->link); } static struct workspace * workspace_create(struct desktop_shell *shell) { struct workspace *ws = malloc(sizeof *ws); if (ws == NULL) return NULL; weston_layer_init(&ws->layer, shell->compositor); wl_list_init(&ws->focus_list); wl_list_init(&ws->seat_destroyed_listener.link); ws->seat_destroyed_listener.notify = seat_destroyed; ws->fsurf_front = NULL; ws->fsurf_back = NULL; ws->focus_animation = NULL; return ws; } static int workspace_is_empty(struct workspace *ws) { return wl_list_empty(&ws->layer.view_list.link); } static struct workspace * get_workspace(struct desktop_shell *shell, unsigned int index) { struct workspace **pws = shell->workspaces.array.data; assert(index < shell->workspaces.num); pws += index; return *pws; } struct workspace * get_current_workspace(struct desktop_shell *shell) { return get_workspace(shell, shell->workspaces.current); } static void activate_workspace(struct desktop_shell *shell, unsigned int index) { struct workspace *ws; ws = get_workspace(shell, index); weston_layer_set_position(&ws->layer, WESTON_LAYER_POSITION_NORMAL); shell->workspaces.current = index; } static unsigned int get_output_height(struct weston_output *output) { return abs(output->region.extents.y1 - output->region.extents.y2); } static struct weston_transform * view_get_transform(struct weston_view *view) { struct focus_surface *fsurf = NULL; struct shell_surface *shsurf = NULL; if (is_focus_view(view)) { fsurf = get_focus_surface(view->surface); return &fsurf->workspace_transform; } shsurf = get_shell_surface(view->surface); if (shsurf) return &shsurf->workspace_transform; return NULL; } static void view_translate(struct workspace *ws, struct weston_view *view, double d) { struct weston_transform *transform = view_get_transform(view); if (!transform) return; if (wl_list_empty(&transform->link)) wl_list_insert(view->geometry.transformation_list.prev, &transform->link); weston_matrix_init(&transform->matrix); weston_matrix_translate(&transform->matrix, 0.0, d, 0.0); weston_view_geometry_dirty(view); } static void workspace_translate_out(struct workspace *ws, double fraction) { struct weston_view *view; unsigned int height; double d; wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { height = get_output_height(view->surface->output); d = height * fraction; view_translate(ws, view, d); } } static void workspace_translate_in(struct workspace *ws, double fraction) { struct weston_view *view; unsigned int height; double d; wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { height = get_output_height(view->surface->output); if (fraction > 0) d = -(height - height * fraction); else d = height + height * fraction; view_translate(ws, view, d); } } static void reverse_workspace_change_animation(struct desktop_shell *shell, unsigned int index, struct workspace *from, struct workspace *to) { shell->workspaces.current = index; shell->workspaces.anim_to = to; shell->workspaces.anim_from = from; shell->workspaces.anim_dir = -1 * shell->workspaces.anim_dir; shell->workspaces.anim_timestamp = (struct timespec) { 0 }; weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); weston_layer_set_position(&from->layer, WESTON_LAYER_POSITION_NORMAL - 1); weston_compositor_schedule_repaint(shell->compositor); } static void workspace_deactivate_transforms(struct workspace *ws) { struct weston_view *view; struct weston_transform *transform; wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { transform = view_get_transform(view); if (!transform) continue; if (!wl_list_empty(&transform->link)) { wl_list_remove(&transform->link); wl_list_init(&transform->link); } weston_view_geometry_dirty(view); } } static void finish_workspace_change_animation(struct desktop_shell *shell, struct workspace *from, struct workspace *to) { struct weston_view *view; weston_compositor_schedule_repaint(shell->compositor); /* Views that extend past the bottom of the output are still * visible after the workspace animation ends but before its layer * is hidden. In that case, we need to damage below those views so * that the screen is properly repainted. */ wl_list_for_each(view, &from->layer.view_list.link, layer_link.link) weston_view_damage_below(view); wl_list_remove(&shell->workspaces.animation.link); workspace_deactivate_transforms(from); workspace_deactivate_transforms(to); shell->workspaces.anim_to = NULL; weston_layer_unset_position(&shell->workspaces.anim_from->layer); } static void animate_workspace_change_frame(struct weston_animation *animation, struct weston_output *output, const struct timespec *time) { struct desktop_shell *shell = container_of(animation, struct desktop_shell, workspaces.animation); struct workspace *from = shell->workspaces.anim_from; struct workspace *to = shell->workspaces.anim_to; int64_t t; double x, y; if (workspace_is_empty(from) && workspace_is_empty(to)) { finish_workspace_change_animation(shell, from, to); return; } if (timespec_is_zero(&shell->workspaces.anim_timestamp)) { if (shell->workspaces.anim_current == 0.0) shell->workspaces.anim_timestamp = *time; else timespec_add_msec(&shell->workspaces.anim_timestamp, time, /* Inverse of movement function 'y' below. */ -(asin(1.0 - shell->workspaces.anim_current) * DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH * M_2_PI)); } t = timespec_sub_to_msec(time, &shell->workspaces.anim_timestamp); /* * x = [0, π/2] * y(x) = sin(x) */ x = t * (1.0/DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH) * M_PI_2; y = sin(x); if (t < DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH) { weston_compositor_schedule_repaint(shell->compositor); workspace_translate_out(from, shell->workspaces.anim_dir * y); workspace_translate_in(to, shell->workspaces.anim_dir * y); shell->workspaces.anim_current = y; weston_compositor_schedule_repaint(shell->compositor); } else finish_workspace_change_animation(shell, from, to); } static void animate_workspace_change(struct desktop_shell *shell, unsigned int index, struct workspace *from, struct workspace *to) { struct weston_output *output; int dir; if (index > shell->workspaces.current) dir = -1; else dir = 1; shell->workspaces.current = index; shell->workspaces.anim_dir = dir; shell->workspaces.anim_from = from; shell->workspaces.anim_to = to; shell->workspaces.anim_current = 0.0; shell->workspaces.anim_timestamp = (struct timespec) { 0 }; output = container_of(shell->compositor->output_list.next, struct weston_output, link); wl_list_insert(&output->animation_list, &shell->workspaces.animation.link); weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); weston_layer_set_position(&from->layer, WESTON_LAYER_POSITION_NORMAL - 1); workspace_translate_in(to, 0); restore_focus_state(shell, to); weston_compositor_schedule_repaint(shell->compositor); } static void update_workspace(struct desktop_shell *shell, unsigned int index, struct workspace *from, struct workspace *to) { shell->workspaces.current = index; weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); weston_layer_unset_position(&from->layer); } static void change_workspace(struct desktop_shell *shell, unsigned int index) { struct workspace *from; struct workspace *to; struct focus_state *state; if (index == shell->workspaces.current) return; /* Don't change workspace when there is any fullscreen surfaces. */ if (!wl_list_empty(&shell->fullscreen_layer.view_list.link)) return; from = get_current_workspace(shell); to = get_workspace(shell, index); if (shell->workspaces.anim_from == to && shell->workspaces.anim_to == from) { restore_focus_state(shell, to); reverse_workspace_change_animation(shell, index, from, to); return; } if (shell->workspaces.anim_to != NULL) finish_workspace_change_animation(shell, shell->workspaces.anim_from, shell->workspaces.anim_to); restore_focus_state(shell, to); if (shell->focus_animation_type != ANIMATION_NONE) { wl_list_for_each(state, &from->focus_list, link) if (state->keyboard_focus) animate_focus_change(shell, from, get_default_view(state->keyboard_focus), NULL); wl_list_for_each(state, &to->focus_list, link) if (state->keyboard_focus) animate_focus_change(shell, to, NULL, get_default_view(state->keyboard_focus)); } if (workspace_is_empty(to) && workspace_is_empty(from)) update_workspace(shell, index, from, to); else animate_workspace_change(shell, index, from, to); } static bool workspace_has_only(struct workspace *ws, struct weston_surface *surface) { struct wl_list *list = &ws->layer.view_list.link; struct wl_list *e; if (wl_list_empty(list)) return false; e = list->next; if (e->next != list) return false; return container_of(e, struct weston_view, layer_link.link)->surface == surface; } static void surface_keyboard_focus_lost(struct weston_surface *surface) { struct weston_compositor *compositor = surface->compositor; struct weston_seat *seat; struct weston_surface *focus; wl_list_for_each(seat, &compositor->seat_list, link) { struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); if (!keyboard) continue; focus = weston_surface_get_main_surface(keyboard->focus); if (focus == surface) weston_keyboard_set_focus(keyboard, NULL); } } static void take_surface_to_workspace_by_seat(struct desktop_shell *shell, struct weston_seat *seat, unsigned int index) { struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); struct weston_surface *surface; struct weston_view *view; struct shell_surface *shsurf; struct workspace *from; struct workspace *to; struct focus_state *state; surface = weston_surface_get_main_surface(keyboard->focus); view = get_default_view(surface); if (view == NULL || index == shell->workspaces.current || is_focus_view(view)) return; from = get_current_workspace(shell); to = get_workspace(shell, index); weston_layer_entry_remove(&view->layer_link); weston_layer_entry_insert(&to->layer.view_list, &view->layer_link); shsurf = get_shell_surface(surface); if (shsurf != NULL) shell_surface_update_child_surface_layers(shsurf); replace_focus_state(shell, to, seat); drop_focus_state(shell, from, surface); if (shell->workspaces.anim_from == to && shell->workspaces.anim_to == from) { reverse_workspace_change_animation(shell, index, from, to); return; } if (shell->workspaces.anim_to != NULL) finish_workspace_change_animation(shell, shell->workspaces.anim_from, shell->workspaces.anim_to); if (workspace_is_empty(from) && workspace_has_only(to, surface)) update_workspace(shell, index, from, to); else { if (shsurf != NULL && wl_list_empty(&shsurf->workspace_transform.link)) wl_list_insert(&shell->workspaces.anim_sticky_list, &shsurf->workspace_transform.link); animate_workspace_change(shell, index, from, to); } state = ensure_focus_state(shell, seat); if (state != NULL) focus_state_set_focus(state, surface); } static void touch_move_grab_down(struct weston_touch_grab *grab, const struct timespec *time, int touch_id, wl_fixed_t x, wl_fixed_t y) { } static void touch_move_grab_up(struct weston_touch_grab *grab, const struct timespec *time, int touch_id) { struct weston_touch_move_grab *move = (struct weston_touch_move_grab *) container_of( grab, struct shell_touch_grab, grab); if (touch_id == 0) move->active = 0; if (grab->touch->num_tp == 0) { shell_touch_grab_end(&move->base); free(move); } } static void touch_move_grab_motion(struct weston_touch_grab *grab, const struct timespec *time, int touch_id, wl_fixed_t x, wl_fixed_t y) { struct weston_touch_move_grab *move = (struct weston_touch_move_grab *) grab; struct shell_surface *shsurf = move->base.shsurf; struct weston_surface *es; int dx = wl_fixed_to_int(grab->touch->grab_x + move->dx); int dy = wl_fixed_to_int(grab->touch->grab_y + move->dy); if (!shsurf || !move->active) return; es = weston_desktop_surface_get_surface(shsurf->desktop_surface); weston_view_set_position(shsurf->view, dx, dy); weston_compositor_schedule_repaint(es->compositor); } static void touch_move_grab_frame(struct weston_touch_grab *grab) { } static void touch_move_grab_cancel(struct weston_touch_grab *grab) { struct weston_touch_move_grab *move = (struct weston_touch_move_grab *) container_of( grab, struct shell_touch_grab, grab); shell_touch_grab_end(&move->base); free(move); } static const struct weston_touch_grab_interface touch_move_grab_interface = { touch_move_grab_down, touch_move_grab_up, touch_move_grab_motion, touch_move_grab_frame, touch_move_grab_cancel, }; static int surface_touch_move(struct shell_surface *shsurf, struct weston_touch *touch) { struct weston_touch_move_grab *move; if (!shsurf) return -1; if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || weston_desktop_surface_get_maximized(shsurf->desktop_surface)) return 0; move = malloc(sizeof *move); if (!move) return -1; move->active = 1; move->dx = wl_fixed_from_double(shsurf->view->geometry.x) - touch->grab_x; move->dy = wl_fixed_from_double(shsurf->view->geometry.y) - touch->grab_y; shell_touch_grab_start(&move->base, &touch_move_grab_interface, shsurf, touch); return 0; } static void noop_grab_focus(struct weston_pointer_grab *grab) { } static void noop_grab_axis(struct weston_pointer_grab *grab, const struct timespec *time, struct weston_pointer_axis_event *event) { } static void noop_grab_axis_source(struct weston_pointer_grab *grab, uint32_t source) { } static void noop_grab_frame(struct weston_pointer_grab *grab) { } static void constrain_position(struct weston_move_grab *move, int *cx, int *cy) { struct shell_surface *shsurf = move->base.shsurf; struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); struct weston_pointer *pointer = move->base.grab.pointer; int x, y, bottom; const int safety = 50; pixman_rectangle32_t area; struct weston_geometry geometry; x = wl_fixed_to_int(pointer->x + move->dx); y = wl_fixed_to_int(pointer->y + move->dy); if (shsurf->shell->panel_position == WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP) { get_output_work_area(shsurf->shell, surface->output, &area); geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); bottom = y + geometry.height + geometry.y; if (bottom - safety < area.y) y = area.y + safety - geometry.height - geometry.y; if (move->client_initiated && y + geometry.y < area.y) y = area.y - geometry.y; } *cx = x; *cy = y; } static void move_grab_motion(struct weston_pointer_grab *grab, const struct timespec *time, struct weston_pointer_motion_event *event) { struct weston_move_grab *move = (struct weston_move_grab *) grab; struct weston_pointer *pointer = grab->pointer; struct shell_surface *shsurf = move->base.shsurf; struct weston_surface *surface; int cx, cy; weston_pointer_move(pointer, event); if (!shsurf) return; surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); constrain_position(move, &cx, &cy); weston_view_set_position(shsurf->view, cx, cy); weston_compositor_schedule_repaint(surface->compositor); } static void move_grab_button(struct weston_pointer_grab *grab, const struct timespec *time, uint32_t button, uint32_t state_w) { struct shell_grab *shell_grab = container_of(grab, struct shell_grab, grab); struct weston_pointer *pointer = grab->pointer; enum wl_pointer_button_state state = state_w; if (pointer->button_count == 0 && state == WL_POINTER_BUTTON_STATE_RELEASED) { shell_grab_end(shell_grab); free(grab); } } static void move_grab_cancel(struct weston_pointer_grab *grab) { struct shell_grab *shell_grab = container_of(grab, struct shell_grab, grab); shell_grab_end(shell_grab); free(grab); } static const struct weston_pointer_grab_interface move_grab_interface = { noop_grab_focus, move_grab_motion, move_grab_button, noop_grab_axis, noop_grab_axis_source, noop_grab_frame, move_grab_cancel, }; static int surface_move(struct shell_surface *shsurf, struct weston_pointer *pointer, bool client_initiated) { struct weston_move_grab *move; if (!shsurf) return -1; if (shsurf->grabbed || weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || weston_desktop_surface_get_maximized(shsurf->desktop_surface)) return 0; move = malloc(sizeof *move); if (!move) return -1; move->dx = wl_fixed_from_double(shsurf->view->geometry.x) - pointer->grab_x; move->dy = wl_fixed_from_double(shsurf->view->geometry.y) - pointer->grab_y; move->client_initiated = client_initiated; shell_grab_start(&move->base, &move_grab_interface, shsurf, pointer, WESTON_DESKTOP_SHELL_CURSOR_MOVE); return 0; } struct weston_resize_grab { struct shell_grab base; uint32_t edges; int32_t width, height; }; static void resize_grab_motion(struct weston_pointer_grab *grab, const struct timespec *time, struct weston_pointer_motion_event *event) { struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; struct weston_pointer *pointer = grab->pointer; struct shell_surface *shsurf = resize->base.shsurf; int32_t width, height; struct weston_size min_size, max_size; wl_fixed_t from_x, from_y; wl_fixed_t to_x, to_y; weston_pointer_move(pointer, event); if (!shsurf) return; weston_view_from_global_fixed(shsurf->view, pointer->grab_x, pointer->grab_y, &from_x, &from_y); weston_view_from_global_fixed(shsurf->view, pointer->x, pointer->y, &to_x, &to_y); width = resize->width; if (resize->edges & WL_SHELL_SURFACE_RESIZE_LEFT) { width += wl_fixed_to_int(from_x - to_x); } else if (resize->edges & WL_SHELL_SURFACE_RESIZE_RIGHT) { width += wl_fixed_to_int(to_x - from_x); } height = resize->height; if (resize->edges & WL_SHELL_SURFACE_RESIZE_TOP) { height += wl_fixed_to_int(from_y - to_y); } else if (resize->edges & WL_SHELL_SURFACE_RESIZE_BOTTOM) { height += wl_fixed_to_int(to_y - from_y); } max_size = weston_desktop_surface_get_max_size(shsurf->desktop_surface); min_size = weston_desktop_surface_get_min_size(shsurf->desktop_surface); min_size.width = MAX(1, min_size.width); min_size.height = MAX(1, min_size.height); if (width < min_size.width) width = min_size.width; else if (max_size.width > 0 && width > max_size.width) width = max_size.width; if (height < min_size.height) height = min_size.height; else if (max_size.width > 0 && width > max_size.width) width = max_size.width; weston_desktop_surface_set_size(shsurf->desktop_surface, width, height); } static void resize_grab_button(struct weston_pointer_grab *grab, const struct timespec *time, uint32_t button, uint32_t state_w) { struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; struct weston_pointer *pointer = grab->pointer; enum wl_pointer_button_state state = state_w; if (pointer->button_count == 0 && state == WL_POINTER_BUTTON_STATE_RELEASED) { if (resize->base.shsurf != NULL) { struct weston_desktop_surface *desktop_surface = resize->base.shsurf->desktop_surface; weston_desktop_surface_set_resizing(desktop_surface, false); } shell_grab_end(&resize->base); free(grab); } } static void resize_grab_cancel(struct weston_pointer_grab *grab) { struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; if (resize->base.shsurf != NULL) { struct weston_desktop_surface *desktop_surface = resize->base.shsurf->desktop_surface; weston_desktop_surface_set_resizing(desktop_surface, false); } shell_grab_end(&resize->base); free(grab); } static const struct weston_pointer_grab_interface resize_grab_interface = { noop_grab_focus, resize_grab_motion, resize_grab_button, noop_grab_axis, noop_grab_axis_source, noop_grab_frame, resize_grab_cancel, }; /* * Returns the bounding box of a surface and all its sub-surfaces, * in surface-local coordinates. */ static void surface_subsurfaces_boundingbox(struct weston_surface *surface, int32_t *x, int32_t *y, int32_t *w, int32_t *h) { pixman_region32_t region; pixman_box32_t *box; struct weston_subsurface *subsurface; pixman_region32_init_rect(®ion, 0, 0, surface->width, surface->height); wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) { pixman_region32_union_rect(®ion, ®ion, subsurface->position.x, subsurface->position.y, subsurface->surface->width, subsurface->surface->height); } box = pixman_region32_extents(®ion); if (x) *x = box->x1; if (y) *y = box->y1; if (w) *w = box->x2 - box->x1; if (h) *h = box->y2 - box->y1; pixman_region32_fini(®ion); } static int surface_resize(struct shell_surface *shsurf, struct weston_pointer *pointer, uint32_t edges) { struct weston_resize_grab *resize; const unsigned resize_topbottom = WL_SHELL_SURFACE_RESIZE_TOP | WL_SHELL_SURFACE_RESIZE_BOTTOM; const unsigned resize_leftright = WL_SHELL_SURFACE_RESIZE_LEFT | WL_SHELL_SURFACE_RESIZE_RIGHT; const unsigned resize_any = resize_topbottom | resize_leftright; struct weston_geometry geometry; if (shsurf->grabbed || weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || weston_desktop_surface_get_maximized(shsurf->desktop_surface)) return 0; /* Check for invalid edge combinations. */ if (edges == WL_SHELL_SURFACE_RESIZE_NONE || edges > resize_any || (edges & resize_topbottom) == resize_topbottom || (edges & resize_leftright) == resize_leftright) return 0; resize = malloc(sizeof *resize); if (!resize) return -1; resize->edges = edges; geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); resize->width = geometry.width; resize->height = geometry.height; shsurf->resize_edges = edges; weston_desktop_surface_set_resizing(shsurf->desktop_surface, true); shell_grab_start(&resize->base, &resize_grab_interface, shsurf, pointer, edges); return 0; } static void busy_cursor_grab_focus(struct weston_pointer_grab *base) { struct shell_grab *grab = (struct shell_grab *) base; struct weston_pointer *pointer = base->pointer; struct weston_desktop_surface *desktop_surface; struct weston_view *view; wl_fixed_t sx, sy; view = weston_compositor_pick_view(pointer->seat->compositor, pointer->x, pointer->y, &sx, &sy); desktop_surface = weston_surface_get_desktop_surface(view->surface); if (!grab->shsurf || grab->shsurf->desktop_surface != desktop_surface) { shell_grab_end(grab); free(grab); } } static void busy_cursor_grab_motion(struct weston_pointer_grab *grab, const struct timespec *time, struct weston_pointer_motion_event *event) { weston_pointer_move(grab->pointer, event); } static void busy_cursor_grab_button(struct weston_pointer_grab *base, const struct timespec *time, uint32_t button, uint32_t state) { struct shell_grab *grab = (struct shell_grab *) base; struct shell_surface *shsurf = grab->shsurf; struct weston_pointer *pointer = grab->grab.pointer; struct weston_seat *seat = pointer->seat; if (shsurf && button == BTN_LEFT && state) { activate(shsurf->shell, shsurf->view, seat, WESTON_ACTIVATE_FLAG_CONFIGURE); surface_move(shsurf, pointer, false); } else if (shsurf && button == BTN_RIGHT && state) { activate(shsurf->shell, shsurf->view, seat, WESTON_ACTIVATE_FLAG_CONFIGURE); surface_rotate(shsurf, pointer); } } static void busy_cursor_grab_cancel(struct weston_pointer_grab *base) { struct shell_grab *grab = (struct shell_grab *) base; shell_grab_end(grab); free(grab); } static const struct weston_pointer_grab_interface busy_cursor_grab_interface = { busy_cursor_grab_focus, busy_cursor_grab_motion, busy_cursor_grab_button, noop_grab_axis, noop_grab_axis_source, noop_grab_frame, busy_cursor_grab_cancel, }; static void handle_pointer_focus(struct wl_listener *listener, void *data) { struct weston_pointer *pointer = data; struct weston_view *view = pointer->focus; struct shell_surface *shsurf; struct weston_desktop_client *client; if (!view) return; shsurf = get_shell_surface(view->surface); if (!shsurf) return; client = weston_desktop_surface_get_client(shsurf->desktop_surface); if (shsurf->unresponsive) set_busy_cursor(shsurf, pointer); else weston_desktop_client_ping(client); } static void shell_surface_lose_keyboard_focus(struct shell_surface *shsurf) { if (--shsurf->focus_count == 0) weston_desktop_surface_set_activated(shsurf->desktop_surface, false); } static void shell_surface_gain_keyboard_focus(struct shell_surface *shsurf) { if (shsurf->focus_count++ == 0) weston_desktop_surface_set_activated(shsurf->desktop_surface, true); } static void handle_keyboard_focus(struct wl_listener *listener, void *data) { struct weston_keyboard *keyboard = data; struct shell_seat *seat = get_shell_seat(keyboard->seat); if (seat->focused_surface) { struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); if (shsurf) shell_surface_lose_keyboard_focus(shsurf); } seat->focused_surface = weston_surface_get_main_surface(keyboard->focus); if (seat->focused_surface) { struct shell_surface *shsurf = get_shell_surface(seat->focused_surface); if (shsurf) shell_surface_gain_keyboard_focus(shsurf); } } /* The surface will be inserted into the list immediately after the link * returned by this function (i.e. will be stacked immediately above the * returned link). */ static struct weston_layer_entry * shell_surface_calculate_layer_link (struct shell_surface *shsurf) { struct workspace *ws; if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) && !shsurf->state.lowered) { return &shsurf->shell->fullscreen_layer.view_list; } /* Move the surface to a normal workspace layer so that surfaces * which were previously fullscreen or transient are no longer * rendered on top. */ ws = get_current_workspace(shsurf->shell); return &ws->layer.view_list; } static void shell_surface_update_child_surface_layers (struct shell_surface *shsurf) { weston_desktop_surface_propagate_layer(shsurf->desktop_surface); } /* Update the surface’s layer. Mark both the old and new views as having dirty * geometry to ensure the changes are redrawn. * * If any child surfaces exist and are mapped, ensure they’re in the same layer * as this surface. */ static void shell_surface_update_layer(struct shell_surface *shsurf) { struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); struct weston_layer_entry *new_layer_link; new_layer_link = shell_surface_calculate_layer_link(shsurf); if (new_layer_link == NULL) return; if (new_layer_link == &shsurf->view->layer_link) return; weston_view_geometry_dirty(shsurf->view); weston_layer_entry_remove(&shsurf->view->layer_link); weston_layer_entry_insert(new_layer_link, &shsurf->view->layer_link); weston_view_geometry_dirty(shsurf->view); weston_surface_damage(surface); shell_surface_update_child_surface_layers(shsurf); } static void notify_output_destroy(struct wl_listener *listener, void *data) { struct shell_surface *shsurf = container_of(listener, struct shell_surface, output_destroy_listener); shsurf->output = NULL; shsurf->output_destroy_listener.notify = NULL; } static void shell_surface_set_output(struct shell_surface *shsurf, struct weston_output *output) { struct weston_surface *es = weston_desktop_surface_get_surface(shsurf->desktop_surface); /* get the default output, if the client set it as NULL check whether the output is available */ if (output) shsurf->output = output; else if (es->output) shsurf->output = es->output; else shsurf->output = get_default_output(es->compositor); if (shsurf->output_destroy_listener.notify) { wl_list_remove(&shsurf->output_destroy_listener.link); shsurf->output_destroy_listener.notify = NULL; } if (!shsurf->output) return; shsurf->output_destroy_listener.notify = notify_output_destroy; wl_signal_add(&shsurf->output->destroy_signal, &shsurf->output_destroy_listener); } static void weston_view_set_initial_position(struct weston_view *view, struct desktop_shell *shell); static void unset_fullscreen(struct shell_surface *shsurf) { /* Unset the fullscreen output, driver configuration and transforms. */ wl_list_remove(&shsurf->fullscreen.transform.link); wl_list_init(&shsurf->fullscreen.transform.link); if (shsurf->fullscreen.black_view) weston_surface_destroy(shsurf->fullscreen.black_view->surface); shsurf->fullscreen.black_view = NULL; if (shsurf->saved_position_valid) weston_view_set_position(shsurf->view, shsurf->saved_x, shsurf->saved_y); else weston_view_set_initial_position(shsurf->view, shsurf->shell); shsurf->saved_position_valid = false; if (shsurf->saved_rotation_valid) { wl_list_insert(&shsurf->view->geometry.transformation_list, &shsurf->rotation.transform.link); shsurf->saved_rotation_valid = false; } } static void unset_maximized(struct shell_surface *shsurf) { struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); /* undo all maximized things here */ shell_surface_set_output(shsurf, get_default_output(surface->compositor)); if (shsurf->saved_position_valid) weston_view_set_position(shsurf->view, shsurf->saved_x, shsurf->saved_y); else weston_view_set_initial_position(shsurf->view, shsurf->shell); shsurf->saved_position_valid = false; if (shsurf->saved_rotation_valid) { wl_list_insert(&shsurf->view->geometry.transformation_list, &shsurf->rotation.transform.link); shsurf->saved_rotation_valid = false; } } static void set_minimized(struct weston_surface *surface) { struct shell_surface *shsurf; struct workspace *current_ws; struct weston_view *view; view = get_default_view(surface); if (!view) return; assert(weston_surface_get_main_surface(view->surface) == view->surface); shsurf = get_shell_surface(surface); current_ws = get_current_workspace(shsurf->shell); weston_layer_entry_remove(&view->layer_link); weston_layer_entry_insert(&shsurf->shell->minimized_layer.view_list, &view->layer_link); drop_focus_state(shsurf->shell, current_ws, view->surface); surface_keyboard_focus_lost(surface); shell_surface_update_child_surface_layers(shsurf); weston_view_damage_below(view); } static struct desktop_shell * shell_surface_get_shell(struct shell_surface *shsurf) { return shsurf->shell; } static int black_surface_get_label(struct weston_surface *surface, char *buf, size_t len) { struct weston_view *fs_view = surface->committed_private; struct weston_surface *fs_surface = fs_view->surface; int n; int rem; int ret; n = snprintf(buf, len, "black background surface for "); if (n < 0) return n; rem = (int)len - n; if (rem < 0) rem = 0; if (fs_surface->get_label) ret = fs_surface->get_label(fs_surface, buf + n, rem); else ret = snprintf(buf + n, rem, ""); if (ret < 0) return n; return n + ret; } static void black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy); static struct weston_view * create_black_surface(struct weston_compositor *ec, struct weston_view *fs_view, float x, float y, int w, int h) { struct weston_surface *surface = NULL; struct weston_view *view; surface = weston_surface_create(ec); if (surface == NULL) { weston_log("no memory\n"); return NULL; } view = weston_view_create(surface); if (surface == NULL) { weston_log("no memory\n"); weston_surface_destroy(surface); return NULL; } surface->committed = black_surface_committed; surface->committed_private = fs_view; weston_surface_set_label_func(surface, black_surface_get_label); weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1); pixman_region32_fini(&surface->opaque); pixman_region32_init_rect(&surface->opaque, 0, 0, w, h); pixman_region32_fini(&surface->input); pixman_region32_init_rect(&surface->input, 0, 0, w, h); weston_surface_set_size(surface, w, h); weston_view_set_position(view, x, y); return view; } static void shell_ensure_fullscreen_black_view(struct shell_surface *shsurf) { struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); struct weston_output *output = shsurf->fullscreen_output; assert(weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)); if (!shsurf->fullscreen.black_view) shsurf->fullscreen.black_view = create_black_surface(surface->compositor, shsurf->view, output->x, output->y, output->width, output->height); weston_view_geometry_dirty(shsurf->fullscreen.black_view); weston_layer_entry_remove(&shsurf->fullscreen.black_view->layer_link); weston_layer_entry_insert(&shsurf->view->layer_link, &shsurf->fullscreen.black_view->layer_link); weston_view_geometry_dirty(shsurf->fullscreen.black_view); weston_surface_damage(surface); shsurf->fullscreen.black_view->is_mapped = true; shsurf->state.lowered = false; } /* Create black surface and append it to the associated fullscreen surface. * Handle size dismatch and positioning according to the method. */ static void shell_configure_fullscreen(struct shell_surface *shsurf) { struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); int32_t surf_x, surf_y, surf_width, surf_height; /* Reverse the effect of lower_fullscreen_layer() */ weston_layer_entry_remove(&shsurf->view->layer_link); weston_layer_entry_insert(&shsurf->shell->fullscreen_layer.view_list, &shsurf->view->layer_link); if (!shsurf->fullscreen_output) { /* If there is no output, there's not much we can do. * Position the window somewhere, whatever. */ weston_view_set_position(shsurf->view, 0, 0); return; } shell_ensure_fullscreen_black_view(shsurf); surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y, &surf_width, &surf_height); if (surface->buffer_ref.buffer) center_on_output(shsurf->view, shsurf->fullscreen_output); } static void shell_map_fullscreen(struct shell_surface *shsurf) { shell_configure_fullscreen(shsurf); } static struct weston_output * get_focused_output(struct weston_compositor *compositor) { struct weston_seat *seat; struct weston_output *output = NULL; wl_list_for_each(seat, &compositor->seat_list, link) { struct weston_touch *touch = weston_seat_get_touch(seat); struct weston_pointer *pointer = weston_seat_get_pointer(seat); struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); /* Priority has touch focus, then pointer and * then keyboard focus. We should probably have * three for loops and check first for touch, * then for pointer, etc. but unless somebody has some * objections, I think this is sufficient. */ if (touch && touch->focus) output = touch->focus->output; else if (pointer && pointer->focus) output = pointer->focus->output; else if (keyboard && keyboard->focus) output = keyboard->focus->output; if (output) break; } return output; } static void destroy_shell_seat(struct wl_listener *listener, void *data) { struct shell_seat *shseat = container_of(listener, struct shell_seat, seat_destroy_listener); wl_list_remove(&shseat->seat_destroy_listener.link); free(shseat); } static void shell_seat_caps_changed(struct wl_listener *listener, void *data) { struct weston_keyboard *keyboard; struct weston_pointer *pointer; struct shell_seat *seat; seat = container_of(listener, struct shell_seat, caps_changed_listener); keyboard = weston_seat_get_keyboard(seat->seat); pointer = weston_seat_get_pointer(seat->seat); if (keyboard && wl_list_empty(&seat->keyboard_focus_listener.link)) { wl_signal_add(&keyboard->focus_signal, &seat->keyboard_focus_listener); } else if (!keyboard) { wl_list_remove(&seat->keyboard_focus_listener.link); wl_list_init(&seat->keyboard_focus_listener.link); } if (pointer && wl_list_empty(&seat->pointer_focus_listener.link)) { wl_signal_add(&pointer->focus_signal, &seat->pointer_focus_listener); } else if (!pointer) { wl_list_remove(&seat->pointer_focus_listener.link); wl_list_init(&seat->pointer_focus_listener.link); } } static struct shell_seat * create_shell_seat(struct weston_seat *seat) { struct shell_seat *shseat; shseat = calloc(1, sizeof *shseat); if (!shseat) { weston_log("no memory to allocate shell seat\n"); return NULL; } shseat->seat = seat; shseat->seat_destroy_listener.notify = destroy_shell_seat; wl_signal_add(&seat->destroy_signal, &shseat->seat_destroy_listener); shseat->keyboard_focus_listener.notify = handle_keyboard_focus; wl_list_init(&shseat->keyboard_focus_listener.link); shseat->pointer_focus_listener.notify = handle_pointer_focus; wl_list_init(&shseat->pointer_focus_listener.link); shseat->caps_changed_listener.notify = shell_seat_caps_changed; wl_signal_add(&seat->updated_caps_signal, &shseat->caps_changed_listener); shell_seat_caps_changed(&shseat->caps_changed_listener, NULL); return shseat; } static struct shell_seat * get_shell_seat(struct weston_seat *seat) { struct wl_listener *listener; listener = wl_signal_get(&seat->destroy_signal, destroy_shell_seat); assert(listener != NULL); return container_of(listener, struct shell_seat, seat_destroy_listener); } static void fade_out_done_idle_cb(void *data) { struct shell_surface *shsurf = data; weston_surface_destroy(shsurf->view->surface); if (shsurf->output_destroy_listener.notify) { wl_list_remove(&shsurf->output_destroy_listener.link); shsurf->output_destroy_listener.notify = NULL; } free(shsurf); } static void fade_out_done(struct weston_view_animation *animation, void *data) { struct shell_surface *shsurf = data; struct wl_event_loop *loop; loop = wl_display_get_event_loop(shsurf->shell->compositor->wl_display); if (weston_view_is_mapped(shsurf->view)) { weston_view_unmap(shsurf->view); wl_event_loop_add_idle(loop, fade_out_done_idle_cb, shsurf); } } struct shell_surface * get_shell_surface(struct weston_surface *surface) { if (weston_surface_is_desktop_surface(surface)) { struct weston_desktop_surface *desktop_surface = weston_surface_get_desktop_surface(surface); return weston_desktop_surface_get_user_data(desktop_surface); } return NULL; } /* * libweston-desktop */ static void desktop_surface_added(struct weston_desktop_surface *desktop_surface, void *shell) { struct weston_desktop_client *client = weston_desktop_surface_get_client(desktop_surface); struct wl_client *wl_client = weston_desktop_client_get_client(client); struct weston_view *view; struct shell_surface *shsurf; struct weston_surface *surface = weston_desktop_surface_get_surface(desktop_surface); view = weston_desktop_surface_create_view(desktop_surface); if (!view) return; shsurf = calloc(1, sizeof *shsurf); if (!shsurf) { if (wl_client) wl_client_post_no_memory(wl_client); else weston_log("no memory to allocate shell surface\n"); return; } weston_surface_set_label_func(surface, shell_surface_get_label); shsurf->shell = (struct desktop_shell *) shell; shsurf->unresponsive = 0; shsurf->saved_position_valid = false; shsurf->saved_rotation_valid = false; shsurf->desktop_surface = desktop_surface; shsurf->view = view; shsurf->fullscreen.black_view = NULL; wl_list_init(&shsurf->fullscreen.transform.link); shell_surface_set_output( shsurf, get_default_output(shsurf->shell->compositor)); wl_signal_init(&shsurf->destroy_signal); /* empty when not in use */ wl_list_init(&shsurf->rotation.transform.link); weston_matrix_init(&shsurf->rotation.rotation); wl_list_init(&shsurf->workspace_transform.link); /* * initialize list as well as link. The latter allows to use * wl_list_remove() even when this surface is not in another list. */ wl_list_init(&shsurf->children_list); wl_list_init(&shsurf->children_link); weston_desktop_surface_set_user_data(desktop_surface, shsurf); weston_desktop_surface_set_activated(desktop_surface, shsurf->focus_count > 0); } static void desktop_surface_removed(struct weston_desktop_surface *desktop_surface, void *shell) { struct shell_surface *shsurf = weston_desktop_surface_get_user_data(desktop_surface); struct shell_surface *shsurf_child, *tmp; struct weston_surface *surface = weston_desktop_surface_get_surface(desktop_surface); if (!shsurf) return; wl_list_for_each_safe(shsurf_child, tmp, &shsurf->children_list, children_link) { wl_list_remove(&shsurf_child->children_link); wl_list_init(&shsurf_child->children_link); } wl_list_remove(&shsurf->children_link); wl_signal_emit(&shsurf->destroy_signal, shsurf); if (shsurf->fullscreen.black_view) weston_surface_destroy(shsurf->fullscreen.black_view->surface); weston_surface_set_label_func(surface, NULL); weston_desktop_surface_set_user_data(shsurf->desktop_surface, NULL); shsurf->desktop_surface = NULL; weston_desktop_surface_unlink_view(shsurf->view); if (weston_surface_is_mapped(surface) && shsurf->shell->win_close_animation_type == ANIMATION_FADE) { pixman_region32_fini(&surface->pending.input); pixman_region32_init(&surface->pending.input); pixman_region32_fini(&surface->input); pixman_region32_init(&surface->input); weston_fade_run(shsurf->view, 1.0, 0.0, 300.0, fade_out_done, shsurf); } else { weston_view_destroy(shsurf->view); if (shsurf->output_destroy_listener.notify) { wl_list_remove(&shsurf->output_destroy_listener.link); shsurf->output_destroy_listener.notify = NULL; } free(shsurf); } } static void set_maximized_position(struct desktop_shell *shell, struct shell_surface *shsurf) { pixman_rectangle32_t area; struct weston_geometry geometry; get_output_work_area(shell, shsurf->output, &area); geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); weston_view_set_position(shsurf->view, area.x - geometry.x, area.y - geometry.y); } static void set_position_from_xwayland(struct shell_surface *shsurf) { struct weston_geometry geometry; float x; float y; assert(shsurf->xwayland.is_set); geometry = weston_desktop_surface_get_geometry(shsurf->desktop_surface); x = shsurf->xwayland.x - geometry.x; y = shsurf->xwayland.y - geometry.y; weston_view_set_position(shsurf->view, x, y); #ifdef WM_DEBUG weston_log("%s: XWM %d, %d; geometry %d, %d; view %f, %f\n", __func__, shsurf->xwayland.x, shsurf->xwayland.y, geometry.x, geometry.y, x, y); #endif } static void map(struct desktop_shell *shell, struct shell_surface *shsurf, int32_t sx, int32_t sy) { struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); struct weston_compositor *compositor = shell->compositor; struct weston_seat *seat; /* initial positioning, see also configure() */ if (shsurf->state.fullscreen) { center_on_output(shsurf->view, shsurf->fullscreen_output); shell_map_fullscreen(shsurf); } else if (shsurf->state.maximized) { set_maximized_position(shell, shsurf); } else if (shsurf->xwayland.is_set) { set_position_from_xwayland(shsurf); } else { weston_view_set_initial_position(shsurf->view, shell); } /* Surface stacking order, see also activate(). */ shell_surface_update_layer(shsurf); weston_view_update_transform(shsurf->view); shsurf->view->is_mapped = true; if (shsurf->state.maximized) { surface->output = shsurf->output; weston_view_set_output(shsurf->view, shsurf->output); } if (!shell->locked) { wl_list_for_each(seat, &compositor->seat_list, link) activate(shell, shsurf->view, seat, WESTON_ACTIVATE_FLAG_CONFIGURE); } if (!shsurf->state.fullscreen && !shsurf->state.maximized) { switch (shell->win_animation_type) { case ANIMATION_FADE: weston_fade_run(shsurf->view, 0.0, 1.0, 300.0, NULL, NULL); break; case ANIMATION_ZOOM: weston_zoom_run(shsurf->view, 0.5, 1.0, NULL, NULL); break; case ANIMATION_NONE: default: break; } } } static void desktop_surface_committed(struct weston_desktop_surface *desktop_surface, int32_t sx, int32_t sy, void *data) { struct shell_surface *shsurf = weston_desktop_surface_get_user_data(desktop_surface); struct weston_surface *surface = weston_desktop_surface_get_surface(desktop_surface); struct weston_view *view = shsurf->view; struct desktop_shell *shell = data; bool was_fullscreen; bool was_maximized; if (surface->width == 0) return; was_fullscreen = shsurf->state.fullscreen; was_maximized = shsurf->state.maximized; shsurf->state.fullscreen = weston_desktop_surface_get_fullscreen(desktop_surface); shsurf->state.maximized = weston_desktop_surface_get_maximized(desktop_surface); if (!weston_surface_is_mapped(surface)) { map(shell, shsurf, sx, sy); surface->is_mapped = true; if (shsurf->shell->win_close_animation_type == ANIMATION_FADE) ++surface->ref_count; return; } if (sx == 0 && sy == 0 && shsurf->last_width == surface->width && shsurf->last_height == surface->height && was_fullscreen == shsurf->state.fullscreen && was_maximized == shsurf->state.maximized) return; if (was_fullscreen) unset_fullscreen(shsurf); if (was_maximized) unset_maximized(shsurf); if ((shsurf->state.fullscreen || shsurf->state.maximized) && !shsurf->saved_position_valid) { shsurf->saved_x = shsurf->view->geometry.x; shsurf->saved_y = shsurf->view->geometry.y; shsurf->saved_position_valid = true; if (!wl_list_empty(&shsurf->rotation.transform.link)) { wl_list_remove(&shsurf->rotation.transform.link); wl_list_init(&shsurf->rotation.transform.link); weston_view_geometry_dirty(shsurf->view); shsurf->saved_rotation_valid = true; } } if (shsurf->state.fullscreen) { shell_configure_fullscreen(shsurf); } else if (shsurf->state.maximized) { set_maximized_position(shell, shsurf); surface->output = shsurf->output; } else { float from_x, from_y; float to_x, to_y; float x, y; if (shsurf->resize_edges) { sx = 0; sy = 0; } if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_LEFT) sx = shsurf->last_width - surface->width; if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_TOP) sy = shsurf->last_height - surface->height; weston_view_to_global_float(shsurf->view, 0, 0, &from_x, &from_y); weston_view_to_global_float(shsurf->view, sx, sy, &to_x, &to_y); x = shsurf->view->geometry.x + to_x - from_x; y = shsurf->view->geometry.y + to_y - from_y; weston_view_set_position(shsurf->view, x, y); } shsurf->last_width = surface->width; shsurf->last_height = surface->height; /* XXX: would a fullscreen surface need the same handling? */ if (surface->output) { wl_list_for_each(view, &surface->views, surface_link) weston_view_update_transform(view); } } static void get_maximized_size(struct shell_surface *shsurf, int32_t *width, int32_t *height) { struct desktop_shell *shell; pixman_rectangle32_t area; shell = shell_surface_get_shell(shsurf); get_output_work_area(shell, shsurf->output, &area); *width = area.width; *height = area.height; } static void set_fullscreen(struct shell_surface *shsurf, bool fullscreen, struct weston_output *output) { struct weston_desktop_surface *desktop_surface = shsurf->desktop_surface; struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); int32_t width = 0, height = 0; if (fullscreen) { /* handle clients launching in fullscreen */ if (output == NULL && !weston_surface_is_mapped(surface)) { /* Set the output to the one that has focus currently. */ output = get_focused_output(surface->compositor); } shell_surface_set_output(shsurf, output); shsurf->fullscreen_output = shsurf->output; width = shsurf->output->width; height = shsurf->output->height; } else if (weston_desktop_surface_get_maximized(desktop_surface)) { get_maximized_size(shsurf, &width, &height); } weston_desktop_surface_set_fullscreen(desktop_surface, fullscreen); weston_desktop_surface_set_size(desktop_surface, width, height); } static void desktop_surface_move(struct weston_desktop_surface *desktop_surface, struct weston_seat *seat, uint32_t serial, void *shell) { struct weston_pointer *pointer = weston_seat_get_pointer(seat); struct weston_touch *touch = weston_seat_get_touch(seat); struct shell_surface *shsurf = weston_desktop_surface_get_user_data(desktop_surface); struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); struct wl_resource *resource = surface->resource; struct weston_surface *focus; if (pointer && pointer->focus && pointer->button_count > 0 && pointer->grab_serial == serial) { focus = weston_surface_get_main_surface(pointer->focus->surface); if ((focus == surface) && (surface_move(shsurf, pointer, true) < 0)) wl_resource_post_no_memory(resource); } else if (touch && touch->focus && touch->grab_serial == serial) { focus = weston_surface_get_main_surface(touch->focus->surface); if ((focus == surface) && (surface_touch_move(shsurf, touch) < 0)) wl_resource_post_no_memory(resource); } } static void desktop_surface_resize(struct weston_desktop_surface *desktop_surface, struct weston_seat *seat, uint32_t serial, enum weston_desktop_surface_edge edges, void *shell) { struct weston_pointer *pointer = weston_seat_get_pointer(seat); struct shell_surface *shsurf = weston_desktop_surface_get_user_data(desktop_surface); struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); struct wl_resource *resource = surface->resource; struct weston_surface *focus; if (!pointer || pointer->button_count == 0 || pointer->grab_serial != serial || pointer->focus == NULL) return; focus = weston_surface_get_main_surface(pointer->focus->surface); if (focus != surface) return; if (surface_resize(shsurf, pointer, edges) < 0) wl_resource_post_no_memory(resource); } static void desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, struct weston_desktop_surface *parent, void *shell) { struct shell_surface *shsurf_parent; struct shell_surface *shsurf = weston_desktop_surface_get_user_data(desktop_surface); /* unlink any potential child */ wl_list_remove(&shsurf->children_link); if (parent) { shsurf_parent = weston_desktop_surface_get_user_data(parent); wl_list_insert(shsurf_parent->children_list.prev, &shsurf->children_link); } else { wl_list_init(&shsurf->children_link); } } static void desktop_surface_fullscreen_requested(struct weston_desktop_surface *desktop_surface, bool fullscreen, struct weston_output *output, void *shell) { struct shell_surface *shsurf = weston_desktop_surface_get_user_data(desktop_surface); set_fullscreen(shsurf, fullscreen, output); } static void set_maximized(struct shell_surface *shsurf, bool maximized) { struct weston_desktop_surface *desktop_surface = shsurf->desktop_surface; struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); int32_t width = 0, height = 0; if (maximized) { struct weston_output *output; if (!weston_surface_is_mapped(surface)) output = get_focused_output(surface->compositor); else output = surface->output; shell_surface_set_output(shsurf, output); get_maximized_size(shsurf, &width, &height); } weston_desktop_surface_set_maximized(desktop_surface, maximized); weston_desktop_surface_set_size(desktop_surface, width, height); } static void desktop_surface_maximized_requested(struct weston_desktop_surface *desktop_surface, bool maximized, void *shell) { struct shell_surface *shsurf = weston_desktop_surface_get_user_data(desktop_surface); set_maximized(shsurf, maximized); } static void desktop_surface_minimized_requested(struct weston_desktop_surface *desktop_surface, void *shell) { struct weston_surface *surface = weston_desktop_surface_get_surface(desktop_surface); /* apply compositor's own minimization logic (hide) */ set_minimized(surface); } static void set_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer) { struct shell_grab *grab; if (pointer->grab->interface == &busy_cursor_grab_interface) return; grab = malloc(sizeof *grab); if (!grab) return; shell_grab_start(grab, &busy_cursor_grab_interface, shsurf, pointer, WESTON_DESKTOP_SHELL_CURSOR_BUSY); /* Mark the shsurf as ungrabbed so that button binding is able * to move it. */ shsurf->grabbed = 0; } static void end_busy_cursor(struct weston_compositor *compositor, struct weston_desktop_client *desktop_client) { struct shell_surface *shsurf; struct shell_grab *grab; struct weston_seat *seat; wl_list_for_each(seat, &compositor->seat_list, link) { struct weston_pointer *pointer = weston_seat_get_pointer(seat); struct weston_desktop_client *grab_client; if (!pointer) continue; if (pointer->grab->interface != &busy_cursor_grab_interface) continue; grab = (struct shell_grab *) pointer->grab; shsurf = grab->shsurf; if (!shsurf) continue; grab_client = weston_desktop_surface_get_client(shsurf->desktop_surface); if (grab_client == desktop_client) { shell_grab_end(grab); free(grab); } } } static void desktop_surface_set_unresponsive(struct weston_desktop_surface *desktop_surface, void *user_data) { struct shell_surface *shsurf = weston_desktop_surface_get_user_data(desktop_surface); bool *unresponsive = user_data; shsurf->unresponsive = *unresponsive; } static void desktop_surface_ping_timeout(struct weston_desktop_client *desktop_client, void *shell_) { struct desktop_shell *shell = shell_; struct shell_surface *shsurf; struct weston_seat *seat; bool unresponsive = true; weston_desktop_client_for_each_surface(desktop_client, desktop_surface_set_unresponsive, &unresponsive); wl_list_for_each(seat, &shell->compositor->seat_list, link) { struct weston_pointer *pointer = weston_seat_get_pointer(seat); struct weston_desktop_client *grab_client; if (!pointer || !pointer->focus) continue; shsurf = get_shell_surface(pointer->focus->surface); if (!shsurf) continue; grab_client = weston_desktop_surface_get_client(shsurf->desktop_surface); if (grab_client == desktop_client) set_busy_cursor(shsurf, pointer); } } static void desktop_surface_pong(struct weston_desktop_client *desktop_client, void *shell_) { struct desktop_shell *shell = shell_; bool unresponsive = false; weston_desktop_client_for_each_surface(desktop_client, desktop_surface_set_unresponsive, &unresponsive); end_busy_cursor(shell->compositor, desktop_client); } static void desktop_surface_set_xwayland_position(struct weston_desktop_surface *surface, int32_t x, int32_t y, void *shell_) { struct shell_surface *shsurf = weston_desktop_surface_get_user_data(surface); shsurf->xwayland.x = x; shsurf->xwayland.y = y; shsurf->xwayland.is_set = true; } static const struct weston_desktop_api shell_desktop_api = { .struct_size = sizeof(struct weston_desktop_api), .surface_added = desktop_surface_added, .surface_removed = desktop_surface_removed, .committed = desktop_surface_committed, .move = desktop_surface_move, .resize = desktop_surface_resize, .set_parent = desktop_surface_set_parent, .fullscreen_requested = desktop_surface_fullscreen_requested, .maximized_requested = desktop_surface_maximized_requested, .minimized_requested = desktop_surface_minimized_requested, .ping_timeout = desktop_surface_ping_timeout, .pong = desktop_surface_pong, .set_xwayland_position = desktop_surface_set_xwayland_position, }; /* ************************ * * end of libweston-desktop * * ************************ */ static void configure_static_view(struct weston_view *ev, struct weston_layer *layer, int x, int y) { struct weston_view *v, *next; if (!ev->output) return; wl_list_for_each_safe(v, next, &layer->view_list.link, layer_link.link) { if (v->output == ev->output && v != ev) { weston_view_unmap(v); v->surface->committed = NULL; weston_surface_set_label_func(v->surface, NULL); } } weston_view_set_position(ev, ev->output->x + x, ev->output->y + y); ev->surface->is_mapped = true; ev->is_mapped = true; if (wl_list_empty(&ev->layer_link.link)) { weston_layer_entry_insert(&layer->view_list, &ev->layer_link); weston_compositor_schedule_repaint(ev->surface->compositor); } } static struct shell_output * find_shell_output_from_weston_output(struct desktop_shell *shell, struct weston_output *output) { struct shell_output *shell_output; wl_list_for_each(shell_output, &shell->output_list, link) { if (shell_output->output == output) return shell_output; } return NULL; } static int background_get_label(struct weston_surface *surface, char *buf, size_t len) { return snprintf(buf, len, "background for output %s", (surface->output ? surface->output->name : "NULL")); } static void background_committed(struct weston_surface *es, int32_t sx, int32_t sy) { struct desktop_shell *shell = es->committed_private; struct weston_view *view; view = container_of(es->views.next, struct weston_view, surface_link); configure_static_view(view, &shell->background_layer, 0, 0); } static void handle_background_surface_destroy(struct wl_listener *listener, void *data) { struct shell_output *output = container_of(listener, struct shell_output, background_surface_listener); weston_log("background surface gone\n"); wl_list_remove(&output->background_surface_listener.link); output->background_surface = NULL; } static void desktop_shell_set_background(struct wl_client *client, struct wl_resource *resource, struct wl_resource *output_resource, struct wl_resource *surface_resource) { struct desktop_shell *shell = wl_resource_get_user_data(resource); struct weston_surface *surface = wl_resource_get_user_data(surface_resource); struct shell_output *sh_output; struct weston_view *view, *next; if (surface->committed) { wl_resource_post_error(surface_resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "surface role already assigned"); return; } wl_list_for_each_safe(view, next, &surface->views, surface_link) weston_view_destroy(view); view = weston_view_create(surface); surface->committed = background_committed; surface->committed_private = shell; weston_surface_set_label_func(surface, background_get_label); surface->output = weston_head_from_resource(output_resource)->output; weston_view_set_output(view, surface->output); sh_output = find_shell_output_from_weston_output(shell, surface->output); if (sh_output->background_surface) { /* The output already has a background, tell our helper * there is no need for another one. */ weston_desktop_shell_send_configure(resource, 0, surface_resource, 0, 0); } else { weston_desktop_shell_send_configure(resource, 0, surface_resource, surface->output->width, surface->output->height); sh_output->background_surface = surface; sh_output->background_surface_listener.notify = handle_background_surface_destroy; wl_signal_add(&surface->destroy_signal, &sh_output->background_surface_listener); } } static int panel_get_label(struct weston_surface *surface, char *buf, size_t len) { return snprintf(buf, len, "panel for output %s", (surface->output ? surface->output->name : "NULL")); } static void panel_committed(struct weston_surface *es, int32_t sx, int32_t sy) { struct desktop_shell *shell = es->committed_private; struct weston_view *view; int width, height; int x = 0, y = 0; view = container_of(es->views.next, struct weston_view, surface_link); get_panel_size(shell, view, &width, &height); switch (shell->panel_position) { case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: break; case WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM: y = view->output->height - height; break; case WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT: break; case WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT: x = view->output->width - width; break; } configure_static_view(view, &shell->panel_layer, x, y); } static void handle_panel_surface_destroy(struct wl_listener *listener, void *data) { struct shell_output *output = container_of(listener, struct shell_output, panel_surface_listener); weston_log("panel surface gone\n"); wl_list_remove(&output->panel_surface_listener.link); output->panel_surface = NULL; } static void desktop_shell_set_panel(struct wl_client *client, struct wl_resource *resource, struct wl_resource *output_resource, struct wl_resource *surface_resource) { struct desktop_shell *shell = wl_resource_get_user_data(resource); struct weston_surface *surface = wl_resource_get_user_data(surface_resource); struct weston_view *view, *next; struct shell_output *sh_output; if (surface->committed) { wl_resource_post_error(surface_resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "surface role already assigned"); return; } wl_list_for_each_safe(view, next, &surface->views, surface_link) weston_view_destroy(view); view = weston_view_create(surface); surface->committed = panel_committed; surface->committed_private = shell; weston_surface_set_label_func(surface, panel_get_label); surface->output = weston_head_from_resource(output_resource)->output; weston_view_set_output(view, surface->output); sh_output = find_shell_output_from_weston_output(shell, surface->output); if (sh_output->panel_surface) { /* The output already has a panel, tell our helper * there is no need for another one. */ weston_desktop_shell_send_configure(resource, 0, surface_resource, 0, 0); } else { weston_desktop_shell_send_configure(resource, 0, surface_resource, surface->output->width, surface->output->height); sh_output->panel_surface = surface; sh_output->panel_surface_listener.notify = handle_panel_surface_destroy; wl_signal_add(&surface->destroy_signal, &sh_output->panel_surface_listener); } } static int lock_surface_get_label(struct weston_surface *surface, char *buf, size_t len) { return snprintf(buf, len, "lock window"); } static void lock_surface_committed(struct weston_surface *surface, int32_t sx, int32_t sy) { struct desktop_shell *shell = surface->committed_private; struct weston_view *view; view = container_of(surface->views.next, struct weston_view, surface_link); if (surface->width == 0) return; center_on_output(view, get_default_output(shell->compositor)); if (!weston_surface_is_mapped(surface)) { weston_layer_entry_insert(&shell->lock_layer.view_list, &view->layer_link); weston_view_update_transform(view); surface->is_mapped = true; view->is_mapped = true; shell_fade(shell, FADE_IN); } } static void handle_lock_surface_destroy(struct wl_listener *listener, void *data) { struct desktop_shell *shell = container_of(listener, struct desktop_shell, lock_surface_listener); weston_log("lock surface gone\n"); shell->lock_surface = NULL; } static void desktop_shell_set_lock_surface(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource) { struct desktop_shell *shell = wl_resource_get_user_data(resource); struct weston_surface *surface = wl_resource_get_user_data(surface_resource); shell->prepare_event_sent = false; if (!shell->locked) return; shell->lock_surface = surface; shell->lock_surface_listener.notify = handle_lock_surface_destroy; wl_signal_add(&surface->destroy_signal, &shell->lock_surface_listener); weston_view_create(surface); surface->committed = lock_surface_committed; surface->committed_private = shell; weston_surface_set_label_func(surface, lock_surface_get_label); } static void resume_desktop(struct desktop_shell *shell) { struct workspace *ws = get_current_workspace(shell); weston_layer_unset_position(&shell->lock_layer); if (shell->showing_input_panels) weston_layer_set_position(&shell->input_panel_layer, WESTON_LAYER_POSITION_TOP_UI); weston_layer_set_position(&shell->fullscreen_layer, WESTON_LAYER_POSITION_FULLSCREEN); weston_layer_set_position(&shell->panel_layer, WESTON_LAYER_POSITION_UI); weston_layer_set_position(&ws->layer, WESTON_LAYER_POSITION_NORMAL); restore_focus_state(shell, get_current_workspace(shell)); shell->locked = false; shell_fade(shell, FADE_IN); weston_compositor_damage_all(shell->compositor); } static void desktop_shell_unlock(struct wl_client *client, struct wl_resource *resource) { struct desktop_shell *shell = wl_resource_get_user_data(resource); shell->prepare_event_sent = false; if (shell->locked) resume_desktop(shell); } static void desktop_shell_set_grab_surface(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource) { struct desktop_shell *shell = wl_resource_get_user_data(resource); shell->grab_surface = wl_resource_get_user_data(surface_resource); weston_view_create(shell->grab_surface); } static void desktop_shell_desktop_ready(struct wl_client *client, struct wl_resource *resource) { struct desktop_shell *shell = wl_resource_get_user_data(resource); shell_fade_startup(shell); } static void desktop_shell_set_panel_position(struct wl_client *client, struct wl_resource *resource, uint32_t position) { struct desktop_shell *shell = wl_resource_get_user_data(resource); if (position != WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP && position != WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM && position != WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT && position != WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT) { wl_resource_post_error(resource, WESTON_DESKTOP_SHELL_ERROR_INVALID_ARGUMENT, "bad position argument"); return; } shell->panel_position = position; } static const struct weston_desktop_shell_interface desktop_shell_implementation = { desktop_shell_set_background, desktop_shell_set_panel, desktop_shell_set_lock_surface, desktop_shell_unlock, desktop_shell_set_grab_surface, desktop_shell_desktop_ready, desktop_shell_set_panel_position }; static void move_binding(struct weston_pointer *pointer, const struct timespec *time, uint32_t button, void *data) { struct weston_surface *focus; struct weston_surface *surface; struct shell_surface *shsurf; if (pointer->focus == NULL) return; focus = pointer->focus->surface; surface = weston_surface_get_main_surface(focus); if (surface == NULL) return; shsurf = get_shell_surface(surface); if (shsurf == NULL || weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || weston_desktop_surface_get_maximized(shsurf->desktop_surface)) return; surface_move(shsurf, pointer, false); } static void maximize_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t button, void *data) { struct weston_surface *focus = keyboard->focus; struct weston_surface *surface; struct shell_surface *shsurf; surface = weston_surface_get_main_surface(focus); if (surface == NULL) return; shsurf = get_shell_surface(surface); if (shsurf == NULL) return; set_maximized(shsurf, !weston_desktop_surface_get_maximized(shsurf->desktop_surface)); } static void fullscreen_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t button, void *data) { struct weston_surface *focus = keyboard->focus; struct weston_surface *surface; struct shell_surface *shsurf; bool fullscreen; surface = weston_surface_get_main_surface(focus); if (surface == NULL) return; shsurf = get_shell_surface(surface); if (shsurf == NULL) return; fullscreen = weston_desktop_surface_get_fullscreen(shsurf->desktop_surface); set_fullscreen(shsurf, !fullscreen, NULL); } static void touch_move_binding(struct weston_touch *touch, const struct timespec *time, void *data) { struct weston_surface *focus; struct weston_surface *surface; struct shell_surface *shsurf; if (touch->focus == NULL) return; focus = touch->focus->surface; surface = weston_surface_get_main_surface(focus); if (surface == NULL) return; shsurf = get_shell_surface(surface); if (shsurf == NULL || weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || weston_desktop_surface_get_maximized(shsurf->desktop_surface)) return; surface_touch_move(shsurf, touch); } static void resize_binding(struct weston_pointer *pointer, const struct timespec *time, uint32_t button, void *data) { struct weston_surface *focus; struct weston_surface *surface; uint32_t edges = 0; int32_t x, y; struct shell_surface *shsurf; if (pointer->focus == NULL) return; focus = pointer->focus->surface; surface = weston_surface_get_main_surface(focus); if (surface == NULL) return; shsurf = get_shell_surface(surface); if (shsurf == NULL || weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) || weston_desktop_surface_get_maximized(shsurf->desktop_surface)) return; weston_view_from_global(shsurf->view, wl_fixed_to_int(pointer->grab_x), wl_fixed_to_int(pointer->grab_y), &x, &y); if (x < surface->width / 3) edges |= WL_SHELL_SURFACE_RESIZE_LEFT; else if (x < 2 * surface->width / 3) edges |= 0; else edges |= WL_SHELL_SURFACE_RESIZE_RIGHT; if (y < surface->height / 3) edges |= WL_SHELL_SURFACE_RESIZE_TOP; else if (y < 2 * surface->height / 3) edges |= 0; else edges |= WL_SHELL_SURFACE_RESIZE_BOTTOM; surface_resize(shsurf, pointer, edges); } static void surface_opacity_binding(struct weston_pointer *pointer, const struct timespec *time, struct weston_pointer_axis_event *event, void *data) { float step = 0.005; struct shell_surface *shsurf; struct weston_surface *focus = pointer->focus->surface; struct weston_surface *surface; /* XXX: broken for windows containing sub-surfaces */ surface = weston_surface_get_main_surface(focus); if (surface == NULL) return; shsurf = get_shell_surface(surface); if (!shsurf) return; shsurf->view->alpha -= event->value * step; if (shsurf->view->alpha > 1.0) shsurf->view->alpha = 1.0; if (shsurf->view->alpha < step) shsurf->view->alpha = step; weston_view_geometry_dirty(shsurf->view); weston_surface_damage(surface); } static void do_zoom(struct weston_seat *seat, const struct timespec *time, uint32_t key, uint32_t axis, double value) { struct weston_compositor *compositor = seat->compositor; struct weston_pointer *pointer = weston_seat_get_pointer(seat); struct weston_output *output; float increment; if (!pointer) { weston_log("Zoom hotkey pressed but seat '%s' contains no pointer.\n", seat->seat_name); return; } wl_list_for_each(output, &compositor->output_list, link) { if (pixman_region32_contains_point(&output->region, wl_fixed_to_double(pointer->x), wl_fixed_to_double(pointer->y), NULL)) { if (key == KEY_PAGEUP) increment = output->zoom.increment; else if (key == KEY_PAGEDOWN) increment = -output->zoom.increment; else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) /* For every pixel zoom 20th of a step */ increment = output->zoom.increment * -value / 20.0; else increment = 0; output->zoom.level += increment; if (output->zoom.level < 0.0) output->zoom.level = 0.0; else if (output->zoom.level > output->zoom.max_level) output->zoom.level = output->zoom.max_level; if (!output->zoom.active) { if (output->zoom.level <= 0.0) continue; weston_output_activate_zoom(output, seat); } output->zoom.spring_z.target = output->zoom.level; weston_output_update_zoom(output); } } } static void zoom_axis_binding(struct weston_pointer *pointer, const struct timespec *time, struct weston_pointer_axis_event *event, void *data) { do_zoom(pointer->seat, time, 0, event->axis, event->value); } static void zoom_key_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { do_zoom(keyboard->seat, time, key, 0, 0); } static void terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct weston_compositor *compositor = data; weston_compositor_exit(compositor); } static void rotate_grab_motion(struct weston_pointer_grab *grab, const struct timespec *time, struct weston_pointer_motion_event *event) { struct rotate_grab *rotate = container_of(grab, struct rotate_grab, base.grab); struct weston_pointer *pointer = grab->pointer; struct shell_surface *shsurf = rotate->base.shsurf; struct weston_surface *surface; float cx, cy, dx, dy, cposx, cposy, dposx, dposy, r; weston_pointer_move(pointer, event); if (!shsurf) return; surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); cx = 0.5f * surface->width; cy = 0.5f * surface->height; dx = wl_fixed_to_double(pointer->x) - rotate->center.x; dy = wl_fixed_to_double(pointer->y) - rotate->center.y; r = sqrtf(dx * dx + dy * dy); wl_list_remove(&shsurf->rotation.transform.link); weston_view_geometry_dirty(shsurf->view); if (r > 20.0f) { struct weston_matrix *matrix = &shsurf->rotation.transform.matrix; weston_matrix_init(&rotate->rotation); weston_matrix_rotate_xy(&rotate->rotation, dx / r, dy / r); weston_matrix_init(matrix); weston_matrix_translate(matrix, -cx, -cy, 0.0f); weston_matrix_multiply(matrix, &shsurf->rotation.rotation); weston_matrix_multiply(matrix, &rotate->rotation); weston_matrix_translate(matrix, cx, cy, 0.0f); wl_list_insert( &shsurf->view->geometry.transformation_list, &shsurf->rotation.transform.link); } else { wl_list_init(&shsurf->rotation.transform.link); weston_matrix_init(&shsurf->rotation.rotation); weston_matrix_init(&rotate->rotation); } /* We need to adjust the position of the surface * in case it was resized in a rotated state before */ cposx = shsurf->view->geometry.x + cx; cposy = shsurf->view->geometry.y + cy; dposx = rotate->center.x - cposx; dposy = rotate->center.y - cposy; if (dposx != 0.0f || dposy != 0.0f) { weston_view_set_position(shsurf->view, shsurf->view->geometry.x + dposx, shsurf->view->geometry.y + dposy); } /* Repaint implies weston_view_update_transform(), which * lazily applies the damage due to rotation update. */ weston_compositor_schedule_repaint(surface->compositor); } static void rotate_grab_button(struct weston_pointer_grab *grab, const struct timespec *time, uint32_t button, uint32_t state_w) { struct rotate_grab *rotate = container_of(grab, struct rotate_grab, base.grab); struct weston_pointer *pointer = grab->pointer; struct shell_surface *shsurf = rotate->base.shsurf; enum wl_pointer_button_state state = state_w; if (pointer->button_count == 0 && state == WL_POINTER_BUTTON_STATE_RELEASED) { if (shsurf) weston_matrix_multiply(&shsurf->rotation.rotation, &rotate->rotation); shell_grab_end(&rotate->base); free(rotate); } } static void rotate_grab_cancel(struct weston_pointer_grab *grab) { struct rotate_grab *rotate = container_of(grab, struct rotate_grab, base.grab); shell_grab_end(&rotate->base); free(rotate); } static const struct weston_pointer_grab_interface rotate_grab_interface = { noop_grab_focus, rotate_grab_motion, rotate_grab_button, noop_grab_axis, noop_grab_axis_source, noop_grab_frame, rotate_grab_cancel, }; static void surface_rotate(struct shell_surface *shsurf, struct weston_pointer *pointer) { struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); struct rotate_grab *rotate; float dx, dy; float r; rotate = malloc(sizeof *rotate); if (!rotate) return; weston_view_to_global_float(shsurf->view, surface->width * 0.5f, surface->height * 0.5f, &rotate->center.x, &rotate->center.y); dx = wl_fixed_to_double(pointer->x) - rotate->center.x; dy = wl_fixed_to_double(pointer->y) - rotate->center.y; r = sqrtf(dx * dx + dy * dy); if (r > 20.0f) { struct weston_matrix inverse; weston_matrix_init(&inverse); weston_matrix_rotate_xy(&inverse, dx / r, -dy / r); weston_matrix_multiply(&shsurf->rotation.rotation, &inverse); weston_matrix_init(&rotate->rotation); weston_matrix_rotate_xy(&rotate->rotation, dx / r, dy / r); } else { weston_matrix_init(&shsurf->rotation.rotation); weston_matrix_init(&rotate->rotation); } shell_grab_start(&rotate->base, &rotate_grab_interface, shsurf, pointer, WESTON_DESKTOP_SHELL_CURSOR_ARROW); } static void rotate_binding(struct weston_pointer *pointer, const struct timespec *time, uint32_t button, void *data) { struct weston_surface *focus; struct weston_surface *base_surface; struct shell_surface *surface; if (pointer->focus == NULL) return; focus = pointer->focus->surface; base_surface = weston_surface_get_main_surface(focus); if (base_surface == NULL) return; surface = get_shell_surface(base_surface); if (surface == NULL || weston_desktop_surface_get_fullscreen(surface->desktop_surface) || weston_desktop_surface_get_maximized(surface->desktop_surface)) return; surface_rotate(surface, pointer); } /* Move all fullscreen layers down to the current workspace and hide their * black views. The surfaces' state is set to both fullscreen and lowered, * and this is reversed when such a surface is re-configured, see * shell_configure_fullscreen() and shell_ensure_fullscreen_black_view(). * * lowering_output = NULL - Lower on all outputs, else only lower on the * specified output. * * This should be used when implementing shell-wide overlays, such as * the alt-tab switcher, which need to de-promote fullscreen layers. */ void lower_fullscreen_layer(struct desktop_shell *shell, struct weston_output *lowering_output) { struct workspace *ws; struct weston_view *view, *prev; ws = get_current_workspace(shell); wl_list_for_each_reverse_safe(view, prev, &shell->fullscreen_layer.view_list.link, layer_link.link) { struct shell_surface *shsurf = get_shell_surface(view->surface); if (!shsurf) continue; /* Only lower surfaces which have lowering_output as their fullscreen * output, unless a NULL output asks for lowering on all outputs. */ if (lowering_output && (shsurf->fullscreen_output != lowering_output)) continue; /* We can have a non-fullscreen popup for a fullscreen surface * in the fullscreen layer. */ if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)) { /* Hide the black view */ weston_layer_entry_remove(&shsurf->fullscreen.black_view->layer_link); wl_list_init(&shsurf->fullscreen.black_view->layer_link.link); weston_view_damage_below(shsurf->fullscreen.black_view); } /* Lower the view to the workspace layer */ weston_layer_entry_remove(&view->layer_link); weston_layer_entry_insert(&ws->layer.view_list, &view->layer_link); weston_view_damage_below(view); weston_surface_damage(view->surface); shsurf->state.lowered = true; } } static struct shell_surface *get_last_child(struct shell_surface *shsurf) { struct shell_surface *shsurf_child; wl_list_for_each_reverse(shsurf_child, &shsurf->children_list, children_link) { if (weston_view_is_mapped(shsurf_child->view)) return shsurf_child; } return NULL; } void activate(struct desktop_shell *shell, struct weston_view *view, struct weston_seat *seat, uint32_t flags) { struct weston_surface *es = view->surface; struct weston_surface *main_surface; struct focus_state *state; struct workspace *ws; struct weston_surface *old_es; struct shell_surface *shsurf, *shsurf_child; main_surface = weston_surface_get_main_surface(es); shsurf = get_shell_surface(main_surface); assert(shsurf); shsurf_child = get_last_child(shsurf); if (shsurf_child) { /* Activate last xdg child instead of parent. */ activate(shell, shsurf_child->view, seat, flags); return; } /* Only demote fullscreen surfaces on the output of activated shsurf. * Leave fullscreen surfaces on unrelated outputs alone. */ if (shsurf->output) lower_fullscreen_layer(shell, shsurf->output); weston_view_activate(view, seat, flags); state = ensure_focus_state(shell, seat); if (state == NULL) return; old_es = state->keyboard_focus; focus_state_set_focus(state, es); if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface) && flags & WESTON_ACTIVATE_FLAG_CONFIGURE) shell_configure_fullscreen(shsurf); /* Update the surface’s layer. This brings it to the top of the stacking * order as appropriate. */ shell_surface_update_layer(shsurf); if (shell->focus_animation_type != ANIMATION_NONE) { ws = get_current_workspace(shell); animate_focus_change(shell, ws, get_default_view(old_es), get_default_view(es)); } } /* no-op func for checking black surface */ static void black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) { } static bool is_black_surface_view(struct weston_view *view, struct weston_view **fs_view) { struct weston_surface *surface = view->surface; if (surface->committed == black_surface_committed) { if (fs_view) *fs_view = surface->committed_private; return true; } return false; } static void activate_binding(struct weston_seat *seat, struct desktop_shell *shell, struct weston_view *focus_view, uint32_t flags) { struct weston_view *main_view; struct weston_surface *main_surface; if (!focus_view) return; if (is_black_surface_view(focus_view, &main_view)) focus_view = main_view; main_surface = weston_surface_get_main_surface(focus_view->surface); if (!get_shell_surface(main_surface)) return; activate(shell, focus_view, seat, flags); } static void click_to_activate_binding(struct weston_pointer *pointer, const struct timespec *time, uint32_t button, void *data) { if (pointer->grab != &pointer->default_grab) return; if (pointer->focus == NULL) return; activate_binding(pointer->seat, data, pointer->focus, WESTON_ACTIVATE_FLAG_CLICKED | WESTON_ACTIVATE_FLAG_CONFIGURE); } static void touch_to_activate_binding(struct weston_touch *touch, const struct timespec *time, void *data) { if (touch->grab != &touch->default_grab) return; if (touch->focus == NULL) return; activate_binding(touch->seat, data, touch->focus, WESTON_ACTIVATE_FLAG_CONFIGURE); } static void unfocus_all_seats(struct desktop_shell *shell) { struct weston_seat *seat, *next; wl_list_for_each_safe(seat, next, &shell->compositor->seat_list, link) { struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); if (!keyboard) continue; weston_keyboard_set_focus(keyboard, NULL); } } static void lock(struct desktop_shell *shell) { struct workspace *ws = get_current_workspace(shell); if (shell->locked) { weston_compositor_sleep(shell->compositor); return; } shell->locked = true; /* Hide all surfaces by removing the fullscreen, panel and * toplevel layers. This way nothing else can show or receive * input events while we are locked. */ weston_layer_unset_position(&shell->panel_layer); weston_layer_unset_position(&shell->fullscreen_layer); if (shell->showing_input_panels) weston_layer_unset_position(&shell->input_panel_layer); weston_layer_unset_position(&ws->layer); weston_layer_set_position(&shell->lock_layer, WESTON_LAYER_POSITION_LOCK); weston_compositor_sleep(shell->compositor); /* Remove the keyboard focus on all seats. This will be * restored to the workspace's saved state via * restore_focus_state when the compositor is unlocked */ unfocus_all_seats(shell); /* TODO: disable bindings that should not work while locked. */ /* All this must be undone in resume_desktop(). */ } static void unlock(struct desktop_shell *shell) { struct wl_resource *shell_resource; if (!shell->locked || shell->lock_surface) { shell_fade(shell, FADE_IN); return; } /* If desktop-shell client has gone away, unlock immediately. */ if (!shell->child.desktop_shell) { resume_desktop(shell); return; } if (shell->prepare_event_sent) return; shell_resource = shell->child.desktop_shell; weston_desktop_shell_send_prepare_lock_surface(shell_resource); shell->prepare_event_sent = true; } static void shell_fade_done_for_output(struct weston_view_animation *animation, void *data) { struct shell_output *shell_output = data; struct desktop_shell *shell = shell_output->shell; shell_output->fade.animation = NULL; switch (shell_output->fade.type) { case FADE_IN: weston_surface_destroy(shell_output->fade.view->surface); shell_output->fade.view = NULL; break; case FADE_OUT: lock(shell); break; default: break; } } static struct weston_view * shell_fade_create_surface_for_output(struct desktop_shell *shell, struct shell_output *shell_output) { struct weston_compositor *compositor = shell->compositor; struct weston_surface *surface; struct weston_view *view; surface = weston_surface_create(compositor); if (!surface) return NULL; view = weston_view_create(surface); if (!view) { weston_surface_destroy(surface); return NULL; } weston_surface_set_size(surface, shell_output->output->width, shell_output->output->height); weston_view_set_position(view, shell_output->output->x, shell_output->output->y); weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1.0); weston_layer_entry_insert(&compositor->fade_layer.view_list, &view->layer_link); pixman_region32_init(&surface->input); surface->is_mapped = true; view->is_mapped = true; return view; } static void shell_fade(struct desktop_shell *shell, enum fade_type type) { float tint; struct shell_output *shell_output; switch (type) { case FADE_IN: tint = 0.0; break; case FADE_OUT: tint = 1.0; break; default: weston_log("shell: invalid fade type\n"); return; } /* Create a separate fade surface for each output */ wl_list_for_each(shell_output, &shell->output_list, link) { shell_output->fade.type = type; if (shell_output->fade.view == NULL) { shell_output->fade.view = shell_fade_create_surface_for_output(shell, shell_output); if (!shell_output->fade.view) continue; shell_output->fade.view->alpha = 1.0 - tint; weston_view_update_transform(shell_output->fade.view); } if (shell_output->fade.view->output == NULL) { /* If the black view gets a NULL output, we lost the * last output and we'll just cancel the fade. This * happens when you close the last window under the * X11 or Wayland backends. */ shell->locked = false; weston_surface_destroy(shell_output->fade.view->surface); shell_output->fade.view = NULL; } else if (shell_output->fade.animation) { weston_fade_update(shell_output->fade.animation, tint); } else { shell_output->fade.animation = weston_fade_run(shell_output->fade.view, 1.0 - tint, tint, 300.0, shell_fade_done_for_output, shell_output); } } } static void do_shell_fade_startup(void *data) { struct desktop_shell *shell = data; struct shell_output *shell_output; if (shell->startup_animation_type == ANIMATION_FADE) { shell_fade(shell, FADE_IN); } else { weston_log("desktop shell: " "unexpected fade-in animation type %d\n", shell->startup_animation_type); wl_list_for_each(shell_output, &shell->output_list, link) { weston_surface_destroy(shell_output->fade.view->surface); shell_output->fade.view = NULL; } } } static void shell_fade_startup(struct desktop_shell *shell) { struct wl_event_loop *loop; struct shell_output *shell_output; bool has_fade = false; wl_list_for_each(shell_output, &shell->output_list, link) { if (!shell_output->fade.startup_timer) continue; wl_event_source_remove(shell_output->fade.startup_timer); shell_output->fade.startup_timer = NULL; has_fade = true; } if (has_fade) { loop = wl_display_get_event_loop(shell->compositor->wl_display); wl_event_loop_add_idle(loop, do_shell_fade_startup, shell); } } static int fade_startup_timeout(void *data) { struct desktop_shell *shell = data; shell_fade_startup(shell); return 0; } static void shell_fade_init(struct desktop_shell *shell) { /* Make compositor output all black, and wait for the desktop-shell * client to signal it is ready, then fade in. The timer triggers a * fade-in, in case the desktop-shell client takes too long. */ struct wl_event_loop *loop; struct shell_output *shell_output; if (shell->startup_animation_type == ANIMATION_NONE) return; wl_list_for_each(shell_output, &shell->output_list, link) { if (shell_output->fade.view != NULL) { weston_log("%s: warning: fade surface already exists\n", __func__); continue; } shell_output->fade.view = shell_fade_create_surface_for_output(shell, shell_output); if (!shell_output->fade.view) continue; weston_view_update_transform(shell_output->fade.view); weston_surface_damage(shell_output->fade.view->surface); loop = wl_display_get_event_loop(shell->compositor->wl_display); shell_output->fade.startup_timer = wl_event_loop_add_timer(loop, fade_startup_timeout, shell); wl_event_source_timer_update(shell_output->fade.startup_timer, 15000); } } static void idle_handler(struct wl_listener *listener, void *data) { struct desktop_shell *shell = container_of(listener, struct desktop_shell, idle_listener); struct weston_seat *seat; wl_list_for_each(seat, &shell->compositor->seat_list, link) weston_seat_break_desktop_grabs(seat); shell_fade(shell, FADE_OUT); /* lock() is called from shell_fade_done_for_output() */ } static void wake_handler(struct wl_listener *listener, void *data) { struct desktop_shell *shell = container_of(listener, struct desktop_shell, wake_listener); unlock(shell); } static void transform_handler(struct wl_listener *listener, void *data) { struct weston_surface *surface = data; struct shell_surface *shsurf = get_shell_surface(surface); const struct weston_xwayland_surface_api *api; int x, y; if (!shsurf) return; api = shsurf->shell->xwayland_surface_api; if (!api) { api = weston_xwayland_surface_get_api(shsurf->shell->compositor); shsurf->shell->xwayland_surface_api = api; } if (!api || !api->is_xwayland_surface(surface)) return; if (!weston_view_is_mapped(shsurf->view)) return; x = shsurf->view->geometry.x; y = shsurf->view->geometry.y; api->send_position(surface, x, y); } static void center_on_output(struct weston_view *view, struct weston_output *output) { int32_t surf_x, surf_y, width, height; float x, y; if (!output) { weston_view_set_position(view, 0, 0); return; } surface_subsurfaces_boundingbox(view->surface, &surf_x, &surf_y, &width, &height); x = output->x + (output->width - width) / 2 - surf_x / 2; y = output->y + (output->height - height) / 2 - surf_y / 2; weston_view_set_position(view, x, y); } static void weston_view_set_initial_position(struct weston_view *view, struct desktop_shell *shell) { struct weston_compositor *compositor = shell->compositor; int ix = 0, iy = 0; int32_t range_x, range_y; int32_t x, y; struct weston_output *output, *target_output = NULL; struct weston_seat *seat; pixman_rectangle32_t area; /* As a heuristic place the new window on the same output as the * pointer. Falling back to the output containing 0, 0. * * TODO: Do something clever for touch too? */ wl_list_for_each(seat, &compositor->seat_list, link) { struct weston_pointer *pointer = weston_seat_get_pointer(seat); if (pointer) { ix = wl_fixed_to_int(pointer->x); iy = wl_fixed_to_int(pointer->y); break; } } wl_list_for_each(output, &compositor->output_list, link) { if (pixman_region32_contains_point(&output->region, ix, iy, NULL)) { target_output = output; break; } } if (!target_output) { weston_view_set_position(view, 10 + random() % 400, 10 + random() % 400); return; } /* Valid range within output where the surface will still be onscreen. * If this is negative it means that the surface is bigger than * output. */ get_output_work_area(shell, target_output, &area); x = area.x; y = area.y; range_x = area.width - view->surface->width; range_y = area.height - view->surface->height; if (range_x > 0) x += random() % range_x; if (range_y > 0) y += random() % range_y; weston_view_set_position(view, x, y); } static bool check_desktop_shell_crash_too_early(struct desktop_shell *shell) { struct timespec now; if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) return false; /* * If the shell helper client dies before the session has been * up for roughly 30 seconds, better just make Weston shut down, * because the user likely has no way to interact with the desktop * anyway. */ if (now.tv_sec - shell->startup_time.tv_sec < 30) { weston_log("Error: %s apparently cannot run at all.\n", shell->client); weston_log_continue(STAMP_SPACE "Quitting..."); weston_compositor_exit_with_code(shell->compositor, EXIT_FAILURE); return true; } return false; } static void launch_desktop_shell_process(void *data); static void respawn_desktop_shell_process(struct desktop_shell *shell) { struct timespec time; /* if desktop-shell dies more than 5 times in 30 seconds, give up */ weston_compositor_get_time(&time); if (timespec_sub_to_msec(&time, &shell->child.deathstamp) > 30000) { shell->child.deathstamp = time; shell->child.deathcount = 0; } shell->child.deathcount++; if (shell->child.deathcount > 5) { weston_log("%s disconnected, giving up.\n", shell->client); return; } weston_log("%s disconnected, respawning...\n", shell->client); launch_desktop_shell_process(shell); } static void desktop_shell_client_destroy(struct wl_listener *listener, void *data) { struct desktop_shell *shell; shell = container_of(listener, struct desktop_shell, child.client_destroy_listener); wl_list_remove(&shell->child.client_destroy_listener.link); shell->child.client = NULL; /* * unbind_desktop_shell() will reset shell->child.desktop_shell * before the respawned process has a chance to create a new * desktop_shell object, because we are being called from the * wl_client destructor which destroys all wl_resources before * returning. */ if (!check_desktop_shell_crash_too_early(shell)) respawn_desktop_shell_process(shell); shell_fade_startup(shell); } static void launch_desktop_shell_process(void *data) { struct desktop_shell *shell = data; shell->child.client = weston_client_start(shell->compositor, shell->client); if (!shell->child.client) { weston_log("not able to start %s\n", shell->client); return; } shell->child.client_destroy_listener.notify = desktop_shell_client_destroy; wl_client_add_destroy_listener(shell->child.client, &shell->child.client_destroy_listener); } static void unbind_desktop_shell(struct wl_resource *resource) { struct desktop_shell *shell = wl_resource_get_user_data(resource); if (shell->locked) resume_desktop(shell); shell->child.desktop_shell = NULL; shell->prepare_event_sent = false; } static void bind_desktop_shell(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct desktop_shell *shell = data; struct wl_resource *resource; resource = wl_resource_create(client, &weston_desktop_shell_interface, 1, id); if (client == shell->child.client) { wl_resource_set_implementation(resource, &desktop_shell_implementation, shell, unbind_desktop_shell); shell->child.desktop_shell = resource; return; } wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "permission to bind desktop_shell denied"); } struct switcher { struct desktop_shell *shell; struct weston_view *current; struct wl_listener listener; struct weston_keyboard_grab grab; struct wl_array minimized_array; }; static void switcher_next(struct switcher *switcher) { struct weston_view *view; struct weston_view *first = NULL, *prev = NULL, *next = NULL; struct shell_surface *shsurf; struct workspace *ws = get_current_workspace(switcher->shell); /* temporary re-display minimized surfaces */ struct weston_view *tmp; struct weston_view **minimized; wl_list_for_each_safe(view, tmp, &switcher->shell->minimized_layer.view_list.link, layer_link.link) { weston_layer_entry_remove(&view->layer_link); weston_layer_entry_insert(&ws->layer.view_list, &view->layer_link); minimized = wl_array_add(&switcher->minimized_array, sizeof *minimized); *minimized = view; } wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { shsurf = get_shell_surface(view->surface); if (shsurf) { if (first == NULL) first = view; if (prev == switcher->current) next = view; prev = view; view->alpha = 0.25; weston_view_geometry_dirty(view); weston_surface_damage(view->surface); } if (is_black_surface_view(view, NULL)) { view->alpha = 0.25; weston_view_geometry_dirty(view); weston_surface_damage(view->surface); } } if (next == NULL) next = first; if (next == NULL) return; wl_list_remove(&switcher->listener.link); wl_signal_add(&next->destroy_signal, &switcher->listener); switcher->current = next; wl_list_for_each(view, &next->surface->views, surface_link) view->alpha = 1.0; shsurf = get_shell_surface(switcher->current->surface); if (shsurf && weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)) shsurf->fullscreen.black_view->alpha = 1.0; } static void switcher_handle_view_destroy(struct wl_listener *listener, void *data) { struct switcher *switcher = container_of(listener, struct switcher, listener); switcher_next(switcher); } static void switcher_destroy(struct switcher *switcher) { struct weston_view *view; struct weston_keyboard *keyboard = switcher->grab.keyboard; struct workspace *ws = get_current_workspace(switcher->shell); wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { if (is_focus_view(view)) continue; view->alpha = 1.0; weston_surface_damage(view->surface); } if (switcher->current) { activate(switcher->shell, switcher->current, keyboard->seat, WESTON_ACTIVATE_FLAG_CONFIGURE); } wl_list_remove(&switcher->listener.link); weston_keyboard_end_grab(keyboard); if (keyboard->input_method_resource) keyboard->grab = &keyboard->input_method_grab; /* re-hide surfaces that were temporary shown during the switch */ struct weston_view **minimized; wl_array_for_each(minimized, &switcher->minimized_array) { /* with the exception of the current selected */ if ((*minimized)->surface != switcher->current->surface) { weston_layer_entry_remove(&(*minimized)->layer_link); weston_layer_entry_insert(&switcher->shell->minimized_layer.view_list, &(*minimized)->layer_link); weston_view_damage_below(*minimized); } } wl_array_release(&switcher->minimized_array); free(switcher); } static void switcher_key(struct weston_keyboard_grab *grab, const struct timespec *time, uint32_t key, uint32_t state_w) { struct switcher *switcher = container_of(grab, struct switcher, grab); enum wl_keyboard_key_state state = state_w; if (key == KEY_TAB && state == WL_KEYBOARD_KEY_STATE_PRESSED) switcher_next(switcher); } static void switcher_modifier(struct weston_keyboard_grab *grab, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { struct switcher *switcher = container_of(grab, struct switcher, grab); struct weston_seat *seat = grab->keyboard->seat; if ((seat->modifier_state & switcher->shell->binding_modifier) == 0) switcher_destroy(switcher); } static void switcher_cancel(struct weston_keyboard_grab *grab) { struct switcher *switcher = container_of(grab, struct switcher, grab); switcher_destroy(switcher); } static const struct weston_keyboard_grab_interface switcher_grab = { switcher_key, switcher_modifier, switcher_cancel, }; static void switcher_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct desktop_shell *shell = data; struct switcher *switcher; switcher = malloc(sizeof *switcher); switcher->shell = shell; switcher->current = NULL; switcher->listener.notify = switcher_handle_view_destroy; wl_list_init(&switcher->listener.link); wl_array_init(&switcher->minimized_array); lower_fullscreen_layer(switcher->shell, NULL); switcher->grab.interface = &switcher_grab; weston_keyboard_start_grab(keyboard, &switcher->grab); weston_keyboard_set_focus(keyboard, NULL); switcher_next(switcher); } static void backlight_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct weston_compositor *compositor = data; struct weston_output *output; long backlight_new = 0; /* TODO: we're limiting to simple use cases, where we assume just * control on the primary display. We'd have to extend later if we * ever get support for setting backlights on random desktop LCD * panels though */ output = get_default_output(compositor); if (!output) return; if (!output->set_backlight) return; if (key == KEY_F9 || key == KEY_BRIGHTNESSDOWN) backlight_new = output->backlight_current - 25; else if (key == KEY_F10 || key == KEY_BRIGHTNESSUP) backlight_new = output->backlight_current + 25; if (backlight_new < 5) backlight_new = 5; if (backlight_new > 255) backlight_new = 255; output->backlight_current = backlight_new; output->set_backlight(output, output->backlight_current); } static void force_kill_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct weston_surface *focus_surface; struct wl_client *client; struct desktop_shell *shell = data; struct weston_compositor *compositor = shell->compositor; pid_t pid; focus_surface = keyboard->focus; if (!focus_surface) return; wl_signal_emit(&compositor->kill_signal, focus_surface); client = wl_resource_get_client(focus_surface->resource); wl_client_get_credentials(client, &pid, NULL, NULL); /* Skip clients that we launched ourselves (the credentials of * the socketpair is ours) */ if (pid == getpid()) return; kill(pid, SIGKILL); } static void workspace_up_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct desktop_shell *shell = data; unsigned int new_index = shell->workspaces.current; if (shell->locked) return; if (new_index != 0) new_index--; change_workspace(shell, new_index); } static void workspace_down_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct desktop_shell *shell = data; unsigned int new_index = shell->workspaces.current; if (shell->locked) return; if (new_index < shell->workspaces.num - 1) new_index++; change_workspace(shell, new_index); } static void workspace_f_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct desktop_shell *shell = data; unsigned int new_index; if (shell->locked) return; new_index = key - KEY_F1; if (new_index >= shell->workspaces.num) new_index = shell->workspaces.num - 1; change_workspace(shell, new_index); } static void workspace_move_surface_up_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct desktop_shell *shell = data; unsigned int new_index = shell->workspaces.current; if (shell->locked) return; if (new_index != 0) new_index--; take_surface_to_workspace_by_seat(shell, keyboard->seat, new_index); } static void workspace_move_surface_down_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct desktop_shell *shell = data; unsigned int new_index = shell->workspaces.current; if (shell->locked) return; if (new_index < shell->workspaces.num - 1) new_index++; take_surface_to_workspace_by_seat(shell, keyboard->seat, new_index); } static void shell_reposition_view_on_output_change(struct weston_view *view) { struct weston_output *output, *first_output; struct weston_compositor *ec = view->surface->compositor; struct shell_surface *shsurf; float x, y; int visible; if (wl_list_empty(&ec->output_list)) return; x = view->geometry.x; y = view->geometry.y; /* At this point the destroyed output is not in the list anymore. * If the view is still visible somewhere, we leave where it is, * otherwise, move it to the first output. */ visible = 0; wl_list_for_each(output, &ec->output_list, link) { if (pixman_region32_contains_point(&output->region, x, y, NULL)) { visible = 1; break; } } if (!visible) { first_output = container_of(ec->output_list.next, struct weston_output, link); x = first_output->x + first_output->width / 4; y = first_output->y + first_output->height / 4; weston_view_set_position(view, x, y); } else { weston_view_geometry_dirty(view); } shsurf = get_shell_surface(view->surface); if (!shsurf) return; shsurf->saved_position_valid = false; set_maximized(shsurf, false); set_fullscreen(shsurf, false, NULL); } void shell_for_each_layer(struct desktop_shell *shell, shell_for_each_layer_func_t func, void *data) { struct workspace **ws; func(shell, &shell->fullscreen_layer, data); func(shell, &shell->panel_layer, data); func(shell, &shell->background_layer, data); func(shell, &shell->lock_layer, data); func(shell, &shell->input_panel_layer, data); wl_array_for_each(ws, &shell->workspaces.array) func(shell, &(*ws)->layer, data); } static void shell_output_changed_move_layer(struct desktop_shell *shell, struct weston_layer *layer, void *data) { struct weston_view *view; wl_list_for_each(view, &layer->view_list.link, layer_link.link) shell_reposition_view_on_output_change(view); } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct shell_output *output_listener = container_of(listener, struct shell_output, destroy_listener); struct desktop_shell *shell = output_listener->shell; shell_for_each_layer(shell, shell_output_changed_move_layer, NULL); if (output_listener->panel_surface) wl_list_remove(&output_listener->panel_surface_listener.link); if (output_listener->background_surface) wl_list_remove(&output_listener->background_surface_listener.link); wl_list_remove(&output_listener->destroy_listener.link); wl_list_remove(&output_listener->link); free(output_listener); } static void shell_resize_surface_to_output(struct desktop_shell *shell, struct weston_surface *surface, const struct weston_output *output) { if (!surface) return; weston_desktop_shell_send_configure(shell->child.desktop_shell, 0, surface->resource, output->width, output->height); } static void handle_output_resized(struct wl_listener *listener, void *data) { struct desktop_shell *shell = container_of(listener, struct desktop_shell, resized_listener); struct weston_output *output = (struct weston_output *)data; struct shell_output *sh_output = find_shell_output_from_weston_output(shell, output); shell_resize_surface_to_output(shell, sh_output->background_surface, output); shell_resize_surface_to_output(shell, sh_output->panel_surface, output); } static void create_shell_output(struct desktop_shell *shell, struct weston_output *output) { struct shell_output *shell_output; shell_output = zalloc(sizeof *shell_output); if (shell_output == NULL) return; shell_output->output = output; shell_output->shell = shell; shell_output->destroy_listener.notify = handle_output_destroy; wl_signal_add(&output->destroy_signal, &shell_output->destroy_listener); wl_list_insert(shell->output_list.prev, &shell_output->link); if (wl_list_length(&shell->output_list) == 1) shell_for_each_layer(shell, shell_output_changed_move_layer, NULL); } static void handle_output_create(struct wl_listener *listener, void *data) { struct desktop_shell *shell = container_of(listener, struct desktop_shell, output_create_listener); struct weston_output *output = (struct weston_output *)data; create_shell_output(shell, output); } static void handle_output_move_layer(struct desktop_shell *shell, struct weston_layer *layer, void *data) { struct weston_output *output = data; struct weston_view *view; float x, y; wl_list_for_each(view, &layer->view_list.link, layer_link.link) { if (view->output != output) continue; x = view->geometry.x + output->move_x; y = view->geometry.y + output->move_y; weston_view_set_position(view, x, y); } } static void handle_output_move(struct wl_listener *listener, void *data) { struct desktop_shell *shell; shell = container_of(listener, struct desktop_shell, output_move_listener); shell_for_each_layer(shell, handle_output_move_layer, data); } static void setup_output_destroy_handler(struct weston_compositor *ec, struct desktop_shell *shell) { struct weston_output *output; wl_list_init(&shell->output_list); wl_list_for_each(output, &ec->output_list, link) create_shell_output(shell, output); shell->output_create_listener.notify = handle_output_create; wl_signal_add(&ec->output_created_signal, &shell->output_create_listener); shell->output_move_listener.notify = handle_output_move; wl_signal_add(&ec->output_moved_signal, &shell->output_move_listener); } static void shell_destroy(struct wl_listener *listener, void *data) { struct desktop_shell *shell = container_of(listener, struct desktop_shell, destroy_listener); struct workspace **ws; struct shell_output *shell_output, *tmp; /* Force state to unlocked so we don't try to fade */ shell->locked = false; if (shell->child.client) { /* disable respawn */ wl_list_remove(&shell->child.client_destroy_listener.link); wl_client_destroy(shell->child.client); } wl_list_remove(&shell->destroy_listener.link); wl_list_remove(&shell->idle_listener.link); wl_list_remove(&shell->wake_listener.link); wl_list_remove(&shell->transform_listener.link); text_backend_destroy(shell->text_backend); input_panel_destroy(shell); wl_list_for_each_safe(shell_output, tmp, &shell->output_list, link) { wl_list_remove(&shell_output->destroy_listener.link); wl_list_remove(&shell_output->link); free(shell_output); } wl_list_remove(&shell->output_create_listener.link); wl_list_remove(&shell->output_move_listener.link); wl_list_remove(&shell->resized_listener.link); wl_array_for_each(ws, &shell->workspaces.array) workspace_destroy(*ws); wl_array_release(&shell->workspaces.array); free(shell->client); free(shell); } static void shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) { uint32_t mod; int i, num_workspace_bindings; if (shell->allow_zap) weston_compositor_add_key_binding(ec, KEY_BACKSPACE, MODIFIER_CTRL | MODIFIER_ALT, terminate_binding, ec); /* fixed bindings */ weston_compositor_add_button_binding(ec, BTN_LEFT, 0, click_to_activate_binding, shell); weston_compositor_add_button_binding(ec, BTN_RIGHT, 0, click_to_activate_binding, shell); weston_compositor_add_touch_binding(ec, 0, touch_to_activate_binding, shell); weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSDOWN, 0, backlight_binding, ec); weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSUP, 0, backlight_binding, ec); /* configurable bindings */ if (shell->exposay_modifier) weston_compositor_add_modifier_binding(ec, shell->exposay_modifier, exposay_binding, shell); mod = shell->binding_modifier; if (!mod) return; /* This binding is not configurable, but is only enabled if there is a * valid binding modifier. */ weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, MODIFIER_SUPER | MODIFIER_ALT, surface_opacity_binding, NULL); weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, mod, zoom_axis_binding, NULL); weston_compositor_add_key_binding(ec, KEY_PAGEUP, mod, zoom_key_binding, NULL); weston_compositor_add_key_binding(ec, KEY_PAGEDOWN, mod, zoom_key_binding, NULL); weston_compositor_add_key_binding(ec, KEY_M, mod | MODIFIER_SHIFT, maximize_binding, NULL); weston_compositor_add_key_binding(ec, KEY_F, mod | MODIFIER_SHIFT, fullscreen_binding, NULL); weston_compositor_add_button_binding(ec, BTN_LEFT, mod, move_binding, shell); weston_compositor_add_touch_binding(ec, mod, touch_move_binding, shell); weston_compositor_add_button_binding(ec, BTN_RIGHT, mod, resize_binding, shell); weston_compositor_add_button_binding(ec, BTN_LEFT, mod | MODIFIER_SHIFT, resize_binding, shell); if (ec->capabilities & WESTON_CAP_ROTATION_ANY) weston_compositor_add_button_binding(ec, BTN_MIDDLE, mod, rotate_binding, NULL); weston_compositor_add_key_binding(ec, KEY_TAB, mod, switcher_binding, shell); weston_compositor_add_key_binding(ec, KEY_F9, mod, backlight_binding, ec); weston_compositor_add_key_binding(ec, KEY_F10, mod, backlight_binding, ec); weston_compositor_add_key_binding(ec, KEY_K, mod, force_kill_binding, shell); weston_compositor_add_key_binding(ec, KEY_UP, mod, workspace_up_binding, shell); weston_compositor_add_key_binding(ec, KEY_DOWN, mod, workspace_down_binding, shell); weston_compositor_add_key_binding(ec, KEY_UP, mod | MODIFIER_SHIFT, workspace_move_surface_up_binding, shell); weston_compositor_add_key_binding(ec, KEY_DOWN, mod | MODIFIER_SHIFT, workspace_move_surface_down_binding, shell); /* Add bindings for mod+F[1-6] for workspace 1 to 6. */ if (shell->workspaces.num > 1) { num_workspace_bindings = shell->workspaces.num; if (num_workspace_bindings > 6) num_workspace_bindings = 6; for (i = 0; i < num_workspace_bindings; i++) weston_compositor_add_key_binding(ec, KEY_F1 + i, mod, workspace_f_binding, shell); } weston_install_debug_key_binding(ec, mod); } static void handle_seat_created(struct wl_listener *listener, void *data) { struct weston_seat *seat = data; create_shell_seat(seat); } WL_EXPORT int wet_shell_init(struct weston_compositor *ec, int *argc, char *argv[]) { struct weston_seat *seat; struct desktop_shell *shell; struct workspace **pws; unsigned int i; struct wl_event_loop *loop; shell = zalloc(sizeof *shell); if (shell == NULL) return -1; shell->compositor = ec; if (!weston_compositor_add_destroy_listener_once(ec, &shell->destroy_listener, shell_destroy)) { free(shell); return 0; } shell->idle_listener.notify = idle_handler; wl_signal_add(&ec->idle_signal, &shell->idle_listener); shell->wake_listener.notify = wake_handler; wl_signal_add(&ec->wake_signal, &shell->wake_listener); shell->transform_listener.notify = transform_handler; wl_signal_add(&ec->transform_signal, &shell->transform_listener); weston_layer_init(&shell->fullscreen_layer, ec); weston_layer_init(&shell->panel_layer, ec); weston_layer_init(&shell->background_layer, ec); weston_layer_init(&shell->lock_layer, ec); weston_layer_init(&shell->input_panel_layer, ec); weston_layer_set_position(&shell->fullscreen_layer, WESTON_LAYER_POSITION_FULLSCREEN); weston_layer_set_position(&shell->panel_layer, WESTON_LAYER_POSITION_UI); weston_layer_set_position(&shell->background_layer, WESTON_LAYER_POSITION_BACKGROUND); wl_array_init(&shell->workspaces.array); wl_list_init(&shell->workspaces.client_list); if (input_panel_setup(shell) < 0) return -1; shell->text_backend = text_backend_init(ec); if (!shell->text_backend) return -1; shell_configuration(shell); shell->exposay.state_cur = EXPOSAY_LAYOUT_INACTIVE; shell->exposay.state_target = EXPOSAY_TARGET_CANCEL; for (i = 0; i < shell->workspaces.num; i++) { pws = wl_array_add(&shell->workspaces.array, sizeof *pws); if (pws == NULL) return -1; *pws = workspace_create(shell); if (*pws == NULL) return -1; } activate_workspace(shell, 0); weston_layer_init(&shell->minimized_layer, ec); wl_list_init(&shell->workspaces.anim_sticky_list); wl_list_init(&shell->workspaces.animation.link); shell->workspaces.animation.frame = animate_workspace_change_frame; shell->desktop = weston_desktop_create(ec, &shell_desktop_api, shell); if (!shell->desktop) return -1; if (wl_global_create(ec->wl_display, &weston_desktop_shell_interface, 1, shell, bind_desktop_shell) == NULL) return -1; weston_compositor_get_time(&shell->child.deathstamp); shell->panel_position = WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP; setup_output_destroy_handler(ec, shell); loop = wl_display_get_event_loop(ec->wl_display); wl_event_loop_add_idle(loop, launch_desktop_shell_process, shell); wl_list_for_each(seat, &ec->seat_list, link) handle_seat_created(NULL, seat); shell->seat_create_listener.notify = handle_seat_created; wl_signal_add(&ec->seat_created_signal, &shell->seat_create_listener); shell->resized_listener.notify = handle_output_resized; wl_signal_add(&ec->output_resized_signal, &shell->resized_listener); screenshooter_create(ec); shell_add_bindings(ec, shell); shell_fade_init(shell); clock_gettime(CLOCK_MONOTONIC, &shell->startup_time); return 0; } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.348963 weston-8.0.0/desktop-shell/shell.h0000644000175000017460000001535200000000000017363 0ustar00simonwheel00000000000000/* * Copyright © 2010-2012 Intel Corporation * Copyright © 2011-2012 Collabora, Ltd. * Copyright © 2013 Raspberry Pi Foundation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include "weston-desktop-shell-server-protocol.h" enum animation_type { ANIMATION_NONE, ANIMATION_ZOOM, ANIMATION_FADE, ANIMATION_DIM_LAYER, }; enum fade_type { FADE_IN, FADE_OUT }; enum exposay_target_state { EXPOSAY_TARGET_OVERVIEW, /* show all windows */ EXPOSAY_TARGET_CANCEL, /* return to normal, same focus */ EXPOSAY_TARGET_SWITCH, /* return to normal, switch focus */ }; enum exposay_layout_state { EXPOSAY_LAYOUT_INACTIVE = 0, /* normal desktop */ EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE, /* in transition to normal */ EXPOSAY_LAYOUT_OVERVIEW, /* show all windows */ EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW, /* in transition to all windows */ }; struct exposay_output { int num_surfaces; int grid_size; int surface_size; int hpadding_outer; int vpadding_outer; int padding_inner; }; struct exposay { /* XXX: Make these exposay_surfaces. */ struct weston_view *focus_prev; struct weston_view *focus_current; struct weston_view *clicked; struct workspace *workspace; struct weston_seat *seat; struct wl_list surface_list; struct weston_keyboard_grab grab_kbd; struct weston_pointer_grab grab_ptr; enum exposay_target_state state_target; enum exposay_layout_state state_cur; int in_flight; /* number of animations still running */ int row_current; int column_current; struct exposay_output *cur_output; bool mod_pressed; bool mod_invalid; }; struct focus_surface { struct weston_surface *surface; struct weston_view *view; struct weston_transform workspace_transform; }; struct workspace { struct weston_layer layer; struct wl_list focus_list; struct wl_listener seat_destroyed_listener; struct focus_surface *fsurf_front; struct focus_surface *fsurf_back; struct weston_view_animation *focus_animation; }; struct shell_output { struct desktop_shell *shell; struct weston_output *output; struct exposay_output eoutput; struct wl_listener destroy_listener; struct wl_list link; struct weston_surface *panel_surface; struct wl_listener panel_surface_listener; struct weston_surface *background_surface; struct wl_listener background_surface_listener; struct { struct weston_view *view; struct weston_view_animation *animation; enum fade_type type; struct wl_event_source *startup_timer; } fade; }; struct weston_desktop; struct desktop_shell { struct weston_compositor *compositor; struct weston_desktop *desktop; const struct weston_xwayland_surface_api *xwayland_surface_api; struct wl_listener idle_listener; struct wl_listener wake_listener; struct wl_listener transform_listener; struct wl_listener resized_listener; struct wl_listener destroy_listener; struct wl_listener show_input_panel_listener; struct wl_listener hide_input_panel_listener; struct wl_listener update_input_panel_listener; struct weston_layer fullscreen_layer; struct weston_layer panel_layer; struct weston_layer background_layer; struct weston_layer lock_layer; struct weston_layer input_panel_layer; struct wl_listener pointer_focus_listener; struct weston_surface *grab_surface; struct { struct wl_client *client; struct wl_resource *desktop_shell; struct wl_listener client_destroy_listener; unsigned deathcount; struct timespec deathstamp; } child; bool locked; bool showing_input_panels; bool prepare_event_sent; struct text_backend *text_backend; struct { struct weston_surface *surface; pixman_box32_t cursor_rectangle; } text_input; struct weston_surface *lock_surface; struct wl_listener lock_surface_listener; struct { struct wl_array array; unsigned int current; unsigned int num; struct wl_list client_list; struct weston_animation animation; struct wl_list anim_sticky_list; int anim_dir; struct timespec anim_timestamp; double anim_current; struct workspace *anim_from; struct workspace *anim_to; } workspaces; struct { struct wl_resource *binding; struct wl_list surfaces; } input_panel; struct exposay exposay; bool allow_zap; uint32_t binding_modifier; uint32_t exposay_modifier; enum animation_type win_animation_type; enum animation_type win_close_animation_type; enum animation_type startup_animation_type; enum animation_type focus_animation_type; struct weston_layer minimized_layer; struct wl_listener seat_create_listener; struct wl_listener output_create_listener; struct wl_listener output_move_listener; struct wl_list output_list; enum weston_desktop_shell_panel_position panel_position; char *client; struct timespec startup_time; }; struct weston_output * get_default_output(struct weston_compositor *compositor); struct weston_view * get_default_view(struct weston_surface *surface); struct shell_surface * get_shell_surface(struct weston_surface *surface); struct workspace * get_current_workspace(struct desktop_shell *shell); void lower_fullscreen_layer(struct desktop_shell *shell, struct weston_output *lowering_output); void activate(struct desktop_shell *shell, struct weston_view *view, struct weston_seat *seat, uint32_t flags); void exposay_binding(struct weston_keyboard *keyboard, enum weston_keyboard_modifier modifier, void *data); int input_panel_setup(struct desktop_shell *shell); void input_panel_destroy(struct desktop_shell *shell); typedef void (*shell_for_each_layer_func_t)(struct desktop_shell *, struct weston_layer *, void *); void shell_for_each_layer(struct desktop_shell *shell, shell_for_each_layer_func_t func, void *data); ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3556297 weston-8.0.0/doc/0000755000175000017460000000000000000000000014064 5ustar00simonwheel00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.348963 weston-8.0.0/doc/doxygen/0000755000175000017460000000000000000000000015541 5ustar00simonwheel00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.348963 weston-8.0.0/doc/doxygen/devtools.dox0000644000175000017460000000361100000000000020115 0ustar00simonwheel00000000000000/* * Copyright © 2015 Samsung Electronics Co., Ltd * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** @mainpage - @ref zunitc - Simple test framework @section tools_overview Overview The tools area currently consists of one sub-project (@ref zunitc) that is refined from the prior single weston/tests source folder. @subsection tools_overview_old Old Code Organization The original 'tests' folder contained basic weston testing with an integrated test runner framework. Over time things progressed to the stage where splitting apart into discrete layers was warranted. @dotfile tools_arch_old.gv "Original test code organization" @subsection tools_overview_new New Code Organization The test code that is not weston-specific gets split out to a separate folder and/or folders. @dotfile tools_arch_new.gv "Refactored test code organization" */ ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.348963 weston-8.0.0/doc/doxygen/tooldev.doxygen.in0000644000175000017460000000067000000000000021224 0ustar00simonwheel00000000000000PROJECT_NAME = "Tool Internals" OUTPUT_DIRECTORY = @top_builddir@/docs/developer JAVADOC_AUTOBRIEF = YES OPTIMIZE_OUTPUT_FOR_C = YES EXTRACT_ALL = YES INPUT = \ @top_srcdir@/doc/doxygen/devtools.dox \ @top_srcdir@/tools/zunitc RECURSIVE = YES GENERATE_LATEX = NO DOTFILE_DIRS = @top_srcdir@/doc/doxygen STRIP_FROM_PATH = @top_srcdir@ ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.348963 weston-8.0.0/doc/doxygen/tools.dox0000644000175000017460000000231700000000000017420 0ustar00simonwheel00000000000000/* * Copyright © 2015 Samsung Electronics Co., Ltd * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** @mainpage - @ref zunitc - Simple test framework */ ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.348963 weston-8.0.0/doc/doxygen/tools.doxygen.in0000644000175000017460000000066600000000000020715 0ustar00simonwheel00000000000000PROJECT_NAME = "Tools" OUTPUT_DIRECTORY = @top_builddir@/docs/tools JAVADOC_AUTOBRIEF = YES OPTIMIZE_OUTPUT_FOR_C = YES INPUT = \ @top_srcdir@/doc/doxygen/tools.dox \ @top_srcdir@/tools/zunitc/doc/zunitc.dox \ @top_srcdir@/tools/zunitc/inc/zunitc/zunitc.h GENERATE_LATEX = NO DOTFILE_DIRS = @top_srcdir@/doc/doxygen STRIP_FROM_PATH = @top_srcdir@ ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.348963 weston-8.0.0/doc/doxygen/tools_arch_new.gv0000644000175000017460000000533500000000000021113 0ustar00simonwheel00000000000000/* * Copyright © 2015 Samsung Electronics Co., Ltd * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ digraph toolarch_new { rankdir = "TB"; node[shape=record] subgraph cluster_0 { label = "./tests"; keyboard_test_c [label = "{keyboard-test.c|tests\l}"] text_test_c [label = "{text-test.c|tests\l}"] vertex_clip_test_c [label = "{vertex-clip-test.c|tests\l}"] spacer [shape = point, style = invis] weston_test_client_helper [label = "{weston-test-client-helper.h/.c|Weston test protocol\l}"] weston_test_c [label = "{weston-test.c|Extension protocol\nimplementation}"] } subgraph cluster_1 { label = "./tools/waycheck"; waycheck [label = "{waycheck.c| \n \n }"] } subgraph cluster_2 { label = "./tools/wayland_fixtures"; wtst_fixtures [label = "{wtst_fixtures.h/c|Wayland tracking structs\lWayland callbacks\l}"] } subgraph cluster_3 { label = "./tools/zunitc"; zunitc [label = "{zunitc|Test definition macros\lTest running functions\lTest reporting functions\lTest run lifecycle\l}"] } keyboard_test_c -> weston_test_client_helper keyboard_test_c -> wtst_fixtures keyboard_test_c -> zunitc vertex_clip_test_c -> zunitc text_test_c -> weston_test_client_helper text_test_c -> wtst_fixtures text_test_c -> zunitc waycheck -> wtst_fixtures waycheck -> zunitc wtst_fixtures -> zunitc edge [style = dashed, arrowhead = open] weston_test_client_helper -> weston_test_c edge [style = invis] weston_test_client_helper -> zunitc text_test_c -> spacer keyboard_test_c -> spacer spacer -> weston_test_client_helper } ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1579896318.348963 weston-8.0.0/doc/doxygen/tools_arch_old.gv0000644000175000017460000000433300000000000021075 0ustar00simonwheel00000000000000/* * Copyright © 2015 Samsung Electronics Co., Ltd * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ digraph toolarch_old { rankdir = "TB"; node[shape = record] subgraph cluster_0 { label = "./tests"; keyboard_test_c [label = "{keyboard-test.c|tests\l}"] text_test_c [label = "{text-test.c|tests\l}"] vertex_clip_test_c [label = "{vertex-clip-test.c|tests\l}"] weston_test_client_helper [label = "{weston-test-client-helper.h/.c|Wayland tracking structs\lWeston test protocol\lWayland callbacks\lTest run lifecycle\l}"] weston_test_c [label = "{weston-test.c|Extension protocol\nimplementation}"] weston_test_runner [label = "{weston-test-runner.h/.c|Test definition macros\lTest running functions\lTest reporting functions\lTest run lifecycle\l}"] } weston_test_client_helper -> weston_test_runner keyboard_test_c -> weston_test_client_helper keyboard_test_c -> weston_test_runner vertex_clip_test_c -> weston_test_runner text_test_c -> weston_test_client_helper text_test_c -> weston_test_runner edge [style = dashed, arrowhead = open] weston_test_client_helper -> weston_test_c } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3522964 weston-8.0.0/doc/scripts/0000755000175000017460000000000000000000000015553 5ustar00simonwheel00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3522964 weston-8.0.0/doc/scripts/calibration-helper.bash0000755000175000017460000000554000000000000022165 0ustar00simonwheel00000000000000#!/bin/bash # Copyright 2018 Collabora, Ltd. # Copyright 2018 General Electric Company # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice (including the # next paragraph) shall be included in all copies or substantial # portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # This is an example script working as Weston's calibration helper. # Its purpose is to permanently store the calibration matrix for the given # touchscreen input device into a udev property. Since this script naturally # runs as the user that runs Weston, it presumably cannot write directly into # /etc. It is left for the administrator to set up appropriate files and # permissions. # To use this script, one needs to edit weston.ini, in section [libinput], add: # calibration_helper=/path/to/bin/calibration-helper.bash # exit immediately if any command fails set -e # The arguments Weston gives us: SYSPATH="$1" MATRIX="$2 $3 $4 $5 $6 $7" # Pick something to recognize the right touch device with. # Usually one would use something like a serial. SERIAL=$(udevadm info "$SYSPATH" --query=property | \ awk -- 'BEGIN { FS="=" } { if ($1 == "ID_SERIAL") { print $2; exit } }') # If cannot find a serial, tell the server to not use the new calibration. [ -z "$SERIAL" ] && exit 1 # You'd have this write a file instead. echo "ACTION==\"add|change\",SUBSYSTEM==\"input\",ENV{ID_SERIAL}==\"$SERIAL\",ENV{LIBINPUT_CALIBRATION_MATRIX}=\"$MATRIX\"" # Then you'd tell udev to reload the rules: #udevadm control --reload # This lets Weston get the new calibration if you unplug and replug the input # device. Instead of writing a udev rule directly, you could have a udev rule # with IMPORT{file}="/path/to/calibration", write # "LIBINPUT_CALIBRATION_MATRIX=\"$MATRIX\"" into /path/to/calibration instead, # and skip this reload step. # Make udev process the new rule by triggering a "change" event: #udevadm trigger "$SYSPATH" # If you were to restart Weston without rebooting, this lets it pick up the new # calibration. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3522964 weston-8.0.0/doc/scripts/gdb/0000755000175000017460000000000000000000000016307 5ustar00simonwheel00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3522964 weston-8.0.0/doc/scripts/gdb/flight_rec.py0000755000175000017460000000763300000000000021003 0ustar00simonwheel00000000000000# # Copyright © 2019 Collabora Ltd. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice (including the # next paragraph) shall be included in all copies or substantial # portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # Usage: source this script then 'display_flight_rec' # import gdb class DisplayFlightRecorder(gdb.Command): def __init__(self): self.rb = '' symbol_found = False ring_buff = gdb.lookup_global_symbol("weston_primary_flight_recorder_ring_buffer") if ring_buff == None: print("'weston_ring_buffer' symbol not found!") print("Either weston is too old or weston hasn't been loaded in memory") else: self.rb = ring_buff self.display_rb_data(self.rb) symbol_found = True if symbol_found: super(DisplayFlightRecorder, self).__init__("display_flight_rec", gdb.COMMAND_DATA) def display_rb_data(self, rb): print("Flight recorder data found. Use 'display_flight_rec' " "to display its contents") # display this data (only) if symbol is not empty (happens if the program is not ran at all) if rb.value(): print("Data at byte {append}, Size: {size}B, " "Overlaped: {overlap}".format(append=rb.value()['append_pos'], size=rb.value()['size'], overlap=rb.value()['overlap'])) # poor's man fwrite() def gen_contents(self, start, stop): _str = '' for j in range(start, stop): _str += chr(self.rb.value()['buf'][j]) return _str # mirrors C version, as to make sure we're not reading other parts... def display_flight_rec_contents(self): # symbol is there but not loaded, we're not far enough if self.rb.value() == 0x0: print("Flight recorder found, but not loaded yet!") return else: print("Displaying flight recorder contents:") append_pos = self.rb.value()['append_pos'] size = self.rb.value()['size'] overlap = self.rb.value()['overlap'] # if we haven't overflown and we're still at 0 means # we still aren't far enough to be populated if append_pos == 0 and not overlap: print("Flight recorder doesn't have anything to display right now") return # now we can print stuff rb_data = '' if not overlap: if append_pos: rb_data = self.gen_contents(0, append_pos) else: rb_data = self.gen_contents(0, size) else: rb_data = self.gen_contents(append_pos, size) rb_data += self.gen_contents(0, append_pos) print("{data}".format(data=rb_data)) # called when invoking 'display_flight_rec' def invoke(self, arg, from_tty): self.display_flight_rec_contents() DisplayFlightRecorder() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3522964 weston-8.0.0/doc/scripts/remoting-client-receive.bash0000755000175000017460000000326200000000000023140 0ustar00simonwheel00000000000000#!/bin/bash # Copyright © 2018 Renesas Electronics Corp. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice (including the # next paragraph) shall be included in all copies or substantial # portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # Authors: IGEL Co., Ltd. # By using this script, client can receive remoted output via gstreamer. # Usage: # remoting-client-receive.bash gst-launch-1.0 rtpbin name=rtpbin \ udpsrc caps="application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=JPEG,payload=26" port=$1 ! \ rtpbin.recv_rtp_sink_0 \ rtpbin. ! rtpjpegdepay ! jpegdec ! autovideosink \ udpsrc port=$(($1 + 1)) ! rtpbin.recv_rtcp_sink_0 \ rtpbin.send_rtcp_src_0 ! \ udpsink port=$(($1 + 2)) sync=false async=false ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3522964 weston-8.0.0/doc/sphinx/0000755000175000017460000000000000000000000015375 5ustar00simonwheel00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3522964 weston-8.0.0/doc/sphinx/conf.py.in0000644000175000017460000001356400000000000017312 0ustar00simonwheel00000000000000# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys import sphinx sys.path.append(os.path.abspath('sphinxext')) # -- Project information ----------------------------------------------------- project = u'weston' copyright = u'2019, Weston community' author = u'Weston community ' # The short X.Y version version = u'' # The full version, including alpha/beta/rc tags release = u'@VERSION@' # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '2.1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.autosectionlabel', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'breathe', ] breathe_projects = { "weston": "@BUILD_ROOT@/xml/" } breathe_default_members = ('members', 'undoc-members') breathe_default_project = "weston" # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: source_suffix = ['.rst' ] # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None # default domain primary_domain = 'cpp' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'weston' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'weston.tex', u'Weston Documentation', u'Weston community', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'weston', u'Weston Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'weston', u'Wweston Documentation', author, 'Weston community', 'Weston Documentation' 'Miscellaneous'), ] # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # -- Extension configuration ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/3': None} # -- Options for todo extension ---------------------------------------------- # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1579896318.3522964 weston-8.0.0/doc/sphinx/doxygen.ini.in0000644000175000017460000032251500000000000020170 0ustar00simonwheel00000000000000# Doxyfile 1.8.13 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "libweston" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = @OUTPUT_DIR@ # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = "rst=\verbatim embed:rst" ALIASES += "endrst=\endverbatim" ALIASES += "rststar=\verbatim embed:rst:leading-asterisk" ALIASES += "endrststar=\endverbatim" # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = YES # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 0. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 0 # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO, these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES, upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete # parameter documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. # The default value is: NO. WARN_AS_ERROR = YES # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = @SRC_ROOT@/libweston @SRC_ROOT@/include/libweston # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.idl \ *.ddl \ *.odl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.cs \ *.d \ *.php \ *.php4 \ *.php5 \ *.phtml \ *.inc \ *.m \ *.markdown \ *.md \ *.mm \ *.dox \ *.py \ *.pyw \ *.f90 \ *.f95 \ *.f03 \ *.f08 \ *.f \ *.for \ *.tcl \ *.vhd \ *.vhdl \ *.ucf \ *.qsf # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see http://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = NO # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to YES can help to show when doxygen was last run and thus if the # documentation is up to date. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the master .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /