diff --git a/BUILD.gn b/BUILD.gn new file mode 100755 index 0000000000000000000000000000000000000000..f2453a7c7454800c18abfd1e172fb87a8a434fae --- /dev/null +++ b/BUILD.gn @@ -0,0 +1,541 @@ +import("//build/ohos.gni") +import("//third_party/wayland_standard/wayland_protocol.gni") + + +this is test for ljqc 2021-07-21 + + +## Generate Wayland Protocols {{{ +wayland_protocol("text_cursor_position_protocol") { + sources = [ "protocol/text-cursor-position.xml" ] +} +wayland_protocol("weston_content_protection_protocol") { + sources = [ "protocol/weston-content-protection.xml" ] +} +wayland_protocol("weston_touch_calibration_protocol") { + sources = [ "protocol/weston-touch-calibration.xml" ] +} +wayland_protocol("weston_direct_display_protocol") { + sources = [ "protocol/weston-direct-display.xml" ] +} +wayland_protocol("weston_screenshooter_protocol") { + sources = [ "protocol/weston-screenshooter.xml" ] +} +wayland_protocol("ivi_application_protocol") { + sources = [ "protocol/ivi-application.xml" ] +} +wayland_protocol("drm_auth_protocol") { + sources = [ "protocol/drm-auth.xml" ] +} + +## Generate Wayland Protocols }}} + +## Build libshared.a {{{ +config("libshared_config") { + visibility = [ ":*" ] + + include_dirs = [ + "include", + "//third_party/wayland_standard/src", + "//third_party/wayland-ivi-extension_standard/include", + ] + + cflags = [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + ] +} + +config("libshared_public_config") { + include_dirs = [ "shared" ] + + cflags = [] +} + +ohos_static_library("libshared") { + sources = [ + "shared/config-parser.c", + "shared/file-util.c", + "shared/matrix.c", + "shared/option-parser.c", + "shared/os-compatibility.c", + ] + + configs = [ ":libshared_config" ] + + public_configs = [ ":libshared_public_config" ] + + deps = [] + + public_deps = [ "//third_party/wayland_standard:libwayland_server" ] +} + +## Build libshared.a }}} + +## Build libweston.so {{{ +config("libweston_config") { + visibility = [ ":*" ] + + include_dirs = [ + "//third_party/weston/include", + "libweston", + "//third_party/wayland_standard/src", + "//third_party/wayland-ivi-extension_standard/include", + "//third_party/weston", + ] + + cflags = [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wno-unused-function", + "-Wno-missing-field-initializers", + "-Wno-unused-variable", + "-Wno-implicit-function-declaration", + ] +} + +config("libweston_public_config") { + include_dirs = [ + "include", + "libweston/backend-drm", + "//third_party/bounds_checking_function/include", + ] + + cflags = [] +} + +ohos_shared_library("libweston") { + sources = [ + "libweston/animation.c", + "libweston/bindings.c", + "libweston/clipboard.c", + "libweston/compositor.c", + "libweston/content-protection.c", + "libweston/data-device.c", + "libweston/input.c", + "libweston/launcher-direct.c", + "libweston/launcher-util.c", + "libweston/launcher-weston-launch.c", + "libweston/linux-dmabuf.c", + "libweston/linux-explicit-synchronization.c", + "libweston/linux-sync-file.c", + "libweston/pixel-formats.c", + "libweston/pixman-renderer.c", + "libweston/plugin-registry.c", + "libweston/screenshooter.c", + "libweston/tde-render-part.c", + "libweston/touch-calibration.c", + "libweston/vertex-clipping.c", + "libweston/weston-direct-display.c", + "libweston/zoom.c", + ] + + configs = [ + ":libweston_config", + ":display_gfx_public_config", + ] + + public_configs = [ + ":libweston_public_config", + ":drm-backend_public_config", + ] + + deps = [ + ":libdisplay_gfx", + "//foundation/graphic/standard/prebuilts/librarys/display_gralloc:libdisplay_gralloc", + "//third_party/libdrm:libdrm", + "//third_party/libinput:libinput-third", + "//utils/native/base:utils", + ] + + public_deps = [ + ":libshared", + ":text_cursor_position_protocol", + ":weston_content_protection_protocol", + ":weston_direct_display_protocol", + ":weston_touch_calibration_protocol", + "//base/hiviewdfx/hilog/interfaces/native/innerkits:libhilog", + "//third_party/libxkbcommon:libxkbcommon", + "//third_party/pixman:libpixman", + "//third_party/wayland-protocols_standard:input_timestamps_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:linux_dmabuf_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:linux_explicit_synchronization_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:pointer_constraints_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:presentation_time_protocol", + "//third_party/wayland-protocols_standard:relative_pointer_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:viewporter_protocol", + "//third_party/wayland-protocols_standard:xdg_output_unstable_v1_protocol", + ] + + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +## Build libweston.so }}} + +## Build libexec_weston.a {{{ +config("libexec_weston_config") { + visibility = [ ":*" ] + + include_dirs = [ + "libweston", + ".", + ] + + cflags = [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wno-missing-field-initializers", + "-Wno-unused-variable", + ] +} + +config("libexec_weston_public_config") { + include_dirs = [] + + cflags = [] +} + +ohos_static_library("libexec_weston") { + sources = [ + "compositor/main.c", + "compositor/text-backend.c", + "compositor/weston-screenshooter.c", + ] + + configs = [ ":libexec_weston_config" ] + + public_configs = [ ":libexec_weston_public_config" ] + + deps = [ + ":weston_screenshooter_protocol", + "//third_party/libinput:libinput-third", + "//third_party/wayland-protocols_standard:input_method_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:text_input_unstable_v1_protocol", + ] + + public_deps = [ + ":libweston", + "//third_party/wayland_standard:wayland_core_protocol", + ] +} + +## Build libexec_weston.a }}} + +## Build weston {{{ +config("weston_config") { + visibility = [ ":*" ] + + include_dirs = [] + + cflags = [ + "-Wall", + "-Werror", + ] +} + +config("weston_public_config") { + include_dirs = [] + + cflags = [] +} + +ohos_executable("weston") { + install_enable = true + + sources = [ "compositor/executable.c" ] + + configs = [ ":weston_config" ] + + public_configs = [ ":weston_public_config" ] + + deps = [ ":libexec_weston" ] + + public_deps = [] + + subsystem_name = "graphic" + part_name = "graphic_standard" +} + +## Build weston }}} + +## Build libweston-desktop.so {{{ +config("libweston-desktop_config") { + visibility = [ ":*" ] + + include_dirs = [ "." ] + + cflags = [ + "-Wall", + "-Werror", + "-Wno-unused-function", + ] +} + +config("libweston-desktop_public_config") { + include_dirs = [ "include/libweston-desktop" ] + + cflags = [] +} + +ohos_shared_library("libweston-desktop") { + sources = [ + "libweston-desktop/client.c", + "libweston-desktop/libweston-desktop.c", + "libweston-desktop/seat.c", + "libweston-desktop/surface.c", + "libweston-desktop/wl-shell.c", + "libweston-desktop/xdg-shell-v6.c", + "libweston-desktop/xdg-shell.c", + ] + + configs = [ ":libweston-desktop_config" ] + + public_configs = [ ":libweston-desktop_public_config" ] + + deps = [] + + public_deps = [ + ":libexec_weston", + "//third_party/wayland-protocols_standard:xdg_shell_protocol", + "//third_party/wayland-protocols_standard:xdg_shell_unstable_v6_protocol", + ] + + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +## Build libweston-desktop.so }}} + +## Build ivi-shell.so {{{ +config("ivi-shell_config") { + visibility = [ ":*" ] + + include_dirs = [ "." ] + + cflags = [ + "-Wall", + "-Werror", + "-Wno-unused-function", + ] +} + +config("ivi-shell_public_config") { + include_dirs = [] + + cflags = [] +} + +ohos_shared_library("ivi-shell") { + sources = [ + "ivi-shell/ivi-layout-transition.c", + "ivi-shell/ivi-layout.c", + "ivi-shell/ivi-shell.c", + ] + + configs = [ ":ivi-shell_config" ] + + public_configs = [ ":ivi-shell_public_config" ] + + deps = [ + ":ivi_application_protocol", + ":libweston-desktop", + "//third_party/wayland_standard:wayland_core_protocol", + ] + + public_deps = [] + + subsystem_name = "graphic" + part_name = "graphic_standard" +} + +## Build ivi-shell.so }}} + +## Build drm-backend.so {{{ +config("drm-backend_config") { + visibility = [ ":*" ] + + include_dirs = [ + "include", + "libweston", + "libweston/backend-drm/auth", + ".", + ] + + cflags = [ + "-Wall", + "-Wno-error", + "-Wno-unused-parameter", + "-Wno-missing-field-initializers", + "-Wno-implicit-function-declaration", + "-Wno-unused-variable", + "-Wno-unused-function", + "-Wno-sometimes-uninitialized", + ] +} + +config("drm-backend_public_config") { + include_dirs = [] + + cflags = [] +} + +ohos_executable("simple-preasenation") { + sources = [ + "clients/presentation-shm.c", + # "libweston/backend-drm/drm-gbm.c", + ] + + configs = [ ":drm-backend_config" ] + + public_configs = [ ":drm-backend_public_config" ] + + deps = [ + "//third_party/wayland-protocols_standard:presentation_time_protocol", + "//third_party/wayland_standard:libwayland_client", + "//third_party/wayland_standard:libwayland_cursor", + "//third_party/wayland_standard:wayland_core_protocol", + "//third_party/weston:ivi_application_protocol", + ] + + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +ohos_shared_library("drm-backend") { + sources = [ + "libweston/backend-drm/auth/wayland_drm_auth_server.c", + "libweston/backend-drm/drm.c", + "libweston/backend-drm/fb.c", + "libweston/backend-drm/kms.c", + "libweston/backend-drm/libbacklight.c", + "libweston/backend-drm/modes.c", + "libweston/backend-drm/state-helpers.c", + "libweston/backend-drm/state-propose.c", + "libweston/libinput-device.c", + "libweston/libinput-seat.c", + + # "libweston/backend-drm/drm-gbm.c", + ] + + configs = [ ":drm-backend_config" ] + + public_configs = [ ":drm-backend_public_config" ] + + deps = [ + ":drm_auth_protocol", + ":libweston", + "//third_party/libdrm:libdrm", + "//third_party/libinput:libinput-third", + "//third_party/wayland-protocols_standard:linux_dmabuf_unstable_v1_protocol", + "//third_party/wayland-protocols_standard:presentation_time_protocol", + "//third_party/wayland-protocols_standard:relative_pointer_unstable_v1_protocol", + "//third_party/wayland_standard:wayland_core_protocol", + ] + + public_deps = [] + + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +## Build drm-backend.so }}} + +## Install weston.ini to /system/etc/weston.ini {{{ +ohos_prebuilt_etc("weston.ini") { + source = "weston.ini" + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +## Install weston.ini to /system/etc/weston.ini }}} + +## Install weston.rc to /system/etc/init/weston.rc {{{ +ohos_prebuilt_etc("weston.rc") { + source = "weston.rc" + relative_install_dir = "init" + part_name = "graphic_standard" + subsystem_name = "graphic" +} + +## Install weston.rc to /system/etc/init/weston.rc }}} + +# gl-renderer.so waiting for libEGL & libGLESv2 +### Build gl-renderer.so {{{ +#config("gl-renderer_config") { +# visibility = [ ":*" ] +# +# include_dirs = [ +# "//third_party/weston", +# "//third_party/wayland_standard/egl", +# "//third_party/weston/libweston", +# "//third_party/wayland-ivi-extension_standard/include", +# ] +# +# cflags = [ +# "-Wno-return-type", +# "-Wno-visibility", +# "-Wno-unused-function", +# ] +#} +# +#config("gl-renderer_public_config") { +# include_dirs = [ +# ] +# +# cflags = [ +# ] +#} +# +#ohos_shared_library("gl-renderer") { +# sources = [ +# "libweston/renderer-gl/egl-glue.c", +# "libweston/renderer-gl/gl-renderer.c", +# "libweston/vertex-clipping.c", +# ] +# +# configs = [ +# ":gl-renderer_config", +# ] +# +# public_configs = [ +# ":gl-renderer_public_config", +# ] +# +# deps = [ +# ":libweston", +# "//third_party/wayland-protocols_standard:linux_dmabuf_unstable_v1_protocol", +# ] +# +# asdk_deps = [ +# "shared_library:libEGL", +# "shared_library:libGLESv2", +# ] +# +# public_deps = [ +# ] +# +# subsystem_name = "graphic" +# part_name = "graphic_standard" +#} +### Build gl-renderer.so }}} + +## Prebuilt libdisplay_gfx.so {{{ +config("display_gfx_public_config") { + include_dirs = [ "//drivers/peripheral/display/interfaces/include" ] + + libs = [ "//device/hisilicon/hardware/display/libs/hispark_taurus/ext/libdisplay_gfx.z.so" ] +} + +ohos_prebuilt_shared_library("libdisplay_gfx") { + source = "//device/hisilicon/hardware/display/libs/hispark_taurus/ext/libdisplay_gfx.z.so" + + public_configs = [ ":display_gfx_public_config" ] + + part_name = "graphic_standard" + subsystem_name = "graphic" +} +## Prebuilt libdisplay_gfx.so }}} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..98fa565d439b44ad21815b3fa9317563aafde906 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,372 @@ +Contributing 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 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000000000000000000000000000000000000..fa4a8c48e13fbeccb02a740bff2ee11df553773d --- /dev/null +++ b/COPYING @@ -0,0 +1,26 @@ + + +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 diff --git a/DCO-1.1.txt b/DCO-1.1.txt new file mode 100644 index 0000000000000000000000000000000000000000..1ffe05c3e20022e9aa7dca0499567b2d5aaecfb0 --- /dev/null +++ b/DCO-1.1.txt @@ -0,0 +1,38 @@ +test +Developer 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. diff --git a/OAT.xml b/OAT.xml new file mode 100644 index 0000000000000000000000000000000000000000..90cd95bd7bf389554b50bd03e71b5a3c5f32ff5e --- /dev/null +++ b/OAT.xml @@ -0,0 +1,72 @@ + + + + + + COPYING + + + + + + + + + + + + + diff --git a/README.OpenSource b/README.OpenSource new file mode 100644 index 0000000000000000000000000000000000000000..9d1c806e7a8b8b47b9555b361be02b769111952f --- /dev/null +++ b/README.OpenSource @@ -0,0 +1,11 @@ +[ + { + "Name": "weston", + "License": "MIT license", + "License File": "COPYING", + "Version Number": "9.0.0", + "Owner": "lizheng2@huawei.com", + "Upstream URL": "https://wayland.freedesktop.org/releases/weston-9.0.0.tar.xz", + "Description": "Weston is the reference implementation of a Wayland compositor, as well as a useful environment in and of itself." + } +] diff --git a/README.md b/README.md index 25dc9639cb9d50c73819a7faa7993d45dbb6882a..30da9afa413d6161774f3b154f5c1b87f0e4e821 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,361 @@ -# python +测试 +Weston +====== -# Python for OHOS +![screenshot of skeletal Weston desktop](doc/wayland-screenshot.jpg) -#### 介绍 -这个仓库是为了能够在OpenHarmony设备上使用 Python 进行应用程序开发而创建。 -[1. 使用Python开发OpenHarmony设备程序(0-初体验)](https://harmonyos.51cto.com/posts/1887) +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. -## 以下说明针对lite-python文件夹下的代码 -#### 软件架构 -这个仓库下的 Baseline 是 [MicroPython v1.13](https://github.com/micropython/micropython/tree/v1.13),在 MicroPython 的基础上进行了必要的剪裁以满足 OHOS 上的应用开发需求。 +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. -#### 编译说明 -1. 编译环境: - 1)OS - Ubuntu 16+ - 2)Make - 3.81+ - 3)Python - 3.8+ -2. 配置交叉编译器: - 1)打开源码根目录中的 Makefile - 2)对变量 CROSS_COMPILE 进行赋值,如:CROSS_COMPILE ?= /home/harmony/gcc_riscv32/bin/riscv32-unknown-elf- -3. 在源码根目录中执行 make +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. -#### 使用说明 -1. 将编译得到的库文件 //build/libdtpython.a 拷贝到 //vendor/hisi/hi3861/hi3861/build/libs 目录下,如图: -![输入图片说明](https://images.gitee.com/uploads/images/2020/1130/102742_bea41b9a_8048968.png "82c43e952c89d664c1513719385ef6b12ba1a7.png") -2. 在设备应用中加载 Python 并执行代码 +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. -``` -#include "dtpython.h" +Reporting issues and contributing +================================= -extern const char* c_test_py; // test.py -extern const char* c_another_py; // another.py +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. -static void DTPython_Demo_Entry(void) -{ - printf("[DTPython_Demo] DTPython_Demo_Entry()\n"); +Building Weston +=============== - DTPython_Init(); // 初始化Python环境 +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). - DTPython_RunCode("print(\'Python Code Begin\')"); // 执行Python语句:print('Python Code Begin') - - DTPython_RunCode("s = \'HOS Device Development\'"); // 执行Python语句:s = 'HOS Device Development' - DTPython_RunCode("print(s)"); // 执行Python语句:print(s) +If necessary, the latest Meson can be installed as a user with: - DTPython_RunCode(c_test_py); // 模拟执行Python文件:DTPython_RunFile("test.py"); - DTPython_RunCode(c_another_py); // 模拟执行Python文件:DTPython_RunFile("another.py"); + $ pip3 install --user meson - DTPython_RunCode("print(\'Python Code End\')"); // 执行Python语句:print('Python Code End') +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. - DTPython_Deinit(); // 清理Python环境 -} -``` + $ 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 (deprecated), 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. diff --git a/clients/calibrator.c b/clients/calibrator.c new file mode 100644 index 0000000000000000000000000000000000000000..21ca876f7224adfeac65e40f628b9f78f3ac8838 --- /dev/null +++ b/clients/calibrator.c @@ -0,0 +1,311 @@ +/* + * 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; +} + diff --git a/clients/clickdot.c b/clients/clickdot.c new file mode 100644 index 0000000000000000000000000000000000000000..4e8a945e7c055a94959ed909ee6a51752ed2f666 --- /dev/null +++ b/clients/clickdot.c @@ -0,0 +1,345 @@ +/* + * 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; +} diff --git a/clients/cliptest.c b/clients/cliptest.c new file mode 100644 index 0000000000000000000000000000000000000000..89983850a3e708567aad88eedb1719c7c9214b9c --- /dev/null +++ b/clients/cliptest.c @@ -0,0 +1,637 @@ +/* + * 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; +} diff --git a/clients/confine.c b/clients/confine.c new file mode 100644 index 0000000000000000000000000000000000000000..6f3845712c49469a991618b1806518cfd23e57bf --- /dev/null +++ b/clients/confine.c @@ -0,0 +1,512 @@ +/* + * 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; +} diff --git a/clients/content_protection.c b/clients/content_protection.c new file mode 100644 index 0000000000000000000000000000000000000000..27e2b8a9daa71ac1c019e652036ac0c7f393b362 --- /dev/null +++ b/clients/content_protection.c @@ -0,0 +1,384 @@ +/* + * 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; +} diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c new file mode 100644 index 0000000000000000000000000000000000000000..bde5dc82073ae7a359f3b8184173e120e0065149 --- /dev/null +++ b/clients/desktop-shell.c @@ -0,0 +1,1559 @@ +/* + * 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; +} diff --git a/clients/dnd.c b/clients/dnd.c new file mode 100644 index 0000000000000000000000000000000000000000..8323f4fdccb598c61bee26a0caa56a6eda42f214 --- /dev/null +++ b/clients/dnd.c @@ -0,0 +1,867 @@ +/* + * 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; +} diff --git a/clients/editor.c b/clients/editor.c new file mode 100644 index 0000000000000000000000000000000000000000..a59f9679a682715b93495c9b181bd17e67027d69 --- /dev/null +++ b/clients/editor.c @@ -0,0 +1,1681 @@ +/* + * 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; +} diff --git a/clients/eventdemo.c b/clients/eventdemo.c new file mode 100644 index 0000000000000000000000000000000000000000..0a1a7ca91f7587087d56557092686c712243a527 --- /dev/null +++ b/clients/eventdemo.c @@ -0,0 +1,541 @@ +/* + * 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; +} diff --git a/clients/flower.c b/clients/flower.c new file mode 100644 index 0000000000000000000000000000000000000000..e3471ce7e0ec8c43b444e20c10ac4e645a7cd55f --- /dev/null +++ b/clients/flower.c @@ -0,0 +1,206 @@ +/* + * 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; +} diff --git a/clients/fullscreen.c b/clients/fullscreen.c new file mode 100644 index 0000000000000000000000000000000000000000..1b44ad535e7f318ac3a5f46e0246c19c8f10ee2c --- /dev/null +++ b/clients/fullscreen.c @@ -0,0 +1,581 @@ +/* + * 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; +} diff --git a/clients/gears.c b/clients/gears.c new file mode 100644 index 0000000000000000000000000000000000000000..6090a850a55ee843bc259d9cef42217efc1c79a3 --- /dev/null +++ b/clients/gears.c @@ -0,0 +1,503 @@ +/* + * 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; +} diff --git a/clients/image.c b/clients/image.c new file mode 100644 index 0000000000000000000000000000000000000000..0a8fb5b5b339bbd7fe2005d0f6b72b4b9c05c624 --- /dev/null +++ b/clients/image.c @@ -0,0 +1,437 @@ +/* + * 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; +} diff --git a/clients/ivi-shell-user-interface.c b/clients/ivi-shell-user-interface.c new file mode 100644 index 0000000000000000000000000000000000000000..7d2d1a20f075f28ab31a944bf7ed43c4cfda268c --- /dev/null +++ b/clients/ivi-shell-user-interface.c @@ -0,0 +1,1338 @@ +/* + * 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; +} diff --git a/clients/keyboard.c b/clients/keyboard.c new file mode 100644 index 0000000000000000000000000000000000000000..e39d76dd2d1c669bfb18448cc1c08c1c50da9159 --- /dev/null +++ b/clients/keyboard.c @@ -0,0 +1,1041 @@ +/* + * 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; +} diff --git a/clients/meson.build b/clients/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..2c016b845490e772d79ccd74a6f0d804df827d25 --- /dev/null +++ b/clients/meson.build @@ -0,0 +1,373 @@ +if 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, + 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_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 diff --git a/clients/multi-resource.c b/clients/multi-resource.c new file mode 100644 index 0000000000000000000000000000000000000000..b86503db962376f3296ef2fe53c8148c27c6be68 --- /dev/null +++ b/clients/multi-resource.c @@ -0,0 +1,591 @@ +/* + * 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; +} diff --git a/clients/nested-client.c b/clients/nested-client.c new file mode 100644 index 0000000000000000000000000000000000000000..a9e034efbb629fddfed0a5268b928801cef5373f --- /dev/null +++ b/clients/nested-client.c @@ -0,0 +1,374 @@ +/* + * 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; +} diff --git a/clients/nested.c b/clients/nested.c new file mode 100644 index 0000000000000000000000000000000000000000..4bbaa27ea21fc7a1d96936e582f028e39f36eeba --- /dev/null +++ b/clients/nested.c @@ -0,0 +1,1137 @@ +/* + * 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; +} diff --git a/clients/presentation-shm.c b/clients/presentation-shm.c new file mode 100644 index 0000000000000000000000000000000000000000..d5d73a2a854ed81f3cf950b133ab387baad7ab16 --- /dev/null +++ b/clients/presentation-shm.c @@ -0,0 +1,956 @@ +/* + * 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; +} diff --git a/clients/resizor.c b/clients/resizor.c new file mode 100644 index 0000000000000000000000000000000000000000..cfc5d419ea611f906e01f2d7e9e1ec668895bded --- /dev/null +++ b/clients/resizor.c @@ -0,0 +1,456 @@ +/* + * 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; +} diff --git a/clients/scaler.c b/clients/scaler.c new file mode 100644 index 0000000000000000000000000000000000000000..91736fb36e70fe5a9081a6b60605a9d8d2c40297 --- /dev/null +++ b/clients/scaler.c @@ -0,0 +1,326 @@ +/* + * 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; +} diff --git a/clients/screenshot.c b/clients/screenshot.c new file mode 100644 index 0000000000000000000000000000000000000000..bbf2e6bdff3e5893aa7a75e748dde23b9357532c --- /dev/null +++ b/clients/screenshot.c @@ -0,0 +1,329 @@ +/* + * 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; +} diff --git a/clients/simple-damage.c b/clients/simple-damage.c new file mode 100644 index 0000000000000000000000000000000000000000..821b42b5411a9e734a22837044f50e7e91b421a1 --- /dev/null +++ b/clients/simple-damage.c @@ -0,0 +1,957 @@ +/* + * 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 = wy; + *by = window->width - wx; + break; + case WL_OUTPUT_TRANSFORM_180: + *bx = window->width - wx; + *by = window->height - wy; + break; + case WL_OUTPUT_TRANSFORM_270: + *bx = window->height - wy; + *by = wx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + *bx = window->width - wx; + *by = wy; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + *bx = wy; + *by = wx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + *bx = wx; + *by = window->height - wy; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + *bx = window->height - wy; + *by = window->width - 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 = bheight - tx; + off_x = ty; + break; + case WL_OUTPUT_TRANSFORM_180: + off_y = bheight - ty; + off_x = bwidth - tx; + break; + case WL_OUTPUT_TRANSFORM_270: + off_y = tx; + off_x = bwidth - ty; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + off_y = ty; + off_x = bwidth - tx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + off_y = tx; + off_x = ty; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + off_y = bheight - ty; + off_x = tx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + off_y = bheight - tx; + off_x = bwidth - 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; +} diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c new file mode 100644 index 0000000000000000000000000000000000000000..10e72a9bc0281373a482eee94de62d5ef18fa9f9 --- /dev/null +++ b/clients/simple-dmabuf-egl.c @@ -0,0 +1,1562 @@ +/* + * 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; +} diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c new file mode 100644 index 0000000000000000000000000000000000000000..331f049f8cde083550f340fa5f6b93eaa9db425f --- /dev/null +++ b/clients/simple-dmabuf-v4l.c @@ -0,0 +1,1080 @@ +/* + * 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 +#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 "shared/helpers.h" + +#define CLEAR(x) memset(&(x), 0, sizeof(x)) +#define OPT_FLAG_INVERT (1 << 0) +#define OPT_FLAG_DIRECT_DISPLAY (1 << 1) + +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; + struct weston_direct_display_v1 *direct_display; + bool requested_format_found; + uint32_t opts; + + 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; + struct v4l2_input input; + int index_input = -1; + 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 (xioctl(display->v4l_fd, VIDIOC_G_INPUT, &index_input) == 0) { + input.index = index_input; + if (xioctl(display->v4l_fd, VIDIOC_ENUMINPUT, &input) == 0) { + if (input.status & V4L2_IN_ST_VFLIP) { + fprintf(stdout, "Found camera sensor y-flipped\n"); + display->opts |= OPT_FLAG_INVERT; + } + } + } + + 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; + + if (display->opts & OPT_FLAG_INVERT) + flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; + + params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); + + if ((display->opts & OPT_FLAG_DIRECT_DISPLAY) && display->direct_display) { + weston_direct_display_v1_enable(display->direct_display, params); + + if (display->opts & OPT_FLAG_INVERT) { + flags &= ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; + fprintf(stdout, "dmabuf y-inverted attribute flag was removed" + ", as display-direct flag was set\n"); + } + } + + 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_modifier(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; + uint64_t modifier = ((uint64_t) modifier_hi << 32 ) | modifier_lo; + + if (format == d->drm_format && modifier == DRM_FORMAT_MOD_LINEAR) + d->requested_format_found = true; +} + + +static void +dmabuf_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, + uint32_t format) +{ + /* deprecated */ +} + +static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = { + dmabuf_format, + dmabuf_modifier +}; + +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, 3); + zwp_linux_dmabuf_v1_add_listener(d->dmabuf, &dmabuf_listener, + d); + } 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 struct display * +create_display(uint32_t requested_format, uint32_t opt_flags) +{ + struct display *display; + + display = zalloc(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); + + if (!display->requested_format_found) { + fprintf(stderr, "0x%lx requested DRM format not available\n", + (unsigned long) requested_format); + exit(1); + } + + if (opt_flags) + display->opts = opt_flags; + 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 [-v v4l2_device] [-f v4l2_format] [-d drm_format] [-i|--y-invert] [-g|--d-display]\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\n" + "Flags:\n" + "- y-invert force the image to be y-flipped;\n note will be " + "automatically added if we detect if the camera sensor is " + "y-flipped\n" + "- d-display skip importing dmabuf-based buffer into the GPU\n " + "and attempt pass the buffer straight to the display controller\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" + "- optionally could add 'allocators=0x1' to options as to create" + " the buffer in a dmabuf-contiguous way\n" + " (as some display-controllers require it)\n" + "- launch the demo:\n" + " $ %s -v /dev/video0 -f XR24 -d 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 = NULL; + uint32_t v4l_format = 0x0; + uint32_t drm_format = 0x0; + uint32_t opts_flags = 0x0; + int c, opt_index, ret = 0; + + static struct option long_options[] = { + { "v4l2-device", required_argument, NULL, 'v' }, + { "v4l2-format", required_argument, NULL, 'f' }, + { "drm-format", required_argument, NULL, 'd' }, + { "y-invert", no_argument, NULL, 'i' }, + { "d-display", no_argument, NULL, 'g' }, + { "help", no_argument, NULL, 'h' }, + { 0, 0, NULL, 0 } + }; + + while ((c = getopt_long(argc, argv, "hiv:d:f:g", long_options, + &opt_index)) != -1) { + switch (c) { + case 'v': + v4l_device = optarg; + break; + case 'f': + v4l_format = parse_format(optarg); + break; + case 'd': + drm_format = parse_format(optarg); + break; + case 'i': + opts_flags |= OPT_FLAG_INVERT; + break; + case 'g': + opts_flags |= OPT_FLAG_DIRECT_DISPLAY; + break; + default: + case 'h': + usage(argv[0]); + break; + } + } + + if (!v4l_device) + v4l_device = "/dev/video0"; + + if (v4l_format == 0x0) + v4l_format = parse_format("YUYV"); + + if (drm_format == 0x0) + drm_format = v4l_format; + + display = create_display(drm_format, opts_flags); + 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; +} diff --git a/clients/simple-egl.c b/clients/simple-egl.c new file mode 100644 index 0000000000000000000000000000000000000000..cd7408e9421f38d5bcc29c5f32ba304b6985bdcd --- /dev/null +++ b/clients/simple-egl.c @@ -0,0 +1,896 @@ +/* + * 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; +} diff --git a/clients/simple-im.c b/clients/simple-im.c new file mode 100644 index 0000000000000000000000000000000000000000..a5b834ded5c58ddf218961ef93bb7f8f7cf14a48 --- /dev/null +++ b/clients/simple-im.c @@ -0,0 +1,526 @@ +/* + * 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; +} diff --git a/clients/simple-shm.c b/clients/simple-shm.c new file mode 100644 index 0000000000000000000000000000000000000000..90892c87fe1ea82d2498c06e332d0a60a9103b12 --- /dev/null +++ b/clients/simple-shm.c @@ -0,0 +1,528 @@ +/* + * 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; +} diff --git a/clients/simple-touch.c b/clients/simple-touch.c new file mode 100644 index 0000000000000000000000000000000000000000..385188c3caf5f0e6b588688881992bb67acc2d76 --- /dev/null +++ b/clients/simple-touch.c @@ -0,0 +1,360 @@ +/* + * 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; +} diff --git a/clients/smoke.c b/clients/smoke.c new file mode 100644 index 0000000000000000000000000000000000000000..f1b90ec7a1bd2ce7c69137b1a6b9f65c58aa9586 --- /dev/null +++ b/clients/smoke.c @@ -0,0 +1,318 @@ +/* + * 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; +} diff --git a/clients/stacking.c b/clients/stacking.c new file mode 100644 index 0000000000000000000000000000000000000000..5e9084f8736df79854588527b73f2064715efe00 --- /dev/null +++ b/clients/stacking.c @@ -0,0 +1,309 @@ +/* + * 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; +} diff --git a/clients/subsurfaces.c b/clients/subsurfaces.c new file mode 100644 index 0000000000000000000000000000000000000000..df4372f0db082e563a3ca23e93e76ed533d2b729 --- /dev/null +++ b/clients/subsurfaces.c @@ -0,0 +1,811 @@ +/* + * 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; +} diff --git a/clients/terminal.c b/clients/terminal.c new file mode 100644 index 0000000000000000000000000000000000000000..66e2bf549e0f53537886745dc8557c91fc65536a --- /dev/null +++ b/clients/terminal.c @@ -0,0 +1,3185 @@ +/* + * 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; +} diff --git a/clients/touch-calibrator.c b/clients/touch-calibrator.c new file mode 100644 index 0000000000000000000000000000000000000000..66208d16fb04b938a34eca31abf32200a2786956 --- /dev/null +++ b/clients/touch-calibrator.c @@ -0,0 +1,970 @@ +/* + * 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; +} diff --git a/clients/transformed.c b/clients/transformed.c new file mode 100644 index 0000000000000000000000000000000000000000..59f44bcaac1476f2382270d32bc9d908729acfeb --- /dev/null +++ b/clients/transformed.c @@ -0,0 +1,303 @@ +/* + * 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" + " -w \tSet window width to \n" + " -h \tSet window height to \n" + " --help\tShow this help text\n\n"); + + fprintf(stderr, "This version has been fixed for " + "https://gitlab.freedesktop.org/wayland/weston/issues/99 .\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; +} diff --git a/clients/weston-debug.c b/clients/weston-debug.c new file mode 100644 index 0000000000000000000000000000000000000000..3060dec97ec88c805588b1fef1ccad1536121fc6 --- /dev/null +++ b/clients/weston-debug.c @@ -0,0 +1,501 @@ +/* + * 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; +} diff --git a/clients/weston-info.c b/clients/weston-info.c new file mode 100644 index 0000000000000000000000000000000000000000..9772527320e6d5ef51b781b6d25ba43f894ea5da --- /dev/null +++ b/clients/weston-info.c @@ -0,0 +1,1890 @@ +/* + * 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; + } + + fprintf(stderr, "\n"); + fprintf(stderr, "*** Please use wayland-info instead\n"); + fprintf(stderr, "*** weston-info is deprecated and will be removed in a future version\n"); + fprintf(stderr, "\n"); + + 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; +} diff --git a/clients/window.c b/clients/window.c new file mode 100644 index 0000000000000000000000000000000000000000..ca7e62d07cc685a2e54b981fcdbaf718fed51a8f --- /dev/null +++ b/clients/window.c @@ -0,0 +1,6673 @@ +/* + * 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 + M_PI_2; + translate_x = 0; + translate_y = surface_width; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + angle = M_PI + M_PI_2; + translate_x = 0; + translate_y = 0; + 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_2; + translate_x = surface_height; + translate_y = 0; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + angle = M_PI_2; + translate_x = surface_height; + translate_y = surface_width; + 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); + if (!frame->frame) { + free(frame); + return 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); + } +} + +static void +touch_handle_shape(void *data, struct wl_touch *wl_touch, int32_t id, + wl_fixed_t major, wl_fixed_t minor) +{ +} + +static 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(XKB_CONTEXT_NO_FLAGS); + 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); +} diff --git a/clients/window.h b/clients/window.h new file mode 100644 index 0000000000000000000000000000000000000000..c66dd065ec5edb5fd068642102aa667337bbafb4 --- /dev/null +++ b/clients/window.h @@ -0,0 +1,744 @@ +/* + * 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 diff --git a/compositor/cms-colord.c b/compositor/cms-colord.c new file mode 100644 index 0000000000000000000000000000000000000000..d4efdb433c1a98821b484661e9fd9082a86a7102 --- /dev/null +++ b/compositor/cms-colord.c @@ -0,0 +1,588 @@ +/* + * 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; +} diff --git a/compositor/cms-helper.c b/compositor/cms-helper.c new file mode 100644 index 0000000000000000000000000000000000000000..bc56a9dcaa258fd6e5a76cabe6ef5299d640e4f1 --- /dev/null +++ b/compositor/cms-helper.c @@ -0,0 +1,136 @@ +/* + * 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; +} diff --git a/compositor/cms-helper.h b/compositor/cms-helper.h new file mode 100644 index 0000000000000000000000000000000000000000..4a5b711e59bab1100447357abaf01aa95bb4b2ba --- /dev/null +++ b/compositor/cms-helper.h @@ -0,0 +1,75 @@ +/* + * 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 diff --git a/compositor/cms-static.c b/compositor/cms-static.c new file mode 100644 index 0000000000000000000000000000000000000000..540d6ad3828a202559b2bc66cb5a48a252d1057d --- /dev/null +++ b/compositor/cms-static.c @@ -0,0 +1,124 @@ +/* + * 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; +} diff --git a/compositor/executable.c b/compositor/executable.c new file mode 100644 index 0000000000000000000000000000000000000000..0644077afbf86411837c6bec848d254f9d1f14f0 --- /dev/null +++ b/compositor/executable.c @@ -0,0 +1,34 @@ +/* + * 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); +} diff --git a/compositor/main.c b/compositor/main.c new file mode 100755 index 0000000000000000000000000000000000000000..efaa7b22ca208bf9bd938d2e60817498c2dc4c2d --- /dev/null +++ b/compositor/main.c @@ -0,0 +1,3463 @@ +/* + * 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; +// OHOS remove logger +//static struct weston_log_scope *log_scope; +//static struct weston_log_scope *protocol_scope; +static int cached_tm_mday = -1; + +// OHOS remove logger +//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 bool +//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)); +// } else { +// fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno)); +// return false; +// } +// } +// +// if (weston_logfile == NULL) +// weston_logfile = stderr; +// else +// setvbuf(weston_logfile, NULL, _IOLBF, 256); +// +// return true; +//} +// +//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" + " --continue-without-input\tAllow the compositor to start without input devices\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" + " --scale=SCALE\t\tScale factor of output\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 }, + { "rotate-90", WL_OUTPUT_TRANSFORM_90 }, + { "rotate-180", WL_OUTPUT_TRANSFORM_180 }, + { "rotate-270", WL_OUTPUT_TRANSFORM_270 }, + { "flipped", WL_OUTPUT_TRANSFORM_FLIPPED }, + { "flipped-rotate-90", WL_OUTPUT_TRANSFORM_FLIPPED_90 }, + { "flipped-rotate-180", WL_OUTPUT_TRANSFORM_FLIPPED_180 }, + { "flipped-rotate-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 int +wet_output_set_transform(struct weston_output *output, + struct weston_config_section *section, + uint32_t default_transform, + uint32_t parsed_transform) +{ + char *t = NULL; + uint32_t transform = default_transform; + + if (section) { + weston_config_section_get_string(section, + "transform", &t, NULL); + } + + if (t) { + if (weston_parse_transform(t, &transform) < 0) { + weston_log("Invalid transform \"%s\" for output %s\n", + t, output->name); + return -1; + } + free(t); + } + + if (parsed_transform != UINT32_MAX) + transform = parsed_transform; + + weston_output_set_transform(output, transform); + + return 0; +} + +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); + if (wet_output_set_transform(output, section, defaults->transform, + parsed_options->transform) < 0) { + return -1; + } + + 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; + uint32_t transform = WL_OUTPUT_TRANSFORM_NORMAL; + 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); + + if (count_remaining_heads(output, NULL) == 1) { + struct weston_head *head = weston_output_get_first_head(output); + transform = weston_head_get_transform(head); + } + + wet_output_set_scale(output, section, 1, 0); + if (wet_output_set_transform(output, section, transform, + UINT32_MAX) < 0) { + return -1; + } + + 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); + if (wet_output_set_transform(output, section, + WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX) < 0) { + return -1; + }; + + 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); + if (wet_output_set_transform(output, section, + WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX) < 0) { + return -1; + } + + 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 }, + { WESTON_OPTION_BOOLEAN, "continue-without-input", 0, &config.continue_without_input }, + }; + + 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 = false; + 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_INTEGER, "scale", 0, &parsed_options->scale }, + { 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); + return -1; + } + 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"); + + if (wet_output_set_transform(output, section, + WL_OUTPUT_TRANSFORM_NORMAL, + UINT32_MAX) < 0) { + return -1; + } + + 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_BOOLEAN, "use-tde", 0, &config.use_tde }, // OHOS tde + { 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 + +// OHOS remove logger +//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 }; +// OHOS remove logger +// 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; + } + +// OHOS remove logger +// log_ctx = weston_log_ctx_create(); +// if (!log_ctx) { +// fprintf(stderr, "Failed to initialize weston debug framework.\n"); +// return EXIT_FAILURE; +// } +// +// log_scope = weston_log_ctx_add_log_scope(log_ctx, "log", +// "Weston and Wayland log\n", NULL, NULL, NULL); +// +// if (!weston_log_file_open(log)) +// return EXIT_FAILURE; +// +// 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); + +// OHOS remove debugger +// 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(); + } + +// OHOS remove logger +// wet.compositor = weston_compositor_create(display, log_ctx, &wet); + wet.compositor = weston_compositor_create(display, &wet); + if (wet.compositor == NULL) { + weston_log("fatal: failed to create compositor\n"); + goto out; + } + segv_compositor = wet.compositor; + +// OHOS remove log, debugger +// protocol_scope = +// weston_log_ctx_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); + // OHOS + // if (wet.init_failed) + // goto out; + if (wet.init_failed && wl_list_empty(&wet.compositor->output_list)) + 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); + +// OHOS remove logger +// if (protologger) +// wl_protocol_logger_destroy(protologger); + + weston_compositor_destroy(wet.compositor); +// OHOS remove logger +// weston_log_scope_destroy(protocol_scope); +// protocol_scope = NULL; +// weston_log_scope_destroy(log_scope); +// log_scope = NULL; +// weston_log_subscriber_destroy(logger); +// weston_log_subscriber_destroy(flight_rec); +// weston_log_ctx_destroy(log_ctx); + +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: +// OHOS remove logger +// 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; +} diff --git a/compositor/meson.build b/compositor/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..9dc95f3fc7c2f3d83839c944ea72a0d58ce9d5cc --- /dev/null +++ b/compositor/meson.build @@ -0,0 +1,187 @@ +srcs_weston = [ + git_version_h, + 'main.c', + 'testsuite-util.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, + install_rpath: '$ORIGIN' + ) + 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, + install_rpath: '$ORIGIN' + ) + 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, dep_lcms2 ] + + 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: '', + 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 +) diff --git a/compositor/screen-share.c b/compositor/screen-share.c new file mode 100644 index 0000000000000000000000000000000000000000..8c37452da67413c1f232b86f7ec6167363107d29 --- /dev/null +++ b/compositor/screen-share.c @@ -0,0 +1,1184 @@ +/* + * 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, 0, fw); + 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, fh, 0); + 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; + struct weston_config_section *section; + + ss = zalloc(sizeof *ss); + if (ss == NULL) + return -1; + ss->compositor = compositor; + + config = wet_get_config(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; +} diff --git a/compositor/systemd-notify.c b/compositor/systemd-notify.c new file mode 100644 index 0000000000000000000000000000000000000000..61196d8182224427479dbe88676c0379d11eab24 --- /dev/null +++ b/compositor/systemd-notify.c @@ -0,0 +1,168 @@ +/* + * 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; +} + diff --git a/compositor/testsuite-util.c b/compositor/testsuite-util.c new file mode 100644 index 0000000000000000000000000000000000000000..34882d1fc2c5da1cc9a9c2ec58fbd0d7cfe2e81e --- /dev/null +++ b/compositor/testsuite-util.c @@ -0,0 +1,62 @@ +/* + * 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 +#include "weston.h" + +static struct wet_testsuite_data *wet_testsuite_data_global; + +/** Set global test suite data + * + * \param data Custom test suite data. + * + * The type struct wet_testsuite_data is free to be defined by any test suite + * in any way they want. This function stores a single pointer to that data + * in a global variable. + * + * The data is expected to be fetched from a test suite specific plugin that + * knows how to interpret it. + * + * \sa wet_testsuite_data_get + */ +WL_EXPORT void +wet_testsuite_data_set(struct wet_testsuite_data *data) +{ + wet_testsuite_data_global = data; +} + +/** Get global test suite data + * + * \return Custom test suite data. + * + * Returns the value last set with wet_testsuite_data_set(). + */ +WL_EXPORT struct wet_testsuite_data * +wet_testsuite_data_get(void) +{ + return wet_testsuite_data_global; +} diff --git a/compositor/text-backend.c b/compositor/text-backend.c new file mode 100644 index 0000000000000000000000000000000000000000..722dcb2c1c4a86118db9fdedc1ade6f4542bf757 --- /dev/null +++ b/compositor/text-backend.c @@ -0,0 +1,1099 @@ +/* + * 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; +} diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c new file mode 100755 index 0000000000000000000000000000000000000000..fd2234c80f54ff314a6efb62b076c87b5295e9bc --- /dev/null +++ b/compositor/weston-screenshooter.c @@ -0,0 +1,202 @@ +/* + * 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; +// OHOS remove debugger +// bool debug_enabled = +// weston_compositor_is_debug_protocol_enabled(shooter->ec); + bool debug_enabled = false; + + 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); +} diff --git a/compositor/weston.desktop b/compositor/weston.desktop new file mode 100644 index 0000000000000000000000000000000000000000..009b6929aa478e97a419b318ca28ef1e494ec390 --- /dev/null +++ b/compositor/weston.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=Weston +Comment=The reference Wayland server +Exec=weston +Type=Application diff --git a/compositor/weston.h b/compositor/weston.h new file mode 100644 index 0000000000000000000000000000000000000000..e09397f9b1c23db2863d43efb0021e28787a4f07 --- /dev/null +++ b/compositor/weston.h @@ -0,0 +1,117 @@ +/* + * 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[]); + + +/* test suite utilities */ + +/** Opaque type for a test suite to define. */ +struct wet_testsuite_data; + +void +wet_testsuite_data_set(struct wet_testsuite_data *data); + +struct wet_testsuite_data * +wet_testsuite_data_get(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/compositor/xwayland.c b/compositor/xwayland.c new file mode 100644 index 0000000000000000000000000000000000000000..8eadbe252190a2d624bdf24d247a39eb72066672 --- /dev/null +++ b/compositor/xwayland.c @@ -0,0 +1,212 @@ +/* + * 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; +} diff --git a/data/COPYING b/data/COPYING new file mode 100644 index 0000000000000000000000000000000000000000..034502397205b2bf37a97ef62ea2a16f82931db9 --- /dev/null +++ b/data/COPYING @@ -0,0 +1,55 @@ +For 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 diff --git a/data/background.png b/data/background.png new file mode 100644 index 0000000000000000000000000000000000000000..67caee38c48b2f9ea146d1eeb37f3bee0d80052d Binary files /dev/null and b/data/background.png differ diff --git a/data/border.png b/data/border.png new file mode 100644 index 0000000000000000000000000000000000000000..3f9388bfa65102d768332f9542838d36d1b4440e Binary files /dev/null and b/data/border.png differ diff --git a/data/fullscreen.png b/data/fullscreen.png new file mode 100644 index 0000000000000000000000000000000000000000..dd4944b15db6cc32c9a36ecb8003f95bc80fe699 Binary files /dev/null and b/data/fullscreen.png differ diff --git a/data/home.png b/data/home.png new file mode 100644 index 0000000000000000000000000000000000000000..b7ff0a55459d9119b1bb5bee257a7eb742a01419 Binary files /dev/null and b/data/home.png differ diff --git a/data/icon_editor.png b/data/icon_editor.png new file mode 100644 index 0000000000000000000000000000000000000000..d1324ad97e9969eec8209930b3b0289b83e0c2b7 Binary files /dev/null and b/data/icon_editor.png differ diff --git a/data/icon_flower.png b/data/icon_flower.png new file mode 100644 index 0000000000000000000000000000000000000000..1e80f2c8cde36d8ceeb69bc2d9ba62ab808d5258 Binary files /dev/null and b/data/icon_flower.png differ diff --git a/data/icon_ivi_clickdot.png b/data/icon_ivi_clickdot.png new file mode 100644 index 0000000000000000000000000000000000000000..392ca95a6278321c102589459d74ae9c6ea84b4b Binary files /dev/null and b/data/icon_ivi_clickdot.png differ diff --git a/data/icon_ivi_flower.png b/data/icon_ivi_flower.png new file mode 100644 index 0000000000000000000000000000000000000000..7d32315660a4a275b461f228d0339a425c2740da Binary files /dev/null and b/data/icon_ivi_flower.png differ diff --git a/data/icon_ivi_simple-egl.png b/data/icon_ivi_simple-egl.png new file mode 100644 index 0000000000000000000000000000000000000000..dedc8eecdcf6037f1cf005f6b197acafbfd690cf Binary files /dev/null and b/data/icon_ivi_simple-egl.png differ diff --git a/data/icon_ivi_simple-shm.png b/data/icon_ivi_simple-shm.png new file mode 100644 index 0000000000000000000000000000000000000000..85676f29ff5806a32ac6713e601fdcb71dd03777 Binary files /dev/null and b/data/icon_ivi_simple-shm.png differ diff --git a/data/icon_ivi_smoke.png b/data/icon_ivi_smoke.png new file mode 100644 index 0000000000000000000000000000000000000000..aead31d3ba14a6f4d7089fd59906e7c0fa95fb9d Binary files /dev/null and b/data/icon_ivi_smoke.png differ diff --git a/data/icon_terminal.png b/data/icon_terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..4c4c3b69468f6083a659092d7522991ec2632eed Binary files /dev/null and b/data/icon_terminal.png differ diff --git a/data/icon_window.png b/data/icon_window.png new file mode 100644 index 0000000000000000000000000000000000000000..2a07cc1619242a84bfbbbf6303fd55872c84a105 Binary files /dev/null and b/data/icon_window.png differ diff --git a/data/icons.svg b/data/icons.svg new file mode 100644 index 0000000000000000000000000000000000000000..f57a02b3bd2f065dc18fc8b02a9e4ad4663d61b1 --- /dev/null +++ b/data/icons.svg @@ -0,0 +1,1012 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + $ weston + + + + + + + + + + + W + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..16f172feca3334c5204d5cbe32098c7d5f560a46 --- /dev/null +++ b/data/meson.build @@ -0,0 +1,29 @@ +install_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') +) diff --git a/data/panel.png b/data/panel.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f46b2b9db87df497f5ae65d3b6ea685a6e8567 Binary files /dev/null and b/data/panel.png differ diff --git a/data/pattern.png b/data/pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..27dddc4baa644f9a2ffb07a3e9d64f30a49c0f29 Binary files /dev/null and b/data/pattern.png differ diff --git a/data/random.png b/data/random.png new file mode 100644 index 0000000000000000000000000000000000000000..2ed2a1c56e96e802bdd82c6a1c87a320669375b4 Binary files /dev/null and b/data/random.png differ diff --git a/data/sidebyside.png b/data/sidebyside.png new file mode 100644 index 0000000000000000000000000000000000000000..f5b6f5363fe77c5f9d856871b035838d6b2c0583 Binary files /dev/null and b/data/sidebyside.png differ diff --git a/data/sign_close.png b/data/sign_close.png new file mode 100644 index 0000000000000000000000000000000000000000..56182a3c460813b04cb1b928db76b00ffed1328b Binary files /dev/null and b/data/sign_close.png differ diff --git a/data/sign_maximize.png b/data/sign_maximize.png new file mode 100644 index 0000000000000000000000000000000000000000..ae9e86abea358a359c42eb6a0406f1b74ab17bbb Binary files /dev/null and b/data/sign_maximize.png differ diff --git a/data/sign_minimize.png b/data/sign_minimize.png new file mode 100644 index 0000000000000000000000000000000000000000..fd3a64aaffa6afd4117a5b13e5c7416cc6555253 Binary files /dev/null and b/data/sign_minimize.png differ diff --git a/data/terminal.png b/data/terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..6295753447c26151ff4fdedb191f344abcb24bc2 Binary files /dev/null and b/data/terminal.png differ diff --git a/data/tiling.png b/data/tiling.png new file mode 100644 index 0000000000000000000000000000000000000000..3d711dc077e3ac4361a886d7428e2763b2e5dcbb Binary files /dev/null and b/data/tiling.png differ diff --git a/data/wayland.png b/data/wayland.png new file mode 100644 index 0000000000000000000000000000000000000000..fe3525ed40c19dc6a03bd209f0851b2ce2946785 Binary files /dev/null and b/data/wayland.png differ diff --git a/data/wayland.svg b/data/wayland.svg new file mode 100644 index 0000000000000000000000000000000000000000..f1488155cf4d4ee5d1e33f9ec12fea38300f4451 --- /dev/null +++ b/data/wayland.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c new file mode 100644 index 0000000000000000000000000000000000000000..7e9a324c417c981d856394c3bee15a167f5626f9 --- /dev/null +++ b/desktop-shell/exposay.c @@ -0,0 +1,737 @@ +/* + * 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); +} + +/* Compute each surface size and then inner pad (10% of surface size). + * After that, it's necessary to recompute surface size (90% of its + * original size). Also, each surface can't be bigger than half the + * exposay area width and height. + */ +static void +exposay_surface_and_inner_pad_size(pixman_rectangle32_t exposay_area, struct exposay_output *eoutput) +{ + if (exposay_area.height < exposay_area.width) + eoutput->surface_size = exposay_area.height / eoutput->grid_size; + else + eoutput->surface_size = exposay_area.width / eoutput->grid_size; + + eoutput->padding_inner = eoutput->surface_size / 10; + eoutput->surface_size -= eoutput->padding_inner; + + if ((uint32_t)eoutput->surface_size > (exposay_area.width / 2)) + eoutput->surface_size = exposay_area.width / 2; + if ((uint32_t)eoutput->surface_size > (exposay_area.height / 2)) + eoutput->surface_size = exposay_area.height / 2; +} + +/* Compute the exposay top/left margin in order to centralize it */ +static void +exposay_margin_size(struct desktop_shell *shell, pixman_rectangle32_t exposay_area, + int row_size, int column_size, int *left_margin, int *top_margin) +{ + (*left_margin) = exposay_area.x + (exposay_area.width - row_size) / 2; + (*top_margin) = exposay_area.y + (exposay_area.height - column_size) / 2; +} + +/* 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. + * + * 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 maximise the area made available to surfaces + * after this. Also, add an inner padding between surfaces that varies + * with the surface size (10% of its size). + * + * If we can't make a square grid, add one extra row at the bottom which + * will have a smaller number of columns. + */ +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; + pixman_rectangle32_t exposay_area; + int pad, row_size, column_size, left_margin, top_margin; + int last_row_size, last_row_margin_increase; + int populated_rows; + int i; + + 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->padding_inner = 0; + eoutput->surface_size = 0; + return EXPOSAY_LAYOUT_OVERVIEW; + } + + /* Get exposay area and position, taking into account + * the shell panel position and size */ + get_output_work_area(shell, output, &exposay_area); + + /* Compute grid size */ + eoutput->grid_size = floor(sqrtf(eoutput->num_surfaces)); + if (pow(eoutput->grid_size, 2) != eoutput->num_surfaces) + eoutput->grid_size++; + + /* Compute each surface size and the inner padding between them */ + exposay_surface_and_inner_pad_size(exposay_area, eoutput); + + /* Compute each row/column size */ + pad = eoutput->surface_size + eoutput->padding_inner; + row_size = (pad * eoutput->grid_size) - eoutput->padding_inner; + /* We may have empty rows that should be desconsidered to compute + * column size */ + populated_rows = ceil(eoutput->num_surfaces / (float) eoutput->grid_size); + column_size = (pad * populated_rows) - eoutput->padding_inner; + + /* The last row size can be different, since it may have less surfaces + * than the grid size. Also, its margin may be increased to centralize + * its surfaces, in the case where we don't have a perfect grid. */ + last_row_size = ((eoutput->num_surfaces % eoutput->grid_size) * pad) - eoutput->padding_inner; + if (eoutput->num_surfaces % eoutput->grid_size) + last_row_margin_increase = (row_size - last_row_size) / 2; + else + last_row_margin_increase = 0; + + /* Compute a top/left margin to centralize the exposay */ + exposay_margin_size(shell, exposay_area, row_size, column_size, &left_margin, &top_margin); + + i = 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; + + 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 = left_margin + (pad * esurface->column); + esurface->y = top_margin + (pad * esurface->row); + + /* If this is the last row, increase left margin (it sums 0 if + * we have a perfect square) to centralize the surfaces */ + if (eoutput->num_surfaces / eoutput->grid_size == esurface->row) + esurface->x += last_row_margin_increase; + + 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; + + /* Surfaces are usually rectangular, but their exposay surfaces + * are square. centralize them in their own square */ + if (esurface->width > esurface->height) + esurface->y += (esurface->width - esurface->height) / 2; + else + esurface->x += (esurface->height - esurface->width) / 2; + + 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); +} diff --git a/desktop-shell/input-panel.c b/desktop-shell/input-panel.c new file mode 100644 index 0000000000000000000000000000000000000000..8292f20a6c1f7cbc3292e332bd244dc4299b325c --- /dev/null +++ b/desktop-shell/input-panel.c @@ -0,0 +1,412 @@ +/* + * 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; +} diff --git a/desktop-shell/meson.build b/desktop-shell/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..c67bfd6003a524f34ffb762b436840225ed2c347 --- /dev/null +++ b/desktop-shell/meson.build @@ -0,0 +1,31 @@ +if 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, + install_rpath: '$ORIGIN' + ) + env_modmap += 'desktop-shell.so=@0@;'.format(plugin_shell_desktop.full_path()) +endif diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c new file mode 100755 index 0000000000000000000000000000000000000000..b35aab2097b8e752742269b02f743e72f1336fe2 --- /dev/null +++ b/desktop-shell/shell.c @@ -0,0 +1,5253 @@ +/* + * 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 */ +} + +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; + + if (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); + if (!switcher) + return; + + 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); + +// OHOS remove text_backend +// 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; + +// OHOS remove text_backend +// 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); +// OHOS +// 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; +} diff --git a/desktop-shell/shell.h b/desktop-shell/shell.h new file mode 100644 index 0000000000000000000000000000000000000000..9f0b6abe92315d4f1c495b454b72151de166a111 --- /dev/null +++ b/desktop-shell/shell.h @@ -0,0 +1,263 @@ +/* + * 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 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 +get_output_work_area(struct desktop_shell *shell, + struct weston_output *output, + pixman_rectangle32_t *area); + +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); diff --git a/doc/doxygen/devtools.dox b/doc/doxygen/devtools.dox new file mode 100644 index 0000000000000000000000000000000000000000..2d6672fb3e012111a2f104c69d0c7b06ef87efd4 --- /dev/null +++ b/doc/doxygen/devtools.dox @@ -0,0 +1,51 @@ +/* + * 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" + +*/ diff --git a/doc/doxygen/tooldev.doxygen.in b/doc/doxygen/tooldev.doxygen.in new file mode 100644 index 0000000000000000000000000000000000000000..b3d86f58e6b453300a8cbc678b90abc2b34af1b7 --- /dev/null +++ b/doc/doxygen/tooldev.doxygen.in @@ -0,0 +1,12 @@ +PROJECT_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@ diff --git a/doc/doxygen/tools.dox b/doc/doxygen/tools.dox new file mode 100644 index 0000000000000000000000000000000000000000..9bbc11d6db8828fa5f6cdc0a283ace2ddf86246e --- /dev/null +++ b/doc/doxygen/tools.dox @@ -0,0 +1,31 @@ +/* + * 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 + +*/ diff --git a/doc/doxygen/tools.doxygen.in b/doc/doxygen/tools.doxygen.in new file mode 100644 index 0000000000000000000000000000000000000000..613edd47dd40603f7a65ee90af5c4a0a5a1d01bf --- /dev/null +++ b/doc/doxygen/tools.doxygen.in @@ -0,0 +1,11 @@ +PROJECT_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@ diff --git a/doc/doxygen/tools_arch_new.gv b/doc/doxygen/tools_arch_new.gv new file mode 100644 index 0000000000000000000000000000000000000000..e4626f3f2e705525182006b5756862755da8c5c6 --- /dev/null +++ b/doc/doxygen/tools_arch_new.gv @@ -0,0 +1,85 @@ +/* + * 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 +} diff --git a/doc/doxygen/tools_arch_old.gv b/doc/doxygen/tools_arch_old.gv new file mode 100644 index 0000000000000000000000000000000000000000..1b381232840a7ed06264ffb41c2c3626d7292902 --- /dev/null +++ b/doc/doxygen/tools_arch_old.gv @@ -0,0 +1,53 @@ +/* + * 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 +} diff --git a/doc/scripts/calibration-helper.bash b/doc/scripts/calibration-helper.bash new file mode 100755 index 0000000000000000000000000000000000000000..72effe3a8bf70310f630b36455983a5a59876204 --- /dev/null +++ b/doc/scripts/calibration-helper.bash @@ -0,0 +1,66 @@ +#!/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. diff --git a/doc/scripts/gdb/flight_rec.py b/doc/scripts/gdb/flight_rec.py new file mode 100755 index 0000000000000000000000000000000000000000..9872f17fd0ed6223fc1da81cc9b537a3eda6aa81 --- /dev/null +++ b/doc/scripts/gdb/flight_rec.py @@ -0,0 +1,103 @@ +# +# 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() diff --git a/doc/scripts/remoting-client-receive.bash b/doc/scripts/remoting-client-receive.bash new file mode 100755 index 0000000000000000000000000000000000000000..b518689a611990c56be8fa08f767520e17c42130 --- /dev/null +++ b/doc/scripts/remoting-client-receive.bash @@ -0,0 +1,38 @@ +#!/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 diff --git a/doc/sphinx/conf.py.in b/doc/sphinx/conf.py.in new file mode 100644 index 0000000000000000000000000000000000000000..ab8f9bf11429b3e14e0dabae8c52ca8af2a16879 --- /dev/null +++ b/doc/sphinx/conf.py.in @@ -0,0 +1,204 @@ +# -*- 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' + +# To automatically number figures, tables, etc. and be able to reference them. +numfig = True + +# -- 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 diff --git a/doc/sphinx/doxygen.ini.in b/doc/sphinx/doxygen.ini.in new file mode 100644 index 0000000000000000000000000000000000000000..cfeba0905770ecf8860383d0f3165a2dd3b2f8d1 --- /dev/null +++ b/doc/sphinx/doxygen.ini.in @@ -0,0 +1,2480 @@ +# 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 \ + @SRC_ROOT@/tests + +# 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 +# , /