pax_global_header00006660000000000000000000000064142211165000014502gustar00rootroot0000000000000052 comment=4f72cf1e67a3f0f224326b2662b7dedab3f5a8cf phoc-v0.13.1/000077500000000000000000000000001422111650000127035ustar00rootroot00000000000000phoc-v0.13.1/.dir-locals.el000066400000000000000000000005731422111650000153410ustar00rootroot00000000000000;; Indentation defaults for emacs ( (c-mode . ( (c-file-style . "linux") (indent-tabs-mode . nil) (c-basic-offset . 2) )) (setq auto-mode-alist (cons '("\\.ui$" . nxml-mode) auto-mode-alist)) (nxml-mode . ( (indent-tabs-mode . nil) )) (css-mode . ( (css-indent-offset . 2) )) ) phoc-v0.13.1/.editorconfig000066400000000000000000000005721422111650000153640ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true [meson.build] indent_size = 2 tab_size = 2 indent_style = space [*.{c,h,h.in}] indent_size = 2 tab_size = 2 indent_style = space max_line_length = 80 [*.xml] indent_size = 2 tab_size = 2 indent_style = space [*.json] indent_size = 2 tab_size = 2 indent_style = space [NEWS] max_line_length = 72 phoc-v0.13.1/.gitignore000066400000000000000000000003041422111650000146700ustar00rootroot00000000000000.clang_complete _build/ wayland-*-protocol.* On branch master debian/.debhelper/ debian/debhelper-build-stamp debian/files debian/phoc/ debian/tmp debian/*.debhelper.log debian/*.substvars obj-*/ phoc-v0.13.1/.gitlab-ci.yml000066400000000000000000000057321422111650000153460ustar00rootroot00000000000000include: - 'https://source.puri.sm/Librem5/librem5-ci/raw/master/librem5-pipeline-definitions.yml' stages: - build - test+docs - package - test-package - deploy variables: DEPS: git lcov ALPINE_EDGE_DEPS: | git meson ninja gnome-desktop-dev gobject-introspection-dev libinput-dev wayland-dev wayland-protocols libxkbcommon-dev wlroots-dev XVFB_RUN: xvfb-run -s -noreset DEBIAN_IMAGE: $CI_REGISTRY/world/phosh/phoc/debian:v0.0.20211205-wlroots0.14.1 .build: &build script: - export LC_ALL=C.UTF-8 - git submodule update --init - meson . _build $BUILD_ARGS - ninja -C _build - ninja -C _build install .before_script_debian: &before_script_debian - rm -f ../* || true - dpkg -l .before_script_alpine: &before_script_alpine - apk -q add alpine-sdk - apk -q add $ALPINE_EDGE_DEPS build-with-xwayland-debian-bookworm: stage: build image: ${DEBIAN_IMAGE} before_script: *before_script_debian variables: BUILD_ARGS: -Dembed-wlroots=disabled -Dxwayland=enabled -Db_coverage=true <<: *build artifacts: paths: - _build except: variables: - $PKG_ONLY == "1" build-with-xwayland-alpinelinux-edge: stage: build image: alpine:edge before_script: *before_script_alpine variables: BUILD_ARGS: -Dxwayland=enabled <<: *build allow_failure: true except: variables: - $PKG_ONLY == "1" build-without-xwayland-alpinelinux-edge: stage: build image: alpine:edge before_script: *before_script_alpine variables: BUILD_ARGS: -Dxwayland=disabled <<: *build allow_failure: true except: variables: - $PKG_ONLY == "1" unit-test-with-xwayland-debian-bookworm: stage: test+docs image: ${DEBIAN_IMAGE} needs: - build-with-xwayland-debian-bookworm before_script: *before_script_debian script: - ${XVFB_RUN} meson test --print-errorlogs -C _build - ninja -C _build coverage coverage: '/^\s+lines\.+:\s+([\d.]+\%)\s+/' artifacts: when: always paths: - _build except: variables: - $PKG_ONLY == "1" build-gtkdoc: stage: test+docs image: ${DEBIAN_IMAGE} before_script: *before_script_debian variables: BUILD_ARGS: -Dgtk_doc=true -Dembed-wlroots=disabled script: - git clean -dfx - 'echo "Build opts: ${BUILD_ARGS}"' - meson . _build $BUILD_ARGS - ninja -C _build - mv _build/doc/phoc-*/ _reference/ artifacts: paths: - _reference except: variables: - $PKG_ONLY == "1" package-pureos-byzantium:arm64: variables: L5_DOCKER_IMAGE: pureos/byzantium tags: - aarch64 extends: .l5-build-debian-package package-pureos-byzantium:sanitizers:arm64: variables: L5_DOCKER_IMAGE: pureos/byzantium DEB_BUILD_PROFILES: 'pkg.phoc.embedwlroots pkg.phoc.sanitizers' tags: - aarch64 when: manual extends: .l5-build-debian-package pages: stage: deploy needs: - build-gtkdoc script: - mv _reference/ public/ artifacts: paths: - public only: - master phoc-v0.13.1/.gitlab-ci/000077500000000000000000000000001422111650000146145ustar00rootroot00000000000000phoc-v0.13.1/.gitlab-ci/README.md000066400000000000000000000012211422111650000160670ustar00rootroot00000000000000### Checklist for Updating the Docker Images - [ ] Update the `${image}.Dockerfile` file with the dependencies - [ ] Run `./run-docker.sh build --base ${image} --version ${number}` - [ ] Run `./run-docker.sh push --base ${image} --version ${number}` once the Docker image is built; you may need to log in by using `docker login` or `podman login` like podman login -u -p registry.gitlab.gnome.org/world/phosh/phoc See https://docs.gitlab.com/ee/user/packages/container_registry/ - [ ] Update the `image` keys in the `.gitlab-ci.yml` file with the new image tag - [ ] Open a merge request with your changes and let it run phoc-v0.13.1/.gitlab-ci/debian.Dockerfile000066400000000000000000000011611422111650000200260ustar00rootroot00000000000000FROM debian:bookworm-slim RUN export DEBIAN_FRONTEND=noninteractive \ && echo "deb http://deb.debian.org/debian/ experimental main" >> /etc/apt/sources.list.d/exp.list \ && apt-get -y update \ && apt-get -y install --no-install-recommends libwlroots-dev/experimental \ && apt-get -y install --no-install-recommends wget ca-certificates gnupg eatmydata \ && eatmydata apt-get -y update \ && cd /home/user/app \ && eatmydata apt-get --no-install-recommends -y build-dep . \ && eatmydata apt-get --no-install-recommends -y install build-essential git wget gcovr locales \ && eatmydata apt-get clean phoc-v0.13.1/.gitlab-ci/run-docker.sh000077500000000000000000000070741422111650000172340ustar00rootroot00000000000000#!/bin/bash read_arg() { # $1 = arg name # $2 = arg value # $3 = arg parameter local rematch='^[^=]*=(.*)$' if [[ $2 =~ $rematch ]]; then read "$1" <<< "${BASH_REMATCH[1]}" else read "$1" <<< "$3" # There is no way to shift our callers args, so # return 1 to indicate they should do it instead. return 1 fi } set -e build=0 run=0 push=0 list=0 print_help=0 no_login=0 while (($# > 0)); do case "${1%%=*}" in build) build=1;; run) run=1;; push) push=1;; list) list=1;; help) print_help=1;; --base|-b) read_arg base "$@" || shift;; --version|-v) read_arg base_version "$@" || shift;; --no-login) no_login=1;; *) echo -e "\e[1;31mERROR\e[0m: Unknown option '$1'"; exit 1;; esac shift done if [ $print_help == 1 ]; then echo "$0 - Build and run Docker images" echo "" echo "Usage: $0 [options] [basename]" echo "" echo "Available commands" echo "" echo " build --base= - Build Docker image .Dockerfile" echo " run --base= - Run Docker image " echo " push --base= - Push Docker image to the registry" echo " list - List available images" echo " help - This help message" echo "" exit 0 fi cd "$(dirname "$0")" if [ $list == 1 ]; then echo "Available Docker images:" for f in *.Dockerfile; do filename=$( basename -- "$f" ) basename="${filename%.*}" echo -e " \e[1;39m$basename\e[0m" done exit 0 fi # All commands after this require --base to be set if [ -z $base ]; then echo "Usage: $0 " exit 1 fi if [ ! -f "$base.Dockerfile" ]; then echo -e "\e[1;31mERROR\e[0m: Dockerfile for '$base' not found" exit 1 fi if [ -z $base_version ]; then base_version="latest" elif [ $base_version != "latest" ]; then base_version="v$base_version" fi if [ ! -x "$(command -v docker)" ] || [ docker --help |& grep -q podman ]; then # Docker is actually implemented by podman, and its OCI output # is incompatible with some of the dockerd instances on GitLab # CI runners. echo "Using: Podman" format="--format docker" CMD="podman" else echo "Using: Docker" format="" CMD="sudo docker" fi REGISTRY="registry.gitlab.gnome.org" REPO="world/phosh/phoc" TAG="${REGISTRY}/${REPO}/${base}:${base_version}" if [ $build == 1 ]; then echo -e "\e[1;32mBUILDING\e[0m: ${base} as ${TAG}" ${CMD} build \ ${format} \ --volume "$(pwd)/..:/home/user/app" \ --build-arg HOST_USER_ID="$UID" \ --tag "${TAG}" \ --file "${base}.Dockerfile" . exit $? fi if [ $push == 1 ]; then echo -e "\e[1;32mPUSHING\e[0m: ${base} as ${TAG}" if [ $no_login == 0 ]; then ${CMD} login ${REGISTRY} fi ${CMD} push ${TAG} exit $? fi if [ $run == 1 ]; then echo -e "\e[1;32mRUNNING\e[0m: ${base} as ${TAG}" ${CMD} run \ --rm \ --volume "$(pwd)/..:/home/user/app" \ --workdir "/home/user/app" \ --tty \ --interactive "${TAG}" \ bash exit $? fi phoc-v0.13.1/.gitmodules000066400000000000000000000002011422111650000150510ustar00rootroot00000000000000[submodule "subprojects/wlroots"] path = subprojects/wlroots url = https://source.puri.sm/Librem5/wlroots.git branch = master phoc-v0.13.1/COPYING000066400000000000000000001045131422111650000137420ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . phoc-v0.13.1/HACKING.md000066400000000000000000000030201422111650000142640ustar00rootroot00000000000000Building ======== For build instructions see the README.md Pull requests ============= Before filing a pull request run the tests: ```sh ninja -C _build test ``` Use descriptive commit messages, see https://wiki.gnome.org/Git/CommitMessages and check https://wiki.openstack.org/wiki/GitCommitMessages for good examples. Coding Style ============ The code base currently uses two coding styles 1. the one followed in [libhandy][1] 2. the [wlroots][2] one (for files taken from wlroots) New files should use [libhandy][1] style. For other files use the style prevalent in that file. It's also o.k. to use [libhandy][1] style for completely new functions and structs in a file indented otherwise but don't mix indentation within a single function or struct. ## Function names New public functions and structs should have a `phoc_` prefix for consistencty and so they get picked up with documentation builds ## `wl_listener` callbacks Callbacks for `wl_listener` should be prefixed with `handle_`: ```c self->keyboard_key.notify = handle_keyboard_key; wl_signal_add (&device->keyboard->events.key, &self->keyboard_key); ``` ## GObject signal callbacks Callbacks for GObject signals should be prefixed with `on_`. ```c g_signal_connect_swapped (keyboard, "device-destroy", G_CALLBACK (on_keyboard_destroy), seat); ``` [1]: https://source.puri.sm/Librem5/libhandy/blob/master/HACKING.md [2]: https://github.com/swaywm/wlroots/blob/master/CONTRIBUTING.md phoc-v0.13.1/README.md000066400000000000000000000051171422111650000141660ustar00rootroot00000000000000Phone compositor ================ [![Code coverage](https://source.puri.sm/Librem5/phoc/badges/master/coverage.svg)](https://source.puri.sm/Librem5/phoc/commits/master) [wlroots][1] based Phone compositor as used on the Librem5. Phoc is pronounced like the English word fog. ## Dependencies On a Debian based system run: ```sh sudo apt-get -y install build-essential sudo apt-get -y build-dep . ``` For an explicit list of dependencies check the `Build-Depends` entry in the [debian/control][] file. ## Building We use the meson (and thereby Ninja) build system for phoc. The quickest way to get going is to do the following: meson . _build ninja -C _build ninja -C _build install This assumes you have wlroots installed on your system. If you don't have that and/or want to build from source run: git submodule update --init meson -Dembed-wlroots=enabled --default-library=static _build ninja -C _build This will fetch a matching version of wlroots and build that as well. ## Running To run from the source tree use _build/run ## Test After making source changes run xvfb-run ninja -C _build test to see if anything broke. # Configuration phoc's behaviour can be configured via `GSettings`. For your convienience, a set of scripts to manipulate config values is available in `helpers` directory. - `scale-to-fit` toggles automatic scaling of applications that don't fit the screen. This setting is enabled per application using its reported app-id. For instance, to enable scaling of GNOME Maps windows use: helpers/scale-to-fit org.gnome.Maps on - `auto-maximize` toggles automatic maximization of Wayland windows. Disabling it allows windows to be resized and moved, which may be desired when running phoc on desktop-like setups. helpers/auto-maximize off Outputs are configured via `phoc.ini` config file - see `src/phoc.ini.example` for more information. # Debugging phoc uses glib so the `G_MESSAGES_DEBUG` environment variable can be used to enable more log messages and `G_DEBUG` to assert on warnings and criticals. The log domains all start with `phoc-` and are usally `phoc-`. All wlroots related messages are logged with `phoc-wlroots`. See https://developer.gnome.org/glib/stable/glib-running.html for more details on these environment variables. There's also a `PHOC_DEBUG` enviroment variable to turn on some debugging features. Use `PHOC_DEBUG=help phoc` to see supported flags. # API docs API documentation is available at https://world.pages.gitlab.gnome.org/Phosh/phoc/ [1]: https://github.com/swaywm/wlroots phoc-v0.13.1/build-aux/000077500000000000000000000000001422111650000145755ustar00rootroot00000000000000phoc-v0.13.1/build-aux/post_install.py000077500000000000000000000004561422111650000176720ustar00rootroot00000000000000#!/usr/bin/python3 import os import subprocess import sys destdir = os.environ.get('DESTDIR', '') if not destdir and len(sys.argv) > 1: datadir = sys.argv[1] print('Compiling gsettings schemas...') subprocess.call(['glib-compile-schemas', os.path.join(datadir, 'glib-2.0', 'schemas')]) phoc-v0.13.1/config.h.in000066400000000000000000000004621422111650000147300ustar00rootroot00000000000000/* * Autogenerated by the Meson build system. * Do not edit, your changes will be lost. */ #pragma once #mesondefine PHOC_VERSION #mesondefine PHOC_XWAYLAND #mesondefine PHOC_HAVE_WLR_SET_STARTUP_ID #mesondefine PHOC_HAVE_WLR_REMOVE_STARTUP_INFO #mesondefine PHOC_HAVE_WLR_XDG_ACTIVATION_V1_ADD_TOKEN phoc-v0.13.1/data/000077500000000000000000000000001422111650000136145ustar00rootroot00000000000000phoc-v0.13.1/data/meson.build000066400000000000000000000012611422111650000157560ustar00rootroot00000000000000schemas = ['sm.puri.phoc.gschema.xml'] compiled = gnome.compile_schemas( build_by_default: true ) install_data( schemas, install_dir: 'share/glib-2.0/schemas' ) devconf = configuration_data() devconf.set('compositor', meson.build_root() / 'src' / 'phoc') devconf.set('devscript', meson.current_build_dir() / 'phoc-dev') devconf.set('devconfig', meson.current_source_dir() / 'phoc-dev.ini') devconf.set('devuid', get_option('dev-uid')) devconf.set('version', meson.project_version()) configure_file( input: 'phoc-dev.in', output: 'phoc-dev', configuration: devconf, ) configure_file( input: 'phoc-dev.service.in', output: 'phoc-dev.service', configuration: devconf, ) phoc-v0.13.1/data/phoc-dev.in000077500000000000000000000012201422111650000156470ustar00rootroot00000000000000#!/bin/sh # # You can edit the processed file in phoc's build directory. COMPOSITOR="@compositor@" PHOC_INI="@devconfig@" help() { cat < true Auto maximize applications Whether to maximize application windows. This only affects Wayland applications and only their main window. false Enable automatic scaling of all oversized windows Whether to scale-to-fit all windows that are larger than the screen they're on. false Enable automatic scaling of oversized windows Whether to scale-to-fit windows belonging to this application that are larger than the screen they're on. phoc-v0.13.1/debian/000077500000000000000000000000001422111650000141255ustar00rootroot00000000000000phoc-v0.13.1/debian/README.source000066400000000000000000000017701422111650000163110ustar00rootroot00000000000000This package is maintained with git-buildpackage(1). It follows DEP-14 for branch naming (e.g. using debian/sid for the current version in Debian unstable). It uses pristine-tar(1) to store enough information in git to generate bit identical tarballs when building the package without having downloaded an upstream tarball first. When working with patches it is recommended to use "gbp pq import" to import the patches, modify the source and then use "gbp pq export --commit" to commit the modifications. The changelog is generated using "gbp dch" so if you submit any changes don't bother to add changelog entries but rather provide a nice git commit message that can then end up in the changelog. It is recommended to build the package with pbuilder using: gbp buildpackage --git-pbuilder For information on how to set up a pbuilder environment see the git-pbuilder(1) manpage. In short: DIST=sid git-pbuilder create gbp clone cd gbp buildpackage --git-pbuilder phoc-v0.13.1/debian/changelog000066400000000000000000001615711422111650000160120ustar00rootroot00000000000000phoc (0.13.1) byzantium; urgency=medium [ Sebastian Krzyszkowiak ] * view: move_resize: Cancel a pending move_resize * render: scan_out_fullscreen_view: Get rid of view centering [ Guido Günther ] * testlib: Convert the whole buffer during abgr_to_argb conversion * layer_shell: Move surfaces between layers before rearranging * tests: Remove superfluous surface commit * layer-shell: Test set_layer -- Sebastian Krzyszkowiak Wed, 30 Mar 2022 20:10:58 +0200 phoc (0.13.0) byzantium; urgency=medium [ Pablo Barciela ] * pointer: remove unused struct member * keyboard: declare some parameters with const * testlib: Fix weird assignment [ Guido Günther ] * scale-to-fit: Split printing of into separate function * scale-to-fit: Make shellcheck happy * seat: Don't dispose input * seat: Invoke finalize * cursor: Finalize object * cursor: Move wlr_xcursor_manager init from seat to cursor * desktop: Destroy output layout * desktop: Make xcursor manager depend on XWayland * desktop: Avoid indirection * settings: Reindent and modernize public functions * Drop ini file parser * settings: Use GKeyFile * settings: Don't exit when parsing settings * settings: Drop remainin users of roots_config_* * settings: Use g_free() to match the g_* allocations * settings: Drop unused prefixes * settings: Use g_assert() * settings: Remove unused includes * settings: Use PHOC_CONFIG_DEFAULT_SEAT_NAME * server: Invoke phoc_config_destroy * Don't include wlr_log.h * server: Sort debug flags alphabetically * view: Use PhocView consistently * view: Use PhocViewType * view: Use PhocViewInterface consistently * view: Fix parameter name * view: Add doc string for view * view: Link PhocChildView to PhocView * d/control: Add dependencies for embedded wlroots build * server: Add and use renderer getter * render: Use g_clear_list * render: Move damage tracking rendering to separate function * render: Don't set clear color again * render: Move fullscreen scanout into scanout_fullscreen * render: Simplify scanout check a bit * output: Add and use phoc_output_has_fullscreen_view () * render: Use faster list empty check * output: Remove listeners in the same order as in the struct * output: Use g_clear_list * render: Invoke wlr_renderer_end(wlr_renderer earlier * view: Fix more doc links * output: Document phoc_output_damage_from_view * output: Add docstring for PhocOutput * server: Add docstring for PhocServer * server: Add docstring for PhocKeyboard * seat: Add getters to query seat capabilities * layer-surface: Add getter for namespace * cursor: Avoid seat round trip for handle_pointer_motion * cursor: Avoid seat round trip for handle_pointer_motion_absolute * cursor: Avoid seat round trip for handle_pointer_button * cursor: Avoid seat round trip for handle_pointer_axis * cursor: Avoid seat round trip for handle_pointer_frame * server: Fix indentation * output: Fix indentation * output: Improve indentation * layer-surface: Document link * layer-surface: Add and use phoc_layer_shell_get_output() * desktop: drop last_frame * output: Drop last_frame * output: Use typedef for iterator * render: Make output_render a method * render: Make view_render_to_buffer a method * render: Use cast * view: Drop unused declaration * utils: Fix doc string * view: Add a GObject as first element * Make xdg-surface a minimal GObject * xdg-surface: Make destruction private * xdg-surface: Add xdg-surface property * xdg-surface: Add xdg_surface_get_geometry * xdg-surface: Move view interface implementation over * server: Move xwayland include out of header * Rename xwayland surface's to PhocXWaylandSurface * Use phoc_xwayland_surface_from_view() * Make PhocXWayland a minimal GObject * xwayland-surface: Set PhocXWaylandSurface on the wlr surface * xwayland-surface: Remove listeners in finalize * xwayland-surface: Move view interface over * Update wlroots submodule * build: Conditionally check for wlr_xdg_activation_v1_add_token * Handle xdg-activation * gtk-shell: Submit startup-id as token to xdg-activation * Make PhocView a GObject * view: Use PHOC_VIEW () as it does type checks * view: Move fixed value init to _init() * view: Drop destroy * view: Move destruction into finalize * view: Drop view_destroy() * view: Drop custom impl * view: Introduce private data * view: Make settings private * view: Make title and app_id private * view: Drop view_init * view: Document virtual functions * view: Prefix view types with PHOC_ * view: Move PhocViewDecoPort * view: Move deco parts into phoc namespace * Use PhocViewDecoPart everywhere * desktop: Drop roots_ * build: Add xdg-surface.h to sources * build: Add xdg-activation-v1.h to sources * view: Make PhocSubsurface private * view: Drop roots_xdg_toplevel_decoration forward declaration * view: Make roots_xdg_toplevel_decoration private * view: Make roots_xdg_popup private * view: Move phoc_xdg_surface_from_view to xdg-surface.h * view: Move phoc_xwayland_surface_from_view to xwayland-surface.h * view: Remove now unneeded forward declarations * view: Drop xdg-* headers * Revert "gitlab-ci: Allow PureOS job to fail" [ Doug Wood ] * scale-to-fit: Query current state if called with just the app id. [ Sebastian Krzyszkowiak ] * view_render_to_buffer: Gracefully handle buffer creation failure * desktop: Handle cases where only some outputs are on in toggle_output_blank * desktop: toggle_output_blank: Ignore outputs that aren't part of the layout * layer-shell: Don't give focus to TOP layer when there's a fullscreen view * cursor: Update layer-shell focus when (un)revealing shell -- Sebastian Krzyszkowiak Fri, 25 Mar 2022 16:47:20 +0100 phoc (0.12.0) byzantium; urgency=medium [ Andreas Streichardt ] * remove xkb default assumptions [ Guido Günther ] * desktop: Enable xdg-foreign support * seat: Drop NULL check * seat: Make object allocation consistent * seat: Fix indentation * seat: Use g_critical instead of wlr_log * layer-shell: Use g_critical instead of wlr_log * desktop: Use g_critical instead of wlr_log * settings: Use g_critical instead of wlr_log * text-input: Use g_debug instead of wlr_log * d/control: Use << for smaller than relationships * d/copyright: Drop references to nonexistent files * d/control: Bump standards version * desktop: Don't remove startup-id listener when not connected * desktop: Drop unused code * desktop: Move xwayland configuration to seprate function * desktop: Deconfigure xwayland only when created * desktop: Handle XWayland startup errors * Use g_setenv() consistently * desktop: Enable viewporter * output: Remove redundant check in phoc_view_accept_damage() * view: Add and use phoc_view_is_mapped * view: Rename and reindent view_child_init to phoc_view_child_init * view: Use typedefs for child views as well * view: Reindent child views * view: Clarify "base" classes * view: Use typedefs in the header * view: Clarify source "object" on `new_subsurface` * subsurface: Rename subsurface_create to phoc_view_subsurface_create * subsurface: Make phoc_view_subsurface_create void * subsurface: Use g_new0 when allocating subsurface * view-child: Use the shorter `child` in structs that embed it * view: Drop output.h include * view: Prefix and reindent view_apply_damage * output: Allow phoc_output_damage_from_view() to optionally damage the whole view * output: Drop phoc_output_damage_whole_view() * view: Make view_damage_whole match phoc_view_apply_damage * view-child: Start tracking mapped state * view-child: Add phoc_view_child_{apply_damage,damage_whole} * xdg-popup: Use phoc_view_child_damage_whole() * view: Move subsurface initialization to separate function * view: Rename and reindent view_child_destroy to phoc_view_child_destroy * view: Move public function to the end * view-child: Use child specific damage function when destroying child * view-child: Make sure to init a childs children * view-child: Document where `link` links to * view: Reindent and rename view_child_handle_new_subsurface * view-child: Invoke the child specific creation function * view: Use the passed in surface * view: Drop remaining use of `struct roots_subsurface` * view: Drop remaining use of `struct roots_view_child` * view-child: Document struct members a bit * view: Only damage child on subsurface map/unmap * view: Reindent and rename view_child_handle_new_commit * view-child: Only damage child on commit * view: Make PhocSubsurface private * output: Rename surface iterator to PhocSurfaceIterator * text-input: Add typedefs * text-input: Separate summary * text-input: Move member documentation to docstring * text-input: Modernize phoc_text_input_create() * text-input: Modernize relay_handle_text_input * text-input: Modernize phoc_input_method_relay_{init,destroy} * text-input: Modernize phoc_input_method_relay_set_focus() * text-input: Modernize relay_handle_input_method() * text-input: Use Phoc* types * text-input: Make PhocTextInput private * text-input: Handle text-input being registered late * output: Use PhocView * desktop: Use PhocView * xwayland: Use PhocView * xdg_shell: Use PhocView * cursor: Use PhocView * keybindings: Use PhocView * seat: Use PhocView * gtk-shell: Use PhocView * input: Use PhocView * layer-shell: Use PhocView * phosh-private: Use PhocView * render: Use PhocView * Rename roots_view_from_wlr_surface * view: Fix comment * output: Use G_N_ELEMENTS() for layers consistently * view: Add phoc_ prefix to view_set_fullscreen * output: Unfullscreen when output damage goes away * output: Free output-damage releated bits in handle_output_damage_destroy * output: Only emit output-destroy from phoc_output_destroy() -- Sebastian Krzyszkowiak Tue, 25 Jan 2022 14:28:19 +0100 phoc (0.11.0) byzantium; urgency=medium [ Sebastian Krzyszkowiak ] * Update to wlroots 0.13 and drop support for older versions * Adjust to subsurface handling changes in wlroots 0.14 * desktop: Switch to wlr_primary_selection_v1 * desktop: Remove unused wlr_list header * render: Visualize touch points with rects instead of circles * Depend on wlroots 0.14 * Thumbnail rendering with wlr_allocator [ Guido Günther ] * build: Allow wlroots >= 0.14.1 * ci: Use system wlroots on Debian * ci: Print package list on Debian * ci: Switch to wlroots 0.14.1 * input: Add guard to public function * testlib: Use g_test_message for buffer match messages * testlib: Print all mismatches * tests: Update empty.png * tests: Update test-xdg-shell-maximized-1.png * tests: Update test-xdg-shell-normal-1.png * tests: Update test-phosh-private-thumbnail-simple-1.png * tests: Update layer-shell screenshots * test-layer-shell: Reverse layer surface distruction * tests: Add resolution for headless backend * tests: Hide thumbnail test behind PHOC_TEST_HAVE_DRM * gitlab-ci: No need for xvfb when collecting coverage * gitlab-ci: Run tests through meson * gitlab-ci: Allow PureOS job to fail -- Sebastian Krzyszkowiak Wed, 15 Dec 2021 21:04:53 +0100 phoc (0.10.0) byzantium; urgency=medium [ Guido Günther ] * cursor: Drop unused struct member * Use g_debug() instead of wlr_log() * build: Sort settings correctly * text-input: Fix doc string * input: Add a doc string * seat: Make a GObject * Add input-device class * pointer: Use PhocInputDevice * touch: Move public function and _init() to the end of the file * touch: Remove unused dispose * touch: Use input-device as base class * touch: Remove unused headers * touch: Add doc string * pointer: Add doc string * data: Add systemd unit and config to simplify phoc development * data: Make dev user id configurable * output: Untangle includes * output: Remove unused includes * output: Use 'pragma once' and G_{BEGIN,END}_DECLS * view: Untangle includes * view: Use 'pragma once' and G_{BEGIN,END}_DECLS * input: Drop unneeded includes * dekstop: Untangle includes * Use #pragma consistently * xcursor: Use PHOC_ prefix * Make layer-surface a GObject * layers: Use PhocLayerSurface * cursor: Use PhocLayerSurface * output: Use PhocLayerSurface * desktop: Use PhocLayerSurface * layer_shell: Use PhocLayerSurface * layer-surface: Remove compat define * README: Fix embed-wlroots option * phosh-private: Properly name callback * output: Fix debug message * build: Make sure g-ir-scanner knows if XWayland is enabled * build: Rename server_protos_sources to protos_sources * seat: Drop superfluous line breaks * Replace PhocSeat leftovers * view: Add typedefs for views * view: Fix doc string * desktop: Document phoc_desktop_surface_at * layer-surface: Add doc string * layer-surface: Move unmap into layer-surface * layer-surface: Move object destruction to finalize * input: Add seat to list of known seats * input: Document phoc_input_get_seat * seat: Don't hold a ref on the input * server: Unref input * input: Unref seat * view: Don't leak title * view: Don't leak app_id * view: Use g_strdup () * desktop: Disconnect wlr signal listeners * ci: Add helpers to build prebuilt docker images * gitlab-ci: Use prebuilt images * data: Set `version` in configuration data too * input: Add G_{BEGIN,END}_DECLS * input: Replace forward declaration by include * input: Add type checks to public functions * input: Move _init and _new downwards * build: Use glib >= 2.64.0 * input: Use GSList instead of wl_list * input: Move PhocInput out of header * input: Drop unused declraration * Move get_last_active_seat * input: Use PhocView * seat: Make arguments names match docstring / definition * touch: Add G_{BEGIN,END}_DECLS * touch: Use correct parent class * cursor: Use surface relative coordinates for touch motion (Closes: #212) * server: Set debug flags early * desktop: Allow to override auto-maximize via a debug flag * utils: Add helpers to get an instance from it's private data * input-device: Add getter for device name * input-device: Add 'device-destroy' signal * touch: Drop "touch-destroyed" signal * pointer: Use "device-destroyed" signal * seat: Use a GSList to track pointers * pointer: Move PhocPointer out of header * seat: Use a GSList to track touch devices * touch: Move PhocTouch out of header * HACKING: document callback names * seat/cursor: Add TODOs * keyboard: Remove trailing semicolons from defines * keyboard: Add G_{BEGIN,END}_DECLS * seat: Drop unused header * keyboard: Drop unused incudes * keyboard: Document parameter in `phoc_keyboard_next_layout` * input: Drop unused include * keyboard: Move list membership removal to seat * keyboard: Derive PhocKeyboard from PhocInput too * keyboard: Use destroy signal from input_device * keyboard: Add "activity" signal * keyboard: Move keyboard listeners from seat to keyboard * seat: Use a GSList for keyboards * keyboard: Add getter for meta key * keyboard: Move PhocKeyboard out of header * keyboard: Make phoc_keyboard_handle_{key,modifiers} static * tablet: Make a minimal GObject * tablet: Use seat from PhocInput * tablet: Use device from PhocInputDevice * tablet: Use "device-destroy" signal * tablet: Remove unused struct members * seat: Use a GSList for tablets * output: Add helper to match on make/model/serial * desktop: Add method to find an output by make/model/serial * desktop: Add method to find built-in output * desktop: Merge #ifdef PHOC_XWAYLAND blocks * input-device: Add type getter * input-device: Drop unneeded cast * input-device: Add getters for vendor and product id * seat: Move variable inits to _init () * seat: Make device-destroy handler names consistent * seat: Don't track device mappings on pointer changes * seat: Drop seat_reset_device_mappings() * seat: Track input-output mappings * output: Use g_* logging functions consistently * output: Improve logging output * input: Fix annotation errors * seat: Hide cursor on tablet proximity out * run: Select a suitable backend for nested [ Sebastian Krzyszkowiak ] * view: Report toplevel's parent via wlr_foreign_toplevel_management * xdg-shell: Send frame done events on configure and close to invisible views * view: Always recenter a floating view in auto maximize mode * xdg-shell: Only compensate size in pending_move_resize for floating views * xwayland: Only compensate size in pending_move_resize for floating views * roots_view_interface: Rename activate and maximize functions * view_arrange_tiled: Handle view scale * view: Take geometry into account when maximizing and tiling * view: Add set_tiled to roots_view_interface * xdg-shell: Implement set_tiled interface * keybindings: Restore the view when trying to tile it again * view_move_resize: Move immediately if size doesn't change * debian: Reintroduce pkg.phoc.embedwlroots build profile support * debian/control: Bump meson dependency to 0.54 * gitlab-ci: Add an optional job to build a deb with ASan and UBSan * xdg-shell: Use output's usable area for popup positioning * layer-shell: Reformat the code in arrange_layers * layer-shell: Split and rename arrange_layers * phoc_layer_shell_arrange: Iterate over a list of layers [ Arnaud Ferraris ] * seat: don't add POINTER capability to tablets -- Sebastian Krzyszkowiak Wed, 15 Dec 2021 20:19:07 +0100 phoc (0.9.0) byzantium; urgency=medium [ Guido Günther ] * ci: Drop unneeded tags * ci: Drop support for bullseye and amber * ci: Add the arm64 job explicitly * server: Don't assert when we fail to initialize the backend * cursor: Rename to PhocCursor and reindent * Make PhocCursor a GObject * seat: Unref cursor * cursor: Use g_debug() instead of wlr_debug() * seat: No need to poke cursor directly * seat: Rename structs to Phoc* and reindent * wlroots: Update to 0.12.x * gitignore: Ignore more package build files * Add dir-locals.el * Rename phosh to phosh-private to match protocol name * phosh-private: Rename test as well * phosh-private: Make a GObject * phosh-private: Make screencopy frame private * phosh-private: Use g_new0 * phosh-private: Remove unused define * phosh-private: Remove panel tracking * phosh-private: Make phoc_phosh_private_from_resource static * phosh-private: Mark unused interfaces and requests * testlib: Wire up gtk_shell1 protocol * phosh-private: Allow clients to be informed about application startup * protocols: Update toplevel management * protocols: Update layer-shell * seat: Ignore docs for internal comments * seat: Fix doc strings * utils: Fix doc strings * settings: Remove unused declarations * settings: Move doc comments to c file * keyboard: Ignore docs for inline function * build: Use shared wlroots * desktop: Add doc header * build: Add headers to list of sources * build: Rename phoc_dep to libphoc_dep * d/control: Add deps for doc build * build: Add gi-docgen * build: Forbid doc generation with embedded wlroots build * build: Add doc generation via introspection data * protocols: Use custom_target instead of generate * gitlab-ci: Update and publish docs * Update screencopy-unstable protocol * wlroots: Update submodule * xwayland: Forward startup-ids as well * desktop: Handle xwayland startup-id removal * Adjust section headers for gi-docgen * desktop: Cleanup headers * server.c: Add doc string * output: Use correct base class for signal * touch: Remove unused headers * touch: Use correct base class for signal * gitlab-ci: Use bookworm * Bump minimum wlroots version to 0.12.0 * build: Generate enum types * server: Set name for wayland source * input: Drop nowadays unused config argument * output: Handle desktop prop as GObject * phosh-private: Remove 'desktop' property * Make renderer a GObject * gitlab-ci: Fix branch name for publishing * README: link to API docs * utils: Add ease in/out helpers * phosh-private: Add shell state protocol and property * server: Allow to pass mode flags * main: Add shell mode command line option * renderer: Emit signals at start and end of rendering loop * server: Render shield until shell is attached [ Sebastian Krzyszkowiak ] * seat: Trigger layer arrangement when unsetting layer focus * xwayland: Guard set_startup_id listener removal with PHOC_HAVE_WLR_SET_STARTUP_ID * seat: Don't allow to start resizing a fullscreen window * cursor, seat: Allow to move fullscreen windows between outputs * view: Don't center tiled views in view_setup * view: Turn roots_view::saved into wlr_box * view: Guard roots_view::saved usage by wlr_box_empty checks * view: Center the surface when resizing after (0, 0) configure * view: Move view_update_output to update_position/size * view: Fix updating surface's fullscreen state when exiting fullscreen * view: Rename PHOC_VIEW_STATE_NORMAL to FLOATING and explicitly initialize * view: Revamp view_is_[state] functions * output: Turn damage_whole_decoration into damage_whole_view * view: Don't recreate GSettings object on every window resize * gtk-shell/xdg-shell: Use app_id from gtk_surface::set_dbus_properties * phosh-private: Fix zwlr_screencopy_frame_v1_flags handling * view: Don't allow unfocused surfaces to make themselves fullscreen * view: Implement fullscreen view handling in view_move_to_next_output * view: Allow to pass output to center the view on to view_center * view: Check whether the view is floating before attempting to center * view: Center the view in view_move_to_next_output [ Evangelos Ribeiro Tzaras ] * server: Stop using deprecated g_spawn_check_exit_status() -- Sebastian Krzyszkowiak Wed, 27 Oct 2021 16:56:10 +0200 phoc (0.8.0) byzantium; urgency=medium [ Guido Günther ] * desktop: Don't create idle-inhibit * settings: Drop device configuration from config file * input: Drop references to config file * input: s/roots/phoc/ * settings: Drop cursor handling * Make pointers proper GObjects * input: Remove unused headers * pointer: Handle some touchpad configuration (Closes: #70) * build: Require gsettings-desktop-schemas * pointer: Handle left-right mode on touchpads * pointer: Handle mouse settings * settings: Drop switch configuration -- Sebastian Krzyszkowiak Sat, 10 Jul 2021 02:02:33 +0200 phoc (0.7.1) byzantium; urgency=medium * cursor: g_assert on NULL cursor. Don't try to go on without a cursor (which can only happen when calloc fails). wlroots also only returns NULL in case of memory problems. This allows to remove the NULL checks elsewhere * tests/phosh: Use GrabStatus. The current accelerator tests try to encode two boolean values into two variables making it a bit hard to read. Use a single enum instead. * tests/phosh: Make tested keybindins more obvious. Currently one needs to look up what's tested in an array but each most of the keys are only used a single time. So drop that array and use a define for the single key that is used multiple times so it's clear we just add an accelerator. * tests/phosh: Set and check both test vars. Always set and check both keybinding results. This tests that we don't invoke the wrong callback accidentally. * phosh: Don't crash when we failed to parse the accelerator. Passing an invalid keybinding otherwise doesn't end well. * phosh: Allow to bind misc keys. Until we allow to bind all keys let's allow for these as well since this block includes e.g. PrintScreen for screenshots. -- Guido Günther Sun, 13 Jun 2021 13:05:17 +0200 phoc (0.7.0) byzantium; urgency=medium [ Sebastian Krzyszkowiak ] * gitlab-ci: Use "needs" keyword to specify dependencies * debian: Drop pkg.phoc.embedwlroots build profile support * gitlab-ci: Switch to CI job templates for amber-phone and byzantium * gitlab-ci: Exclude non-package jobs when PKG_ONLY variable is set * view: Allow to explicitly set an output when tiling or maximizing * cursor: Implement snap-to-edge behavior for tiling and maximizing * cursor: Define PHOC_SHELL_REVEAL_*_THRESHOLD in layout pixels * Take view geometry into account when dealing with saved state * xdg-shell: Adjust position for updated geometry * Make all views appear activated when automaximizing is enabled * text-input: Don't leave dangling roots_text_input signal listeners * text-input: Use text_input_clear_pending_focused_surface in handle_pending_focused_surface_destroy * text-input: Remove relay's signal listeners when seat is destroyed * text-input: Assert a non-null surface in text_input_set_pending_focused_surface * seat: Update the cursor in roots_seat_end_compositor_grab * roots_passthrough_cursor: Reset the cursor when there's no surface under it * seat: Fix primary touch tracking * cursor: Add preliminary touch support for move and resize * render: Use wlr_presentation_surface_sampled_on_output for fullscreen surfaces * render: Use wlr_output_test to check whether fullscreen scan out is possible [ louib ] * Add build-dep command to README -- Sebastian Krzyszkowiak Fri, 19 Mar 2021 03:20:49 +0100 phoc (0.6.0) amber-phone; urgency=medium [ Clayton Craft ] * utils: apply phosh code formatting styles * output: apply phosh code formatting styles * Move rotate_child_position to phoc_utils_rotate_child_position * output: convert roots_output to PhocOutput gobject * desktop: add handler for new output * desktop: add handler for output destroy * Use PhocOutput instead of roots_output * touch: emit g_signal on touch destroy * seat: use touch-destroy signal from PhocTouch * desktop: add input_output_map hash for tracking mapping of input-->output * seat: create mapping of touch devices to outputs * seat: disable touch events when mapped output is disabled [ Sebastian Krzyszkowiak ] * d/rules: Enable all hardening options * Reintroduce a5bdd630bbb67a680aa5308a32c2e69aef0a08e5 (!210) back * server: Don't set _WAYLAND_DISPLAY * view_move: Cancel pending move_resize when moving * seat: begin_move: Try to put the surface behind the cursor when unmaximized * cursor: Update cursor immediately when move/resize/rotate ends * cursor: Don't error out on a unhandled meta key+mouse button press * view: Add view_is_tiled function * view: Split view_restore from view_maximize * view: Don't backup state when the surface is already maximized or tiled * view: Move tiling logic into view_arrange_tiled * view: Move state saving into dedicated function * view_restore: Don't restore saved state * view_set_fullscreen: Correctly restore previous window state when leaving fullscreen * view: Properly restore maximized/tiled views when moved or resized * automaximize: Don't draw surfaces that aren't part of the current stack * desktop: Rename desktop_surface_at to phoc_desktop_surface_at * output: Don't accept damage from views that aren't visible * render: Don't send frame done events to views that aren't visible * view_unmap: Damage the newly activated stack when in automaximize mode * view_activate: Damage the output when disabling forced shell reveal * Remove view rotation handling * Replace struct memcpy's with assigment operator * view_center: Try to clamp the view size into available space * roots_handle_shell_reveal: Do not require surface to be non-null * gitlab-ci: Add package-deb-without-wlroots:arm64 job * roots_handle_shell_reveal: Null-check wlr_output_layout_output_at result * phoc_desktop_view_is_visible: Check for the view being unmapped * view_unmap: Remove from the view list after destroying children [ Guido Günther ] * cursor: Fix missing-default-case warning -- Sebastian Krzyszkowiak Mon, 04 Jan 2021 20:38:41 +0100 phoc (0.5.1) amber-phone; urgency=high [ Dorota Czaplejewicz ] * text_input: Don't forward events from unfocused text inputs -- Sebastian Krzyszkowiak Wed, 18 Nov 2020 21:20:45 +0100 phoc (0.5.0) amber-phone; urgency=high [ Guido Günther ] * phosh: Allow to bind rebular keys plust modifier * gitlab-ci: Remove build artifacts in .. * output: handle_output_manager_apply: Make both loops use the same logic * Drop PHOC_HAS_WLR_OUTPUT_POWER_MANAGEMENT * output: handle_output_manager_apply: Don't disable already disabled outputs * phosh: Define keysym for older libxkb-common * phosh: Allow to bind more keys [ Dorota Czaplejewicz ] * keyboard: Do not override keymap on virtual keyboard [ Sebastian Krzyszkowiak ] * view: Allow to switch outputs of fullscreen surface * output: Refresh fullscreen view when its output is reconfigured * gitlab-ci: Don't pull wlroots from sid * virtual: Allow suggested_output usage on recent enough wlroots [ Alexander Mikhaylenko ] * meson: Compile gschemas on install [ Clayton Craft ] * input: remove unused PhocInput members * touch: add new PhocTouch gobject for touch input devices * seat: use PhocTouch for touch input devices -- Sebastian Krzyszkowiak Sat, 14 Nov 2020 17:54:34 +0100 phoc (0.4.4) amber-phone; urgency=medium [ Guido Günther ] * desktop: Refresh all views when auto-maximize changes. So far this only affected new views but when e.g. unplugging an external (switching to 'phone' mode) we want running apps to behave. * phosh: Drop rotate-display. The rotate_display served us well but it's too simplistic (no events, degrees instead of transforms, ...). Since phosh now uses wlr-output-management for bid to use this part of the private protocol. * d/control: Add breaks for older phosh. These require the rotate_display private protocol bit * phosh: Fix indentation * phosh: Ungrab accelerators. This lead to a compositor crash so far [ Clayton Craft ] * input: convert roots_input to PhocInput gobject * input: apply phosh code formatting styles. This is a cosmetic change that applies code formatting styles from Phosh's style guidelines to input.c/h. * Use PhocInput objects instead of roots_input -- Guido Günther Tue, 27 Oct 2020 09:01:06 +0100 phoc (0.4.3) amber-phone; urgency=medium [ Guido Günther ] * Drop xdg_shell_V6 support * xdg-shell: Drop mention of v6 protocol * desktop: Adjust to wlr_xcursor_manager_load 0.11.0 API change * server: Unblock SIGUSR1 when spawning child * desktop: Reindent handle_layout_change * desktop: Refresh all outputs on layout changes [ Arnaud Ferraris ] * src: add utility function to fix rotation on wlroots v0.11+ [ Sebastian Krzyszkowiak ] * xwayland: Don't apply size constraints to maximized windows * xwayland: Correctly handle windows that are maximized on map * xwayland: Automaximization support * seat: Check whether moving and resizing is allowed * xwayland: Don't allow to move non-regular windows * xwayland: Don't automaximize non-resizable windows * layer-shell: Recenter non-maximized views when rearranging * Update wlroots submodule [ Dorota Czaplejewicz ] * input method: Forward only supported state -- Sebastian Krzyszkowiak Thu, 08 Oct 2020 01:40:58 +0200 phoc (0.4.2) amber-phone; urgency=medium [ Evangelos Ribeiro Tzaras ] * d/control: Use Rules-Requires-Root: no * Implement keyforwarding with a new interface in phosh-private protocol [ Guido Günther ] * phosh: Damage output after rotation [ Sebastian Krzyszkowiak ] * render: Allow view_render_to_buffer to indicate failure * phosh: Handle view_render_to_buffer failing in thumbnail_frame_handle_copy * render: Check whether the root surface isn't NULL in view_render_to_buffer -- Sebastian Krzyszkowiak Thu, 06 Aug 2020 14:42:40 +0200 phoc (0.4.1) amber-phone; urgency=high [ Sebastian Krzyszkowiak ] * render: Rework damage tracking debug mode * render: Use G_(UN)LIKELY for debug flags * render: Use defines for color values * cursor: Put a touched view into focus * debian: Add sign-tags=true to gbp.conf * gitlab-ci: Un-silence grep invocations * gitlab-ci: Set package jobs dependencies to empty * render: Don't try to render a surface with no texture * cursor: Avoid damaging the whole output in touch points debug mode * render: Catch up with wlroots' wlr_client_buffer * render: Make the EGL context current when rendering to texture [ &t ] * Add --version flag [ Yann Büchau ] * scale-to-fit: Default to "on" without extra argument -- Sebastian Krzyszkowiak Wed, 22 Jul 2020 15:47:52 +0200 phoc (0.4.0) amber-phone; urgency=high [ Guido Günther ] * server: Always print socket on startup * phosh: Reindent * phosh: Remove leftovers from the xdg interface [ Sebastian Krzyszkowiak ] * Scale down oversized views * Make auto-scaling configurable per application * phoc.ini.example: Drop outdated entries * phosh: Make sure thumbnail dimensions aren't set to zeros * phosh: Remove signal listener from view when destroying screencopy frame * Send wl_surface_{enter,leave} events to subsurfaces -- Sebastian Krzyszkowiak Tue, 30 Jun 2020 16:52:02 +0200 phoc (0.1.9) amber-phone; urgency=medium [ Arnaud Ferraris ] * gtk-shell: fix typos [ Sebastian Krzyszkowiak ] * protocols: Add wlr-foreign-toplevel-management-unstable-v1 * Create render.h header to hold declarations from render.c * view: Make view_get_geometry accessible outside of view.c * view: Store a pointer to view in wlr_foreign_toplevel_handle data * meson: Depend on GLESv2 * protocols: Update wlr-screencopy-unstable-v1 to version 2 * Update wlroots submodule * Implement window thumbnails via phosh-private+wlr-foreign-toplevel-management+wlr-screencopy protocol hybrid * tests: Introduce PhocTestScreencopyFrame and generalize screencopy logic * tests: Fix inverted condition in phoc_test_buffer_equal * tests: phoc_test_client_capture_output: Handle ARGB/XRGB format mismatch * tests: Basic window thumbnail test * protocols: Add a description for thumbnail interface in phosh-private * CI: Move coverage gathering to separate step -- Sebastian Krzyszkowiak Tue, 23 Jun 2020 13:56:47 +0200 phoc (0.1.8) amber-phone; urgency=medium [ Guido Günther ] * d/control: Bump wlroots dependency. Bumped to 0.10.0 since this is sufficient to build and run although 0.10.1 is preferred. * build: Bump project version. This makes it match the version in the changelog * PhocServer: Move startup command from config. No need to tuck away the command we run in config, it's only configurable via the command line. * main: Use automtic cleanup for mainloop * main: Use automatic cleanup for the server * server: Get hold of the main loop. This allows the server to exit cleanly * server: Exit when session exits. This allows e.g. a systemd unit to properly restart compositor and shell in case of session crash. It also allows to support logout under display managers like GDM. Prerequisite for phosh#117 since we also need to updte gnome-session for this. * Remove output mapping for devices and cursors. For cursors we don't have a real use and for devices we're going to make this default properly. * output: Add helper to detect built-in panels. This is similar to what phosh does. * roots_seat_configure_cursor: Pass roots_output. This avoids going back and forth between roots_ and wlr_output; * seat: Map touch screens to build in displays by default. This is currently hard coded and can not be changed * seat: Map tablet devices as well * seat: Fix wrong struct type. Touch is not a pointer but 'struct roots_touch' * server: Fix odd indentation * server: Introduce debug flags. This keeps the number of arguments and variables under control. * Parse debug flags from environment. This makes things more consistent with other glib applications and allows us to use expressive comma separated lists instead of command line flags. * Use PHOC_SERVER_DEBUG_DAMAGE_TRACKING. This avoids passing boolean flags around * Use PHOC_SERVER_DEBUG_TOUCH_POINTS. This avoids passing boolean flags around * server: Introduce no-quit debug flag. This allow to not quit the session which is useful for e.g. phosh development where one wants to replace the running shell without the compositor caring. * README: Document PHOC_DEBUG * gitlab-ci: Enable coverage information * README: add coverage information. This makes current coverage very visible * README: fix cut'n'paste error * d/copyright: Use correct licenses. As per de56ea6b1e3cfa41981fd4dd349b0eef852aee23 we default to GPL-3+ (same as phosh). That's also consistent with the COPYING file. We keep `src/*` separate to respect the copyright notices brought over from rootston and keep the protocols under their initial licenses. * server: Fix method name. We use it as finalize() but called it dispose(). * Don't destroy xwayland in phoc_server. We create it in phoc_desktop so it should be destroyed there as well. This also fixes a crash on server shutdown when the desktop wasn't even created. * server: Track multiple setup. We currently track whether the singleton was inited which is broken since we have a weak ref. Rather make sure we don't setup the instance multiple times. * server: Fix error output. We only create the backend, it's started later on in _setup(). * tests: Add simple test that runs a main loop * server: Remove wayland source on shutdown * server: Unset envvars on shutdown. It's always better to clean up but this also allows to run two tests using the wayland backend in the same process. * tests: Properly finalize the server. So far we used the same server instance in all the tests which fails when we allow to run init just once. * tests: Pass a phoc.ini. This one disables Xwayland since that causes trouble when restarting it multiple times and we currently don't need it. * server: Use self conistently. We call the first method argument self elsewhere so this consistently in all methods. * server: Move wl_display_destroy_clients() to dispose. This allows clients to shut down properly, we'll call into already freed objects like input. Note that wl_display_destroy() was never run since the wl_display was already NULL when and we were using g_clear_pointer(). * server: Call wlr_backend_destroy() in dispose. This allows wlroots to clean up and we can call wl_display_destroy () in finalize. * input: Guard against NULL input. This one pops up often when wlroots or the server cleans up and this makes it obvious at a glance what's wrong. We can't use PHOC_IS_INPUT yet since it's not a gobject yet. * README: document how to run the tests. This allows us to mention xvfb-run which is needed for screenshot comparison tests. * protocols: Generate client protocols. These are needed by the tests * protocols: Add wlr-screencopy-manager * tests: Add testlib. This allows to run a wayland client against the compositor and to take and compare screenshot of the output. (Closes: #40) * tests: Add simple client test. This validates that clients can connect and find their globals and also demos the library usage. * tests: Add layer shell test. Initial tests to test layer-shell layout (since we had several surprises there). This also demos the screenhost machinery. For tests to succeed they need to run under e.g. xfvb since the mouse cursor might get placed differently otherwise. * gitlab-ci: Don't reset xvfb between test runs. This should avoid connection failures with multiple tests. We do the same with libhandy. * seat: Be consistent with whitespace. Let's not mix tab and spaces in the same function. * seat: Don't notify activity on `set_cursor` Rather notify on tool set_cursor activity where it is important. This avoids setting activity events on `set_cursor` when nothing really changed. To see what thi fixes blank the screen using `lock sreen` in phosh with https://source.puri.sm/Librem5/phosh/-/merge_requests/300 applied and see it wake up right away. * desktop: Add getter/setter for maximization * tests: testlib: Add server prepare hook. This allows to tweak the compositor configuration before launching the client * tests: Wire up xdg_wm_base. This will be used in followup commits for xdg-shell testing * tests: Add initial xdg-shell tests * output: Enable new outputs by default. This was done by wlroots but isn't in 'recent' versions (which gives the compositor more room). (Closes: #130) * phosh: Drop xdg-switcher interface. Instead of requiring a phosh/phoc lockstep upgrade post an error if a client wants to bind the protcol. This keeps the code we need carry minimal but we can still be backward compatible (this is not breaking clients that don't use that part of the protocol). * input: Make_device_type() public public. While at that rename to phoc_get_device_type () * Rename virtual_keyboard.[ch] to virtual_[ch] We handle virtual pointer there as well. * virtual: Use g_return_if_fail () instead of wlr_log () We can turn this into a type check later on and it makes sure log domains work correctly. * Implement virtual pointer protocol [ Arnaud Ferraris ] * d/copyright: fix upstream files list and improve readability [ Nícolas F. R. A. Prado ] * Use default switch cases where they make sense. This removes -Wswitch-default warnings. * Use default switch cases to report invalid values. This removes -Wswitch-default warnings. * Add -Wswitch-default to compile flags [ Jordi Masip ] * build: add missing dependency 'libdrm' used for 'drmModeModeInfo' [ Sebastian Krzyszkowiak ] * cursor: Fix touch point surface attachment. Touch points should remain attached to surface they originate from regardless of the touch point position on screen. -- Guido Günther Wed, 10 Jun 2020 17:14:04 +0200 phoc (0.1.7) amber-phone; urgency=medium [ &t ] * keybindings: Declare roots_seat as incomplete type * cursor: Don't shadow sx and sy * build: Add -Wshadow to cflags [ Sebastian Krzyszkowiak ] * Guard wlr-output-power-management usage with header existence check * gbp.conf: Set multimaint-merge as default when generating changelogs -- Sebastian Krzyszkowiak Thu, 26 Mar 2020 17:04:53 +0100 phoc (0.1.6) amber-phone; urgency=medium [ Guido Günther ] * Add arm64 build * Move server setup into PhocServer * build: Build phoc_lib * Add initial test * gitlab-ci: Run unit tests * debian: Run tests via xvfb * settings: Don't init logging twice * server: Move command line parsing to main() * tests: Test option passing * Adjust to wlroots 0.8.1 layer shell changes * debian: Add breaks on older phosh * build: Drop custom tags generation * gitlab-ci: Run lintian and autopkgtests * Add superficial autopkgtest * Add wlr-output-power-management protocol * Update wlroots submodule * Drop roots_output_from_wlr_output * Support wlr-output-power-management [ Sebastian Krzyszkowiak ] * view: Simplify roots_view_get_from_wlr_surface * view: Rename roots_view_get_from_wlr_surface to roots_view_from_wlr_surface * main: Fix uninitialized debug_damage value * Touch point visualization * Update to wlr_output's atomic API and wlroots 0.9.x branch * layer-shell: Fix incorrect variable passed to debug log * layer-shell: Handle layer changes * Update git submodule to 8fd62aaa18bd4f31933e99ef5d5fd15aff5b6c5e (wlroots 0.10.0) * CI: Update to deal with newer wlroots * debian: Update wlroots dependency version * Update wlroots submodule [ Dorota Czaplejewicz ] * doap: Fix malformed Person tag [ Simon Ser ] * Update to new presentation-time API -- Sebastian Krzyszkowiak Tue, 24 Mar 2020 15:59:24 +0100 phoc (0.1.5) amber; urgency=medium [ Guido Günther ] * input: Use g_* for logging. This makes log domains work for input as well. * seat: Don't initialize tablets if we don't have libinput. This leads to crashes otherwise e.g. on the wayland backend * phosh: Fix indentation * view: Introduce helper to find a roots_surface from a given wlr_surface. * protocols: Add gtk-shell v3. Taken from gtk 3.24.8. This will allow to implement raising surfaces on their request and startup notifications if we want these before there's a mainlineable protocol. * Implement gtk-shell's handle_request_focus. This allows to raise windows on their request. See https://source.puri.sm/Librem5/phoc/issues/93 * desktop: Wire up gtk-shell [ Sebastian Krzyszkowiak ] * view: Hide shell overlay when activating a fullscreen window. Without that, the overlay stays needlessly visible after selecting a fullscreen window in the activity switcher until further input event. -- Guido Günther Thu, 02 Jan 2020 23:52:47 +0100 phoc (0.1.4) amber; urgency=medium [ Guido Günther ] * build: drop rootston=false from wlroots submodule build rootston is no longer part of wlroots so the option causes a warning during build. * view: Add helper to move windows between outputs * keybindings: allow to move windows to left/right output * Update submodule. This pulls in the gbm fix. * Add doap file. This helps to identify project maintainers and contact points. * d/control: Add Sebastian to uploaders * xdg_shell_v6: Implement get_geometry. As long as these are around there's no reason why v6 should be worse than stable * Introduce view_is_maximized. This will allow us to introduce a tiled state as well. * view: Simple left right tilig. This uses the 'maximized' state to get rid of GTK's drop shadows until we have fixed https://gitlab.gnome.org/GNOME/gtk/issues/2171 * view: Differentiate between tiled and normal on unmaximize * keybindings: bind left and right tiling * d/control: Depend on gsettings schemas. Not having them makes us not start [ Sebastian Krzyszkowiak ] * Update wlroots submodule. Forward to 9fb251c33fc404f106f0b4e3b6839de9d4753a4c for virtual keyboard keycode 0 fix. [ Simon Ser ] * render: set surface as sampled for presentation. This is necessary after [1]. [1]: https://github.com/swaywm/wlroots/pull/1798 * Fix presentation feedback when scanning out fullscreen views. References: https://github.com/swaywm/sway/pull/4667 -- Guido Günther Tue, 17 Dec 2019 17:54:07 +0100 phoc (0.1.3) amber; urgency=medium [ Guido Günther ] * wlroots: Update submodule * desktop: Allow to blank/unblank all outputs * keyboard: Blank/unblank all outputs on power button press (Closes: #86) -- Sebastian Krzyszkowiak Sat, 23 Nov 2019 00:27:10 +0100 phoc (0.1.2) amber-phone; urgency=medium [ Sebastian Krzyszkowiak ] * layer-shell: Don't apply exclusive zones of unmapped surfaces * Revert "Use old gamma control protocol if available" * Update wlroots submodule * Add a way to reveal shell on a fullscreen surface * layer-shell: Create/destroy layer_subsurface objects on parent map/unmap [ Guido Günther ] * gitlab-ci: Remove redundancy in alpine deps * Add libgnome-desktop-3-dev * Turn keyboard into a gobject * keyboard: Read keyboard config from gsettings * settings: Remove superfluous include * keyboard: fix indentation * Use g_clear_object * keyboard: Set wlr_keyboard's data to phoc_keyboard * keyboard: Add method to witch to next layout * keybindinds: Listen to switch-input-source changes [ Simon Ser ] * Remove orbital screenshooter and gamma-control * rootston: add support for direct scan-out * Remove all wayland-server.h includes -- Sebastian Krzyszkowiak Mon, 18 Nov 2019 22:48:51 +0100 phoc (0.1.1) amber-phone; urgency=medium [ Dorota Czaplejewicz ] * text_input: Add asserts guarding focus state * Clear pending surface before setting it to a new value * text_input: Eliminate excessive messages * layer shell: Adjust position of osk depending on presence of the prompter [ Guido Günther ] * gitlab-ci: Disable the deb build for the moment * build: Delay config file creation * build: print detected wlroots version * desktop: Include config.h early * Use old gamma control protocol if available * debian: Allow to build against 0.7.0 * switch: Drop switch handling * keyboard: Reformat keyboard_execute_binding * Add PhocKeybindings * Use PhocKeybindings * Drop old bindings related code * gitlab-ci: De-duplicate build logic * gitlab-ci: Specify the docker image to use * gitlab-ci: Build against Debian Bullseye as well * keybindings: bind toggle-maximized too * view_arrange_maximized: Drop unused variable * keybindings: Pass settings to add_keybindings * view: Drop view_cycle_alpha * gitlab-ci: Allow alpine linux build to fail but not break ci * Include config.h first in all .c files * render: Use PHOC_XWAYLAND instead of WLR_HAS_XWAYLAND * outpout: Use PHOC_XWAYLAND instead of WLR_HAS_XWAYLAND * Update wlroots submodule * server: Drop redundant 'extern struct' * server: Turn into a GObject * Use the PhocServer singleton * server: Move server init/deinit into constructor/dispose * keybindings: Fix missing chain-up in constructed() * debian: Add gbp.conf [ Sebastian Krzyszkowiak ] * layer_surface_at: Fix the order of surface processing to match rendering * seat: Call roots_input_method_relay_set_focus for layer-surfaces * layer-shell: Elevate layer of OSK when same or higher layer is focused * meson: Don't add wlroots as subproject when embed-wlroots is disabled * xdg-shells: Block interactive resize when in auto-maximizing mode * layer-shell: Update focus order in arrange_layers * phosh: Fix rejection of multiple clients binding to phosh private protocol * seat: Handle unfocusing layer-surface with no other focusable view * layer-shell: Handle subsurfaces and nested popups * desktop_surface_at: Set a "view" argument in case of fullscreen surface * Clean up desktop_surface_at calls [ Simon Ser ] * Remove orbital screenshooter * layer-shell: Don't give focus to unmapped layer surfaces [ Aleksis ] * layer-shell: Remove unused "configured" variable [ Bart Ribbers ] * gitlab-ci: Build and unit test for Alpine Linux edge as well -- Sebastian Krzyszkowiak Mon, 14 Oct 2019 23:08:40 +0200 phoc (0.1.0) purple; urgency=medium [ Guido Günther ] * Avoid conflict with close(2) * Add HACKING.md * Drop wlroots CONTRIBUTING to avoid confusion * Use glib main loop * Rename roots_server to phoc_server * main: Block SIGUSR1 early * Maximize xdg_shell surfaces by default * desktop: Allow to toggle maximization via dconf * helpers: Add helper to toggle maximization * Add run script * Add editorconfig from libhandy * desktop: Allow to toggle maximization via dconf * Revert "Avoid conflict with close(2)" * Don't access views directly * Avoid conflict with close(2) * Switch to wlroots 0.5.0 * outpout: Check for PHOC_XwAYLAND instead of WLR_HAS_XWAYLAND * xwayland: Don't include config.h twice * rootston: Also iterate layer shell popups * render: Don't use rootston's types and headers * Add phosh private protocol (Closes: #2) * view: Move want_maximize upwards * view: Introduce maybe_maximize() * Update wlroots to 930e37eae97e2ae965f7ae3a05d2fdd700827688 * layer-shell: Don't dereference gone output * Update wlroots to 0.6.0 (Closes: #14) * run: Avoid word splitting * main: Fix indentation * main: Launch client in idle main loop * xdg_shell: Don't move windows with auto_maximize == true (Closes: #8) * main: Block SIGUSR1 instead of ignoring it (Closes: #20) * Use glib logging (Closes: #21) * gitlab-ci: Build a deb * debian: Add compositor parts (Closes: #16) * build: Allow to toggle using the embedded wlroots * debian: Use a build profile for linking against embedded wlroots (Closes: #17) * gitlab-ci: Use build profile * gitlab-ci: Build against packaged wlroots as well (Closes: #25) * debian: Add missing build-dep for x11 backend with embedded wlroots * Turn desktop in a PhocDesktop GObject * PhocDesktop: Use constructed and finalize * PhocDesktop: Use g_signal_connect_swapped * desktop: Fix type check * Only set a cursor when we have a pointer device (Closes: #31) * Update submodule * view: Make maximaziation logic match the comment * Add logging domains * Log wlroots messages as 'phoc-wlroots' * view: Take geometry into account when centering views * view: Use sane geometry when impl is missing * view: Default geometry x,y to 0 * render: Clear to black background * Add some debugging hints * view: Take usable area into account when centering views (Closes: #45) * roots_seat: Add roots_set_get_cursor () * phosh-private: Add a close request * phosh: Implement phosh-private's close request * phosh: Use g_* logging * Update wlroots submodule * protocols: Remove unused protocols * protocols: Drop client protocol generation * protocols: Don't generate unused server protocols * text_input: Don't forget to send enter events (Closes: #51, #28, #12) * cursor: Drop btn left emulation for touch * Update submodule * switch: Drop switch handling * keyboard: Reformat keyboard_execute_binding * Add PhocKeybindings * Use PhocKeybindings * Drop old bindings related code * Revert "Drop old bindings related code" * Revert "Use PhocKeybindings" * Revert "Add PhocKeybindings" * Revert "keyboard: Reformat keyboard_execute_binding" * Revert "switch: Drop switch handling" [ emersion ] * data-device: refactor wlr_drag * rootston: cancel drag on invalid serial * rootston: move part of desktop.c to view.c, use an interface for views * rootston: add a view child interface * rootston: make roots_view embedded and remove unions * rootston: refactor rendering * rootston: fix rotated views rendering * rootston: fix Xwayland children rendering when fullscreen * rootston: split rendering code into render.c * rootston: fix input events for rotated views * xwayland: don't set DISPLAY * rootston: add output-management-v1 support * rootston: update output-management-v1 state when output is modeset * output-management-v1: support applying configuration * output-management-v1: update protocol, add set_custom_mode * rootston: disable then enable outputs when applying output-management state * output: remove lx, ly [ Sebastian Krzyszkowiak ] * rootston: surface_at: check for fullscreen surfaces in between TOP and OVERLAY layers * Auto-maximize unfullscreened surfaces * phosh: Fix incorrect type being passed to sizeof in calloc * seat: Move view damage in set_focus to where the drawing list is handled * Handle stacks (xdg_toplevel::set_parent relationships) (#4) * seat: Move focus back to first shell surface when unfocusing layer surface * xdg_shell(_v6): Take maximize/fullscreen state into account on view init * seat: Don't try to raise unmapped surfaces in stacks * Update wlroots submodule * view: Move want_maximize above view_maximize * view: Don't allow to unmaximize auto-maximized surfaces * view: Null-check output in view_arrange_maximized * view: Rename "maximized" argument in view_maximize * view: Move want_maximize logic into protocol code * Auto-maximize before mapping the surface * Update wlroots submodule * layer_shell: Guard against negative exclusive zone * view: Create foreign-toplevel-handle before focusing * Move wlr-foreign-toplevel-management support into view * Refine the ordering of layer-shell surfaces * Update cursor focus on popup unmap * Update wlroots submodule [ Ryan Walklin ] * s/lid_switch/switch_device [ Ilia Bozhinov ] * rootston: remove disabled outputs from the output layout * rootston: add support for foreign-toplevel fullscreening [ Alyssa Ross ] * Fix missing headers when building without X11 [ Simon Ser ] * rootston: use wlr_output_preferred_mode * output-damage: refactor API * output: rename needs_commit to needs_frame * rootston: don't submit too much damage * rootston: fix damage tracking debug mode [ Drew DeVault ] * Remove wlr_wl_shell [ Dorota Czaplejewicz ] * build: Link supplied wlroots statically * debian: Use build profile for shared wlroots build -- Sebastian Krzyszkowiak Mon, 16 Sep 2019 19:17:34 +0200 phoc (0.0.0) purple; urgency=medium * Initial release -- Guido Günther Mon, 25 Feb 2019 19:31:00 +0100 phoc-v0.13.1/debian/compat000066400000000000000000000000031422111650000153240ustar00rootroot0000000000000012 phoc-v0.13.1/debian/control000066400000000000000000000037611422111650000155370ustar00rootroot00000000000000Source: phoc Priority: optional Maintainer: Guido Günther Uploaders: Sebastian Krzyszkowiak Build-Depends: debhelper (>= 12), gsettings-desktop-schemas, libglib2.0-dev, libgnome-desktop-3-dev, libinput-dev, libpixman-1-dev, libwayland-dev, libxkbcommon-dev, meson (>= 0.54.0), pkg-config, wayland-protocols, libgirepository1.0-dev , libxcb1-dev, libwlroots-dev (>= 0.14.0) , libwlroots-dev (<< 0.15.0) , python3-jinja2 , python3-pygments , python3-toml , python3-typogrify , # For wlroots subproject build libavformat-dev , libavcodec-dev , libcap-dev , libdrm-dev (>= 2.4.95) , libegl1-mesa-dev , libgbm-dev (>= 17.1.0) , libgles2-mesa-dev , libpng-dev , libseat-dev , libsystemd-dev , libxcb-composite0-dev , libxcb-icccm4-dev , libxcb-image0-dev , libxcb-render0-dev , libxcb-res0-dev , libxcb-xfixes0-dev , libxcb-xinput-dev , libx11-xcb-dev , xwayland , # to run the tests mutter-common , xvfb , xauth , Standards-Version: 4.6.0 Section: libs Homepage: https://source.puri.sm/librem5/phoc Rules-Requires-Root: no Package: phoc Architecture: any Depends: ${misc:Depends}, ${shlibs:Depends}, gsettings-desktop-schemas, mutter-common, Recommends: phosh Breaks: phosh (<< 0.4.5), Description: Wayland compositor for mobile phones Tiny wayland compositor based on wlroots for use on mobile phones like the Librem 5. . You likely want to use Phosh (the phone shell) with it. phoc-v0.13.1/debian/copyright000066400000000000000000000064551422111650000160720ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: phoc Source: https://source.puri.sm/Librem5/phoc Files: * Copyright: 2019, 2020 Purism SPC License: GPL-3+ Files: src/cursor.* src/desktop.* src/input.* src/keyboard.* src/layers.h src/layer_shell.c src/main.* src/output.* src/render.* src/seat.* src/settings.* src/switch.* src/text_input.* src/view.* src/xcursor.h src/xdg_shell.c src/xwayland.c Copyright: 2017, Drew DeVault 2014, Jari Vetoniemi 2019, Purism SPC License: GPL-3+ or MIT Files: debian/* Copyright: 2018, Guido Günther License: GPL-2+ Files: protocols/* Copyright: 2017, Drew DeVault 2018, Simon Ser 2019, Andri Yngvason 2019, Purism SPC License: MIT License: MIT 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 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. License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". License: GPL-3+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU General Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". phoc-v0.13.1/debian/gbp.conf000066400000000000000000000002301422111650000155370ustar00rootroot00000000000000[DEFAULT] debian-branch = master debian-tag = v%(version)s debian-tag-msg = %(pkg)s v%(version)s [dch] multimaint-merge = True [tag] sign-tags = true phoc-v0.13.1/debian/install000066400000000000000000000000471422111650000155170ustar00rootroot00000000000000usr/bin/* usr/share/glib-2.0/schemas/* phoc-v0.13.1/debian/not-installed000066400000000000000000000001721422111650000166250ustar00rootroot00000000000000usr/include/wlr/* usr/include/wlr/backend/* usr/include/wlr/types/* usr/lib/*/libwlroots.* usr/lib/*/pkgconfig/wlroots.pc phoc-v0.13.1/debian/rules000077500000000000000000000012011422111650000151770ustar00rootroot00000000000000#!/usr/bin/make -f export DEB_BUILD_MAINT_OPTIONS = hardening=+all ifneq ($(filter pkg.phoc.embedwlroots,$(DEB_BUILD_PROFILES)),) MESON_OPTS += -Dembed-wlroots=enabled --default-library=static else MESON_OPTS += -Dembed-wlroots=disabled endif ifneq ($(filter pkg.phoc.sanitizers,$(DEB_BUILD_PROFILES)),) MESON_OPTS += -Db_sanitize=address,undefined endif %: dh $@ override_dh_auto_configure: dh_auto_configure -- $(MESON_OPTS) override_dh_auto_install: dh_auto_install --destdir=debian/tmp override_dh_auto_test: ifeq ($(filter nocheck,$(DEB_BUILD_OPTIONS)),) LC_ALL=C.UTF-8 xvfb-run ninja -C _build test endif phoc-v0.13.1/debian/source/000077500000000000000000000000001422111650000154255ustar00rootroot00000000000000phoc-v0.13.1/debian/source/format000066400000000000000000000000151422111650000166340ustar00rootroot000000000000003.0 (native) phoc-v0.13.1/debian/tests/000077500000000000000000000000001422111650000152675ustar00rootroot00000000000000phoc-v0.13.1/debian/tests/control000066400000000000000000000001141422111650000166660ustar00rootroot00000000000000Test-Command: /usr/bin/phoc --help Restrictions: superficial Depends: phoc phoc-v0.13.1/doc/000077500000000000000000000000001422111650000134505ustar00rootroot00000000000000phoc-v0.13.1/doc/meson.build000066400000000000000000000016101422111650000156100ustar00rootroot00000000000000if get_option('gtk_doc') toml_data = configuration_data() toml_data.set('VERSION', meson.project_version()) phoc_toml = configure_file( input: 'phoc.toml.in', output: 'phoc.toml', configuration: toml_data ) dependency('gi-docgen', version: '>= 2021.1', fallback: ['gi-docgen', 'dummy_dep'], native: true, required: get_option('gtk_doc')) gidocgen = find_program('gi-docgen') docs_dir = datadir / 'doc' custom_target('phoc-doc', input: [ phoc_toml, phoc_gir[0] ], output: 'phoc-0', command: [ gidocgen, 'generate', '--quiet', '--add-include-path=@0@'.format(meson.current_build_dir() / '../../src'), '--config=@INPUT0@', '--output-dir=@OUTPUT@', '--no-namespace-dir', '--content-dir=@0@'.format(meson.current_source_dir()), '@INPUT1@', ], build_by_default: true, install: true, install_dir: docs_dir, ) endif phoc-v0.13.1/doc/phoc.toml.in000066400000000000000000000014641422111650000157100ustar00rootroot00000000000000[library] version = "@VERSION@" description = "Phone Compositor" authors = "Purism SPC" license = "GPL-3-or-later" browse_url = "https://gitlab.gnome.org/World/Phosh/phoc/" repository_url = "https://gitlab.gnome.org/World/Phosh/phoc.git" website_url = "https://world.pages.gitlab.gnome.org/Phosh/phoc" dependencies = [ "GObject-2.0", ] devhelp = true search_index = true [dependencies."GObject-2.0"] name = "GObject" description = "The base type system library" docs_url = "https://developer.gnome.org/gobject/stable" [theme] name = "basic" show_index_summary = true show_class_hierarchy = true [source-location] # The base URL for the web UI base_url = "https://gitlab.gnome.org/World/Phosh/phoc/-/tree/master" # The format for links, using "filename" and "line" for the format file_format = "{filename}#L{line}" phoc-v0.13.1/helpers/000077500000000000000000000000001422111650000143455ustar00rootroot00000000000000phoc-v0.13.1/helpers/auto-maximize000077500000000000000000000004131422111650000170620ustar00rootroot00000000000000#!/bin/bash set -e export GSETTINGS_SCHEMA_DIR=_build/data/ case "$1" in on|1|ON) val=true ;; *) val=false ;; esac gsettings set sm.puri.phoc auto-maximize "${val}" echo -n "Auto-maximize is now " G_MESSAGES_DEBUG= gsettings get sm.puri.phoc auto-maximize phoc-v0.13.1/helpers/scale-to-fit000077500000000000000000000013551422111650000165660ustar00rootroot00000000000000#!/bin/bash set -e export GSETTINGS_SCHEMA_DIR=_build/data/ function print_current() { local app_id="$1" local munged_id="$2" echo -n "Scale-to-fit for $app_id: " G_MESSAGES_DEBUG='' gsettings get sm.puri.phoc.application:/sm/puri/phoc/application/"$munged_id"/ scale-to-fit } if [ -z "$1" ]; then echo "Usage: $0 APP-ID [VALUE]" exit 0 fi APP_ID="$1" MUNGED_ID=$(echo "$1" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]') if [ -z "$2" ]; then print_current "$APP_ID" "$MUNGED_ID" exit 0 fi case "$2" in ""|on|1|ON|true) val=true ;; *) val=false ;; esac G_MESSAGES_DEBUG='' gsettings set sm.puri.phoc.application:/sm/puri/phoc/application/"$MUNGED_ID"/ scale-to-fit "$val" print_current "$APP_ID" "$MUNGED_ID" phoc-v0.13.1/meson.build000066400000000000000000000146561422111650000150610ustar00rootroot00000000000000project('phoc', 'c', version: '0.13.1', license: 'GPLv3+', meson_version: '>= 0.54.0', default_options: [ 'warning_level=1', 'buildtype=debugoptimized', 'c_std=gnu11', ], ) add_project_arguments([ '-DWLR_USE_UNSTABLE', '-I' + meson.build_root(), ], language: 'c') root_inc = include_directories('.') src_inc = include_directories('src') protocol_inc = include_directories('protocols') prefix = get_option('prefix') datadir = join_paths(prefix, get_option('datadir')) cc = meson.get_compiler('c') gio = dependency('gio-2.0', version: '>=2.64.0') glesv2 = dependency('glesv2') glib = dependency('glib-2.0', version: '>=2.64.0') gobject = dependency('gobject-2.0', version: '>=2.64.0') gnome_desktop = dependency('gnome-desktop-3.0', version: '>=3.26') gsettings_desktop_schemas_dep = dependency('gsettings-desktop-schemas') input = dependency('libinput') drm = dependency('libdrm') pixman = dependency('pixman-1') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols', version: '>=1.15') wayland_server = dependency('wayland-server') xkbcommon = dependency('xkbcommon') math = cc.find_library('m') embed_wlroots = get_option('embed-wlroots') if not embed_wlroots.disabled() # Try first to find wlroots as a subproject, then as a system dependency wlroots_proj = subproject( 'wlroots', default_options: ['examples=false'], required: false, ) if embed_wlroots.enabled() and not wlroots_proj.found() error('Wlroots subproject not found but enabled') endif if get_option('gtk_doc') != false error('Can\'t generate docs and use embedded wlroots build') endif endif if not embed_wlroots.disabled() and wlroots_proj.found() wlroots = wlroots_proj.get_variable('wlroots') wlroots_conf = wlroots_proj.get_variable('conf_data') wlroots_has_xwayland = wlroots_conf.get('WLR_HAS_XWAYLAND') == 1 wlroots_has_output_power_management = true have_wlr_set_startup_id = true have_wlr_remove_startup_info = true have_wlr_xdg_activation_v1_add_token = true else wlroots = dependency('wlroots', version: '>= 0.14.1') wlroots_has_xwayland = cc.get_define('WLR_HAS_XWAYLAND', prefix: '#include ', dependencies: wlroots) == '1' wlroots_has_output_power_management = cc.has_header('wlr/types/wlr_output_power_management_v1.h', dependencies: wlroots) have_wlr_set_startup_id = false if wlroots_has_xwayland and cc.has_member('struct wlr_xwayland_surface', 'events.set_startup_id', prefix: '''#include "wlr/xwayland.h"''', args: '-DWLR_USE_UNSTABLE', dependencies: wlroots) have_wlr_set_startup_id = true endif have_wlr_remove_startup_info = false if wlroots_has_xwayland and cc.has_member('struct wlr_xwayland', 'events.remove_startup_info', prefix: '''#include "wlr/xwayland.h"''', args: '-DWLR_USE_UNSTABLE', dependencies: wlroots) have_wlr_remove_startup_info = true endif have_wlr_xdg_activation_v1_add_token = false if cc.has_function('wlr_xdg_activation_v1_add_token', args: '-DWLR_USE_UNSTABLE', dependencies: wlroots) have_wlr_xdg_activation_v1_add_token = true endif endif if get_option('xwayland').enabled() and not wlroots_has_xwayland error('Cannot enable Xwayland in phoc: wlroots has been built without Xwayland support') endif have_xwayland = get_option('xwayland').enabled() global_c_args = [] test_c_args = [ '-Wcast-align', '-Wdate-time', ['-Werror=format-security', '-Werror=format=2'], '-Wendif-labels', '-Werror=incompatible-pointer-types', '-Werror=missing-declarations', '-Werror=overflow', '-Werror=return-type', '-Werror=shift-count-overflow', '-Werror=shift-overflow=2', '-Werror=implicit-fallthrough=3', '-Wformat-nonliteral', '-Wformat-security', '-Winit-self', '-Wmaybe-uninitialized', '-Wmissing-field-initializers', '-Wmissing-include-dirs', '-Wmissing-noreturn', '-Wnested-externs', '-Wno-missing-field-initializers', '-Wno-sign-compare', '-Wno-strict-aliasing', '-Wno-unused-parameter', '-Wold-style-definition', '-Wpointer-arith', '-Wredundant-decls', '-Wshadow', '-Wstrict-prototypes', '-Wtype-limits', '-Wundef', '-Wunused-function', '-Wswitch-default', ] # TODO: # '-Wfloat-equal', # '-Wswitch-enum', if get_option('buildtype') != 'plain' test_c_args += '-fstack-protector-strong' endif add_project_arguments( cc.get_supported_arguments(test_c_args), language: 'c' ) gnome = import('gnome') meson.add_install_script( join_paths('build-aux', 'post_install.py'), join_paths(get_option('prefix'), get_option('datadir')) ) subdir('protocols') subdir('src') subdir('tests') subdir('data') subdir('doc') summary = [ '', '--------------------------', 'phoc @0@'.format(meson.project_version()), '', ' xwayland: @0@'.format(have_xwayland), ' wlroots subproject: @0@'.format(not embed_wlroots.disabled() and wlroots_proj.found()), ' wlroots version: @0@'.format(wlroots.version()), ' Documentation: @0@'.format(get_option('gtk_doc')), '--------------------------', '' ] message('\n'.join(summary)) config_h = configuration_data() config_h.set_quoted('PHOC_VERSION', meson.project_version()) config_h.set('PHOC_XWAYLAND', have_xwayland) config_h.set('PHOC_HAVE_WLR_SET_STARTUP_ID', have_wlr_set_startup_id) config_h.set('PHOC_HAVE_WLR_REMOVE_STARTUP_INFO', have_wlr_remove_startup_info) config_h.set('PHOC_HAVE_WLR_XDG_ACTIVATION_V1_ADD_TOKEN', have_wlr_xdg_activation_v1_add_token) configure_file( input: 'config.h.in', output: 'config.h', configuration: config_h, ) run_data = configuration_data() run_data.set('ABS_BUILDDIR', meson.current_build_dir()) run_data.set('ABS_SRCDIR', meson.current_source_dir()) configure_file( input: 'run.in', output: 'run', configuration: run_data) phoc-v0.13.1/meson_options.txt000066400000000000000000000010301422111650000163320ustar00rootroot00000000000000option('xwayland', type : 'feature', value : 'enabled') option('embed-wlroots',type : 'feature', value : 'auto', description : 'Wheter to use wlroots as a subproject and link statically against it') option('tests', type: 'boolean', value: true, description: 'Whether to compile unit tests') option('gtk_doc', type: 'boolean', value: false, description: 'Whether to generate the API reference') option('dev-uid', type: 'integer', value: 1000, description: 'User id for phoc development') phoc-v0.13.1/phoc.doap000066400000000000000000000021241422111650000145000ustar00rootroot00000000000000 phoc phoc A wayland composior not only for mobile phones Phoc aims to be a wayland compositor targeted at mobile phones but also works for desktops and tablets. C Guido Günther Sebastian Krzyszkowiak phoc-v0.13.1/protocols/000077500000000000000000000000001422111650000147275ustar00rootroot00000000000000phoc-v0.13.1/protocols/gtk-shell.xml000066400000000000000000000052431422111650000173470ustar00rootroot00000000000000 gtk_shell is a protocol extension providing additional features for clients implementing it. phoc-v0.13.1/protocols/meson.build000066400000000000000000000027561422111650000171030ustar00rootroot00000000000000fs = import('fs') wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') wayland_scanner = find_program('wayland-scanner') server_protocols = [ [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], [wl_protocol_dir, 'unstable/tablet/tablet-unstable-v2.xml'], ['gtk-shell.xml'], ['phosh-private.xml'], ['wlr-foreign-toplevel-management-unstable-v1.xml'], ['wlr-layer-shell-unstable-v1.xml'], ['wlr-output-power-management-unstable-v1.xml'], ['wlr-screencopy-unstable-v1.xml'] ] protos_sources = [] server_protos_headers = [] client_protos_headers = [] foreach p : server_protocols xml = join_paths(p) base = fs.name(xml) proto = fs.stem(base) protos_sources += custom_target('@0@ source'.format(proto), input: xml, output: '@0@-protocol.c'.format(proto), command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'] ) server_protos_headers += custom_target('@0@ server header'.format(proto), input: xml, output: '@0@-protocol.h'.format(proto), command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'] ) client_protos_headers += custom_target('@0@ client header'.format(proto), input: xml, output: '@0@-client-protocol.h'.format(proto), command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'] ) endforeach phoc-v0.13.1/protocols/phosh-private.xml000066400000000000000000000226461422111650000202540ustar00rootroot00000000000000 Private protocol between phosh and the compositor. This request is unused, ignore. Use wlr-output-management instead. This request is unused, ignore. Use wlr-foreign-toplevel-management instead. Allows to retrieve a window thumbnail image for a given foreign toplevel via wlr_screencopy protocol. The thumbnail will be scaled down to the size provided by max_width and max_height arguments, preserving original aspect ratio. Pass 0 to leave it unconstrained. Allows to subscribe to specific keyboard events. The client grabs an accelerator by a string and gets an action id returned. When the accelerator is used the client will be informed via the corresponding action id. Allows to track application startup. This allows the shell to report it's current state. This can e.g. be used to notify the compositor that the shell is up. The interface is meant to allow subscription and forwarding of keyboard events. Forward an action to the client. A previous accelerator grab request has failed. A previous accelerator grab request has succeeded. Client subscribes to a specific accelerator. A previous accelerator ungrab request has failed. A previous accelerator ungrab request has suceeded. Client unsubscribes a specific accelerator" This interface is unused, ignore. Use wlr-foreign-toplevel-management instead. Allows shells to track application startup. This event indicates that the client sent it's startup id. (which implies the app is running). This event indicates that the launcher spawned the app. The Client should invoke this when done using the interface. phoc-v0.13.1/protocols/wlr-foreign-toplevel-management-unstable-v1.xml000066400000000000000000000264221422111650000260130ustar00rootroot00000000000000 Copyright © 2018 Ilia Bozhinov Permission to use, copy, modify, distribute, and sell this software and its documentation 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 software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. The purpose of this protocol is to enable the creation of taskbars and docks by providing them with a list of opened applications and letting them request certain actions on them, like maximizing, etc. After a client binds the zwlr_foreign_toplevel_manager_v1, each opened toplevel window will be sent via the toplevel event This event is emitted whenever a new toplevel window is created. It is emitted for all toplevels, regardless of the app that has created them. All initial details of the toplevel(title, app_id, states, etc.) will be sent immediately after this event via the corresponding events in zwlr_foreign_toplevel_handle_v1. Indicates the client no longer wishes to receive events for new toplevels. However the compositor may emit further toplevel_created events, until the finished event is emitted. The client must not send any more requests after this one. This event indicates that the compositor is done sending events to the zwlr_foreign_toplevel_manager_v1. The server will destroy the object immediately after sending this request, so it will become invalid and the client should free any resources associated with it. A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel window. Each app may have multiple opened toplevels. Each toplevel has a list of outputs it is visible on, conveyed to the client with the output_enter and output_leave events. This event is emitted whenever the title of the toplevel changes. This event is emitted whenever the app-id of the toplevel changes. This event is emitted whenever the toplevel becomes visible on the given output. A toplevel may be visible on multiple outputs. This event is emitted whenever the toplevel stops being visible on the given output. It is guaranteed that an entered-output event with the same output has been emitted before this event. Requests that the toplevel be maximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be unmaximized. If the maximized state actually changes, this will be indicated by the state event. Requests that the toplevel be minimized. If the minimized state actually changes, this will be indicated by the state event. Requests that the toplevel be unminimized. If the minimized state actually changes, this will be indicated by the state event. Request that this toplevel be activated on the given seat. There is no guarantee the toplevel will be actually activated. The different states that a toplevel can have. These have the same meaning as the states with the same names defined in xdg-toplevel This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 is created and each time the toplevel state changes, either because of a compositor action or because of a request in this protocol. This event is sent after all changes in the toplevel state have been sent. This allows changes to the zwlr_foreign_toplevel_handle_v1 properties to be seen as atomic, even if they happen via multiple events. Send a request to the toplevel to close itself. The compositor would typically use a shell-specific method to carry out this request, for example by sending the xdg_toplevel.close event. However, this gives no guarantees the toplevel will actually be destroyed. If and when this happens, the zwlr_foreign_toplevel_handle_v1.closed event will be emitted. The rectangle of the surface specified in this request corresponds to the place where the app using this protocol represents the given toplevel. It can be used by the compositor as a hint for some operations, e.g minimizing. The client is however not required to set this, in which case the compositor is free to decide some default value. If the client specifies more than one rectangle, only the last one is considered. The dimensions are given in surface-local coordinates. Setting width=height=0 removes the already-set rectangle. This event means the toplevel has been destroyed. It is guaranteed there won't be any more events for this zwlr_foreign_toplevel_handle_v1. The toplevel itself becomes inert so any requests will be ignored except the destroy request. Destroys the zwlr_foreign_toplevel_handle_v1 object. This request should be called either when the client does not want to use the toplevel anymore or after the closed event to finalize the destruction of the object. Requests that the toplevel be fullscreened on the given output. If the fullscreen state and/or the outputs the toplevel is visible on actually change, this will be indicated by the state and output_enter/leave events. The output parameter is only a hint to the compositor. Also, if output is NULL, the compositor should decide which output the toplevel will be fullscreened on, if at all. Requests that the toplevel be unfullscreened. If the fullscreen state actually changes, this will be indicated by the state event. This event is emitted whenever the parent of the toplevel changes. No event is emitted when the parent handle is destroyed by the client. phoc-v0.13.1/protocols/wlr-layer-shell-unstable-v1.xml000066400000000000000000000440361422111650000226420ustar00rootroot00000000000000 Copyright © 2017 Drew DeVault Permission to use, copy, modify, distribute, and sell this software and its documentation 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 software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and rendered with a defined z-depth respective to each other. They may also be anchored to the edges and corners of a screen and specify input handling semantics. This interface should be suitable for the implementation of many desktop shell components, and a broad number of other applications that interact with the desktop. Create a layer surface for an existing surface. This assigns the role of layer_surface, or raises a protocol error if another role is already assigned. Creating a layer surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. After creating a layer_surface object and setting it up, the client must perform an initial commit without any buffer attached. The compositor will reply with a layer_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface. You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. Clients can specify a namespace that defines the purpose of the layer surface. These values indicate which layers a surface can be rendered in. They are ordered by z depth, bottom-most first. Traditional shell surfaces will typically be rendered between the bottom and top layers. Fullscreen shell surfaces are typically rendered at the top layer. Multiple surfaces can share a single layer, and ordering within a single layer is undefined. This request indicates that the client will not use the layer_shell object any more. Objects that have been created through this instance are not affected. An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like environment. Layer surface state (layer, size, anchor, exclusive zone, margin, interactivity) is double-buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. Attaching a null buffer to a layer surface unmaps it. Unmapping a layer_surface means that the surface cannot be shown by the compositor until it is explicitly mapped again. The layer_surface returns to the state it had right after layer_shell.get_layer_surface. The client can re-map the surface by performing a commit without any buffer attached, waiting for a configure event and handling it as usual. Sets the size of the surface in surface-local coordinates. The compositor will display the surface centered with respect to its anchors. If you pass 0 for either value, the compositor will assign it and inform you of the assignment in the configure event. You must set your anchor to opposite edges in the dimensions you omit; not doing so is a protocol error. Both values are 0 by default. Size is double-buffered, see wl_surface.commit. Requests that the compositor anchor the surface to the specified edges and corners. If two orthogonal edges are specified (e.g. 'top' and 'left'), then the anchor point will be the intersection of the edges (e.g. the top left corner of the output); otherwise the anchor point will be centered on that edge, or in the center if none is specified. Anchor is double-buffered, see wl_surface.commit. Requests that the compositor avoids occluding an area with other surfaces. The compositor's use of this information is implementation-dependent - do not assume that this region will not actually be occluded. A positive value is only meaningful if the surface is anchored to one edge or an edge and both perpendicular edges. If the surface is not anchored, anchored to only two perpendicular edges (a corner), anchored to only two parallel edges or anchored to all edges, a positive value will be treated the same as zero. A positive zone is the distance from the edge in surface-local coordinates to consider exclusive. Surfaces that do not wish to have an exclusive zone may instead specify how they should interact with surfaces that do. If set to zero, the surface indicates that it would like to be moved to avoid occluding surfaces with a positive exclusive zone. If set to -1, the surface indicates that it would not like to be moved to accommodate for other surfaces, and the compositor should extend it all the way to the edges it is anchored to. For example, a panel might set its exclusive zone to 10, so that maximized shell surfaces are not shown on top of it. A notification might set its exclusive zone to 0, so that it is moved to avoid occluding the panel, but shell surfaces are shown underneath it. A wallpaper or lock screen might set their exclusive zone to -1, so that they stretch below or over the panel. The default value is 0. Exclusive zone is double-buffered, see wl_surface.commit. Requests that the surface be placed some distance away from the anchor point on the output, in surface-local coordinates. Setting this value for edges you are not anchored to has no effect. The exclusive zone includes the margin. Margin is double-buffered, see wl_surface.commit. Types of keyboard interaction possible for layer shell surfaces. The rationale for this is twofold: (1) some applications are not interested in keyboard events and not allowing them to be focused can improve the desktop experience; (2) some applications will want to take exclusive keyboard focus. This value indicates that this surface is not interested in keyboard events and the compositor should never assign it the keyboard focus. This is the default value, set for newly created layer shell surfaces. This is useful for e.g. desktop widgets that display information or only have interaction with non-keyboard input devices. Request exclusive keyboard focus if this surface is above the shell surface layer. For the top and overlay layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to exclusive. If this layer contains multiple surfaces with keyboard interactivity set to exclusive, the compositor determines the one receiving keyboard events in an implementation- defined manner. In this case, no guarantee is made when this surface will receive keyboard focus (if ever). For the bottom and background layers, the compositor is allowed to use normal focus semantics. This setting is mainly intended for applications that need to ensure they receive all keyboard events, such as a lock screen or a password prompt. This requests the compositor to allow this surface to be focused and unfocused by the user in an implementation-defined manner. The user should be able to unfocus this surface even regardless of the layer it is on. Typically, the compositor will want to use its normal mechanism to manage keyboard focus between layer shell surfaces with this setting and regular toplevels on the desktop layer (e.g. click to focus). Nevertheless, it is possible for a compositor to require a special interaction to focus or unfocus layer shell surfaces (e.g. requiring a click even if focus follows the mouse normally, or providing a keybinding to switch focus between layers). This setting is mainly intended for desktop shell components (e.g. panels) that allow keyboard interaction. Using this option can allow implementing a desktop shell that can be fully usable without the mouse. Set how keyboard events are delivered to this surface. By default, layer shell surfaces do not receive keyboard events; this request can be used to change this. This setting is inherited by child surfaces set by the get_popup request. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. Keyboard interactivity is double-buffered, see wl_surface.commit. This assigns an xdg_popup's parent to this layer_surface. This popup should have been created via xdg_surface::get_popup with the parent set to NULL, and this request must be invoked before committing the popup's initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. This request destroys the layer surface. The configure event asks the client to resize its surface. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. The client is free to dismiss all but the last configure event it received. The width and height arguments specify the size of the window in surface-local coordinates. The size is a hint, in the sense that the client is free to ignore it if it doesn't resize, pick a smaller size (to satisfy aspect ratio or resize in steps of NxM pixels). If the client picks a smaller size and is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the surface will be centered on this axis. If the width or height arguments are zero, it means the client should decide its own window dimension. The closed event is sent by the compositor when the surface will no longer be shown. The output may have been destroyed or the user may have asked for it to be removed. Further changes to the surface will be ignored. The client should destroy the resource after receiving this event, and create a new surface if they so choose. Change the layer that the surface is rendered on. Layer is double-buffered, see wl_surface.commit. phoc-v0.13.1/protocols/wlr-output-power-management-unstable-v1.xml000066400000000000000000000127331422111650000252240ustar00rootroot00000000000000 Copyright © 2019 Purism SPC 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 protocol allows clients to control power management modes of outputs that are currently part of the compositor space. The intent is to allow special clients like desktop shells to power down outputs when the system is idle. To modify outputs not currently part of the compositor space see wlr-output-management. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This interface is a manager that allows creating per-output power management mode controls. Create a output power management mode control that can be used to adjust the power management mode for a given output. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object offers requests to set the power management mode of an output. Set an output's power save mode to the given mode. The mode change is effective immediately. If the output does not support the given mode a failed event is sent. Report the power management mode change of an output. The mode event is sent after an output changed its power management mode. The reason can be a client using set_mode or the compositor deciding to change an output's mode. This event is also sent immediately when the object is created so the client is informed about the current power management mode. This event indicates that the output power management mode control is no longer valid. This can happen for a number of reasons, including: - The output doesn't support power management - Another client already has exclusive power management mode control for this output - The output disappeared Upon receiving this event, the client should destroy this object. Destroys the output power management mode control object. phoc-v0.13.1/protocols/wlr-screencopy-unstable-v1.xml000066400000000000000000000236661422111650000226010ustar00rootroot00000000000000 Copyright © 2018 Simon Ser Copyright © 2019 Andri Yngvason 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 protocol allows clients to ask the compositor to copy part of the screen content to a client buffer. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This object is a manager which offers requests to start capturing from a source. Capture the next frame of an entire output. Capture the next frame of an output's region. The region is given in output logical coordinates, see xdg_output.logical_size. The region will be clipped to the output's extents. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object represents a single frame. When created, a series of buffer events will be sent, each representing a supported buffer type. The "buffer_done" event is sent afterwards to indicate that all supported buffer types have been enumerated. The client will then be able to send a "copy" request. If the capture is successful, the compositor will send a "flags" followed by a "ready" event. For objects version 2 or lower, wl_shm buffers are always supported, ie. the "buffer" event is guaranteed to be sent. If the capture failed, the "failed" event is sent. This can happen anytime before the "ready" event. Once either a "ready" or a "failed" event is received, the client should destroy the frame. Provides information about wl_shm buffer parameters that need to be used for this frame. This event is sent once after the frame is created if wl_shm buffers are supported. Copy the frame to the supplied buffer. The buffer must have a the correct size, see zwlr_screencopy_frame_v1.buffer and zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a supported format. If the frame is successfully copied, a "flags" and a "ready" events are sent. Otherwise, a "failed" event is sent. Provides flags about the frame. This event is sent once before the "ready" event. Called as soon as the frame is copied, indicating it is available for reading. This event includes the time at which presentation happened at. The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, each component being an unsigned 32-bit value. Whole seconds are in tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, and the additional fractional part in tv_nsec as nanoseconds. Hence, for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part may have an arbitrary offset at start. After receiving this event, the client should destroy the object. This event indicates that the attempted frame copy has failed. After receiving this event, the client should destroy the object. Destroys the frame. This request can be sent at any time by the client. Same as copy, except it waits until there is damage to copy. This event is sent right before the ready event when copy_with_damage is requested. It may be generated multiple times for each copy_with_damage request. The arguments describe a box around an area that has changed since the last copy request that was derived from the current screencopy manager instance. The union of all regions received between the call to copy_with_damage and a ready event is the total damage since the prior ready event. Provides information about linux-dmabuf buffer parameters that need to be used for this frame. This event is sent once after the frame is created if linux-dmabuf buffers are supported. This event is sent once after all buffer events have been sent. The client should proceed to create a buffer of one of the supported types, and send a "copy" request. phoc-v0.13.1/run.in000077500000000000000000000004331422111650000140420ustar00rootroot00000000000000#!/bin/sh set -e ABS_BUILDDIR='@ABS_BUILDDIR@' ABS_SRCDIR='@ABS_SRCDIR@' [ -z "${DISPLAY}" ] || WLR_BACKENDS=x11 [ -z "${WAYLAND_DISPLAY}" ] || WLR_BACKENDS=wayland export WLR_BACKENDS export GSETTINGS_SCHEMA_DIR="${ABS_BUILDDIR}/data" set -x exec "${ABS_BUILDDIR}/src/phoc" "$@" phoc-v0.13.1/src/000077500000000000000000000000001422111650000134725ustar00rootroot00000000000000phoc-v0.13.1/src/cursor.c000066400000000000000000000756201422111650000151650ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * * SPDX-License-Identifier: GPL-3-or-later or MIT */ #define G_LOG_DOMAIN "phoc-cursor" #include "config.h" #include "server.h" #define _XOPEN_SOURCE 700 #include #include #include #include #include #include #include #include #include "cursor.h" #include "desktop.h" #include "utils.h" #include "view.h" #include "xcursor.h" enum { PROP_0, PROP_SEAT, PROP_LAST_PROP }; static GParamSpec *props[PROP_LAST_PROP]; G_DEFINE_TYPE (PhocCursor, phoc_cursor, G_TYPE_OBJECT) static void handle_pointer_motion (struct wl_listener *listener, void *data); static void handle_pointer_motion_absolute (struct wl_listener *listener, void *data); static void handle_pointer_button (struct wl_listener *listener, void *data); static void handle_pointer_axis (struct wl_listener *listener, void *data); static void handle_pointer_frame (struct wl_listener *listener, void *data); static void phoc_cursor_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PhocCursor *self = PHOC_CURSOR (object); switch (property_id) { case PROP_SEAT: self->seat = g_value_get_pointer (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void phoc_cursor_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PhocCursor *self = PHOC_CURSOR (object); switch (property_id) { case PROP_SEAT: g_value_set_pointer (value, self->seat); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void seat_view_deco_motion (PhocSeatView *view, double deco_sx, double deco_sy) { PhocCursor *self = phoc_seat_get_cursor (view->seat); double sx = deco_sx; double sy = deco_sy; if (view->has_button_grab) { sx = view->grab_sx; sy = view->grab_sy; } PhocViewDecoPart parts = view_get_deco_part (view->view, sx, sy); bool is_titlebar = (parts & PHOC_VIEW_DECO_PART_TITLEBAR); uint32_t edges = 0; if (parts & PHOC_VIEW_DECO_PART_LEFT_BORDER) { edges |= WLR_EDGE_LEFT; } else if (parts & PHOC_VIEW_DECO_PART_RIGHT_BORDER) { edges |= WLR_EDGE_RIGHT; } else if (parts & PHOC_VIEW_DECO_PART_BOTTOM_BORDER) { edges |= WLR_EDGE_BOTTOM; } else if (parts & PHOC_VIEW_DECO_PART_TOP_BORDER) { edges |= WLR_EDGE_TOP; } if (view->has_button_grab) { if (is_titlebar) { phoc_seat_begin_move (view->seat, view->view); } else if (edges) { phoc_seat_begin_resize (view->seat, view->view, edges); } view->has_button_grab = false; } else { if (is_titlebar) { phoc_seat_maybe_set_cursor (self->seat, NULL); } else if (edges) { const char *resize_name = wlr_xcursor_get_resize_name (edges); phoc_seat_maybe_set_cursor (self->seat, resize_name); } } } static void seat_view_deco_leave (PhocSeatView *view) { PhocCursor *self = phoc_seat_get_cursor (view->seat); phoc_seat_maybe_set_cursor (self->seat, NULL); view->has_button_grab = false; } static void seat_view_deco_button (PhocSeatView *view, double sx, double sy, uint32_t button, uint32_t state) { if (button == BTN_LEFT && state == WLR_BUTTON_PRESSED) { view->has_button_grab = true; view->grab_sx = sx; view->grab_sy = sy; } else { view->has_button_grab = false; } PhocViewDecoPart parts = view_get_deco_part (view->view, sx, sy); if (state == WLR_BUTTON_RELEASED && (parts & PHOC_VIEW_DECO_PART_TITLEBAR)) { phoc_seat_maybe_set_cursor (view->seat, NULL); } } static bool roots_handle_shell_reveal (struct wlr_surface *surface, double lx, double ly, int threshold) { PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; if (surface) { struct wlr_surface *root = wlr_surface_get_root_surface (surface), *iter = root; while (wlr_surface_is_xdg_surface (iter)) { struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_from_wlr_surface (iter); if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { iter = xdg_surface->popup->parent; } else { break; } } if (wlr_surface_is_layer_surface (iter)) { return false; } } struct wlr_output *wlr_output = wlr_output_layout_output_at (desktop->layout, lx, ly); if (!wlr_output) { return false; } PhocOutput *output = wlr_output->data; struct wlr_box *output_box = wlr_output_layout_get_box (desktop->layout, wlr_output); PhocLayerSurface *roots_surface; bool left = false, right = false, top = false, bottom = false; wl_list_for_each (roots_surface, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], link) { struct wlr_layer_surface_v1 *layer = roots_surface->layer_surface; struct wlr_layer_surface_v1_state *state = &layer->current; const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; if (state->anchor == (both_horiz | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { top = true; } if (state->anchor == (both_horiz | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { bottom = true; } if (state->anchor == (both_vert | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { left = true; } if (state->anchor == (both_vert | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { right = true; } } if ((top && ly <= output_box->y + threshold) || (bottom && ly >= output_box->y + output_box->height - 1 - threshold) || (left && lx <= output_box->x + threshold) || (right && lx >= output_box->x + output_box->width - 1 - threshold)) { if (output->fullscreen_view) { output->force_shell_reveal = true; phoc_layer_shell_update_focus (); phoc_output_damage_whole (output); } return true; } else { if (output->force_shell_reveal) { output->force_shell_reveal = false; phoc_layer_shell_update_focus (); phoc_output_damage_whole (output); } } return false; } static void roots_passthrough_cursor (PhocCursor *self, uint32_t time) { PhocServer *server = phoc_server_get_default (); double sx, sy; PhocView *view = NULL; PhocSeat *seat = self->seat; PhocDesktop *desktop = server->desktop; struct wlr_surface *surface = phoc_desktop_surface_at (desktop, self->cursor->x, self->cursor->y, &sx, &sy, &view); struct wl_client *client = NULL; if (surface) { client = wl_resource_get_client (surface->resource); } if (surface && !phoc_seat_allow_input (seat, surface->resource)) { return; } if (self->cursor_client != client || !client) { phoc_seat_maybe_set_cursor (seat, NULL); self->cursor_client = client; } if (view) { PhocSeatView *seat_view = phoc_seat_view_from_view (seat, view); if (self->pointer_view && !self->wlr_surface && (surface || seat_view != self->pointer_view)) { seat_view_deco_leave (self->pointer_view); } self->pointer_view = seat_view; if (!surface) { seat_view_deco_motion (seat_view, sx, sy); } } else { self->pointer_view = NULL; } self->wlr_surface = surface; if (surface) { wlr_seat_pointer_notify_enter (seat->seat, surface, sx, sy); wlr_seat_pointer_notify_motion (seat->seat, time, sx, sy); } else { wlr_seat_pointer_clear_focus (seat->seat); } if (seat->drag_icon != NULL) { phoc_drag_icon_update_position (seat->drag_icon); } } static inline int64_t timespec_to_msec (const struct timespec *a) { return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; } static void phoc_cursor_constructed (GObject *object) { PhocCursor *self = PHOC_CURSOR (object); struct wlr_cursor *wlr_cursor = self->cursor; g_assert (self->cursor); self->xcursor_manager = wlr_xcursor_manager_create (NULL, PHOC_XCURSOR_SIZE); g_assert (self->xcursor_manager); wl_signal_add (&wlr_cursor->events.motion, &self->motion); self->motion.notify = handle_pointer_motion; wl_signal_add (&wlr_cursor->events.motion_absolute, &self->motion_absolute); self->motion_absolute.notify = handle_pointer_motion_absolute; wl_signal_add (&wlr_cursor->events.button, &self->button); self->button.notify = handle_pointer_button; wl_signal_add (&wlr_cursor->events.axis, &self->axis); self->axis.notify = handle_pointer_axis; wl_signal_add (&wlr_cursor->events.frame, &self->frame); self->frame.notify = handle_pointer_frame; G_OBJECT_CLASS (phoc_cursor_parent_class)->constructed (object); } static void phoc_cursor_finalize (GObject *object) { PhocCursor *self = PHOC_CURSOR (object); wl_list_remove (&self->motion.link); wl_list_remove (&self->motion_absolute.link); wl_list_remove (&self->button.link); wl_list_remove (&self->axis.link); wl_list_remove (&self->frame.link); wl_list_remove (&self->swipe_begin.link); wl_list_remove (&self->swipe_update.link); wl_list_remove (&self->swipe_end.link); wl_list_remove (&self->pinch_begin.link); wl_list_remove (&self->pinch_update.link); wl_list_remove (&self->pinch_end.link); wl_list_remove (&self->touch_down.link); wl_list_remove (&self->touch_up.link); wl_list_remove (&self->touch_motion.link); wl_list_remove (&self->tool_axis.link); wl_list_remove (&self->tool_tip.link); wl_list_remove (&self->tool_proximity.link); wl_list_remove (&self->tool_button.link); wl_list_remove (&self->request_set_cursor.link); wl_list_remove (&self->focus_change.link); g_clear_pointer (&self->xcursor_manager, wlr_xcursor_manager_destroy); g_clear_pointer (&self->cursor, wlr_cursor_destroy); G_OBJECT_CLASS (phoc_cursor_parent_class)->finalize (object); } static void phoc_cursor_class_init (PhocCursorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = phoc_cursor_constructed; object_class->finalize = phoc_cursor_finalize; object_class->get_property = phoc_cursor_get_property; object_class->set_property = phoc_cursor_set_property; props[PROP_SEAT] = g_param_spec_pointer ("seat", "", "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } static void phoc_cursor_init (PhocCursor *self) { self->cursor = wlr_cursor_create (); self->default_xcursor = PHOC_XCURSOR_DEFAULT; } void phoc_cursor_update_focus (PhocCursor *self) { struct timespec now; clock_gettime (CLOCK_MONOTONIC, &now); roots_passthrough_cursor (self, timespec_to_msec (&now)); } void phoc_cursor_update_position (PhocCursor *self, uint32_t time) { PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; PhocSeat *seat = self->seat; PhocView *view; switch (self->mode) { case PHOC_CURSOR_PASSTHROUGH: roots_passthrough_cursor (self, time); break; case PHOC_CURSOR_MOVE: view = phoc_seat_get_focus (seat); if (view != NULL) { struct wlr_box geom; view_get_geometry (view, &geom); double dx = self->cursor->x - self->offs_x; double dy = self->cursor->y - self->offs_y; struct wlr_output *wlr_output = wlr_output_layout_output_at (desktop->layout, self->cursor->x, self->cursor->y); struct wlr_box *output_box = wlr_output_layout_get_box (desktop->layout, wlr_output); bool output_is_landscape = output_box->width > output_box->height; if (view_is_fullscreen (view)) { phoc_view_set_fullscreen (view, true, wlr_output); } else if (self->cursor->y < output_box->y + PHOC_EDGE_SNAP_THRESHOLD) { view_maximize (view, wlr_output); } else if (output_is_landscape && self->cursor->x < output_box->x + PHOC_EDGE_SNAP_THRESHOLD) { view_tile (view, PHOC_VIEW_TILE_LEFT, wlr_output); } else if (output_is_landscape && self->cursor->x > output_box->x + output_box->width - PHOC_EDGE_SNAP_THRESHOLD) { view_tile (view, PHOC_VIEW_TILE_RIGHT, wlr_output); } else { view_restore (view); view_move (view, self->view_x + dx - geom.x * view->scale, self->view_y + dy - geom.y * view->scale); } } break; case PHOC_CURSOR_RESIZE: view = phoc_seat_get_focus (seat); if (view != NULL) { struct wlr_box geom; view_get_geometry (view, &geom); double dx = self->cursor->x - self->offs_x; double dy = self->cursor->y - self->offs_y; double x = view->box.x; double y = view->box.y; int width = self->view_width; int height = self->view_height; if (self->resize_edges & WLR_EDGE_TOP) { y = self->view_y + dy - geom.y * view->scale; height -= dy; if (height < 1) { y += height; } } else if (self->resize_edges & WLR_EDGE_BOTTOM) { height += dy; } if (self->resize_edges & WLR_EDGE_LEFT) { x = self->view_x + dx - geom.x * view->scale; width -= dx; if (width < 1) { x += width; } } else if (self->resize_edges & WLR_EDGE_RIGHT) { width += dx; } view_move_resize (view, x, y, width < 1 ? 1 : width, height < 1 ? 1 : height); } break; default: g_error ("Invalid cursor mode %d", self->mode); } } static void phoc_cursor_press_button (PhocCursor *self, struct wlr_input_device *device, uint32_t time, uint32_t button, uint32_t state, double lx, double ly) { PhocServer *server = phoc_server_get_default (); PhocSeat *seat = self->seat; PhocDesktop *desktop = server->desktop; bool is_touch = device->type == WLR_INPUT_DEVICE_TOUCH; double sx, sy; PhocView *view; struct wlr_surface *surface = phoc_desktop_surface_at (desktop, lx, ly, &sx, &sy, &view); if (state == WLR_BUTTON_PRESSED && view && phoc_seat_has_meta_pressed (seat)) { phoc_seat_set_focus (seat, view); uint32_t edges; switch (button) { case BTN_LEFT: phoc_seat_begin_move (seat, view); break; case BTN_RIGHT: edges = 0; if (sx < view->wlr_surface->current.width/2) { edges |= WLR_EDGE_LEFT; } else { edges |= WLR_EDGE_RIGHT; } if (sy < view->wlr_surface->current.height/2) { edges |= WLR_EDGE_TOP; } else { edges |= WLR_EDGE_BOTTOM; } phoc_seat_begin_resize (seat, view, edges); break; default: /* don't care */ break; } } else { if (view && !surface && self->pointer_view) { seat_view_deco_button (self->pointer_view, sx, sy, button, state); } if (state == WLR_BUTTON_RELEASED && self->mode != PHOC_CURSOR_PASSTHROUGH) { self->mode = PHOC_CURSOR_PASSTHROUGH; phoc_cursor_update_focus (self); } if (state == WLR_BUTTON_PRESSED) { if (view) { phoc_seat_set_focus (seat, view); } if (surface && wlr_surface_is_layer_surface (surface)) { struct wlr_layer_surface_v1 *layer = wlr_layer_surface_v1_from_wlr_surface (surface); if (layer->current.keyboard_interactive) { phoc_seat_set_focus_layer (seat, layer); } } } } if (!roots_handle_shell_reveal (surface, lx, ly, PHOC_SHELL_REVEAL_POINTER_THRESHOLD) && !is_touch) { wlr_seat_pointer_notify_button (seat->seat, time, button, state); } } static void handle_pointer_motion (struct wl_listener *listener, void *data) { PhocCursor *self = wl_container_of (listener, self, motion); struct wlr_event_pointer_motion *event = data; PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; double dx = event->delta_x; double dy = event->delta_y; double dx_unaccel = event->unaccel_dx; double dy_unaccel = event->unaccel_dy; wlr_idle_notify_activity (desktop->idle, self->seat->seat); wlr_relative_pointer_manager_v1_send_relative_motion ( server->desktop->relative_pointer_manager, self->seat->seat, (uint64_t)event->time_msec * 1000, dx, dy, dx_unaccel, dy_unaccel); if (self->active_constraint) { PhocView *view = self->pointer_view->view; assert (view); double lx1 = self->cursor->x; double ly1 = self->cursor->y; double lx2 = lx1 + dx; double ly2 = ly1 + dy; double sx1 = lx1 - view->box.x; double sy1 = ly1 - view->box.y; double sx2 = lx2 - view->box.x; double sy2 = ly2 - view->box.y; double sx2_confined, sy2_confined; if (!wlr_region_confine (&self->confine, sx1, sy1, sx2, sy2, &sx2_confined, &sy2_confined)) { return; } dx = sx2_confined - sx1; dy = sy2_confined - sy1; } wlr_cursor_move (self->cursor, event->device, dx, dy); phoc_cursor_update_position (self, event->time_msec); } static void handle_pointer_motion_absolute (struct wl_listener *listener, void *data) { PhocCursor *self = wl_container_of (listener, self, motion_absolute); struct wlr_event_pointer_motion_absolute *event = data; PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; double lx, ly; wlr_idle_notify_activity (desktop->idle, self->seat->seat); wlr_cursor_absolute_to_layout_coords (self->cursor, event->device, event->x, event->y, &lx, &ly); double dx = lx - self->cursor->x; double dy = ly - self->cursor->y; wlr_relative_pointer_manager_v1_send_relative_motion ( server->desktop->relative_pointer_manager, self->seat->seat, (uint64_t)event->time_msec * 1000, dx, dy, dx, dy); if (self->pointer_view) { PhocView *view = self->pointer_view->view; if (self->active_constraint && !pixman_region32_contains_point (&self->confine, floor (lx - view->box.x), floor (ly - view->box.y), NULL)) { return; } } wlr_cursor_warp_closest (self->cursor, event->device, lx, ly); phoc_cursor_update_position (self, event->time_msec); } static void handle_pointer_button (struct wl_listener *listener, void *data) { PhocCursor *self = wl_container_of (listener, self, button); struct wlr_event_pointer_button *event = data; PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; wlr_idle_notify_activity (desktop->idle, self->seat->seat); phoc_cursor_press_button (self, event->device, event->time_msec, event->button, event->state, self->cursor->x, self->cursor->y); } static void handle_pointer_axis (struct wl_listener *listener, void *data) { PhocCursor *self = wl_container_of (listener, self, axis); struct wlr_event_pointer_axis *event = data; PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; wlr_idle_notify_activity (desktop->idle, self->seat->seat); wlr_seat_pointer_notify_axis (self->seat->seat, event->time_msec, event->orientation, event->delta, event->delta_discrete, event->source); } static void handle_pointer_frame (struct wl_listener *listener, void *data) { PhocCursor *self = wl_container_of (listener, self, frame); PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; wlr_idle_notify_activity (desktop->idle, self->seat->seat); wlr_seat_pointer_notify_frame (self->seat->seat); } void phoc_cursor_handle_touch_down (PhocCursor *self, struct wlr_event_touch_down *event) { PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; PhocSeat *seat = self->seat; double lx, ly; wlr_cursor_absolute_to_layout_coords (self->cursor, event->device, event->x, event->y, &lx, &ly); if (seat->touch_id == -1 && self->mode == PHOC_CURSOR_PASSTHROUGH) { seat->touch_id = event->touch_id; seat->touch_x = lx; seat->touch_y = ly; } double sx, sy; PhocView *view; struct wlr_surface *surface = phoc_desktop_surface_at ( desktop, lx, ly, &sx, &sy, &view); bool shell_revealed = roots_handle_shell_reveal (surface, lx, ly, PHOC_SHELL_REVEAL_TOUCH_THRESHOLD); if (!shell_revealed && surface && phoc_seat_allow_input (seat, surface->resource)) { wlr_seat_touch_notify_down (seat->seat, surface, event->time_msec, event->touch_id, sx, sy); wlr_seat_touch_point_focus (seat->seat, surface, event->time_msec, event->touch_id, sx, sy); if (view) phoc_seat_set_focus (seat, view); if (wlr_surface_is_layer_surface (surface)) { struct wlr_layer_surface_v1 *layer = wlr_layer_surface_v1_from_wlr_surface (surface); if (layer->current.keyboard_interactive) { phoc_seat_set_focus_layer (seat, layer); } } } if (server->debug_flags & PHOC_SERVER_DEBUG_FLAG_TOUCH_POINTS) { PhocOutput *output; wl_list_for_each (output, &desktop->outputs, link) { if (wlr_output_layout_contains_point (desktop->layout, output->wlr_output, lx, ly)) { double ox = lx, oy = ly; wlr_output_layout_output_coords (desktop->layout, output->wlr_output, &ox, &oy); struct wlr_box box = { .x = ox, .y = oy, .width = 1, .height = 1 }; wlr_output_damage_add_box (output->damage, &box); } } } } void phoc_cursor_handle_touch_up (PhocCursor *self, struct wlr_event_touch_up *event) { struct wlr_touch_point *point = wlr_seat_touch_get_point (self->seat->seat, event->touch_id); if (self->seat->touch_id == event->touch_id) self->seat->touch_id = -1; if (!point) return; if (self->mode != PHOC_CURSOR_PASSTHROUGH) { self->mode = PHOC_CURSOR_PASSTHROUGH; phoc_cursor_update_focus (self); } wlr_seat_touch_notify_up (self->seat->seat, event->time_msec, event->touch_id); } void phoc_cursor_handle_touch_motion (PhocCursor *self, struct wlr_event_touch_motion *event) { PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; struct wlr_touch_point *point = wlr_seat_touch_get_point (self->seat->seat, event->touch_id); if (!point) return; double lx, ly; wlr_cursor_absolute_to_layout_coords (self->cursor, event->device, event->x, event->y, &lx, &ly); struct wlr_output *wlr_output = wlr_output_layout_output_at (desktop->layout, lx, ly); if (!wlr_output) return; PhocOutput *phoc_output = wlr_output->data; double sx, sy; struct wlr_surface *surface = point->focus_surface; // TODO: test with input regions if (surface) { bool found = false; float scale = 1.0; struct wlr_surface *root = wlr_surface_get_root_surface (surface); if (wlr_surface_is_layer_surface (root)) { struct wlr_layer_surface_v1 *layer_surface = wlr_layer_surface_v1_from_wlr_surface (root); struct wlr_box *output_box = wlr_output_layout_get_box (desktop->layout, wlr_output); PhocLayerSurface *layer; wl_list_for_each_reverse (layer, &phoc_output->layers[layer_surface->current.layer], link) { if (layer->layer_surface->surface == root) { sx = lx - layer->geo.x - output_box->x; sy = ly - layer->geo.y - output_box->y; found = true; break; } } // try the overlay layer as well since the on-screen keyboard might have been elevated there wl_list_for_each_reverse (layer, &phoc_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], link) { if (layer->layer_surface->surface == root) { sx = lx - layer->geo.x - output_box->x; sy = ly - layer->geo.y - output_box->y; found = true; break; } } } else { PhocView *view = phoc_view_from_wlr_surface (root); if (view) { scale = view->scale; sx = lx / scale - view->box.x; sy = ly / scale - view->box.y; found = true; } else { // FIXME: buggy fallback, but at least handles xdg_popups for now... surface = phoc_desktop_surface_at (desktop, lx, ly, &sx, &sy, NULL); } } if (found) { struct wlr_surface *sub = surface; while (sub && wlr_surface_is_subsurface (sub)) { struct wlr_subsurface *subsurface = wlr_subsurface_from_wlr_surface (sub); sx -= subsurface->current.x; sy -= subsurface->current.y; sub = subsurface->parent; } } } if (surface && phoc_seat_allow_input (self->seat, surface->resource)) { wlr_seat_touch_notify_motion (self->seat->seat, event->time_msec, event->touch_id, sx, sy); } if (event->touch_id == self->seat->touch_id) { self->seat->touch_x = lx; self->seat->touch_y = ly; if (self->mode != PHOC_CURSOR_PASSTHROUGH) { wlr_cursor_warp (self->cursor, NULL, lx, ly); phoc_cursor_update_position (self, event->time_msec); } } } void phoc_cursor_handle_tool_axis (PhocCursor *self, struct wlr_event_tablet_tool_axis *event) { double x = NAN, y = NAN; if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X) && (event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) { x = event->x; y = event->y; } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X)) { x = event->x; } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) { y = event->y; } double lx, ly; wlr_cursor_absolute_to_layout_coords (self->cursor, event->device, x, y, &lx, &ly); if (self->pointer_view) { PhocView *view = self->pointer_view->view; if (self->active_constraint && !pixman_region32_contains_point (&self->confine, floor (lx - view->box.x), floor (ly - view->box.y), NULL)) { return; } } wlr_cursor_warp_closest (self->cursor, event->device, lx, ly); phoc_cursor_update_position (self, event->time_msec); } void phoc_cursor_handle_tool_tip (PhocCursor *self, struct wlr_event_tablet_tool_tip *event) { phoc_cursor_press_button (self, event->device, event->time_msec, BTN_LEFT, event->state, self->cursor->x, self->cursor->y); } void phoc_cursor_handle_request_set_cursor (PhocCursor *self, struct wlr_seat_pointer_request_set_cursor_event *event) { struct wlr_surface *focused_surface = event->seat_client->seat->pointer_state.focused_surface; bool has_focused = focused_surface != NULL && focused_surface->resource != NULL; struct wl_client *focused_client = NULL; if (has_focused) { focused_client = wl_resource_get_client (focused_surface->resource); } if (event->seat_client->client != focused_client || self->mode != PHOC_CURSOR_PASSTHROUGH) { g_debug ("Denying request to set cursor from unfocused client"); return; } wlr_cursor_set_surface (self->cursor, event->surface, event->hotspot_x, event->hotspot_y); self->cursor_client = event->seat_client->client; } void phoc_cursor_handle_focus_change (PhocCursor *self, struct wlr_seat_pointer_focus_change_event *event) { PhocServer *server = phoc_server_get_default (); double sx = event->sx; double sy = event->sy; double lx = self->cursor->x; double ly = self->cursor->y; g_debug ("entered surface %p, lx: %f, ly: %f, sx: %f, sy: %f", event->new_surface, lx, ly, sx, sy); phoc_cursor_constrain (self, wlr_pointer_constraints_v1_constraint_for_surface ( server->desktop->pointer_constraints, event->new_surface, self->seat->seat), sx, sy); } void phoc_cursor_handle_constraint_commit (PhocCursor *self) { PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; double sx, sy; struct wlr_surface *surface = phoc_desktop_surface_at (desktop, self->cursor->x, self->cursor->y, &sx, &sy, NULL); // This should never happen but views move around right when they're // created from (0, 0) to their actual coordinates. if (surface != self->active_constraint->surface) phoc_cursor_update_focus (self); else phoc_cursor_constrain (self, self->active_constraint, sx, sy); } static void handle_constraint_commit (struct wl_listener *listener, void *data) { PhocCursor *self = wl_container_of (listener, self, constraint_commit); assert (self->active_constraint->surface == data); phoc_cursor_handle_constraint_commit (self); } void phoc_cursor_constrain (PhocCursor *self, struct wlr_pointer_constraint_v1 *constraint, double sx, double sy) { if (self->active_constraint == constraint) return; g_debug ("phoc_cursor_constrain(%p, %p)", self, constraint); g_debug ("self->active_constraint: %p", self->active_constraint); wl_list_remove (&self->constraint_commit.link); wl_list_init (&self->constraint_commit.link); if (self->active_constraint) { wlr_pointer_constraint_v1_send_deactivated ( self->active_constraint); } self->active_constraint = constraint; if (constraint == NULL) return; wlr_pointer_constraint_v1_send_activated (constraint); wl_list_remove (&self->constraint_commit.link); wl_signal_add (&constraint->surface->events.commit, &self->constraint_commit); self->constraint_commit.notify = handle_constraint_commit; pixman_region32_clear (&self->confine); pixman_region32_t *region = &constraint->region; if (!pixman_region32_contains_point (region, floor (sx), floor (sy), NULL)) { // Warp into region if possible int nboxes; pixman_box32_t *boxes = pixman_region32_rectangles (region, &nboxes); if (nboxes > 0) { PhocView *view = self->pointer_view->view; double lx = view->box.x + (boxes[0].x1 + boxes[0].x2) / 2.; double ly = view->box.y + (boxes[0].y1 + boxes[0].y2) / 2.; wlr_cursor_warp_closest (self->cursor, NULL, lx, ly); } } // A locked pointer will result in an empty region, thus disallowing all movement if (constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) { pixman_region32_copy (&self->confine, region); } } PhocCursor * phoc_cursor_new (PhocSeat *seat) { return PHOC_CURSOR (g_object_new (PHOC_TYPE_CURSOR, "seat", seat, NULL)); } phoc-v0.13.1/src/cursor.h000066400000000000000000000115451422111650000151660ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "seat.h" #include G_BEGIN_DECLS #define PHOC_TYPE_CURSOR (phoc_cursor_get_type ()) G_DECLARE_FINAL_TYPE (PhocCursor, phoc_cursor, PHOC, CURSOR, GObject) #define PHOC_SHELL_REVEAL_TOUCH_THRESHOLD 10 #define PHOC_SHELL_REVEAL_POINTER_THRESHOLD 0 #define PHOC_EDGE_SNAP_THRESHOLD 20 typedef enum { PHOC_CURSOR_PASSTHROUGH = 0, PHOC_CURSOR_MOVE = 1, PHOC_CURSOR_RESIZE = 2, } PhocCursorMode; typedef struct _PhocSeatView PhocSeatView; /* TODO: we keep the struct public due to the list links and notifiers but we should avoid other member access */ typedef struct _PhocCursor { GObject parent; PhocSeat *seat; struct wlr_cursor *cursor; struct wlr_pointer_constraint_v1 *active_constraint; pixman_region32_t confine; // invalid if active_constraint == NULL const char *default_xcursor; PhocCursorMode mode; // state from input (review if this is necessary) struct wlr_xcursor_manager *xcursor_manager; struct wl_client *cursor_client; int offs_x, offs_y; int view_x, view_y, view_width, view_height; uint32_t resize_edges; PhocSeatView *pointer_view; struct wlr_surface *wlr_surface; struct wl_listener motion; struct wl_listener motion_absolute; struct wl_listener button; struct wl_listener axis; struct wl_listener frame; struct wl_listener swipe_begin; struct wl_listener swipe_update; struct wl_listener swipe_end; struct wl_listener pinch_begin; struct wl_listener pinch_update; struct wl_listener pinch_end; struct wl_listener touch_down; struct wl_listener touch_up; struct wl_listener touch_motion; struct wl_listener tool_axis; struct wl_listener tool_tip; struct wl_listener tool_proximity; struct wl_listener tool_button; struct wl_listener request_set_cursor; struct wl_listener focus_change; struct wl_listener constraint_commit; } PhocCursor; PhocCursor *phoc_cursor_new (PhocSeat *seat); void phoc_cursor_handle_touch_down (PhocCursor *self, struct wlr_event_touch_down *event); void phoc_cursor_handle_touch_up (PhocCursor *self, struct wlr_event_touch_up *event); void phoc_cursor_handle_touch_motion (PhocCursor *self, struct wlr_event_touch_motion *event); void phoc_cursor_handle_tool_axis (PhocCursor *self, struct wlr_event_tablet_tool_axis *event); void phoc_cursor_handle_tool_tip (PhocCursor *self, struct wlr_event_tablet_tool_tip *event); void phoc_cursor_handle_request_set_cursor (PhocCursor *self, struct wlr_seat_pointer_request_set_cursor_event *event); void phoc_cursor_handle_focus_change (PhocCursor *self, struct wlr_seat_pointer_focus_change_event *event); void phoc_cursor_handle_constraint_commit (PhocCursor *self); void phoc_cursor_update_position (PhocCursor *self, uint32_t time); void phoc_cursor_update_focus (PhocCursor *self); void phoc_cursor_constrain (PhocCursor *self, struct wlr_pointer_constraint_v1 *constraint, double sx, double sy); void phoc_maybe_set_cursor (PhocCursor *self); phoc-v0.13.1/src/desktop.c000066400000000000000000000705521422111650000153200ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-desktop" #include "config.h" #define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cursor.h" #include "layers.h" #include "output.h" #include "seat.h" #include "server.h" #include "utils.h" #include "view.h" #include "virtual.h" #include "xcursor.h" #include "xdg-activation-v1.h" #include "wlr-layer-shell-unstable-v1-protocol.h" #include "xdg-surface.h" /** * PhocDesktop: * * Desktop singleton */ enum { PROP_0, PROP_CONFIG, PROP_LAST_PROP, }; static GParamSpec *props[PROP_LAST_PROP]; G_DEFINE_TYPE(PhocDesktop, phoc_desktop, G_TYPE_OBJECT); static void phoc_desktop_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PhocDesktop *self = PHOC_DESKTOP (object); switch (property_id) { case PROP_CONFIG: self->config = g_value_get_pointer (value); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void phoc_desktop_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PhocDesktop *self = PHOC_DESKTOP (object); switch (property_id) { case PROP_CONFIG: g_value_set_pointer (value, self->config); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static bool view_at(PhocView *view, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { if (!phoc_view_is_mapped (view)) { return false; } double view_sx = lx / view->scale - view->box.x; double view_sy = ly / view->scale - view->box.y; double _sx, _sy; struct wlr_surface *_surface = NULL; switch (view->type) { case PHOC_XDG_SHELL_VIEW:; PhocXdgSurface *xdg_surface = phoc_xdg_surface_from_view(view); _surface = wlr_xdg_surface_surface_at(xdg_surface->xdg_surface, view_sx, view_sy, &_sx, &_sy); break; #ifdef PHOC_XWAYLAND case PHOC_XWAYLAND_VIEW: _surface = wlr_surface_surface_at(view->wlr_surface, view_sx, view_sy, &_sx, &_sy); break; #endif default: g_error("Invalid view type %d", view->type); } if (_surface != NULL) { *sx = _sx; *sy = _sy; *surface = _surface; return true; } if (view_get_deco_part(view, view_sx, view_sy)) { *sx = view_sx; *sy = view_sy; *surface = NULL; return true; } return false; } static PhocView *desktop_view_at(PhocDesktop *desktop, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) { PhocView *view; wl_list_for_each(view, &desktop->views, link) { if (phoc_desktop_view_is_visible(desktop, view) && view_at(view, lx, ly, surface, sx, sy)) { return view; } } return NULL; } static struct wlr_surface *layer_surface_at(struct wl_list *layer, double ox, double oy, double *sx, double *sy) { PhocLayerSurface *layer_surface; wl_list_for_each_reverse(layer_surface, layer, link) { if (layer_surface->layer_surface->current.exclusive_zone <= 0) { continue; } double _sx = ox - layer_surface->geo.x; double _sy = oy - layer_surface->geo.y; struct wlr_surface *sub = wlr_layer_surface_v1_surface_at( layer_surface->layer_surface, _sx, _sy, sx, sy); if (sub) { return sub; } } wl_list_for_each(layer_surface, layer, link) { if (layer_surface->layer_surface->current.exclusive_zone > 0) { continue; } double _sx = ox - layer_surface->geo.x; double _sy = oy - layer_surface->geo.y; struct wlr_surface *sub = wlr_layer_surface_v1_surface_at( layer_surface->layer_surface, _sx, _sy, sx, sy); if (sub) { return sub; } } return NULL; } /** * phoc_desktop_surface_at: * @desktop: The `PhocDesktop` to look the surface up for * @lx: X coordinate the surface to look up at in layout coordinates * @ly: Y coordinate the surface to look up at in layout coordinates * @sx: (out) (not nullable): Surface-local x coordinate * @sy: (out) (not nullable): Surface-local y coordinate * @view: (out) (optional): The corresponding [class@Phoc.View] * * Looks up the surface at `lx,ly` and returns the topmost surface at * that position (if any) and the surface-local coordinates of `sx,sy` * on that surface. * * Returns: (nullable): The `struct wlr_surface` */ struct wlr_surface *phoc_desktop_surface_at(PhocDesktop *desktop, double lx, double ly, double *sx, double *sy, PhocView **view) { struct wlr_surface *surface = NULL; struct wlr_output *wlr_output = wlr_output_layout_output_at(desktop->layout, lx, ly); PhocOutput *phoc_output = NULL; double ox = lx, oy = ly; if (view) { *view = NULL; } if (wlr_output) { phoc_output = wlr_output->data; wlr_output_layout_output_coords(desktop->layout, wlr_output, &ox, &oy); if ((surface = layer_surface_at(&phoc_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], ox, oy, sx, sy))) { return surface; } PhocOutput *output = wlr_output->data; if (output != NULL && output->fullscreen_view != NULL) { if (output->force_shell_reveal) { if ((surface = layer_surface_at(&phoc_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], ox, oy, sx, sy))) { return surface; } } if (view_at(output->fullscreen_view, lx, ly, &surface, sx, sy)) { if (view) { *view = output->fullscreen_view; } return surface; } else { return NULL; } } if ((surface = layer_surface_at(&phoc_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], ox, oy, sx, sy))) { return surface; } } PhocView *_view; if ((_view = desktop_view_at(desktop, lx, ly, &surface, sx, sy))) { if (view) { *view = _view; } return surface; } if (wlr_output) { if ((surface = layer_surface_at(&phoc_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], ox, oy, sx, sy))) { return surface; } if ((surface = layer_surface_at(&phoc_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], ox, oy, sx, sy))) { return surface; } } return NULL; } gboolean phoc_desktop_view_is_visible (PhocDesktop *desktop, PhocView *view) { if (!phoc_view_is_mapped (view)) { return false; } g_assert_false (wl_list_empty (&desktop->views)); if (wl_list_length (&desktop->outputs) != 1) { // current heuristics work well only for single output return true; } if (!desktop->maximize) { return true; } PhocView *top_view = wl_container_of (desktop->views.next, view, link); #ifdef PHOC_XWAYLAND // XWayland parent relations can be complicated and aren't described by PhocView // relationships very well at the moment, so just make all XWayland windows visible // when some XWayland window is active for now if (view->type == PHOC_XWAYLAND_VIEW && top_view->type == PHOC_XWAYLAND_VIEW) { return true; } #endif PhocView *v = top_view; while (v) { if (v == view) { return true; } if (view_is_maximized (v)) { return false; } v = v->parent; } return false; } static void handle_layout_change (struct wl_listener *listener, void *data) { PhocDesktop *self; struct wlr_output *center_output; struct wlr_box *center_output_box; double center_x, center_y; PhocView *view; PhocOutput *output; self = wl_container_of (listener, self, layout_change); center_output = wlr_output_layout_get_center_output (self->layout); if (center_output == NULL) return; center_output_box = wlr_output_layout_get_box (self->layout, center_output); center_x = center_output_box->x + center_output_box->width / 2; center_y = center_output_box->y + center_output_box->height / 2; /* Make sure all views are on an existing output */ wl_list_for_each (view, &self->views, link) { struct wlr_box box; view_get_box (view, &box); if (wlr_output_layout_intersects (self->layout, NULL, &box)) continue; view_move (view, center_x - box.width / 2, center_y - box.height / 2); } /* Damage all outputs since the move above damaged old layout space */ wl_list_for_each(output, &self->outputs, link) phoc_output_damage_whole(output); } static void input_inhibit_activate(struct wl_listener *listener, void *data) { PhocDesktop *desktop = wl_container_of( listener, desktop, input_inhibit_activate); PhocServer *server = phoc_server_get_default (); for (GSList *elem = phoc_input_get_seats (server->input); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); phoc_seat_set_exclusive_client(seat, desktop->input_inhibit->active_client); } } static void input_inhibit_deactivate(struct wl_listener *listener, void *data) { PhocDesktop *desktop = wl_container_of( listener, desktop, input_inhibit_deactivate); PhocServer *server = phoc_server_get_default (); for (GSList *elem = phoc_input_get_seats (server->input); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); phoc_seat_set_exclusive_client(seat, NULL); } } static void handle_constraint_destroy(struct wl_listener *listener, void *data) { PhocPointerConstraint *constraint = wl_container_of(listener, constraint, destroy); struct wlr_pointer_constraint_v1 *wlr_constraint = data; PhocSeat *seat = wlr_constraint->seat->data; PhocCursor *cursor = phoc_seat_get_cursor (seat); wl_list_remove(&constraint->destroy.link); if (cursor->active_constraint == wlr_constraint) { wl_list_remove(&cursor->constraint_commit.link); wl_list_init(&cursor->constraint_commit.link); cursor->active_constraint = NULL; if (wlr_constraint->current.committed & WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT && cursor->pointer_view) { PhocView *view = cursor->pointer_view->view; double lx = view->box.x + wlr_constraint->current.cursor_hint.x; double ly = view->box.y + wlr_constraint->current.cursor_hint.y; wlr_cursor_warp(cursor->cursor, NULL, lx, ly); } } free(constraint); } static void handle_pointer_constraint(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); struct wlr_pointer_constraint_v1 *wlr_constraint = data; PhocSeat *seat = wlr_constraint->seat->data; PhocCursor *cursor = phoc_seat_get_cursor (seat); PhocPointerConstraint *constraint = calloc(1, sizeof(PhocPointerConstraint)); constraint->constraint = wlr_constraint; constraint->destroy.notify = handle_constraint_destroy; wl_signal_add(&wlr_constraint->events.destroy, &constraint->destroy); double sx, sy; struct wlr_surface *surface = phoc_desktop_surface_at( server->desktop, cursor->cursor->x, cursor->cursor->y, &sx, &sy, NULL); if (surface == wlr_constraint->surface) { assert(!cursor->active_constraint); phoc_cursor_constrain(cursor, wlr_constraint, sx, sy); } } static void auto_maximize_changed_cb (PhocDesktop *self, const gchar *key, GSettings *settings) { gboolean max = g_settings_get_boolean (settings, key); g_return_if_fail (PHOC_IS_DESKTOP (self)); g_return_if_fail (G_IS_SETTINGS (settings)); phoc_desktop_set_auto_maximize (self, max); } static void scale_to_fit_changed_cb (PhocDesktop *self, const gchar *key, GSettings *settings) { gboolean max = g_settings_get_boolean (settings, key); g_return_if_fail (PHOC_IS_DESKTOP (self)); g_return_if_fail (G_IS_SETTINGS (settings)); phoc_desktop_set_scale_to_fit (self, max); } #ifdef PHOC_XWAYLAND static const char *atom_map[XWAYLAND_ATOM_LAST] = { "_NET_WM_WINDOW_TYPE_NORMAL", "_NET_WM_WINDOW_TYPE_DIALOG" }; static void handle_xwayland_ready(struct wl_listener *listener, void *data) { PhocDesktop *desktop = wl_container_of ( listener, desktop, xwayland_ready); xcb_connection_t *xcb_conn = xcb_connect (NULL, NULL); int err = xcb_connection_has_error (xcb_conn); if (err) { g_warning ("XCB connect failed: %d", err); return; } xcb_intern_atom_cookie_t cookies[XWAYLAND_ATOM_LAST]; for (size_t i = 0; i < XWAYLAND_ATOM_LAST; i++) cookies[i] = xcb_intern_atom (xcb_conn, 0, strlen (atom_map[i]), atom_map[i]); for (size_t i = 0; i < XWAYLAND_ATOM_LAST; i++) { xcb_generic_error_t *error = NULL; xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply (xcb_conn, cookies[i], &error); if (error) { g_warning ("could not resolve atom %s, X11 error code %d", atom_map[i], error->error_code); free (error); } if (reply) desktop->xwayland_atoms[i] = reply->atom; free (reply); } xcb_disconnect (xcb_conn); } #ifdef PHOC_HAVE_WLR_REMOVE_STARTUP_INFO static void handle_xwayland_remove_startup_id(struct wl_listener *listener, void *data) { PhocDesktop *desktop = wl_container_of ( listener, desktop, xwayland_remove_startup_id); struct wlr_xwayland_remove_startup_info_event *ev = data; g_assert (PHOC_IS_DESKTOP (desktop)); g_assert (ev->id); phoc_phosh_private_notify_startup_id (desktop->phosh, ev->id, PHOSH_PRIVATE_STARTUP_TRACKER_PROTOCOL_X11); } #endif /* PHOC_HAVE_WLR_REMOVE_STARTUP_INFO */ #endif /* PHOC_XWAYLAND */ static void handle_output_destroy (PhocOutput *destroyed_output) { PhocDesktop *self = destroyed_output->desktop; PhocOutput *output; char *input_name; GHashTableIter iter; g_hash_table_iter_init (&iter, self->input_output_map); while (g_hash_table_iter_next (&iter, (gpointer) &input_name, (gpointer) &output)){ if (destroyed_output == output){ g_debug ("Removing mapping for input device '%s' to output '%s'", input_name, output->wlr_output->name); g_hash_table_remove (self->input_output_map, input_name); break; } } g_object_unref (destroyed_output); } static void handle_new_output (struct wl_listener *listener, void *data) { PhocDesktop *self = wl_container_of (listener, self, new_output); PhocOutput *output = phoc_output_new (self, (struct wlr_output *)data); g_signal_connect (output, "output-destroyed", G_CALLBACK (handle_output_destroy), NULL); } static void phoc_desktop_setup_xwayland (PhocDesktop *self) { #ifdef PHOC_XWAYLAND const char *cursor_default = PHOC_XCURSOR_DEFAULT; PhocConfig *config = self->config; PhocServer *server = phoc_server_get_default (); self->xcursor_manager = wlr_xcursor_manager_create(NULL, PHOC_XCURSOR_SIZE); g_return_if_fail (self->xcursor_manager); if (config->xwayland) { self->xwayland = wlr_xwayland_create(server->wl_display, server->compositor, config->xwayland_lazy); if (!self->xwayland) { g_critical ("Failed to initialize Xwayland"); g_unsetenv ("DISPLAY"); return; } wl_signal_add(&self->xwayland->events.new_surface, &self->xwayland_surface); self->xwayland_surface.notify = handle_xwayland_surface; wl_signal_add(&self->xwayland->events.ready, &self->xwayland_ready); self->xwayland_ready.notify = handle_xwayland_ready; #ifdef PHOC_HAVE_WLR_REMOVE_STARTUP_INFO wl_signal_add(&self->xwayland->events.remove_startup_info, &self->xwayland_remove_startup_id); self->xwayland_remove_startup_id.notify = handle_xwayland_remove_startup_id; #endif g_setenv ("DISPLAY", self->xwayland->display_name, true); if (!wlr_xcursor_manager_load(self->xcursor_manager, 1)) g_critical ("Cannot load XWayland XCursor theme"); struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor( self->xcursor_manager, cursor_default, 1); if (xcursor != NULL) { struct wlr_xcursor_image *image = xcursor->images[0]; wlr_xwayland_set_cursor(self->xwayland, image->buffer, image->width * 4, image->width, image->height, image->hotspot_x, image->hotspot_y); } } #endif } static void phoc_desktop_constructed (GObject *object) { PhocDesktop *self = PHOC_DESKTOP (object); PhocServer *server = phoc_server_get_default (); G_OBJECT_CLASS (phoc_desktop_parent_class)->constructed (object); wl_list_init(&self->views); wl_list_init(&self->outputs); self->new_output.notify = handle_new_output; wl_signal_add(&server->backend->events.new_output, &self->new_output); self->layout = wlr_output_layout_create(); wlr_xdg_output_manager_v1_create(server->wl_display, self->layout); self->layout_change.notify = handle_layout_change; wl_signal_add(&self->layout->events.change, &self->layout_change); self->xdg_shell = wlr_xdg_shell_create(server->wl_display); wl_signal_add(&self->xdg_shell->events.new_surface, &self->xdg_shell_surface); self->xdg_shell_surface.notify = handle_xdg_shell_surface; self->layer_shell = wlr_layer_shell_v1_create(server->wl_display); wl_signal_add(&self->layer_shell->events.new_surface, &self->layer_shell_surface); self->layer_shell_surface.notify = handle_layer_shell_surface; self->tablet_v2 = wlr_tablet_v2_create(server->wl_display); char cursor_size_fmt[16]; snprintf(cursor_size_fmt, sizeof(cursor_size_fmt), "%d", PHOC_XCURSOR_SIZE); g_setenv("XCURSOR_SIZE", cursor_size_fmt, 1); phoc_desktop_setup_xwayland (self); self->gamma_control_manager_v1 = wlr_gamma_control_manager_v1_create(server->wl_display); self->export_dmabuf_manager_v1 = wlr_export_dmabuf_manager_v1_create(server->wl_display); self->server_decoration_manager = wlr_server_decoration_manager_create(server->wl_display); wlr_server_decoration_manager_set_default_mode(self->server_decoration_manager, WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT); self->idle = wlr_idle_create(server->wl_display); self->primary_selection_device_manager = wlr_primary_selection_v1_device_manager_create(server->wl_display); self->input_inhibit = wlr_input_inhibit_manager_create(server->wl_display); self->input_inhibit_activate.notify = input_inhibit_activate; wl_signal_add(&self->input_inhibit->events.activate, &self->input_inhibit_activate); self->input_inhibit_deactivate.notify = input_inhibit_deactivate; wl_signal_add(&self->input_inhibit->events.deactivate, &self->input_inhibit_deactivate); self->input_method = wlr_input_method_manager_v2_create(server->wl_display); self->text_input = wlr_text_input_manager_v3_create(server->wl_display); self->gtk_shell = phoc_gtk_shell_create(self, server->wl_display); self->phosh = phoc_phosh_private_new (); self->xdg_activation_v1 = wlr_xdg_activation_v1_create (server->wl_display); self->xdg_activation_v1_request_activate.notify = phoc_xdg_activation_v1_handle_request_activate; wl_signal_add (&self->xdg_activation_v1->events.request_activate, &self->xdg_activation_v1_request_activate); self->virtual_keyboard = wlr_virtual_keyboard_manager_v1_create( server->wl_display); wl_signal_add(&self->virtual_keyboard->events.new_virtual_keyboard, &self->virtual_keyboard_new); self->virtual_keyboard_new.notify = phoc_handle_virtual_keyboard; self->virtual_pointer = wlr_virtual_pointer_manager_v1_create(server->wl_display); wl_signal_add(&self->virtual_pointer->events.new_virtual_pointer, &self->virtual_pointer_new); self->virtual_pointer_new.notify = phoc_handle_virtual_pointer; self->screencopy = wlr_screencopy_manager_v1_create(server->wl_display); self->xdg_decoration_manager = wlr_xdg_decoration_manager_v1_create(server->wl_display); wl_signal_add(&self->xdg_decoration_manager->events.new_toplevel_decoration, &self->xdg_toplevel_decoration); self->xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration; wlr_viewporter_create(server->wl_display); struct wlr_xdg_foreign_registry *foreign_registry = wlr_xdg_foreign_registry_create(server->wl_display); wlr_xdg_foreign_v1_create(server->wl_display, foreign_registry); wlr_xdg_foreign_v2_create(server->wl_display, foreign_registry); self->pointer_constraints = wlr_pointer_constraints_v1_create(server->wl_display); self->pointer_constraint.notify = handle_pointer_constraint; wl_signal_add(&self->pointer_constraints->events.new_constraint, &self->pointer_constraint); self->presentation = wlr_presentation_create(server->wl_display, server->backend); self->foreign_toplevel_manager_v1 = wlr_foreign_toplevel_manager_v1_create(server->wl_display); self->relative_pointer_manager = wlr_relative_pointer_manager_v1_create(server->wl_display); self->pointer_gestures = wlr_pointer_gestures_v1_create(server->wl_display); self->output_manager_v1 = wlr_output_manager_v1_create(server->wl_display); self->output_manager_apply.notify = handle_output_manager_apply; wl_signal_add(&self->output_manager_v1->events.apply, &self->output_manager_apply); self->output_manager_test.notify = handle_output_manager_test; wl_signal_add(&self->output_manager_v1->events.test, &self->output_manager_test); self->output_power_manager_v1 = wlr_output_power_manager_v1_create(server->wl_display); self->output_power_manager_set_mode.notify = phoc_output_handle_output_power_manager_set_mode; wl_signal_add(&self->output_power_manager_v1->events.set_mode, &self->output_power_manager_set_mode); wlr_data_control_manager_v1_create(server->wl_display); self->settings = g_settings_new ("sm.puri.phoc"); g_signal_connect_swapped(self->settings, "changed::auto-maximize", G_CALLBACK (auto_maximize_changed_cb), self); auto_maximize_changed_cb(self, "auto-maximize", self->settings); g_signal_connect_swapped(self->settings, "changed::scale-to-fit", G_CALLBACK (scale_to_fit_changed_cb), self); scale_to_fit_changed_cb(self, "scale-to-fit", self->settings); } static void phoc_desktop_finalize (GObject *object) { PhocDesktop *self = PHOC_DESKTOP (object); /* TODO: currently destroys the backend before the desktop */ //wl_list_remove (&self->new_output.link); wl_list_remove (&self->layout_change.link); wl_list_remove (&self->xdg_shell_surface.link); wl_list_remove (&self->layer_shell_surface.link); wl_list_remove (&self->xdg_toplevel_decoration.link); wl_list_remove (&self->input_inhibit_activate.link); wl_list_remove (&self->input_inhibit_deactivate.link); wl_list_remove (&self->virtual_keyboard_new.link); wl_list_remove (&self->virtual_pointer_new.link); wl_list_remove (&self->pointer_constraint.link); wl_list_remove (&self->output_manager_apply.link); wl_list_remove (&self->output_manager_test.link); wl_list_remove (&self->output_power_manager_set_mode.link); wl_list_remove (&self->xdg_activation_v1_request_activate.link); #ifdef PHOC_XWAYLAND /* Disconnect XWayland listener before shutting it down */ if (self->xwayland) { wl_list_remove (&self->xwayland_surface.link); wl_list_remove (&self->xwayland_ready.link); #ifdef PHOC_HAVE_WLR_REMOVE_STARTUP_INFO wl_list_remove (&self->xwayland_remove_startup_id.link); #endif } g_clear_pointer (&self->xcursor_manager, wlr_xcursor_manager_destroy); // We need to shutdown Xwayland before disconnecting all clients, otherwise // wlroots will restart it automatically. g_clear_pointer (&self->xwayland, wlr_xwayland_destroy); #endif g_clear_object (&self->phosh); g_clear_pointer (&self->gtk_shell, phoc_gtk_shell_destroy); g_clear_pointer (&self->layout, wlr_output_layout_destroy); g_hash_table_remove_all (self->input_output_map); g_hash_table_unref (self->input_output_map); G_OBJECT_CLASS (phoc_desktop_parent_class)->finalize (object); } static void phoc_desktop_class_init (PhocDesktopClass *klass) { GObjectClass *object_class = (GObjectClass *)klass; object_class->set_property = phoc_desktop_set_property; object_class->get_property = phoc_desktop_get_property; object_class->constructed = phoc_desktop_constructed; object_class->finalize = phoc_desktop_finalize; props[PROP_CONFIG] = g_param_spec_pointer ( "config", "Config", "The config object", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } static void phoc_desktop_init (PhocDesktop *self) { self->input_output_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); } PhocDesktop * phoc_desktop_new (PhocConfig *config) { return g_object_new (PHOC_TYPE_DESKTOP, "config", config, NULL); } /** * phoc_desktop_toggle_output_blank: * * Blank or unblank all outputs depending on the current state. * Brings all outputs into consistent state (either all blanked * or all enabled). */ void phoc_desktop_toggle_output_blank (PhocDesktop *self) { PhocOutput *output; gboolean enable = true; wl_list_for_each(output, &self->outputs, link) { if (output->wlr_output->enabled) { enable = false; break; } } wl_list_for_each(output, &self->outputs, link) { if (!wlr_output_layout_get (self->layout, output->wlr_output)) continue; wlr_output_enable (output->wlr_output, enable); wlr_output_commit (output->wlr_output); if (enable) phoc_output_damage_whole(output); } } /** * phoc_desktop_set_auto_maximize: * * Turn auto maximization of toplevels on (%TRUE) or off (%FALSE) */ void phoc_desktop_set_auto_maximize (PhocDesktop *self, gboolean enable) { PhocView *view; PhocServer *server = phoc_server_get_default(); if (G_UNLIKELY (server->debug_flags & PHOC_SERVER_DEBUG_FLAG_AUTO_MAXIMIZE)) { if (enable == FALSE) g_info ("Not disabling auto-maximize due to `auto-maximize` debug flag"); enable = TRUE; } g_debug ("auto-maximize: %d", enable); self->maximize = enable; /* Disabling auto-maximize leaves all views in their current position */ if (!enable) { wl_list_for_each (view, &self->views, link) view_appear_activated (view, phoc_input_view_has_focus (phoc_server_get_default()->input, view)); return; } wl_list_for_each (view, &self->views, link) { view_auto_maximize (view); view_appear_activated (view, true); } } gboolean phoc_desktop_get_auto_maximize (PhocDesktop *self) { return self->maximize; } /** * phoc_desktop_set_scale_to_fit: * * Turn auto scaling of all oversized toplevels on (%TRUE) or off (%FALSE) */ void phoc_desktop_set_scale_to_fit (PhocDesktop *self, gboolean enable) { g_debug ("scale to fit: %d", enable); self->scale_to_fit = enable; } gboolean phoc_desktop_get_scale_to_fit (PhocDesktop *self) { return self->scale_to_fit; } /** * phoc_desktop_find_output: * @self: The desktop * @make: The output's make / vendor * @model: The output's model / product * @serial: The output's serial number * * Find an output by make, model and serial * * Returns: (transfer none) (nullable): The matching output or * %NULL if no output matches. */ PhocOutput * phoc_desktop_find_output (PhocDesktop *self, const char *make, const char *model, const char *serial) { PhocOutput *output; g_assert (PHOC_IS_DESKTOP (self)); wl_list_for_each (output, &self->outputs, link) { if (phoc_output_is_match (output, make, model, serial)) return output; } return NULL; } /** * phoc_desktop_get_builtin_output: * * Get the built-in output. This assumes there's only one * and returns the first. * * Returns: (transfer none) (nullable): The built-in output. * %NULL if there's no built-in output. */ PhocOutput * phoc_desktop_get_builtin_output (PhocDesktop *self) { PhocOutput *output; g_assert (PHOC_IS_DESKTOP (self)); wl_list_for_each (output, &self->outputs, link) { if (phoc_output_is_builtin (output)) return output; } return NULL; } phoc-v0.13.1/src/desktop.h000066400000000000000000000116511422111650000153200ustar00rootroot00000000000000#pragma once #include "config.h" #include "gtk-shell.h" #include "phosh-private.h" #include "view.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "settings.h" #ifdef PHOC_XWAYLAND #include "xwayland.h" #endif #define PHOC_TYPE_DESKTOP (phoc_desktop_get_type()) G_DECLARE_FINAL_TYPE (PhocDesktop, phoc_desktop, PHOC, DESKTOP, GObject); struct _PhocDesktop { GObject parent; struct wl_list views; // PhocView::link struct wl_list outputs; // PhocOutput::link PhocConfig *config; struct wlr_output_layout *layout; struct wlr_xdg_shell *xdg_shell; struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1; struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager_v1; struct wlr_server_decoration_manager *server_decoration_manager; struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager; struct wlr_primary_selection_v1_device_manager *primary_selection_device_manager; struct wlr_idle *idle; struct wlr_input_inhibit_manager *input_inhibit; struct wlr_layer_shell_v1 *layer_shell; struct wlr_input_method_manager_v2 *input_method; struct wlr_text_input_manager_v3 *text_input; struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; struct wlr_virtual_pointer_manager_v1 *virtual_pointer; struct wlr_screencopy_manager_v1 *screencopy; struct wlr_tablet_manager_v2 *tablet_v2; struct wlr_pointer_constraints_v1 *pointer_constraints; struct wlr_presentation *presentation; struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager_v1; struct wlr_relative_pointer_manager_v1 *relative_pointer_manager; struct wlr_pointer_gestures_v1 *pointer_gestures; struct wlr_output_manager_v1 *output_manager_v1; struct wlr_output_power_manager_v1 *output_power_manager_v1; struct wlr_xdg_activation_v1 *xdg_activation_v1; struct wl_listener new_output; struct wl_listener layout_change; struct wl_listener xdg_shell_surface; struct wl_listener layer_shell_surface; struct wl_listener xdg_toplevel_decoration; struct wl_listener input_inhibit_activate; struct wl_listener input_inhibit_deactivate; struct wl_listener virtual_keyboard_new; struct wl_listener virtual_pointer_new; struct wl_listener pointer_constraint; struct wl_listener output_manager_apply; struct wl_listener output_manager_test; struct wl_listener output_power_manager_set_mode; struct wl_listener xdg_activation_v1_request_activate; #ifdef PHOC_XWAYLAND struct wlr_xcursor_manager *xcursor_manager; struct wlr_xwayland *xwayland; struct wl_listener xwayland_surface; struct wl_listener xwayland_ready; struct wl_listener xwayland_remove_startup_id; xcb_atom_t xwayland_atoms[XWAYLAND_ATOM_LAST]; #endif GSettings *settings; gboolean maximize, scale_to_fit; GHashTable *input_output_map; /* Protocols without upstreamable implementations */ PhocPhoshPrivate *phosh; PhocGtkShell *gtk_shell; }; PhocDesktop *phoc_desktop_new (PhocConfig *config); void phoc_desktop_toggle_output_blank (PhocDesktop *self); void phoc_desktop_set_auto_maximize (PhocDesktop *self, gboolean on); gboolean phoc_desktop_get_auto_maximize (PhocDesktop *self); void phoc_desktop_set_scale_to_fit (PhocDesktop *self, gboolean on); gboolean phoc_desktop_get_scale_to_fit (PhocDesktop *self); PhocOutput *phoc_desktop_find_output (PhocDesktop *self, const char *make, const char *model, const char *serial); PhocOutput *phoc_desktop_get_builtin_output (PhocDesktop *self); struct wlr_surface *phoc_desktop_surface_at(PhocDesktop *desktop, double lx, double ly, double *sx, double *sy, PhocView **view); gboolean phoc_desktop_view_is_visible (PhocDesktop *desktop, PhocView *view); void handle_xdg_shell_surface(struct wl_listener *listener, void *data); void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data); void handle_layer_shell_surface(struct wl_listener *listener, void *data); void handle_xwayland_surface(struct wl_listener *listener, void *data); phoc-v0.13.1/src/gtk-shell.c000066400000000000000000000225011422111650000155300ustar00rootroot00000000000000/* * Copyright (C) 2020 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #define G_LOG_DOMAIN "phoc-gtk-shell" #include "config.h" #include "server.h" #include "cursor.h" #include "desktop.h" #include "input.h" #include "phosh-private.h" #include "gtk-shell.h" #include #include /** * PhocGtkShell: * * A minimal implementeation of gtk_shell1 protocol * * Implement just enough to raise windows for GTK based applications * until there's an agreed on upstream protocol. */ static void handle_set_dbus_properties(struct wl_client *client, struct wl_resource *resource, const char *application_id, const char *app_menu_path, const char *menubar_path, const char *window_object_path, const char *application_object_path, const char *unique_bus_name) { PhocGtkSurface *gtk_surface = gtk_surface_from_resource (resource); PhocView *view; g_debug ("Setting app-id %s for surface %p (res %p)", application_id, gtk_surface->wlr_surface, resource); if (!gtk_surface->wlr_surface) return; g_free (gtk_surface->app_id); gtk_surface->app_id = g_strdup (application_id); view = phoc_view_from_wlr_surface (gtk_surface->wlr_surface); if (view) view_set_app_id (view, application_id); } static void handle_set_modal(struct wl_client *client, struct wl_resource *resource) { g_debug ("%s not implemented", __func__); } static void handle_unset_modal(struct wl_client *client, struct wl_resource *resource) { g_debug ("%s not implemented", __func__); } static void handle_present(struct wl_client *client, struct wl_resource *resource, uint32_t time) { g_debug ("%s not implemented", __func__); } static void handle_request_focus(struct wl_client *client, struct wl_resource *resource, const char *startup_id) { PhocGtkSurface *gtk_surface = gtk_surface_from_resource (resource); PhocServer *server = phoc_server_get_default (); PhocInput *input = server->input; PhocSeat *seat = phoc_input_get_last_active_seat (input); PhocView *view; g_debug ("Requesting focus for surface %p (res %p)", gtk_surface->wlr_surface, resource); if (!gtk_surface->wlr_surface) return; view = phoc_view_from_wlr_surface (gtk_surface->wlr_surface); if (view) phoc_seat_set_focus(seat, view); } static const struct gtk_surface1_interface gtk_surface1_impl = { handle_set_dbus_properties, handle_set_modal, handle_unset_modal, handle_present, handle_request_focus, }; static void gtk_surface_handle_resource_destroy(struct wl_resource *resource) { PhocGtkSurface *gtk_surface = gtk_surface_from_resource(resource); g_debug ("Destroying gtk_surface %p (res %p)", gtk_surface, gtk_surface->resource); if (gtk_surface->wlr_surface) { wl_list_remove(>k_surface->wlr_surface_handle_destroy.link); gtk_surface->wlr_surface = NULL; } gtk_surface->gtk_shell->surfaces = g_slist_remove (gtk_surface->gtk_shell->surfaces, gtk_surface); g_free (gtk_surface->app_id); g_free (gtk_surface); } static void handle_wlr_surface_handle_destroy(struct wl_listener *listener, void *data) { PhocGtkSurface *gtk_surface = wl_container_of(listener, gtk_surface, wlr_surface_handle_destroy); /* Make sure we don't try to raise an already gone surface */ gtk_surface->wlr_surface = NULL; } static void handle_get_gtk_surface(struct wl_client *client, struct wl_resource *gtk_shell_resource, uint32_t id, struct wl_resource *surface_resource) { struct wlr_surface *wlr_surface = wlr_surface_from_resource (surface_resource); PhocGtkSurface *gtk_surface; gtk_surface = g_new0 (PhocGtkSurface, 1); if (gtk_surface == NULL) { wl_client_post_no_memory (client); return; } int version = wl_resource_get_version(gtk_shell_resource); gtk_surface->gtk_shell = phoc_gtk_shell_from_resource (gtk_shell_resource); gtk_surface->resource = wl_resource_create(client, >k_surface1_interface, version, id); if (gtk_surface->resource == NULL) { g_free (gtk_surface); wl_client_post_no_memory(client); return; } g_debug ("New gtk_surface_surface %p (res %p)", gtk_surface, gtk_surface->resource); wl_resource_set_implementation(gtk_surface->resource, >k_surface1_impl, gtk_surface, gtk_surface_handle_resource_destroy); gtk_surface->wlr_surface = wlr_surface; gtk_surface->wlr_surface_handle_destroy.notify = handle_wlr_surface_handle_destroy; wl_signal_add(&wlr_surface->events.destroy, >k_surface->wlr_surface_handle_destroy); /* Send empty configure */ struct wl_array states; wl_array_init (&states); gtk_surface1_send_configure (gtk_surface->resource, &states); wl_array_release (&states); wl_signal_init(>k_surface->events.destroy); gtk_surface->gtk_shell->surfaces = g_slist_prepend (gtk_surface->gtk_shell->surfaces, gtk_surface); } static void handle_set_startup_id(struct wl_client *client, struct wl_resource *resource, const char *startup_id) { PhocServer *server = phoc_server_get_default (); g_debug ("%s: %s", __func__, startup_id); if (startup_id) { phoc_phosh_private_notify_startup_id (server->desktop->phosh, startup_id, PHOSH_PRIVATE_STARTUP_TRACKER_PROTOCOL_GTK_SHELL); } } static void handle_system_bell(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface) { g_debug ("%s not implemented", __func__); } static void handle_notify_launch(struct wl_client *client, struct wl_resource *resource, const char *startup_id) { PhocServer *server = phoc_server_get_default (); g_debug ("%s: %s", __func__, startup_id); if (startup_id) { #ifdef PHOC_HAVE_WLR_XDG_ACTIVATION_V1_ADD_TOKEN wlr_xdg_activation_v1_add_token (server->desktop->xdg_activation_v1, startup_id); #endif phoc_phosh_private_notify_launch (server->desktop->phosh, startup_id, PHOSH_PRIVATE_STARTUP_TRACKER_PROTOCOL_GTK_SHELL); } } static void gtk_shell1_handle_resource_destroy(struct wl_resource *resource) { PhocGtkShell *gtk_shell = phoc_gtk_shell_from_resource (resource); g_debug ("Destroying gtk_shell %p (res %p)", gtk_shell, resource); gtk_shell->resources = g_slist_remove (gtk_shell->resources, resource); } static const struct gtk_shell1_interface gtk_shell1_impl = { handle_get_gtk_surface, handle_set_startup_id, handle_system_bell, handle_notify_launch, }; static void gtk_shell_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { PhocGtkShell *gtk_shell = data; struct wl_resource *resource = wl_resource_create(client, >k_shell1_interface, version, id); wl_resource_set_implementation(resource, >k_shell1_impl, gtk_shell, gtk_shell1_handle_resource_destroy); gtk_shell->resources = g_slist_prepend (gtk_shell->resources, resource); gtk_shell1_send_capabilities (resource, 0); return; } PhocGtkShell* phoc_gtk_shell_create(PhocDesktop *desktop, struct wl_display *display) { PhocGtkShell *gtk_shell = g_new0 (PhocGtkShell, 1); if (!gtk_shell) return NULL; g_info ("Initializing gtk-shell interface"); gtk_shell->global = wl_global_create(display, >k_shell1_interface, 3, gtk_shell, gtk_shell_bind); if (!gtk_shell->global) return NULL; return gtk_shell; } void phoc_gtk_shell_destroy (PhocGtkShell *gtk_shell) { g_clear_pointer (>k_shell->resources, g_slist_free); g_clear_pointer (>k_shell->surfaces, g_slist_free); wl_global_destroy(gtk_shell->global); g_free (gtk_shell); } PhocGtkSurface * phoc_gtk_shell_get_gtk_surface_from_wlr_surface (PhocGtkShell *self, struct wlr_surface *wlr_surface) { GSList *item = self->surfaces; while (item) { PhocGtkSurface *surface = item->data; if (surface->wlr_surface == wlr_surface) return surface; item = item->next; } return NULL; } PhocGtkShell * phoc_gtk_shell_from_resource (struct wl_resource *resource) { g_assert(wl_resource_instance_of(resource, >k_shell1_interface, >k_shell1_impl)); return wl_resource_get_user_data(resource); } PhocGtkSurface * gtk_surface_from_resource (struct wl_resource *resource) { g_assert(wl_resource_instance_of (resource, >k_surface1_interface, >k_surface1_impl)); return wl_resource_get_user_data (resource); } phoc-v0.13.1/src/gtk-shell.h000066400000000000000000000017021422111650000155350ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #pragma once G_BEGIN_DECLS typedef struct _PhocGtkShell { struct wl_global *global; GSList *resources; GSList *surfaces; } PhocGtkShell; typedef struct PhocGtkSurface { struct wl_resource *resource; struct wlr_surface *wlr_surface; PhocGtkShell *gtk_shell; char *app_id; struct wl_listener wlr_surface_handle_destroy; struct { struct wl_signal destroy; } events; } PhocGtkSurface; PhocGtkShell *phoc_gtk_shell_create(PhocDesktop *desktop, struct wl_display *display); void phoc_gtk_shell_destroy(PhocGtkShell *gtk_shell); PhocGtkSurface *phoc_gtk_shell_get_gtk_surface_from_wlr_surface (PhocGtkShell *self, struct wlr_surface *wlr_surface); PhocGtkShell *phoc_gtk_shell_from_resource(struct wl_resource *resource); PhocGtkSurface *gtk_surface_from_resource(struct wl_resource *resource); G_END_DECLS phoc-v0.13.1/src/input-device.c000066400000000000000000000221711422111650000162350ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later * * Author: Guido Günther */ #define G_LOG_DOMAIN "phoc-input-device" #include "config.h" #include "input-device.h" #include "seat.h" #include "utils.h" #include #include #define PHOC_INPUT_DEVICE_SELF(p) PHOC_PRIV_CONTAINER(PHOC_INPUT_DEVICE, PhocInputDevice, (p)) enum { PROP_0, PROP_SEAT, PROP_DEVICE, PROP_LAST_PROP }; static GParamSpec *props[PROP_LAST_PROP]; enum { DEVICE_DESTROY, N_SIGNALS }; static guint signals [N_SIGNALS]; /** * PhocInputDevice: * * Abstract base class for input device like pointers or touch. */ typedef struct _PhocInputDevicePrivate { struct wlr_input_device *device; PhocSeat *seat; char *vendor; char *product; struct wl_listener device_destroy; } PhocInputDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE (PhocInputDevice, phoc_input_device, G_TYPE_OBJECT) static void phoc_input_device_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PhocInputDevice *self = PHOC_INPUT_DEVICE (object); PhocInputDevicePrivate *priv = phoc_input_device_get_instance_private (self); switch (property_id) { case PROP_DEVICE: priv->device = g_value_get_pointer (value); priv->device->data = self; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DEVICE]); break; case PROP_SEAT: priv->seat = g_value_dup_object (value); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SEAT]); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void phoc_input_device_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PhocInputDevice *self = PHOC_INPUT_DEVICE (object); PhocInputDevicePrivate *priv = phoc_input_device_get_instance_private (self); switch (property_id) { case PROP_DEVICE: g_value_set_pointer (value, priv->device); break; case PROP_SEAT: g_value_set_pointer (value, priv->seat); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void handle_device_destroy (struct wl_listener *listener, void *data) { PhocInputDevicePrivate *priv = wl_container_of (listener, priv, device_destroy); PhocInputDevice *self = PHOC_INPUT_DEVICE_SELF (priv); g_assert (PHOC_IS_INPUT_DEVICE (self)); g_debug ("Device destroy %p", self); /* Prevent further signal emission */ wl_list_remove (&priv->device_destroy.link); g_signal_emit (self, signals[DEVICE_DESTROY], 0); } static void phoc_input_device_dispose (GObject *object) { PhocInputDevice *self = PHOC_INPUT_DEVICE(object); PhocInputDevicePrivate *priv = phoc_input_device_get_instance_private (self); g_clear_object (&priv->seat); g_clear_pointer (&priv->vendor, g_free); g_clear_pointer (&priv->product, g_free); G_OBJECT_CLASS (phoc_input_device_parent_class)->dispose (object); } static void phoc_input_device_constructed (GObject *object) { PhocInputDevicePrivate *priv; PhocInputDevice *self = PHOC_INPUT_DEVICE (object); G_OBJECT_CLASS (phoc_input_device_parent_class)->constructed (object); priv = phoc_input_device_get_instance_private (self); if (priv->device) { priv->device_destroy.notify = handle_device_destroy; wl_signal_add (&priv->device->events.destroy, &priv->device_destroy); priv->vendor = g_strdup_printf ("%.4x", priv->device->vendor); priv->product = g_strdup_printf ("%.4x", priv->device->product); } } static void phoc_input_device_class_init (PhocInputDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = phoc_input_device_constructed; object_class->get_property = phoc_input_device_get_property; object_class->set_property = phoc_input_device_set_property; object_class->dispose = phoc_input_device_dispose; /** * PhocInputDevice:device: * * The underlying wlroots device */ props[PROP_DEVICE] = g_param_spec_pointer ("device", "", "", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * PhocInputDevice:seat: * * The seat this device belongs to */ props[PROP_SEAT] = g_param_spec_object ("seat", "", "", PHOC_TYPE_SEAT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); /** * PhocInputDevice::device-destroy: * * The underlying wlr input device is about to be destroyed */ signals[DEVICE_DESTROY] = g_signal_new ("device-destroy", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PhocInputDeviceClass, device_destroy), NULL, NULL, NULL, G_TYPE_NONE, 0); } static void phoc_input_device_init (PhocInputDevice *self) { } /** * phoc_input_device_get_seat: * @self: The %PhocInputDevice * * Returns: (transfer none): The seat this input device belongs to. */ PhocSeat * phoc_input_device_get_seat (PhocInputDevice *self) { PhocInputDevicePrivate *priv; g_assert (PHOC_IS_INPUT_DEVICE (self)); priv = phoc_input_device_get_instance_private (self); return priv->seat; } /** * phoc_input_device_get_device: * @self: The %PhocInputDevice * * Returns: (transfer none): The wlr_input_device. Note that * %PhocInputDevice device owns this so don't keep references around. */ struct wlr_input_device * phoc_input_device_get_device (PhocInputDevice *self) { PhocInputDevicePrivate *priv; g_assert (PHOC_IS_INPUT_DEVICE (self)); priv = phoc_input_device_get_instance_private (self); return priv->device; } /** * phoc_input_device_get_is_touchpad: * @self: The %PhocInputDevice * * Returns: %TRUE if this is a touchpad */ gboolean phoc_input_device_get_is_touchpad (PhocInputDevice *self) { struct libinput_device *ldev; PhocInputDevicePrivate *priv; g_assert (PHOC_IS_INPUT_DEVICE (self)); priv = phoc_input_device_get_instance_private (self); if (!wlr_input_device_is_libinput (priv->device)) return FALSE; ldev = phoc_input_device_get_libinput_device_handle (self); if (libinput_device_config_tap_get_finger_count (ldev) == 0) return FALSE; g_debug ("%s is a touchpad device", libinput_device_get_name (ldev)); return TRUE; } /** * phoc_input_device_get_is_libinput: * @self: The %PhocInputDevice * * Returns: %TRUE if the device is driven by libinput */ gboolean phoc_input_device_get_is_libinput (PhocInputDevice *self) { PhocInputDevicePrivate *priv; g_assert (PHOC_IS_INPUT_DEVICE (self)); priv = phoc_input_device_get_instance_private (self); return wlr_input_device_is_libinput (priv->device); } /** * phoc_input_device_get_libinput_device_handle: * @self: The %PhocInputDevice * * Returns: (nullable): The libinput device */ struct libinput_device * phoc_input_device_get_libinput_device_handle (PhocInputDevice *self) { PhocInputDevicePrivate *priv; g_assert (PHOC_IS_INPUT_DEVICE (self)); priv = phoc_input_device_get_instance_private (self); return wlr_libinput_get_device_handle (priv->device); } /** * phoc_input_get_name * @self: The %PhocInputDevice * * Returns: (nullable): The input device name */ const char * phoc_input_device_get_name (PhocInputDevice *self) { PhocInputDevicePrivate *priv; g_assert (PHOC_IS_INPUT_DEVICE (self)); priv = phoc_input_device_get_instance_private (self); return priv->device->name; } /** * phoc_input_device_get_device_type: * @self: The %PhocInputDevice * * Returns: The wlr type of the input device */ enum wlr_input_device_type phoc_input_device_get_device_type (PhocInputDevice *self) { PhocInputDevicePrivate *priv; g_assert (PHOC_IS_INPUT_DEVICE (self)); priv = phoc_input_device_get_instance_private (self); return priv->device->type; } /** * phoc_input_device_get_vendor_id: * @self: The %PhocInputDevice * * Gets the vendor id as string. This is often represented by a hex * number corresponding to the usb vendor id. * * Returns: The vendor id */ const char * phoc_input_device_get_vendor_id (PhocInputDevice *self) { PhocInputDevicePrivate *priv; g_assert (PHOC_IS_INPUT_DEVICE (self)); priv = phoc_input_device_get_instance_private (self); return priv->vendor; } /** * phoc_input_device_get_product_id: * @self: The %PhocInputDevice * * Gets the product id as string. This is often represented by a hex * number corresponding to the usb product id. * * Returns: The vendor id */ const char * phoc_input_device_get_product_id (PhocInputDevice *self) { PhocInputDevicePrivate *priv; g_assert (PHOC_IS_INPUT_DEVICE (self)); priv = phoc_input_device_get_instance_private (self); return priv->product; } phoc-v0.13.1/src/input-device.h000066400000000000000000000026771422111650000162530ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "seat.h" #include #include G_BEGIN_DECLS #define PHOC_TYPE_INPUT_DEVICE (phoc_input_device_get_type ()) G_DECLARE_DERIVABLE_TYPE (PhocInputDevice, phoc_input_device, PHOC, INPUT_DEVICE, GObject) /** * PhocInputDeviceClass: * @parent_class: The parent class */ struct _PhocInputDeviceClass { GObjectClass parent_class; /* Signals */ void (*device_destroy) (PhocInputDevice *self); }; PhocSeat *phoc_input_device_get_seat (PhocInputDevice *self); struct wlr_input_device *phoc_input_device_get_device (PhocInputDevice *self); gboolean phoc_input_device_get_is_touchpad (PhocInputDevice *self); gboolean phoc_input_device_get_is_libinput (PhocInputDevice *self); struct libinput_device *phoc_input_device_get_libinput_device_handle (PhocInputDevice *self); const char *phoc_input_device_get_name (PhocInputDevice *self); enum wlr_input_device_type phoc_input_device_get_device_type (PhocInputDevice *self); const char *phoc_input_device_get_vendor_id (PhocInputDevice *self); const char *phoc_input_device_get_product_id (PhocInputDevice *self); G_END_DECLS phoc-v0.13.1/src/input.c000066400000000000000000000124661422111650000150060ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-input" #include "config.h" #define _POSIX_C_SOURCE 200112L #include #include #include #include #include "cursor.h" #include "input.h" #include "seat.h" #include "server.h" /** * PhocInput: * * PhocInput handles new input devices and seats */ struct _PhocInput { GObject parent; struct wl_listener new_input; GSList *seats; // PhocSeat }; G_DEFINE_TYPE (PhocInput, phoc_input, G_TYPE_OBJECT); const char * phoc_input_get_device_type (enum wlr_input_device_type type) { switch (type) { case WLR_INPUT_DEVICE_KEYBOARD: return "keyboard"; case WLR_INPUT_DEVICE_POINTER: return "pointer"; case WLR_INPUT_DEVICE_SWITCH: return "switch"; case WLR_INPUT_DEVICE_TOUCH: return "touch"; case WLR_INPUT_DEVICE_TABLET_TOOL: return "tablet tool"; case WLR_INPUT_DEVICE_TABLET_PAD: return "tablet pad"; default: return NULL; } } /** * phoc_input_get_seat: * @self: The input to look up the seat on * @name: The seats name * * Returns: (transfer none): The seat of the given name. */ PhocSeat * phoc_input_get_seat (PhocInput *self, char *name) { PhocSeat *seat = NULL; g_assert (PHOC_IS_INPUT (self)); for (GSList *elem = self->seats; elem; elem = elem->next) { seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); if (strcmp (seat->seat->name, name) == 0) { return seat; } } seat = phoc_seat_new (self, name); self->seats = g_slist_prepend (self->seats, seat); return seat; } static void handle_new_input (struct wl_listener *listener, void *data) { struct wlr_input_device *device = data; PhocInput *input = wl_container_of (listener, input, new_input); char *seat_name = PHOC_CONFIG_DEFAULT_SEAT_NAME; PhocSeat *seat = phoc_input_get_seat (input, seat_name); if (!seat) { g_warning ("could not create PhocSeat"); return; } g_debug ("New input device: %s (%d:%d) %s seat:%s", device->name, device->vendor, device->product, phoc_input_get_device_type (device->type), seat_name); phoc_seat_add_device (seat, device); } static void phoc_input_constructed (GObject *object) { PhocInput *self = PHOC_INPUT (object); PhocServer *server = phoc_server_get_default (); g_debug ("Initializing phoc input"); assert (server->desktop); self->new_input.notify = handle_new_input; wl_signal_add (&server->backend->events.new_input, &self->new_input); G_OBJECT_CLASS (phoc_input_parent_class)->constructed (object); } static void phoc_input_finalize (GObject *object) { PhocInput *self = PHOC_INPUT (object); g_clear_slist (&self->seats, g_object_unref); G_OBJECT_CLASS (phoc_input_parent_class)->finalize (object); } static void phoc_input_class_init (PhocInputClass *klass) { GObjectClass *object_class = (GObjectClass *)klass; object_class->constructed = phoc_input_constructed; object_class->finalize = phoc_input_finalize; } static void phoc_input_init (PhocInput *self) { } PhocInput * phoc_input_new (void) { return g_object_new (PHOC_TYPE_INPUT, NULL); } PhocSeat * phoc_input_seat_from_wlr_seat (PhocInput *self, struct wlr_seat *wlr_seat) { g_assert (PHOC_IS_INPUT (self)); for (GSList *elem = phoc_input_get_seats (self); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); if (seat->seat == wlr_seat) { return seat; } } return NULL; } bool phoc_input_view_has_focus (PhocInput *self, PhocView *view) { g_assert (PHOC_IS_INPUT (self)); if (!view) return false; for (GSList *elem = phoc_input_get_seats (self); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); if (view == phoc_seat_get_focus (seat)) { return true; } } return false; } static inline int64_t timespec_to_msec (const struct timespec *a) { return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; } void phoc_input_update_cursor_focus (PhocInput *self) { struct timespec now; g_assert (PHOC_IS_INPUT (self)); clock_gettime (CLOCK_MONOTONIC, &now); g_assert_nonnull (self); for (GSList *elem = phoc_input_get_seats (self); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); phoc_cursor_update_position (phoc_seat_get_cursor (seat), timespec_to_msec (&now)); } } /** * phoc_input_get_seats: * @self: The input * * Returns: (element-type PhocSeat) (transfer none): a list of seats associated with the input */ GSList * phoc_input_get_seats (PhocInput *self) { g_assert (PHOC_IS_INPUT (self)); return self->seats; } /** * phoc_input_get_last_active_seat: * @self: The input * * Returns: (nullable) (transfer none): The last active seat or %NULL */ PhocSeat * phoc_input_get_last_active_seat (PhocInput *self) { PhocSeat *seat = NULL; g_assert (PHOC_IS_INPUT (self)); for (GSList *elem = phoc_input_get_seats (self); elem; elem = elem->next) { PhocSeat *_seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (_seat)); if (!seat || (seat->seat->last_event.tv_sec > _seat->seat->last_event.tv_sec && seat->seat->last_event.tv_nsec > _seat->seat->last_event.tv_nsec)) { seat = _seat; } } return seat; } phoc-v0.13.1/src/input.h000066400000000000000000000020311422111650000147760ustar00rootroot00000000000000#pragma once #include #include #include #include #include "settings.h" #include "seat.h" #include "view.h" G_BEGIN_DECLS #define PHOC_TYPE_INPUT (phoc_input_get_type ()) G_DECLARE_FINAL_TYPE (PhocInput, phoc_input, PHOC, INPUT, GObject); PhocInput *phoc_input_new (void); bool phoc_input_view_has_focus (PhocInput *self, PhocView *view); const char *phoc_input_get_device_type (enum wlr_input_device_type type); PhocSeat *phoc_input_get_seat (PhocInput *self, char *name); void phoc_input_update_cursor_focus (PhocInput *self); PhocSeat *phoc_input_seat_from_wlr_seat (PhocInput *self, struct wlr_seat *seat); GSList * phoc_input_get_seats (PhocInput *self); PhocSeat *phoc_input_get_last_active_seat (PhocInput *self); G_END_DECLS phoc-v0.13.1/src/keybindings.c000066400000000000000000000400051422111650000161430ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-keybindings" /** * PhocKeybindings: * * Keybindings stored in gsettings */ #include "config.h" #include "keybindings.h" #include "seat.h" #include "keyboard.h" #include #include #define KEYBINDINGS_SCHEMA_ID "org.gnome.desktop.wm.keybindings" #define MUTTER_KEYBINDINGS_SCHEMA_ID "org.gnome.mutter.keybindings" typedef void (*PhocKeyHandlerFunc) (PhocSeat *); typedef struct { gchar *name; PhocKeyHandlerFunc func; GSList *combos; } PhocKeybinding; typedef struct _PhocKeybindings { GObject parent; GSList *bindings; GSettings *settings; GSettings *mutter_settings; } PhocKeybindings; G_DEFINE_TYPE (PhocKeybindings, phoc_keybindings, G_TYPE_OBJECT); static void handle_maximize (PhocSeat *seat) { PhocView *focus = phoc_seat_get_focus(seat); if (focus != NULL) view_maximize(focus, NULL); } static void handle_unmaximize (PhocSeat *seat) { PhocView *focus = phoc_seat_get_focus(seat); if (focus != NULL) view_restore(focus); } static void handle_tile_right (PhocSeat *seat) { PhocView *view = phoc_seat_get_focus(seat); if (!view) return; if (view_is_tiled (view) && view->tile_direction == PHOC_VIEW_TILE_RIGHT) view_restore (view); else view_tile (view, PHOC_VIEW_TILE_RIGHT, NULL); } static void handle_tile_left (PhocSeat *seat) { PhocView *view = phoc_seat_get_focus(seat); if (!view) return; if (view_is_tiled (view) && view->tile_direction == PHOC_VIEW_TILE_LEFT) view_restore (view); else view_tile (view, PHOC_VIEW_TILE_LEFT, NULL); } static void handle_toggle_maximized (PhocSeat *seat) { PhocView *focus = phoc_seat_get_focus(seat); if (focus != NULL) { if (view_is_maximized(focus)) view_restore(focus); else view_maximize(focus, NULL); } } static void handle_toggle_fullscreen (PhocSeat *seat) { PhocView *focus = phoc_seat_get_focus(seat); if (focus) { phoc_view_set_fullscreen(focus, !view_is_fullscreen (focus), NULL); } } static void handle_cycle_windows (PhocSeat *seat) { phoc_seat_cycle_focus(seat); } static void handle_close (PhocSeat *seat) { PhocView *focus = phoc_seat_get_focus(seat); if (focus) view_close(focus); } static void handle_move_to_monitor_right (PhocSeat *seat) { PhocView *view = phoc_seat_get_focus(seat); if (view) view_move_to_next_output(view, WLR_DIRECTION_RIGHT); } static void handle_move_to_monitor_left (PhocSeat *seat) { PhocView *view = phoc_seat_get_focus(seat); if (view) view_move_to_next_output(view, WLR_DIRECTION_LEFT); } static void handle_switch_input_source (PhocSeat *seat) { struct wlr_keyboard *wlr_keyboard = wlr_seat_get_keyboard (seat->seat); PhocKeyboard *keyboard; if (wlr_keyboard == NULL) return; keyboard = wlr_keyboard->data; g_return_if_fail (PHOC_IS_KEYBOARD (keyboard)); phoc_keyboard_next_layout (keyboard); } /* This is copied from mutter which in turn got it form GTK+ */ static inline gboolean is_alt (const gchar *string) { return ((string[0] == '<') && (string[1] == 'a' || string[1] == 'A') && (string[2] == 'l' || string[2] == 'L') && (string[3] == 't' || string[3] == 'T') && (string[4] == '>')); } static inline gboolean is_ctl (const gchar *string) { return ((string[0] == '<') && (string[1] == 'c' || string[1] == 'C') && (string[2] == 't' || string[2] == 'T') && (string[3] == 'l' || string[3] == 'L') && (string[4] == '>')); } static inline gboolean is_modx (const gchar *string) { return ((string[0] == '<') && (string[1] == 'm' || string[1] == 'M') && (string[2] == 'o' || string[2] == 'O') && (string[3] == 'd' || string[3] == 'D') && (string[4] >= '1' && string[4] <= '5') && (string[5] == '>')); } static inline gboolean is_ctrl (const gchar *string) { return ((string[0] == '<') && (string[1] == 'c' || string[1] == 'C') && (string[2] == 't' || string[2] == 'T') && (string[3] == 'r' || string[3] == 'R') && (string[4] == 'l' || string[4] == 'L') && (string[5] == '>')); } static inline gboolean is_shft (const gchar *string) { return ((string[0] == '<') && (string[1] == 's' || string[1] == 'S') && (string[2] == 'h' || string[2] == 'H') && (string[3] == 'f' || string[3] == 'F') && (string[4] == 't' || string[4] == 'T') && (string[5] == '>')); } static inline gboolean is_shift (const gchar *string) { return ((string[0] == '<') && (string[1] == 's' || string[1] == 'S') && (string[2] == 'h' || string[2] == 'H') && (string[3] == 'i' || string[3] == 'I') && (string[4] == 'f' || string[4] == 'F') && (string[5] == 't' || string[5] == 'T') && (string[6] == '>')); } static inline gboolean is_control (const gchar *string) { return ((string[0] == '<') && (string[1] == 'c' || string[1] == 'C') && (string[2] == 'o' || string[2] == 'O') && (string[3] == 'n' || string[3] == 'N') && (string[4] == 't' || string[4] == 'T') && (string[5] == 'r' || string[5] == 'R') && (string[6] == 'o' || string[6] == 'O') && (string[7] == 'l' || string[7] == 'L') && (string[8] == '>')); } static inline gboolean is_meta (const gchar *string) { return ((string[0] == '<') && (string[1] == 'm' || string[1] == 'M') && (string[2] == 'e' || string[2] == 'E') && (string[3] == 't' || string[3] == 'T') && (string[4] == 'a' || string[4] == 'A') && (string[5] == '>')); } static inline gboolean is_super (const gchar *string) { return ((string[0] == '<') && (string[1] == 's' || string[1] == 'S') && (string[2] == 'u' || string[2] == 'U') && (string[3] == 'p' || string[3] == 'P') && (string[4] == 'e' || string[4] == 'E') && (string[5] == 'r' || string[5] == 'R') && (string[6] == '>')); } static inline gboolean is_hyper (const gchar *string) { return ((string[0] == '<') && (string[1] == 'h' || string[1] == 'H') && (string[2] == 'y' || string[2] == 'Y') && (string[3] == 'p' || string[3] == 'P') && (string[4] == 'e' || string[4] == 'E') && (string[5] == 'r' || string[5] == 'R') && (string[6] == '>')); } static inline gboolean is_primary (const gchar *string) { return ((string[0] == '<') && (string[1] == 'p' || string[1] == 'P') && (string[2] == 'r' || string[2] == 'R') && (string[3] == 'i' || string[3] == 'I') && (string[4] == 'm' || string[4] == 'M') && (string[5] == 'a' || string[5] == 'A') && (string[6] == 'r' || string[6] == 'R') && (string[7] == 'y' || string[7] == 'Y') && (string[8] == '>')); } static inline gboolean is_keycode (const gchar *string) { return (string[0] == '0' && string[1] == 'x' && g_ascii_isxdigit (string[2]) && g_ascii_isxdigit (string[3])); } PhocKeyCombo * parse_accelerator (const gchar *accelerator) { PhocKeyCombo *combo; xkb_keysym_t keyval; guint32 mods; gint len; if (accelerator == NULL) return FALSE; keyval = 0; mods = 0; len = strlen (accelerator); while (len) { if (*accelerator == '<') { if (len >= 9 && is_control (accelerator)) { accelerator += 9; len -= 9; mods |= WLR_MODIFIER_CTRL; } else if (len >= 7 && is_shift (accelerator)) { accelerator += 7; len -= 7; mods |= WLR_MODIFIER_SHIFT; } else if (len >= 6 && is_shft (accelerator)) { accelerator += 6; len -= 6; mods |= WLR_MODIFIER_SHIFT; } else if (len >= 6 && is_ctrl (accelerator)) { accelerator += 6; len -= 6; mods |= WLR_MODIFIER_CTRL; } else if (len >= 6 && is_modx (accelerator)) { static const guint mod_vals[] = { WLR_MODIFIER_ALT, WLR_MODIFIER_MOD2, WLR_MODIFIER_MOD3, WLR_MODIFIER_LOGO, WLR_MODIFIER_MOD5, }; len -= 6; accelerator += 4; mods |= mod_vals[*accelerator - '1']; accelerator += 2; } else if (len >= 5 && is_ctl (accelerator)) { accelerator += 5; len -= 5; mods |= WLR_MODIFIER_CTRL; } else if (len >= 5 && is_alt (accelerator)) { accelerator += 5; len -= 5; mods |= WLR_MODIFIER_ALT; } else if (len >= 6 && is_meta (accelerator)) { accelerator += 6; len -= 6; g_warning ("Unhandled modifier meta"); return FALSE; } else if (len >= 7 && is_hyper (accelerator)) { accelerator += 7; len -= 7; g_warning ("Unhandled modifier hyper"); return FALSE; } else if (len >= 7 && is_super (accelerator)) { accelerator += 7; len -= 7; mods |= WLR_MODIFIER_LOGO; } else { gchar last_ch; last_ch = *accelerator; while (last_ch && last_ch != '>') { last_ch = *accelerator; accelerator += 1; len -= 1; } } } else { if (len >= 4 && is_keycode (accelerator)) { //keycode = strtoul (accelerator, NULL, 16); g_warning ("Unhandled keycode accelerator'"); goto out; } else if (strcmp (accelerator, "Above_Tab") == 0) { g_warning ("Unhandled key 'Above_Tab'"); return FALSE; } else { keyval = xkb_keysym_from_name (accelerator, XKB_KEYSYM_CASE_INSENSITIVE); if (keyval == XKB_KEY_NoSymbol) { g_autofree gchar *with_xf86 = g_strconcat ("XF86", accelerator, NULL); keyval = xkb_keysym_from_name (with_xf86, XKB_KEYSYM_CASE_INSENSITIVE); if (keyval == XKB_KEY_NoSymbol) return FALSE; } } accelerator += len; len -= len; } } out: combo = g_new0 (PhocKeyCombo, 1); combo->modifiers = mods; combo->keysym = keyval; return combo; } static void phoc_keybinding_free (PhocKeybinding *self) { g_slist_free_full (self->combos, (GDestroyNotify)g_free); g_free (self->name); g_free (self); } static gboolean key_combo_eq (const PhocKeyCombo *sym1, const PhocKeyCombo *sym2) { return (sym1->modifiers == sym2->modifiers && sym1->keysym == sym2->keysym); } static gboolean keybinding_by_name (const PhocKeybinding *keybinding, const gchar *name) { return g_strcmp0 (keybinding->name, name); } static gboolean keybinding_by_key_combo (const PhocKeybinding *keybinding, const PhocKeyCombo *combo) { GSList *elem = keybinding->combos; while (elem) { if (key_combo_eq (elem->data, combo)) return FALSE; elem = elem->next; }; return TRUE; } static void on_keybinding_setting_changed (PhocKeybindings *self, const gchar *key, GSettings *settings) { g_auto(GStrv) accelerators = NULL; PhocKeybinding *keybinding; GSList *elem; int i; g_return_if_fail (PHOC_IS_KEYBINDINGS (self)); g_return_if_fail (G_IS_SETTINGS (settings)); accelerators = g_settings_get_strv (settings, key); elem = g_slist_find_custom (self->bindings, key, (GCompareFunc)keybinding_by_name); if (!elem) { g_warning ("Changed keybinding %s not known", key); return; } keybinding = elem->data; g_slist_free_full (keybinding->combos, g_free); keybinding->combos = NULL; for (i = 0; accelerators && accelerators[i]; i++) { PhocKeyCombo *combo; g_debug ("New keybinding %s for %s", key, accelerators[i]); combo = parse_accelerator (accelerators[i]); if (combo) keybinding->combos = g_slist_append (keybinding->combos, combo); } } static gboolean phoc_add_keybinding (PhocKeybindings *self, GSettings *settings, const gchar *name, PhocKeyHandlerFunc func) { g_autofree gchar *signal_name = NULL; PhocKeybinding *binding; if (g_slist_find_custom (self->bindings, name, (GCompareFunc)keybinding_by_name)) { g_warning ("Keybinding '%s' already exists", name); return FALSE; } binding = g_new0 (PhocKeybinding, 1); binding->name = g_strdup (name); binding->func = func; signal_name = g_strdup_printf ("changed::%s", name); g_signal_connect_swapped (settings, signal_name, G_CALLBACK (on_keybinding_setting_changed), self); self->bindings = g_slist_append (self->bindings, binding); /* Fill in initial values */ on_keybinding_setting_changed (self, name, settings); return TRUE; } static void phoc_keybindings_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void phoc_keybindings_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void phoc_keybindings_dispose (GObject *object) { PhocKeybindings *self = PHOC_KEYBINDINGS (object); g_slist_free_full (self->bindings, (GDestroyNotify)phoc_keybinding_free); self->bindings = NULL; G_OBJECT_CLASS (phoc_keybindings_parent_class)->dispose (object); } static void phoc_keybindings_finalize (GObject *object) { PhocKeybindings *self = PHOC_KEYBINDINGS (object); g_clear_object (&self->settings); g_clear_object (&self->mutter_settings); G_OBJECT_CLASS (phoc_keybindings_parent_class)->finalize (object); } static void phoc_keybindings_constructed (GObject *object) { PhocKeybindings *self = PHOC_KEYBINDINGS (object); G_OBJECT_CLASS (phoc_keybindings_parent_class)->constructed (object); self->settings = g_settings_new (KEYBINDINGS_SCHEMA_ID); phoc_add_keybinding (self, self->settings, "close", handle_close); phoc_add_keybinding (self, self->settings, "cycle-windows", handle_cycle_windows); phoc_add_keybinding (self, self->settings, "maximize", handle_maximize); phoc_add_keybinding (self, self->settings, "toggle-fullscreen", handle_toggle_fullscreen); phoc_add_keybinding (self, self->settings, "toggle-maximized", handle_toggle_maximized); phoc_add_keybinding (self, self->settings, "move-to-monitor-right", handle_move_to_monitor_right); phoc_add_keybinding (self, self->settings, "move-to-monitor-left", handle_move_to_monitor_left); /* TODO: we need a real switch-applications but ALT-TAB should do s.th. * useful */ phoc_add_keybinding (self, self->settings, "switch-applications", handle_cycle_windows); phoc_add_keybinding (self, self->settings, "unmaximize", handle_unmaximize); phoc_add_keybinding (self, self->settings, "switch-input-source", handle_switch_input_source); self->mutter_settings = g_settings_new (MUTTER_KEYBINDINGS_SCHEMA_ID); phoc_add_keybinding (self, self->mutter_settings, "toggle-tiled-left", handle_tile_left); phoc_add_keybinding (self, self->mutter_settings, "toggle-tiled-right", handle_tile_right); G_OBJECT_CLASS (phoc_keybindings_parent_class)->constructed (object); } static void phoc_keybindings_class_init (PhocKeybindingsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = phoc_keybindings_constructed; object_class->dispose = phoc_keybindings_dispose; object_class->finalize = phoc_keybindings_finalize; object_class->set_property = phoc_keybindings_set_property; object_class->get_property = phoc_keybindings_get_property; } static void phoc_keybindings_init (PhocKeybindings *self) { self->bindings = NULL; } PhocKeybindings * phoc_keybindings_new (void) { return g_object_new (PHOC_TYPE_KEYBINDINGS, NULL); } /** * phoc_keybindings_handle_pressed: * * Check if a keybinding is known and run the associated action */ gboolean phoc_keybindings_handle_pressed (PhocKeybindings *self, guint32 modifiers, xkb_keysym_t *pressed_keysyms, guint32 length, PhocSeat *seat) { PhocKeybinding *keybinding; GSList *elem; PhocKeyCombo combo; if (length != 1) return FALSE; combo.keysym = pressed_keysyms[0]; combo.modifiers = modifiers; elem = g_slist_find_custom (self->bindings, &combo, (GCompareFunc)keybinding_by_key_combo); if (!elem) return FALSE; g_return_val_if_fail (elem->data, FALSE); keybinding = elem->data; (*keybinding->func) (seat); return TRUE; } phoc-v0.13.1/src/keybindings.h000066400000000000000000000016341422111650000161550ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * * SPDX-License-Identifier: GPL-3.0+ */ #pragma once #include #define GNOME_DESKTOP_USE_UNSTABLE_API #include #include G_BEGIN_DECLS #define PHOC_TYPE_KEYBINDINGS (phoc_keybindings_get_type()) G_DECLARE_FINAL_TYPE (PhocKeybindings, phoc_keybindings, PHOC, KEYBINDINGS, GObject); PhocKeybindings *phoc_keybindings_new (void); /** * PhocKeyCombo: * * A combination of modifiers and a key describing a keyboard shortcut */ typedef struct { guint32 modifiers; xkb_keysym_t keysym; } PhocKeyCombo; typedef struct _PhocSeat PhocSeat; gboolean phoc_keybindings_handle_pressed (PhocKeybindings *self, guint32 modifiers, xkb_keysym_t *pressed_keysyms, guint32 length, PhocSeat *seat); PhocKeyCombo *parse_accelerator (const gchar * accelerator); G_END_DECLS phoc-v0.13.1/src/keyboard.c000066400000000000000000000466141422111650000154510ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later * * Author: Guido Günther * The wlroots authors */ #define G_LOG_DOMAIN "phoc-keyboard" #include "config.h" #include "server.h" #include #include #include #include #include #include #include #include #include "keyboard.h" #include "phosh-private.h" #include "seat.h" #include #include /** * PhocKeyboard: * * A keyboard input device * * It tracks keybindings and it's keymap. */ struct _PhocKeyboard { PhocInputDevice parent; struct wl_listener keyboard_key; struct wl_listener keyboard_modifiers; GSettings *input_settings; GSettings *keyboard_settings; struct xkb_keymap *keymap; uint32_t meta_key; GnomeXkbInfo *xkbinfo; xkb_keysym_t pressed_keysyms_translated[PHOC_KEYBOARD_PRESSED_KEYSYMS_CAP]; xkb_keysym_t pressed_keysyms_raw[PHOC_KEYBOARD_PRESSED_KEYSYMS_CAP]; }; G_DEFINE_TYPE(PhocKeyboard, phoc_keyboard, PHOC_TYPE_INPUT_DEVICE) enum { ACTIVITY, N_SIGNALS }; static guint signals [N_SIGNALS]; static ssize_t pressed_keysyms_index(const xkb_keysym_t *pressed_keysyms, xkb_keysym_t keysym) { for (size_t i = 0; i < PHOC_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) { if (pressed_keysyms[i] == keysym) { return i; } } return -1; } static size_t pressed_keysyms_length(const xkb_keysym_t *pressed_keysyms) { size_t n = 0; for (size_t i = 0; i < PHOC_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) { if (pressed_keysyms[i] != XKB_KEY_NoSymbol) { ++n; } } return n; } static void pressed_keysyms_add(xkb_keysym_t *pressed_keysyms, xkb_keysym_t keysym) { ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym); if (i < 0) { i = pressed_keysyms_index(pressed_keysyms, XKB_KEY_NoSymbol); if (i >= 0) { pressed_keysyms[i] = keysym; } } } static void pressed_keysyms_remove(xkb_keysym_t *pressed_keysyms, xkb_keysym_t keysym) { ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym); if (i >= 0) { pressed_keysyms[i] = XKB_KEY_NoSymbol; } } static bool keysym_is_modifier(xkb_keysym_t keysym) { switch (keysym) { case XKB_KEY_Shift_L: case XKB_KEY_Shift_R: case XKB_KEY_Control_L: case XKB_KEY_Control_R: case XKB_KEY_Caps_Lock: case XKB_KEY_Shift_Lock: case XKB_KEY_Meta_L: case XKB_KEY_Meta_R: case XKB_KEY_Alt_L: case XKB_KEY_Alt_R: case XKB_KEY_Super_L: case XKB_KEY_Super_R: case XKB_KEY_Hyper_L: case XKB_KEY_Hyper_R: return true; default: return false; } } static void pressed_keysyms_update(xkb_keysym_t *pressed_keysyms, const xkb_keysym_t *keysyms, size_t keysyms_len, enum wl_keyboard_key_state state) { for (size_t i = 0; i < keysyms_len; ++i) { if (keysym_is_modifier(keysyms[i])) { continue; } if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { pressed_keysyms_add(pressed_keysyms, keysyms[i]); } else { // WL_KEYBOARD_KEY_STATE_RELEASED pressed_keysyms_remove(pressed_keysyms, keysyms[i]); } } } /* * Execute a built-in, hardcoded compositor binding. These are triggered from a * single keysym. * * Returns true if the keysym was handled by a binding and false if the event * should be propagated to clients. */ static bool keyboard_execute_compositor_binding(PhocKeyboard *self, xkb_keysym_t keysym) { PhocServer *server = phoc_server_get_default (); if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { struct wlr_session *session = wlr_backend_get_session(server->backend); if (session) { unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; wlr_session_change_vt(session, vt); } return true; } if (keysym == XKB_KEY_XF86PowerDown || keysym == XKB_KEY_XF86PowerOff) { g_debug ("Power button pressed"); phoc_desktop_toggle_output_blank (server->desktop); return true; } if (keysym == XKB_KEY_Escape) { PhocSeat *seat = phoc_input_device_get_seat (PHOC_INPUT_DEVICE (self)); wlr_seat_pointer_end_grab(seat->seat); wlr_seat_keyboard_end_grab(seat->seat); phoc_seat_end_compositor_grab(seat); } return false; } /* * Execute keyboard bindings. These include compositor bindings and user-defined * bindings. * * Returns true if the keysym was handled by a binding and false if the event * should be propagated to clients. */ static bool keyboard_execute_binding(PhocKeyboard *self, xkb_keysym_t *pressed_keysyms, uint32_t modifiers, const xkb_keysym_t *keysyms, size_t keysyms_len) { PhocServer *server = phoc_server_get_default (); PhocKeybindings *keybindings; PhocSeat *seat = phoc_input_device_get_seat (PHOC_INPUT_DEVICE (self)); /* TODO: should be handled via PhocKeybindings as well */ for (size_t i = 0; i < keysyms_len; ++i) { if (keyboard_execute_compositor_binding(self, keysyms[i])) { return true; } } size_t n = pressed_keysyms_length(pressed_keysyms); keybindings = server->config->keybindings; if (phoc_keybindings_handle_pressed (keybindings, modifiers, pressed_keysyms, n, seat)) return true; return false; } /* * Forward keyboard bindings. * * Returns true if the keysym was handled by forwarding and false if the event * should be propagated to clients. */ static bool keyboard_execute_subscribed_binding(PhocKeyboard *self, xkb_keysym_t *pressed_keysyms, uint32_t modifiers, const xkb_keysym_t *keysyms, size_t keysyms_len, uint32_t time) { bool handled = false; for (size_t i = 0; i < keysyms_len; ++i) { PhocKeyCombo combo = { modifiers, keysyms[i] }; handled = handled | phoc_phosh_private_forward_keysym (&combo, time); } return handled; } /* * Get keysyms and modifiers from the keyboard as xkb sees them. * * This uses the xkb keysyms translation based on pressed modifiers and clears * the consumed modifiers from the list of modifiers passed to keybind * detection. * * On US layout, pressing Alt+Shift+2 will trigger Alt+@. */ static size_t keyboard_keysyms_translated(PhocKeyboard *self, xkb_keycode_t keycode, const xkb_keysym_t **keysyms, uint32_t *modifiers) { PhocInputDevice *input_device = PHOC_INPUT_DEVICE (self); struct wlr_input_device *device = phoc_input_device_get_device (input_device); *modifiers = wlr_keyboard_get_modifiers(device->keyboard); xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2( device->keyboard->xkb_state, keycode, XKB_CONSUMED_MODE_XKB); *modifiers = *modifiers & ~consumed; return xkb_state_key_get_syms(device->keyboard->xkb_state, keycode, keysyms); } /* * Get keysyms and modifiers from the keyboard as if modifiers didn't change * keysyms. * * This avoids the xkb keysym translation based on modifiers considered pressed * in the state. * * This will trigger keybinds such as Alt+Shift+2. */ static size_t keyboard_keysyms_raw(PhocKeyboard *self, xkb_keycode_t keycode, const xkb_keysym_t **keysyms, uint32_t *modifiers) { PhocInputDevice *input_device = PHOC_INPUT_DEVICE (self); struct wlr_input_device *device = phoc_input_device_get_device (input_device); *modifiers = wlr_keyboard_get_modifiers(device->keyboard); xkb_layout_index_t layout_index = xkb_state_key_get_layout(device->keyboard->xkb_state, keycode); return xkb_keymap_key_get_syms_by_level(device->keyboard->keymap, keycode, layout_index, 0, keysyms); } static void phoc_keyboard_handle_key(PhocKeyboard *self, struct wlr_event_keyboard_key *event) { xkb_keycode_t keycode = event->keycode + 8; bool handled = false; uint32_t modifiers; const xkb_keysym_t *keysyms; size_t keysyms_len; // Handle translated keysyms keysyms_len = keyboard_keysyms_translated(self, keycode, &keysyms, &modifiers); pressed_keysyms_update(self->pressed_keysyms_translated, keysyms, keysyms_len, event->state); if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { handled = keyboard_execute_binding(self, self->pressed_keysyms_translated, modifiers, keysyms, keysyms_len); } // Handle raw keysyms keysyms_len = keyboard_keysyms_raw(self, keycode, &keysyms, &modifiers); pressed_keysyms_update(self->pressed_keysyms_raw, keysyms, keysyms_len, event->state); if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED && !handled) { handled = keyboard_execute_binding(self, self->pressed_keysyms_raw, modifiers, keysyms, keysyms_len); } // Handle subscribed keysyms if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED && !handled) { handled = keyboard_execute_subscribed_binding (self, self->pressed_keysyms_raw, modifiers, keysyms, keysyms_len, event->time_msec); } if (!handled) { PhocInputDevice *input_device = PHOC_INPUT_DEVICE (self); PhocSeat *seat = phoc_input_device_get_seat (input_device); struct wlr_input_device *device = phoc_input_device_get_device (input_device); wlr_seat_set_keyboard(seat->seat, device); wlr_seat_keyboard_notify_key(seat->seat, event->time_msec, event->keycode, event->state); } } static void phoc_keyboard_handle_modifiers(PhocKeyboard *self) { PhocInputDevice *input_device = PHOC_INPUT_DEVICE (self); PhocSeat *seat = phoc_input_device_get_seat (input_device); struct wlr_input_device *device = phoc_input_device_get_device (input_device); wlr_seat_set_keyboard(seat->seat, device); wlr_seat_keyboard_notify_modifiers(seat->seat, &device->keyboard->modifiers); } static void set_fallback_keymap (PhocKeyboard *self) { struct xkb_context *context; PhocInputDevice *input_device = PHOC_INPUT_DEVICE (self); struct wlr_input_device *device = phoc_input_device_get_device (input_device); context = xkb_context_new (XKB_CONTEXT_NO_FLAGS); if (context == NULL) { return; } xkb_keymap_unref (self->keymap); self->keymap = xkb_keymap_new_from_names (context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS); xkb_context_unref (context); wlr_keyboard_set_keymap(device->keyboard, self->keymap); } static void set_xkb_keymap (PhocKeyboard *self, const gchar *layout, const gchar *variant, const gchar *options) { struct xkb_rule_names rules = { 0 }; struct xkb_context *context = NULL; struct xkb_keymap *keymap = NULL; PhocInputDevice *input_device = PHOC_INPUT_DEVICE (self); struct wlr_input_device *device = phoc_input_device_get_device (input_device); g_assert (device->keyboard); rules.layout = layout; rules.variant = variant; rules.options = options; context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (context == NULL) { g_warning ("Cannot create XKB context"); goto out; } keymap = xkb_map_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); if (keymap == NULL) { g_warning ("Cannot create XKB keymap"); } out: if (context) xkb_context_unref(context); if (keymap) { xkb_keymap_unref (self->keymap); self->keymap = keymap; } else if (self->keymap == NULL) { set_fallback_keymap (self); return; } wlr_keyboard_set_keymap(device->keyboard, self->keymap); } static void on_input_setting_changed (PhocKeyboard *self, const gchar *key, GSettings *settings) { g_auto(GStrv) xkb_options = NULL; g_autoptr(GVariant) sources = NULL; GVariantIter iter; g_autofree gchar *id = NULL; g_autofree gchar *type = NULL; g_autofree gchar *xkb_options_string = NULL; const gchar *layout = NULL; const gchar *variant = NULL; PhocInputDevice *input_device; struct wlr_input_device *device; g_return_if_fail (PHOC_IS_KEYBOARD (self)); g_return_if_fail (G_IS_SETTINGS (settings)); input_device = PHOC_INPUT_DEVICE (self); device = phoc_input_device_get_device (input_device); g_debug ("Setting changed, reloading input settings"); if (wlr_input_device_get_virtual_keyboard(device) != NULL) { g_debug ("Virtual keyboard in use, not switching layout"); return; } sources = g_settings_get_value(settings, "sources"); g_variant_iter_init (&iter, sources); g_variant_iter_next (&iter, "(ss)", &type, &id); if (g_strcmp0 (type, "xkb")) { g_debug ("Not a xkb layout: '%s' - ignoring", id); return; } xkb_options = g_settings_get_strv (settings, "xkb-options"); if (xkb_options) { xkb_options_string = g_strjoinv (",", xkb_options); g_debug ("Setting options %s", xkb_options_string); } if (!gnome_xkb_info_get_layout_info (self->xkbinfo, id, NULL, NULL, &layout, &variant)) { g_debug ("Failed to get layout info for %s", id); return; } g_debug ("Switching to layout %s %s", layout, variant); set_xkb_keymap (self, layout, variant, xkb_options_string); } static void on_keyboard_setting_changed (PhocKeyboard *self, const gchar *key, GSettings *settings) { gboolean repeat; gint rate = 0, delay = 0; PhocInputDevice *input_device; struct wlr_input_device *device; g_return_if_fail (PHOC_IS_KEYBOARD (self)); g_return_if_fail (G_IS_SETTINGS (settings)); input_device = PHOC_INPUT_DEVICE (self); device = phoc_input_device_get_device (input_device); repeat = g_settings_get_boolean (self->keyboard_settings, "repeat"); if (repeat) { guint interval = g_settings_get_uint (self->keyboard_settings, "repeat-interval"); /* The setting is in the milliseconds between keys. "rate" is the number * of keys per second. */ if (interval > 0) rate = (1000 / interval); else rate = 0; delay = g_settings_get_uint (self->keyboard_settings, "delay"); } g_debug ("Setting repeat rate to %d, delay %d", rate, delay); wlr_keyboard_set_repeat_info(device->keyboard, rate, delay); } static void handle_keyboard_key (struct wl_listener *listener, void *data) { PhocKeyboard *self = wl_container_of (listener, self, keyboard_key); struct wlr_event_keyboard_key *event = data; g_assert (PHOC_IS_KEYBOARD (self)); phoc_keyboard_handle_key (self, event); g_signal_emit (self, signals[ACTIVITY], 0); } static void handle_keyboard_modifiers (struct wl_listener *listener, void *data) { PhocKeyboard *self = wl_container_of (listener, self, keyboard_modifiers); g_assert (PHOC_IS_KEYBOARD (self)); phoc_keyboard_handle_modifiers (self); g_signal_emit (self, signals[ACTIVITY], 0); } static void phoc_keyboard_dispose(GObject *object) { PhocKeyboard *self = PHOC_KEYBOARD (object); g_clear_object (&self->input_settings); g_clear_object (&self->keyboard_settings); g_clear_object (&self->xkbinfo); G_OBJECT_CLASS (phoc_keyboard_parent_class)->dispose (object); } static void phoc_keyboard_finalize(GObject *object) { PhocKeyboard *self = PHOC_KEYBOARD (object); wl_list_remove (&self->keyboard_key.link); wl_list_remove (&self->keyboard_modifiers.link); xkb_keymap_unref (self->keymap); self->keymap = NULL; G_OBJECT_CLASS (phoc_keyboard_parent_class)->finalize (object); } static void phoc_keyboard_constructed (GObject *object) { PhocKeyboard *self = PHOC_KEYBOARD (object); PhocInputDevice *input_device = PHOC_INPUT_DEVICE (self); struct wlr_input_device *device = phoc_input_device_get_device (input_device); device->keyboard->data = self; /* wlr listeners */ self->keyboard_key.notify = handle_keyboard_key; wl_signal_add (&device->keyboard->events.key, &self->keyboard_key); self->keyboard_modifiers.notify = handle_keyboard_modifiers; wl_signal_add (&device->keyboard->events.modifiers, &self->keyboard_modifiers); /* Keyboard settings */ self->input_settings = g_settings_new ("org.gnome.desktop.input-sources"); self->keyboard_settings = g_settings_new ( "org.gnome.desktop.peripherals.keyboard"); self->meta_key = WLR_MODIFIER_LOGO; set_fallback_keymap (self); self->xkbinfo = gnome_xkb_info_new (); g_object_connect (self->input_settings, "swapped-signal::changed::sources", G_CALLBACK (on_input_setting_changed), self, "swapped-signal::changed::xkb-options", G_CALLBACK (on_input_setting_changed), self, NULL); on_input_setting_changed (self, NULL, self->input_settings); g_object_connect (self->keyboard_settings, "swapped-signal::changed::repeat", G_CALLBACK (on_keyboard_setting_changed), self, "swapped-signal::changed::repeat-interval", G_CALLBACK (on_keyboard_setting_changed), self, "swapped-signal::changed::delay", G_CALLBACK (on_keyboard_setting_changed), self, NULL); on_keyboard_setting_changed (self, NULL, self->keyboard_settings); G_OBJECT_CLASS (phoc_keyboard_parent_class)->constructed (object); } static void phoc_keyboard_class_init (PhocKeyboardClass *klass) { GObjectClass *object_class = (GObjectClass *)klass; object_class->constructed = phoc_keyboard_constructed; object_class->dispose = phoc_keyboard_dispose; object_class->finalize = phoc_keyboard_finalize; /** * PhocKeyboard::activity * * Emitted whenver there is input activity on this device */ signals[ACTIVITY] = g_signal_new ("activity", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } static void phoc_keyboard_init (PhocKeyboard *self) { } PhocKeyboard * phoc_keyboard_new (struct wlr_input_device *device, PhocSeat *seat) { return g_object_new (PHOC_TYPE_KEYBOARD, "device", device, "seat", seat, NULL); } /** * phoc_keyboard_next_layout: * @self: the keyboard * * Switch to next keyboard in the list of available layouts */ void phoc_keyboard_next_layout (PhocKeyboard *self) { g_autoptr(GVariant) sources = NULL; GVariantIter iter; gchar *type, *id, *cur_type, *cur_id; GVariantBuilder builder; gboolean next; g_return_if_fail (PHOC_IS_KEYBOARD (self)); sources = g_settings_get_value(self->input_settings, "sources"); if (g_variant_n_children (sources) < 2) return; g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)")); g_variant_iter_init (&iter, sources); next = g_variant_iter_next (&iter, "(ss)", &cur_type, &cur_id); while (next) { next = g_variant_iter_next (&iter, "(ss)", &type, &id); if (!next) break; g_variant_builder_add (&builder, "(ss)", type, id); } g_variant_builder_add (&builder, "(ss)", cur_type, cur_id); g_settings_set_value(self->input_settings, "sources", g_variant_builder_end(&builder)); } /** * phoc_keyboard_get_meta_key: * @self: the keyboard * * Returns: the current Meta key */ uint32_t phoc_keyboard_get_meta_key (PhocKeyboard *self) { g_assert (PHOC_IS_KEYBOARD (self)); return self->meta_key; } phoc-v0.13.1/src/keyboard.h000066400000000000000000000011231422111650000154400ustar00rootroot00000000000000#pragma once #include "input.h" #include "input-device.h" #include #include #include G_BEGIN_DECLS #define PHOC_KEYBOARD_PRESSED_KEYSYMS_CAP 32 #define PHOC_TYPE_KEYBOARD (phoc_keyboard_get_type()) G_DECLARE_FINAL_TYPE (PhocKeyboard, phoc_keyboard, PHOC, KEYBOARD, PhocInputDevice) PhocKeyboard *phoc_keyboard_new (struct wlr_input_device *device, PhocSeat *seat); void phoc_keyboard_next_layout (PhocKeyboard *self); uint32_t phoc_keyboard_get_meta_key (PhocKeyboard *self); G_END_DECLS phoc-v0.13.1/src/layer-surface.c000066400000000000000000000052041422111650000164010ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #define G_LOG_DOMAIN "phoc-layer-surface" #include "config.h" #include "layer-surface.h" #include "layers.h" #include "output.h" G_DEFINE_TYPE (PhocLayerSurface, phoc_layer_surface, G_TYPE_OBJECT) static void phoc_layer_surface_finalize (GObject *object) { PhocLayerSurface *self = PHOC_LAYER_SURFACE (object); if (self->layer_surface->mapped) phoc_layer_surface_unmap (self); wl_list_remove (&self->link); wl_list_remove (&self->destroy.link); wl_list_remove (&self->map.link); wl_list_remove (&self->unmap.link); wl_list_remove (&self->surface_commit.link); if (self->layer_surface->output) { PhocOutput *output = phoc_layer_surface_get_output (self); g_assert (PHOC_IS_OUTPUT (output)); wl_list_remove (&self->output_destroy.link); phoc_layer_shell_arrange (output); phoc_layer_shell_update_focus (); } G_OBJECT_CLASS (phoc_layer_surface_parent_class)->finalize (object); } static void phoc_layer_surface_class_init (PhocLayerSurfaceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = phoc_layer_surface_finalize; } static void phoc_layer_surface_init (PhocLayerSurface *self) { } PhocLayerSurface * phoc_layer_surface_new (void) { return PHOC_LAYER_SURFACE (g_object_new (PHOC_TYPE_LAYER_SURFACE, NULL)); } /** * phoc_layer_surface_unmap: * @self: The layer surface to unmap * * Unmaps a layer surface */ void phoc_layer_surface_unmap (PhocLayerSurface *self) { struct wlr_layer_surface_v1 *layer_surface; struct wlr_output *wlr_output; g_assert (PHOC_IS_LAYER_SURFACE (self)); layer_surface = self->layer_surface; wlr_output = layer_surface->output; if (wlr_output != NULL) { phoc_output_damage_whole_local_surface(wlr_output->data, layer_surface->surface, self->geo.x, self->geo.y); } } /** * phoc_layer_surface_get_namespace: * @self: The layer surface * * Returns: (nullable): The layer surface's namespace */ const char * phoc_layer_surface_get_namespace (PhocLayerSurface *self) { g_assert (PHOC_IS_LAYER_SURFACE (self)); return self->layer_surface->namespace; } /** * phoc_layer_surface_get_output: * @self: The layer surface * * Returns: (transfer none) (nullable): The layer surface's output or * %NULL if the output was destroyed. */ PhocOutput * phoc_layer_surface_get_output (PhocLayerSurface *self) { g_assert (PHOC_IS_LAYER_SURFACE (self)); if (self->layer_surface->output == NULL) return NULL; return self->layer_surface->output->data; } phoc-v0.13.1/src/layer-surface.h000066400000000000000000000025201422111650000164040ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "output.h" #include #include G_BEGIN_DECLS #define PHOC_TYPE_LAYER_SURFACE (phoc_layer_surface_get_type ()) G_DECLARE_FINAL_TYPE (PhocLayerSurface, phoc_layer_surface, PHOC, LAYER_SURFACE, GObject) /** * PhocLayerSurface: * * A Layer surface backed by the wlr-layer-surface wayland protocol. * * For details on how to setup a layer surface see `handle_layer_shell_surface`. */ struct _PhocLayerSurface { GObject parent; struct wlr_layer_surface_v1 *layer_surface; struct wl_list link; // PhocOutput::layers[current] struct wl_listener destroy; struct wl_listener map; struct wl_listener unmap; struct wl_listener surface_commit; struct wl_listener output_destroy; struct wl_listener new_popup; struct wl_listener new_subsurface; struct wl_list subsurfaces; // roots_layer_subsurface::link struct wlr_box geo; enum zwlr_layer_shell_v1_layer layer; }; PhocLayerSurface *phoc_layer_surface_new (void); void phoc_layer_surface_unmap (PhocLayerSurface *self); const char *phoc_layer_surface_get_namespace (PhocLayerSurface *self); PhocOutput *phoc_layer_surface_get_output (PhocLayerSurface *self); G_END_DECLS phoc-v0.13.1/src/layer_shell.c000066400000000000000000000721341422111650000161500ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-layer-shell" #include "config.h" #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200112L #endif #include #include #include #include #include #include #include #include #include #include #include "cursor.h" #include "desktop.h" #include "layers.h" #include "output.h" #include "seat.h" #include "server.h" #define LAYER_SHELL_LAYER_COUNT 4 static void apply_exclusive(struct wlr_box *usable_area, uint32_t anchor, int32_t exclusive, int32_t margin_top, int32_t margin_right, int32_t margin_bottom, int32_t margin_left) { if (exclusive <= 0) { return; } struct { uint32_t anchors; int *positive_axis; int *negative_axis; int margin; } edges[] = { { .anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, .positive_axis = &usable_area->y, .negative_axis = &usable_area->height, .margin = margin_top, }, { .anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, .positive_axis = NULL, .negative_axis = &usable_area->height, .margin = margin_bottom, }, { .anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, .positive_axis = &usable_area->x, .negative_axis = &usable_area->width, .margin = margin_left, }, { .anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, .positive_axis = NULL, .negative_axis = &usable_area->width, .margin = margin_right, }, }; for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) { if ((anchor & edges[i].anchors) == edges[i].anchors && exclusive + edges[i].margin > 0) { if (edges[i].positive_axis) { *edges[i].positive_axis += exclusive + edges[i].margin; } if (edges[i].negative_axis) { *edges[i].negative_axis -= exclusive + edges[i].margin; } } } } static void update_cursors(PhocLayerSurface *roots_surface, GSList *seats /* PhocSeat */) { PhocServer *server = phoc_server_get_default (); for (GSList *elem = phoc_input_get_seats (server->input); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); PhocCursor *cursor = phoc_seat_get_cursor(seat); double sx, sy; struct wlr_surface *surface = phoc_desktop_surface_at( server->desktop, cursor->cursor->x, cursor->cursor->y, &sx, &sy, NULL); if (surface == roots_surface->layer_surface->surface) { struct timespec time; if (clock_gettime(CLOCK_MONOTONIC, &time) == 0) { phoc_cursor_update_position(cursor, time.tv_sec * 1000 + time.tv_nsec / 1000000); } else { g_critical ("Failed to get time, not updating position. Errno: %s\n", strerror(errno)); } } } } static void arrange_layer(struct wlr_output *output, GSList *seats /* PhocSeat */, struct wl_list *list /* PhocLayerSurface */, struct wlr_box *usable_area, bool exclusive) { PhocLayerSurface *roots_surface; struct wlr_box full_area = { 0 }; wlr_output_effective_resolution(output, &full_area.width, &full_area.height); wl_list_for_each_reverse(roots_surface, list, link) { struct wlr_layer_surface_v1 *layer = roots_surface->layer_surface; struct wlr_layer_surface_v1_state *state = &layer->current; if (exclusive != (state->exclusive_zone > 0)) { continue; } struct wlr_box bounds; if (state->exclusive_zone == -1) { bounds = full_area; } else { bounds = *usable_area; } struct wlr_box box = { .width = state->desired_width, .height = state->desired_height }; // Horizontal axis const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; if ((state->anchor & both_horiz) && box.width == 0) { box.x = bounds.x; box.width = bounds.width; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { box.x = bounds.x; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { box.x = bounds.x + (bounds.width - box.width); } else { box.x = bounds.x + ((bounds.width / 2) - (box.width / 2)); } // Vertical axis const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; if ((state->anchor & both_vert) && box.height == 0) { box.y = bounds.y; box.height = bounds.height; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { box.y = bounds.y; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { box.y = bounds.y + (bounds.height - box.height); } else { box.y = bounds.y + ((bounds.height / 2) - (box.height / 2)); } // Margin if ((state->anchor & both_horiz) == both_horiz) { box.x += state->margin.left; box.width -= state->margin.left + state->margin.right; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { box.x += state->margin.left; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { box.x -= state->margin.right; } if ((state->anchor & both_vert) == both_vert) { box.y += state->margin.top; box.height -= state->margin.top + state->margin.bottom; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { box.y += state->margin.top; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { box.y -= state->margin.bottom; } if (box.width < 0 || box.height < 0) { // TODO: Bubble up a protocol error? wlr_layer_surface_v1_close(layer); continue; } // Apply struct wlr_box old_geo = roots_surface->geo; roots_surface->geo = box; if (layer->mapped) { apply_exclusive(usable_area, state->anchor, state->exclusive_zone, state->margin.top, state->margin.right, state->margin.bottom, state->margin.left); } wlr_layer_surface_v1_configure(layer, box.width, box.height); // Having a cursor newly end up over the moved layer will not // automatically send a motion event to the surface. The event needs to // be synthesized. // Only update layer surfaces which kept their size (and so buffers) the // same, because those with resized buffers will be handled separately. if (roots_surface->geo.x != old_geo.x || roots_surface->geo.y != old_geo.y) { update_cursors(roots_surface, seats); } } } struct osk_origin { struct wlr_layer_surface_v1_state state; PhocLayerSurface *surface; enum zwlr_layer_shell_v1_layer layer; }; static struct osk_origin find_osk(struct wl_list layers[LAYER_SHELL_LAYER_COUNT]) { struct osk_origin origin = {0}; for (unsigned i = 0; i < LAYER_SHELL_LAYER_COUNT; i++) { struct wl_list *list = &layers[i]; PhocLayerSurface *roots_surface; wl_list_for_each(roots_surface, list, link) { if (strcmp(roots_surface->layer_surface->namespace, "osk") == 0) { origin.state = roots_surface->layer_surface->current; origin.surface = roots_surface; origin.layer = i; return origin; } } } return origin; } /// Adjusts keyboard properties static void change_osk(const struct osk_origin *osk, struct wl_list layers[LAYER_SHELL_LAYER_COUNT], bool force_overlay) { if (force_overlay && osk->layer != ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) { wl_list_remove(&osk->surface->link); wl_list_insert(&layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &osk->surface->link); } if (!force_overlay && osk->layer != osk->surface->layer_surface->client_pending.layer) { wl_list_remove(&osk->surface->link); wl_list_insert(&layers[osk->surface->layer_surface->client_pending.layer], &osk->surface->link); } } void phoc_layer_shell_arrange (PhocOutput *output) { struct wlr_box usable_area = { 0 }; PhocServer *server = phoc_server_get_default (); GSList *seats = phoc_input_get_seats (server->input); enum zwlr_layer_shell_v1_layer layers[] = { ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, ZWLR_LAYER_SHELL_V1_LAYER_TOP, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND }; wlr_output_effective_resolution (output->wlr_output, &usable_area.width, &usable_area.height); struct osk_origin osk_place = find_osk (output->layers); if (osk_place.surface) { bool osk_force_overlay = false; for (GSList *elem = seats; elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); if (seat->focused_layer && seat->focused_layer->client_pending.layer >= osk_place.surface->layer_surface->client_pending.layer) { osk_force_overlay = true; break; } } change_osk (&osk_place, output->layers, osk_force_overlay); } // Arrange exclusive surfaces from top->bottom for (size_t i = 0; i < G_N_ELEMENTS(layers); ++i) arrange_layer (output->wlr_output, seats, &output->layers[layers[i]], &usable_area, true); output->usable_area = usable_area; PhocView *view; wl_list_for_each (view, &output->desktop->views, link) { if (view_is_maximized (view)) { view_arrange_maximized (view, NULL); } else if (view_is_tiled (view)) { view_arrange_tiled (view, NULL); } else if (output->desktop->maximize) { view_center (view, NULL); } } // Arrange non-exlusive surfaces from top->bottom for (size_t i = 0; i < G_N_ELEMENTS(layers); ++i) arrange_layer (output->wlr_output, seats, &output->layers[layers[i]], &usable_area, false); } void phoc_layer_shell_update_focus (void) { PhocServer *server = phoc_server_get_default (); enum zwlr_layer_shell_v1_layer layers_above_shell[] = { ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, ZWLR_LAYER_SHELL_V1_LAYER_TOP, }; PhocLayerSurface *layer, *topmost = NULL; // Find topmost keyboard interactive layer, if such a layer exists // TODO: Make layer surface focus per-output based on cursor position PhocOutput *output; wl_list_for_each (output, &server->desktop->outputs, link) { for (size_t i = 0; i < G_N_ELEMENTS(layers_above_shell); ++i) { if (output->fullscreen_view && !output->force_shell_reveal) { if (layers_above_shell[i] == ZWLR_LAYER_SHELL_V1_LAYER_TOP) continue; } wl_list_for_each(layer, &output->layers[layers_above_shell[i]], link) { if (layer->layer_surface->current.keyboard_interactive && layer->layer_surface->mapped) { topmost = layer; break; } } if (topmost != NULL) break; } } for (GSList *elem = phoc_input_get_seats (server->input); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); phoc_seat_set_focus_layer(seat, topmost ? topmost->layer_surface : NULL); } } static void handle_output_destroy(struct wl_listener *listener, void *data) { PhocLayerSurface *layer = wl_container_of(listener, layer, output_destroy); layer->layer_surface->output = NULL; wl_list_remove(&layer->output_destroy.link); wlr_layer_surface_v1_close(layer->layer_surface); } static void handle_surface_commit(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocLayerSurface *layer = wl_container_of(listener, layer, surface_commit); struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface; struct wlr_output *wlr_output = layer_surface->output; if (wlr_output != NULL) { PhocOutput *output = wlr_output->data; struct wlr_box old_geo = layer->geo; bool layer_changed = layer->layer != layer_surface->current.layer; if (layer_changed) { wl_list_remove(&layer->link); wl_list_insert(&output->layers[layer_surface->current.layer], &layer->link); layer->layer = layer_surface->current.layer; } phoc_layer_shell_arrange (output); phoc_layer_shell_update_focus (); // Cursor changes which happen as a consequence of resizing a layer // surface are applied in phoc_layer_shell_arrange. Because the resize happens // before the underlying surface changes, it will only receive a cursor // update if the new cursor position crosses the *old* sized surface in // the *new* layer surface. // Another cursor move event is needed when the surface actually // changes. struct wlr_surface *surface = layer_surface->surface; if (surface->previous.width != surface->current.width || surface->previous.height != surface->current.height) { update_cursors(layer, phoc_input_get_seats (server->input)); } bool geo_changed = memcmp(&old_geo, &layer->geo, sizeof(struct wlr_box)) != 0; if (geo_changed || layer_changed) { phoc_output_damage_whole_local_surface(output, layer_surface->surface, old_geo.x, old_geo.y); phoc_output_damage_whole_local_surface(output, layer_surface->surface, layer->geo.x, layer->geo.y); } else { phoc_output_damage_from_local_surface(output, layer_surface->surface, layer->geo.x, layer->geo.y); } } } static void handle_destroy(struct wl_listener *listener, void *data) { PhocLayerSurface *layer = wl_container_of( listener, layer, destroy); g_object_unref (layer); } static void subsurface_destroy(struct roots_layer_subsurface *subsurface) { wl_list_remove(&subsurface->map.link); wl_list_remove(&subsurface->unmap.link); wl_list_remove(&subsurface->destroy.link); wl_list_remove(&subsurface->commit.link); wl_list_remove(&subsurface->link); free(subsurface); } static struct roots_layer_popup *popup_create(struct wlr_xdg_popup *wlr_popup); static struct roots_layer_subsurface *layer_subsurface_create(struct wlr_subsurface *wlr_subsurface); static PhocLayerSurface *popup_get_root_layer(struct roots_layer_popup *popup) { while (popup->parent_type == LAYER_PARENT_POPUP) { popup = popup->parent_popup; } return popup->parent_layer; } static void popup_unconstrain(struct roots_layer_popup *popup) { PhocLayerSurface *layer = popup_get_root_layer(popup); struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; PhocOutput *output = phoc_layer_surface_get_output (layer); // the output box expressed in the coordinate system of the toplevel parent // of the popup struct wlr_box output_toplevel_sx_box = { .x = -layer->geo.x, .y = -layer->geo.y, .width = output->usable_area.width, .height = output->usable_area.height, }; wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box); } static void popup_damage(struct roots_layer_popup *layer_popup, bool whole) { struct wlr_xdg_popup *popup = layer_popup->wlr_popup; struct wlr_surface *surface = popup->base->surface; int popup_sx = popup->geometry.x - popup->base->geometry.x; int popup_sy = popup->geometry.y - popup->base->geometry.y; int ox = popup_sx, oy = popup_sy; PhocLayerSurface *layer; while (layer_popup->parent_type == LAYER_PARENT_POPUP) { layer_popup = layer_popup->parent_popup; ox += layer_popup->wlr_popup->geometry.x; oy += layer_popup->wlr_popup->geometry.y; } layer = layer_popup->parent_layer; ox += layer->geo.x; oy += layer->geo.y; PhocOutput *output = phoc_layer_surface_get_output (layer); if (!output) { return; } if (whole) { phoc_output_damage_whole_local_surface(output, surface, ox, oy); } else { phoc_output_damage_from_local_surface(output, surface, ox, oy); } } static void popup_new_popup(struct wl_listener *listener, void *data) { struct roots_layer_popup *popup = wl_container_of(listener, popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; struct roots_layer_popup *new_popup = popup_create(wlr_popup); new_popup->parent_type = LAYER_PARENT_POPUP; new_popup->parent_popup = popup; popup_unconstrain(new_popup); } static void popup_new_subsurface(struct wl_listener *listener, void *data) { struct roots_layer_popup *popup = wl_container_of(listener, popup, new_subsurface); struct wlr_subsurface *wlr_subsurface = data; struct roots_layer_subsurface *subsurface = layer_subsurface_create(wlr_subsurface); subsurface->parent_type = LAYER_PARENT_POPUP; subsurface->parent_popup = popup; wl_list_insert(&popup->subsurfaces, &subsurface->link); } static void popup_handle_map(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); struct roots_layer_popup *popup = wl_container_of(listener, popup, map); PhocLayerSurface *layer = popup_get_root_layer(popup); struct wlr_output *wlr_output = layer->layer_surface->output; if (!wlr_output) { return; } struct wlr_subsurface *child; wl_list_for_each(child, &popup->wlr_popup->base->surface->subsurfaces_below, parent_link) { struct roots_layer_subsurface *new_subsurface = layer_subsurface_create(child); new_subsurface->parent_type = LAYER_PARENT_POPUP; new_subsurface->parent_popup = popup; wl_list_insert(&popup->subsurfaces, &new_subsurface->link); } wl_list_for_each(child, &popup->wlr_popup->base->surface->subsurfaces_above, parent_link) { struct roots_layer_subsurface *new_subsurface = layer_subsurface_create(child); new_subsurface->parent_type = LAYER_PARENT_POPUP; new_subsurface->parent_popup = popup; wl_list_insert(&popup->subsurfaces, &new_subsurface->link); } popup->new_subsurface.notify = popup_new_subsurface; wl_signal_add(&popup->wlr_popup->base->surface->events.new_subsurface, &popup->new_subsurface); wlr_surface_send_enter(popup->wlr_popup->base->surface, wlr_output); popup_damage(popup, true); phoc_input_update_cursor_focus(server->input); } static void popup_handle_unmap(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); struct roots_layer_popup *popup = wl_container_of(listener, popup, unmap); struct roots_layer_subsurface *child, *tmp; wl_list_for_each_safe(child, tmp, &popup->subsurfaces, link) { subsurface_destroy(child); } wl_list_remove(&popup->new_subsurface.link); popup_damage(popup, true); phoc_input_update_cursor_focus(server->input); } static void popup_handle_commit(struct wl_listener *listener, void *data) { struct roots_layer_popup *popup = wl_container_of(listener, popup, commit); popup_damage(popup, false); } static void popup_handle_destroy(struct wl_listener *listener, void *data) { struct roots_layer_popup *popup = wl_container_of(listener, popup, destroy); wl_list_remove(&popup->map.link); wl_list_remove(&popup->unmap.link); wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->commit.link); wl_list_remove(&popup->new_popup.link); free(popup); } static struct roots_layer_popup *popup_create(struct wlr_xdg_popup *wlr_popup) { struct roots_layer_popup *popup = calloc(1, sizeof(struct roots_layer_popup)); if (popup == NULL) { return NULL; } popup->wlr_popup = wlr_popup; popup->map.notify = popup_handle_map; wl_signal_add(&wlr_popup->base->events.map, &popup->map); popup->unmap.notify = popup_handle_unmap; wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); popup->destroy.notify = popup_handle_destroy; wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); popup->commit.notify = popup_handle_commit; wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); popup->new_popup.notify = popup_new_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); wl_list_init(&popup->subsurfaces); return popup; } static void handle_new_popup(struct wl_listener *listener, void *data) { PhocLayerSurface *roots_layer_surface = wl_container_of(listener, roots_layer_surface, new_popup); struct wlr_xdg_popup *wlr_popup = data; struct roots_layer_popup *popup = popup_create(wlr_popup); popup->parent_type = LAYER_PARENT_LAYER; popup->parent_layer = roots_layer_surface; popup_unconstrain(popup); } static PhocLayerSurface *subsurface_get_root_layer(struct roots_layer_subsurface *subsurface) { while (subsurface->parent_type == LAYER_PARENT_SUBSURFACE) { subsurface = subsurface->parent_subsurface; } if (subsurface->parent_type == LAYER_PARENT_POPUP) { return popup_get_root_layer(subsurface->parent_popup); } return subsurface->parent_layer; } static void subsurface_damage(struct roots_layer_subsurface *subsurface, bool whole) { PhocLayerSurface *layer = subsurface_get_root_layer(subsurface); PhocOutput *output = phoc_layer_surface_get_output (layer); if (!output) { return; } int ox = subsurface->wlr_subsurface->current.x + layer->geo.x; int oy = subsurface->wlr_subsurface->current.y + layer->geo.y; if (whole) { phoc_output_damage_whole_local_surface(output, subsurface->wlr_subsurface->surface, ox, oy); } else { phoc_output_damage_from_local_surface(output, subsurface->wlr_subsurface->surface, ox, oy); } } static void subsurface_new_subsurface(struct wl_listener *listener, void *data) { struct roots_layer_subsurface *subsurface = wl_container_of(listener, subsurface, new_subsurface); struct wlr_subsurface *wlr_subsurface = data; struct roots_layer_subsurface *new_subsurface = layer_subsurface_create(wlr_subsurface); new_subsurface->parent_type = LAYER_PARENT_SUBSURFACE; new_subsurface->parent_subsurface = subsurface; wl_list_insert(&subsurface->subsurfaces, &new_subsurface->link); } static void subsurface_handle_map(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); struct roots_layer_subsurface *subsurface = wl_container_of(listener, subsurface, map); struct wlr_subsurface *child; wl_list_for_each(child, &subsurface->wlr_subsurface->surface->subsurfaces_below, parent_link) { struct roots_layer_subsurface *new_subsurface = layer_subsurface_create(child); new_subsurface->parent_type = LAYER_PARENT_SUBSURFACE; new_subsurface->parent_subsurface = subsurface; wl_list_insert(&subsurface->subsurfaces, &new_subsurface->link); } wl_list_for_each(child, &subsurface->wlr_subsurface->surface->subsurfaces_above, parent_link) { struct roots_layer_subsurface *new_subsurface = layer_subsurface_create(child); new_subsurface->parent_type = LAYER_PARENT_SUBSURFACE; new_subsurface->parent_subsurface = subsurface; wl_list_insert(&subsurface->subsurfaces, &new_subsurface->link); } subsurface->new_subsurface.notify = subsurface_new_subsurface; wl_signal_add(&subsurface->wlr_subsurface->surface->events.new_subsurface, &subsurface->new_subsurface); wlr_surface_send_enter(subsurface->wlr_subsurface->surface, subsurface_get_root_layer(subsurface)->layer_surface->output); subsurface_damage(subsurface, true); phoc_input_update_cursor_focus(server->input); } static void subsurface_handle_unmap(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); struct roots_layer_subsurface *subsurface = wl_container_of(listener, subsurface, unmap); struct roots_layer_subsurface *child, *tmp; wl_list_for_each_safe(child, tmp, &subsurface->subsurfaces, link) { subsurface_destroy(child); } wl_list_remove(&subsurface->new_subsurface.link); subsurface_damage(subsurface, true); phoc_input_update_cursor_focus(server->input); } static void subsurface_handle_commit(struct wl_listener *listener, void *data) { struct roots_layer_subsurface *subsurface = wl_container_of(listener, subsurface, commit); subsurface_damage(subsurface, false); } static void subsurface_handle_destroy(struct wl_listener *listener, void *data) { struct roots_layer_subsurface *subsurface = wl_container_of(listener, subsurface, destroy); subsurface_destroy(subsurface); } static struct roots_layer_subsurface *layer_subsurface_create(struct wlr_subsurface *wlr_subsurface) { struct roots_layer_subsurface *subsurface = calloc(1, sizeof(struct roots_layer_subsurface)); if (subsurface == NULL) { return NULL; } subsurface->wlr_subsurface = wlr_subsurface; subsurface->map.notify = subsurface_handle_map; wl_signal_add(&wlr_subsurface->events.map, &subsurface->map); subsurface->unmap.notify = subsurface_handle_unmap; wl_signal_add(&wlr_subsurface->events.unmap, &subsurface->unmap); subsurface->destroy.notify = subsurface_handle_destroy; wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy); subsurface->commit.notify = subsurface_handle_commit; wl_signal_add(&wlr_subsurface->surface->events.commit, &subsurface->commit); wl_list_init(&subsurface->subsurfaces); wl_list_init(&subsurface->link); return subsurface; } static void handle_new_subsurface(struct wl_listener *listener, void *data) { PhocLayerSurface *roots_layer_surface = wl_container_of(listener, roots_layer_surface, new_subsurface); struct wlr_subsurface *wlr_subsurface = data; struct roots_layer_subsurface *subsurface = layer_subsurface_create(wlr_subsurface); subsurface->parent_type = LAYER_PARENT_LAYER; subsurface->parent_layer = roots_layer_surface; wl_list_insert(&roots_layer_surface->subsurfaces, &subsurface->link); } static void handle_map(struct wl_listener *listener, void *data) { struct wlr_layer_surface_v1 *layer_surface = data; PhocLayerSurface *layer = PHOC_LAYER_SURFACE (layer_surface->data); PhocOutput *output = phoc_layer_surface_get_output (layer); if (!output) { return; } struct wlr_subsurface *subsurface; wl_list_for_each(subsurface, &layer_surface->surface->subsurfaces_below, parent_link) { struct roots_layer_subsurface *roots_subsurface = layer_subsurface_create(subsurface); roots_subsurface->parent_type = LAYER_PARENT_LAYER; roots_subsurface->parent_layer = layer; wl_list_insert(&layer->subsurfaces, &roots_subsurface->link); } wl_list_for_each(subsurface, &layer_surface->surface->subsurfaces_above, parent_link) { struct roots_layer_subsurface *roots_subsurface = layer_subsurface_create(subsurface); roots_subsurface->parent_type = LAYER_PARENT_LAYER; roots_subsurface->parent_layer = layer; wl_list_insert(&layer->subsurfaces, &roots_subsurface->link); } layer->new_subsurface.notify = handle_new_subsurface; wl_signal_add(&layer_surface->surface->events.new_subsurface, &layer->new_subsurface); phoc_output_damage_whole_local_surface(output, layer_surface->surface, layer->geo.x, layer->geo.y); wlr_surface_send_enter(layer_surface->surface, output->wlr_output); } static void handle_unmap(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocLayerSurface *layer = wl_container_of( listener, layer, unmap); struct roots_layer_subsurface *subsurface, *tmp; wl_list_for_each_safe(subsurface, tmp, &layer->subsurfaces, link) { subsurface_destroy(subsurface); } wl_list_remove(&layer->new_subsurface.link); phoc_layer_surface_unmap (layer); phoc_input_update_cursor_focus(server->input); } void handle_layer_shell_surface(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); struct wlr_layer_surface_v1 *layer_surface = data; PhocDesktop *desktop = wl_container_of(listener, desktop, layer_shell_surface); g_debug ("new layer surface: namespace %s layer %d anchor %d " "size %dx%d margin %d,%d,%d,%d", layer_surface->namespace, layer_surface->client_pending.layer, layer_surface->client_pending.anchor, layer_surface->client_pending.desired_width, layer_surface->client_pending.desired_height, layer_surface->client_pending.margin.top, layer_surface->client_pending.margin.right, layer_surface->client_pending.margin.bottom, layer_surface->client_pending.margin.left); if (!layer_surface->output) { PhocInput *input = server->input; PhocSeat *seat = phoc_input_get_last_active_seat(input); assert(seat); // Technically speaking we should handle this case PhocCursor *cursor = phoc_seat_get_cursor(seat); struct wlr_output *output = wlr_output_layout_output_at(desktop->layout, cursor->cursor->x, cursor->cursor->y); if (!output) { g_critical ("Couldn't find output at (%.0f,%.0f)", cursor->cursor->x, cursor->cursor->y); output = wlr_output_layout_get_center_output(desktop->layout); } if (output) { layer_surface->output = output; } else { wlr_layer_surface_v1_close(layer_surface); return; } } PhocLayerSurface *roots_surface = phoc_layer_surface_new (); if (!roots_surface) { return; } wl_list_init(&roots_surface->subsurfaces); roots_surface->surface_commit.notify = handle_surface_commit; wl_signal_add(&layer_surface->surface->events.commit, &roots_surface->surface_commit); roots_surface->output_destroy.notify = handle_output_destroy; wl_signal_add(&layer_surface->output->events.destroy, &roots_surface->output_destroy); roots_surface->destroy.notify = handle_destroy; wl_signal_add(&layer_surface->events.destroy, &roots_surface->destroy); roots_surface->map.notify = handle_map; wl_signal_add(&layer_surface->events.map, &roots_surface->map); roots_surface->unmap.notify = handle_unmap; wl_signal_add(&layer_surface->events.unmap, &roots_surface->unmap); roots_surface->new_popup.notify = handle_new_popup; wl_signal_add(&layer_surface->events.new_popup, &roots_surface->new_popup); roots_surface->layer_surface = layer_surface; layer_surface->data = roots_surface; PhocOutput *output = layer_surface->output->data; wl_list_insert(&output->layers[layer_surface->client_pending.layer], &roots_surface->link); // Temporarily set the layer's current state to client_pending // So that we can easily arrange it struct wlr_layer_surface_v1_state old_state = layer_surface->current; layer_surface->current = layer_surface->client_pending; phoc_layer_shell_arrange (output); phoc_layer_shell_update_focus (); layer_surface->current = old_state; } phoc-v0.13.1/src/layers.h000066400000000000000000000024331422111650000151440ustar00rootroot00000000000000#pragma once #include #include #include #include #include "output.h" #include "layer-surface.h" G_BEGIN_DECLS enum layer_parent { LAYER_PARENT_LAYER, LAYER_PARENT_POPUP, LAYER_PARENT_SUBSURFACE }; struct roots_layer_popup { enum layer_parent parent_type; union { PhocLayerSurface *parent_layer; struct roots_layer_popup *parent_popup; }; struct wlr_xdg_popup *wlr_popup; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; struct wl_listener commit; struct wl_listener new_popup; struct wl_listener new_subsurface; struct wl_list subsurfaces; // roots_layer_subsurface::link }; struct roots_layer_subsurface { enum layer_parent parent_type; union { PhocLayerSurface *parent_layer; struct roots_layer_popup *parent_popup; struct roots_layer_subsurface *parent_subsurface; }; struct wl_list link; struct wlr_subsurface *wlr_subsurface; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; struct wl_listener commit; struct wl_listener new_subsurface; struct wl_list subsurfaces; // roots_layer_subsurface::link }; void phoc_layer_shell_arrange (PhocOutput *output); void phoc_layer_shell_update_focus (void); G_END_DECLS phoc-v0.13.1/src/main.c000066400000000000000000000070431422111650000145660ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc" #include "config.h" #define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include #include #include #include #include #include "settings.h" #include "server.h" static void print_version (void) { printf ("Phoc %s - Phone compositor\n", PHOC_VERSION); exit (0); } static void setup_signals (void) { sigset_t mask; /* wlroots uses this to talk to xwayland, block it before we spawn other threads */ sigemptyset(&mask); sigaddset(&mask, SIGUSR1); sigprocmask(SIG_BLOCK, &mask, NULL); } static void log_glib(enum wlr_log_importance verbosity, const char *fmt, va_list args) { int level; switch (verbosity) { case WLR_ERROR: level = G_LOG_LEVEL_CRITICAL; break; case WLR_INFO: level = G_LOG_LEVEL_INFO; break; case WLR_DEBUG: level = G_LOG_LEVEL_DEBUG; break; default: g_assert_not_reached (); } g_logv("phoc-wlroots", level, fmt, args); } static GDebugKey debug_keys[] = { { .key = "auto-maximize", .value = PHOC_SERVER_DEBUG_FLAG_AUTO_MAXIMIZE, }, { .key = "damage-tracking", .value = PHOC_SERVER_DEBUG_FLAG_DAMAGE_TRACKING, }, { .key = "no-quit", .value = PHOC_SERVER_DEBUG_FLAG_NO_QUIT, }, { .key = "touch-points", .value = PHOC_SERVER_DEBUG_FLAG_TOUCH_POINTS, }, }; static PhocServerDebugFlags parse_debug_env (void) { const char *debugenv; PhocServerDebugFlags flags = PHOC_SERVER_DEBUG_FLAG_NONE; debugenv = g_getenv("PHOC_DEBUG"); if (!debugenv) return flags; return g_parse_debug_string(debugenv, debug_keys, G_N_ELEMENTS (debug_keys)); } int main(int argc, char **argv) { g_autoptr(GOptionContext) opt_context = NULL; g_autoptr(GError) err = NULL; g_autoptr(GMainLoop) loop = NULL; g_autoptr(PhocServer) server = NULL; g_autofree gchar *config_path = NULL; g_autofree gchar *exec = NULL; PhocServerFlags flags = PHOC_SERVER_FLAG_NONE; PhocServerDebugFlags debug_flags = PHOC_SERVER_DEBUG_FLAG_NONE; gboolean version = FALSE, shell_mode = FALSE; setup_signals(); const GOptionEntry options [] = { {"config", 'C', 0, G_OPTION_ARG_STRING, &config_path, "Path to the configuration file. (default: phoc.ini).", NULL}, {"exec", 'E', 0, G_OPTION_ARG_STRING, &exec, "Command (session) that will be ran at startup", NULL}, {"shell", 'S', 0, G_OPTION_ARG_NONE, &shell_mode, "Whether to expect a shell to attach", NULL}, {"version", 0, 0, G_OPTION_ARG_NONE, &version, "Show version information", NULL}, { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } }; opt_context = g_option_context_new ("- A phone compositor"); g_option_context_add_main_entries (opt_context, options, NULL); if (!g_option_context_parse (opt_context, &argc, &argv, &err)) { g_warning ("%s", err->message); g_clear_error (&err); return 1; } if (version) { print_version (); } debug_flags = parse_debug_env (); wlr_log_init(WLR_DEBUG, log_glib); server = phoc_server_get_default (); if (server == NULL) { /* phoc_server_get_default already printed an error */ return 1; } if (shell_mode) flags |= PHOC_SERVER_FLAG_SHELL_MODE; loop = g_main_loop_new (NULL, FALSE); if (!phoc_server_setup (server, config_path, exec, loop, flags, debug_flags)) return 1; g_main_loop_run (loop); return phoc_server_get_session_exit_status (server); } phoc-v0.13.1/src/meson.build000066400000000000000000000045211422111650000156360ustar00rootroot00000000000000phoc_enum_headers = files( [ 'phosh-private.h', ]) phoc_enum_sources = gnome.mkenums_simple( 'phoc-enums', sources : phoc_enum_headers) sources = files( 'cursor.c', 'cursor.h', 'desktop.c', 'desktop.h', 'gtk-shell.c', 'gtk-shell.h', 'input.c', 'input.h', 'input-device.c', 'input-device.h', 'keyboard.c', 'keyboard.h', 'keybindings.c', 'keybindings.h', 'layer-surface.c', 'layer-surface.h', 'layer_shell.c', 'layers.h', 'output.c', 'output.h', 'phosh-private.c', 'phosh-private.h', 'pointer.c', 'pointer.h', 'render.c', 'render.h', 'seat.c', 'seat.h', 'server.c', 'server.h', 'settings.c', 'settings.h', 'switch.c', 'switch.h', 'tablet.c', 'tablet.h', 'text_input.c', 'text_input.h', 'touch.c', 'touch.h', 'utils.c', 'utils.h', 'view.c', 'view.h', 'virtual.c', 'virtual.h', 'xdg-activation-v1.c', 'xdg-activation-v1.h', 'xdg_shell.c', 'xdg-surface.c', 'xdg-surface.h', ) libphoc_generated_sources = [ phoc_enum_sources, server_protos_headers, protos_sources, ] phoc_deps = [ input, drm, gio, glesv2, gnome_desktop, gsettings_desktop_schemas_dep, math, pixman, wayland_server, wlroots, xkbcommon, ] if have_xwayland sources += ['xwayland.c', 'xwayland-surface.c', 'xwayland-surface.h', ] phoc_deps += dependency('xcb') endif phoc_lib = static_library( 'phoc', sources, libphoc_generated_sources, dependencies: phoc_deps, install: false, ) libphoc_dep = declare_dependency( include_directories: [include_directories('.'), protocol_inc], link_with: phoc_lib, dependencies: phoc_deps, sources: libphoc_generated_sources) if get_option('gtk_doc') phoc_gir_extra_args = [ '--quiet', ] if have_xwayland phoc_gir_extra_args += '-DPHOC_XWAYLAND' endif phoc_gir = gnome.generate_gir(phoc_lib, sources: sources, nsversion: '0', namespace: 'Phoc', export_packages: 'phoc-0', symbol_prefix: 'phoc', identifier_prefix: 'Phoc', includes: ['Gio-2.0'], install: false, extra_args: phoc_gir_extra_args, dependencies: [phoc_deps, libphoc_dep], ) endif executable( 'phoc', sources: 'main.c', dependencies: libphoc_dep, install: true, ) phoc-v0.13.1/src/output.c000066400000000000000000000736221422111650000152100ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-output" #include "config.h" #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include "settings.h" #include "layers.h" #include "output.h" #include "render.h" #include "seat.h" #include "server.h" #include "utils.h" #include "xwayland-surface.h" G_DEFINE_TYPE (PhocOutput, phoc_output, G_TYPE_OBJECT); enum { PROP_0, PROP_DESKTOP, PROP_WLR_OUTPUT, PROP_LAST_PROP }; static GParamSpec *props[PROP_LAST_PROP]; enum { OUTPUT_DESTROY, N_SIGNALS }; static guint signals[N_SIGNALS] = { 0 }; typedef struct { PhocSurfaceIterator user_iterator; void *user_data; PhocOutput *output; double ox, oy; int width, height; float rotation, scale; } PhocOutputSurfaceIteratorData; static bool get_surface_box (PhocOutputSurfaceIteratorData *data, struct wlr_surface *surface, int sx, int sy, struct wlr_box *surface_box) { PhocOutput *self = data->output; if (!wlr_surface_has_buffer (surface)) { return false; } int sw = surface->current.width; int sh = surface->current.height; double _sx = sx + surface->sx; double _sy = sy + surface->sy; phoc_utils_rotate_child_position (&_sx, &_sy, sw, sh, data->width, data->height, data->rotation); struct wlr_box box = { .x = data->ox + _sx, .y = data->oy + _sy, .width = sw, .height = sh, }; if (surface_box != NULL) { *surface_box = box; } struct wlr_box rotated_box; wlr_box_rotated_bounds (&rotated_box, &box, data->rotation); struct wlr_box output_box = {0}; wlr_output_effective_resolution (self->wlr_output, &output_box.width, &output_box.height); phoc_output_scale_box (self, &output_box, 1 / data->scale); struct wlr_box intersection; return wlr_box_intersection (&intersection, &output_box, &rotated_box); } static void phoc_output_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PhocOutput *self = PHOC_OUTPUT (object); switch (property_id) { case PROP_DESKTOP: self->desktop = g_value_dup_object (value); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DESKTOP]); break; case PROP_WLR_OUTPUT: self->wlr_output = g_value_get_pointer (value); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_WLR_OUTPUT]); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void phoc_output_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PhocOutput *self = PHOC_OUTPUT (object); switch (property_id) { case PROP_DESKTOP: g_value_set_object (value, self->desktop); break; case PROP_WLR_OUTPUT: g_value_set_pointer (value, self->wlr_output); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void phoc_output_init (PhocOutput *self) { } PhocOutput * phoc_output_new (PhocDesktop *desktop, struct wlr_output *wlr_output) { return g_object_new (PHOC_TYPE_OUTPUT, "desktop", desktop, "wlr-output", wlr_output, NULL); } static void update_output_manager_config (PhocDesktop *desktop) { struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create (); PhocOutput *output; wl_list_for_each (output, &desktop->outputs, link) { struct wlr_output_configuration_head_v1 *config_head = wlr_output_configuration_head_v1_create (config, output->wlr_output); struct wlr_box *output_box = wlr_output_layout_get_box ( output->desktop->layout, output->wlr_output); if (output_box) { config_head->state.x = output_box->x; config_head->state.y = output_box->y; } } wlr_output_manager_v1_set_configuration (desktop->output_manager_v1, config); } static void phoc_output_handle_destroy (struct wl_listener *listener, void *data) { PhocOutput *self = wl_container_of (listener, self, output_destroy); update_output_manager_config (self->desktop); g_signal_emit (self, signals[OUTPUT_DESTROY], 0); } static void phoc_output_handle_enable (struct wl_listener *listener, void *data) { PhocOutput *self = wl_container_of (listener, self, enable); update_output_manager_config (self->desktop); } static void phoc_output_damage_handle_frame (struct wl_listener *listener, void *data) { PhocOutput *self = wl_container_of (listener, self, damage_frame); PhocServer *server = phoc_server_get_default (); PhocRenderer *renderer = phoc_server_get_renderer (server); phoc_renderer_render_output (renderer, self); } static void phoc_output_damage_handle_destroy (struct wl_listener *listener, void *data) { PhocOutput *self = wl_container_of (listener, self, damage_destroy); g_assert (PHOC_IS_OUTPUT (self)); if (self->fullscreen_view) phoc_view_set_fullscreen (self->fullscreen_view, false, NULL); wl_list_remove (&self->damage_frame.link); wl_list_remove (&self->damage_destroy.link); } static void phoc_output_handle_mode (struct wl_listener *listener, void *data) { PhocOutput *self = wl_container_of (listener, self, mode); phoc_layer_shell_arrange (self); update_output_manager_config (self->desktop); } static void phoc_output_handle_commit (struct wl_listener *listener, void *data) { PhocOutput *self = wl_container_of (listener, self, commit); phoc_layer_shell_arrange (self); } static void phoc_output_set_mode (struct wlr_output *output, PhocOutputConfig *oc) { int mhz = (int)(oc->mode.refresh_rate * 1000); if (wl_list_empty (&output->modes)) { // Output has no mode, try setting a custom one wlr_output_set_custom_mode (output, oc->mode.width, oc->mode.height, mhz); return; } struct wlr_output_mode *mode, *best = NULL; wl_list_for_each (mode, &output->modes, link) { if (mode->width == oc->mode.width && mode->height == oc->mode.height) { if (mode->refresh == mhz) { best = mode; break; } best = mode; } } if (!best) { g_warning ("Configured mode for %s not available", output->name); } else { g_debug ("Assigning configured mode to %s", output->name); wlr_output_set_mode (output, best); } } static void phoc_output_constructed (GObject *object) { PhocOutput *self = PHOC_OUTPUT (object); PhocServer *server = phoc_server_get_default (); PhocInput *input = server->input; assert (server->desktop); PhocConfig *config = self->desktop->config; g_message ("Output '%s' added ('%s'/'%s'/'%s'), " "%" PRId32 "mm x %" PRId32 "mm", self->wlr_output->name, self->wlr_output->make, self->wlr_output->model, self->wlr_output->serial, self->wlr_output->phys_width, self->wlr_output->phys_height); self->wlr_output->data = self; wl_list_insert (&self->desktop->outputs, &self->link); self->damage = wlr_output_damage_create (self->wlr_output); self->debug_touch_points = NULL; self->output_destroy.notify = phoc_output_handle_destroy; wl_signal_add (&self->wlr_output->events.destroy, &self->output_destroy); self->enable.notify = phoc_output_handle_enable; wl_signal_add (&self->wlr_output->events.enable, &self->enable); self->mode.notify = phoc_output_handle_mode; wl_signal_add (&self->wlr_output->events.mode, &self->mode); self->commit.notify = phoc_output_handle_commit; wl_signal_add (&self->wlr_output->events.commit, &self->commit); self->damage_frame.notify = phoc_output_damage_handle_frame; wl_signal_add (&self->damage->events.frame, &self->damage_frame); self->damage_destroy.notify = phoc_output_damage_handle_destroy; wl_signal_add (&self->damage->events.destroy, &self->damage_destroy); for (size_t i = 0; i < G_N_ELEMENTS (self->layers); ++i) wl_list_init (&self->layers[i]); PhocOutputConfig *output_config = phoc_config_get_output (config, self->wlr_output); struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode (self->wlr_output); if (output_config) { if (output_config->enable) { if (wlr_output_is_drm (self->wlr_output)) { PhocOutputModeConfig *mode_config; wl_list_for_each (mode_config, &output_config->modes, link) { wlr_drm_connector_add_mode (self->wlr_output, &mode_config->info); } } else if (!wl_list_empty (&output_config->modes)) { g_warning ("Can only add modes for DRM backend"); } if (output_config->mode.width) { phoc_output_set_mode (self->wlr_output, output_config); } else if (preferred_mode != NULL) { wlr_output_set_mode (self->wlr_output, preferred_mode); } wlr_output_set_scale (self->wlr_output, output_config->scale); wlr_output_set_transform (self->wlr_output, output_config->transform); wlr_output_layout_add (self->desktop->layout, self->wlr_output, output_config->x, output_config->y); } else { wlr_output_enable (self->wlr_output, false); } } else { if (preferred_mode != NULL) { wlr_output_set_mode (self->wlr_output, preferred_mode); } wlr_output_enable (self->wlr_output, true); wlr_output_layout_add_auto (self->desktop->layout, self->wlr_output); } wlr_output_commit (self->wlr_output); for (GSList *elem = phoc_input_get_seats (input); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); phoc_seat_configure_cursor (seat); phoc_seat_configure_xcursor (seat); } phoc_layer_shell_arrange (self); phoc_layer_shell_update_focus (); phoc_output_damage_whole (self); update_output_manager_config (self->desktop); G_OBJECT_CLASS (phoc_output_parent_class)->constructed (object); } static void phoc_output_finalize (GObject *object) { PhocOutput *self = PHOC_OUTPUT (object); wl_list_remove (&self->link); wl_list_remove (&self->enable.link); wl_list_remove (&self->mode.link); wl_list_remove (&self->commit.link); wl_list_remove (&self->output_destroy.link); g_clear_list (&self->debug_touch_points, g_free); for (size_t i = 0; i < G_N_ELEMENTS (self->layers); ++i) wl_list_remove (&self->layers[i]); g_clear_object (&self->desktop); G_OBJECT_CLASS (phoc_output_parent_class)->finalize (object); } static void phoc_output_class_init (PhocOutputClass *klass) { GObjectClass *object_class = (GObjectClass *)klass; object_class->set_property = phoc_output_set_property; object_class->get_property = phoc_output_get_property; object_class->constructed = phoc_output_constructed; object_class->finalize = phoc_output_finalize; props[PROP_DESKTOP] = g_param_spec_object ( "desktop", "Desktop", "The desktop object", PHOC_TYPE_DESKTOP, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); props[PROP_WLR_OUTPUT] = g_param_spec_pointer ( "wlr-output", "wlr-output", "The wlroots output object", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); signals[OUTPUT_DESTROY] = g_signal_new ("output-destroyed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } static void phoc_output_for_each_surface_iterator (struct wlr_surface *surface, int sx, int sy, void *_data) { PhocOutputSurfaceIteratorData *data = _data; struct wlr_box box; bool intersects = get_surface_box (data, surface, sx, sy, &box); if (!intersects) { return; } data->user_iterator (data->output, surface, &box, data->rotation, data->scale, data->user_data); } void phoc_output_surface_for_each_surface (PhocOutput *self, struct wlr_surface *surface, double ox, double oy, PhocSurfaceIterator iterator, void *user_data) { PhocOutputSurfaceIteratorData data = { .user_iterator = iterator, .user_data = user_data, .output = self, .ox = ox, .oy = oy, .width = surface->current.width, .height = surface->current.height, .rotation = 0, .scale = 1.0 }; wlr_surface_for_each_surface (surface, phoc_output_for_each_surface_iterator, &data); } void phoc_output_xdg_surface_for_each_surface (PhocOutput *self, struct wlr_xdg_surface *xdg_surface, double ox, double oy, PhocSurfaceIterator iterator, void *user_data) { PhocOutputSurfaceIteratorData data = { .user_iterator = iterator, .user_data = user_data, .output = self, .ox = ox, .oy = oy, .width = xdg_surface->surface->current.width, .height = xdg_surface->surface->current.height, .rotation = 0, .scale = 1.0 }; wlr_xdg_surface_for_each_surface (xdg_surface, phoc_output_for_each_surface_iterator, &data); } void phoc_output_view_for_each_surface (PhocOutput *self, PhocView *view, PhocSurfaceIterator iterator, void *user_data) { struct wlr_box *output_box = wlr_output_layout_get_box (self->desktop->layout, self->wlr_output); if (!output_box) { return; } PhocOutputSurfaceIteratorData data = { .user_iterator = iterator, .user_data = user_data, .output = self, .ox = view->box.x - output_box->x, .oy = view->box.y - output_box->y, .width = view->box.width, .height = view->box.height, .rotation = 0, .scale = view->scale }; view_for_each_surface (view, phoc_output_for_each_surface_iterator, &data); } #ifdef PHOC_XWAYLAND void phoc_output_xwayland_children_for_each_surface (PhocOutput *self, struct wlr_xwayland_surface *surface, PhocSurfaceIterator iterator, void *user_data) { struct wlr_box *output_box = wlr_output_layout_get_box (self->desktop->layout, self->wlr_output); if (!output_box) { return; } struct wlr_xwayland_surface *child; wl_list_for_each (child, &surface->children, parent_link) { if (child->mapped) { double ox = child->x - output_box->x; double oy = child->y - output_box->y; phoc_output_surface_for_each_surface (self, child->surface, ox, oy, iterator, user_data); } phoc_output_xwayland_children_for_each_surface (self, child, iterator, user_data); } } #endif static void phoc_output_layer_handle_surface (PhocOutput *self, PhocLayerSurface *layer_surface, PhocSurfaceIterator iterator, void *user_data) { struct wlr_layer_surface_v1 *wlr_layer_surface_v1 = layer_surface->layer_surface; phoc_output_surface_for_each_surface (self, wlr_layer_surface_v1->surface, layer_surface->geo.x, layer_surface->geo.y, iterator, user_data); struct wlr_xdg_popup *state; wl_list_for_each (state, &wlr_layer_surface_v1->popups, link) { struct wlr_xdg_surface *popup = state->base; if (!popup->configured) { continue; } double popup_sx, popup_sy; popup_sx = layer_surface->geo.x; popup_sx += popup->popup->geometry.x - popup->geometry.x; popup_sy = layer_surface->geo.y; popup_sy += popup->popup->geometry.y - popup->geometry.y; phoc_output_xdg_surface_for_each_surface (self, popup, popup_sx, popup_sy, iterator, user_data); } } void phoc_output_layer_for_each_surface (PhocOutput *self, struct wl_list *layer_surfaces, PhocSurfaceIterator iterator, void *user_data) { PhocLayerSurface *layer_surface; wl_list_for_each_reverse (layer_surface, layer_surfaces, link) { if (layer_surface->layer_surface->current.exclusive_zone <= 0) { phoc_output_layer_handle_surface (self, layer_surface, iterator, user_data); } } wl_list_for_each (layer_surface, layer_surfaces, link) { if (layer_surface->layer_surface->current.exclusive_zone > 0) { phoc_output_layer_handle_surface (self, layer_surface, iterator, user_data); } } } void phoc_output_drag_icons_for_each_surface (PhocOutput *self, PhocInput *input, PhocSurfaceIterator iterator, void *user_data) { struct wlr_box *output_box = wlr_output_layout_get_box (self->desktop->layout, self->wlr_output); if (!output_box) { return; } for (GSList *elem = phoc_input_get_seats (input); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); PhocDragIcon *drag_icon = seat->drag_icon; if (!drag_icon || !drag_icon->wlr_drag_icon->mapped) { continue; } double ox = drag_icon->x - output_box->x; double oy = drag_icon->y - output_box->y; phoc_output_surface_for_each_surface (self, drag_icon->wlr_drag_icon->surface, ox, oy, iterator, user_data); } } void phoc_output_for_each_surface (PhocOutput *self, PhocSurfaceIterator iterator, void *user_data, gboolean visible_only) { PhocDesktop *desktop = self->desktop; PhocServer *server = phoc_server_get_default (); if (self->fullscreen_view != NULL) { PhocView *view = self->fullscreen_view; phoc_output_view_for_each_surface (self, view, iterator, user_data); #ifdef PHOC_XWAYLAND if (view->type == PHOC_XWAYLAND_VIEW) { PhocXWaylandSurface *xwayland_surface = phoc_xwayland_surface_from_view (view); phoc_output_xwayland_children_for_each_surface (self, xwayland_surface->xwayland_surface, iterator, user_data); } #endif } else { PhocView *view; wl_list_for_each_reverse (view, &desktop->views, link) { if (!visible_only || phoc_desktop_view_is_visible (desktop, view)) { phoc_output_view_for_each_surface (self, view, iterator, user_data); } } } phoc_output_drag_icons_for_each_surface (self, server->input, iterator, user_data); for (size_t i = 0; i < G_N_ELEMENTS (self->layers); ++i) { phoc_output_layer_for_each_surface (self, &self->layers[i], iterator, user_data); } } static int scale_length (int length, int offset, float scale) { return round ((offset + length) * scale) - round (offset * scale); } void phoc_output_scale_box (PhocOutput *self, struct wlr_box *box, float scale) { box->width = scale_length (box->width, box->x, scale); box->height = scale_length (box->height, box->y, scale); box->x = round (box->x * scale); box->y = round (box->y * scale); } void phoc_output_get_decoration_box (PhocOutput *self, PhocView *view, struct wlr_box *box) { struct wlr_box deco_box; view_get_deco_box (view, &deco_box); double x = deco_box.x; double y = deco_box.y; wlr_output_layout_output_coords (self->desktop->layout, self->wlr_output, &x, &y); box->x = x * self->wlr_output->scale; box->y = y * self->wlr_output->scale; box->width = deco_box.width * self->wlr_output->scale; box->height = deco_box.height * self->wlr_output->scale; } void phoc_output_damage_whole (PhocOutput *self) { wlr_output_damage_add_whole (self->damage); } static bool phoc_view_accept_damage (PhocOutput *self, PhocView *view) { PhocServer *server = phoc_server_get_default (); if (!phoc_desktop_view_is_visible (server->desktop, view)) { return false; } if (self->fullscreen_view == NULL) { return true; } if (self->fullscreen_view == view) { return true; } #ifdef PHOC_XWAYLAND if (self->fullscreen_view->type == PHOC_XWAYLAND_VIEW && view->type == PHOC_XWAYLAND_VIEW) { // Special case: accept damage from children struct wlr_xwayland_surface *xsurface = phoc_xwayland_surface_from_view (view)->xwayland_surface; struct wlr_xwayland_surface *fullscreen_xsurface = phoc_xwayland_surface_from_view (self->fullscreen_view)->xwayland_surface; while (xsurface != NULL) { if (fullscreen_xsurface == xsurface) { return true; } xsurface = xsurface->parent; } } #endif return false; } static void damage_surface_iterator (PhocOutput *self, struct wlr_surface *surface, struct wlr_box *_box, float rotation, float scale, void *data) { bool *whole = data; struct wlr_box box = *_box; phoc_output_scale_box (self, &box, scale); phoc_output_scale_box (self, &box, self->wlr_output->scale); int center_x = box.x + box.width/2; int center_y = box.y + box.height/2; if (pixman_region32_not_empty (&surface->buffer_damage)) { pixman_region32_t damage; pixman_region32_init (&damage); wlr_surface_get_effective_damage (surface, &damage); wlr_region_scale (&damage, &damage, scale); wlr_region_scale (&damage, &damage, self->wlr_output->scale); if (ceil (self->wlr_output->scale) > surface->current.scale) { // When scaling up a surface, it'll become blurry so we need to // expand the damage region wlr_region_expand (&damage, &damage, ceil (self->wlr_output->scale) - surface->current.scale); } pixman_region32_translate (&damage, box.x, box.y); wlr_region_rotated_bounds (&damage, &damage, rotation, center_x, center_y); wlr_output_damage_add (self->damage, &damage); pixman_region32_fini (&damage); } if (*whole) { wlr_box_rotated_bounds (&box, &box, rotation); wlr_output_damage_add_box (self->damage, &box); } wlr_output_schedule_frame (self->wlr_output); } void phoc_output_damage_whole_local_surface (PhocOutput *self, struct wlr_surface *surface, double ox, double oy) { bool whole = true; phoc_output_surface_for_each_surface (self, surface, ox, oy, damage_surface_iterator, &whole); } static void damage_whole_view (PhocOutput *self, PhocView *view) { if (!phoc_view_is_mapped (view)) { return; } struct wlr_box box; phoc_output_get_decoration_box (self, view, &box); wlr_output_damage_add_box (self->damage, &box); } /** * phoc_output_damage_from_view: * @self: The output to add damage to * @view: The view providing the damage * @whole: Whether * * Adds a [type@PhocView]'s damage to the damaged area of @self. If * @whole is %TRUE the whole view is damaged (including any window * decorations if they exist). If @whole is %FALSE only buffer damage * is taken into account. * Also schedules a new frame. */ void phoc_output_damage_from_view (PhocOutput *self, PhocView *view, bool whole) { if (!phoc_view_accept_damage (self, view)) { return; } if (whole) damage_whole_view (self, view); phoc_output_view_for_each_surface (self, view, damage_surface_iterator, &whole); } void phoc_output_damage_whole_drag_icon (PhocOutput *self, PhocDragIcon *icon) { bool whole = true; phoc_output_surface_for_each_surface (self, icon->wlr_drag_icon->surface, icon->x, icon->y, damage_surface_iterator, &whole); } void phoc_output_damage_from_local_surface (PhocOutput *self, struct wlr_surface *surface, double ox, double oy) { bool whole = false; phoc_output_surface_for_each_surface (self, surface, ox, oy, damage_surface_iterator, &whole); } void handle_output_manager_apply (struct wl_listener *listener, void *data) { PhocDesktop *desktop = wl_container_of (listener, desktop, output_manager_apply); struct wlr_output_configuration_v1 *config = data; bool ok = true; struct wlr_output_configuration_head_v1 *config_head; // First disable outputs we need to disable wl_list_for_each (config_head, &config->heads, link) { struct wlr_output *wlr_output = config_head->state.output; if (config_head->state.enabled) continue; if (!wlr_output->enabled) continue; wlr_output_enable (wlr_output, false); wlr_output_layout_remove (desktop->layout, wlr_output); ok &= wlr_output_commit (wlr_output); } // Then enable outputs that need to wl_list_for_each (config_head, &config->heads, link) { struct wlr_output *wlr_output = config_head->state.output; PhocOutput *output = wlr_output->data; if (!config_head->state.enabled) continue; wlr_output_enable (wlr_output, true); if (config_head->state.mode != NULL) { wlr_output_set_mode (wlr_output, config_head->state.mode); } else { wlr_output_set_custom_mode (wlr_output, config_head->state.custom_mode.width, config_head->state.custom_mode.height, config_head->state.custom_mode.refresh); } wlr_output_layout_add (desktop->layout, wlr_output, config_head->state.x, config_head->state.y); wlr_output_set_transform (wlr_output, config_head->state.transform); wlr_output_set_scale (wlr_output, config_head->state.scale); ok &= wlr_output_commit (wlr_output); if (output->fullscreen_view) { phoc_view_set_fullscreen (output->fullscreen_view, true, wlr_output); } } if (ok) { wlr_output_configuration_v1_send_succeeded (config); } else { wlr_output_configuration_v1_send_failed (config); } wlr_output_configuration_v1_destroy (config); update_output_manager_config (desktop); } void handle_output_manager_test (struct wl_listener *listener, void *data) { PhocDesktop *desktop = wl_container_of (listener, desktop, output_manager_test); struct wlr_output_configuration_v1 *config = data; // TODO: implement test-only mode wlr_output_configuration_v1_send_succeeded (config); wlr_output_configuration_v1_destroy (config); } void phoc_output_handle_output_power_manager_set_mode (struct wl_listener *listener, void *data) { struct wlr_output_power_v1_set_mode_event *event = data; PhocOutput *self; bool enable = true; g_return_if_fail (event && event->output && event->output->data); self = event->output->data; g_debug ("Request to set output power mode of %p to %d", self, event->mode); switch (event->mode) { case ZWLR_OUTPUT_POWER_V1_MODE_OFF: enable = false; break; case ZWLR_OUTPUT_POWER_V1_MODE_ON: enable = true; break; default: g_warning ("Unhandled power state %d for %p", event->mode, self); return; } if (enable == self->wlr_output->enabled) return; wlr_output_enable (self->wlr_output, enable); if (!wlr_output_commit (self->wlr_output)) { g_warning ("Failed to commit power mode change to %d for %p", enable, self); return; } if (enable) phoc_output_damage_whole (self); } /** * phoc_output_is_builtin: * * Returns: %TRUE if the output a built in panel (e.g. laptop panel or * phone LCD), %FALSE otherwise. */ gboolean phoc_output_is_builtin (PhocOutput *output) { g_return_val_if_fail (output, FALSE); g_return_val_if_fail (output->wlr_output, FALSE); g_return_val_if_fail (output->wlr_output->name, FALSE); if (g_str_has_prefix (output->wlr_output->name, "LVDS-")) return TRUE; else if (g_str_has_prefix (output->wlr_output->name, "eDP-")) return TRUE; else if (g_str_has_prefix (output->wlr_output->name, "DSI-")) return TRUE; return FALSE; } /** * phoc_output_is_match: * @self: The output * @make: The make / vendor name * @model: The model / product name * @serial: The serial number * * Checks if an output matches the given vendor/product/serial information. * This is usually used to match on an outputs EDID information. * * Returns: %TRUE if the output matches the given information, otherwise %FALSE. */ gboolean phoc_output_is_match (PhocOutput *self, const char *make, const char *model, const char *serial) { gboolean match; g_assert (PHOC_IS_OUTPUT (self)); match = (g_strcmp0 (self->wlr_output->make, make) == 0 && g_strcmp0 (self->wlr_output->model, model) == 0 && g_strcmp0 (self->wlr_output->serial, serial) == 0); return match; } /* * phoc_output_has_fullscreen_view: * @self: The #PhocOutput * * Returns: %TRUE if the output has a fullscreen view attached, * %FALSE otherwise. */ gboolean phoc_output_has_fullscreen_view (PhocOutput *self) { g_assert (PHOC_IS_OUTPUT (self)); return self->fullscreen_view != NULL && self->fullscreen_view->wlr_surface != NULL; } phoc-v0.13.1/src/output.h000066400000000000000000000136421422111650000152110ustar00rootroot00000000000000#pragma once #include "view.h" #include #include #include #include #include #include G_BEGIN_DECLS #define PHOC_TYPE_OUTPUT (phoc_output_get_type ()) G_DECLARE_FINAL_TYPE (PhocOutput, phoc_output, PHOC, OUTPUT, GObject); typedef struct _PhocDesktop PhocDesktop; typedef struct _PhocInput PhocInput; /** * PhocOutput: * * The output region of a compositor (typically a monitor). * * See wlroot's #wlr_output. */ /* TODO: we keep the struct public due to the list links and notifiers but we should avoid other member access */ struct _PhocOutput { GObject parent; PhocDesktop *desktop; struct wlr_output *wlr_output; struct wl_list link; // PhocDesktop::outputs PhocView *fullscreen_view; struct wl_list layers[4]; // layer_surface::link bool force_shell_reveal; struct wlr_output_damage *damage; GList *debug_touch_points; struct wlr_box usable_area; struct wl_listener enable; struct wl_listener mode; struct wl_listener commit; struct wl_listener damage_frame; struct wl_listener damage_destroy; struct wl_listener output_destroy; }; PhocOutput *phoc_output_new (PhocDesktop *desktop, struct wlr_output *wlr_output); /* Surface iterators */ typedef void (*PhocSurfaceIterator)(PhocOutput *self, struct wlr_surface *surface, struct wlr_box *box, float rotation, float scale, void *user_data); void phoc_output_xdg_surface_for_each_surface (PhocOutput *self, struct wlr_xdg_surface *xdg_surface, double ox, double oy, PhocSurfaceIterator iterator, void *user_data); void phoc_output_surface_for_each_surface (PhocOutput *self, struct wlr_surface *surface, double ox, double oy, PhocSurfaceIterator iterator, void *user_data); void phoc_output_view_for_each_surface (PhocOutput *self, PhocView *view, PhocSurfaceIterator iterator, void *user_data); void phoc_output_drag_icons_for_each_surface (PhocOutput *self, PhocInput *input, PhocSurfaceIterator iterator, void *user_data); void phoc_output_layer_for_each_surface (PhocOutput *self, struct wl_list *layer_surfaces, PhocSurfaceIterator iterator, void *user_data); #ifdef PHOC_XWAYLAND struct wlr_xwayland_surface; void phoc_output_xwayland_children_for_each_surface (PhocOutput *self, struct wlr_xwayland_surface *surface, PhocSurfaceIterator iterator, void *user_data); #endif void phoc_output_for_each_surface (PhocOutput *self, PhocSurfaceIterator iterator, void *user_data, gboolean visible_only); /* signal handlers */ void handle_output_manager_apply (struct wl_listener *listener, void *data); void handle_output_manager_test (struct wl_listener *listener, void *data); void phoc_output_handle_output_power_manager_set_mode (struct wl_listener *listener, void *data); /* methods */ typedef struct _PhocDragIcon PhocDragIcon; void phoc_output_damage_whole (PhocOutput *output); void phoc_output_damage_from_view (PhocOutput *self, PhocView *view, bool whole); void phoc_output_damage_whole_drag_icon (PhocOutput *self, PhocDragIcon *icon); void phoc_output_damage_from_local_surface (PhocOutput *self, struct wlr_surface *surface, double ox, double oy); void phoc_output_damage_whole_local_surface (PhocOutput *self, struct wlr_surface *surface, double ox, double oy); void phoc_output_scale_box (PhocOutput *self, struct wlr_box *box, float scale); void phoc_output_get_decoration_box (PhocOutput *self, PhocView *view, struct wlr_box *box); gboolean phoc_output_is_builtin (PhocOutput *output); gboolean phoc_output_is_match (PhocOutput *self, const char *make, const char *model, const char *serial); gboolean phoc_output_has_fullscreen_view (PhocOutput *self); G_END_DECLS phoc-v0.13.1/src/phoc.ini.example000066400000000000000000000017661422111650000165700ustar00rootroot00000000000000[core] # X11 support # - true: enables X11, xwayland is started only when an X11 client connects # - immediate: enables X11, xwayland is started immediately # - false: disables xwayland xwayland=false # Single output configuration. String after colon must match output's name. [output:VGA-1] # Set logical (layout) coordinates for this screen x = 1920 y = 0 # Screen transformation # possible values are: # '90', '180' or '270' - rotate output by specified angle clockwise # 'flipped' - flip output horizontally # 'flipped-90', 'flipped-180', 'flipped-270' - flip output horizontally # and rotate by specified angle rotate = 90 # Additional video mode to add # Format is generated by cvt and is documented in x.org.conf(5) modeline = 87.25 720 776 848 976 1440 1443 1453 1493 -hsync +vsync modeline = 65.13 768 816 896 1024 1024 1025 1028 1060 -HSync +VSync # Select one of the above modes mode = 768x1024 [cursor] # Load a custom XCursor theme theme = default phoc-v0.13.1/src/phosh-private.c000066400000000000000000000702511422111650000164340ustar00rootroot00000000000000/* * Copyright (C) 2019,2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #define G_LOG_DOMAIN "phoc-phosh-private" #include "config.h" #include "phoc-enums.h" #include "phosh-private.h" #include #include #include #include #include #include #include #include #include #include #include "server.h" #include "desktop.h" #include "render.h" #include "utils.h" /* help older (0.8.2) libxkbcommon */ #ifndef XKB_KEY_XF86RotationLockToggle # define XKB_KEY_XF86RotationLockToggle 0x1008FFB7 #endif /** * PhocPhoshPrivate: * * Private protocol to interface with phosh */ enum { PROP_0, PROP_SHELL_STATE, PROP_LAST_PROP }; static GParamSpec *props[PROP_LAST_PROP]; struct _PhocPhoshPrivate { GObject parent; guint32 version; struct wl_resource* resource; struct wl_global *global; GList *keyboard_events; guint last_action_id; GList *startup_trackers; PhocPhoshPrivateShellState state; }; G_DEFINE_TYPE (PhocPhoshPrivate, phoc_phosh_private, G_TYPE_OBJECT) typedef struct { GHashTable *subscribed_accelerators; struct wl_resource *resource; PhocPhoshPrivate *phosh; } PhocPhoshPrivateKeyboardEventData; typedef struct { struct wl_resource *resource, *toplevel; struct phosh_private *phosh; struct wl_listener view_destroy; enum wl_shm_format format; uint32_t width; uint32_t height; uint32_t stride; struct wl_shm_buffer *buffer; PhocView *view; } PhocPhoshPrivateScreencopyFrame; typedef struct { struct wl_resource *resource; PhocPhoshPrivate *phosh; } PhocPhoshPrivateStartupTracker; static PhocPhoshPrivate *phoc_phosh_private_from_resource (struct wl_resource *resource); static PhocPhoshPrivateKeyboardEventData *phoc_phosh_private_keyboard_event_from_resource (struct wl_resource *resource); static PhocPhoshPrivateScreencopyFrame *phoc_phosh_private_screencopy_frame_from_resource(struct wl_resource *resource); static PhocPhoshPrivateStartupTracker *phoc_phosh_private_startup_tracker_from_resource(struct wl_resource *resource); #define PHOSH_PRIVATE_VERSION 6 static void phoc_phosh_private_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PhocPhoshPrivate *self = PHOC_PHOSH_PRIVATE (object); switch (property_id) { case PROP_SHELL_STATE: g_value_set_enum (value, self->state); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void handle_rotate_display (struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource, uint32_t degrees) { wl_resource_post_error (resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "Use wlr-output-management protocol instead"); } static void handle_get_xdg_switcher (struct wl_client *client, struct wl_resource *phosh_private_resource, uint32_t id) { int version = wl_resource_get_version (phosh_private_resource); struct wl_resource *resource = wl_resource_create (client, &phosh_private_xdg_switcher_interface, version, id); wl_resource_post_error (resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "Use wlr-toplevel-management protocol instead"); } static void phoc_phosh_private_keyboard_event_destroy (PhocPhoshPrivateKeyboardEventData *kbevent) { PhocPhoshPrivate *phosh; if (kbevent == NULL) return; g_debug ("Destroying private_keyboard_event %p (res %p)", kbevent, kbevent->resource); phosh = kbevent->phosh; g_hash_table_remove_all (kbevent->subscribed_accelerators); g_hash_table_unref (kbevent->subscribed_accelerators); wl_resource_set_user_data (kbevent->resource, NULL); phosh->keyboard_events = g_list_remove (phosh->keyboard_events, kbevent); g_free (kbevent); } static void phoc_phosh_private_keyboard_event_handle_resource_destroy (struct wl_resource *resource) { PhocPhoshPrivateKeyboardEventData *kbevent = phoc_phosh_private_keyboard_event_from_resource (resource); phoc_phosh_private_keyboard_event_destroy (kbevent); } static bool phoc_phosh_private_keyboard_event_accelerator_is_registered (PhocKeyCombo *combo, PhocPhoshPrivateKeyboardEventData *kbevent) { gint64 key = ((gint64) combo->modifiers << 32) | combo->keysym; gpointer ret = g_hash_table_lookup (kbevent->subscribed_accelerators, &key); g_debug ("Accelerator is registered: Lookup -> %p", ret); return (ret != NULL); } static bool phoc_phosh_private_accelerator_already_subscribed (PhocKeyCombo *combo) { GList *l; PhocPhoshPrivateKeyboardEventData *kbevent; PhocServer *server = phoc_server_get_default (); PhocPhoshPrivate *phosh = server->desktop->phosh; for (l = phosh->keyboard_events; l != NULL; l = l->next) { kbevent = (PhocPhoshPrivateKeyboardEventData *)l->data; if (phoc_phosh_private_keyboard_event_accelerator_is_registered (combo, kbevent)) return true; } return false; } static bool keysym_is_subscribeable (PhocKeyCombo *combo) { /* Allow to bind all keys with modifiers that aren't just shift/caps */ if (combo->modifiers >= WLR_MODIFIER_CTRL) return true; /* keys on multi media keyboards */ if (combo->keysym >= XKB_KEY_XF86MonBrightnessUp && combo->keysym <= XKB_KEY_XF86RotationLockToggle) return true; /* misc functions */ if (combo->keysym >= XKB_KEY_Select && combo->keysym <= XKB_KEY_Num_Lock) return true; return false; } static void phoc_phosh_private_keyboard_event_grab_accelerator_request (struct wl_client *wl_client, struct wl_resource *resource, const char *accelerator) { guint new_action_id; gint64 *new_key; PhocPhoshPrivateKeyboardEventData *kbevent = phoc_phosh_private_keyboard_event_from_resource (resource); g_autofree PhocKeyCombo *combo = parse_accelerator (accelerator); if (kbevent == NULL) return; if (combo == NULL) { g_debug ("Failed to parse accelerator %s", accelerator); phosh_private_keyboard_event_send_grab_failed_event (resource, accelerator, PHOSH_PRIVATE_KEYBOARD_EVENT_ERROR_INVALID_KEYSYM); return; } if (phoc_phosh_private_accelerator_already_subscribed (combo)) { g_debug ("Accelerator %s already subscribed to!", accelerator); phosh_private_keyboard_event_send_grab_failed_event (resource, accelerator, PHOSH_PRIVATE_KEYBOARD_EVENT_ERROR_ALREADY_SUBSCRIBED); return; } if (!keysym_is_subscribeable (combo)) { g_debug ("Requested keysym %s is not subscribeable!", accelerator); phosh_private_keyboard_event_send_grab_failed_event (resource, accelerator, PHOSH_PRIVATE_KEYBOARD_EVENT_ERROR_INVALID_KEYSYM); return; } new_action_id = kbevent->phosh->last_action_id++; /* detect wrap-around and make sure we fail from here on out */ if (new_action_id == 0) { g_debug ("Action ID wrap-around detected while trying to subscribe %s", accelerator); phosh_private_keyboard_event_send_grab_failed_event (resource, accelerator, PHOSH_PRIVATE_KEYBOARD_EVENT_ERROR_MISC_ERROR); kbevent->phosh->last_action_id--; return; } new_key = (gint64 *) g_malloc (sizeof (gint64)); *new_key = ((gint64) combo->modifiers << 32) | combo->keysym; /* subscribed accelerators of kbevent */ g_hash_table_insert (kbevent->subscribed_accelerators, new_key, GUINT_TO_POINTER (new_action_id)); phosh_private_keyboard_event_send_grab_success_event (resource, accelerator, new_action_id); g_debug ("Registered accelerator %s (sym %d mod %d) on phosh_private_keyboard_event %p (client %p)", accelerator, combo->keysym, combo->modifiers, kbevent, wl_client); } static void phoc_phosh_private_keyboard_event_ungrab_accelerator_request (struct wl_client *client, struct wl_resource *resource, uint32_t action_id) { GHashTableIter iter; gpointer key, value, found = NULL; PhocPhoshPrivateKeyboardEventData *kbevent = phoc_phosh_private_keyboard_event_from_resource (resource); g_debug ("Ungrabbing accelerator %d", action_id); g_hash_table_iter_init (&iter, kbevent->subscribed_accelerators); while (g_hash_table_iter_next (&iter, &key, &value)) { if (GPOINTER_TO_INT (value) == action_id) { found = key; break; } } if (found) { g_hash_table_remove (kbevent->subscribed_accelerators, key); phosh_private_keyboard_event_send_ungrab_success_event (resource, action_id); } else { phosh_private_keyboard_event_send_ungrab_failed_event (resource, action_id, PHOSH_PRIVATE_KEYBOARD_EVENT_ERROR_INVALID_ARGUMENT); } } static void phoc_phosh_private_keyboard_event_handle_destroy (struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy (resource); } static const struct phosh_private_keyboard_event_interface phoc_phosh_private_keyboard_event_impl = { .grab_accelerator_request = phoc_phosh_private_keyboard_event_grab_accelerator_request, .ungrab_accelerator_request = phoc_phosh_private_keyboard_event_ungrab_accelerator_request, .destroy = phoc_phosh_private_keyboard_event_handle_destroy }; static void handle_get_keyboard_event (struct wl_client *client, struct wl_resource *phosh_private_resource, uint32_t id) { PhocPhoshPrivateKeyboardEventData *kbevent = g_new0 (PhocPhoshPrivateKeyboardEventData, 1); if (kbevent == NULL) { wl_client_post_no_memory (client); return; } int version = wl_resource_get_version (phosh_private_resource); kbevent->resource = wl_resource_create (client, &phosh_private_keyboard_event_interface, version, id); if (kbevent->resource == NULL) { g_free (kbevent); wl_client_post_no_memory (client); return; } kbevent->subscribed_accelerators = g_hash_table_new_full (g_int64_hash, g_int64_equal, g_free, NULL); if (kbevent->subscribed_accelerators == NULL) { wl_resource_destroy (kbevent->resource); g_free (kbevent); wl_client_post_no_memory (client); return; } PhocPhoshPrivate *phosh_private = phoc_phosh_private_from_resource (phosh_private_resource); phosh_private->keyboard_events = g_list_append (phosh_private->keyboard_events, kbevent); g_debug ("new phosh_private_keyboard_event %p (res %p)", kbevent, kbevent->resource); wl_resource_set_implementation (kbevent->resource, &phoc_phosh_private_keyboard_event_impl, kbevent, phoc_phosh_private_keyboard_event_handle_resource_destroy); kbevent->phosh = phosh_private; } static void phosh_private_screencopy_frame_handle_resource_destroy (struct wl_resource *resource) { PhocPhoshPrivateScreencopyFrame *frame = phoc_phosh_private_screencopy_frame_from_resource (resource); g_debug ("Destroying private_screencopy_frame %p (res %p)", frame, frame->resource); if (frame->view) { wl_list_remove (&frame->view_destroy.link); } free (frame); } static void thumbnail_view_handle_destroy (struct wl_listener *listener, void *data) { PhocPhoshPrivateScreencopyFrame *frame = wl_container_of (listener, frame, view_destroy); frame->view = NULL; } static void thumbnail_frame_handle_copy (struct wl_client *wl_client, struct wl_resource *frame_resource, struct wl_resource *buffer_resource) { PhocServer *server = phoc_server_get_default (); PhocRenderer *self = phoc_server_get_renderer (server); PhocPhoshPrivateScreencopyFrame *frame = phoc_phosh_private_screencopy_frame_from_resource (frame_resource); g_return_if_fail (frame); if (frame->buffer != NULL) { wl_resource_post_error (frame->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); return; } if (!frame->view) { zwlr_screencopy_frame_v1_send_failed (frame->resource); return; } frame->buffer = wl_shm_buffer_get (buffer_resource); if (frame->buffer == NULL) { wl_resource_post_error (frame->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "unsupported buffer type"); return; } enum wl_shm_format fmt = wl_shm_buffer_get_format (frame->buffer); int32_t width = wl_shm_buffer_get_width (frame->buffer); int32_t height = wl_shm_buffer_get_height (frame->buffer); int32_t stride = wl_shm_buffer_get_stride (frame->buffer); if (fmt != frame->format || width != frame->width || height != frame->height || stride != frame->stride) { wl_resource_post_error (frame->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer attributes"); return; } PhocView *view = frame->view; wl_list_remove (&frame->view_destroy.link); frame->view = NULL; wl_shm_buffer_begin_access (frame->buffer); void *data = wl_shm_buffer_get_data (frame->buffer); uint32_t renderer_flags = 0; if (!phoc_renderer_render_view_to_buffer (self, view, width, height, stride, &renderer_flags, data)) { wl_shm_buffer_end_access (frame->buffer); zwlr_screencopy_frame_v1_send_failed (frame->resource); return; } enum zwlr_screencopy_frame_v1_flags flags = (renderer_flags & WLR_RENDERER_READ_PIXELS_Y_INVERT) ? ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT : 0; wl_shm_buffer_end_access (frame->buffer); zwlr_screencopy_frame_v1_send_flags (frame->resource, flags); struct timespec now; clock_gettime (CLOCK_MONOTONIC, &now); uint32_t tv_sec_hi = (sizeof(now.tv_sec) > 4) ? now.tv_sec >> 32 : 0; uint32_t tv_sec_lo = now.tv_sec & 0xFFFFFFFF; zwlr_screencopy_frame_v1_send_ready (frame->resource, tv_sec_hi, tv_sec_lo, now.tv_nsec); } static void thumbnail_frame_handle_copy_with_damage (struct wl_client *wl_client, struct wl_resource *frame_resource, struct wl_resource *buffer_resource) { // XXX: unimplemented (void)wl_client; (void)buffer_resource; zwlr_screencopy_frame_v1_send_failed (frame_resource); } static void thumbnail_frame_handle_destroy (struct wl_client *wl_client, struct wl_resource *frame_resource) { wl_resource_destroy (frame_resource); } static const struct zwlr_screencopy_frame_v1_interface phoc_phosh_private_screencopy_frame_impl = { .copy = thumbnail_frame_handle_copy, .destroy = thumbnail_frame_handle_destroy, .copy_with_damage = thumbnail_frame_handle_copy_with_damage, }; static void handle_get_thumbnail (struct wl_client *client, struct wl_resource *phosh_private_resource, uint32_t id, struct wl_resource *toplevel, uint32_t max_width, uint32_t max_height) { PhocPhoshPrivateScreencopyFrame *frame = g_new0 (PhocPhoshPrivateScreencopyFrame, 1); if (frame == NULL) { wl_client_post_no_memory (client); return; } int version = wl_resource_get_version (phosh_private_resource); frame->resource = wl_resource_create (client, &zwlr_screencopy_frame_v1_interface, version, id); if (frame->resource == NULL) { free (frame); wl_client_post_no_memory (client); return; } g_debug ("new phosh_private_screencopy_frame %p (res %p)", frame, frame->resource); wl_resource_set_implementation (frame->resource, &phoc_phosh_private_screencopy_frame_impl, frame, phosh_private_screencopy_frame_handle_resource_destroy); struct wlr_foreign_toplevel_handle_v1 *toplevel_handle = wl_resource_get_user_data (toplevel); if (!toplevel_handle) { zwlr_screencopy_frame_v1_send_failed (frame->resource); return; } PhocView *view = toplevel_handle->data; if (!view) { zwlr_screencopy_frame_v1_send_failed (frame->resource); return; } frame->toplevel = toplevel; frame->view = view; frame->view_destroy.notify = thumbnail_view_handle_destroy; wl_signal_add (&frame->view->events.destroy, &frame->view_destroy); // We hold to the current surface size even though it may change before // the frame is actually rendered. wlr-screencopy doesn't give much // flexibility there, but since the worst thing that may happen in such // case is a rescaled thumbnail with wrong aspect ratio we take the liberty // to ignore it, at least for now. struct wlr_box box; view_get_box (view, &box); frame->format = WL_SHM_FORMAT_ARGB8888; frame->width = box.width * view->wlr_surface->current.scale; frame->height = box.height * view->wlr_surface->current.scale; double scale = 1.0; if (max_width && frame->width > max_width) { scale = max_width / (double)frame->width; } if (max_height && frame->height > max_height) { scale = fmin (scale, max_height / (double)frame->height); } frame->width *= scale; frame->height *= scale; frame->width = frame->width ?: 1; frame->height = frame->height ?: 1; frame->stride = 4 * frame->width; zwlr_screencopy_frame_v1_send_buffer (frame->resource, frame->format, frame->width, frame->height, frame->stride); } static void phoc_phosh_private_startup_tracker_handle_resource_destroy (struct wl_resource *resource) { PhocPhoshPrivateStartupTracker *tracker = phoc_phosh_private_startup_tracker_from_resource (resource); PhocPhoshPrivate *phosh; if (tracker == NULL) return; g_debug ("Destroying startup_tracker %p (res %p)", tracker, tracker->resource); phosh = tracker->phosh; wl_resource_set_user_data (tracker->resource, NULL); phosh->startup_trackers = g_list_remove (phosh->startup_trackers, tracker); g_free (tracker); } static void phoc_phosh_private_startup_tracker_handle_destroy (struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy (resource); } static const struct phosh_private_startup_tracker_interface phoc_phosh_private_startup_tracker_impl = { .destroy = phoc_phosh_private_startup_tracker_handle_destroy }; static void handle_get_startup_tracker (struct wl_client *client, struct wl_resource *phosh_private_resource, uint32_t id) { PhocPhoshPrivateStartupTracker *tracker = g_new0 (PhocPhoshPrivateStartupTracker, 1); PhocPhoshPrivate *phosh_private; if (tracker == NULL) { wl_client_post_no_memory (client); return; } int version = wl_resource_get_version (phosh_private_resource); tracker->resource = wl_resource_create (client, &phosh_private_startup_tracker_interface, version, id); if (tracker->resource == NULL) { g_free (tracker); wl_client_post_no_memory (client); return; } phosh_private = phoc_phosh_private_from_resource (phosh_private_resource); phosh_private->startup_trackers = g_list_append (phosh_private->startup_trackers, tracker); tracker->phosh = phosh_private; g_debug ("New phosh_private_startup_tracker %p (res %p)", tracker, tracker->resource); wl_resource_set_implementation (tracker->resource, &phoc_phosh_private_startup_tracker_impl, tracker, phoc_phosh_private_startup_tracker_handle_resource_destroy); } static void handle_set_shell_state (struct wl_client *client, struct wl_resource *phosh_private_resource, enum phosh_private_shell_state state) { PhocPhoshPrivate *self = wl_resource_get_user_data (phosh_private_resource); g_assert (PHOC_IS_PHOSH_PRIVATE (self)); g_debug ("Shell state set to %d", state); if (self->state == (PhocPhoshPrivateShellState)state) return; self->state = state; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHELL_STATE]); } static void phosh_handle_resource_destroy (struct wl_resource *resource) { PhocPhoshPrivate *phosh = wl_resource_get_user_data (resource); g_debug ("Destroying phosh %p (res %p)", phosh, resource); phosh->resource = NULL; g_list_free (phosh->keyboard_events); phosh->keyboard_events = NULL; phosh->state = PHOC_PHOSH_PRIVATE_SHELL_STATE_UNKNOWN; g_object_notify_by_pspec (G_OBJECT (phosh), props[PROP_SHELL_STATE]); } static const struct phosh_private_interface phosh_private_impl = { handle_rotate_display, /* unused */ handle_get_xdg_switcher, /* unused */ handle_get_thumbnail, /* request */ handle_get_keyboard_event, /* interface */ handle_get_startup_tracker, /* interface */ handle_set_shell_state, }; static void phosh_private_bind (struct wl_client *client, void *data, uint32_t version, uint32_t id) { PhocPhoshPrivate *phosh = data; struct wl_resource *resource = wl_resource_create (client, &phosh_private_interface, version, id); if (phosh->resource) { wl_resource_post_error (resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "Only a single client can bind to phosh's private protocol"); return; } /* FIXME: unsafe, needs client == shell->child.client */ if (true) { g_info ("FIXME: allowing every client to bind as phosh"); wl_resource_set_implementation (resource, &phosh_private_impl, phosh, phosh_handle_resource_destroy); phosh->resource = resource; g_debug ("Bound client %d with version %d", id, version); phosh->version = version; return; } wl_resource_post_error (resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "permission to bind phosh denied"); } static PhocPhoshPrivate * phoc_phosh_private_from_resource (struct wl_resource *resource) { assert (wl_resource_instance_of (resource, &phosh_private_interface, &phosh_private_impl)); return wl_resource_get_user_data (resource); } static PhocPhoshPrivateScreencopyFrame * phoc_phosh_private_screencopy_frame_from_resource (struct wl_resource *resource) { assert (wl_resource_instance_of (resource, &zwlr_screencopy_frame_v1_interface, &phoc_phosh_private_screencopy_frame_impl)); return wl_resource_get_user_data (resource); } static PhocPhoshPrivateKeyboardEventData * phoc_phosh_private_keyboard_event_from_resource (struct wl_resource *resource) { assert (wl_resource_instance_of (resource, &phosh_private_keyboard_event_interface, &phoc_phosh_private_keyboard_event_impl)); return wl_resource_get_user_data (resource); } static PhocPhoshPrivateStartupTracker * phoc_phosh_private_startup_tracker_from_resource (struct wl_resource *resource) { assert (wl_resource_instance_of (resource, &phosh_private_startup_tracker_interface, &phoc_phosh_private_startup_tracker_impl)); return wl_resource_get_user_data (resource); } static void phoc_phosh_private_constructed (GObject *object) { PhocPhoshPrivate *self = PHOC_PHOSH_PRIVATE (object); struct wl_display *display = phoc_server_get_default ()->wl_display; G_OBJECT_CLASS (phoc_phosh_private_parent_class)->constructed (object); g_info ("Initializing phosh private interface"); self->global = wl_global_create (display, &phosh_private_interface, PHOSH_PRIVATE_VERSION, self, phosh_private_bind); } static void phoc_phosh_private_finalize (GObject *object) { PhocPhoshPrivate *self = PHOC_PHOSH_PRIVATE (object); wl_global_destroy (self->global); G_OBJECT_CLASS (phoc_phosh_private_parent_class)->finalize (object); } static void phoc_phosh_private_class_init (PhocPhoshPrivateClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = phoc_phosh_private_constructed; object_class->finalize = phoc_phosh_private_finalize; object_class->get_property = phoc_phosh_private_get_property; /** * PhoshPhocPrivate:shell-state: * * The attached shell's state */ props[PROP_SHELL_STATE] = g_param_spec_enum ("shell-state", "", "", PHOC_TYPE_PHOSH_PRIVATE_SHELL_STATE, PHOC_PHOSH_PRIVATE_SHELL_STATE_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } static void phoc_phosh_private_init (PhocPhoshPrivate *self) { self->last_action_id = 1; } PhocPhoshPrivate * phoc_phosh_private_new (void) { return PHOC_PHOSH_PRIVATE (g_object_new (PHOC_TYPE_PHOSH_PRIVATE, NULL)); } bool phoc_phosh_private_forward_keysym (PhocKeyCombo *combo, uint32_t timestamp) { GList *l; PhocPhoshPrivateKeyboardEventData *kbevent; PhocServer *server = phoc_server_get_default (); PhocPhoshPrivate *phosh = server->desktop->phosh; bool forwarded = false; for (l = phosh->keyboard_events; l != NULL; l = l->next) { kbevent = l->data; g_debug("addr of kbevent and res kbev %p res %p", kbevent, kbevent->resource); /* forward the keysym if it is has been subscribed to */ if (phoc_phosh_private_keyboard_event_accelerator_is_registered (combo, kbevent)) { gint64 key = ((gint64)combo->modifiers << 32) | combo->keysym; guint action_id = GPOINTER_TO_UINT (g_hash_table_lookup (kbevent->subscribed_accelerators, &key)); phosh_private_keyboard_event_send_accelerator_activated_event (kbevent->resource, action_id, timestamp); forwarded = true; } } return forwarded; } void phoc_phosh_private_notify_startup_id (PhocPhoshPrivate *self, const char *startup_id, enum phosh_private_startup_tracker_protocol proto) { g_assert (PHOC_IS_PHOSH_PRIVATE (self)); /* Nobody bound the protocol */ if (!self->resource) return; if (self->version < 6) return; for (GList *l = self->startup_trackers; l; l = l->next) { PhocPhoshPrivateStartupTracker *tracker = (PhocPhoshPrivateStartupTracker *)l->data; phosh_private_startup_tracker_send_startup_id (tracker->resource, startup_id, proto, 0); } } void phoc_phosh_private_notify_launch (PhocPhoshPrivate *self, const char *startup_id, enum phosh_private_startup_tracker_protocol proto) { g_assert (PHOC_IS_PHOSH_PRIVATE (self)); /* Nobody bound the protocol */ if (!self->resource) return; if (self->version < 6) return; for (GList *l = self->startup_trackers; l; l = l->next) { PhocPhoshPrivateStartupTracker *tracker = (PhocPhoshPrivateStartupTracker *)l->data; phosh_private_startup_tracker_send_launched (tracker->resource, startup_id, proto, 0); } } PhocPhoshPrivateShellState phoc_phosh_private_get_shell_state (PhocPhoshPrivate *self) { g_assert (PHOC_IS_PHOSH_PRIVATE (self)); return self->state; } phoc-v0.13.1/src/phosh-private.h000066400000000000000000000026231422111650000164370ustar00rootroot00000000000000/* * Copyright (C) 2019,2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #pragma once #include "keybindings.h" #include #include "glib-object.h" G_BEGIN_DECLS #define PHOC_TYPE_PHOSH_PRIVATE (phoc_phosh_private_get_type ()) G_DECLARE_FINAL_TYPE (PhocPhoshPrivate, phoc_phosh_private, PHOC, PHOSH_PRIVATE, GObject) typedef enum { PHOC_PHOSH_PRIVATE_SHELL_STATE_UNKNOWN = 0, PHOC_PHOSH_PRIVATE_SHELL_STATE_UP = 1, } PhocPhoshPrivateShellState; PhocPhoshPrivate *phoc_phosh_private_new (void); bool phoc_phosh_private_forward_keysym (PhocKeyCombo *combo, uint32_t timestamp); void phoc_phosh_private_notify_startup_id (PhocPhoshPrivate *self, const char *startup_id, enum phosh_private_startup_tracker_protocol proto); void phoc_phosh_private_notify_launch (PhocPhoshPrivate *self, const char *startup_id, enum phosh_private_startup_tracker_protocol proto); PhocPhoshPrivateShellState phoc_phosh_private_get_shell_state (PhocPhoshPrivate *self); G_END_DECLS phoc-v0.13.1/src/pointer.c000066400000000000000000000153051422111650000153220ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-pointer" #include "config.h" #include "pointer.h" #include "seat.h" #include #include #include #include /** * PhocPointer: * * A pointer input device */ struct _PhocPointer { PhocInputDevice parent; /* private */ gboolean touchpad; GSettings *touchpad_settings; GSettings *mouse_settings; }; G_DEFINE_TYPE (PhocPointer, phoc_pointer, PHOC_TYPE_INPUT_DEVICE); static void on_mouse_settings_changed (PhocPointer *self, const gchar *key, GSettings *settings) { struct libinput_device *ldev; gboolean enabled; gdouble speed; g_debug ("Setting changed, reloading mouse settings"); g_assert (PHOC_IS_POINTER (self)); g_assert (G_IS_SETTINGS (settings)); if (!phoc_input_device_get_is_libinput (PHOC_INPUT_DEVICE (self))) return; ldev = phoc_input_device_get_libinput_device_handle(PHOC_INPUT_DEVICE (self)); if (libinput_device_config_scroll_has_natural_scroll (ldev)) { enabled = g_settings_get_boolean (settings, "natural-scroll"); libinput_device_config_scroll_set_natural_scroll_enabled (ldev, enabled); } if (libinput_device_config_middle_emulation_is_available (ldev)) { enabled = g_settings_get_boolean (settings, "middle-click-emulation"); libinput_device_config_middle_emulation_set_enabled (ldev, enabled); } speed = g_settings_get_double (settings, "speed"); libinput_device_config_accel_set_speed (ldev, CLAMP (speed, -1, 1)); if (libinput_device_config_left_handed_is_available (ldev)) { enabled = g_settings_get_boolean (self->mouse_settings, "left-handed"); libinput_device_config_left_handed_set (ldev, enabled); } } static void on_touchpad_settings_changed (PhocPointer *self, const gchar *key) { struct libinput_device *ldev; gboolean enabled; gdouble speed; enum libinput_config_scroll_method current, method; GDesktopTouchpadHandedness handedness; GSettings *settings; g_debug ("Setting changed, reloading touchpad settings"); g_assert (PHOC_IS_POINTER (self)); settings = self->touchpad_settings; if (!phoc_input_device_get_is_libinput (PHOC_INPUT_DEVICE (self))) return; ldev = phoc_input_device_get_libinput_device_handle (PHOC_INPUT_DEVICE (self)); if (libinput_device_config_scroll_has_natural_scroll (ldev)) { enabled = g_settings_get_boolean (settings, "natural-scroll"); libinput_device_config_scroll_set_natural_scroll_enabled (ldev, enabled); } enabled = g_settings_get_boolean (settings, "tap-to-click"); libinput_device_config_tap_set_enabled (ldev, enabled ? LIBINPUT_CONFIG_TAP_ENABLED : LIBINPUT_CONFIG_TAP_DISABLED); enabled = g_settings_get_boolean (settings, "tap-and-drag"); libinput_device_config_tap_set_drag_enabled (ldev, enabled ? LIBINPUT_CONFIG_DRAG_ENABLED : LIBINPUT_CONFIG_DRAG_DISABLED); enabled = g_settings_get_boolean (settings, "tap-and-drag-lock"); libinput_device_config_tap_set_drag_lock_enabled (ldev, enabled ? LIBINPUT_CONFIG_DRAG_LOCK_ENABLED : LIBINPUT_CONFIG_DRAG_LOCK_DISABLED); enabled = g_settings_get_boolean (settings, "disable-while-typing"); libinput_device_config_dwt_set_enabled (ldev, enabled ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED); if (libinput_device_config_middle_emulation_is_available (ldev)) { enabled = g_settings_get_boolean (settings, "middle-click-emulation"); libinput_device_config_middle_emulation_set_enabled (ldev, enabled); } current = libinput_device_config_scroll_get_method (ldev); current &= ~(LIBINPUT_CONFIG_SCROLL_EDGE | LIBINPUT_CONFIG_SCROLL_2FG); enabled = g_settings_get_boolean (settings, "edge-scrolling-enabled"); method = enabled ? LIBINPUT_CONFIG_SCROLL_EDGE : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; current |= method; enabled = g_settings_get_boolean (settings, "two-finger-scrolling-enabled"); method = enabled ? LIBINPUT_CONFIG_SCROLL_2FG : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; current |= method; libinput_device_config_scroll_set_method (ldev, current | method); speed = g_settings_get_double (settings, "speed"); libinput_device_config_accel_set_speed (ldev, CLAMP (speed, -1, 1)); handedness = g_settings_get_enum (self->touchpad_settings, "left-handed"); switch (handedness) { case G_DESKTOP_TOUCHPAD_HANDEDNESS_RIGHT: enabled = FALSE; break; case G_DESKTOP_TOUCHPAD_HANDEDNESS_LEFT: enabled = TRUE; break; case G_DESKTOP_TOUCHPAD_HANDEDNESS_MOUSE: enabled = g_settings_get_boolean (self->mouse_settings, "left-handed"); break; default: g_assert_not_reached (); } if (libinput_device_config_left_handed_is_available (ldev)) libinput_device_config_left_handed_set (ldev, enabled); } static void phoc_pointer_constructed (GObject *object) { PhocPointer *self = PHOC_POINTER (object); G_OBJECT_CLASS (phoc_pointer_parent_class)->constructed (object); self->touchpad = phoc_input_device_get_is_touchpad (PHOC_INPUT_DEVICE (self)); self->mouse_settings = g_settings_new ("org.gnome.desktop.peripherals.mouse"); if (self->touchpad) { self->touchpad_settings = g_settings_new ("org.gnome.desktop.peripherals.touchpad"); g_signal_connect_swapped (self->touchpad_settings, "changed", G_CALLBACK (on_touchpad_settings_changed), self); /* "left-handed" is read from mouse settings */ g_signal_connect_swapped (self->mouse_settings, "changed::left-handed", G_CALLBACK (on_touchpad_settings_changed), self); on_touchpad_settings_changed (self, NULL); } else { g_signal_connect_swapped (self->mouse_settings, "changed", G_CALLBACK (on_mouse_settings_changed), self); on_mouse_settings_changed (self, NULL, self->mouse_settings); } } static void phoc_pointer_dispose(GObject *object) { PhocPointer *self = PHOC_POINTER (object); g_clear_object (&self->touchpad_settings); g_clear_object (&self->mouse_settings); G_OBJECT_CLASS (phoc_pointer_parent_class)->dispose (object); } static void phoc_pointer_class_init (PhocPointerClass *klass) { GObjectClass *object_class = (GObjectClass *)klass; object_class->constructed = phoc_pointer_constructed; object_class->dispose = phoc_pointer_dispose; } static void phoc_pointer_init (PhocPointer *self) { } PhocPointer * phoc_pointer_new (struct wlr_input_device *device, PhocSeat *seat) { return g_object_new (PHOC_TYPE_POINTER, "device", device, "seat", seat, NULL); } phoc-v0.13.1/src/pointer.h000066400000000000000000000005051422111650000153230ustar00rootroot00000000000000#pragma once #include "input.h" #include "input-device.h" #include #include #define PHOC_TYPE_POINTER (phoc_pointer_get_type ()) G_DECLARE_FINAL_TYPE (PhocPointer, phoc_pointer, PHOC, POINTER, PhocInputDevice); PhocPointer *phoc_pointer_new (struct wlr_input_device *device, PhocSeat *seat); phoc-v0.13.1/src/render.c000066400000000000000000000632111422111650000151200ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later * * Authors: The wlroots authors * Sebastian Krzyszkowiak * Guido Günther */ #define G_LOG_DOMAIN "phoc-render" #include "config.h" #include "layers.h" #include "seat.h" #include "server.h" #include "render.h" #include "xwayland-surface.h" #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Private wlr_allocator headers */ struct wlr_gbm_allocator *wlr_gbm_allocator_create(int drm_fd); struct wlr_buffer *wlr_allocator_create_buffer (struct wlr_allocator *alloc, int width, int height, const struct wlr_drm_format *format); void wlr_allocator_destroy (struct wlr_allocator *alloc); struct wlr_drm_format *wlr_drm_format_create (uint32_t format); /* ----------------------------- */ #define TOUCH_POINT_SIZE 20 #define TOUCH_POINT_BORDER 0.1 #define COLOR_BLACK {0.0f, 0.0f, 0.0f, 1.0f} #define COLOR_TRANSPARENT {0.0f, 0.0f, 0.0f, 0.0f} #define COLOR_TRANSPARENT_WHITE {0.5f, 0.5f, 0.5f, 0.5f} #define COLOR_TRANSPARENT_YELLOW {0.5f, 0.5f, 0.0f, 0.5f} #define COLOR_TRANSPARENT_MAGENTA {0.5f, 0.0f, 0.5f, 0.5f} /** * PhocRenderer: * * The renderer */ enum { RENDER_START, RENDER_END, N_SIGNALS }; static guint signals[N_SIGNALS] = { 0 }; enum { PROP_0, PROP_WLR_RENDERER, PROP_LAST_PROP }; static GParamSpec *props[PROP_LAST_PROP]; struct _PhocRenderer { GObject parent; struct wlr_renderer *wlr_renderer; struct wlr_allocator *allocator; }; G_DEFINE_TYPE (PhocRenderer, phoc_renderer, G_TYPE_OBJECT) struct render_data { pixman_region32_t *damage; float alpha; }; struct view_render_data { PhocView *view; int width; int height; }; struct touch_point_data { int id; double x; double y; }; static void phoc_renderer_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PhocRenderer *self = PHOC_RENDERER (object); switch (property_id) { case PROP_WLR_RENDERER: self->wlr_renderer = g_value_get_pointer (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void phoc_renderer_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PhocRenderer *self = PHOC_RENDERER (object); switch (property_id) { case PROP_WLR_RENDERER: g_value_set_pointer (value, self->wlr_renderer); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void scissor_output(struct wlr_output *wlr_output, pixman_box32_t *rect) { struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend); assert(renderer); struct wlr_box box = { .x = rect->x1, .y = rect->y1, .width = rect->x2 - rect->x1, .height = rect->y2 - rect->y1, }; int ow, oh; wlr_output_transformed_resolution(wlr_output, &ow, &oh); enum wl_output_transform transform = wlr_output_transform_invert(wlr_output->transform); wlr_box_transform(&box, &box, transform, ow, oh); wlr_renderer_scissor(renderer, &box); } static void render_texture(struct wlr_output *wlr_output, pixman_region32_t *output_damage, struct wlr_texture *texture, const struct wlr_fbox *src_box, const struct wlr_box *dst_box, const float matrix[static 9], float rotation, float alpha) { struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend); assert(renderer); struct wlr_box rotated; wlr_box_rotated_bounds(&rotated, dst_box, rotation); pixman_region32_t damage; pixman_region32_init(&damage); pixman_region32_union_rect(&damage, &damage, rotated.x, rotated.y, rotated.width, rotated.height); pixman_region32_intersect(&damage, &damage, output_damage); bool damaged = pixman_region32_not_empty(&damage); if (!damaged) { goto buffer_damage_finish; } int nrects; pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); for (int i = 0; i < nrects; ++i) { scissor_output(wlr_output, &rects[i]); if (src_box != NULL) { wlr_render_subtexture_with_matrix(renderer, texture, src_box, matrix, alpha); } else { wlr_render_texture_with_matrix(renderer, texture, matrix, alpha); } } buffer_damage_finish: pixman_region32_fini(&damage); } static void collect_touch_points (PhocOutput *output, struct wlr_surface *surface, struct wlr_box box, float scale) { PhocServer *server = phoc_server_get_default (); if (G_LIKELY (!(server->debug_flags & PHOC_SERVER_DEBUG_FLAG_TOUCH_POINTS))) { return; } for (GSList *elem = phoc_input_get_seats (server->input); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); struct wlr_touch_point *point; wl_list_for_each(point, &seat->seat->touch_state.touch_points, link) { if (point->surface != surface) { continue; } struct touch_point_data *touch_point = g_malloc(sizeof(struct touch_point_data)); touch_point->id = point->touch_id; touch_point->x = box.x + point->sx * output->wlr_output->scale * scale; touch_point->y = box.y + point->sy * output->wlr_output->scale * scale; output->debug_touch_points = g_list_append(output->debug_touch_points, touch_point); } } } static void render_surface_iterator(PhocOutput *output, struct wlr_surface *surface, struct wlr_box *_box, float rotation, float scale, void *_data) { struct render_data *data = _data; struct wlr_output *wlr_output = output->wlr_output; pixman_region32_t *output_damage = data->damage; float alpha = data->alpha; struct wlr_texture *texture = wlr_surface_get_texture(surface); if (!texture) { return; } struct wlr_fbox src_box; wlr_surface_get_buffer_source_box(surface, &src_box); struct wlr_box dst_box = *_box; phoc_output_scale_box(wlr_output->data, &dst_box, scale); phoc_output_scale_box(wlr_output->data, &dst_box, wlr_output->scale); float matrix[9]; enum wl_output_transform transform = wlr_output_transform_invert(surface->current.transform); wlr_matrix_project_box(matrix, &dst_box, transform, rotation, wlr_output->transform_matrix); render_texture(wlr_output, output_damage, texture, &src_box, &dst_box, matrix, rotation, alpha); wlr_presentation_surface_sampled_on_output(output->desktop->presentation, surface, wlr_output); collect_touch_points(output, surface, dst_box, scale); } static void render_decorations(PhocOutput *output, PhocView *view, struct render_data *data) { if (!view->decorated || !phoc_view_is_mapped (view)) { return; } struct wlr_renderer *renderer = wlr_backend_get_renderer(output->wlr_output->backend); assert(renderer); struct wlr_box box; phoc_output_get_decoration_box(output, view, &box); pixman_region32_t damage; pixman_region32_init(&damage); pixman_region32_union_rect(&damage, &damage, box.x, box.y, box.width, box.height); pixman_region32_intersect(&damage, &damage, data->damage); bool damaged = pixman_region32_not_empty(&damage); if (!damaged) { goto buffer_damage_finish; } float matrix[9]; wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, output->wlr_output->transform_matrix); float color[] = { 0.2, 0.2, 0.2, view->alpha }; int nrects; pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); for (int i = 0; i < nrects; ++i) { scissor_output(output->wlr_output, &rects[i]); wlr_render_quad_with_matrix(renderer, color, matrix); } buffer_damage_finish: pixman_region32_fini(&damage); } static void render_view(PhocOutput *output, PhocView *view, struct render_data *data) { // Do not render views fullscreened on other outputs if (view_is_fullscreen (view) && view->fullscreen_output != output) { return; } data->alpha = view->alpha; if (!view_is_fullscreen (view)) { render_decorations(output, view, data); } phoc_output_view_for_each_surface(output, view, render_surface_iterator, data); } static void render_layer(PhocOutput *output, pixman_region32_t *damage, struct wl_list *layer_surfaces) { struct render_data data = { .damage = damage, .alpha = 1.0f, }; phoc_output_layer_for_each_surface(output, layer_surfaces, render_surface_iterator, &data); } static void count_surface_iterator(PhocOutput *output, struct wlr_surface *surface, struct wlr_box *_box, float rotation, float scale, void *data) { size_t *n = data; n++; } static bool scan_out_fullscreen_view(PhocOutput *output) { struct wlr_output *wlr_output = output->wlr_output; PhocServer *server = phoc_server_get_default (); for (GSList *elem = phoc_input_get_seats (server->input); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); PhocDragIcon *drag_icon = seat->drag_icon; if (drag_icon && drag_icon->wlr_drag_icon->mapped) { return false; } } if (!wl_list_empty(&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY])) { return false; } struct wlr_output_cursor *cursor; wl_list_for_each(cursor, &wlr_output->cursors, link) { if (cursor->enabled && cursor->visible && wlr_output->hardware_cursor != cursor) { return false; } } PhocView *view = output->fullscreen_view; assert(view != NULL); if (!phoc_view_is_mapped (view)) { return false; } size_t n_surfaces = 0; phoc_output_view_for_each_surface(output, view, count_surface_iterator, &n_surfaces); if (n_surfaces > 1) { return false; } #if WLR_HAS_XWAYLAND if (view->type == PHOC_XWAYLAND_VIEW) { PhocXWaylandSurface *xwayland_surface = phoc_xwayland_surface_from_view(view); if (!wl_list_empty(&xwayland_surface->xwayland_surface->children)) { return false; } } #endif struct wlr_surface *surface = view->wlr_surface; if (surface->buffer == NULL) { return false; } if ((float)surface->current.scale != wlr_output->scale || surface->current.transform != wlr_output->transform) { return false; } wlr_output_attach_buffer(wlr_output, &surface->buffer->base); if (!wlr_output_test(wlr_output)) { return false; } wlr_presentation_surface_sampled_on_output(output->desktop->presentation, surface, output->wlr_output); return wlr_output_commit(wlr_output); } static void render_drag_icons(PhocOutput *output, pixman_region32_t *damage, PhocInput *input) { struct render_data data = { .damage = damage, .alpha = 1.0f, }; phoc_output_drag_icons_for_each_surface(output, input, render_surface_iterator, &data); } static void color_hsv_to_rgb (float* color) { float h = color[0], s = color[1], v = color[2]; h = fmodf (h, 360); if (h < 0) { h += 360; } int d = h / 60; float e = h / 60 - d; float a = v * (1 - s); float b = v * (1 - e * s); float c = v * (1 - (1 - e) * s); switch (d) { default: case 0: color[0] = v, color[1] = c, color[2] = a; return; case 1: color[0] = b, color[1] = v, color[2] = a; return; case 2: color[0] = a, color[1] = v, color[2] = c; return; case 3: color[0] = a, color[1] = b, color[2] = v; return; case 4: color[0] = c, color[1] = a, color[2] = v; return; case 5: color[0] = v, color[1] = a, color[2] = b; return; } } static struct wlr_box wlr_box_from_touch_point (struct touch_point_data *touch_point, int width, int height) { return (struct wlr_box) { .x = touch_point->x - width / 2.0, .y = touch_point->y - height / 2.0, .width = width, .height = height }; } static void render_touch_point_cb (gpointer data, gpointer user_data) { struct touch_point_data *touch_point = data; PhocOutput *output = user_data; struct wlr_output *wlr_output = output->wlr_output; int size = TOUCH_POINT_SIZE * wlr_output->scale; struct wlr_box point_box = wlr_box_from_touch_point (touch_point, size, size); struct wlr_renderer *renderer = wlr_backend_get_renderer (wlr_output->backend); float color[4] = {touch_point->id * 100 + 240, 1.0, 1.0, 0.75}; color_hsv_to_rgb (color); wlr_render_rect (renderer, &point_box, color, wlr_output->transform_matrix); size = TOUCH_POINT_SIZE * (1.0 - TOUCH_POINT_BORDER) * wlr_output->scale; point_box = wlr_box_from_touch_point (touch_point, size, size); wlr_render_rect (renderer, &point_box, (float[])COLOR_TRANSPARENT_WHITE, wlr_output->transform_matrix); point_box = wlr_box_from_touch_point (touch_point, 8 * wlr_output->scale, 2 * wlr_output->scale); wlr_render_rect (renderer, &point_box, color, wlr_output->transform_matrix); point_box = wlr_box_from_touch_point (touch_point, 2 * wlr_output->scale, 8 * wlr_output->scale); wlr_render_rect (renderer, &point_box, color, wlr_output->transform_matrix); } static void render_touch_points (PhocOutput *output) { PhocServer *server = phoc_server_get_default (); if (G_LIKELY (!(server->debug_flags & PHOC_SERVER_DEBUG_FLAG_TOUCH_POINTS))) { return; } g_list_foreach (output->debug_touch_points, render_touch_point_cb, output); } static void damage_touch_point_cb (gpointer data, gpointer user_data) { struct touch_point_data *touch_point = data; PhocOutput *output = user_data; struct wlr_output *wlr_output = output->wlr_output; int size = TOUCH_POINT_SIZE * wlr_output->scale; struct wlr_box box = wlr_box_from_touch_point (touch_point, size, size); pixman_region32_t region; pixman_region32_init_rect(®ion, box.x, box.y, box.width, box.height); wlr_output_damage_add(output->damage, ®ion); pixman_region32_fini(®ion); } static void damage_touch_points (PhocOutput *output) { if (output->debug_touch_points == NULL) return; g_list_foreach (output->debug_touch_points, damage_touch_point_cb, output); wlr_output_schedule_frame(output->wlr_output); } static void view_render_iterator (struct wlr_surface *surface, int sx, int sy, void *_data) { if (!wlr_surface_has_buffer (surface)) { return; } PhocServer *server = phoc_server_get_default (); PhocRenderer *self = phoc_server_get_renderer (server); struct wlr_texture *view_texture = wlr_surface_get_texture (surface); struct view_render_data *data = _data; PhocView *view = data->view; struct wlr_surface *root = view->wlr_surface; struct wlr_box box; view_get_box (view, &box); struct wlr_box geo; view_get_geometry (view, &geo); float mat[16]; wlr_matrix_identity (mat); wlr_matrix_scale (mat, data->width / (float)geo.width, data->height / (float)geo.height); wlr_matrix_scale (mat, 1 / (float)root->current.scale, 1 / (float)root->current.scale); wlr_matrix_scale (mat, view->scale, view->scale); wlr_matrix_scale (mat, root->current.scale / surface->current.scale, root->current.scale / surface->current.scale); wlr_matrix_translate (mat, -geo.x, -geo.y); wlr_render_texture (self->wlr_renderer, view_texture, mat, sx * surface->current.scale, sy * surface->current.scale, 1.0); } gboolean phoc_renderer_render_view_to_buffer (PhocRenderer *self, PhocView *view, int width, int height, int stride, uint32_t *flags, void *data) { struct wlr_surface *surface = view->wlr_surface; g_return_val_if_fail (surface, false); g_return_val_if_fail (self->allocator, false); struct wlr_drm_format *fmt = wlr_drm_format_create (DRM_FORMAT_ARGB8888); struct wlr_buffer *buffer = wlr_allocator_create_buffer (self->allocator, width, height, fmt); if (!buffer) { free (fmt); g_return_val_if_reached (false); } struct view_render_data render_data ={ .view = view, .width = width, .height = height }; wlr_renderer_begin_with_buffer (self->wlr_renderer, buffer); wlr_renderer_clear (self->wlr_renderer, (float[])COLOR_TRANSPARENT); wlr_surface_for_each_surface (surface, view_render_iterator, &render_data); wlr_renderer_read_pixels (self->wlr_renderer, DRM_FORMAT_ARGB8888, NULL, stride, width, height, 0, 0, 0, 0, data); wlr_renderer_end (self->wlr_renderer); wlr_buffer_drop (buffer); free(fmt); return true; } static void surface_send_frame_done_iterator(PhocOutput *output, struct wlr_surface *surface, struct wlr_box *box, float rotation, float scale, void *data) { struct timespec *when = data; wlr_surface_send_frame_done(surface, when); } static void render_damage (PhocRenderer *self, PhocOutput *output) { int nrects; pixman_box32_t *rects; struct wlr_box box; pixman_region32_t previous_damage; pixman_region32_init(&previous_damage); pixman_region32_subtract(&previous_damage, &output->damage->previous[output->damage->previous_idx], &output->damage->current); rects = pixman_region32_rectangles(&previous_damage, &nrects); for (int i = 0; i < nrects; ++i) { wlr_box_from_pixman_box32(&box, rects[i]); wlr_render_rect(self->wlr_renderer, &box, (float[])COLOR_TRANSPARENT_MAGENTA, output->wlr_output->transform_matrix); } rects = pixman_region32_rectangles(&output->damage->current, &nrects); for (int i = 0; i < nrects; ++i) { wlr_box_from_pixman_box32(&box, rects[i]); wlr_render_rect(self->wlr_renderer, &box, (float[])COLOR_TRANSPARENT_YELLOW, output->wlr_output->transform_matrix); } wlr_output_schedule_frame(output->wlr_output); pixman_region32_fini(&previous_damage); } /** * phoc_renderer_render_output: * @self: The renderer * @output: The output to render * * Render a given output. */ void phoc_renderer_render_output (PhocRenderer *self, PhocOutput *output) { struct wlr_output *wlr_output = output->wlr_output; PhocDesktop *desktop = PHOC_DESKTOP (output->desktop); PhocServer *server = phoc_server_get_default (); struct wlr_renderer *wlr_renderer; g_assert (PHOC_IS_RENDERER (self)); wlr_renderer = self->wlr_renderer; if (!wlr_output->enabled) { return; } struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); float clear_color[] = COLOR_BLACK; g_signal_emit (self, signals[RENDER_START], 0, output); // Check if we can delegate the fullscreen surface to the output if (phoc_output_has_fullscreen_view (output)) { static bool last_scanned_out = false; bool scanned_out = scan_out_fullscreen_view(output); if (scanned_out && !last_scanned_out) { g_debug ("Starting fullscreen view scan out"); } else if (!scanned_out && last_scanned_out) { g_debug ("Stopping fullscreen view scan out"); } last_scanned_out = scanned_out; if (scanned_out) { goto send_frame_done; } } bool needs_frame; pixman_region32_t buffer_damage; pixman_region32_init(&buffer_damage); if (!wlr_output_damage_attach_render(output->damage, &needs_frame, &buffer_damage)) { return; } struct render_data data = { .damage = &buffer_damage, .alpha = 1.0, }; enum wl_output_transform transform = wlr_output_transform_invert(wlr_output->transform); if (G_UNLIKELY (server->debug_flags & PHOC_SERVER_DEBUG_FLAG_DAMAGE_TRACKING)) { pixman_region32_union_rect(&buffer_damage, &buffer_damage, 0, 0, wlr_output->width, wlr_output->height); wlr_region_transform(&buffer_damage, &buffer_damage, transform, wlr_output->width, wlr_output->height); needs_frame |= pixman_region32_not_empty(&output->damage->current); needs_frame |= pixman_region32_not_empty(&output->damage->previous[output->damage->previous_idx]); } if (!needs_frame) { // Output doesn't need swap and isn't damaged, skip rendering completely wlr_output_rollback(wlr_output); goto buffer_damage_finish; } wlr_renderer_begin(wlr_renderer, wlr_output->width, wlr_output->height); if (!pixman_region32_not_empty(&buffer_damage)) { // Output isn't damaged but needs buffer swap goto renderer_end; } int nrects; pixman_box32_t *rects = pixman_region32_rectangles(&buffer_damage, &nrects); for (int i = 0; i < nrects; ++i) { scissor_output(output->wlr_output, &rects[i]); wlr_renderer_clear(wlr_renderer, clear_color); } // If a view is fullscreen on this output, render it if (output->fullscreen_view != NULL) { PhocView *view = output->fullscreen_view; render_view(output, view, &data); // During normal rendering the xwayland window tree isn't traversed // because all windows are rendered. Here we only want to render // the fullscreen window's children so we have to traverse the tree. #ifdef PHOC_XWAYLAND if (view->type == PHOC_XWAYLAND_VIEW) { PhocXWaylandSurface *xwayland_surface = phoc_xwayland_surface_from_view(view); phoc_output_xwayland_children_for_each_surface(output, xwayland_surface->xwayland_surface, render_surface_iterator, &data); } #endif if (output->force_shell_reveal) { // Render top layer above fullscreen view when requested render_layer(output, &buffer_damage, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); } } else { // Render background and bottom layers under views render_layer(output, &buffer_damage, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]); render_layer(output, &buffer_damage, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); PhocView *view; // Render all views wl_list_for_each_reverse(view, &desktop->views, link) { if (phoc_desktop_view_is_visible(desktop, view)) { render_view(output, view, &data); } } // Render top layer above views render_layer(output, &buffer_damage, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); } render_drag_icons(output, &buffer_damage, server->input); render_layer(output, &buffer_damage, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); renderer_end: wlr_output_render_software_cursors(wlr_output, &buffer_damage); wlr_renderer_scissor(wlr_renderer, NULL); render_touch_points (output); g_signal_emit (self, signals[RENDER_END], 0, output); if (G_UNLIKELY (server->debug_flags & PHOC_SERVER_DEBUG_FLAG_DAMAGE_TRACKING)) render_damage (self, output); wlr_renderer_end(wlr_renderer); int width, height; wlr_output_transformed_resolution(wlr_output, &width, &height); pixman_region32_t frame_damage; pixman_region32_init(&frame_damage); wlr_region_transform(&frame_damage, &output->damage->current, transform, width, height); wlr_output_set_damage(wlr_output, &frame_damage); pixman_region32_fini(&frame_damage); if (!wlr_output_commit(wlr_output)) { goto buffer_damage_finish; } buffer_damage_finish: pixman_region32_fini(&buffer_damage); send_frame_done: // Send frame done events to all visible surfaces phoc_output_for_each_surface(output, surface_send_frame_done_iterator, &now, true); damage_touch_points(output); g_clear_list (&output->debug_touch_points, g_free); } static void phoc_renderer_constructed (GObject *object) { PhocRenderer *self = PHOC_RENDERER (object); int drm_fd = fcntl (wlr_renderer_get_drm_fd (self->wlr_renderer), F_DUPFD_CLOEXEC, 0); // TODO: use wlr_allocator_autocreate or retrieve a reference from wlr_renderer once possible if (wlr_renderer_is_gles2 (self->wlr_renderer)) self->allocator = (struct wlr_allocator*) wlr_gbm_allocator_create (drm_fd); } static void phoc_renderer_finalize (GObject *object) { PhocRenderer *self = PHOC_RENDERER (object); if (self->allocator) wlr_allocator_destroy (self->allocator); /* TODO: destroy wlr_renderer */ } static void phoc_renderer_class_init (PhocRendererClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = phoc_renderer_get_property; object_class->set_property = phoc_renderer_set_property; object_class->constructed = phoc_renderer_constructed; object_class->finalize = phoc_renderer_finalize; /** * PhocRenderer:wlr-renderer * * The wlr-renderer from wlroots to use */ props[PROP_WLR_RENDERER] = g_param_spec_pointer ("wlr-renderer", "", "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); /** * PhocRenderer::render-start * @self: The renderer emitting the signal * @output: The output being rendered on * * This signal is emitted at the start of a render pass */ signals[RENDER_START] = g_signal_new ("render-start", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, PHOC_TYPE_OUTPUT); /** * PhocRenderer::render-end * @self: The renderer emitting the signal * @output: The output being rendered on * * This signal is emitted at the end of a render pass */ signals[RENDER_END] = g_signal_new ("render-end", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, PHOC_TYPE_OUTPUT); } static void phoc_renderer_init (PhocRenderer *self) { } PhocRenderer * phoc_renderer_new (struct wlr_renderer *wlr_renderer) { return PHOC_RENDERER (g_object_new (PHOC_TYPE_RENDERER, "wlr-renderer", wlr_renderer, NULL)); } phoc-v0.13.1/src/render.h000066400000000000000000000017621422111650000151300ustar00rootroot00000000000000/* * Copyright (C) 2020,2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "output.h" #include #include G_BEGIN_DECLS #define PHOC_TYPE_RENDERER (phoc_renderer_get_type ()) G_DECLARE_FINAL_TYPE (PhocRenderer, phoc_renderer, PHOC, RENDERER, GObject) PhocRenderer *phoc_renderer_new (struct wlr_renderer *wlr_renderer); void phoc_renderer_render_output (PhocRenderer *self, PhocOutput *output); gboolean phoc_renderer_render_view_to_buffer (PhocRenderer *self, PhocView *view, int width, int height, int stride, uint32_t *flags, void *data); G_END_DECLS phoc-v0.13.1/src/seat.c000066400000000000000000001676561422111650000146170ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * * SPDX-License-Identifier: GPL-3-or-later or MIT */ #define G_LOG_DOMAIN "phoc-seat" #include "config.h" #define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cursor.h" #include "keyboard.h" #include "pointer.h" #include "seat.h" #include "server.h" #include "tablet.h" #include "text_input.h" #include "touch.h" #include "xcursor.h" #include "xwayland-surface.h" enum { PROP_0, PROP_INPUT, PROP_NAME, PROP_LAST_PROP }; static GParamSpec *props[PROP_LAST_PROP]; G_DEFINE_TYPE (PhocSeat, phoc_seat, G_TYPE_OBJECT) static void phoc_seat_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PhocSeat *self = PHOC_SEAT (object); switch (property_id) { case PROP_INPUT: /* Don't hold a ref since the input object "owns" the seat */ self->input = g_value_get_object (value); break; case PROP_NAME: self->name = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void phoc_seat_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PhocSeat *self = PHOC_SEAT (object); switch (property_id) { case PROP_INPUT: g_value_set_object (value, self->input); break; case PROP_NAME: g_value_set_string (value, self->name); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void handle_swipe_begin (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, swipe_begin); struct wlr_pointer_gestures_v1 *gestures = server->desktop->pointer_gestures; struct wlr_event_pointer_swipe_begin *event = data; wlr_pointer_gestures_v1_send_swipe_begin (gestures, cursor->seat->seat, event->time_msec, event->fingers); } static void handle_swipe_update (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, swipe_update); struct wlr_pointer_gestures_v1 *gestures = server->desktop->pointer_gestures; struct wlr_event_pointer_swipe_update *event = data; wlr_pointer_gestures_v1_send_swipe_update (gestures, cursor->seat->seat, event->time_msec, event->dx, event->dy); } static void handle_swipe_end (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, swipe_end); struct wlr_pointer_gestures_v1 *gestures = server->desktop->pointer_gestures; struct wlr_event_pointer_swipe_end *event = data; wlr_pointer_gestures_v1_send_swipe_end (gestures, cursor->seat->seat, event->time_msec, event->cancelled); } static void handle_pinch_begin (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, pinch_begin); struct wlr_pointer_gestures_v1 *gestures = server->desktop->pointer_gestures; struct wlr_event_pointer_pinch_begin *event = data; wlr_pointer_gestures_v1_send_pinch_begin (gestures, cursor->seat->seat, event->time_msec, event->fingers); } static void handle_pinch_update (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, pinch_update); struct wlr_pointer_gestures_v1 *gestures = server->desktop->pointer_gestures; struct wlr_event_pointer_pinch_update *event = data; wlr_pointer_gestures_v1_send_pinch_update (gestures, cursor->seat->seat, event->time_msec, event->dx, event->dy, event->scale, event->rotation); } static void handle_pinch_end (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, pinch_end); struct wlr_pointer_gestures_v1 *gestures = server->desktop->pointer_gestures; struct wlr_event_pointer_pinch_end *event = data; wlr_pointer_gestures_v1_send_pinch_end (gestures, cursor->seat->seat, event->time_msec, event->cancelled); } static void handle_switch_toggle (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); struct roots_switch *switch_device = wl_container_of (listener, switch_device, toggle); PhocDesktop *desktop = server->desktop; wlr_idle_notify_activity (desktop->idle, switch_device->seat->seat); struct wlr_event_switch_toggle *event = data; roots_switch_handle_toggle (switch_device, event); } static void handle_touch_down (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, touch_down); struct wlr_event_touch_down *event = data; PhocDesktop *desktop = server->desktop; PhocOutput *output = g_hash_table_lookup (desktop->input_output_map, event->device->name); if (output && !output->wlr_output->enabled) { g_debug ("Touch event ignored since output '%s' is disabled.", output->wlr_output->name); return; } wlr_idle_notify_activity (desktop->idle, cursor->seat->seat); phoc_cursor_handle_touch_down (cursor, event); } static void handle_touch_up (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, touch_up); struct wlr_event_touch_up *event = data; PhocDesktop *desktop = server->desktop; PhocOutput *output = g_hash_table_lookup (desktop->input_output_map, event->device->name); /* handle touch up regardless of output status so events don't become stuck */ phoc_cursor_handle_touch_up (cursor, event); if (output && !output->wlr_output->enabled) { g_debug ("Touch event ignored since output '%s' is disabled.", output->wlr_output->name); return; } wlr_idle_notify_activity (desktop->idle, cursor->seat->seat); } static void handle_touch_motion (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, touch_motion); struct wlr_event_touch_motion *event = data; PhocDesktop *desktop = server->desktop; PhocOutput *output = g_hash_table_lookup (desktop->input_output_map, event->device->name); /* handle touch motion regardless of output status so events don't become stuck */ phoc_cursor_handle_touch_motion (cursor, event); if (output && !output->wlr_output->enabled) { g_debug ("Touch event ignored since output '%s' is disabled.", output->wlr_output->name); return; } wlr_idle_notify_activity (desktop->idle, cursor->seat->seat); } static void handle_tablet_tool_position (PhocCursor *cursor, PhocTablet *tablet, struct wlr_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy) { PhocServer *server = phoc_server_get_default (); struct wlr_input_device *device = phoc_input_device_get_device (PHOC_INPUT_DEVICE (tablet)); if (!change_x && !change_y) { return; } switch (tool->type) { case WLR_TABLET_TOOL_TYPE_MOUSE: // They are 0 either way when they weren't modified wlr_cursor_move (cursor->cursor, device, dx, dy); break; default: wlr_cursor_warp_absolute (cursor->cursor, device, change_x ? x : NAN, change_y ? y : NAN); } double sx, sy; PhocDesktop *desktop = server->desktop; struct wlr_surface *surface = phoc_desktop_surface_at (desktop, cursor->cursor->x, cursor->cursor->y, &sx, &sy, NULL); PhocTabletTool *phoc_tool = tool->data; if (!surface) { wlr_tablet_v2_tablet_tool_notify_proximity_out (phoc_tool->tablet_v2_tool); /* XXX: TODO: Fallback pointer semantics */ return; } if (!wlr_surface_accepts_tablet_v2 (tablet->tablet_v2, surface)) { wlr_tablet_v2_tablet_tool_notify_proximity_out (phoc_tool->tablet_v2_tool); /* XXX: TODO: Fallback pointer semantics */ return; } wlr_tablet_v2_tablet_tool_notify_proximity_in (phoc_tool->tablet_v2_tool, tablet->tablet_v2, surface); wlr_tablet_v2_tablet_tool_notify_motion (phoc_tool->tablet_v2_tool, sx, sy); } static void handle_tool_axis (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, tool_axis); PhocDesktop *desktop = server->desktop; wlr_idle_notify_activity (desktop->idle, cursor->seat->seat); struct wlr_event_tablet_tool_axis *event = data; PhocTabletTool *phoc_tool = event->tool->data; if (!phoc_tool) { // Should this be an assert? g_debug ("Tool Axis, before proximity"); return; } /* * We need to handle them ourselves, not pass it into the cursor * without any consideration */ handle_tablet_tool_position (cursor, event->device->data, event->tool, event->updated_axes & WLR_TABLET_TOOL_AXIS_X, event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, event->x, event->y, event->dx, event->dy); if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) { wlr_tablet_v2_tablet_tool_notify_pressure ( phoc_tool->tablet_v2_tool, event->pressure); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) { wlr_tablet_v2_tablet_tool_notify_distance ( phoc_tool->tablet_v2_tool, event->distance); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) { phoc_tool->tilt_x = event->tilt_x; } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) { phoc_tool->tilt_y = event->tilt_y; } if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) { wlr_tablet_v2_tablet_tool_notify_tilt ( phoc_tool->tablet_v2_tool, phoc_tool->tilt_x, phoc_tool->tilt_y); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) { wlr_tablet_v2_tablet_tool_notify_rotation ( phoc_tool->tablet_v2_tool, event->rotation); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) { wlr_tablet_v2_tablet_tool_notify_slider ( phoc_tool->tablet_v2_tool, event->slider); } if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) { wlr_tablet_v2_tablet_tool_notify_wheel ( phoc_tool->tablet_v2_tool, event->wheel_delta, 0); } } static void handle_tool_tip (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, tool_tip); PhocDesktop *desktop = server->desktop; wlr_idle_notify_activity (desktop->idle, cursor->seat->seat); struct wlr_event_tablet_tool_tip *event = data; PhocTabletTool *phoc_tool = event->tool->data; if (event->state == WLR_TABLET_TOOL_TIP_DOWN) { wlr_tablet_v2_tablet_tool_notify_down (phoc_tool->tablet_v2_tool); wlr_tablet_tool_v2_start_implicit_grab (phoc_tool->tablet_v2_tool); } else { wlr_tablet_v2_tablet_tool_notify_up (phoc_tool->tablet_v2_tool); } } static void handle_tablet_tool_destroy (struct wl_listener *listener, void *data) { PhocTabletTool *tool = wl_container_of (listener, tool, tool_destroy); wl_list_remove (&tool->link); wl_list_remove (&tool->tool_link); wl_list_remove (&tool->tool_destroy.link); wl_list_remove (&tool->set_cursor.link); free (tool); } static void handle_tool_button (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, tool_button); PhocDesktop *desktop = server->desktop; wlr_idle_notify_activity (desktop->idle, cursor->seat->seat); struct wlr_event_tablet_tool_button *event = data; PhocTabletTool *phoc_tool = event->tool->data; wlr_tablet_v2_tablet_tool_notify_button (phoc_tool->tablet_v2_tool, (enum zwp_tablet_pad_v2_button_state)event->button, (enum zwp_tablet_pad_v2_button_state)event->state); } static void handle_tablet_tool_set_cursor (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocTabletTool *tool = wl_container_of (listener, tool, set_cursor); struct wlr_tablet_v2_event_cursor *evt = data; PhocDesktop *desktop = server->desktop; struct wlr_seat_pointer_request_set_cursor_event event = { .surface = evt->surface, .hotspot_x = evt->hotspot_x, .hotspot_y = evt->hotspot_y, .serial = evt->serial, .seat_client = evt->seat_client, }; wlr_idle_notify_activity (desktop->idle, tool->seat->seat); phoc_cursor_handle_request_set_cursor (tool->seat->cursor, &event); } static void handle_tool_proximity (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocCursor *cursor = wl_container_of (listener, cursor, tool_proximity); PhocDesktop *desktop = server->desktop; wlr_idle_notify_activity (desktop->idle, cursor->seat->seat); struct wlr_event_tablet_tool_proximity *event = data; struct wlr_tablet_tool *tool = event->tool; if (!tool->data) { PhocTabletTool *phoc_tool = g_new0 (PhocTabletTool, 1); phoc_tool->seat = cursor->seat; tool->data = phoc_tool; phoc_tool->tablet_v2_tool = wlr_tablet_tool_create (desktop->tablet_v2, cursor->seat->seat, tool); phoc_tool->tool_destroy.notify = handle_tablet_tool_destroy; wl_signal_add (&tool->events.destroy, &phoc_tool->tool_destroy); phoc_tool->set_cursor.notify = handle_tablet_tool_set_cursor; wl_signal_add (&phoc_tool->tablet_v2_tool->events.set_cursor, &phoc_tool->set_cursor); wl_list_init (&phoc_tool->link); wl_list_init (&phoc_tool->tool_link); } if (event->state == WLR_TABLET_TOOL_PROXIMITY_OUT) { PhocTabletTool *phoc_tool = tool->data; wlr_tablet_v2_tablet_tool_notify_proximity_out (phoc_tool->tablet_v2_tool); /* Clear cursor image if there's no pointing device. */ if (phoc_seat_has_pointer (cursor->seat) == FALSE) phoc_seat_maybe_set_cursor (cursor->seat, cursor->default_xcursor); return; } handle_tablet_tool_position (cursor, event->device->data, event->tool, true, true, event->x, event->y, 0, 0); } static void handle_request_set_cursor (struct wl_listener *listener, void *data) { PhocCursor *cursor = wl_container_of (listener, cursor, request_set_cursor); struct wlr_seat_pointer_request_set_cursor_event *event = data; phoc_cursor_handle_request_set_cursor (cursor, event); } static void handle_pointer_focus_change (struct wl_listener *listener, void *data) { PhocCursor *cursor = wl_container_of (listener, cursor, focus_change); struct wlr_seat_pointer_focus_change_event *event = data; phoc_cursor_handle_focus_change (cursor, event); } static PhocOutput * get_output_from_settings (PhocSeat *self, PhocInputDevice *device) { PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; GSettings *settings; g_auto (GStrv) edid = NULL; settings = g_hash_table_lookup (self->input_mapping_settings, device); g_assert (G_IS_SETTINGS (settings)); edid = g_settings_get_strv (settings, "output"); if (g_strv_length (edid) != 3) { g_warning ("EDID configuration for '%s' does not have 3 values", phoc_input_device_get_name (device)); return NULL; } if (!*edid[0] && !*edid[1] && !*edid[2]) return NULL; g_debug ("Looking up output %s/%s/%s", edid[0], edid[1], edid[2]); return phoc_desktop_find_output (desktop, edid[0], edid[1], edid[2]); } static void seat_set_device_output_mappings (PhocSeat *self, PhocInputDevice *device) { PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; struct wlr_cursor *cursor = self->cursor->cursor; PhocOutput *output; const char *type = ""; switch (phoc_input_device_get_device_type (device)) { /* only map devices with absolute positions */ case WLR_INPUT_DEVICE_TOUCH: type = "touch"; break; case WLR_INPUT_DEVICE_TABLET_TOOL: type = "tablet"; break; default: g_assert_not_reached (); } output = get_output_from_settings (self, device); if (!output) output = phoc_desktop_get_builtin_output (desktop); if (!output) return; g_debug ("Mapping %s device %s to %s", type, phoc_input_device_get_name (device), output->wlr_output->name); wlr_cursor_map_input_to_output (cursor, phoc_input_device_get_device (device), output->wlr_output); g_hash_table_insert (desktop->input_output_map, g_strdup (phoc_input_device_get_name (device)), output); return; } static void reset_device_mappings (gpointer data, gpointer user_data) { PhocInputDevice *device = PHOC_INPUT_DEVICE (data); PhocSeat *seat = PHOC_SEAT (user_data); struct wlr_cursor *cursor = seat->cursor->cursor; wlr_cursor_map_input_to_output (cursor, phoc_input_device_get_device (device), NULL); } void phoc_seat_configure_cursor (PhocSeat *seat) { struct wlr_cursor *cursor = seat->cursor->cursor; // reset mappings wlr_cursor_map_to_output (cursor, NULL); g_slist_foreach (seat->touch, reset_device_mappings, seat); g_slist_foreach (seat->tablets, reset_device_mappings, seat); // configure device to output mappings for (GSList *elem = seat->tablets; elem; elem = elem->next) { PhocInputDevice *input_device = PHOC_INPUT_DEVICE (elem->data); seat_set_device_output_mappings (seat, input_device); } for (GSList *elem = seat->touch; elem; elem = elem->next) { PhocInputDevice *input_device = PHOC_INPUT_DEVICE (elem->data); seat_set_device_output_mappings (seat, input_device); } } static void phoc_seat_init_cursor (PhocSeat *seat) { PhocServer *server = phoc_server_get_default (); seat->cursor = phoc_cursor_new (seat); struct wlr_cursor *wlr_cursor = seat->cursor->cursor; PhocDesktop *desktop = server->desktop; wlr_cursor_attach_output_layout (wlr_cursor, desktop->layout); phoc_seat_configure_cursor (seat); phoc_seat_configure_xcursor (seat); // add input signals wl_signal_add (&wlr_cursor->events.swipe_begin, &seat->cursor->swipe_begin); seat->cursor->swipe_begin.notify = handle_swipe_begin; wl_signal_add (&wlr_cursor->events.swipe_update, &seat->cursor->swipe_update); seat->cursor->swipe_update.notify = handle_swipe_update; wl_signal_add (&wlr_cursor->events.swipe_end, &seat->cursor->swipe_end); seat->cursor->swipe_end.notify = handle_swipe_end; wl_signal_add (&wlr_cursor->events.pinch_begin, &seat->cursor->pinch_begin); seat->cursor->pinch_begin.notify = handle_pinch_begin; wl_signal_add (&wlr_cursor->events.pinch_update, &seat->cursor->pinch_update); seat->cursor->pinch_update.notify = handle_pinch_update; wl_signal_add (&wlr_cursor->events.pinch_end, &seat->cursor->pinch_end); seat->cursor->pinch_end.notify = handle_pinch_end; wl_signal_add (&wlr_cursor->events.touch_down, &seat->cursor->touch_down); seat->cursor->touch_down.notify = handle_touch_down; wl_signal_add (&wlr_cursor->events.touch_up, &seat->cursor->touch_up); seat->cursor->touch_up.notify = handle_touch_up; wl_signal_add (&wlr_cursor->events.touch_motion, &seat->cursor->touch_motion); seat->cursor->touch_motion.notify = handle_touch_motion; wl_signal_add (&wlr_cursor->events.tablet_tool_axis, &seat->cursor->tool_axis); seat->cursor->tool_axis.notify = handle_tool_axis; wl_signal_add (&wlr_cursor->events.tablet_tool_tip, &seat->cursor->tool_tip); seat->cursor->tool_tip.notify = handle_tool_tip; wl_signal_add (&wlr_cursor->events.tablet_tool_proximity, &seat->cursor->tool_proximity); seat->cursor->tool_proximity.notify = handle_tool_proximity; wl_signal_add (&wlr_cursor->events.tablet_tool_button, &seat->cursor->tool_button); seat->cursor->tool_button.notify = handle_tool_button; wl_signal_add (&seat->seat->events.request_set_cursor, &seat->cursor->request_set_cursor); seat->cursor->request_set_cursor.notify = handle_request_set_cursor; wl_signal_add (&seat->seat->pointer_state.events.focus_change, &seat->cursor->focus_change); seat->cursor->focus_change.notify = handle_pointer_focus_change; wl_list_init (&seat->cursor->constraint_commit.link); } static void phoc_drag_icon_handle_surface_commit (struct wl_listener *listener, void *data) { PhocDragIcon *icon = wl_container_of (listener, icon, surface_commit); phoc_drag_icon_update_position (icon); } static void phoc_drag_icon_handle_map (struct wl_listener *listener, void *data) { PhocDragIcon *icon = wl_container_of (listener, icon, map); phoc_drag_icon_damage_whole (icon); } static void phoc_drag_icon_handle_unmap (struct wl_listener *listener, void *data) { PhocDragIcon *icon = wl_container_of (listener, icon, unmap); phoc_drag_icon_damage_whole (icon); } static void phoc_drag_icon_handle_destroy (struct wl_listener *listener, void *data) { PhocDragIcon *icon = wl_container_of (listener, icon, destroy); phoc_drag_icon_damage_whole (icon); assert (icon->seat->drag_icon == icon); icon->seat->drag_icon = NULL; wl_list_remove (&icon->surface_commit.link); wl_list_remove (&icon->unmap.link); wl_list_remove (&icon->destroy.link); free (icon); } static void phoc_seat_handle_request_start_drag (struct wl_listener *listener, void *data) { PhocSeat *seat = wl_container_of (listener, seat, request_start_drag); struct wlr_seat_request_start_drag_event *event = data; if (wlr_seat_validate_pointer_grab_serial (seat->seat, event->origin, event->serial)) { wlr_seat_start_pointer_drag (seat->seat, event->drag, event->serial); return; } struct wlr_touch_point *point; if (wlr_seat_validate_touch_grab_serial (seat->seat, event->origin, event->serial, &point)) { wlr_seat_start_touch_drag (seat->seat, event->drag, event->serial, point); return; } g_debug ("Ignoring start_drag request: " "could not validate pointer or touch serial %" PRIu32, event->serial); wlr_data_source_destroy (event->drag->source); } static void phoc_seat_handle_start_drag (struct wl_listener *listener, void *data) { PhocSeat *seat = wl_container_of (listener, seat, start_drag); struct wlr_drag *wlr_drag = data; struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon; if (wlr_drag_icon == NULL) { return; } PhocDragIcon *icon = g_new0 (PhocDragIcon, 1); icon->seat = seat; icon->wlr_drag_icon = wlr_drag_icon; icon->surface_commit.notify = phoc_drag_icon_handle_surface_commit; wl_signal_add (&wlr_drag_icon->surface->events.commit, &icon->surface_commit); icon->unmap.notify = phoc_drag_icon_handle_unmap; wl_signal_add (&wlr_drag_icon->events.unmap, &icon->unmap); icon->map.notify = phoc_drag_icon_handle_map; wl_signal_add (&wlr_drag_icon->events.map, &icon->map); icon->destroy.notify = phoc_drag_icon_handle_destroy; wl_signal_add (&wlr_drag_icon->events.destroy, &icon->destroy); assert (seat->drag_icon == NULL); seat->drag_icon = icon; phoc_drag_icon_update_position (icon); } static void phoc_seat_handle_request_set_selection ( struct wl_listener *listener, void *data) { PhocSeat *seat = wl_container_of (listener, seat, request_set_selection); struct wlr_seat_request_set_selection_event *event = data; wlr_seat_set_selection (seat->seat, event->source, event->serial); } static void phoc_seat_handle_request_set_primary_selection ( struct wl_listener *listener, void *data) { PhocSeat *seat = wl_container_of (listener, seat, request_set_primary_selection); struct wlr_seat_request_set_primary_selection_event *event = data; wlr_seat_set_primary_selection (seat->seat, event->source, event->serial); } void phoc_drag_icon_update_position (PhocDragIcon *icon) { phoc_drag_icon_damage_whole (icon); PhocSeat *seat = icon->seat; struct wlr_drag *wlr_drag = icon->wlr_drag_icon->drag; assert (wlr_drag != NULL); switch (seat->seat->drag->grab_type) { case WLR_DRAG_GRAB_KEYBOARD: assert (false); case WLR_DRAG_GRAB_KEYBOARD_POINTER:; struct wlr_cursor *cursor = seat->cursor->cursor; icon->x = cursor->x; icon->y = cursor->y; break; case WLR_DRAG_GRAB_KEYBOARD_TOUCH:; struct wlr_touch_point *point = wlr_seat_touch_get_point (seat->seat, wlr_drag->touch_id); if (point == NULL) { return; } icon->x = seat->touch_x; icon->y = seat->touch_y; break; default: g_error ("Invalid drag grab type %d", seat->seat->drag->grab_type); } phoc_drag_icon_damage_whole (icon); } void phoc_drag_icon_damage_whole (PhocDragIcon *icon) { PhocServer *server = phoc_server_get_default (); PhocOutput *output; wl_list_for_each (output, &server->desktop->outputs, link) { phoc_output_damage_whole_drag_icon (output, icon); } } static void seat_view_destroy (PhocSeatView *seat_view); static void phoc_seat_handle_destroy (struct wl_listener *listener, void *data) { PhocSeat *seat = wl_container_of (listener, seat, destroy); // TODO: probably more to be freed here wl_list_remove (&seat->destroy.link); phoc_input_method_relay_destroy (&seat->im_relay); PhocSeatView *view, *nview; wl_list_for_each_safe (view, nview, &seat->views, link) { seat_view_destroy (view); } } static void seat_update_capabilities (PhocSeat *seat) { uint32_t caps = 0; if (seat->keyboards != NULL) { caps |= WL_SEAT_CAPABILITY_KEYBOARD; } if (seat->pointers != NULL) { caps |= WL_SEAT_CAPABILITY_POINTER; } if (seat->touch != NULL) { caps |= WL_SEAT_CAPABILITY_TOUCH; } wlr_seat_set_capabilities (seat->seat, caps); phoc_seat_maybe_set_cursor (seat, seat->cursor->default_xcursor); } static void on_settings_output_changed (PhocSeat *seat) { g_assert (PHOC_IS_SEAT (seat)); g_debug ("Input output mappings changed, reloading settings"); phoc_seat_configure_cursor (seat); } static void phoc_seat_add_input_mapping_settings (PhocSeat *self, PhocInputDevice *device) { const char *schema, *group, *vendor, *product; g_autofree char *path = NULL; g_autoptr (GSettings) settings = NULL; switch (phoc_input_device_get_device_type (device)) { case WLR_INPUT_DEVICE_TOUCH: schema = "org.gnome.desktop.peripherals.touchscreen"; group = "touchscreens"; break; case WLR_INPUT_DEVICE_TABLET_TOOL: schema = "org.gnome.desktop.peripherals.tablet"; group = "tablets"; break; default: g_assert_not_reached (); } vendor = phoc_input_device_get_vendor_id (device); product = phoc_input_device_get_product_id (device); path = g_strdup_printf ("/org/gnome/desktop/peripherals/%s/%s:%s/", group, vendor, product); g_debug ("Tracking config path %s for %s", path, phoc_input_device_get_name (device)); settings = g_settings_new_with_path (schema, path); g_signal_connect_swapped (settings, "changed::output", G_CALLBACK (on_settings_output_changed), self); g_hash_table_insert (self->input_mapping_settings, device, g_steal_pointer (&settings)); on_settings_output_changed (self); } static void on_keyboard_destroy (PhocSeat *self, PhocKeyboard *keyboard) { g_assert (PHOC_IS_SEAT (self)); g_assert (PHOC_IS_KEYBOARD (keyboard)); self->keyboards = g_slist_remove (self->keyboards, keyboard); g_object_unref (keyboard); seat_update_capabilities (self); } static void on_keyboard_activity (PhocSeat *self, PhocKeyboard *keyboard) { PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; g_assert (PHOC_IS_SEAT (self)); g_assert (PHOC_IS_KEYBOARD (keyboard)); wlr_idle_notify_activity (desktop->idle, self->seat); } static void seat_add_keyboard (PhocSeat *seat, struct wlr_input_device *device) { assert (device->type == WLR_INPUT_DEVICE_KEYBOARD); PhocKeyboard *keyboard = phoc_keyboard_new (device, seat); seat->keyboards = g_slist_prepend (seat->keyboards, keyboard); g_signal_connect_swapped (keyboard, "device-destroy", G_CALLBACK (on_keyboard_destroy), seat); g_signal_connect_swapped (keyboard, "activity", G_CALLBACK (on_keyboard_activity), seat); wlr_seat_set_keyboard (seat->seat, device); } static void on_pointer_destroy (PhocTouch *pointer) { PhocSeat *seat = phoc_input_device_get_seat (PHOC_INPUT_DEVICE (pointer)); struct wlr_input_device *device = phoc_input_device_get_device (PHOC_INPUT_DEVICE (pointer)); g_assert (PHOC_IS_POINTER (pointer)); g_debug ("Removing pointer device: %s", device->name); seat->pointers = g_slist_remove (seat->pointers, pointer); wlr_cursor_detach_input_device (seat->cursor->cursor, device); g_object_unref (pointer); seat_update_capabilities (seat); } static void seat_add_pointer (PhocSeat *seat, struct wlr_input_device *device) { PhocPointer *pointer = phoc_pointer_new (device, seat); seat->pointers = g_slist_prepend (seat->pointers, pointer); g_signal_connect (pointer, "device-destroy", G_CALLBACK (on_pointer_destroy), NULL); wlr_cursor_attach_input_device (seat->cursor->cursor, device); } static void handle_switch_destroy (struct wl_listener *listener, void *data) { struct roots_switch *switch_device = wl_container_of (listener, switch_device, device_destroy); PhocSeat *seat = switch_device->seat; wl_list_remove (&switch_device->link); wl_list_remove (&switch_device->device_destroy.link); free (switch_device); seat_update_capabilities (seat); } static void seat_add_switch (PhocSeat *seat, struct wlr_input_device *device) { assert (device->type == WLR_INPUT_DEVICE_SWITCH); struct roots_switch *switch_device = g_new0 (struct roots_switch, 1); device->data = switch_device; switch_device->device = device; switch_device->seat = seat; wl_list_insert (&seat->switches, &switch_device->link); switch_device->device_destroy.notify = handle_switch_destroy; switch_device->toggle.notify = handle_switch_toggle; wl_signal_add (&switch_device->device->switch_device->events.toggle, &switch_device->toggle); } static void on_touch_destroy (PhocTouch *touch) { PhocSeat *seat = phoc_input_device_get_seat (PHOC_INPUT_DEVICE (touch)); PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; struct wlr_input_device *device = phoc_input_device_get_device (PHOC_INPUT_DEVICE (touch)); g_assert (PHOC_IS_TOUCH (touch)); g_debug ("Removing touch device: %s", device->name); g_hash_table_remove (desktop->input_output_map, device->name); g_hash_table_remove (seat->input_mapping_settings, touch); seat->touch = g_slist_remove (seat->touch, touch); wlr_cursor_detach_input_device (seat->cursor->cursor, device); g_object_unref (touch); seat_update_capabilities (seat); } static void seat_add_touch (PhocSeat *seat, struct wlr_input_device *device) { PhocTouch *touch = phoc_touch_new (device, seat); seat->touch = g_slist_prepend (seat->touch, touch); g_signal_connect (touch, "device-destroy", G_CALLBACK (on_touch_destroy), NULL); wlr_cursor_attach_input_device (seat->cursor->cursor, device); phoc_seat_add_input_mapping_settings (seat, PHOC_INPUT_DEVICE (touch)); } static void handle_tablet_pad_destroy (struct wl_listener *listener, void *data) { PhocTabletPad *tablet_pad = wl_container_of (listener, tablet_pad, device_destroy); PhocSeat *seat = tablet_pad->seat; wl_list_remove (&tablet_pad->device_destroy.link); wl_list_remove (&tablet_pad->tablet_destroy.link); wl_list_remove (&tablet_pad->attach.link); wl_list_remove (&tablet_pad->link); wl_list_remove (&tablet_pad->button.link); wl_list_remove (&tablet_pad->strip.link); wl_list_remove (&tablet_pad->ring.link); free (tablet_pad); seat_update_capabilities (seat); } static void handle_pad_tool_destroy (struct wl_listener *listener, void *data) { PhocTabletPad *pad = wl_container_of (listener, pad, tablet_destroy); pad->tablet = NULL; wl_list_remove (&pad->tablet_destroy.link); wl_list_init (&pad->tablet_destroy.link); } static void attach_tablet_pad (PhocTabletPad *pad, PhocTablet *tool) { struct wlr_input_device *device = phoc_input_device_get_device (PHOC_INPUT_DEVICE (tool)); g_debug ("Attaching tablet pad \"%s\" to tablet tool \"%s\"", pad->device->name, device->name); pad->tablet = tool; wl_list_remove (&pad->tablet_destroy.link); pad->tablet_destroy.notify = handle_pad_tool_destroy; wl_signal_add (&device->events.destroy, &pad->tablet_destroy); } static void handle_tablet_pad_attach (struct wl_listener *listener, void *data) { PhocTabletPad *pad = wl_container_of (listener, pad, attach); struct wlr_tablet_tool *wlr_tool = data; PhocTablet *tool = wlr_tool->data; attach_tablet_pad (pad, tool); } static void handle_tablet_pad_ring (struct wl_listener *listener, void *data) { PhocTabletPad *pad = wl_container_of (listener, pad, ring); struct wlr_event_tablet_pad_ring *event = data; wlr_tablet_v2_tablet_pad_notify_ring (pad->tablet_v2_pad, event->ring, event->position, event->source == WLR_TABLET_PAD_RING_SOURCE_FINGER, event->time_msec); } static void handle_tablet_pad_strip (struct wl_listener *listener, void *data) { PhocTabletPad *pad = wl_container_of (listener, pad, strip); struct wlr_event_tablet_pad_strip *event = data; wlr_tablet_v2_tablet_pad_notify_strip (pad->tablet_v2_pad, event->strip, event->position, event->source == WLR_TABLET_PAD_STRIP_SOURCE_FINGER, event->time_msec); } static void handle_tablet_pad_button (struct wl_listener *listener, void *data) { PhocTabletPad *pad = wl_container_of (listener, pad, button); struct wlr_event_tablet_pad_button *event = data; wlr_tablet_v2_tablet_pad_notify_mode (pad->tablet_v2_pad, event->group, event->mode, event->time_msec); wlr_tablet_v2_tablet_pad_notify_button (pad->tablet_v2_pad, event->button, event->time_msec, (enum zwp_tablet_pad_v2_button_state)event->state); } static void seat_add_tablet_pad (PhocSeat *seat, struct wlr_input_device *device) { PhocServer *server = phoc_server_get_default (); PhocTabletPad *tablet_pad = g_new0 (PhocTabletPad, 1); device->data = tablet_pad; tablet_pad->device = device; tablet_pad->seat = seat; wl_list_insert (&seat->tablet_pads, &tablet_pad->link); tablet_pad->device_destroy.notify = handle_tablet_pad_destroy; wl_signal_add (&tablet_pad->device->events.destroy, &tablet_pad->device_destroy); tablet_pad->attach.notify = handle_tablet_pad_attach; wl_signal_add (&tablet_pad->device->tablet_pad->events.attach_tablet, &tablet_pad->attach); tablet_pad->button.notify = handle_tablet_pad_button; wl_signal_add (&tablet_pad->device->tablet_pad->events.button, &tablet_pad->button); tablet_pad->strip.notify = handle_tablet_pad_strip; wl_signal_add (&tablet_pad->device->tablet_pad->events.strip, &tablet_pad->strip); tablet_pad->ring.notify = handle_tablet_pad_ring; wl_signal_add (&tablet_pad->device->tablet_pad->events.ring, &tablet_pad->ring); wl_list_init (&tablet_pad->tablet_destroy.link); PhocDesktop *desktop = server->desktop; tablet_pad->tablet_v2_pad = wlr_tablet_pad_create (desktop->tablet_v2, seat->seat, device); /* Search for a sibling tablet */ if (!wlr_input_device_is_libinput (device)) { /* We can only do this on libinput devices */ return; } struct libinput_device_group *group = libinput_device_get_device_group (wlr_libinput_get_device_handle (device)); for (GSList *elem = seat->tablets; elem; elem = elem->next) { PhocInputDevice *input_device = PHOC_INPUT_DEVICE (elem->data); //struct wlr_input_device *tool_device = phoc_input_device_get_device (input_deviceool); if (!phoc_input_device_get_is_libinput (input_device)) continue; struct libinput_device *li_dev = phoc_input_device_get_libinput_device_handle (input_device); if (libinput_device_get_device_group (li_dev) == group) { attach_tablet_pad (tablet_pad, PHOC_TABLET (input_device)); break; } } } static void on_tablet_destroy (PhocSeat *seat, PhocTablet *tablet) { PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; struct wlr_input_device *device = phoc_input_device_get_device (PHOC_INPUT_DEVICE (tablet)); wlr_cursor_detach_input_device (seat->cursor->cursor, device); g_hash_table_remove (seat->input_mapping_settings, tablet); g_hash_table_remove (desktop->input_output_map, device->name); seat->tablets = g_slist_remove (seat->tablets, tablet); g_object_unref (tablet); seat_update_capabilities (seat); } static void seat_add_tablet_tool (PhocSeat *seat, struct wlr_input_device *device) { PhocServer *server = phoc_server_get_default (); if (!wlr_input_device_is_libinput (device)) return; PhocTablet *tablet = phoc_tablet_new (device, seat); seat->tablets = g_slist_prepend (seat->tablets, tablet); g_signal_connect_swapped (tablet, "device-destroy", G_CALLBACK (on_tablet_destroy), seat); wlr_cursor_attach_input_device (seat->cursor->cursor, device); phoc_seat_add_input_mapping_settings (seat, PHOC_INPUT_DEVICE (tablet)); PhocDesktop *desktop = server->desktop; tablet->tablet_v2 = wlr_tablet_create (desktop->tablet_v2, seat->seat, device); struct libinput_device_group *group = libinput_device_get_device_group (wlr_libinput_get_device_handle (device)); PhocTabletPad *pad; wl_list_for_each (pad, &seat->tablet_pads, link) { if (!wlr_input_device_is_libinput (pad->device)) { continue; } struct libinput_device *li_dev = wlr_libinput_get_device_handle (pad->device); if (libinput_device_get_device_group (li_dev) == group) { attach_tablet_pad (pad, tablet); } } } void phoc_seat_add_device (PhocSeat *seat, struct wlr_input_device *device) { g_debug ("Adding device %s %d", device->name, device->type); switch (device->type) { case WLR_INPUT_DEVICE_KEYBOARD: seat_add_keyboard (seat, device); break; case WLR_INPUT_DEVICE_POINTER: seat_add_pointer (seat, device); break; case WLR_INPUT_DEVICE_SWITCH: seat_add_switch (seat, device); break; case WLR_INPUT_DEVICE_TOUCH: seat_add_touch (seat, device); break; case WLR_INPUT_DEVICE_TABLET_PAD: seat_add_tablet_pad (seat, device); break; case WLR_INPUT_DEVICE_TABLET_TOOL: seat_add_tablet_tool (seat, device); break; default: g_error ("Invalid device type %d", device->type); } seat_update_capabilities (seat); } void phoc_seat_configure_xcursor (PhocSeat *seat) { PhocServer *server = phoc_server_get_default (); PhocOutput *output; wl_list_for_each (output, &server->desktop->outputs, link) { float scale = output->wlr_output->scale; if (!wlr_xcursor_manager_load (seat->cursor->xcursor_manager, scale)) { g_critical ("Cannot load xcursor theme for output '%s' " "with scale %f", output->wlr_output->name, scale); } } phoc_seat_maybe_set_cursor (seat, seat->cursor->default_xcursor); wlr_cursor_warp (seat->cursor->cursor, NULL, seat->cursor->cursor->x, seat->cursor->cursor->y); } bool phoc_seat_has_meta_pressed (PhocSeat *seat) { for (GSList *elem = seat->keyboards; elem; elem = elem->next) { PhocKeyboard *keyboard = PHOC_KEYBOARD (elem->data); PhocInputDevice *input_device = PHOC_INPUT_DEVICE (keyboard); struct wlr_input_device *device = phoc_input_device_get_device (input_device); uint32_t modifiers = wlr_keyboard_get_modifiers (device->keyboard); if ((modifiers ^ phoc_keyboard_get_meta_key (keyboard)) == 0) { return true; } } return false; } PhocView * phoc_seat_get_focus (PhocSeat *seat) { if (!seat->has_focus || wl_list_empty (&seat->views)) { return NULL; } PhocSeatView *seat_view = wl_container_of (seat->views.next, seat_view, link); return seat_view->view; } static void seat_view_destroy (PhocSeatView *seat_view) { PhocSeat *seat = seat_view->seat; PhocView *view = seat_view->view; if (view == phoc_seat_get_focus (seat)) { seat->has_focus = false; seat->cursor->mode = PHOC_CURSOR_PASSTHROUGH; } if (seat_view == seat->cursor->pointer_view) { seat->cursor->pointer_view = NULL; } wl_list_remove (&seat_view->view_unmap.link); wl_list_remove (&seat_view->view_destroy.link); wl_list_remove (&seat_view->link); free (seat_view); if (view && view->parent) { phoc_seat_set_focus (seat, view->parent); } else if (!wl_list_empty (&seat->views)) { // Focus first view PhocSeatView *first_seat_view = wl_container_of ( seat->views.next, first_seat_view, link); phoc_seat_set_focus (seat, first_seat_view->view); } } static void seat_view_handle_unmap (struct wl_listener *listener, void *data) { PhocSeatView *seat_view = wl_container_of (listener, seat_view, view_unmap); seat_view_destroy (seat_view); } static void seat_view_handle_destroy (struct wl_listener *listener, void *data) { PhocSeatView *seat_view = wl_container_of (listener, seat_view, view_destroy); seat_view_destroy (seat_view); } static PhocSeatView * seat_add_view (PhocSeat *seat, PhocView *view) { PhocSeatView *seat_view = g_new0 (PhocSeatView, 1); seat_view->seat = seat; seat_view->view = view; wl_list_insert (seat->views.prev, &seat_view->link); seat_view->view_unmap.notify = seat_view_handle_unmap; wl_signal_add (&view->events.unmap, &seat_view->view_unmap); seat_view->view_destroy.notify = seat_view_handle_destroy; wl_signal_add (&view->events.destroy, &seat_view->view_destroy); return seat_view; } PhocSeatView * phoc_seat_view_from_view (PhocSeat *seat, PhocView *view) { if (view == NULL) { return NULL; } bool found = false; PhocSeatView *seat_view = NULL; wl_list_for_each (seat_view, &seat->views, link) { if (seat_view->view == view) { found = true; break; } } if (!found) seat_view = seat_add_view (seat, view); return seat_view; } bool phoc_seat_allow_input (PhocSeat *seat, struct wl_resource *resource) { return !seat->exclusive_client || wl_resource_get_client (resource) == seat->exclusive_client; } static void seat_raise_view_stack (PhocSeat *seat, PhocView *view) { PhocServer *server = phoc_server_get_default (); if (!view->wlr_surface) { return; } wl_list_remove (&view->link); wl_list_insert (&server->desktop->views, &view->link); phoc_view_damage_whole (view); PhocView *child; wl_list_for_each_reverse (child, &view->stack, parent_link) { seat_raise_view_stack (seat, child); } } void phoc_seat_set_focus (PhocSeat *seat, PhocView *view) { if (view && !phoc_seat_allow_input (seat, view->wlr_surface->resource)) { return; } // Make sure the view will be rendered on top of others, even if it's // already focused in this seat if (view != NULL) { PhocView *parent = view; // reorder stack while (parent->parent) { wl_list_remove (&parent->parent_link); wl_list_insert (&parent->parent->stack, &parent->parent_link); parent = parent->parent; } seat_raise_view_stack (seat, parent); } bool unfullscreen = true; #ifdef PHOC_XWAYLAND if (view && view->type == PHOC_XWAYLAND_VIEW) { PhocXWaylandSurface *xwayland_surface = phoc_xwayland_surface_from_view (view); if (xwayland_surface->xwayland_surface->override_redirect) { unfullscreen = false; } } #endif if (view && unfullscreen) { PhocDesktop *desktop = view->desktop; PhocOutput *output; struct wlr_box box; view_get_box (view, &box); wl_list_for_each (output, &desktop->outputs, link) { if (output->fullscreen_view && output->fullscreen_view != view && wlr_output_layout_intersects ( desktop->layout, output->wlr_output, &box)) { phoc_view_set_fullscreen (output->fullscreen_view, false, NULL); } } } PhocView *prev_focus = phoc_seat_get_focus (seat); if (view && view == prev_focus) { return; } #ifdef PHOC_XWAYLAND if (view && view->type == PHOC_XWAYLAND_VIEW) { PhocXWaylandSurface *xwayland_surface = phoc_xwayland_surface_from_view (view); if (!wlr_xwayland_or_surface_wants_focus ( xwayland_surface->xwayland_surface)) { return; } } #endif PhocSeatView *seat_view = NULL; if (view != NULL) { seat_view = phoc_seat_view_from_view (seat, view); if (seat_view == NULL) { return; } } seat->has_focus = false; // Deactivate the old view if it is not focused by some other seat if (prev_focus != NULL && !phoc_input_view_has_focus (seat->input, prev_focus)) { view_activate (prev_focus, false); } if (view == NULL) { seat->cursor->mode = PHOC_CURSOR_PASSTHROUGH; wlr_seat_keyboard_clear_focus (seat->seat); phoc_input_method_relay_set_focus (&seat->im_relay, NULL); return; } wl_list_remove (&seat_view->link); wl_list_insert (&seat->views, &seat_view->link); if (seat->focused_layer) { return; } view_activate (view, true); seat->has_focus = true; // An existing keyboard grab might try to deny setting focus, so cancel it wlr_seat_keyboard_end_grab (seat->seat); struct wlr_keyboard *keyboard = wlr_seat_get_keyboard (seat->seat); if (keyboard != NULL) { wlr_seat_keyboard_notify_enter (seat->seat, view->wlr_surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); /* FIXME: Move this to a better place */ PhocTabletPad *pad; wl_list_for_each (pad, &seat->tablet_pads, link) { if (pad->tablet) { wlr_tablet_v2_tablet_pad_notify_enter (pad->tablet_v2_pad, pad->tablet->tablet_v2, view->wlr_surface); } } } else { wlr_seat_keyboard_notify_enter (seat->seat, view->wlr_surface, NULL, 0, NULL); } phoc_cursor_update_focus (seat->cursor); phoc_input_method_relay_set_focus (&seat->im_relay, view->wlr_surface); } /* * Focus semantics of layer surfaces are somewhat detached from the normal focus * flow. For layers above the shell layer, for example, you cannot unfocus them. * You also cannot alt-tab between layer surfaces and shell surfaces. */ void phoc_seat_set_focus_layer (PhocSeat *seat, struct wlr_layer_surface_v1 *layer) { PhocServer *server = phoc_server_get_default (); if (!layer) { if (seat->focused_layer) { seat->focused_layer = NULL; if (!wl_list_empty (&seat->views)) { // Focus first view PhocSeatView *first_seat_view = wl_container_of ( seat->views.next, first_seat_view, link); phoc_seat_set_focus (seat, first_seat_view->view); } else { phoc_seat_set_focus (seat, NULL); } PhocOutput *output; wl_list_for_each (output, &server->desktop->outputs, link) { phoc_layer_shell_arrange (output); } } return; } struct wlr_keyboard *keyboard = wlr_seat_get_keyboard (seat->seat); if (!phoc_seat_allow_input (seat, layer->resource)) { return; } if (seat->has_focus) { PhocView *prev_focus = phoc_seat_get_focus (seat); wlr_seat_keyboard_clear_focus (seat->seat); view_activate (prev_focus, false); } seat->has_focus = false; if (layer->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { seat->focused_layer = layer; } if (keyboard != NULL) { wlr_seat_keyboard_notify_enter (seat->seat, layer->surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); } else { wlr_seat_keyboard_notify_enter (seat->seat, layer->surface, NULL, 0, NULL); } phoc_cursor_update_focus (seat->cursor); phoc_input_method_relay_set_focus (&seat->im_relay, layer->surface); } void phoc_seat_set_exclusive_client (PhocSeat *seat, struct wl_client *client) { if (!client) { seat->exclusive_client = client; // Triggers a refocus of the topmost surface layer if necessary phoc_layer_shell_update_focus (); return; } if (seat->focused_layer) { if (wl_resource_get_client (seat->focused_layer->resource) != client) { phoc_seat_set_focus_layer (seat, NULL); } } if (seat->has_focus) { PhocView *focus = phoc_seat_get_focus (seat); if (wl_resource_get_client (focus->wlr_surface->resource) != client) { phoc_seat_set_focus (seat, NULL); } } if (seat->seat->pointer_state.focused_client) { if (seat->seat->pointer_state.focused_client->client != client) { wlr_seat_pointer_clear_focus (seat->seat); } } struct timespec now; clock_gettime (CLOCK_MONOTONIC, &now); struct wlr_touch_point *point; wl_list_for_each (point, &seat->seat->touch_state.touch_points, link) { if (point->client->client != client) { wlr_seat_touch_point_clear_focus (seat->seat, now.tv_nsec / 1000, point->touch_id); } } seat->exclusive_client = client; } void phoc_seat_cycle_focus (PhocSeat *seat) { if (wl_list_empty (&seat->views)) { return; } PhocSeatView *first_seat_view = wl_container_of ( seat->views.next, first_seat_view, link); if (!seat->has_focus) { phoc_seat_set_focus (seat, first_seat_view->view); return; } if (wl_list_length (&seat->views) < 2) { return; } // Focus the next view PhocSeatView *next_seat_view = wl_container_of ( first_seat_view->link.next, next_seat_view, link); phoc_seat_set_focus (seat, next_seat_view->view); // Move the first view to the end of the list wl_list_remove (&first_seat_view->link); wl_list_insert (seat->views.prev, &first_seat_view->link); } void phoc_seat_begin_move (PhocSeat *seat, PhocView *view) { if (view->desktop->maximize) return; PhocCursor *cursor = seat->cursor; cursor->mode = PHOC_CURSOR_MOVE; if (seat->touch_id != -1) { wlr_cursor_warp (cursor->cursor, NULL, seat->touch_x, seat->touch_y); } cursor->offs_x = cursor->cursor->x; cursor->offs_y = cursor->cursor->y; struct wlr_box geom; view_get_geometry (view, &geom); if (view_is_maximized (view) || view_is_tiled (view)) { // calculate normalized (0..1) position of cursor in maximized window // and make it stay the same after restoring saved size double x = (cursor->cursor->x - view->box.x) / view->box.width; double y = (cursor->cursor->y - view->box.y) / view->box.height; cursor->view_x = cursor->cursor->x - x * view->saved.width; cursor->view_y = cursor->cursor->y - y * view->saved.height; view->saved.x = cursor->view_x; view->saved.y = cursor->view_y; view_restore (view); } else { cursor->view_x = view->box.x + geom.x * view->scale; cursor->view_y = view->box.y + geom.y * view->scale; } wlr_seat_pointer_clear_focus (seat->seat); phoc_seat_maybe_set_cursor (seat, PHOC_XCURSOR_MOVE); } void phoc_seat_begin_resize (PhocSeat *seat, PhocView *view, uint32_t edges) { if (view->desktop->maximize || view_is_fullscreen (view)) return; PhocCursor *cursor = seat->cursor; cursor->mode = PHOC_CURSOR_RESIZE; if (seat->touch_id != -1) { wlr_cursor_warp (cursor->cursor, NULL, seat->touch_x, seat->touch_y); } cursor->offs_x = cursor->cursor->x; cursor->offs_y = cursor->cursor->y; struct wlr_box geom; view_get_geometry (view, &geom); if (view_is_maximized (view) || view_is_tiled (view)) { view->saved.x = view->box.x + geom.x * view->scale; view->saved.y = view->box.y + geom.y * view->scale; view->saved.width = view->box.width; view->saved.height = view->box.height; view_restore (view); } cursor->view_x = view->box.x + geom.x * view->scale; cursor->view_y = view->box.y + geom.y * view->scale; struct wlr_box box; view_get_box (view, &box); cursor->view_width = box.width; cursor->view_height = box.height; cursor->resize_edges = edges; wlr_seat_pointer_clear_focus (seat->seat); const char *resize_name = wlr_xcursor_get_resize_name (edges); phoc_seat_maybe_set_cursor (seat, resize_name); } void phoc_seat_end_compositor_grab (PhocSeat *seat) { PhocCursor *cursor = seat->cursor; PhocView *view = phoc_seat_get_focus (seat); if (view == NULL) { return; } switch (cursor->mode) { case PHOC_CURSOR_MOVE: if (!view_is_fullscreen (view)) view_move (view, cursor->view_x, cursor->view_y); break; case PHOC_CURSOR_RESIZE: view_move_resize (view, cursor->view_x, cursor->view_y, cursor->view_width, cursor->view_height); break; case PHOC_CURSOR_PASSTHROUGH: break; default: g_error ("Invalid cursor mode %d", cursor->mode); } cursor->mode = PHOC_CURSOR_PASSTHROUGH; phoc_cursor_update_focus (seat->cursor); } /** * phoc_seat_maybe_set_cursor: * @self: a PhocSeat * @name: (nullable): a cursor name or %NULL for the themes default cursor * * Show a cursor if the seat has pointer capabilities */ void phoc_seat_maybe_set_cursor (PhocSeat *self, const char *name) { if (phoc_seat_has_pointer (self) == FALSE) { wlr_cursor_set_image (self->cursor->cursor, NULL, 0, 0, 0, 0, 0, 0); } else { if (!name) name = self->cursor->default_xcursor; wlr_xcursor_manager_set_cursor_image (self->cursor->xcursor_manager, name, self->cursor->cursor); } } /** * phoc_seat_get_cursor: * @self: a PhocSeat * * Get the curent cursor * * Returns: (transfer none): The current cursor */ PhocCursor * phoc_seat_get_cursor (PhocSeat *self) { g_return_val_if_fail (self, NULL); return self->cursor; } static void phoc_seat_constructed (GObject *object) { PhocSeat *self = PHOC_SEAT(object); PhocServer *server = phoc_server_get_default (); G_OBJECT_CLASS (phoc_seat_parent_class)->constructed (object); self->seat = wlr_seat_create (server->wl_display, self->name); g_assert (self->seat); self->seat->data = self; phoc_seat_init_cursor (self); g_assert (self->cursor); phoc_input_method_relay_init (self, &self->im_relay); self->request_set_selection.notify = phoc_seat_handle_request_set_selection; wl_signal_add (&self->seat->events.request_set_selection, &self->request_set_selection); self->request_set_primary_selection.notify = phoc_seat_handle_request_set_primary_selection; wl_signal_add (&self->seat->events.request_set_primary_selection, &self->request_set_primary_selection); self->request_start_drag.notify = phoc_seat_handle_request_start_drag; wl_signal_add (&self->seat->events.request_start_drag, &self->request_start_drag); self->start_drag.notify = phoc_seat_handle_start_drag; wl_signal_add (&self->seat->events.start_drag, &self->start_drag); self->destroy.notify = phoc_seat_handle_destroy; wl_signal_add (&self->seat->events.destroy, &self->destroy); } static void phoc_seat_dispose (GObject *object) { PhocSeat *self = PHOC_SEAT (object); g_clear_object (&self->cursor); G_OBJECT_CLASS (phoc_seat_parent_class)->dispose (object); } static void phoc_seat_finalize (GObject *object) { PhocSeat *self = PHOC_SEAT (object); g_clear_pointer (&self->input_mapping_settings, g_hash_table_destroy); phoc_seat_handle_destroy (&self->destroy, self->seat); wlr_seat_destroy (self->seat); g_clear_pointer (&self->name, g_free); G_OBJECT_CLASS (phoc_seat_parent_class)->finalize (object); } static void phoc_seat_class_init (PhocSeatClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = phoc_seat_get_property; object_class->set_property = phoc_seat_set_property; object_class->constructed = phoc_seat_constructed; object_class->dispose = phoc_seat_dispose; object_class->finalize = phoc_seat_finalize; /** * PhocSeat:input: * * The %PhocInput that keeps track of all seats */ props[PROP_INPUT] = g_param_spec_object ("input", "", "", PHOC_TYPE_INPUT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * PhocSeat:name: * * The name of this seat. */ props[PROP_NAME] = g_param_spec_string ("name", "", "", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } static void phoc_seat_init (PhocSeat *self) { wl_list_init (&self->tablet_pads); wl_list_init (&self->switches); wl_list_init (&self->views); self->touch_id = -1; self->input_mapping_settings = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); } PhocSeat * phoc_seat_new (PhocInput *input, const char *name) { return PHOC_SEAT (g_object_new (PHOC_TYPE_SEAT, "input", input, "name", name, NULL)); } gboolean phoc_seat_has_touch (PhocSeat *self) { g_return_val_if_fail (PHOC_IS_SEAT (self), FALSE); g_assert (self->seat); return (self->seat->capabilities & WL_SEAT_CAPABILITY_TOUCH); } gboolean phoc_seat_has_pointer (PhocSeat *self) { g_return_val_if_fail (PHOC_IS_SEAT (self), FALSE); g_assert (self->seat); return (self->seat->capabilities & WL_SEAT_CAPABILITY_POINTER); } gboolean phoc_seat_has_keyboard (PhocSeat *self) { g_return_val_if_fail (PHOC_IS_SEAT (self), FALSE); g_assert (self->seat); return (self->seat->capabilities & WL_SEAT_CAPABILITY_KEYBOARD); } phoc-v0.13.1/src/seat.h000066400000000000000000000132641422111650000146050ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "input.h" #include "layers.h" #include "switch.h" #include "text_input.h" #include G_BEGIN_DECLS #define PHOC_TYPE_SEAT (phoc_seat_get_type ()) G_DECLARE_FINAL_TYPE (PhocSeat, phoc_seat, PHOC, SEAT, GObject) typedef struct _PhocCursor PhocCursor; typedef struct _PhocDragIcon PhocDragIcon; typedef struct _PhocTablet PhocTablet; /** * PhocSeat: * @im_relay: The input method relay for this seat * * Represents a seat */ /* TODO: we keep the struct public due to the list links and notifiers but we should avoid other member access */ typedef struct _PhocSeat { GObject parent; PhocInput *input; char *name; struct wlr_seat *seat; PhocCursor *cursor; // coordinates of the first touch point if it exists int32_t touch_id; double touch_x, touch_y; // If the focused layer is set, views cannot receive keyboard focus struct wlr_layer_surface_v1 *focused_layer; PhocInputMethodRelay im_relay; // If non-null, only this client can receive input events struct wl_client *exclusive_client; struct wl_list views; // PhocSeatView::link bool has_focus; PhocDragIcon *drag_icon; // can be NULL GSList *keyboards; GSList *pointers; struct wl_list switches; GSList *touch; GSList *tablets; struct wl_list tablet_pads; struct wl_listener request_set_selection; struct wl_listener request_set_primary_selection; struct wl_listener request_start_drag; struct wl_listener start_drag; struct wl_listener destroy; GHashTable *input_mapping_settings; } PhocSeat; typedef struct _PhocSeatView { PhocSeat *seat; PhocView *view; bool has_button_grab; double grab_sx; double grab_sy; struct wl_list link; // PhocSeat::views struct wl_listener view_unmap; struct wl_listener view_destroy; } PhocSeatView; struct _PhocDragIcon { PhocSeat *seat; struct wlr_drag_icon *wlr_drag_icon; double x, y; struct wl_listener surface_commit; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; }; typedef struct _PhocTabletPad { struct wl_list link; struct wlr_tablet_v2_tablet_pad *tablet_v2_pad; PhocSeat *seat; struct wlr_input_device *device; struct wl_listener device_destroy; struct wl_listener attach; struct wl_listener button; struct wl_listener ring; struct wl_listener strip; PhocTablet *tablet; struct wl_listener tablet_destroy; } PhocTabletPad; typedef struct _PhocTabletTool { struct wl_list link; struct wl_list tool_link; struct wlr_tablet_v2_tablet_tool *tablet_v2_tool; PhocSeat *seat; double tilt_x, tilt_y; struct wl_listener set_cursor; struct wl_listener tool_destroy; PhocTablet *current_tablet; struct wl_listener tablet_destroy; } PhocTabletTool; typedef struct PhocPointerConstraint { struct wlr_pointer_constraint_v1 *constraint; struct wl_listener destroy; } PhocPointerConstraint; PhocSeat *phoc_seat_new (PhocInput *input, const char *name); void phoc_seat_add_device (PhocSeat *seat, struct wlr_input_device *device); void phoc_seat_configure_cursor (PhocSeat *seat); PhocCursor *phoc_seat_get_cursor (PhocSeat *self); void phoc_seat_configure_xcursor (PhocSeat *seat); bool phoc_seat_has_meta_pressed (PhocSeat *seat); PhocView *phoc_seat_get_focus (PhocSeat *seat); void phoc_seat_set_focus (PhocSeat *seat, PhocView *view); void phoc_seat_set_focus_layer (PhocSeat *seat, struct wlr_layer_surface_v1 *layer); void phoc_seat_cycle_focus (PhocSeat *seat); void phoc_seat_begin_move (PhocSeat *seat, PhocView *view); void phoc_seat_begin_resize (PhocSeat *seat, PhocView *view, uint32_t edges); void phoc_seat_end_compositor_grab (PhocSeat *seat); PhocSeatView *phoc_seat_view_from_view (PhocSeat *seat, PhocView *view); void phoc_drag_icon_update_position (PhocDragIcon *icon); void phoc_drag_icon_damage_whole (PhocDragIcon *icon); void phoc_seat_set_exclusive_client (PhocSeat *seat, struct wl_client *client); bool phoc_seat_allow_input (PhocSeat *seat, struct wl_resource *resource); void phoc_seat_maybe_set_cursor (PhocSeat *self, const char *name); gboolean phoc_seat_has_touch (PhocSeat *self); gboolean phoc_seat_has_pointer (PhocSeat *self); gboolean phoc_seat_has_keyboard (PhocSeat *self); phoc-v0.13.1/src/server.c000066400000000000000000000271561422111650000151570ustar00rootroot00000000000000/* * Copyright (C) 2019 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #define G_LOG_DOMAIN "phoc-server" #include "config.h" #include "render.h" #include "utils.h" #include "seat.h" #include "server.h" #include #include static void phoc_server_initable_iface_init (GInitableIface *iface); G_DEFINE_TYPE_WITH_CODE (PhocServer, phoc_server, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, phoc_server_initable_iface_init)); typedef struct { GSource source; struct wl_display *display; } WaylandEventSource; static gboolean wayland_event_source_prepare (GSource *base, int *timeout) { WaylandEventSource *source = (WaylandEventSource *)base; *timeout = -1; wl_display_flush_clients (source->display); return FALSE; } static gboolean wayland_event_source_dispatch (GSource *base, GSourceFunc callback, void *data) { WaylandEventSource *source = (WaylandEventSource *)base; struct wl_event_loop *loop = wl_display_get_event_loop (source->display); wl_event_loop_dispatch (loop, 0); return TRUE; } static GSourceFuncs wayland_event_source_funcs = { wayland_event_source_prepare, NULL, wayland_event_source_dispatch, NULL }; static GSource * wayland_event_source_new (struct wl_display *display) { WaylandEventSource *source; struct wl_event_loop *loop = wl_display_get_event_loop (display); source = (WaylandEventSource *) g_source_new (&wayland_event_source_funcs, sizeof (WaylandEventSource)); g_source_set_name (&source->source, "[phoc] wayland source"); source->display = display; g_source_add_unix_fd (&source->source, wl_event_loop_get_fd (loop), G_IO_IN | G_IO_ERR); return &source->source; } static void phoc_wayland_init (PhocServer *self) { GSource *wayland_event_source; wayland_event_source = wayland_event_source_new (self->wl_display); self->wl_source = g_source_attach (wayland_event_source, NULL); } static void on_session_exit (GPid pid, gint status, PhocServer *self) { g_autoptr(GError) err = NULL; g_return_if_fail (PHOC_IS_SERVER (self)); g_spawn_close_pid (pid); #if GLIB_CHECK_VERSION (2, 70, 0) if (g_spawn_check_wait_status (status, &err)) { #else if (g_spawn_check_exit_status (status, &err)) { #endif self->exit_status = 0; } else { if (err->domain == G_SPAWN_EXIT_ERROR) self->exit_status = err->code; else g_warning ("Session terminated: %s (%d)", err->message, self->exit_status); } if (!(self->debug_flags & PHOC_SERVER_DEBUG_FLAG_NO_QUIT)) g_main_loop_quit (self->mainloop); } static void on_child_setup (gpointer unused) { sigset_t mask; /* phoc wants SIGUSR1 blocked due to wlroots/xwayland but we don't want to inherit that to childs */ sigemptyset(&mask); sigaddset(&mask, SIGUSR1); sigprocmask(SIG_UNBLOCK, &mask, NULL); } static gboolean phoc_startup_session_in_idle(PhocServer *self) { GPid pid; g_autoptr(GError) err = NULL; gchar *cmd[] = { "/bin/sh", "-c", self->session, NULL }; if (g_spawn_async (NULL, cmd, NULL, G_SPAWN_DO_NOT_REAP_CHILD, on_child_setup, self, &pid, &err)) { g_child_watch_add (pid, (GChildWatchFunc)on_session_exit, self); } else { g_warning ("Failed to launch session: %s", err->message); g_main_loop_quit (self->mainloop); } return FALSE; } static void phoc_startup_session (PhocServer *server) { gint id; id = g_idle_add ((GSourceFunc) phoc_startup_session_in_idle, server); g_source_set_name_by_id (id, "[phoc] phoc_startup_session"); } static void render_shield (PhocServer *self, PhocOutput *output, PhocRenderer *renderer) { struct wlr_output *wlr_output = output->wlr_output; struct wlr_box box = { 0, 0, wlr_output->width, wlr_output->height }; struct wlr_renderer *wlr_renderer = wlr_backend_get_renderer (wlr_output->backend); float color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; g_assert (PHOC_IS_RENDERER (renderer)); color[3] = 1.0 - phoc_ease_in_cubic (self->fader_t); wlr_render_rect (wlr_renderer, &box, color, wlr_output->transform_matrix); if (self->fader_t >= 1.0f) { g_debug ("Shield fade done"); g_clear_signal_handler (&self->render_shield_id, self->renderer); } } #define TICK 50 static void damage_shield (PhocServer *self, PhocOutput *output, PhocRenderer *renderer) { g_assert (PHOC_IS_RENDERER (renderer)); g_assert (PHOC_IS_SERVER (self)); phoc_output_damage_whole (output); self->fader_t += ((float)TICK) / 1000.0; if (self->fader_t > 1.0) self->fader_t = 1.0; if (self->fader_t >= 1.0f) g_clear_signal_handler (&self->damage_shield_id, self->renderer); } static void on_shell_state_changed (PhocServer *self, GParamSpec *pspec, PhocPhoshPrivate *phosh) { PhocPhoshPrivateShellState state; PhocOutput *output; g_assert (PHOC_IS_SERVER (self)); g_assert (PHOC_IS_PHOSH_PRIVATE (phosh)); state = phoc_phosh_private_get_shell_state (phosh); g_debug ("Shell state changed: %d", state); switch (state) { case PHOC_PHOSH_PRIVATE_SHELL_STATE_UP: if (self->render_shield_id) { self->damage_shield_id = g_signal_connect_object (self->renderer, "render-start", G_CALLBACK (damage_shield), self, G_CONNECT_SWAPPED); } break; case PHOC_PHOSH_PRIVATE_SHELL_STATE_UNKNOWN: default: /* TODO: prevent input without a shell attached */ self->fader_t = 0.0f; self->render_shield_id = g_signal_connect_object (self->renderer, "render-end", G_CALLBACK (render_shield), self, G_CONNECT_SWAPPED); wl_list_for_each (output, &self->desktop->outputs, link) phoc_output_damage_whole (output); } } static gboolean phoc_server_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { PhocServer *self = PHOC_SERVER (initable); struct wlr_renderer *wlr_renderer; self->wl_display = wl_display_create(); if (self->wl_display == NULL) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Could not create wayland display"); return FALSE; } self->backend = wlr_backend_autocreate(self->wl_display); if (self->backend == NULL) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Could not create backend"); return FALSE; } wlr_renderer = wlr_backend_get_renderer(self->backend); if (wlr_renderer == NULL) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Could not create renderer"); return FALSE; } self->renderer = phoc_renderer_new (wlr_renderer); self->data_device_manager = wlr_data_device_manager_create(self->wl_display); wlr_renderer_init_wl_display(wlr_renderer, self->wl_display); self->compositor = wlr_compositor_create(self->wl_display, wlr_renderer); return TRUE; } static void phoc_server_initable_iface_init (GInitableIface *iface) { iface->init = phoc_server_initable_init; } static void phoc_server_dispose (GObject *object) { PhocServer *self = PHOC_SERVER (object); if (self->backend) { wl_display_destroy_clients (self->wl_display); wlr_backend_destroy(self->backend); self->backend = NULL; } g_clear_signal_handler (&self->render_shield_id, self->renderer); g_clear_signal_handler (&self->damage_shield_id, self->renderer); g_clear_object (&self->renderer); G_OBJECT_CLASS (phoc_server_parent_class)->dispose (object); } static void phoc_server_finalize (GObject *object) { PhocServer *self = PHOC_SERVER (object); if (self->wl_source) { g_source_remove (self->wl_source); self->wl_source = 0; } g_clear_object (&self->input); g_clear_object (&self->desktop); g_clear_pointer (&self->session, g_free); if (self->inited) { g_unsetenv("WAYLAND_DISPLAY"); self->inited = FALSE; } g_clear_pointer (&self->config, phoc_config_destroy); wl_display_destroy (self->wl_display); G_OBJECT_CLASS (phoc_server_parent_class)->finalize (object); } static void phoc_server_class_init (PhocServerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = phoc_server_finalize; object_class->dispose = phoc_server_dispose; } static void phoc_server_init (PhocServer *self) { } /** * phoc_server_get_default: * * Get the server singleton. * * Returns: (transfer none): The server singleton */ PhocServer * phoc_server_get_default (void) { static PhocServer *instance; if (G_UNLIKELY (instance == NULL)) { g_autoptr (GError) err = NULL; g_debug("Creating server"); instance = g_initable_new (PHOC_TYPE_SERVER, NULL, &err, NULL); if (instance == NULL) { g_critical ("Failed to create server: %s", err->message); return NULL; } g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance); } return instance; } /** * phoc_server_setup: * * Perform wayland server intialization: parse command line and config, * create the wayland socket, setup env vars. * * Returns: %TRUE on success, %FALSE otherwise */ gboolean phoc_server_setup (PhocServer *self, const char *config_path, const char *session, GMainLoop *mainloop, PhocServerFlags flags, PhocServerDebugFlags debug_flags) { g_assert (!self->inited); self->config = phoc_config_create (config_path); if (!self->config) { /* phoc_config_create printed an error */ return FALSE; } self->debug_flags = debug_flags; self->mainloop = mainloop; self->exit_status = 1; self->desktop = phoc_desktop_new (self->config); self->input = phoc_input_new (); self->session = g_strdup (session); self->mainloop = mainloop; self->flags = flags; const char *socket = wl_display_add_socket_auto(self->wl_display); if (!socket) { g_warning("Unable to open wayland socket: %s", strerror(errno)); wlr_backend_destroy(self->backend); return FALSE; } g_print ("Running compositor on wayland display '%s'\n", socket); if (!wlr_backend_start(self->backend)) { g_warning("Failed to start backend"); wlr_backend_destroy(self->backend); wl_display_destroy(self->wl_display); return FALSE; } g_setenv("WAYLAND_DISPLAY", socket, true); #ifdef PHOC_XWAYLAND if (self->desktop->xwayland != NULL) { PhocSeat *xwayland_seat = phoc_input_get_seat(self->input, PHOC_CONFIG_DEFAULT_SEAT_NAME); wlr_xwayland_set_seat(self->desktop->xwayland, xwayland_seat->seat); } #endif if (self->flags & PHOC_SERVER_FLAG_SHELL_MODE) { g_message ("Enabling shell mode"); g_signal_connect_object (self->desktop->phosh, "notify::shell-state", G_CALLBACK (on_shell_state_changed), self, G_CONNECT_SWAPPED); on_shell_state_changed (self, NULL, self->desktop->phosh); } phoc_wayland_init (self); if (self->session) phoc_startup_session (self); self->inited = TRUE; return TRUE; } /** * phoc_server_get_exit_status: * * Return the session's exit status. This is only meaningful * if the session has ended. * * Returns: The session's exit status. */ gint phoc_server_get_session_exit_status (PhocServer *self) { return self->exit_status; } /** * phoc_server_get_renderer: * * Returns: (transfer none): The renderer */ PhocRenderer * phoc_server_get_renderer (PhocServer *self) { g_assert (PHOC_IS_SERVER (self)); return self->renderer; } phoc-v0.13.1/src/server.h000066400000000000000000000043451422111650000151570ustar00rootroot00000000000000#pragma once #include "render.h" #include #include #include #include #include #include #include "settings.h" #include "desktop.h" #include "input.h" G_BEGIN_DECLS #define PHOC_TYPE_SERVER (phoc_server_get_type()) G_DECLARE_FINAL_TYPE (PhocServer, phoc_server, PHOC, SERVER, GObject); /** * PhocServerFlags: * * PHOC_SHELL_FLAG_SHELL_MODE: Expect a shell to attach */ typedef enum _PhocServerFlags { PHOC_SERVER_FLAG_NONE = 0, PHOC_SERVER_FLAG_SHELL_MODE = 1 << 0, } PhocServerFlags; typedef enum _PhocServerDebugFlags { PHOC_SERVER_DEBUG_FLAG_NONE = 0, PHOC_SERVER_DEBUG_FLAG_AUTO_MAXIMIZE = 1 << 0, PHOC_SERVER_DEBUG_FLAG_DAMAGE_TRACKING = 1 << 1, PHOC_SERVER_DEBUG_FLAG_NO_QUIT = 1 << 2, PHOC_SERVER_DEBUG_FLAG_TOUCH_POINTS = 1 << 3, } PhocServerDebugFlags; /** * PhocServer: * * The server singleton. * * Maintains the compositors state. */ /* TODO: we keep the struct public due to heaps of direct access which will be replaced by getters and setters over time */ struct _PhocServer { GObject parent; /* Phoc resources */ PhocConfig *config; PhocDesktop *desktop; PhocInput *input; PhocServerFlags flags; PhocServerDebugFlags debug_flags; gboolean inited; /* The session */ gchar *session; gint exit_status; GMainLoop *mainloop; /* Wayland resources */ struct wl_display *wl_display; guint wl_source; /* WLR tools */ struct wlr_compositor *compositor; struct wlr_backend *backend; PhocRenderer *renderer; /* Global resources */ struct wlr_data_device_manager *data_device_manager; /* Fader */ gulong render_shield_id; gulong damage_shield_id; float fader_t; }; PhocServer *phoc_server_get_default (void); gboolean phoc_server_setup (PhocServer *server, const char *config_path, const char *exec, GMainLoop *mainloop, PhocServerFlags flags, PhocServerDebugFlags debug_flags); gint phoc_server_get_session_exit_status (PhocServer *self); PhocRenderer *phoc_server_get_renderer (PhocServer *self); G_END_DECLS phoc-v0.13.1/src/settings.c000066400000000000000000000201101422111650000154700ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-settings" #include "config.h" #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #include #include #include #include #include #include #include "settings.h" #include "utils.h" static bool parse_modeline(const char *s, drmModeModeInfo *mode) { char hsync[16]; char vsync[16]; float fclock; mode->type = DRM_MODE_TYPE_USERDEF; if (sscanf(s, "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s", &fclock, &mode->hdisplay, &mode->hsync_start, &mode->hsync_end, &mode->htotal, &mode->vdisplay, &mode->vsync_start, &mode->vsync_end, &mode->vtotal, hsync, vsync) != 11) { return false; } mode->clock = fclock * 1000; mode->vrefresh = mode->clock * 1000.0 * 1000.0 / mode->htotal / mode->vtotal; if (strcasecmp(hsync, "+hsync") == 0) { mode->flags |= DRM_MODE_FLAG_PHSYNC; } else if (strcasecmp(hsync, "-hsync") == 0) { mode->flags |= DRM_MODE_FLAG_NHSYNC; } else { return false; } if (strcasecmp(vsync, "+vsync") == 0) { mode->flags |= DRM_MODE_FLAG_PVSYNC; } else if (strcasecmp(vsync, "-vsync") == 0) { mode->flags |= DRM_MODE_FLAG_NVSYNC; } else { return false; } snprintf(mode->name, sizeof(mode->name), "%dx%d@%d", mode->hdisplay, mode->vdisplay, mode->vrefresh / 1000); return true; } static const char *output_prefix = "output:"; static int config_ini_handler(PhocConfig *config, const char *section, const char *name, const char *value) { if (strcmp(section, "core") == 0) { if (strcmp(name, "xwayland") == 0) { if (strcasecmp(value, "true") == 0) { config->xwayland = true; } else if (strcasecmp(value, "immediate") == 0) { config->xwayland = true; config->xwayland_lazy = false; } else if (strcasecmp(value, "false") == 0) { config->xwayland = false; } else { g_critical ("got unknown xwayland value: %s", value); } } else { g_critical ("got unknown core config: %s", name); } } else if (strncmp(output_prefix, section, strlen(output_prefix)) == 0) { const char *output_name = section + strlen(output_prefix); PhocOutputConfig *oc; bool found = false; wl_list_for_each(oc, &config->outputs, link) { if (strcmp(oc->name, output_name) == 0) { found = true; break; } } if (!found) { oc = g_new0 (PhocOutputConfig, 1); oc->name = strdup(output_name); oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; oc->scale = 1; oc->enable = true; wl_list_init(&oc->modes); wl_list_insert(&config->outputs, &oc->link); } if (strcmp(name, "enable") == 0) { if (strcasecmp(value, "true") == 0) { oc->enable = true; } else if (strcasecmp(value, "false") == 0) { oc->enable = false; } else { g_critical ("got invalid output enable value: %s", value); } } else if (strcmp(name, "x") == 0) { oc->x = strtol(value, NULL, 10); } else if (strcmp(name, "y") == 0) { oc->y = strtol(value, NULL, 10); } else if (strcmp(name, "scale") == 0) { oc->scale = strtof(value, NULL); g_assert (oc->scale > 0); } else if (strcmp(name, "rotate") == 0) { if (strcmp(value, "normal") == 0) { oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; } else if (strcmp(value, "90") == 0) { oc->transform = WL_OUTPUT_TRANSFORM_90; } else if (strcmp(value, "180") == 0) { oc->transform = WL_OUTPUT_TRANSFORM_180; } else if (strcmp(value, "270") == 0) { oc->transform = WL_OUTPUT_TRANSFORM_270; } else if (strcmp(value, "flipped") == 0) { oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED; } else if (strcmp(value, "flipped-90") == 0) { oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_90; } else if (strcmp(value, "flipped-180") == 0) { oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_180; } else if (strcmp(value, "flipped-270") == 0) { oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_270; } else { g_critical ("got unknown transform value: %s", value); } /* Make sure we rotate clockwise */ phoc_utils_fix_transform(&oc->transform); } else if (strcmp(name, "mode") == 0) { char *end; oc->mode.width = strtol(value, &end, 10); g_assert (*end == 'x'); ++end; oc->mode.height = strtol(end, &end, 10); if (*end) { g_assert (*end == '@'); ++end; oc->mode.refresh_rate = strtof(end, &end); g_assert (strcmp("Hz", end) == 0); } g_debug ("Configured output %s with mode %dx%d@%f", oc->name, oc->mode.width, oc->mode.height, oc->mode.refresh_rate); } else if (strcmp(name, "modeline") == 0) { PhocOutputModeConfig *mode = g_new0 (PhocOutputModeConfig, 1); if (parse_modeline(value, &mode->info)) { wl_list_insert(&oc->modes, &mode->link); } else { g_free(mode); g_critical ("Invalid modeline: %s", value); } } } else { g_critical ("Found unknown config section: %s", section); } return 1; } /** * phoc_config_create: * @config_path: (nullable): The config file location * * Parse the file at the given location into a configuration. */ PhocConfig * phoc_config_create (const char *config_path) { PhocConfig *config = g_new0 (PhocConfig, 1); g_autoptr (GKeyFile) keyfile = g_key_file_new (); g_autoptr (GError) err = NULL; g_auto (GStrv) sections = NULL; config->xwayland = true; config->xwayland_lazy = true; wl_list_init(&config->outputs); config->config_path = g_strdup (config_path); if (!config->config_path) { // get the config path from the current directory char cwd[MAXPATHLEN]; if (getcwd(cwd, sizeof(cwd)) != NULL) { char buf[MAXPATHLEN]; if (snprintf(buf, MAXPATHLEN, "%s/%s", cwd, "phoc.ini") >= MAXPATHLEN) { g_critical ("config path too long"); return NULL; } config->config_path = g_strdup (buf); } else { g_critical ("could not get cwd"); return NULL; } } if (!g_key_file_load_from_file (keyfile, config->config_path, G_KEY_FILE_NONE, &err)) { if (g_error_matches (err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { g_debug ("No config file found. Using sensible defaults."); goto out; } g_critical ("Failed to parse config %s: %s", config->config_path, err->message); return NULL; } sections = g_key_file_get_groups (keyfile, NULL); for (int i = 0; i < g_strv_length (sections); i++) { const char *section = sections[i]; g_auto (GStrv) keys = g_key_file_get_keys (keyfile, section, NULL, &err); if (!keys) { g_critical ("Failed to get keys for %s: %s", section, err->message); g_clear_error (&err); continue; } for (int j = 0; j < g_strv_length (keys); j++) { const char *key = keys[j]; g_autofree char *value = NULL; value = g_key_file_get_value (keyfile, section, key, &err); if (value == NULL) { g_critical ("Failed to key value for %s.%s: %s", section, key, err->message); g_clear_error (&err); continue; } config_ini_handler (config, section, key, value); } } out: config->keybindings = phoc_keybindings_new (); return config; } /** * phoc_config_destroy: * config: The #PhocConfig. * * Destroy the config and free its resources. */ void phoc_config_destroy (PhocConfig *config) { PhocOutputConfig *oc, *otmp = NULL; wl_list_for_each_safe(oc, otmp, &config->outputs, link) { PhocOutputModeConfig *omc, *omctmp = NULL; wl_list_for_each_safe(omc, omctmp, &oc->modes, link) { g_free (omc); } g_free (oc->name); g_free (oc); } g_object_unref (config->keybindings); g_free (config->config_path); g_free (config); } /** * phoc_config_get_output: * config: The #PhocConfig * output: The wlr output to get the configuration for * * Get configuration for the output. If the output is not configured, returns * NULL. */ PhocOutputConfig * phoc_config_get_output (PhocConfig *config, struct wlr_output *output) { char name[88]; snprintf(name, sizeof(name), "%s %s %s", output->make, output->model, output->serial); PhocOutputConfig *oc; wl_list_for_each(oc, &config->outputs, link) { if (strcmp(oc->name, output->name) == 0 || strcmp(oc->name, name) == 0) { return oc; } } return NULL; } phoc-v0.13.1/src/settings.h000066400000000000000000000020111422111650000154750ustar00rootroot00000000000000#pragma once #include "keybindings.h" #include #include #include #include G_BEGIN_DECLS #define PHOC_CONFIG_DEFAULT_SEAT_NAME "seat0" typedef struct _PhocOutputModeConfig { drmModeModeInfo info; struct wl_list link; } PhocOutputModeConfig; typedef struct _PhocOutputConfig { char *name; bool enable; enum wl_output_transform transform; int x, y; float scale; struct wl_list link; struct { int width, height; float refresh_rate; } mode; struct wl_list modes; } PhocOutputConfig; typedef struct _PhocConfig { bool xwayland; bool xwayland_lazy; PhocKeybindings *keybindings; struct wl_list outputs; char *config_path; } PhocConfig; PhocConfig *phoc_config_create (const char *config_path); void phoc_config_destroy (PhocConfig *config); PhocOutputConfig *phoc_config_get_output (PhocConfig *config, struct wlr_output *output); G_END_DECLS phoc-v0.13.1/src/switch.c000066400000000000000000000005071422111650000151410ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-switch" #include "config.h" #include #include "switch.h" void roots_switch_handle_toggle(struct roots_switch *switch_device, struct wlr_event_switch_toggle *event) { g_debug ("Switch %s, type: %d, state: %d", event->device->name, event->switch_type, event->switch_state); } phoc-v0.13.1/src/switch.h000066400000000000000000000005201422111650000151410ustar00rootroot00000000000000#pragma once #include "input.h" G_BEGIN_DECLS struct roots_switch { PhocSeat *seat; struct wlr_input_device *device; struct wl_listener device_destroy; struct wl_listener toggle; struct wl_list link; }; void roots_switch_handle_toggle(struct roots_switch *switch_device, struct wlr_event_switch_toggle *event); G_END_DECLS phoc-v0.13.1/src/tablet.c000066400000000000000000000010131422111650000151040ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-tablet" #include "config.h" #include "input-device.h" #include "tablet.h" G_DEFINE_TYPE (PhocTablet, phoc_tablet, PHOC_TYPE_INPUT_DEVICE); static void phoc_tablet_class_init (PhocTabletClass *klass) { } static void phoc_tablet_init (PhocTablet *self) { } PhocTablet * phoc_tablet_new (struct wlr_input_device *device, PhocSeat *seat) { return g_object_new (PHOC_TYPE_TABLET, "device", device, "seat", seat, NULL); } phoc-v0.13.1/src/tablet.h000066400000000000000000000007111422111650000151150ustar00rootroot00000000000000#pragma once #include "input-device.h" #include "seat.h" G_BEGIN_DECLS /** * PhocTablet: * * A tablet input device */ struct _PhocTablet { PhocInputDevice parent; struct wlr_tablet_v2_tablet *tablet_v2; }; #define PHOC_TYPE_TABLET (phoc_tablet_get_type ()) G_DECLARE_FINAL_TYPE (PhocTablet, phoc_tablet, PHOC, TABLET, PhocInputDevice); PhocTablet *phoc_tablet_new (struct wlr_input_device *device, PhocSeat *seat); G_END_DECLS phoc-v0.13.1/src/text_input.c000066400000000000000000000313561422111650000160510ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-text-input" #include "config.h" #include #include #include "seat.h" #include "server.h" #include "text_input.h" /** * PhocTextInput: * @pending_focused_surface: The surface getting seat's focus. Stored for when text-input cannot * be sent an enter event immediately after getting focus, e.g. when * there's no input method available. Cleared once text-input is entered. */ typedef struct _PhocTextInput { PhocInputMethodRelay *relay; struct wlr_text_input_v3 *input; struct wlr_surface *pending_focused_surface; struct wl_list link; struct wl_listener pending_focused_surface_destroy; struct wl_listener enable; struct wl_listener commit; struct wl_listener disable; struct wl_listener destroy; } PhocTextInput; static PhocTextInput *relay_get_focusable_text_input( PhocInputMethodRelay *relay) { PhocTextInput *text_input = NULL; wl_list_for_each(text_input, &relay->text_inputs, link) { if (text_input->pending_focused_surface) { return text_input; } } return NULL; } static PhocTextInput *relay_get_focused_text_input( PhocInputMethodRelay *relay) { PhocTextInput *text_input = NULL; wl_list_for_each(text_input, &relay->text_inputs, link) { if (text_input->input->focused_surface) { assert(text_input->pending_focused_surface == NULL); return text_input; } } return NULL; } static void handle_im_commit(struct wl_listener *listener, void *data) { PhocInputMethodRelay *relay = wl_container_of(listener, relay, input_method_commit); PhocTextInput *text_input = relay_get_focused_text_input(relay); if (!text_input) { return; } struct wlr_input_method_v2 *context = data; assert(context == relay->input_method); if (context->current.preedit.text) { wlr_text_input_v3_send_preedit_string(text_input->input, context->current.preedit.text, context->current.preedit.cursor_begin, context->current.preedit.cursor_end); } if (context->current.commit_text) { wlr_text_input_v3_send_commit_string(text_input->input, context->current.commit_text); } if (context->current.delete.before_length || context->current.delete.after_length) { wlr_text_input_v3_send_delete_surrounding_text(text_input->input, context->current.delete.before_length, context->current.delete.after_length); } wlr_text_input_v3_send_done(text_input->input); } static void text_input_clear_pending_focused_surface( PhocTextInput *text_input) { wl_list_remove(&text_input->pending_focused_surface_destroy.link); wl_list_init(&text_input->pending_focused_surface_destroy.link); text_input->pending_focused_surface = NULL; } static void text_input_set_pending_focused_surface( PhocTextInput *text_input, struct wlr_surface *surface) { text_input_clear_pending_focused_surface(text_input); g_assert(surface); text_input->pending_focused_surface = surface; wl_signal_add(&surface->events.destroy, &text_input->pending_focused_surface_destroy); } static void handle_im_destroy(struct wl_listener *listener, void *data) { PhocInputMethodRelay *relay = wl_container_of(listener, relay, input_method_destroy); struct wlr_input_method_v2 *context = data; assert(context == relay->input_method); relay->input_method = NULL; PhocTextInput *text_input = relay_get_focused_text_input(relay); if (text_input) { // keyboard focus is still there, so keep the surface at hand in case // the input method returns assert(text_input->pending_focused_surface == NULL); text_input_set_pending_focused_surface(text_input, text_input->input->focused_surface); wlr_text_input_v3_send_leave(text_input->input); } } static bool text_input_is_focused(struct wlr_text_input_v3 *text_input) { // phoc_input_method_relay_set_focus ensures // that focus sits on the single text input with focused_surface set. return text_input->focused_surface != NULL; } static void relay_send_im_done(PhocInputMethodRelay *relay, struct wlr_text_input_v3 *input) { struct wlr_input_method_v2 *input_method = relay->input_method; if (!input_method) { g_debug ("Sending IM_DONE but im is gone"); return; } if (!text_input_is_focused(input)) { // Don't let input method know about events from unfocused surfaces. return; } // TODO: only send each of those if they were modified if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT) { wlr_input_method_v2_send_surrounding_text(input_method, input->current.surrounding.text, input->current.surrounding.cursor, input->current.surrounding.anchor); } wlr_input_method_v2_send_text_change_cause(input_method, input->current.text_change_cause); if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE) { wlr_input_method_v2_send_content_type(input_method, input->current.content_type.hint, input->current.content_type.purpose); } wlr_input_method_v2_send_done(input_method); // TODO: pass intent, display popup size } static void handle_text_input_enable(struct wl_listener *listener, void *data) { PhocTextInput *text_input = wl_container_of(listener, text_input, enable); PhocInputMethodRelay *relay = text_input->relay; if (relay->input_method == NULL) { g_debug ("Enabling text input when input method is gone"); return; } // relay_send_im_done protects from receiving unfocussed done, // but activate must be prevented too. // TODO: when enter happens? if (!text_input_is_focused(text_input->input)) { return; } wlr_input_method_v2_send_activate(relay->input_method); relay_send_im_done(relay, text_input->input); } static void handle_text_input_commit(struct wl_listener *listener, void *data) { PhocTextInput *text_input = wl_container_of(listener, text_input, commit); PhocInputMethodRelay *relay = text_input->relay; if (!text_input->input->current_enabled) { g_debug ("Inactive text input tried to commit an update"); return; } g_debug ("Text input committed update"); if (relay->input_method == NULL) { g_debug ("Text input committed, but input method is gone"); return; } relay_send_im_done(relay, text_input->input); } static void relay_disable_text_input(PhocInputMethodRelay *relay, PhocTextInput *text_input) { if (relay->input_method == NULL) { g_debug ("Disabling text input, but input method is gone"); return; } // relay_send_im_done protects from receiving unfocussed done, // but deactivate must be prevented too if (!text_input_is_focused(text_input->input)) { return; } wlr_input_method_v2_send_deactivate(relay->input_method); relay_send_im_done(relay, text_input->input); } static void handle_text_input_disable(struct wl_listener *listener, void *data) { PhocTextInput *text_input = wl_container_of(listener, text_input, disable); PhocInputMethodRelay *relay = text_input->relay; relay_disable_text_input(relay, text_input); } static void handle_text_input_destroy(struct wl_listener *listener, void *data) { PhocTextInput *text_input = wl_container_of(listener, text_input, destroy); PhocInputMethodRelay *relay = text_input->relay; if (text_input->input->current_enabled) { relay_disable_text_input(relay, text_input); } text_input_clear_pending_focused_surface(text_input); wl_list_remove(&text_input->commit.link); wl_list_remove(&text_input->destroy.link); wl_list_remove(&text_input->disable.link); wl_list_remove(&text_input->enable.link); wl_list_remove(&text_input->link); text_input->input = NULL; free(text_input); } static void handle_pending_focused_surface_destroy(struct wl_listener *listener, void *data) { PhocTextInput *text_input = wl_container_of(listener, text_input, pending_focused_surface_destroy); struct wlr_surface *surface = data; assert(text_input->pending_focused_surface == surface); text_input_clear_pending_focused_surface(text_input); } static PhocTextInput * phoc_text_input_create (PhocInputMethodRelay *relay, struct wlr_text_input_v3 *text_input) { PhocTextInput *input = g_new0 (PhocTextInput, 1); g_debug ("New text input %p", input); input->input = text_input; input->relay = relay; wl_signal_add (&text_input->events.enable, &input->enable); input->enable.notify = handle_text_input_enable; wl_signal_add (&text_input->events.commit, &input->commit); input->commit.notify = handle_text_input_commit; wl_signal_add (&text_input->events.disable, &input->disable); input->disable.notify = handle_text_input_disable; wl_signal_add (&text_input->events.destroy, &input->destroy); input->destroy.notify = handle_text_input_destroy; input->pending_focused_surface_destroy.notify = handle_pending_focused_surface_destroy; wl_list_init (&input->pending_focused_surface_destroy.link); return input; } static void relay_handle_text_input (struct wl_listener *listener, void *data) { PhocInputMethodRelay *relay = wl_container_of(listener, relay, text_input_new); struct wlr_text_input_v3 *wlr_text_input = data; if (relay->seat->seat != wlr_text_input->seat) { g_warning ("Can't create text-input. Incorrect seat"); return; } PhocTextInput *text_input = phoc_text_input_create (relay, wlr_text_input); g_assert (text_input); wl_list_insert (&relay->text_inputs, &text_input->link); /* If the current focus surface of the seat is the same client make sure we send an enter event */ PhocView *focus_view = phoc_seat_get_focus (relay->seat); if (focus_view && focus_view->wlr_surface) { if (wl_resource_get_client (wlr_text_input->resource) == wl_resource_get_client (focus_view->wlr_surface->resource)) phoc_input_method_relay_set_focus (relay, focus_view->wlr_surface); } } static void relay_handle_input_method (struct wl_listener *listener, void *data) { PhocInputMethodRelay *relay = wl_container_of (listener, relay, input_method_new); struct wlr_input_method_v2 *input_method = data; if (relay->seat->seat != input_method->seat) { g_warning ("Attempted to input method for wrong seat"); return; } if (relay->input_method != NULL) { g_debug ("Attempted to connect second input method to a seat"); wlr_input_method_v2_send_unavailable (input_method); return; } g_debug ("Input method available"); relay->input_method = input_method; wl_signal_add(&relay->input_method->events.commit, &relay->input_method_commit); relay->input_method_commit.notify = handle_im_commit; wl_signal_add(&relay->input_method->events.destroy, &relay->input_method_destroy); relay->input_method_destroy.notify = handle_im_destroy; PhocTextInput *text_input = relay_get_focusable_text_input (relay); if (text_input) { wlr_text_input_v3_send_enter (text_input->input, text_input->pending_focused_surface); text_input_clear_pending_focused_surface (text_input); } } void phoc_input_method_relay_init (PhocSeat *seat, PhocInputMethodRelay *relay) { PhocServer *server = phoc_server_get_default (); g_assert (PHOC_IS_SEAT (seat)); relay->seat = seat; wl_list_init (&relay->text_inputs); relay->text_input_new.notify = relay_handle_text_input; wl_signal_add (&server->desktop->text_input->events.text_input, &relay->text_input_new); relay->input_method_new.notify = relay_handle_input_method; wl_signal_add(&server->desktop->input_method->events.input_method, &relay->input_method_new); } void phoc_input_method_relay_destroy (PhocInputMethodRelay *relay) { wl_list_remove (&relay->text_input_new.link); wl_list_remove (&relay->input_method_new.link); } /** * phoc_input_method_relay_set_focus: * @relay: The input method relay * @surface: The surface to focus * * Updates the currently focused surface. Surface must belong to the * same seat. */ void phoc_input_method_relay_set_focus (PhocInputMethodRelay *relay, struct wlr_surface *surface) { PhocTextInput *text_input; wl_list_for_each(text_input, &relay->text_inputs, link) { if (text_input->pending_focused_surface) { assert(text_input->input->focused_surface == NULL); if (surface != text_input->pending_focused_surface) { text_input_clear_pending_focused_surface(text_input); } } else if (text_input->input->focused_surface) { assert(text_input->pending_focused_surface == NULL); if (surface != text_input->input->focused_surface) { relay_disable_text_input(relay, text_input); wlr_text_input_v3_send_leave(text_input->input); } } if (surface && wl_resource_get_client(text_input->input->resource) == wl_resource_get_client(surface->resource)) { if (relay->input_method) { if (surface != text_input->input->focused_surface) { wlr_text_input_v3_send_enter(text_input->input, surface); } } else if (surface != text_input->pending_focused_surface) { text_input_set_pending_focused_surface(text_input, surface); } } } } phoc-v0.13.1/src/text_input.h000066400000000000000000000030011422111650000160400ustar00rootroot00000000000000#pragma once #include #include #include #include "seat.h" G_BEGIN_DECLS /** * PhocInputMethodRelay: * * The relay structure manages the relationship between text-input and * input_method interfaces on a given seat. * * Multiple text-input interfaces may * be bound to a relay, but at most one will be focused (reveiving events) at * a time. At most one input-method interface may be bound to the seat. The * relay manages life cycle of both sides. When both sides are present and * focused, the relay passes messages between them. * * Text input focus is a subset of keyboard focus - if the text-input is * in the focused state, wl_keyboard sent an enter as well. However, having * wl_keyboard focused doesn't mean that text-input will be focused. */ typedef struct _PhocInputMethodRelay { PhocSeat *seat; struct wl_list text_inputs; // PhocTextInput::link struct wlr_input_method_v2 *input_method; // doesn't have to be present struct wl_listener text_input_new; struct wl_listener input_method_new; struct wl_listener input_method_commit; struct wl_listener input_method_destroy; } PhocInputMethodRelay; void phoc_input_method_relay_init (PhocSeat *seat, PhocInputMethodRelay *relay); void phoc_input_method_relay_destroy (PhocInputMethodRelay *relay); void phoc_input_method_relay_set_focus (PhocInputMethodRelay *relay, struct wlr_surface *surface); G_END_DECLS phoc-v0.13.1/src/touch.c000066400000000000000000000013541422111650000147630ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-touch" #include "config.h" #define _POSIX_C_SOURCE 200112L #include #include #include #include "input-device.h" #include "touch.h" #include "seat.h" /** * PhocTouch: * * A touch input device */ struct _PhocTouch { PhocInputDevice parent; }; G_DEFINE_TYPE (PhocTouch, phoc_touch, PHOC_TYPE_INPUT_DEVICE); static void phoc_touch_class_init (PhocTouchClass *klass) { } static void phoc_touch_init (PhocTouch *self) { } PhocTouch * phoc_touch_new (struct wlr_input_device *device, PhocSeat *seat) { return g_object_new (PHOC_TYPE_TOUCH, "device", device, "seat", seat, NULL); } phoc-v0.13.1/src/touch.h000066400000000000000000000006621422111650000147710ustar00rootroot00000000000000#pragma once #include #include #include #include #include "settings.h" #include "input-device.h" G_BEGIN_DECLS #define PHOC_TYPE_TOUCH (phoc_touch_get_type ()) G_DECLARE_FINAL_TYPE (PhocTouch, phoc_touch, PHOC, TOUCH, PhocInputDevice); PhocTouch *phoc_touch_new (struct wlr_input_device *device, PhocSeat *seat); G_END_DECLS phoc-v0.13.1/src/utils.c000066400000000000000000000041201422111650000147730ustar00rootroot00000000000000/* * Copyright (C) 2020,2021 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later * * Authors: Arnaud Ferraris * Clayton Craft * Guido Günther */ #define G_LOG_DOMAIN "phoc-utils" #include #include "utils.h" void phoc_utils_fix_transform (enum wl_output_transform *transform) { /* * Starting from version 0.11.0, wlroots rotates counter-clockwise, while * it was rotating clockwise previously. * In order to maintain the same behavior, we need to modify the transform * before applying it */ switch (*transform) { case WL_OUTPUT_TRANSFORM_90: *transform = WL_OUTPUT_TRANSFORM_270; break; case WL_OUTPUT_TRANSFORM_270: *transform = WL_OUTPUT_TRANSFORM_90; break; case WL_OUTPUT_TRANSFORM_FLIPPED_90: *transform = WL_OUTPUT_TRANSFORM_FLIPPED_270; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: *transform = WL_OUTPUT_TRANSFORM_FLIPPED_90; break; default: /* Nothing to be done */ break; } } /** * phoc_utils_rotate_child_position: * * Rotate a child's position relative to a parent. The parent size is (pw, ph), * the child position is (*sx, *sy) and its size is (sw, sh). */ void phoc_utils_rotate_child_position (double *sx, double *sy, double sw, double sh, double pw, double ph, float rotation) { if (rotation == 0.0) { return; } // Coordinates relative to the center of the subsurface double cx = *sx - pw/2 + sw/2, cy = *sy - ph/2 + sh/2; // Rotated coordinates double rx = cos (rotation)*cx - sin (rotation)*cy, ry = cos (rotation)*cy + sin (rotation)*cx; *sx = rx + pw/2 - sw/2; *sy = ry + ph/2 - sh/2; } /** * phoc_ease_in_cubic: * @t: The term * * Ease in using cubic interpolation. */ double phoc_ease_in_cubic (double t) { double p = t; return p * p * p; } /** * phoc_ease_out_cubic: * @t: The term * * Ease out using cubic interpolation */ double phoc_ease_out_cubic (double t) { double p = t - 1; return p * p * p + 1; } phoc-v0.13.1/src/utils.h000066400000000000000000000020271422111650000150040ustar00rootroot00000000000000#pragma once #include #include G_BEGIN_DECLS /** * PHOC_PRIV_CONTAINER_P: * t: the name of the type in camel case * p: The pointer to the private part of an instance * * Returns an untyped pointer to the instance containing the instance * private data @p. */ #define PHOC_PRIV_CONTAINER_P(t, p) ((guint8*)p - (t##_private_offset)) /** * PHOC_PRIV_CONTAINER: * c: cast to the type @t * t: the name of the type in camel case * p: The pointer to the private part of an instance * * Returns a pointer to the instance containing the instance private * data @p. */ #define PHOC_PRIV_CONTAINER(c, t, p) (c)(PHOC_PRIV_CONTAINER_P(t,p)) void phoc_utils_fix_transform (enum wl_output_transform *transform); void phoc_utils_rotate_child_position (double *sx, double *sy, double sw, double sh, double pw, double ph, float rotation); double phoc_ease_in_cubic (double t); double phoc_ease_out_cubic (double t); G_END_DECLS phoc-v0.13.1/src/view.c000066400000000000000000001163301422111650000146140ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-view" #include "config.h" #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #include #include #include #include #include "cursor.h" #include "desktop.h" #include "input.h" #include "seat.h" #include "server.h" #include "view.h" typedef struct _PhocViewPrivate { char *title; char *app_id; GSettings *settings; } PhocViewPrivate; G_DEFINE_TYPE_WITH_PRIVATE (PhocView, phoc_view, G_TYPE_OBJECT) typedef struct _PhocSubsurface { PhocViewChild child; struct wlr_subsurface *wlr_subsurface; struct wl_listener destroy; struct wl_listener map; struct wl_listener unmap; } PhocSubsurface; gboolean view_is_floating(const PhocView *view) { return view->state == PHOC_VIEW_STATE_FLOATING && !view_is_fullscreen (view); } gboolean view_is_maximized(const PhocView *view) { return view->state == PHOC_VIEW_STATE_MAXIMIZED && !view_is_fullscreen (view); } gboolean view_is_tiled(const PhocView *view) { return view->state == PHOC_VIEW_STATE_TILED && !view_is_fullscreen (view); } gboolean view_is_fullscreen(const PhocView *view) { return view->fullscreen_output != NULL; } void view_get_box(const PhocView *view, struct wlr_box *box) { box->x = view->box.x; box->y = view->box.y; box->width = view->box.width * view->scale; box->height = view->box.height * view->scale; } void view_get_geometry (PhocView *view, struct wlr_box *geom) { if (PHOC_VIEW_GET_CLASS (view)->get_geometry) { PHOC_VIEW_GET_CLASS (view)->get_geometry (view, geom); } else { geom->x = 0; geom->y = 0; geom->width = view->box.width * view->scale; geom->height = view->box.height * view->scale; } } void view_get_deco_box(const PhocView *view, struct wlr_box *box) { view_get_box(view, box); if (!view->decorated) { return; } box->x -= view->border_width; box->y -= (view->border_width + view->titlebar_height); box->width += view->border_width * 2; box->height += (view->border_width * 2 + view->titlebar_height); } PhocViewDecoPart view_get_deco_part(PhocView *view, double sx, double sy) { if (!view->decorated) { return PHOC_VIEW_DECO_PART_NONE; } int sw = view->wlr_surface->current.width; int sh = view->wlr_surface->current.height; int bw = view->border_width; int titlebar_h = view->titlebar_height; if (sx > 0 && sx < sw && sy < 0 && sy > -view->titlebar_height) { return PHOC_VIEW_DECO_PART_TITLEBAR; } PhocViewDecoPart parts = 0; if (sy >= -(titlebar_h + bw) && sy <= sh + bw) { if (sx < 0 && sx > -bw) { parts |= PHOC_VIEW_DECO_PART_LEFT_BORDER; } else if (sx > sw && sx < sw + bw) { parts |= PHOC_VIEW_DECO_PART_RIGHT_BORDER; } } if (sx >= -bw && sx <= sw + bw) { if (sy > sh && sy <= sh + bw) { parts |= PHOC_VIEW_DECO_PART_BOTTOM_BORDER; } else if (sy >= -(titlebar_h + bw) && sy < 0) { parts |= PHOC_VIEW_DECO_PART_TOP_BORDER; } } // TODO corners return parts; } static void surface_send_enter_iterator(struct wlr_surface *surface, int x, int y, void *data) { struct wlr_output *wlr_output = data; wlr_surface_send_enter(surface, wlr_output); } static void surface_send_leave_iterator(struct wlr_surface *surface, int x, int y, void *data) { struct wlr_output *wlr_output = data; wlr_surface_send_leave(surface, wlr_output); } static void view_update_output(PhocView *view, const struct wlr_box *before) { PhocDesktop *desktop = view->desktop; if (!phoc_view_is_mapped (view)) { return; } struct wlr_box box; view_get_box(view, &box); PhocOutput *output; wl_list_for_each(output, &desktop->outputs, link) { bool intersected = before != NULL && wlr_output_layout_intersects( desktop->layout, output->wlr_output, before); bool intersects = wlr_output_layout_intersects(desktop->layout, output->wlr_output, &box); if (intersected && !intersects) { view_for_each_surface(view, surface_send_leave_iterator, output->wlr_output); if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_output_leave( view->toplevel_handle, output->wlr_output); } } if (!intersected && intersects) { view_for_each_surface(view, surface_send_enter_iterator, output->wlr_output); if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_output_enter( view->toplevel_handle, output->wlr_output); } } } } static void view_save(PhocView *view) { if (!view_is_floating (view)) return; /* backup window state */ struct wlr_box geom; view_get_geometry(view, &geom); view->saved.x = view->box.x + geom.x * view->scale; view->saved.y = view->box.y + geom.y * view->scale; view->saved.width = view->box.width; view->saved.height = view->box.height; } void view_move(PhocView *view, double x, double y) { if (view->box.x == x && view->box.y == y) { return; } view->pending_move_resize.update_x = false; view->pending_move_resize.update_y = false; view->pending_centering = false; struct wlr_box before; view_get_box(view, &before); if (PHOC_VIEW_GET_CLASS (view)->move) { PHOC_VIEW_GET_CLASS (view)->move(view, x, y); } else { view_update_position(view, x, y); } } void view_appear_activated (PhocView *view, bool activated) { if (PHOC_VIEW_GET_CLASS (view)->set_active) PHOC_VIEW_GET_CLASS (view)->set_active (view, activated); } void view_activate(PhocView *view, bool activate) { if (!view->desktop->maximize) { view_appear_activated(view, activate); } if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_set_activated(view->toplevel_handle, activate); } if (activate && view_is_fullscreen (view) && view->fullscreen_output->force_shell_reveal) { view->fullscreen_output->force_shell_reveal = false; phoc_output_damage_whole(view->fullscreen_output); } } void view_resize(PhocView *view, uint32_t width, uint32_t height) { struct wlr_box before; view_get_box(view, &before); if (PHOC_VIEW_GET_CLASS (view)->resize) { PHOC_VIEW_GET_CLASS (view)->resize(view, width, height); } } void view_move_resize(PhocView *view, double x, double y, uint32_t width, uint32_t height) { bool update_x = x != view->box.x; bool update_y = y != view->box.y; bool update_width = width != view->box.width; bool update_height = height != view->box.height; view->pending_move_resize.update_x = false; view->pending_move_resize.update_y = false; if (!update_x && !update_y) { view_resize(view, width, height); return; } if (!update_width && !update_height) { view_move (view, x, y); return; } if (PHOC_VIEW_GET_CLASS (view)->move_resize) { PHOC_VIEW_GET_CLASS (view)->move_resize(view, x, y, width, height); return; } view->pending_move_resize.update_x = update_x; view->pending_move_resize.update_y = update_y; view->pending_move_resize.x = x; view->pending_move_resize.y = y; view->pending_move_resize.width = width; view->pending_move_resize.height = height; view_resize(view, width, height); } static struct wlr_output *view_get_output(PhocView *view) { struct wlr_box view_box; view_get_box(view, &view_box); double output_x, output_y; wlr_output_layout_closest_point(view->desktop->layout, NULL, view->box.x + (double)view_box.width/2, view->box.y + (double)view_box.height/2, &output_x, &output_y); return wlr_output_layout_output_at(view->desktop->layout, output_x, output_y); } void view_arrange_maximized(PhocView *view, struct wlr_output *output) { if (view_is_fullscreen (view)) return; if (!output) output = view_get_output(view); if (!output) return; PhocOutput *phoc_output = output->data; struct wlr_box *output_box = wlr_output_layout_get_box(view->desktop->layout, output); struct wlr_box usable_area = phoc_output->usable_area; usable_area.x += output_box->x; usable_area.y += output_box->y; struct wlr_box geom; view_get_geometry (view, &geom); view_move_resize (view, (usable_area.x - geom.x) / view->scale, (usable_area.y - geom.y) / view->scale, usable_area.width / view->scale, usable_area.height / view->scale); } void view_arrange_tiled (PhocView *view, struct wlr_output *output) { if (view_is_fullscreen (view)) return; if (!output) output = view_get_output(view); if (!output) return; PhocOutput *phoc_output = output->data; struct wlr_box *output_box = wlr_output_layout_get_box (view->desktop->layout, output); struct wlr_box usable_area = phoc_output->usable_area; int x; usable_area.x += output_box->x; usable_area.y += output_box->y; switch (view->tile_direction) { case PHOC_VIEW_TILE_LEFT: x = usable_area.x; break; case PHOC_VIEW_TILE_RIGHT: x = usable_area.x + (0.5 * usable_area.width); break; default: g_error ("Invalid tiling direction %d", view->tile_direction); } struct wlr_box geom; view_get_geometry (view, &geom); view_move_resize (view, (x - geom.x) / view->scale, (usable_area.y - geom.y) / view->scale, usable_area.width / 2 / view->scale, usable_area.height / view->scale); } /* * Check if a view needs to be maximized */ static bool want_auto_maximize(PhocView *view) { if (!view->desktop->maximize) return false; if (PHOC_VIEW_GET_CLASS (view)->want_auto_maximize) return PHOC_VIEW_GET_CLASS (view)->want_auto_maximize(view); return false; } void view_maximize(PhocView *view, struct wlr_output *output) { if (view_is_maximized (view) && view_get_output(view) == output) { return; } if (view_is_fullscreen (view)) { return; } if (PHOC_VIEW_GET_CLASS (view)->set_tiled) { PHOC_VIEW_GET_CLASS (view)->set_tiled (view, false); } if (PHOC_VIEW_GET_CLASS (view)->set_maximized) { PHOC_VIEW_GET_CLASS (view)->set_maximized (view, true); } if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_set_maximized(view->toplevel_handle, true); } view_save (view); view->state = PHOC_VIEW_STATE_MAXIMIZED; view_arrange_maximized(view, output); } /* * Maximize view if in auto-maximize mode otherwise do nothing. */ void view_auto_maximize(PhocView *view) { if (want_auto_maximize (view)) view_maximize (view, NULL); } void view_restore(PhocView *view) { if (!view_is_maximized (view) && !view_is_tiled (view)) return; if (want_auto_maximize (view)) return; struct wlr_box geom; view_get_geometry(view, &geom); view->state = PHOC_VIEW_STATE_FLOATING; if (!wlr_box_empty(&view->saved)) { view_move_resize (view, view->saved.x - geom.x * view->scale, view->saved.y - geom.y * view->scale, view->saved.width, view->saved.height); } else { view_resize (view, 0, 0); view->pending_centering = true; } if (view->toplevel_handle) wlr_foreign_toplevel_handle_v1_set_maximized (view->toplevel_handle, false); if (PHOC_VIEW_GET_CLASS (view)->set_maximized) PHOC_VIEW_GET_CLASS (view)->set_maximized (view, false); if (PHOC_VIEW_GET_CLASS (view)->set_tiled) PHOC_VIEW_GET_CLASS (view)->set_tiled (view, false); } /** * phoc_view_set_fullscreen: * @view: The view * @fullscreen: Whether to fullscreen or unfulscreen * @output: The output to fullscreen the view on. * * If @fullscreen is `true`. fullscreens a view on the given output or * (if @output is %NULL) on the view's current output. Unfullscreens * the @view if @fullscreens is `false`. */ void phoc_view_set_fullscreen(PhocView *view, bool fullscreen, struct wlr_output *output) { bool was_fullscreen = view_is_fullscreen (view); if (was_fullscreen != fullscreen) { /* don't allow unfocused surfaces to make themselves fullscreen */ if (fullscreen && phoc_view_is_mapped (view)) g_return_if_fail (phoc_input_view_has_focus (phoc_server_get_default()->input, view)); if (PHOC_VIEW_GET_CLASS (view)->set_fullscreen) { PHOC_VIEW_GET_CLASS (view)->set_fullscreen(view, fullscreen); } if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_set_fullscreen(view->toplevel_handle, fullscreen); } } struct wlr_box view_geom; view_get_geometry(view, &view_geom); if (fullscreen) { if (output == NULL) { output = view_get_output(view); } PhocOutput *phoc_output = output->data; if (phoc_output == NULL) { return; } if (was_fullscreen) { view->fullscreen_output->fullscreen_view = NULL; } struct wlr_box view_box; view_get_box(view, &view_box); view_save (view); struct wlr_box *output_box = wlr_output_layout_get_box(view->desktop->layout, output); view_move_resize(view, output_box->x, output_box->y, output_box->width, output_box->height); phoc_output->fullscreen_view = view; phoc_output->force_shell_reveal = false; view->fullscreen_output = phoc_output; phoc_output_damage_whole(phoc_output); } if (was_fullscreen && !fullscreen) { PhocOutput *phoc_output = view->fullscreen_output; view->fullscreen_output->fullscreen_view = NULL; view->fullscreen_output = NULL; phoc_output_damage_whole(phoc_output); if (view->state == PHOC_VIEW_STATE_MAXIMIZED) { view_arrange_maximized (view, phoc_output->wlr_output); } else if (view->state == PHOC_VIEW_STATE_TILED) { view_arrange_tiled (view, phoc_output->wlr_output); } else if (!wlr_box_empty(&view->saved)) { view_move_resize(view, view->saved.x - view_geom.x * view->scale, view->saved.y - view_geom.y * view->scale, view->saved.width, view->saved.height); } else { view_resize (view, 0, 0); view->pending_centering = true; } view_auto_maximize(view); } } void view_close(PhocView *view) { if (PHOC_VIEW_GET_CLASS (view)->close) { PHOC_VIEW_GET_CLASS (view)->close(view); } } bool view_move_to_next_output (PhocView *view, enum wlr_direction direction) { PhocDesktop *desktop = view->desktop; struct wlr_output_layout *layout = view->desktop->layout; const struct wlr_output_layout_output *l_output; PhocOutput *phoc_output; struct wlr_output *output, *new_output; struct wlr_box usable_area; double x, y; output = view_get_output(view); if (!output) return false; /* use current view's x,y as ref_lx, ref_ly */ new_output = wlr_output_layout_adjacent_output (layout, direction, output, view->box.x, view->box.y); if (!new_output) return false; phoc_output = new_output->data; usable_area = phoc_output->usable_area; l_output = wlr_output_layout_get(desktop->layout, new_output); /* update saved position to the new output */ x = usable_area.x + l_output->x + usable_area.width / 2 - view->saved.width / 2; y = usable_area.y + l_output->y + usable_area.height / 2 - view->saved.height / 2; g_debug("moving view's saved position to %f %f", x, y); view->saved.x = x; view->saved.y = y; if (view_is_fullscreen (view)) { phoc_view_set_fullscreen (view, true, new_output); return true; } if (view_is_maximized (view)) { view_arrange_maximized (view, new_output); } else if (view_is_tiled (view)) { view_arrange_tiled (view, new_output); } else { view_center (view, new_output); } return true; } void view_tile(PhocView *view, PhocViewTileDirection direction, struct wlr_output *output) { if (view_is_fullscreen (view)) return; view_save (view); view->state = PHOC_VIEW_STATE_TILED; view->tile_direction = direction; if (PHOC_VIEW_GET_CLASS (view)->set_tiled) { PHOC_VIEW_GET_CLASS (view)->set_maximized (view, false); PHOC_VIEW_GET_CLASS (view)->set_tiled (view, true); } else if (PHOC_VIEW_GET_CLASS (view)->set_maximized) { /* fallback to the maximized flag on the toplevel so it can remove its drop shadows */ PHOC_VIEW_GET_CLASS (view)->set_maximized (view, true); } view_arrange_tiled (view, output); } bool view_center(PhocView *view, struct wlr_output *wlr_output) { PhocServer *server = phoc_server_get_default (); struct wlr_box box, geom; view_get_box(view, &box); view_get_geometry (view, &geom); if (!view_is_floating (view)) return false; PhocDesktop *desktop = view->desktop; PhocInput *input = server->input; PhocSeat *seat = phoc_input_get_last_active_seat(input); PhocCursor *cursor; if (!seat) { return false; } cursor = phoc_seat_get_cursor (seat); struct wlr_output *output = wlr_output ?: wlr_output_layout_output_at(desktop->layout, cursor->cursor->x, cursor->cursor->y); if (!output) { // empty layout return false; } const struct wlr_output_layout_output *l_output = wlr_output_layout_get(desktop->layout, output); PhocOutput *phoc_output = PHOC_OUTPUT (output->data); g_assert (PHOC_IS_OUTPUT (phoc_output)); struct wlr_box usable_area = phoc_output->usable_area; double view_x = (double)(usable_area.width - box.width) / 2 + usable_area.x + l_output->x - geom.x * view->scale; double view_y = (double)(usable_area.height - box.height) / 2 + usable_area.y + l_output->y - geom.y * view->scale; g_debug ("moving view to %f %f", view_x, view_y); view_move(view, view_x / view->scale, view_y / view->scale); if (!desktop->maximize) { // TODO: fitting floating oversized windows requires more work (!228) return true; } if (view->box.width > phoc_output->usable_area.width || view->box.height > phoc_output->usable_area.height) { view_resize (view, (view->box.width > phoc_output->usable_area.width) ? phoc_output->usable_area.width : view->box.width, (view->box.height > phoc_output->usable_area.height) ? phoc_output->usable_area.height : view->box.height); } return true; } static bool phoc_view_child_is_mapped (PhocViewChild *child) { while (child) { if (!child->mapped) { return false; } child = child->parent; } return true; } static void phoc_view_child_handle_commit (struct wl_listener *listener, void *data) { PhocViewChild *child = wl_container_of(listener, child, commit); phoc_view_child_apply_damage (child); } static void phoc_view_subsurface_create (PhocView *view, struct wlr_subsurface *wlr_subsurface); static void phoc_view_child_subsurface_create (PhocViewChild *child, struct wlr_subsurface *wlr_subsurface); static void phoc_view_child_handle_new_subsurface (struct wl_listener *listener, void *data) { PhocViewChild *child = wl_container_of(listener, child, new_subsurface); struct wlr_subsurface *wlr_subsurface = data; phoc_view_child_subsurface_create (child, wlr_subsurface); } static void phoc_view_init_subsurfaces (PhocView *view, struct wlr_surface *surface) { struct wlr_subsurface *subsurface; wl_list_for_each(subsurface, &surface->subsurfaces_below, parent_link) phoc_view_subsurface_create (view, subsurface); wl_list_for_each(subsurface, &surface->subsurfaces_above, parent_link) phoc_view_subsurface_create (view, subsurface); } static void phoc_view_child_init_subsurfaces (PhocViewChild *child, struct wlr_surface *surface) { struct wlr_subsurface *subsurface; wl_list_for_each (subsurface, &surface->subsurfaces_below, parent_link) phoc_view_child_subsurface_create (child, subsurface); wl_list_for_each (subsurface, &surface->subsurfaces_above, parent_link) phoc_view_child_subsurface_create (child, subsurface); } void phoc_view_child_init (PhocViewChild *child, const struct phoc_view_child_interface *impl, PhocView *view, struct wlr_surface *wlr_surface) { assert(impl->destroy); child->impl = impl; child->view = view; child->wlr_surface = wlr_surface; child->commit.notify = phoc_view_child_handle_commit; wl_signal_add(&wlr_surface->events.commit, &child->commit); child->new_subsurface.notify = phoc_view_child_handle_new_subsurface; wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface); wl_list_insert(&view->child_surfaces, &child->link); phoc_view_child_init_subsurfaces (child, wlr_surface); } static const struct phoc_view_child_interface subsurface_impl; static void subsurface_destroy(PhocViewChild *child) { assert(child->impl == &subsurface_impl); PhocSubsurface *subsurface = (PhocSubsurface *)child; wl_list_remove(&subsurface->destroy.link); wl_list_remove(&subsurface->map.link); wl_list_remove(&subsurface->unmap.link); free(subsurface); } static const struct phoc_view_child_interface subsurface_impl = { .destroy = subsurface_destroy, }; static void subsurface_handle_destroy(struct wl_listener *listener, void *data) { PhocSubsurface *subsurface = wl_container_of(listener, subsurface, destroy); phoc_view_child_destroy(&subsurface->child); } static void subsurface_handle_map(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocSubsurface *subsurface = wl_container_of(listener, subsurface, map); PhocView *view = subsurface->child.view; subsurface->child.mapped = true; phoc_view_child_damage_whole (&subsurface->child); phoc_input_update_cursor_focus(server->input); struct wlr_box box; view_get_box(view, &box); PhocOutput *output; wl_list_for_each(output, &view->desktop->outputs, link) { bool intersects = wlr_output_layout_intersects(view->desktop->layout, output->wlr_output, &box); if (intersects) { wlr_surface_send_enter (subsurface->wlr_subsurface->surface, output->wlr_output); } } } static void subsurface_handle_unmap(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocSubsurface *subsurface = wl_container_of(listener, subsurface, unmap); phoc_view_child_damage_whole (&subsurface->child); phoc_input_update_cursor_focus(server->input); subsurface->child.mapped = false; } static void phoc_view_subsurface_create (PhocView *view, struct wlr_subsurface *wlr_subsurface) { PhocSubsurface *subsurface = g_new0 (PhocSubsurface, 1); subsurface->wlr_subsurface = wlr_subsurface; phoc_view_child_init (&subsurface->child, &subsurface_impl, view, wlr_subsurface->surface); subsurface->destroy.notify = subsurface_handle_destroy; wl_signal_add (&wlr_subsurface->events.destroy, &subsurface->destroy); subsurface->map.notify = subsurface_handle_map; wl_signal_add (&wlr_subsurface->events.map, &subsurface->map); subsurface->unmap.notify = subsurface_handle_unmap; wl_signal_add (&wlr_subsurface->events.unmap, &subsurface->unmap); } static void phoc_view_child_subsurface_create (PhocViewChild *child, struct wlr_subsurface *wlr_subsurface) { PhocSubsurface *subsurface = g_new0 (PhocSubsurface, 1); subsurface->child.parent = child; child->children = g_slist_prepend (child->children, &subsurface->child); subsurface->wlr_subsurface = wlr_subsurface; phoc_view_child_init (&subsurface->child, &subsurface_impl, child->view, wlr_subsurface->surface); subsurface->destroy.notify = subsurface_handle_destroy; wl_signal_add (&wlr_subsurface->events.destroy, &subsurface->destroy); subsurface->map.notify = subsurface_handle_map; wl_signal_add (&wlr_subsurface->events.map, &subsurface->map); subsurface->unmap.notify = subsurface_handle_unmap; wl_signal_add (&wlr_subsurface->events.unmap, &subsurface->unmap); phoc_view_child_damage_whole (&subsurface->child); } static void phoc_view_handle_surface_new_subsurface (struct wl_listener *listener, void *data) { PhocView *view = wl_container_of (listener, view, surface_new_subsurface); struct wlr_subsurface *wlr_subsurface = data; phoc_view_subsurface_create (view, wlr_subsurface); } static gchar * munge_app_id (const gchar *app_id) { gchar *id = g_strdup (app_id); gint i; g_strcanon (id, "0123456789" "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "-", '-'); for (i = 0; id[i] != '\0'; i++) id[i] = g_ascii_tolower (id[i]); return id; } static void view_update_scale(PhocView *view) { PhocServer *server = phoc_server_get_default (); PhocViewPrivate *priv; g_assert (PHOC_IS_VIEW (view)); priv = phoc_view_get_instance_private (view); if (!PHOC_VIEW_GET_CLASS (view)->want_scaling(view)) { return; } bool scaling_enabled = false; if (priv->settings) { scaling_enabled = g_settings_get_boolean (priv->settings, "scale-to-fit"); } if (!scaling_enabled && !phoc_desktop_get_scale_to_fit (server->desktop)) { return; } struct wlr_output *output = view_get_output(view); if (!output) { return; } PhocOutput *phoc_output = output->data; float scalex = 1.0f, scaley = 1.0f, oldscale = view->scale; scalex = phoc_output->usable_area.width / (float)view->box.width; scaley = phoc_output->usable_area.height / (float)view->box.height; if (scaley < scalex) { view->scale = scaley; } else { view->scale = scalex; } if (view->scale < 0.5f) { view->scale = 0.5f; } if (view->scale > 1.0f || view_is_fullscreen (view)) { view->scale = 1.0f; } if (view->scale != oldscale) { if (view_is_maximized(view)) { view_arrange_maximized(view, NULL); } else if (view_is_tiled(view)) { view_arrange_tiled(view, NULL); } else { view_center(view, NULL); } } } void phoc_view_map (PhocView *view, struct wlr_surface *surface) { PhocServer *server = phoc_server_get_default (); assert(view->wlr_surface == NULL); view->wlr_surface = surface; struct wlr_subsurface *subsurface; wl_list_for_each(subsurface, &view->wlr_surface->subsurfaces_below, parent_link) { phoc_view_subsurface_create(view, subsurface); } wl_list_for_each(subsurface, &view->wlr_surface->subsurfaces_above, parent_link) { phoc_view_subsurface_create(view, subsurface); } phoc_view_init_subsurfaces (view, surface); view->surface_new_subsurface.notify = phoc_view_handle_surface_new_subsurface; wl_signal_add(&view->wlr_surface->events.new_subsurface, &view->surface_new_subsurface); if (view->desktop->maximize) { view_appear_activated(view, true); if (!wl_list_empty(&view->desktop->views)) { // mapping a new stack may make the old stack disappear, so damage its area PhocView *top_view = wl_container_of(view->desktop->views.next, view, link); while (top_view) { phoc_view_damage_whole (top_view); top_view = top_view->parent; } } } wl_list_insert(&view->desktop->views, &view->link); phoc_view_damage_whole (view); phoc_input_update_cursor_focus(server->input); } void view_unmap(PhocView *view) { assert(view->wlr_surface != NULL); bool was_visible = phoc_desktop_view_is_visible(view->desktop, view); wl_signal_emit(&view->events.unmap, view); phoc_view_damage_whole (view); wl_list_remove(&view->surface_new_subsurface.link); PhocViewChild *child, *tmp; wl_list_for_each_safe(child, tmp, &view->child_surfaces, link) { phoc_view_child_destroy(child); } if (view_is_fullscreen (view)) { phoc_output_damage_whole(view->fullscreen_output); view->fullscreen_output->fullscreen_view = NULL; view->fullscreen_output = NULL; } wl_list_remove(&view->link); if (was_visible && view->desktop->maximize && !wl_list_empty(&view->desktop->views)) { // damage the newly activated stack as well since it may have just become visible PhocView *top_view = wl_container_of(view->desktop->views.next, view, link); while (top_view) { phoc_view_damage_whole (top_view); top_view = top_view->parent; } } view->wlr_surface = NULL; view->box.width = view->box.height = 0; if (view->toplevel_handle) { view->toplevel_handle->data = NULL; wlr_foreign_toplevel_handle_v1_destroy(view->toplevel_handle); view->toplevel_handle = NULL; } } void view_initial_focus(PhocView *view) { PhocServer *server = phoc_server_get_default (); PhocInput *input = server->input; // TODO what seat gets focus? the one with the last input event? for (GSList *elem = phoc_input_get_seats (input); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); phoc_seat_set_focus(seat, view); } } /** * view_send_frame_done_if_not_visible: * @view: The #PhocView * * For views that aren't visible, EGL-Wayland can be stuck * in eglSwapBuffers waiting for frame done event. This function * helps it get unstuck, so further events can actually be processed * by the client. It's worth calling this function when sending * events like `configure` or `close`, as these should get processed * immediately regardless of surface visibility. */ void view_send_frame_done_if_not_visible (PhocView *view) { if (!phoc_desktop_view_is_visible (view->desktop, view) && phoc_view_is_mapped (view)) { struct timespec now; clock_gettime (CLOCK_MONOTONIC, &now); wlr_surface_send_frame_done (view->wlr_surface, &now); } } void view_setup(PhocView *view) { PhocViewPrivate *priv = phoc_view_get_instance_private (view); view_create_foreign_toplevel_handle(view); view_initial_focus(view); view_center(view, NULL); view_update_scale(view); view_update_output(view, NULL); wlr_foreign_toplevel_handle_v1_set_fullscreen(view->toplevel_handle, view_is_fullscreen (view)); wlr_foreign_toplevel_handle_v1_set_maximized(view->toplevel_handle, view_is_maximized(view)); wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, priv->title ?: ""); wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, priv->app_id ?: ""); wlr_foreign_toplevel_handle_v1_set_parent(view->toplevel_handle, view->parent ? view->parent->toplevel_handle : NULL); } /** * phoc_view_apply_damage: * @view: A view * * Add the accumulated buffer damage of all surfaces belonging to a * [class@PhocView] to the damaged screen area that needs repaint. */ void phoc_view_apply_damage (PhocView *view) { PhocOutput *output; wl_list_for_each (output, &view->desktop->outputs, link) phoc_output_damage_from_view (output, view, false); } /** * phoc_view_damage_whole: * @view: A view * * Add the damage of all surfaces belonging to a [class@PhocView] to the * damaged screen area that needs repaint. This damages the whole * @view (possibly including server side window decorations) ignoring * any buffer damage. */ void phoc_view_damage_whole (PhocView *view) { PhocOutput *output; wl_list_for_each(output, &view->desktop->outputs, link) phoc_output_damage_from_view (output, view, true); } void view_for_each_surface(PhocView *view, wlr_surface_iterator_func_t iterator, void *user_data) { if (PHOC_VIEW_GET_CLASS (view)->for_each_surface) { PHOC_VIEW_GET_CLASS (view)->for_each_surface(view, iterator, user_data); } else if (view->wlr_surface) { wlr_surface_for_each_surface(view->wlr_surface, iterator, user_data); } } void view_update_position(PhocView *view, int x, int y) { if (view->box.x == x && view->box.y == y) { return; } struct wlr_box before; view_get_box(view, &before); phoc_view_damage_whole (view); view->box.x = x; view->box.y = y; view_update_output(view, &before); phoc_view_damage_whole (view); } void view_update_size(PhocView *view, int width, int height) { if (view->box.width == width && view->box.height == height) { return; } struct wlr_box before; view_get_box(view, &before); phoc_view_damage_whole (view); view->box.width = width; view->box.height = height; if (view->pending_centering || (view_is_floating (view) && phoc_desktop_get_auto_maximize (view->desktop))) { view_center (view, NULL); view->pending_centering = false; } view_update_scale(view); view_update_output(view, &before); phoc_view_damage_whole (view); } void view_update_decorated(PhocView *view, bool decorated) { if (view->decorated == decorated) { return; } phoc_view_damage_whole (view); view->decorated = decorated; if (decorated) { view->border_width = 4; view->titlebar_height = 12; } else { view->border_width = 0; view->titlebar_height = 0; } phoc_view_damage_whole (view); } void view_set_title(PhocView *view, const char *title) { PhocViewPrivate *priv = phoc_view_get_instance_private (view); free(priv->title); priv->title = g_strdup (title); if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, title ?: ""); } } void view_set_parent(PhocView *view, PhocView *parent) { // setting a new parent may cause a cycle PhocView *node = parent; while (node) { g_return_if_fail(node != view); node = node->parent; } if (view->parent) { wl_list_remove(&view->parent_link); wl_list_init(&view->parent_link); } view->parent = parent; if (parent) { wl_list_insert(&parent->stack, &view->parent_link); } if (view->toplevel_handle) wlr_foreign_toplevel_handle_v1_set_parent(view->toplevel_handle, view->parent ? view->parent->toplevel_handle : NULL); } void view_set_app_id(PhocView *view, const char *app_id) { PhocViewPrivate *priv; g_assert (PHOC_IS_VIEW (view)); priv = phoc_view_get_instance_private (view); free(priv->app_id); priv->app_id = g_strdup (app_id); g_clear_object (&priv->settings); if (app_id) { g_autofree gchar *munged_app_id = munge_app_id (app_id); g_autofree gchar *path = g_strconcat ("/sm/puri/phoc/application/", munged_app_id, "/", NULL); priv->settings = g_settings_new_with_path ("sm.puri.phoc.application", path); } view_update_scale(view); if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, app_id ?: ""); } } static void handle_toplevel_handle_request_maximize(struct wl_listener *listener, void *data) { PhocView *view = wl_container_of(listener, view, toplevel_handle_request_maximize); struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data; if (event->maximized) { view_maximize(view, NULL); } else { view_restore(view); } } static void handle_toplevel_handle_request_activate(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocView *view = wl_container_of(listener, view, toplevel_handle_request_activate); struct wlr_foreign_toplevel_handle_v1_activated_event *event = data; for (GSList *elem = phoc_input_get_seats (server->input); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); if (event->seat == seat->seat) { phoc_seat_set_focus(seat, view); } } } static void handle_toplevel_handle_request_fullscreen(struct wl_listener *listener, void *data) { PhocView *view = wl_container_of(listener, view, toplevel_handle_request_fullscreen); struct wlr_foreign_toplevel_handle_v1_fullscreen_event *event = data; phoc_view_set_fullscreen(view, event->fullscreen, event->output); } static void handle_toplevel_handle_request_close(struct wl_listener *listener, void *data) { PhocView *view = wl_container_of(listener, view, toplevel_handle_request_close); view_close(view); } void view_create_foreign_toplevel_handle(PhocView *view) { view->toplevel_handle = wlr_foreign_toplevel_handle_v1_create( view->desktop->foreign_toplevel_manager_v1); view->toplevel_handle_request_maximize.notify = handle_toplevel_handle_request_maximize; wl_signal_add(&view->toplevel_handle->events.request_maximize, &view->toplevel_handle_request_maximize); view->toplevel_handle_request_activate.notify = handle_toplevel_handle_request_activate; wl_signal_add(&view->toplevel_handle->events.request_activate, &view->toplevel_handle_request_activate); view->toplevel_handle_request_fullscreen.notify = handle_toplevel_handle_request_fullscreen; wl_signal_add(&view->toplevel_handle->events.request_fullscreen, &view->toplevel_handle_request_fullscreen); view->toplevel_handle_request_close.notify = handle_toplevel_handle_request_close; wl_signal_add(&view->toplevel_handle->events.request_close, &view->toplevel_handle_request_close); view->toplevel_handle->data = view; } static void phoc_view_finalize (GObject *object) { PhocView *self = PHOC_VIEW (object); PhocViewPrivate *priv = phoc_view_get_instance_private (self); if (self->parent) { wl_list_remove(&self->parent_link); wl_list_init(&self->parent_link); } PhocView *child, *tmp; wl_list_for_each_safe(child, tmp, &self->stack, parent_link) { wl_list_remove(&child->parent_link); wl_list_init(&child->parent_link); child->parent = self->parent; if (child->parent) { wl_list_insert(&child->parent->stack, &child->parent_link); } } wl_signal_emit(&self->events.destroy, self); if (self->wlr_surface != NULL) { view_unmap(self); } // Can happen if fullscreened while unmapped, and hasn't been mapped if (view_is_fullscreen (self)) { self->fullscreen_output->fullscreen_view = NULL; } g_clear_pointer (&priv->title, g_free); g_clear_pointer (&priv->app_id, g_free); g_clear_object (&priv->settings); G_OBJECT_CLASS (phoc_view_parent_class)->finalize (object); } static void phoc_view_class_init (PhocViewClass *klass) { GObjectClass *object_class = (GObjectClass *)klass; object_class->finalize = phoc_view_finalize; } static void phoc_view_init (PhocView *self) { self->alpha = 1.0f; self->scale = 1.0f; self->state = PHOC_VIEW_STATE_FLOATING; wl_signal_init(&self->events.unmap); wl_signal_init(&self->events.destroy); wl_list_init(&self->child_surfaces); wl_list_init(&self->stack); } /** * phoc_view_from_wlr_surface: * @wlr_surface: The wlr_surface * * Given a #wlr_surface return the corresponding view * * Returns: The corresponding view */ PhocView * phoc_view_from_wlr_surface (struct wlr_surface *wlr_surface) { PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = server->desktop; PhocView *view; wl_list_for_each(view, &desktop->views, link) { if (view->wlr_surface == wlr_surface) { return view; } } return NULL; } /** * phoc_view_is_mapped: * @view: (nullable): The view to check * * Check if a @view is currently mapped * Returns: %TRUE if a view is currently mapped, otherwise %FALSE */ bool phoc_view_is_mapped (PhocView *view) { return view && view->wlr_surface; } /** * phoc_view_child_destroy: * @child: The view child to destroy * * Destroys a view child freeing its resources. */ void phoc_view_child_destroy (PhocViewChild *child) { if (child == NULL) return; if (phoc_view_child_is_mapped (child) && phoc_view_is_mapped (child->view)) phoc_view_child_damage_whole (child); /* Remove from parent if it's also a PhocChild */ if (child->parent != NULL) { child->parent->children = g_slist_remove (child->parent->children, child); child->parent = NULL; } /* Detach us from all children */ for (GSList *elem = child->children; elem; elem = elem->next) { PhocViewChild *subchild = elem->data; subchild->parent = NULL; /* The subchild lost its parent, so it cannot see that the parent is unmapped. Unmap it directly */ subchild->mapped = false; } g_clear_pointer (&child->children, g_slist_free); wl_list_remove(&child->link); wl_list_remove(&child->commit.link); wl_list_remove(&child->new_subsurface.link); child->impl->destroy(child); } /* * phoc_view_child_apply_damage: * @child: A view child * * This is the equivalent of [method@Phoc.View.apply_damage] but for * [struct@Phoc.ViewChild]. */ void phoc_view_child_apply_damage (PhocViewChild *child) { if (!child || !phoc_view_child_is_mapped (child) || !phoc_view_is_mapped (child->view)) return; phoc_view_apply_damage (child->view); } /** * phoc_view_child_damage_whole: * @child: A view child * * This is the equivalent of [method@Phoc.View.damage_whole] but for * [struct@Phoc.ViewChild]. */ void phoc_view_child_damage_whole (PhocViewChild *child) { if (!child || !phoc_view_child_is_mapped (child) || !phoc_view_is_mapped (child->view)) return; /* TODO: just damage the whole child instead of the whole view */ phoc_view_damage_whole (child->view); } phoc-v0.13.1/src/view.h000066400000000000000000000207641422111650000146260ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include G_BEGIN_DECLS typedef struct _PhocView PhocView; typedef struct _PhocDesktop PhocDesktop; typedef struct _PhocOutput PhocOutput; typedef enum { PHOC_XDG_SHELL_VIEW, #ifdef PHOC_XWAYLAND PHOC_XWAYLAND_VIEW, #endif } PhocViewType; typedef enum { PHOC_VIEW_TILE_LEFT, PHOC_VIEW_TILE_RIGHT, } PhocViewTileDirection; typedef enum { PHOC_VIEW_STATE_FLOATING, PHOC_VIEW_STATE_MAXIMIZED, PHOC_VIEW_STATE_TILED, } PhocViewState; typedef enum _PhocViewDecoPart { PHOC_VIEW_DECO_PART_NONE = 0, PHOC_VIEW_DECO_PART_TOP_BORDER = 1 << 0, PHOC_VIEW_DECO_PART_BOTTOM_BORDER = 1 << 1, PHOC_VIEW_DECO_PART_LEFT_BORDER = 1 << 2, PHOC_VIEW_DECO_PART_RIGHT_BORDER = 1 << 3, PHOC_VIEW_DECO_PART_TITLEBAR = 1 << 4, } PhocViewDecoPart; /** * PhocView: * @type: The type of the toplevel * * A `PhocView` represents a toplevel like an xdg-toplevel or a xwayland window. */ struct _PhocView { GObject parent_instance; PhocViewType type; PhocDesktop *desktop; struct wl_list link; // PhocDesktop::views struct wl_list parent_link; // PhocView::stack struct wlr_box box; float alpha; float scale; bool decorated; int border_width; int titlebar_height; PhocViewState state; PhocViewTileDirection tile_direction; PhocOutput *fullscreen_output; struct wlr_box saved; struct { bool update_x, update_y; double x, y; uint32_t width, height; } pending_move_resize; bool pending_centering; PhocView *parent; struct wl_list stack; // PhocView::link struct wlr_surface *wlr_surface; // set only when the surface is mapped struct wl_list child_surfaces; // PhocViewChild::link struct wlr_foreign_toplevel_handle_v1 *toplevel_handle; struct wl_listener toplevel_handle_request_maximize; struct wl_listener toplevel_handle_request_activate; struct wl_listener toplevel_handle_request_fullscreen; struct wl_listener toplevel_handle_request_close; struct wl_listener surface_new_subsurface; struct { struct wl_signal unmap; struct wl_signal destroy; } events; }; /** * PhocViewClass: * @parent_class: The object class structure needs to be the first * element in the widget class structure in order for the class mechanism * to work correctly. This allows a PhocViewClass pointer to be cast to * a GObjectClass pointer. * @move: This is called by `PhocView` to move a view to a new position. * @resize: This is called by `PhocView` to move resize a view. * @move_resize: This is called by `PhocView` to move and resize a view a the same time. * @want_auto_maximize: This is called by `PhocView` to determine if a view should * be automatically maximized * @set_active: This is called by `PhocView` to make a view appear active. * @set_fullscreen: This is called by `PhocView` to fullscreen a view * @set_maximized: This is called by `PhocView` to maximize a view * @set_tiled: This is called by `PhocView` to tile a view. * @close: This is called by `PhocView` to close a view. * @for_each_surface: This is used by `PhocView` to iterate over a surface and it's children. * @get_geometry: This is called by `PhocView` to get a views geometry. */ typedef struct _PhocViewClass { GObjectClass parent_class; void (*move) (PhocView *self, double x, double y); void (*resize) (PhocView *self, uint32_t width, uint32_t height); void (*move_resize) (PhocView *self, double x, double y, uint32_t width, uint32_t height); bool (*want_scaling) (PhocView *self); bool (*want_auto_maximize) (PhocView *self); void (*set_active) (PhocView *self, bool active); void (*set_fullscreen) (PhocView *self, bool fullscreen); void (*set_maximized) (PhocView *self, bool maximized); void (*set_tiled) (PhocView *self, bool tiled); void (*close) (PhocView *self); void (*for_each_surface) (PhocView *self, wlr_surface_iterator_func_t iterator, void *user_data); void (*get_geometry) (PhocView *self, struct wlr_box *box); } PhocViewClass; #define PHOC_TYPE_VIEW (phoc_view_get_type ()) GType phoc_view_get_type (void); G_DEFINE_AUTOPTR_CLEANUP_FUNC (PhocView, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (PhocViewClass, g_type_class_unref) static inline PhocView * PHOC_VIEW (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, phoc_view_get_type (), PhocView); } static inline PhocViewClass * PHOC_VIEW_CLASS (gpointer ptr) { return G_TYPE_CHECK_CLASS_CAST (ptr, phoc_view_get_type (), PhocViewClass); } static inline gboolean PHOC_IS_VIEW (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, phoc_view_get_type ()); } static inline gboolean PHOC_IS_VIEW_CLASS (gpointer ptr) { return G_TYPE_CHECK_CLASS_TYPE (ptr, phoc_view_get_type ()); } static inline PhocViewClass * PHOC_VIEW_GET_CLASS (gpointer ptr) { return G_TYPE_INSTANCE_GET_CLASS (ptr, phoc_view_get_type (), PhocViewClass); } typedef struct _PhocViewChild PhocViewChild; struct phoc_view_child_interface { void (*destroy)(PhocViewChild *child); }; /** * PhocViewChild: * @link: Link to PhocView::child_surfaces * @view: The [type@PhocView] this child belongs to * @parent: (nullable): The parent of this child if another child * @children: (nullable): children of this child * * A child of a [type@PhocView], e.g. a popup or subsurface */ typedef struct _PhocViewChild { const struct phoc_view_child_interface *impl; PhocView *view; PhocViewChild *parent; GSList *children; struct wlr_surface *wlr_surface; struct wl_list link; bool mapped; struct wl_listener commit; struct wl_listener new_subsurface; } PhocViewChild; void view_appear_activated(PhocView *view, bool activated); void view_activate(PhocView *view, bool activate); void phoc_view_apply_damage (PhocView *view); void phoc_view_damage_whole (PhocView *view); gboolean view_is_floating(const PhocView *view); gboolean view_is_maximized(const PhocView *view); gboolean view_is_tiled(const PhocView *view); gboolean view_is_fullscreen(const PhocView *view); void view_update_position(PhocView *view, int x, int y); void view_update_size(PhocView *view, int width, int height); void view_update_decorated(PhocView *view, bool decorated); void view_initial_focus(PhocView *view); void phoc_view_map (PhocView *view, struct wlr_surface *surface); void view_unmap(PhocView *view); void view_arrange_maximized(PhocView *view, struct wlr_output *output); void view_arrange_tiled(PhocView *view, struct wlr_output *output); void view_get_box(const PhocView *view, struct wlr_box *box); void view_get_geometry(PhocView *view, struct wlr_box *box); void view_move(PhocView *view, double x, double y); bool view_move_to_next_output (PhocView *view, enum wlr_direction direction); void view_resize(PhocView *view, uint32_t width, uint32_t height); void view_move_resize(PhocView *view, double x, double y, uint32_t width, uint32_t height); void view_auto_maximize(PhocView *view); void view_tile(PhocView *view, PhocViewTileDirection direction, struct wlr_output *output); void view_maximize(PhocView *view, struct wlr_output *output); void view_restore(PhocView *view); void phoc_view_set_fullscreen(PhocView *view, bool fullscreen, struct wlr_output *output); void view_close(PhocView *view); bool view_center(PhocView *view, struct wlr_output *output); void view_send_frame_done_if_not_visible (PhocView *view); void view_setup(PhocView *view); void view_set_title(PhocView *view, const char *title); void view_set_parent(PhocView *view, PhocView *parent); void view_set_app_id(PhocView *view, const char *app_id); void view_create_foreign_toplevel_handle(PhocView *view); void view_get_deco_box(const PhocView *view, struct wlr_box *box); void view_for_each_surface(PhocView *view, wlr_surface_iterator_func_t iterator, void *user_data); PhocView *phoc_view_from_wlr_surface (struct wlr_surface *wlr_surface); bool phoc_view_is_mapped (PhocView *view); PhocViewDecoPart view_get_deco_part(PhocView *view, double sx, double sy); void phoc_view_child_init(PhocViewChild *child, const struct phoc_view_child_interface *impl, PhocView *view, struct wlr_surface *wlr_surface); void phoc_view_child_destroy (PhocViewChild *child); void phoc_view_child_apply_damage (PhocViewChild *child); void phoc_view_child_damage_whole (PhocViewChild *child); G_END_DECLS phoc-v0.13.1/src/virtual.c000066400000000000000000000032341422111650000153260ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-virtual" #include "config.h" #define _POSIX_C_SOURCE 199309L #include #include #include "cursor.h" #include "virtual.h" #include "seat.h" #include "server.h" void phoc_handle_virtual_keyboard (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocDesktop *desktop = wl_container_of (listener, desktop, virtual_keyboard_new); struct wlr_virtual_keyboard_v1 *keyboard = data; PhocSeat*seat = phoc_input_seat_from_wlr_seat (server->input, keyboard->seat); g_return_if_fail (seat); phoc_seat_add_device (seat, &keyboard->input_device); } void phoc_handle_virtual_pointer(struct wl_listener *listener, void *data) { PhocDesktop *desktop = wl_container_of(listener, desktop, virtual_pointer_new); struct wlr_virtual_pointer_v1_new_pointer_event *event = data; struct wlr_virtual_pointer_v1 *pointer = event->new_pointer; struct wlr_input_device *device = &pointer->input_device; char *seat_name = PHOC_CONFIG_DEFAULT_SEAT_NAME; PhocServer *server = phoc_server_get_default (); PhocSeat*seat; g_return_if_fail (PHOC_IS_DESKTOP (desktop)); g_return_if_fail (PHOC_IS_SERVER (server)); seat = phoc_input_get_seat (server->input, seat_name); g_return_if_fail (seat); g_debug ("New virtual input device: %s (%d:%d) %s seat:%s", device->name, device->vendor, device->product, phoc_input_get_device_type(device->type), seat_name); phoc_seat_add_device (seat, device); if (event->suggested_output) { wlr_cursor_map_input_to_output(seat->cursor->cursor, device, event->suggested_output); } } phoc-v0.13.1/src/virtual.h000066400000000000000000000003111422111650000153240ustar00rootroot00000000000000#pragma once #include void phoc_handle_virtual_keyboard(struct wl_listener *listener, void *data); void phoc_handle_virtual_pointer(struct wl_listener *listener, void *data); phoc-v0.13.1/src/xcursor.h000066400000000000000000000003211422111650000153440ustar00rootroot00000000000000#pragma once #include G_BEGIN_DECLS #define PHOC_XCURSOR_SIZE 24 #define PHOC_XCURSOR_DEFAULT "left_ptr" #define PHOC_XCURSOR_MOVE "grabbing" #define PHOC_XCURSOR_ROTATE "grabbing" G_END_DECLS phoc-v0.13.1/src/xdg-activation-v1.c000066400000000000000000000025741422111650000171130ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * * Author: Guido Günther */ #define G_LOG_DOMAIN "phoc-xdg-activation-v1" #include "config.h" #include "view.h" #include "phosh-private.h" #include "server.h" #include "xdg-activation-v1.h" #include void phoc_xdg_activation_v1_handle_request_activate (struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); const struct wlr_xdg_activation_v1_request_activate_event *event = data; const struct wlr_xdg_activation_token_v1 *token = event->token; struct wlr_xdg_surface *xdg_surface; PhocView *view; if (!token) { g_warning ("No activation token"); return; } g_debug ("%s: %s", __func__, token->token); if (!wlr_surface_is_xdg_surface (event->surface)) { return; } xdg_surface = wlr_xdg_surface_from_wlr_surface (event->surface); g_assert (xdg_surface); view = xdg_surface->data; if (view == NULL) return; g_debug ("Activating view %p via token '%s'", view, token->token); view_activate (view, true); phoc_phosh_private_notify_startup_id (server->desktop->phosh, token->token, PHOSH_PRIVATE_STARTUP_TRACKER_PROTOCOL_XDG_ACTIVATION); } phoc-v0.13.1/src/xdg-activation-v1.h000066400000000000000000000005051422111650000171100ustar00rootroot00000000000000/* * Copyright (C) 2021 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * * Author: Guido Günther */ #pragma once G_BEGIN_DECLS void phoc_xdg_activation_v1_handle_request_activate (struct wl_listener *listener, void *data); G_END_DECLS phoc-v0.13.1/src/xdg-surface.c000066400000000000000000000204101422111650000160430ustar00rootroot00000000000000/* * Copyright (C) 2022 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later * * Author: Guido Günther */ #define G_LOG_DOMAIN "phoc-xdg-surface" #include "config.h" #include "xdg-surface.h" #include #include #include enum { PROP_0, PROP_WLR_XDG_SURFACE, PROP_LAST_PROP }; static GParamSpec *props[PROP_LAST_PROP]; G_DEFINE_TYPE (PhocXdgSurface, phoc_xdg_surface, PHOC_TYPE_VIEW) static void phoc_xdg_surface_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PhocXdgSurface *self = PHOC_XDG_SURFACE (object); switch (property_id) { case PROP_WLR_XDG_SURFACE: self->xdg_surface = g_value_get_pointer (value); self->xdg_surface->data = self; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void set_active(PhocView *view, bool active) { struct wlr_xdg_surface *xdg_surface = phoc_xdg_surface_from_view (view)->xdg_surface; if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { wlr_xdg_toplevel_set_activated(xdg_surface, active); } } static void apply_size_constraints(struct wlr_xdg_surface *xdg_surface, uint32_t width, uint32_t height, uint32_t *dest_width, uint32_t *dest_height) { *dest_width = width; *dest_height = height; struct wlr_xdg_toplevel_state *state = &xdg_surface->toplevel->current; if (width < state->min_width) { *dest_width = state->min_width; } else if (state->max_width > 0 && width > state->max_width) { *dest_width = state->max_width; } if (height < state->min_height) { *dest_height = state->min_height; } else if (state->max_height > 0 && height > state->max_height) { *dest_height = state->max_height; } } static void resize(PhocView *view, uint32_t width, uint32_t height) { struct wlr_xdg_surface *xdg_surface = phoc_xdg_surface_from_view (view)->xdg_surface; if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { return; } uint32_t constrained_width, constrained_height; apply_size_constraints(xdg_surface, width, height, &constrained_width, &constrained_height); wlr_xdg_toplevel_set_size(xdg_surface, constrained_width, constrained_height); view_send_frame_done_if_not_visible (view); } static void move_resize(PhocView *view, double x, double y, uint32_t width, uint32_t height) { PhocXdgSurface *xdg_surface = phoc_xdg_surface_from_view (view); struct wlr_xdg_surface *wlr_xdg_surface = xdg_surface->xdg_surface; if (wlr_xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { return; } bool update_x = x != view->box.x; bool update_y = y != view->box.y; uint32_t constrained_width, constrained_height; apply_size_constraints(wlr_xdg_surface, width, height, &constrained_width, &constrained_height); if (update_x) { x = x + width - constrained_width; } if (update_y) { y = y + height - constrained_height; } view->pending_move_resize.update_x = update_x; view->pending_move_resize.update_y = update_y; view->pending_move_resize.x = x; view->pending_move_resize.y = y; view->pending_move_resize.width = constrained_width; view->pending_move_resize.height = constrained_height; uint32_t serial = wlr_xdg_toplevel_set_size(wlr_xdg_surface, constrained_width, constrained_height); if (serial > 0) { xdg_surface->pending_move_resize_configure_serial = serial; } else if (xdg_surface->pending_move_resize_configure_serial == 0) { view_update_position(view, x, y); } view_send_frame_done_if_not_visible (view); } static bool want_scaling(PhocView *view) { return true; } static bool want_auto_maximize(PhocView *view) { struct wlr_xdg_surface *surface = phoc_xdg_surface_from_view (view)->xdg_surface; return surface->toplevel && !surface->toplevel->parent; } static void set_maximized(PhocView *view, bool maximized) { struct wlr_xdg_surface *xdg_surface = phoc_xdg_surface_from_view (view)->xdg_surface; if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { return; } wlr_xdg_toplevel_set_maximized(xdg_surface, maximized); } static void set_tiled (PhocView *view, bool tiled) { struct wlr_xdg_surface *xdg_surface = phoc_xdg_surface_from_view (view)->xdg_surface; if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) return; if (!tiled) { wlr_xdg_toplevel_set_tiled (xdg_surface, WLR_EDGE_NONE); return; } switch (view->tile_direction) { case PHOC_VIEW_TILE_LEFT: wlr_xdg_toplevel_set_tiled (xdg_surface, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT); break; case PHOC_VIEW_TILE_RIGHT: wlr_xdg_toplevel_set_tiled (xdg_surface, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT); break; default: g_warn_if_reached (); } } static void set_fullscreen(PhocView *view, bool fullscreen) { struct wlr_xdg_surface *xdg_surface = phoc_xdg_surface_from_view (view)->xdg_surface; if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { return; } wlr_xdg_toplevel_set_fullscreen(xdg_surface, fullscreen); } static void _close(PhocView *view) { struct wlr_xdg_surface *xdg_surface = phoc_xdg_surface_from_view (view)->xdg_surface; struct wlr_xdg_popup *popup = NULL; wl_list_for_each(popup, &xdg_surface->popups, link) { wlr_xdg_popup_destroy(popup->base); } wlr_xdg_toplevel_send_close(xdg_surface); view_send_frame_done_if_not_visible (view); } static void for_each_surface(PhocView *view, wlr_surface_iterator_func_t iterator, void *user_data) { struct wlr_xdg_surface *xdg_surface = phoc_xdg_surface_from_view (view)->xdg_surface; wlr_xdg_surface_for_each_surface(xdg_surface, iterator, user_data); } static void get_geometry(PhocView *view, struct wlr_box *geom) { phoc_xdg_surface_get_geometry (phoc_xdg_surface_from_view (view), geom); } static void phoc_xdg_surface_finalize (GObject *object) { PhocXdgSurface *self = PHOC_XDG_SURFACE(object); wl_list_remove(&self->surface_commit.link); wl_list_remove(&self->destroy.link); wl_list_remove(&self->new_popup.link); wl_list_remove(&self->map.link); wl_list_remove(&self->unmap.link); wl_list_remove(&self->request_move.link); wl_list_remove(&self->request_resize.link); wl_list_remove(&self->request_maximize.link); wl_list_remove(&self->request_fullscreen.link); wl_list_remove(&self->set_title.link); wl_list_remove(&self->set_app_id.link); wl_list_remove(&self->set_parent.link); self->xdg_surface->data = NULL; G_OBJECT_CLASS (phoc_xdg_surface_parent_class)->finalize (object); } static void phoc_xdg_surface_class_init (PhocXdgSurfaceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PhocViewClass *view_class = PHOC_VIEW_CLASS (klass); object_class->finalize = phoc_xdg_surface_finalize; object_class->set_property = phoc_xdg_surface_set_property; view_class->resize = resize; view_class->move_resize = move_resize; view_class->want_auto_maximize = want_auto_maximize; view_class->want_scaling = want_scaling; view_class->set_active = set_active; view_class->set_fullscreen = set_fullscreen; view_class->set_maximized = set_maximized; view_class->set_tiled = set_tiled; view_class->close = _close; view_class->for_each_surface = for_each_surface; view_class->get_geometry = get_geometry; /** * PhocXdgSurface:wlr-xdg-surface: * * The underlying wlroots xdg-surface */ props[PROP_WLR_XDG_SURFACE] = g_param_spec_pointer ("wlr-xdg-surface", "", "", G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } static void phoc_xdg_surface_init (PhocXdgSurface *self) { PHOC_VIEW (self)->type = PHOC_XDG_SHELL_VIEW; } PhocXdgSurface * phoc_xdg_surface_new (struct wlr_xdg_surface *wlr_xdg_surface) { return PHOC_XDG_SURFACE (g_object_new (PHOC_TYPE_XDG_SURFACE, "wlr-xdg-surface", wlr_xdg_surface, NULL)); } void phoc_xdg_surface_get_geometry (PhocXdgSurface *self, struct wlr_box *geom) { wlr_xdg_surface_get_geometry (self->xdg_surface, geom); } PhocXdgSurface * phoc_xdg_surface_from_view (PhocView *view) { g_assert (PHOC_IS_XDG_SURFACE (view)); return PHOC_XDG_SURFACE (view); } phoc-v0.13.1/src/xdg-surface.h000066400000000000000000000024461422111650000160610ustar00rootroot00000000000000/* * Copyright (C) 2022 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "view.h" #include G_BEGIN_DECLS #define PHOC_TYPE_XDG_SURFACE (phoc_xdg_surface_get_type ()) /** * PhocXdgSurface: * * An xdg surface. * * For how to setup such an object see handle_xdg_shell_surface. */ typedef struct _PhocXdgSurface { PhocView view; struct wlr_xdg_surface *xdg_surface; struct wlr_box saved_geometry; struct wl_listener destroy; struct wl_listener new_popup; struct wl_listener map; struct wl_listener unmap; struct wl_listener request_move; struct wl_listener request_resize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; struct wl_listener set_title; struct wl_listener set_app_id; struct wl_listener set_parent; struct wl_listener surface_commit; uint32_t pending_move_resize_configure_serial; struct roots_xdg_toplevel_decoration *xdg_toplevel_decoration; } PhocXdgSurface; G_DECLARE_FINAL_TYPE (PhocXdgSurface, phoc_xdg_surface, PHOC, XDG_SURFACE, PhocView) PhocXdgSurface *phoc_xdg_surface_new (struct wlr_xdg_surface *xdg_surface); void phoc_xdg_surface_get_geometry (PhocXdgSurface *self, struct wlr_box *geom); PhocXdgSurface *phoc_xdg_surface_from_view(PhocView *view); G_END_DECLS phoc-v0.13.1/src/xdg_shell.c000066400000000000000000000406731422111650000156210ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-xdg-shell" #include "config.h" #include "xdg-surface.h" #include #include #include #include #include #include #include #include "cursor.h" #include "desktop.h" #include "input.h" #include "server.h" #include "view.h" struct roots_xdg_toplevel_decoration { struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration; PhocXdgSurface *surface; struct wl_listener destroy; struct wl_listener request_mode; struct wl_listener surface_commit; }; typedef struct roots_xdg_popup { PhocViewChild child; struct wlr_xdg_popup *wlr_popup; struct wl_listener destroy; struct wl_listener map; struct wl_listener unmap; struct wl_listener new_popup; } PhocXdgPopup; static const struct phoc_view_child_interface popup_impl; static void popup_destroy(PhocViewChild *child) { assert(child->impl == &popup_impl); struct roots_xdg_popup *popup = (struct roots_xdg_popup *)child; wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->new_popup.link); wl_list_remove(&popup->map.link); wl_list_remove(&popup->unmap.link); free(popup); } static const struct phoc_view_child_interface popup_impl = { .destroy = popup_destroy, }; static void popup_handle_destroy(struct wl_listener *listener, void *data) { struct roots_xdg_popup *popup = wl_container_of(listener, popup, destroy); phoc_view_child_destroy(&popup->child); } static void popup_handle_map(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); struct roots_xdg_popup *popup = wl_container_of(listener, popup, map); phoc_view_child_damage_whole (&popup->child); phoc_input_update_cursor_focus(server->input); popup->child.mapped = true; } static void popup_handle_unmap(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); struct roots_xdg_popup *popup = wl_container_of(listener, popup, unmap); phoc_view_child_damage_whole (&popup->child); phoc_input_update_cursor_focus(server->input); popup->child.mapped = false; } static struct roots_xdg_popup *popup_create(PhocView *view, struct wlr_xdg_popup *wlr_popup); static void popup_handle_new_popup(struct wl_listener *listener, void *data) { struct roots_xdg_popup *popup = wl_container_of(listener, popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; popup_create(popup->child.view, wlr_popup); } static void popup_unconstrain(struct roots_xdg_popup *popup) { // get the output of the popup's positioner anchor point and convert it to // the toplevel parent's coordinate system and then pass it to // wlr_xdg_popup_unconstrain_from_box PhocView *view = popup->child.view; struct wlr_output_layout *layout = view->desktop->layout; struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; int anchor_lx, anchor_ly; wlr_xdg_popup_get_anchor_point(wlr_popup, &anchor_lx, &anchor_ly); int popup_lx, popup_ly; wlr_xdg_popup_get_toplevel_coords(wlr_popup, wlr_popup->geometry.x, wlr_popup->geometry.y, &popup_lx, &popup_ly); popup_lx += view->box.x; popup_ly += view->box.y; anchor_lx += popup_lx; anchor_ly += popup_ly; double dest_x = 0, dest_y = 0; wlr_output_layout_closest_point(layout, NULL, anchor_lx, anchor_ly, &dest_x, &dest_y); struct wlr_output *output = wlr_output_layout_output_at(layout, dest_x, dest_y); if (output == NULL) { return; } struct wlr_box *output_box = wlr_output_layout_get_box(view->desktop->layout, output); PhocOutput *phoc_output = output->data; struct wlr_box usable_area = phoc_output->usable_area; usable_area.x += output_box->x; usable_area.y += output_box->y; // the output box expressed in the coordinate system of the toplevel parent // of the popup struct wlr_box output_toplevel_sx_box = { .x = usable_area.x - view->box.x, .y = usable_area.y - view->box.y, .width = usable_area.width, .height = usable_area.height, }; wlr_xdg_popup_unconstrain_from_box( popup->wlr_popup, &output_toplevel_sx_box); } static struct roots_xdg_popup *popup_create(PhocView *view, struct wlr_xdg_popup *wlr_popup) { struct roots_xdg_popup *popup = calloc(1, sizeof(struct roots_xdg_popup)); if (popup == NULL) { return NULL; } popup->wlr_popup = wlr_popup; phoc_view_child_init(&popup->child, &popup_impl, view, wlr_popup->base->surface); popup->destroy.notify = popup_handle_destroy; wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); popup->map.notify = popup_handle_map; wl_signal_add(&wlr_popup->base->events.map, &popup->map); popup->unmap.notify = popup_handle_unmap; wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); popup->new_popup.notify = popup_handle_new_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); popup_unconstrain(popup); return popup; } static void get_size(PhocView *view, struct wlr_box *box) { struct wlr_xdg_surface *xdg_surface = phoc_xdg_surface_from_view (view)->xdg_surface; struct wlr_box geo_box; wlr_xdg_surface_get_geometry(xdg_surface, &geo_box); box->width = geo_box.width; box->height = geo_box.height; } static void handle_request_move(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocXdgSurface *phoc_xdg_surface = wl_container_of(listener, phoc_xdg_surface, request_move); PhocView *view = &phoc_xdg_surface->view; PhocInput *input = server->input; struct wlr_xdg_toplevel_move_event *e = data; PhocSeat *seat = phoc_input_seat_from_wlr_seat(input, e->seat->seat); // TODO verify event serial if (!seat || phoc_seat_get_cursor(seat)->mode != PHOC_CURSOR_PASSTHROUGH) { return; } phoc_seat_begin_move(seat, view); } static void handle_request_resize(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocXdgSurface *phoc_xdg_surface = wl_container_of(listener, phoc_xdg_surface, request_resize); PhocView *view = &phoc_xdg_surface->view; PhocInput *input = server->input; struct wlr_xdg_toplevel_resize_event *e = data; PhocSeat *seat = phoc_input_seat_from_wlr_seat(input, e->seat->seat); // TODO verify event serial assert(seat); if (!seat || phoc_seat_get_cursor(seat)->mode != PHOC_CURSOR_PASSTHROUGH) { return; } phoc_seat_begin_resize(seat, view, e->edges); } static void handle_request_maximize(struct wl_listener *listener, void *data) { PhocXdgSurface *phoc_xdg_surface = wl_container_of(listener, phoc_xdg_surface, request_maximize); PhocView *view = &phoc_xdg_surface->view; struct wlr_xdg_surface *surface = phoc_xdg_surface->xdg_surface; if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { return; } if (surface->toplevel->client_pending.maximized) { view_maximize(view, NULL); } else { view_restore(view); } } static void handle_request_fullscreen(struct wl_listener *listener, void *data) { PhocXdgSurface *phoc_xdg_surface = wl_container_of(listener, phoc_xdg_surface, request_fullscreen); PhocView *view = &phoc_xdg_surface->view; struct wlr_xdg_surface *surface = phoc_xdg_surface->xdg_surface; struct wlr_xdg_toplevel_set_fullscreen_event *e = data; if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { return; } phoc_view_set_fullscreen(view, e->fullscreen, e->output); } static void handle_set_title(struct wl_listener *listener, void *data) { PhocXdgSurface *phoc_xdg_surface = wl_container_of(listener, phoc_xdg_surface, set_title); view_set_title(&phoc_xdg_surface->view, phoc_xdg_surface->xdg_surface->toplevel->title); } static void handle_set_app_id(struct wl_listener *listener, void *data) { PhocXdgSurface *phoc_xdg_surface = wl_container_of(listener, phoc_xdg_surface, set_app_id); view_set_app_id(&phoc_xdg_surface->view, phoc_xdg_surface->xdg_surface->toplevel->app_id); } static void handle_set_parent(struct wl_listener *listener, void *data) { PhocXdgSurface *phoc_xdg_surface = wl_container_of(listener, phoc_xdg_surface, set_parent); if (phoc_xdg_surface->xdg_surface->toplevel->parent) { PhocXdgSurface *parent = phoc_xdg_surface->xdg_surface->toplevel->parent->data; view_set_parent(&phoc_xdg_surface->view, &parent->view); } else { view_set_parent(&phoc_xdg_surface->view, NULL); } } static void handle_surface_commit(struct wl_listener *listener, void *data) { PhocXdgSurface *roots_surface = wl_container_of(listener, roots_surface, surface_commit); PhocView *view = &roots_surface->view; struct wlr_xdg_surface *surface = roots_surface->xdg_surface; if (!surface->mapped) { return; } phoc_view_apply_damage(view); struct wlr_box size; get_size(view, &size); view_update_size(view, size.width, size.height); uint32_t pending_serial = roots_surface->pending_move_resize_configure_serial; if (pending_serial > 0 && pending_serial >= surface->configure_serial) { double x = view->box.x; double y = view->box.y; if (view->pending_move_resize.update_x) { if (view_is_floating (view)) { x = view->pending_move_resize.x + view->pending_move_resize.width - size.width; } else { x = view->pending_move_resize.x; } } if (view->pending_move_resize.update_y) { if (view_is_floating (view)) { y = view->pending_move_resize.y + view->pending_move_resize.height - size.height; } else { y = view->pending_move_resize.y; } } view_update_position(view, x, y); if (pending_serial == surface->configure_serial) { roots_surface->pending_move_resize_configure_serial = 0; } } struct wlr_box geometry; phoc_xdg_surface_get_geometry (roots_surface, &geometry); if (roots_surface->saved_geometry.x != geometry.x || roots_surface->saved_geometry.y != geometry.y) { view_update_position(view, view->box.x + (roots_surface->saved_geometry.x - geometry.x) * view->scale, view->box.y + (roots_surface->saved_geometry.y - geometry.y) * view->scale); } roots_surface->saved_geometry = geometry; } static void handle_new_popup(struct wl_listener *listener, void *data) { PhocXdgSurface *phoc_xdg_surface = wl_container_of(listener, phoc_xdg_surface, new_popup); struct wlr_xdg_popup *wlr_popup = data; popup_create(&phoc_xdg_surface->view, wlr_popup); } static void handle_map(struct wl_listener *listener, void *data) { PhocXdgSurface *phoc_xdg_surface = wl_container_of(listener, phoc_xdg_surface, map); PhocView *view = &phoc_xdg_surface->view; struct wlr_box box; get_size(view, &box); view->box.width = box.width; view->box.height = box.height; phoc_xdg_surface_get_geometry (phoc_xdg_surface, &phoc_xdg_surface->saved_geometry); phoc_view_map(view, phoc_xdg_surface->xdg_surface->surface); view_setup(view); } static void handle_unmap(struct wl_listener *listener, void *data) { PhocXdgSurface *phoc_xdg_surface = wl_container_of(listener, phoc_xdg_surface, unmap); view_unmap(&phoc_xdg_surface->view); } static void handle_destroy(struct wl_listener *listener, void *data) { PhocXdgSurface *phoc_xdg_surface = wl_container_of(listener, phoc_xdg_surface, destroy); g_object_unref (phoc_xdg_surface); } void handle_xdg_shell_surface(struct wl_listener *listener, void *data) { struct wlr_xdg_surface *surface = data; assert(surface->role != WLR_XDG_SURFACE_ROLE_NONE); if (surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { g_debug ("new xdg popup"); return; } PhocDesktop *desktop = wl_container_of(listener, desktop, xdg_shell_surface); g_debug ("new xdg toplevel: title=%s, app_id=%s", surface->toplevel->title, surface->toplevel->app_id); wlr_xdg_surface_ping(surface); PhocXdgSurface *phoc_surface = phoc_xdg_surface_new (surface); phoc_surface->view.desktop = desktop; // catch up with state accumulated before commiting if (surface->toplevel->parent) { PhocXdgSurface *parent = surface->toplevel->parent->data; view_set_parent(&phoc_surface->view, &parent->view); } if (surface->toplevel->client_pending.maximized) { view_maximize(&phoc_surface->view, NULL); } phoc_view_set_fullscreen(&phoc_surface->view, surface->toplevel->client_pending.fullscreen, surface->toplevel->client_pending.fullscreen_output); view_auto_maximize(&phoc_surface->view); view_set_title(&phoc_surface->view, surface->toplevel->title); // Check for app-id override coming from gtk-shell PhocGtkSurface *gtk_surface = phoc_gtk_shell_get_gtk_surface_from_wlr_surface (desktop->gtk_shell, surface->surface); if (gtk_surface && gtk_surface->app_id) { view_set_app_id (&phoc_surface->view, gtk_surface->app_id); } else { view_set_app_id (&phoc_surface->view, surface->toplevel->app_id); } phoc_surface->surface_commit.notify = handle_surface_commit; wl_signal_add(&surface->surface->events.commit, &phoc_surface->surface_commit); phoc_surface->destroy.notify = handle_destroy; wl_signal_add(&surface->events.destroy, &phoc_surface->destroy); phoc_surface->map.notify = handle_map; wl_signal_add(&surface->events.map, &phoc_surface->map); phoc_surface->unmap.notify = handle_unmap; wl_signal_add(&surface->events.unmap, &phoc_surface->unmap); phoc_surface->request_move.notify = handle_request_move; wl_signal_add(&surface->toplevel->events.request_move, &phoc_surface->request_move); phoc_surface->request_resize.notify = handle_request_resize; wl_signal_add(&surface->toplevel->events.request_resize, &phoc_surface->request_resize); phoc_surface->request_maximize.notify = handle_request_maximize; wl_signal_add(&surface->toplevel->events.request_maximize, &phoc_surface->request_maximize); phoc_surface->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&surface->toplevel->events.request_fullscreen, &phoc_surface->request_fullscreen); phoc_surface->set_title.notify = handle_set_title; wl_signal_add(&surface->toplevel->events.set_title, &phoc_surface->set_title); phoc_surface->set_app_id.notify = handle_set_app_id; wl_signal_add(&surface->toplevel->events.set_app_id, &phoc_surface->set_app_id); phoc_surface->set_parent.notify = handle_set_parent; wl_signal_add(&surface->toplevel->events.set_parent, &phoc_surface->set_parent); phoc_surface->new_popup.notify = handle_new_popup; wl_signal_add(&surface->events.new_popup, &phoc_surface->new_popup); } static void decoration_handle_destroy(struct wl_listener *listener, void *data) { struct roots_xdg_toplevel_decoration *decoration = wl_container_of(listener, decoration, destroy); decoration->surface->xdg_toplevel_decoration = NULL; view_update_decorated(&decoration->surface->view, false); wl_list_remove(&decoration->destroy.link); wl_list_remove(&decoration->request_mode.link); wl_list_remove(&decoration->surface_commit.link); free(decoration); } static void decoration_handle_request_mode(struct wl_listener *listener, void *data) { struct roots_xdg_toplevel_decoration *decoration = wl_container_of(listener, decoration, request_mode); enum wlr_xdg_toplevel_decoration_v1_mode mode = decoration->wlr_decoration->client_pending_mode; if (mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE) { mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; } wlr_xdg_toplevel_decoration_v1_set_mode(decoration->wlr_decoration, mode); } static void decoration_handle_surface_commit(struct wl_listener *listener, void *data) { struct roots_xdg_toplevel_decoration *decoration = wl_container_of(listener, decoration, surface_commit); bool decorated = decoration->wlr_decoration->current_mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; view_update_decorated(&decoration->surface->view, decorated); } void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data) { struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration = data; g_debug ("new xdg toplevel decoration"); PhocXdgSurface *xdg_surface = wlr_decoration->surface->data; assert(xdg_surface != NULL); struct wlr_xdg_surface *wlr_xdg_surface = xdg_surface->xdg_surface; struct roots_xdg_toplevel_decoration *decoration = calloc(1, sizeof(struct roots_xdg_toplevel_decoration)); if (decoration == NULL) { return; } decoration->wlr_decoration = wlr_decoration; decoration->surface = xdg_surface; xdg_surface->xdg_toplevel_decoration = decoration; decoration->destroy.notify = decoration_handle_destroy; wl_signal_add(&wlr_decoration->events.destroy, &decoration->destroy); decoration->request_mode.notify = decoration_handle_request_mode; wl_signal_add(&wlr_decoration->events.request_mode, &decoration->request_mode); decoration->surface_commit.notify = decoration_handle_surface_commit; wl_signal_add(&wlr_xdg_surface->surface->events.commit, &decoration->surface_commit); decoration_handle_request_mode(&decoration->request_mode, wlr_decoration); } phoc-v0.13.1/src/xwayland-surface.c000066400000000000000000000175041422111650000171220ustar00rootroot00000000000000/* * Copyright (C) 2022 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later * * Author: Guido Günther */ #define G_LOG_DOMAIN "phoc-xwayland-surface" #include "config.h" #include "server.h" #include "xwayland-surface.h" #include enum { PROP_0, PROP_WLR_XWAYLAND_SURFACE, PROP_LAST_PROP }; static GParamSpec *props[PROP_LAST_PROP]; G_DEFINE_TYPE (PhocXWaylandSurface, phoc_xwayland_surface, PHOC_TYPE_VIEW) static bool is_moveable(PhocView *view) { PhocServer *server = phoc_server_get_default (); struct wlr_xwayland_surface *xwayland_surface = phoc_xwayland_surface_from_view (view)->xwayland_surface; if (xwayland_surface->window_type == NULL) return true; for (guint i = 0; i < xwayland_surface->window_type_len; i++) if (xwayland_surface->window_type[i] != server->desktop->xwayland_atoms[NET_WM_WINDOW_TYPE_NORMAL] && xwayland_surface->window_type[i] != server->desktop->xwayland_atoms[NET_WM_WINDOW_TYPE_DIALOG]) return false; return true; } static void set_active(PhocView *view, bool active) { struct wlr_xwayland_surface *xwayland_surface = phoc_xwayland_surface_from_view(view)->xwayland_surface; wlr_xwayland_surface_activate(xwayland_surface, active); } static void move(PhocView *view, double x, double y) { struct wlr_xwayland_surface *xwayland_surface = phoc_xwayland_surface_from_view(view)->xwayland_surface; if (!is_moveable (view)) return; view_update_position(view, x, y); wlr_xwayland_surface_configure(xwayland_surface, x, y, xwayland_surface->width, xwayland_surface->height); } static void apply_size_constraints(PhocView *view, struct wlr_xwayland_surface *xwayland_surface, uint32_t width, uint32_t height, uint32_t *dest_width, uint32_t *dest_height) { *dest_width = width; *dest_height = height; if (view_is_maximized(view)) return; struct wlr_xwayland_surface_size_hints *size_hints = xwayland_surface->size_hints; if (size_hints != NULL) { if (width < (uint32_t)size_hints->min_width) { *dest_width = size_hints->min_width; } else if (size_hints->max_width > 0 && width > (uint32_t)size_hints->max_width) { *dest_width = size_hints->max_width; } if (height < (uint32_t)size_hints->min_height) { *dest_height = size_hints->min_height; } else if (size_hints->max_height > 0 && height > (uint32_t)size_hints->max_height) { *dest_height = size_hints->max_height; } } } static void resize(PhocView *view, uint32_t width, uint32_t height) { struct wlr_xwayland_surface *xwayland_surface = phoc_xwayland_surface_from_view(view)->xwayland_surface; uint32_t constrained_width, constrained_height; apply_size_constraints(view, xwayland_surface, width, height, &constrained_width, &constrained_height); wlr_xwayland_surface_configure(xwayland_surface, xwayland_surface->x, xwayland_surface->y, constrained_width, constrained_height); } static void move_resize(PhocView *view, double x, double y, uint32_t width, uint32_t height) { struct wlr_xwayland_surface *xwayland_surface = phoc_xwayland_surface_from_view(view)->xwayland_surface; if (!is_moveable (view)) { x = view->box.x; y = view->box.y; } bool update_x = x != view->box.x; bool update_y = y != view->box.y; uint32_t constrained_width, constrained_height; apply_size_constraints(view, xwayland_surface, width, height, &constrained_width, &constrained_height); if (update_x) { x = x + width - constrained_width; } if (update_y) { y = y + height - constrained_height; } view->pending_move_resize.update_x = update_x; view->pending_move_resize.update_y = update_y; view->pending_move_resize.x = x; view->pending_move_resize.y = y; view->pending_move_resize.width = constrained_width; view->pending_move_resize.height = constrained_height; wlr_xwayland_surface_configure(xwayland_surface, x, y, constrained_width, constrained_height); } static void _close(PhocView *view) { struct wlr_xwayland_surface *xwayland_surface = phoc_xwayland_surface_from_view(view)->xwayland_surface; wlr_xwayland_surface_close(xwayland_surface); } static bool want_scaling(PhocView *view) { return false; } static bool want_auto_maximize(PhocView *view) { struct wlr_xwayland_surface *surface = phoc_xwayland_surface_from_view(view)->xwayland_surface; if (surface->size_hints && (surface->size_hints->min_width > 0 && surface->size_hints->min_width == surface->size_hints->max_width) && (surface->size_hints->min_height > 0 && surface->size_hints->min_height == surface->size_hints->max_height)) return false; return is_moveable(view); } static void set_maximized(PhocView *view, bool maximized) { struct wlr_xwayland_surface *xwayland_surface = phoc_xwayland_surface_from_view(view)->xwayland_surface; wlr_xwayland_surface_set_maximized(xwayland_surface, maximized); } static void set_fullscreen(PhocView *view, bool fullscreen) { struct wlr_xwayland_surface *xwayland_surface = phoc_xwayland_surface_from_view(view)->xwayland_surface; wlr_xwayland_surface_set_fullscreen(xwayland_surface, fullscreen); } static void phoc_xwayland_surface_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PhocXWaylandSurface *self = PHOC_XWAYLAND_SURFACE (object); switch (property_id) { case PROP_WLR_XWAYLAND_SURFACE: self->xwayland_surface = g_value_get_pointer (value); self->xwayland_surface->data = self; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void phoc_xwayland_surface_finalize (GObject *object) { PhocXWaylandSurface *self = PHOC_XWAYLAND_SURFACE(object); wl_list_remove(&self->destroy.link); wl_list_remove(&self->request_configure.link); wl_list_remove(&self->request_move.link); wl_list_remove(&self->request_resize.link); wl_list_remove(&self->request_maximize.link); wl_list_remove(&self->set_title.link); wl_list_remove(&self->set_class.link); #ifdef PHOC_HAVE_WLR_SET_STARTUP_ID wl_list_remove(&self->set_startup_id.link); #endif wl_list_remove(&self->map.link); wl_list_remove(&self->unmap.link); self->xwayland_surface->data = NULL; G_OBJECT_CLASS (phoc_xwayland_surface_parent_class)->finalize (object); } static void phoc_xwayland_surface_class_init (PhocXWaylandSurfaceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PhocViewClass *view_class = PHOC_VIEW_CLASS (klass); object_class->set_property = phoc_xwayland_surface_set_property; object_class->finalize = phoc_xwayland_surface_finalize; view_class->resize = resize; view_class->move = move; view_class->move_resize = move_resize; view_class->want_scaling = want_scaling; view_class->want_auto_maximize = want_auto_maximize; view_class->set_active = set_active; view_class->set_fullscreen = set_fullscreen; view_class->set_maximized = set_maximized; view_class->close = _close; /** * PhocXWaylandSurface:wlr-xwayland-surface: * * The underlying wlroots xwayland-surface */ props[PROP_WLR_XWAYLAND_SURFACE] = g_param_spec_pointer ("wlr-xwayland-surface", "", "", G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } static void phoc_xwayland_surface_init (PhocXWaylandSurface *self) { PHOC_VIEW (self)->type = PHOC_XWAYLAND_VIEW; } PhocXWaylandSurface * phoc_xwayland_surface_new (struct wlr_xwayland_surface *surface) { return PHOC_XWAYLAND_SURFACE (g_object_new (PHOC_TYPE_XWAYLAND_SURFACE, "wlr-xwayland-surface", surface, NULL)); } PhocXWaylandSurface *phoc_xwayland_surface_from_view(PhocView *view) { g_assert (PHOC_IS_XWAYLAND_SURFACE (view)); return PHOC_XWAYLAND_SURFACE (view); } phoc-v0.13.1/src/xwayland-surface.h000066400000000000000000000022471422111650000171250ustar00rootroot00000000000000/* * Copyright (C) 2022 Purism SPC * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "view.h" #include G_BEGIN_DECLS #define PHOC_TYPE_XWAYLAND_SURFACE (phoc_xwayland_surface_get_type ()) #ifdef PHOC_XWAYLAND G_DECLARE_FINAL_TYPE (PhocXWaylandSurface, phoc_xwayland_surface, PHOC, XWAYLAND_SURFACE, PhocView) /** * PhocXWaylandSurface * * An XWayland Surface. * * For how to setup such an object see handle_xwayland_surface. */ typedef struct _PhocXWaylandSurface { PhocView view; struct wlr_xwayland_surface *xwayland_surface; struct wl_listener destroy; struct wl_listener request_configure; struct wl_listener request_move; struct wl_listener request_resize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; struct wl_listener map; struct wl_listener unmap; struct wl_listener set_title; struct wl_listener set_class; struct wl_listener set_startup_id; struct wl_listener surface_commit; } PhocXWaylandSurface; PhocXWaylandSurface *phoc_xwayland_surface_new (struct wlr_xwayland_surface *surface); PhocXWaylandSurface *phoc_xwayland_surface_from_view(PhocView *view); #endif G_END_DECLS phoc-v0.13.1/src/xwayland.c000066400000000000000000000213411422111650000154660ustar00rootroot00000000000000#define G_LOG_DOMAIN "phoc-xwayland" #include "config.h" #include #include #include #include #include #include #include #include #include "cursor.h" #include "server.h" #include "seat.h" #include "view.h" #include "xwayland.h" #include "xwayland-surface.h" static void handle_destroy(struct wl_listener *listener, void *data) { PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, destroy); g_object_unref (phoc_surface); } static void handle_request_configure(struct wl_listener *listener, void *data) { PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, request_configure); struct wlr_xwayland_surface *xwayland_surface = phoc_surface->xwayland_surface; struct wlr_xwayland_surface_configure_event *event = data; view_update_position(&phoc_surface->view, event->x, event->y); wlr_xwayland_surface_configure(xwayland_surface, event->x, event->y, event->width, event->height); } static PhocSeat *guess_seat_for_view(PhocView *view) { // the best we can do is to pick the first seat that has the surface focused // for the pointer PhocServer *server = phoc_server_get_default (); PhocInput *input = server->input; for (GSList *elem = phoc_input_get_seats (input); elem; elem = elem->next) { PhocSeat *seat = PHOC_SEAT (elem->data); g_assert (PHOC_IS_SEAT (seat)); if (seat->seat->pointer_state.focused_surface == view->wlr_surface) { return seat; } } return NULL; } static void handle_request_move(struct wl_listener *listener, void *data) { PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, request_move); PhocView *view = &phoc_surface->view; PhocSeat *seat = guess_seat_for_view(view); if (!seat || phoc_seat_get_cursor(seat)->mode != PHOC_CURSOR_PASSTHROUGH) { return; } phoc_seat_begin_move(seat, view); } static void handle_request_resize(struct wl_listener *listener, void *data) { PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, request_resize); PhocView *view = &phoc_surface->view; PhocSeat *seat = guess_seat_for_view(view); struct wlr_xwayland_resize_event *e = data; if (!seat || phoc_seat_get_cursor(seat)->mode != PHOC_CURSOR_PASSTHROUGH) { return; } phoc_seat_begin_resize(seat, view, e->edges); } static void handle_request_maximize(struct wl_listener *listener, void *data) { PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, request_maximize); PhocView *view = &phoc_surface->view; struct wlr_xwayland_surface *xwayland_surface = phoc_surface->xwayland_surface; bool maximized = xwayland_surface->maximized_vert && xwayland_surface->maximized_horz; if (maximized) { view_maximize(view, NULL); } else { view_restore(view); } } static void handle_request_fullscreen(struct wl_listener *listener, void *data) { PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, request_fullscreen); PhocView *view = &phoc_surface->view; struct wlr_xwayland_surface *xwayland_surface = phoc_surface->xwayland_surface; phoc_view_set_fullscreen(view, xwayland_surface->fullscreen, NULL); } static void handle_set_title(struct wl_listener *listener, void *data) { PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, set_title); view_set_title(&phoc_surface->view, phoc_surface->xwayland_surface->title); } static void handle_set_class(struct wl_listener *listener, void *data) { PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, set_class); view_set_app_id(&phoc_surface->view, phoc_surface->xwayland_surface->class); } #ifdef PHOC_HAVE_WLR_SET_STARTUP_ID static void handle_set_startup_id(struct wl_listener *listener, void *data) { PhocServer *server = phoc_server_get_default (); PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, set_startup_id); g_debug ("Got startup-id %s", phoc_surface->xwayland_surface->startup_id); phoc_phosh_private_notify_startup_id (server->desktop->phosh, phoc_surface->xwayland_surface->startup_id, PHOSH_PRIVATE_STARTUP_TRACKER_PROTOCOL_X11); } #endif /* PHOC_HAVE_WLR_SET_STARTUP_ID */ static void handle_surface_commit(struct wl_listener *listener, void *data) { PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, surface_commit); PhocView *view = &phoc_surface->view; struct wlr_surface *wlr_surface = view->wlr_surface; phoc_view_apply_damage(view); int width = wlr_surface->current.width; int height = wlr_surface->current.height; view_update_size(view, width, height); double x = view->box.x; double y = view->box.y; if (view->pending_move_resize.update_x) { if (view_is_floating (view)) { x = view->pending_move_resize.x + view->pending_move_resize.width - width; } else { x = view->pending_move_resize.x; } view->pending_move_resize.update_x = false; } if (view->pending_move_resize.update_y) { if (view_is_floating (view)) { y = view->pending_move_resize.y + view->pending_move_resize.height - height; } else { y = view->pending_move_resize.y; } view->pending_move_resize.update_y = false; } view_update_position(view, x, y); } static void handle_map(struct wl_listener *listener, void *data) { PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, map); struct wlr_xwayland_surface *surface = data; PhocView *view = &phoc_surface->view; view->box.x = surface->x; view->box.y = surface->y; view->box.width = surface->surface->current.width; view->box.height = surface->surface->current.height; phoc_surface->surface_commit.notify = handle_surface_commit; wl_signal_add(&surface->surface->events.commit, &phoc_surface->surface_commit); if (surface->maximized_horz && surface->maximized_vert) { view_maximize(view, NULL); } view_auto_maximize(view); phoc_view_map(view, surface->surface); if (!surface->override_redirect) { if (surface->decorations == WLR_XWAYLAND_SURFACE_DECORATIONS_ALL) { view->decorated = true; view->border_width = 4; view->titlebar_height = 12; } view_setup(view); } else { view_initial_focus(view); } } static void handle_unmap(struct wl_listener *listener, void *data) { PhocXWaylandSurface *phoc_surface = wl_container_of(listener, phoc_surface, unmap); PhocView *view = &phoc_surface->view; wl_list_remove(&phoc_surface->surface_commit.link); view_unmap(view); } void handle_xwayland_surface(struct wl_listener *listener, void *data) { PhocDesktop *desktop = wl_container_of(listener, desktop, xwayland_surface); struct wlr_xwayland_surface *surface = data; g_debug ("new xwayland surface: title=%s, class=%s, instance=%s", surface->title, surface->class, surface->instance); wlr_xwayland_surface_ping(surface); PhocXWaylandSurface *phoc_surface = phoc_xwayland_surface_new (surface); phoc_surface->view.desktop = desktop; phoc_surface->view.box.x = surface->x; phoc_surface->view.box.y = surface->y; phoc_surface->view.box.width = surface->width; phoc_surface->view.box.height = surface->height; view_set_title(&phoc_surface->view, surface->title); view_set_app_id(&phoc_surface->view, surface->class); phoc_surface->destroy.notify = handle_destroy; wl_signal_add(&surface->events.destroy, &phoc_surface->destroy); phoc_surface->request_configure.notify = handle_request_configure; wl_signal_add(&surface->events.request_configure, &phoc_surface->request_configure); phoc_surface->map.notify = handle_map; wl_signal_add(&surface->events.map, &phoc_surface->map); phoc_surface->unmap.notify = handle_unmap; wl_signal_add(&surface->events.unmap, &phoc_surface->unmap); phoc_surface->request_move.notify = handle_request_move; wl_signal_add(&surface->events.request_move, &phoc_surface->request_move); phoc_surface->request_resize.notify = handle_request_resize; wl_signal_add(&surface->events.request_resize, &phoc_surface->request_resize); phoc_surface->request_maximize.notify = handle_request_maximize; wl_signal_add(&surface->events.request_maximize, &phoc_surface->request_maximize); phoc_surface->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&surface->events.request_fullscreen, &phoc_surface->request_fullscreen); phoc_surface->set_title.notify = handle_set_title; wl_signal_add(&surface->events.set_title, &phoc_surface->set_title); phoc_surface->set_class.notify = handle_set_class; wl_signal_add(&surface->events.set_class, &phoc_surface->set_class); #ifdef PHOC_HAVE_WLR_SET_STARTUP_ID phoc_surface->set_startup_id.notify = handle_set_startup_id; wl_signal_add(&surface->events.set_startup_id, &phoc_surface->set_startup_id); #endif } phoc-v0.13.1/src/xwayland.h000066400000000000000000000002521422111650000154710ustar00rootroot00000000000000#pragma once #include #include enum xwayland_atom_name { NET_WM_WINDOW_TYPE_NORMAL, NET_WM_WINDOW_TYPE_DIALOG, XWAYLAND_ATOM_LAST }; phoc-v0.13.1/subprojects/000077500000000000000000000000001422111650000152465ustar00rootroot00000000000000phoc-v0.13.1/subprojects/gi-docgen.wrap000066400000000000000000000002361422111650000177760ustar00rootroot00000000000000[wrap-git] directory=gi-docgen url=https://gitlab.gnome.org/GNOME/gi-docgen.git push-url=ssh://git@gitlab.gnome.org:GNOME/gi-docgen.git revision=main depth=1 phoc-v0.13.1/subprojects/wlroots/000077500000000000000000000000001422111650000167575ustar00rootroot00000000000000phoc-v0.13.1/tests/000077500000000000000000000000001422111650000140455ustar00rootroot00000000000000phoc-v0.13.1/tests/meson.build000066400000000000000000000027751422111650000162220ustar00rootroot00000000000000if get_option('tests') test_env = environment() test_env.set('G_TEST_SRCDIR', meson.current_source_dir()) test_env.set('G_TEST_BUILDDIR', meson.current_build_dir()) test_env.set('G_DEBUG', 'gc-friendly,fatal-warnings') test_env.set('GSETTINGS_BACKEND', 'memory') test_env.set('GSETTINGS_SCHEMA_DIR', '@0@/data'.format(meson.build_root())) test_env.set('PYTHONDONTWRITEBYTECODE', 'yes') test_env.set('MALLOC_CHECK_', '2') test_env.set('XDG_CONFIG_HOME', meson.current_source_dir()) test_env.set('XDG_CONFIG_DIRS', meson.current_source_dir()) # Use x11 backend by default test_env.set('WLR_BACKENDS', 'x11') test_env.set('XDG_RUNTIME_DIR', meson.current_build_dir()) test_cflags = [ '-DTEST_PHOC_INI="@0@/phoc.ini"'.format(meson.current_source_dir()), ] test_link_args = [ '-fPIC', ] tests = [ 'server', 'run', 'client', 'layer-shell', 'xdg-shell', 'phosh-private' ] phoctest_sources = [ 'testlib.c', ] phoctest_lib = static_library('phoctest', phoctest_sources, client_protos_headers, c_args: test_cflags, dependencies: [libphoc_dep, wayland_client]) phoctest_dep = declare_dependency( include_directories: include_directories('.'), link_with: phoctest_lib) # Unit tests foreach test : tests t = executable('test-@0@'.format(test), ['test-@0@.c'.format(test)], c_args: test_cflags, pie: true, link_args: test_link_args, dependencies: [phoctest_dep, libphoc_dep]) test(test, t, env: test_env) endforeach endif phoc-v0.13.1/tests/phoc.ini000066400000000000000000000000711422111650000154750ustar00rootroot00000000000000[core] xwayland=false [output:HEADLESS-1] mode=1024x768 phoc-v0.13.1/tests/screenshots/000077500000000000000000000000001422111650000164055ustar00rootroot00000000000000phoc-v0.13.1/tests/screenshots/empty.png000066400000000000000000000045211422111650000202530ustar00rootroot00000000000000PNG  IHDR5؂ZbKGD IDATx1 Om  ,IENDB`phoc-v0.13.1/tests/screenshots/test-layer-shell-anchor-1.png000066400000000000000000000054151422111650000237240ustar00rootroot00000000000000PNG  IHDR5؂ZbKGD IDATxA@04QoۮYwN1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @/QIENDB`phoc-v0.13.1/tests/screenshots/test-layer-shell-anchor-2.png000066400000000000000000000071631422111650000237270ustar00rootroot00000000000000PNG  IHDR5؂ZbKGD(IDATx 01B=C]VvM5=`w;B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D9kzlY;9" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!Bn*IENDB`phoc-v0.13.1/tests/screenshots/test-layer-shell-exclusive-zone-1.png000066400000000000000000000054051422111650000254310ustar00rootroot00000000000000PNG  IHDR5؂ZbKGD IDATxױ@3K8O6sD;B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b \rh5IENDB`phoc-v0.13.1/tests/screenshots/test-layer-shell-exclusive-zone-2.png000066400000000000000000000071541422111650000254350ustar00rootroot00000000000000PNG  IHDR5؂ZbKGD!IDATx 0 A;4"Tp?hgz!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" dOҙK&>" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!BuڅIENDB`phoc-v0.13.1/tests/screenshots/test-layer-shell-set-layer-1.png000066400000000000000000000063101422111650000243520ustar00rootroot00000000000000PNG  IHDR5؂ZbKGD }IDATx 0 AB=x!"yg*0^0r+A" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!/ɱ&!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B"(WIENDB`phoc-v0.13.1/tests/screenshots/test-layer-shell-set-layer-2.png000066400000000000000000000063001422111650000243520ustar00rootroot00000000000000PNG  IHDR5؂ZbKGD uIDATxٱ Ap=Y 3\Zk>B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@<=OX7!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" D@!B" dIENDB`phoc-v0.13.1/tests/screenshots/test-phosh-private-thumbnail-simple-1.png000066400000000000000000000077071422111650000263020ustar00rootroot00000000000000PNG  IHDR5؂ZbKGD|IDATx1 & v b1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B.o.PIENDB`phoc-v0.13.1/tests/screenshots/test-xdg-shell-maximized-1.png000066400000000000000000000077071422111650000241150ustar00rootroot00000000000000PNG  IHDR5؂ZbKGD|IDATx1 & v b1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B 1b @!B.o.PIENDB`phoc-v0.13.1/tests/screenshots/test-xdg-shell-normal-1.png000066400000000000000000000054211422111650000234050ustar00rootroot00000000000000PNG  IHDR5؂ZbKGD IDATx0@=Cph */ /* Test that wayland clients can connect to the compositor */ #include "testlib.h" #include /* just run the test client, no extra tests */ static void test_phoc_client_noop (void) { phoc_test_client_run (3, NULL, NULL); } static gboolean create_surface (PhocTestClientGlobals *globals, gpointer data) { g_assert_nonnull(wl_compositor_create_surface (globals->compositor)); return TRUE; } static void test_phoc_client_surface (void) { PhocTestClientIface iface = { .client_run = create_surface }; phoc_test_client_run (3, &iface, NULL); } gint main (gint argc, gchar *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func("/phoc/client/noop", test_phoc_client_noop); g_test_add_func("/phoc/client/surface", test_phoc_client_surface); return g_test_run(); } phoc-v0.13.1/tests/test-layer-shell.c000066400000000000000000000154361422111650000174200ustar00rootroot00000000000000/* * Copyright (C) 2020 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #include "testlib.h" #include typedef struct _PhocTestLayerSurface { struct wl_surface *wl_surface; struct zwlr_layer_surface_v1 *layer_surface; PhocTestBuffer buffer; guint32 width, height; gboolean configured; } PhocTestLayerSurface; static void layer_surface_configure (void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height) { PhocTestLayerSurface *ls = data; g_debug ("Configured %p serial %d", surface, serial); g_assert_cmpint (serial, >, 0); g_assert_nonnull (surface); g_assert_cmpint (width, >, 0); g_assert_cmpint (height, >, 0); zwlr_layer_surface_v1_ack_configure (surface, serial); ls->width = width; ls->height = height; ls->configured = TRUE; } static void layer_surface_closed (void *data, struct zwlr_layer_surface_v1 *surface) { g_debug ("Destroyed %p", surface); zwlr_layer_surface_v1_destroy (surface); } static struct zwlr_layer_surface_v1_listener layer_surface_listener = { .configure = layer_surface_configure, .closed = layer_surface_closed, }; static PhocTestLayerSurface * phoc_test_layer_surface_new (PhocTestClientGlobals *globals, guint32 width, guint32 height, guint32 color, guint32 anchor, guint32 exclusive_zone) { PhocTestLayerSurface *ls = g_malloc0 (sizeof(PhocTestLayerSurface)); ls->wl_surface = wl_compositor_create_surface (globals->compositor); g_assert_nonnull (ls->wl_surface); ls->layer_surface = zwlr_layer_shell_v1_get_layer_surface (globals->layer_shell, ls->wl_surface, NULL, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "phoc-test"); g_assert_nonnull (ls->wl_surface); zwlr_layer_surface_v1_set_size (ls->layer_surface, width, height); zwlr_layer_surface_v1_set_exclusive_zone (ls->layer_surface, exclusive_zone); zwlr_layer_surface_v1_add_listener (ls->layer_surface, &layer_surface_listener, ls); zwlr_layer_surface_v1_set_anchor (ls->layer_surface, anchor); wl_surface_commit (ls->wl_surface); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); g_assert_true (ls->configured); phoc_test_client_create_shm_buffer (globals, &ls->buffer, ls->width, ls->height, WL_SHM_FORMAT_XRGB8888); for (int i = 0; i < ls->width * ls->height * 4; i+=4) *(guint32*)(ls->buffer.shm_data + i) = color; wl_surface_attach (ls->wl_surface, ls->buffer.wl_buffer, 0, 0); wl_surface_damage (ls->wl_surface, 0, 0, ls->width, ls->height); wl_surface_commit (ls->wl_surface); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); return ls; } static void phoc_test_layer_surface_free (PhocTestLayerSurface *ls) { zwlr_layer_surface_v1_destroy (ls->layer_surface); wl_surface_destroy (ls->wl_surface); phoc_test_buffer_free (&ls->buffer); g_free (ls); } #define WIDTH 100 #define HEIGHT 200 static gboolean test_client_layer_shell_anchor (PhocTestClientGlobals *globals, gpointer data) { PhocTestLayerSurface *ls_green, *ls_red; ls_green = phoc_test_layer_surface_new (globals, WIDTH, HEIGHT, 0xFF00FF00, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, 0); g_assert_nonnull (ls_green); phoc_assert_screenshot (globals, "test-layer-shell-anchor-1.png"); ls_red = phoc_test_layer_surface_new (globals, WIDTH * 2, HEIGHT * 2, 0xFFFF0000, ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, 0); g_assert_nonnull (ls_red); phoc_assert_screenshot (globals, "test-layer-shell-anchor-2.png"); phoc_test_layer_surface_free (ls_green); phoc_test_layer_surface_free (ls_red); phoc_assert_screenshot (globals, "empty.png"); return TRUE; } static void test_layer_shell_anchor (void) { PhocTestClientIface iface = { .client_run = test_client_layer_shell_anchor }; phoc_test_client_run (3, &iface, NULL); } static gboolean test_client_layer_shell_exclusive_zone (PhocTestClientGlobals *globals, gpointer data) { PhocTestLayerSurface *ls_green, *ls_red; ls_green = phoc_test_layer_surface_new (globals, 0, HEIGHT, 0xFF00FF00, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, HEIGHT); g_assert_nonnull (ls_green); phoc_assert_screenshot (globals, "test-layer-shell-exclusive-zone-1.png"); ls_red = phoc_test_layer_surface_new (globals, WIDTH * 2, HEIGHT * 2, 0xFFFF0000, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, 0); g_assert_nonnull (ls_red); phoc_assert_screenshot (globals, "test-layer-shell-exclusive-zone-2.png"); phoc_test_layer_surface_free (ls_red); phoc_test_layer_surface_free (ls_green); phoc_assert_screenshot (globals, "empty.png"); return TRUE; } static void test_layer_shell_exclusive_zone (void) { PhocTestClientIface iface = { .client_run = test_client_layer_shell_exclusive_zone }; phoc_test_client_run (3, &iface, NULL); } static gboolean test_client_layer_shell_set_layer (PhocTestClientGlobals *globals, gpointer data) { PhocTestLayerSurface *ls_green, *ls_red; ls_green = phoc_test_layer_surface_new (globals, 0, HEIGHT, 0xFF00FF00, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, 0); g_assert_nonnull (ls_green); ls_red = phoc_test_layer_surface_new (globals, WIDTH * 2, HEIGHT * 2, 0xFFFF0000, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, 0); g_assert_nonnull (ls_red); /* Red layer is above green one as this is rendered last */ phoc_assert_screenshot (globals, "test-layer-shell-set-layer-1.png"); zwlr_layer_surface_v1_set_layer (ls_red->layer_surface, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM); wl_surface_commit (ls_red->wl_surface); /* Green layer is above red one as red one moved to bottom */ phoc_assert_screenshot (globals, "test-layer-shell-set-layer-2.png"); phoc_test_layer_surface_free (ls_red); phoc_test_layer_surface_free (ls_green); phoc_assert_screenshot (globals, "empty.png"); return TRUE; } static void test_layer_shell_set_layer (void) { PhocTestClientIface iface = { .client_run = test_client_layer_shell_set_layer }; phoc_test_client_run (3, &iface, NULL); } gint main (gint argc, gchar *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func("/phoc/layer-shell/anchor", test_layer_shell_anchor); g_test_add_func("/phoc/layer-shell/exclusive_zone", test_layer_shell_exclusive_zone); g_test_add_func("/phoc/layer-shell/set_layer", test_layer_shell_set_layer); return g_test_run(); } phoc-v0.13.1/tests/test-phosh-private.c000066400000000000000000000310301422111650000177540ustar00rootroot00000000000000/* * Copyright (C) 2020 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * Author: Sebastian Krzyszkowiak */ #include "testlib.h" #include "gtk-shell-client-protocol.h" typedef struct _PhocTestXdgToplevelSurface { struct wl_surface *wl_surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; PhocTestForeignToplevel *foreign_toplevel; char* title; PhocTestBuffer buffer; guint32 width, height; gboolean configured; gboolean toplevel_configured; } PhocTestXdgToplevelSurface; typedef struct _PhocTestThumbnail { char* title; PhocTestBuffer buffer; guint32 width, height; } PhocTestThumbnail; typedef enum { GRAB_STATUS_FAILED = -1, GRAB_STATUS_UNKNOWN = 0, GRAB_STATUS_OK = 1, } PhocTestGrabStatus; typedef struct _PhocTestKeyboardEvent { char *title; struct phosh_private_keyboard_event *kbevent; PhocTestGrabStatus grab_status; } PhocTestKeyboardEvent; static void xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { PhocTestXdgToplevelSurface *xs = data; g_debug ("Configured %p serial %d", xdg_surface, serial); xdg_surface_ack_configure(xs->xdg_surface, serial); xs->configured = TRUE; } static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; #define WIDTH 100 #define HEIGHT 200 static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) { PhocTestXdgToplevelSurface *xs = data; g_debug ("Configured %p, size: %dx%d", xdg_toplevel, width, height); g_assert_nonnull (xdg_toplevel); xs->width = width ?: WIDTH; xs->height = height ?: HEIGHT; xs->toplevel_configured = TRUE; } static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_surface) { /* TBD */ } static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; static void phoc_test_xdg_surface_free (PhocTestXdgToplevelSurface *xs) { xdg_toplevel_destroy (xs->xdg_toplevel); xdg_surface_destroy (xs->xdg_surface); wl_surface_destroy (xs->wl_surface); phoc_test_buffer_free (&xs->buffer); g_free (xs); } static PhocTestXdgToplevelSurface * phoc_test_xdg_surface_new (PhocTestClientGlobals *globals, guint32 width, guint32 height, char* title, guint32 color) { PhocTestXdgToplevelSurface *xs = g_malloc0 (sizeof(PhocTestXdgToplevelSurface)); xs->wl_surface = wl_compositor_create_surface (globals->compositor); g_assert_nonnull (xs->wl_surface); xs->xdg_surface = xdg_wm_base_get_xdg_surface (globals->xdg_shell, xs->wl_surface); g_assert_nonnull (xs->wl_surface); xdg_surface_add_listener (xs->xdg_surface, &xdg_surface_listener, xs); xs->xdg_toplevel = xdg_surface_get_toplevel (xs->xdg_surface); g_assert_nonnull (xs->xdg_toplevel); xdg_toplevel_add_listener (xs->xdg_toplevel, &xdg_toplevel_listener, xs); xdg_toplevel_set_min_size (xs->xdg_toplevel, WIDTH, HEIGHT); xs->title = title; xdg_toplevel_set_title (xs->xdg_toplevel, xs->title); wl_surface_commit (xs->wl_surface); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); g_assert_true (xs->configured); g_assert_true (xs->toplevel_configured); phoc_test_client_create_shm_buffer (globals, &xs->buffer, xs->width, xs->height, WL_SHM_FORMAT_XRGB8888); for (int i = 0; i < xs->width * xs->height * 4; i+=4) *(guint32*)(xs->buffer.shm_data + i) = color; wl_surface_attach (xs->wl_surface, xs->buffer.wl_buffer, 0, 0); wl_surface_damage (xs->wl_surface, 0, 0, xs->width, xs->height); wl_surface_commit (xs->wl_surface); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); xs->foreign_toplevel = phoc_test_client_get_foreign_toplevel_handle (globals, title); g_assert_true (xs->foreign_toplevel); return xs; } static PhocTestScreencopyFrame * phoc_test_get_thumbnail (PhocTestClientGlobals *globals, guint32 max_width, guint32 max_height, PhocTestForeignToplevel *toplevel) { PhocTestScreencopyFrame *thumbnail = g_malloc0 (sizeof(PhocTestScreencopyFrame)); struct zwlr_screencopy_frame_v1 *handle = phosh_private_get_thumbnail (globals->phosh, toplevel->handle, max_width, max_height); phoc_test_client_capture_frame (globals, thumbnail, handle); return thumbnail; } static void phoc_test_thumbnail_free (PhocTestScreencopyFrame *frame) { phoc_test_buffer_free (&frame->buffer); zwlr_screencopy_frame_v1_destroy (frame->handle); g_free (frame); } static gboolean test_client_phosh_private_thumbnail_simple (PhocTestClientGlobals *globals, gpointer data) { PhocTestXdgToplevelSurface *toplevel_green; PhocTestScreencopyFrame *green_thumbnail; toplevel_green = phoc_test_xdg_surface_new (globals, WIDTH, HEIGHT, "green", 0xFF00FF00); g_assert_nonnull (toplevel_green); phoc_assert_screenshot (globals, "test-phosh-private-thumbnail-simple-1.png"); green_thumbnail = phoc_test_get_thumbnail (globals, toplevel_green->width, toplevel_green->height, toplevel_green->foreign_toplevel); phoc_assert_buffer_equal (&toplevel_green->buffer, &green_thumbnail->buffer); phoc_test_thumbnail_free (green_thumbnail); phoc_test_xdg_surface_free (toplevel_green); phoc_assert_screenshot (globals, "empty.png"); return TRUE; } static void test_phosh_private_thumbnail_simple (void) { PhocTestClientIface iface = { .client_run = test_client_phosh_private_thumbnail_simple, }; if (g_getenv ("PHOC_TEST_HAVE_DRM") == NULL) { g_test_skip ("PHOC_TEST_HAVE_DRM unsed"); return; } phoc_test_client_run (3, &iface, GINT_TO_POINTER (FALSE)); } static void keyboard_event_handle_grab_failed (void *data, struct phosh_private_keyboard_event *kbevent, const char *accelerator, uint32_t error) { PhocTestKeyboardEvent *kbe = data; g_assert_nonnull (kbevent); g_assert (kbe->kbevent == kbevent); kbe->grab_status = GRAB_STATUS_FAILED; } static void keyboard_event_handle_grab_success (void *data, struct phosh_private_keyboard_event *kbevent, const char *accelerator, uint32_t action_id) { PhocTestKeyboardEvent *kbe = data; g_assert_nonnull (kbevent); g_assert (kbe->kbevent == kbevent); if (action_id > 0) kbe->grab_status = GRAB_STATUS_OK; } static const struct phosh_private_keyboard_event_listener keyboard_event_listener = { .grab_failed_event = keyboard_event_handle_grab_failed, .grab_success_event = keyboard_event_handle_grab_success, }; static PhocTestKeyboardEvent * phoc_test_keyboard_event_new (PhocTestClientGlobals *globals, char* title) { PhocTestKeyboardEvent *kbe = g_malloc0 (sizeof (PhocTestKeyboardEvent)); g_assert (phosh_private_get_version (globals->phosh) >= 5); kbe->kbevent = phosh_private_get_keyboard_event (globals->phosh); kbe->title = title; phosh_private_keyboard_event_add_listener (kbe->kbevent, &keyboard_event_listener, kbe); return kbe; } #define RAISE_VOL_KEY "XF86AudioRaiseVolume" static gboolean test_client_phosh_private_kbevent_simple (PhocTestClientGlobals *globals, gpointer unused) { PhocTestKeyboardEvent *test1; PhocTestKeyboardEvent *test2; test1 = phoc_test_keyboard_event_new (globals, "test-mediakey-grabbing"); test2 = phoc_test_keyboard_event_new (globals, "test-invalid-grabbing"); phosh_private_keyboard_event_grab_accelerator_request (test1->kbevent, "XF86AudioLowerVolume"); /* Not allowed to bind this one: */ phosh_private_keyboard_event_grab_accelerator_request (test2->kbevent, "F9"); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); g_assert_cmpint (test1->grab_status, ==, GRAB_STATUS_OK); g_assert_cmpint (test2->grab_status, ==, GRAB_STATUS_FAILED); test1->grab_status = GRAB_STATUS_UNKNOWN; test2->grab_status = GRAB_STATUS_UNKNOWN; phosh_private_keyboard_event_grab_accelerator_request (test1->kbevent, RAISE_VOL_KEY); /* Can't bind same key twice: */ phosh_private_keyboard_event_grab_accelerator_request (test2->kbevent, RAISE_VOL_KEY); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); g_assert_cmpint (test1->grab_status, ==, GRAB_STATUS_OK); g_assert_cmpint (test2->grab_status, ==, GRAB_STATUS_FAILED); test1->grab_status = GRAB_STATUS_UNKNOWN; test2->grab_status = GRAB_STATUS_UNKNOWN; /* Allowing to bind a already bound key with an additional accelerator is o.k. */ phosh_private_keyboard_event_grab_accelerator_request (test1->kbevent, "" RAISE_VOL_KEY); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); g_assert_cmpint (test1->grab_status, ==, GRAB_STATUS_OK); g_assert_cmpint (test2->grab_status, ==, GRAB_STATUS_UNKNOWN); test1->grab_status = GRAB_STATUS_UNKNOWN; test2->grab_status = GRAB_STATUS_UNKNOWN; /* Binding non existing key must fail */ phosh_private_keyboard_event_grab_accelerator_request (test2->kbevent, "does-not-exist"); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); g_assert_cmpint (test1->grab_status, ==, GRAB_STATUS_UNKNOWN); g_assert_cmpint (test2->grab_status, ==, GRAB_STATUS_FAILED); phosh_private_keyboard_event_destroy (test1->kbevent); phosh_private_keyboard_event_destroy (test2->kbevent); return TRUE; } static void test_phosh_private_kbevents_simple (void) { PhocTestClientIface iface = { .client_run = test_client_phosh_private_kbevent_simple, }; phoc_test_client_run (3, &iface, NULL); } static void startup_tracker_handle_launched (void *data, struct phosh_private_startup_tracker *startup_tracker, const char *startup_id, unsigned int protocol, unsigned int flags) { int *counter = data; (*counter)++; g_assert_cmpint (flags, ==, 0); g_assert_cmpint(protocol, ==, PHOSH_PRIVATE_STARTUP_TRACKER_PROTOCOL_GTK_SHELL); } static void startup_tracker_handle_startup_id (void *data, struct phosh_private_startup_tracker *startup_tracker, const char *startup_id, unsigned int protocol, unsigned int flags) { int *counter = data; (*counter)++; g_assert_cmpint (flags, ==, 0); g_assert_cmpint(protocol, ==, PHOSH_PRIVATE_STARTUP_TRACKER_PROTOCOL_GTK_SHELL); } static const struct phosh_private_startup_tracker_listener startup_tracker_listener = { .startup_id = startup_tracker_handle_startup_id, .launched = startup_tracker_handle_launched, }; static gboolean test_client_phosh_private_startup_tracker_simple (PhocTestClientGlobals *globals, gpointer unused) { struct phosh_private_startup_tracker *tracker; int counter = 0; tracker = phosh_private_get_startup_tracker (globals->phosh); g_assert_cmpint (phosh_private_get_version (globals->phosh), >=, 6); g_assert_cmpint (gtk_shell1_get_version (globals->gtk_shell1), >=, 3); phosh_private_startup_tracker_add_listener (tracker, &startup_tracker_listener, &counter); gtk_shell1_set_startup_id (globals->gtk_shell1, "startup_id1"); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); g_assert_cmpint (counter, ==, 1); gtk_shell1_notify_launch (globals->gtk_shell1, "startup_id1"); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); phosh_private_startup_tracker_destroy (tracker); g_assert_cmpint (counter, ==, 2); return TRUE; } static void test_phosh_private_startup_tracker_simple (void) { PhocTestClientIface iface = { .client_run = test_client_phosh_private_startup_tracker_simple, }; phoc_test_client_run (3, &iface, NULL); } gint main (gint argc, gchar *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/phoc/phosh/thumbnail/simple", test_phosh_private_thumbnail_simple); g_test_add_func ("/phoc/phosh/kbevents/simple", test_phosh_private_kbevents_simple); g_test_add_func ("/phoc/phosh/startup-tracker/simple", test_phosh_private_startup_tracker_simple); return g_test_run (); } phoc-v0.13.1/tests/test-run.c000066400000000000000000000031431422111650000157730ustar00rootroot00000000000000/* * Copyright (C) 2020 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #include "server.h" static gboolean on_timer_expired (gpointer unused) { /* Compositor did not quit in time */ g_assert_not_reached (); return FALSE; } static void test_phoc_run_session_success (void) { g_autoptr(PhocServer) server = phoc_server_get_default (); g_autoptr(GMainLoop) loop = g_main_loop_new (NULL, FALSE); g_assert_true (PHOC_IS_SERVER (server)); g_assert_true (phoc_server_setup(server, TEST_PHOC_INI, "/bin/true", loop, PHOC_SERVER_FLAG_NONE, PHOC_SERVER_DEBUG_FLAG_NONE)); g_timeout_add_seconds (3, on_timer_expired, NULL); g_main_loop_run (loop); g_assert_cmpint (phoc_server_get_session_exit_status (server), ==, 0); } static void test_phoc_run_session_failure (void) { g_autoptr(PhocServer) server = phoc_server_get_default (); g_autoptr(GMainLoop) loop = g_main_loop_new (NULL, FALSE); g_assert_true (PHOC_IS_SERVER (server)); g_assert_true (phoc_server_setup(server, TEST_PHOC_INI, "/bin/false", loop, PHOC_SERVER_FLAG_NONE, PHOC_SERVER_DEBUG_FLAG_NONE)); g_timeout_add_seconds (3, on_timer_expired, NULL); g_main_loop_run (loop); g_assert_cmpint (phoc_server_get_session_exit_status (server), ==, 1); } gint main (gint argc, gchar *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func("/phoc/run/session_success", test_phoc_run_session_success); g_test_add_func("/phoc/run/session_failure", test_phoc_run_session_failure); return g_test_run(); } phoc-v0.13.1/tests/test-server.c000066400000000000000000000025661422111650000165050ustar00rootroot00000000000000/* * Copyright (C) 2020 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #include "server.h" static void test_phoc_server_get_default (void) { g_autoptr(PhocServer) server = phoc_server_get_default (); PhocServer *server2; g_assert_true (PHOC_IS_SERVER (server)); server2 = phoc_server_get_default (); g_assert_true (server2 == server); } static void test_phoc_server_setup (void) { g_autoptr(PhocServer) server = phoc_server_get_default (); g_assert_true (PHOC_IS_SERVER (server)); g_assert_true (phoc_server_setup(server, TEST_PHOC_INI, NULL, NULL, PHOC_SERVER_FLAG_NONE, PHOC_SERVER_DEBUG_FLAG_NONE)); } static void test_phoc_server_setup_args (void) { g_autoptr(PhocServer) server = phoc_server_get_default (); g_assert_true (PHOC_IS_SERVER (server)); g_assert_true (phoc_server_setup(server, TEST_PHOC_INI, "/bin/bash", NULL, PHOC_SERVER_FLAG_NONE, PHOC_SERVER_DEBUG_FLAG_NONE)); g_assert_cmpstr (server->session, ==, "/bin/bash"); } gint main (gint argc, gchar *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func("/phoc/server/get_default", test_phoc_server_get_default); g_test_add_func("/phoc/server/setup", test_phoc_server_setup); g_test_add_func("/phoc/server/setup-args", test_phoc_server_setup_args); return g_test_run(); } phoc-v0.13.1/tests/test-xdg-shell.c000066400000000000000000000120561422111650000170610ustar00rootroot00000000000000/* * Copyright (C) 2020 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #include "testlib.h" typedef struct _PhocTestXdgToplevelSurface { struct wl_surface *wl_surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; PhocTestBuffer buffer; guint32 width, height; gboolean configured; gboolean toplevel_configured; } PhocTestXdgToplevelSurface; static void xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { PhocTestXdgToplevelSurface *xs = data; g_debug ("Configured %p serial %d", xdg_surface, serial); xdg_surface_ack_configure(xs->xdg_surface, serial); xs->configured = TRUE; } static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_handle_configure, }; #define WIDTH 100 #define HEIGHT 200 static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) { PhocTestXdgToplevelSurface *xs = data; g_debug ("Configured %p, size: %dx%d", xdg_toplevel, width, height); g_assert_nonnull (xdg_toplevel); xs->width = width ?: WIDTH; xs->height = height ?: HEIGHT; xs->toplevel_configured = TRUE; } static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_surface) { /* TBD */ } static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, }; static void phoc_test_xdg_surface_free (PhocTestXdgToplevelSurface *xs) { xdg_toplevel_destroy (xs->xdg_toplevel); xdg_surface_destroy (xs->xdg_surface); wl_surface_destroy (xs->wl_surface); phoc_test_buffer_free (&xs->buffer); g_free (xs); } static PhocTestXdgToplevelSurface * phoc_test_xdg_surface_new (PhocTestClientGlobals *globals, guint32 width, guint32 height, guint32 color) { PhocTestXdgToplevelSurface *xs = g_malloc0 (sizeof(PhocTestXdgToplevelSurface)); xs->wl_surface = wl_compositor_create_surface (globals->compositor); g_assert_nonnull (xs->wl_surface); xs->xdg_surface = xdg_wm_base_get_xdg_surface (globals->xdg_shell, xs->wl_surface); g_assert_nonnull (xs->wl_surface); xdg_surface_add_listener (xs->xdg_surface, &xdg_surface_listener, xs); xs->xdg_toplevel = xdg_surface_get_toplevel (xs->xdg_surface); g_assert_nonnull (xs->xdg_toplevel); xdg_toplevel_add_listener (xs->xdg_toplevel, &xdg_toplevel_listener, xs); xdg_toplevel_set_min_size (xs->xdg_toplevel, WIDTH, HEIGHT); wl_surface_commit (xs->wl_surface); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); g_assert_true (xs->configured); g_assert_true (xs->toplevel_configured); phoc_test_client_create_shm_buffer (globals, &xs->buffer, xs->width, xs->height, WL_SHM_FORMAT_XRGB8888); for (int i = 0; i < xs->width * xs->height * 4; i+=4) *(guint32*)(xs->buffer.shm_data + i) = color; wl_surface_attach (xs->wl_surface, xs->buffer.wl_buffer, 0, 0); wl_surface_damage (xs->wl_surface, 0, 0, xs->width, xs->height); wl_surface_commit (xs->wl_surface); wl_display_dispatch (globals->display); wl_display_roundtrip (globals->display); return xs; } static gboolean test_client_xdg_shell_normal (PhocTestClientGlobals *globals, gpointer data) { PhocTestXdgToplevelSurface *ls_green; ls_green = phoc_test_xdg_surface_new (globals, WIDTH, HEIGHT, 0xFF00FF00); g_assert_nonnull (ls_green); phoc_assert_screenshot (globals, "test-xdg-shell-normal-1.png"); phoc_test_xdg_surface_free (ls_green); phoc_assert_screenshot (globals, "empty.png"); return TRUE; } static gboolean test_client_xdg_shell_maximized (PhocTestClientGlobals *globals, gpointer data) { PhocTestXdgToplevelSurface *ls_green; ls_green = phoc_test_xdg_surface_new (globals, WIDTH, HEIGHT, 0xFF00FF00); g_assert_nonnull (ls_green); phoc_assert_screenshot (globals, "test-xdg-shell-maximized-1.png"); phoc_test_xdg_surface_free (ls_green); phoc_assert_screenshot (globals, "empty.png"); return TRUE; } static gboolean test_client_xdg_shell_server_prepare (PhocServer *server, gpointer data) { PhocDesktop *desktop = server->desktop; gboolean maximize = GPOINTER_TO_INT (data); g_assert_nonnull (desktop); phoc_desktop_set_auto_maximize (desktop, maximize); return TRUE; } static void test_xdg_shell_normal (void) { PhocTestClientIface iface = { .server_prepare = test_client_xdg_shell_server_prepare, .client_run = test_client_xdg_shell_normal, }; phoc_test_client_run (3, &iface, GINT_TO_POINTER (FALSE)); } static void test_xdg_shell_maximized (void) { PhocTestClientIface iface = { .server_prepare = test_client_xdg_shell_server_prepare, .client_run = test_client_xdg_shell_maximized, }; phoc_test_client_run (3, &iface, GINT_TO_POINTER (TRUE)); } gint main (gint argc, gchar *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func("/phoc/xdg-shell/simple", test_xdg_shell_normal); g_test_add_func("/phoc/xdg-shell/maximize", test_xdg_shell_maximized); return g_test_run(); } phoc-v0.13.1/tests/testlib.c000066400000000000000000000506251422111650000156670ustar00rootroot00000000000000/* * Copyright (C) 2020 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #include "testlib.h" #include "server.h" #include #include #include #include struct task_data { PhocTestClientFunc func; gpointer data; }; static bool abgr_to_argb (PhocTestBuffer *buffer) { g_assert_true (buffer->format == WL_SHM_FORMAT_ABGR8888 || buffer->format == WL_SHM_FORMAT_XBGR8888); guint8 *data = buffer->shm_data; for (int i = 0; i < buffer->height * buffer->stride; i += 4) { guint32 *px = (guint32 *)(data + i); guint8 r, g, b, a; a = (*px >> 24) & 0xFF; b = (*px >> 16) & 0xFF; g = (*px >> 8) & 0xFF; r = *px & 0xFF; *px = (a << 24) | (r << 16) | (g << 8) | b; } switch (buffer->format) { case WL_SHM_FORMAT_ABGR8888: buffer->format = WL_SHM_FORMAT_ARGB8888; break; case WL_SHM_FORMAT_XBGR8888: buffer->format = WL_SHM_FORMAT_XRGB8888; break; default: g_assert_not_reached (); } return true; } static void buffer_to_argb(PhocTestBuffer *buffer) { switch (buffer->format) { case WL_SHM_FORMAT_XRGB8888: case WL_SHM_FORMAT_ARGB8888: break; case WL_SHM_FORMAT_XBGR8888: case WL_SHM_FORMAT_ABGR8888: abgr_to_argb(buffer); break; default: g_assert_not_reached (); } } static void screencopy_frame_handle_buffer (void *data, struct zwlr_screencopy_frame_v1 *handle, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { PhocTestScreencopyFrame *frame = data; gboolean success; success = phoc_test_client_create_shm_buffer (frame->globals, &frame->buffer, width, height, format); g_assert_true (success); zwlr_screencopy_frame_v1_copy(handle, frame->buffer.wl_buffer); } static void screencopy_frame_handle_flags(void *data, struct zwlr_screencopy_frame_v1 *handle, uint32_t flags) { PhocTestScreencopyFrame *frame = data; frame->flags = flags; } static void screencopy_frame_handle_ready (void *data, struct zwlr_screencopy_frame_v1 *handle, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { PhocTestScreencopyFrame *frame = data; frame->done = TRUE; } static void screencopy_frame_handle_failed(void *data, struct zwlr_screencopy_frame_v1 *frame) { g_assert_not_reached (); } static const struct zwlr_screencopy_frame_v1_listener screencopy_frame_listener = { .buffer = screencopy_frame_handle_buffer, .flags = screencopy_frame_handle_flags, .ready = screencopy_frame_handle_ready, .failed = screencopy_frame_handle_failed, }; static void shm_format (void *data, struct wl_shm *wl_shm, guint32 format) { PhocTestClientGlobals *globals = data; globals->formats |= (1 << format); } static void buffer_release (void *data, struct wl_buffer *buffer) { /* TBD */ } struct wl_shm_listener shm_listener = { shm_format, }; static const struct wl_buffer_listener buffer_listener = { buffer_release, }; 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 transform) { /* TBD */ } static void output_handle_mode (void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { PhocTestOutput *output = data; if ((flags & WL_OUTPUT_MODE_CURRENT) != 0) { /* Make sure we got the right mode to not mess up screenshot comparisons */ g_assert_cmpint (width, ==, 1024); g_assert_cmpint (height, ==, 768); output->width = width; output->height = height; } } static void output_handle_done (void *data, struct wl_output *wl_output) { /* TBD */ } static void output_handle_scale (void *data, struct wl_output *wl_output, int32_t scale) { g_assert_cmpint (scale, ==, 1); } static const struct wl_output_listener output_listener = { .geometry = output_handle_geometry, .mode = output_handle_mode, .done = output_handle_done, .scale = output_handle_scale, }; 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 foreign_toplevel_handle_title (void *data, struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, const char* title) { PhocTestForeignToplevel *toplevel = data; toplevel->title = g_strdup (title); g_debug ("Got toplevel's title: %p %s", toplevel, title); } static void foreign_toplevel_handle_app_id (void *data, struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, const char* app_id) { } static void foreign_toplevel_handle_output_enter (void *data, struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, struct wl_output *output) { } static void foreign_toplevel_handle_output_leave (void *data, struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, struct wl_output *output) { } static void foreign_toplevel_handle_state (void *data, struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1, struct wl_array *state) { } static void foreign_toplevel_handle_done (void *data, struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1) { } static void foreign_toplevel_handle_closed (void *data, struct zwlr_foreign_toplevel_handle_v1 *zwlr_foreign_toplevel_handle_v1) { PhocTestForeignToplevel *toplevel = data; PhocTestClientGlobals *globals = toplevel->globals; globals->foreign_toplevels = g_slist_remove (globals->foreign_toplevels, toplevel); g_free (toplevel->title); g_free (toplevel); } static const struct zwlr_foreign_toplevel_handle_v1_listener foreign_toplevel_handle_listener = { foreign_toplevel_handle_title, foreign_toplevel_handle_app_id, foreign_toplevel_handle_output_enter, foreign_toplevel_handle_output_leave, foreign_toplevel_handle_state, foreign_toplevel_handle_done, foreign_toplevel_handle_closed }; static void foreign_toplevel_manager_handle_toplevel (void *data, struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1, struct zwlr_foreign_toplevel_handle_v1 *handle) { PhocTestClientGlobals *globals = data; PhocTestForeignToplevel *toplevel = g_malloc0 (sizeof (PhocTestForeignToplevel)); toplevel->handle = handle; toplevel->globals = globals; globals->foreign_toplevels = g_slist_append (globals->foreign_toplevels, toplevel); zwlr_foreign_toplevel_handle_v1_add_listener (handle, &foreign_toplevel_handle_listener, toplevel); g_debug ("New toplevel: %p", toplevel); } static void foreign_toplevel_manager_handle_finished (void *data, struct zwlr_foreign_toplevel_manager_v1 *zwlr_foreign_toplevel_manager_v1) { g_debug ("wlr_foreign_toplevel_manager_finished"); } static const struct zwlr_foreign_toplevel_manager_v1_listener foreign_toplevel_manager_listener = { foreign_toplevel_manager_handle_toplevel, foreign_toplevel_manager_handle_finished }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { PhocTestClientGlobals *globals = data; if (!g_strcmp0 (interface, wl_compositor_interface.name)) { globals->compositor = wl_registry_bind (registry, name, &wl_compositor_interface, 4); } else if (!g_strcmp0 (interface, wl_shm_interface.name)) { globals->shm = wl_registry_bind (registry, name, &wl_shm_interface, 1); wl_shm_add_listener (globals->shm, &shm_listener, globals); } else if (!g_strcmp0 (interface, wl_output_interface.name)) { /* TODO: only one output atm */ g_assert_null (globals->output.output); globals->output.output = wl_registry_bind (registry, name, &wl_output_interface, 3); wl_output_add_listener(globals->output.output, &output_listener, &globals->output); } else if (!g_strcmp0 (interface, xdg_wm_base_interface.name)) { globals->xdg_shell = wl_registry_bind (registry, name, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(globals->xdg_shell, &wm_base_listener, NULL); } else if (!g_strcmp0 (interface, zwlr_layer_shell_v1_interface.name)) { globals->layer_shell = wl_registry_bind (registry, name, &zwlr_layer_shell_v1_interface, 2); } else if (!g_strcmp0 (interface, zwlr_screencopy_manager_v1_interface.name)) { globals->screencopy_manager = wl_registry_bind (registry, name, &zwlr_screencopy_manager_v1_interface, 1); } else if (!g_strcmp0 (interface, zwlr_foreign_toplevel_manager_v1_interface.name)) { globals->foreign_toplevel_manager = wl_registry_bind (registry, name, &zwlr_foreign_toplevel_manager_v1_interface, 2); zwlr_foreign_toplevel_manager_v1_add_listener (globals->foreign_toplevel_manager, &foreign_toplevel_manager_listener, globals); } else if (!g_strcmp0 (interface, phosh_private_interface.name)) { globals->phosh = wl_registry_bind (registry, name, &phosh_private_interface, 6); } else if (!g_strcmp0 (interface, gtk_shell1_interface.name)) { globals->gtk_shell1 = wl_registry_bind (registry, name, >k_shell1_interface, 3); } } static void registry_handle_global_remove (void *data, struct wl_registry *registry, uint32_t name) { // This space is intentionally left blank } static const struct wl_registry_listener registry_listener = { .global = registry_handle_global, .global_remove = registry_handle_global_remove, }; static void wl_client_run (GTask *task, gpointer source, gpointer data, GCancellable *cancel) { struct wl_registry *registry; gboolean success = FALSE; struct task_data *td = data; PhocTestClientGlobals globals = { 0 }; globals.display = wl_display_connect(NULL); g_assert_nonnull (globals.display); registry = wl_display_get_registry(globals.display); wl_registry_add_listener(registry, ®istry_listener, &globals); wl_display_dispatch(globals.display); wl_display_roundtrip(globals.display); g_assert_nonnull (globals.compositor); g_assert_nonnull (globals.layer_shell); g_assert_nonnull (globals.shm); g_assert_nonnull (globals.xdg_shell); g_assert_nonnull (globals.gtk_shell1); g_assert (globals.formats & (1 << WL_SHM_FORMAT_XRGB8888)); if (td->func) success = (td->func)(&globals, td->data); else success = TRUE; g_task_return_boolean (task, success); } static void on_wl_client_finish (GObject *source, GAsyncResult *res, gpointer data) { GMainLoop *loop = data; gboolean success; g_autoptr(GError) err = NULL; g_assert_true (g_task_is_valid (res, source)); success = g_task_propagate_boolean (G_TASK (res), &err); /* Client ran succesfully */ g_assert_true (success); g_main_loop_quit (loop); } static gboolean on_timer_expired (gpointer unused) { /* Compositor did not quit in time */ g_assert_not_reached (); return FALSE; } /** * phoc_test_client_run: * * timeout: Abort test after timeout seconds * func: The test function to run * data: Data passed to the test function * * Run func in a wayland client connected to compositor instance. The * test function is expected to return %TRUE on success and %FALSE * otherwise. */ void phoc_test_client_run (gint timeout, PhocTestClientIface *iface, gpointer data) { struct task_data td = { .data = data }; g_autoptr(PhocServer) server = phoc_server_get_default (); g_autoptr(GMainLoop) loop = g_main_loop_new (NULL, FALSE); g_autoptr(GTask) wl_client_task = g_task_new (NULL, NULL, on_wl_client_finish, loop); if (iface) td.func = iface->client_run; g_assert_true (PHOC_IS_SERVER (server)); g_assert_true (phoc_server_setup(server, TEST_PHOC_INI, NULL, loop, PHOC_SERVER_FLAG_NONE, PHOC_SERVER_DEBUG_FLAG_NONE)); if (iface && iface->server_prepare) g_assert_true (iface->server_prepare(server, data)); g_task_set_task_data (wl_client_task, &td, NULL); g_task_run_in_thread (wl_client_task, wl_client_run); g_timeout_add_seconds (timeout, on_timer_expired, NULL); g_main_loop_run (loop); } static int create_anon_file (off_t size) { char template[] = "/tmp/phoctest-shared-XXXXXX"; int fd; int ret; fd = mkstemp(template); g_assert_cmpint (fd, >=, 0); do { errno = 0; ret = ftruncate(fd, size); } while (errno == EINTR); g_assert_cmpint (ret, ==, 0); unlink(template); return fd; } /** * phoc_test_client_create_shm_buffer: * * Create a shm buffer, this assumes RGBA8888 */ gboolean phoc_test_client_create_shm_buffer (PhocTestClientGlobals *globals, PhocTestBuffer *buffer, int width, int height, guint32 format) { struct wl_shm_pool *pool; int fd, size; void *data; g_assert (globals->shm); buffer->stride = width * 4; buffer->width = width; buffer->height = height; buffer->format = format; size = buffer->stride * height; fd = create_anon_file(size); g_assert_cmpint (fd, >=, 0); data = mmap (NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); g_assert (data != MAP_FAILED); pool = wl_shm_create_pool(globals->shm, fd, size); buffer->wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height, buffer->stride, format); wl_buffer_add_listener (buffer->wl_buffer, &buffer_listener, buffer); wl_shm_pool_destroy (pool); close (fd); buffer->shm_data = data; return TRUE; } static gint foreign_toplevel_compare (gconstpointer data, gconstpointer title) { const PhocTestForeignToplevel *toplevel = data; return g_strcmp0 (toplevel->title, title); } /** * * phoc_test_client_get_foreign_toplevel_handle: * * Get the PhocTestForeignToplevel for a toplevel with the given title * using the wlr_foreign_toplevel_management protocol. * * Returns: (transfer-none): The toplevel's handle, or NULL if it doesn't exist. */ PhocTestForeignToplevel * phoc_test_client_get_foreign_toplevel_handle (PhocTestClientGlobals *globals, const char *title) { GSList *list = g_slist_find_custom (globals->foreign_toplevels, title, foreign_toplevel_compare); if (!list || !list->data) return NULL; return list->data; } /** * * phoc_test_client_capture_frame: * * Capture the given wlr_screencopy_frame and return its screenshot buffer * * Returns: (transfer-none): The screenshot buffer. */ PhocTestBuffer * phoc_test_client_capture_frame (PhocTestClientGlobals *globals, PhocTestScreencopyFrame *frame, struct zwlr_screencopy_frame_v1 *handle) { frame->globals = globals; frame->handle = handle; g_assert_false (frame->done); zwlr_screencopy_frame_v1_add_listener(handle, &screencopy_frame_listener, frame); while (!frame->done && wl_display_dispatch (globals->display) != -1) { } g_assert_true (frame->done); /* Reverse captured buffer */ if (frame->flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT) { guint32 height = frame->buffer.height; guint32 stride = frame->buffer.stride; guint8 *src = frame->buffer.shm_data; g_autofree guint8 *dst = g_malloc0 (height * stride); for (guint i = 0, j = height - 1; i < height; i++, j--) memmove((dst + (i * stride)), (src + (j * stride)), stride); memmove (src, dst, height * stride); frame->flags &= ~ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; /* There shouldn't be any other flags left */ g_assert_false (frame->flags); } buffer_to_argb(&frame->buffer); frame->done = FALSE; return &frame->buffer; } /** * * phoc_test_client_capture_output: * * Capture the given output and return its screenshot buffer * * Returns: (transfer-none): The screenshot buffer. */ PhocTestBuffer * phoc_test_client_capture_output (PhocTestClientGlobals *globals, PhocTestOutput *output) { struct zwlr_screencopy_frame_v1 *handle = zwlr_screencopy_manager_v1_capture_output (globals->screencopy_manager, FALSE, output->output); phoc_test_client_capture_frame (globals, &output->screenshot, handle); g_assert_cmpint (output->screenshot.buffer.width, ==, output->width); g_assert_cmpint (output->screenshot.buffer.height, ==, output->height); return &output->screenshot.buffer; } /** * * phoc_test_buffer_equal: * * Compare two buffers * * Returns: %TRUE if buffers have identical content, otherwise %FALSE */ gboolean phoc_test_buffer_equal (PhocTestBuffer *buf1, PhocTestBuffer *buf2) { guint8 *c1 = buf1->shm_data; guint8 *c2 = buf2->shm_data; g_assert_true (buf1->format == WL_SHM_FORMAT_XRGB8888 || buf1->format == WL_SHM_FORMAT_ARGB8888); g_assert_true (buf2->format == WL_SHM_FORMAT_XRGB8888 || buf2->format == WL_SHM_FORMAT_ARGB8888); if (buf1->width != buf2->width || buf1->height != buf2->height) { return FALSE; } for (guint y = 0; y < buf1->height; y++) { for (guint x = 0; x < buf1->width; x++) { // B if (c1[y * buf1->stride + x * 4 + 0] != c2[y * buf2->stride + x * 4 + 0]) return FALSE; // G if (c1[y * buf1->stride + x * 4 + 1] != c2[y * buf2->stride + x * 4 + 1]) return FALSE; // R if (c1[y * buf1->stride + x * 4 + 2] != c2[y * buf2->stride + x * 4 + 2]) return FALSE; // A/X if (buf1->format == WL_SHM_FORMAT_ARGB8888 && buf2->format == WL_SHM_FORMAT_ARGB8888) if (c1[y * buf1->stride + x * 4 + 3] != c2[y * buf2->stride + x * 4 + 3]) return FALSE; if (buf1->format == WL_SHM_FORMAT_ARGB8888 && buf2->format == WL_SHM_FORMAT_XRGB8888) if (c1[y * buf1->stride + x * 4 + 3] != 255) return FALSE; if (buf1->format == WL_SHM_FORMAT_XRGB8888 && buf2->format == WL_SHM_FORMAT_ARGB8888) if (c2[y * buf2->stride + x * 4 + 3] != 255) return FALSE; } } return TRUE; } /** * phoc_test_buffer_save: * * Save a buffer as png * * Returns: %TRUE if buffers was saved successfully, otherwise %FALSE */ gboolean phoc_test_buffer_save (PhocTestBuffer *buffer, const gchar *filename) { cairo_surface_t *surface; cairo_status_t status; g_assert_nonnull (buffer); g_assert_nonnull (filename); g_assert_true (buffer->format == WL_SHM_FORMAT_XRGB8888 || buffer->format == WL_SHM_FORMAT_ARGB8888); surface = cairo_image_surface_create_for_data ((guchar*)buffer->shm_data, CAIRO_FORMAT_ARGB32, buffer->width, buffer->height, buffer->stride); status = cairo_surface_write_to_png (surface, filename); g_assert_cmpint (status, ==, CAIRO_STATUS_SUCCESS); g_debug ("Saved buffer png %s", filename); cairo_surface_destroy(surface); return TRUE; } gboolean phoc_test_buffer_matches_screenshot (PhocTestBuffer *buffer, const gchar *filename) { const char *msg; cairo_surface_t *surface = cairo_image_surface_create_from_png (filename); cairo_format_t format; guint32 *l, *r; guint32 mask = 0xFFFFFFFF; int ret; g_assert_true (buffer->format == WL_SHM_FORMAT_XRGB8888 || buffer->format == WL_SHM_FORMAT_ARGB8888); switch (cairo_surface_status (surface)) { case CAIRO_STATUS_NO_MEMORY: msg = "no memory"; break; case CAIRO_STATUS_FILE_NOT_FOUND: msg = "file not found"; break; case CAIRO_STATUS_READ_ERROR: msg = "read error"; break; case CAIRO_STATUS_PNG_ERROR: msg = "png error"; break; default: msg = NULL; } if (msg) g_error("Failed to load screenshot %s: %s", filename, msg); format = cairo_image_surface_get_format (surface); switch (format) { case CAIRO_FORMAT_RGB24: mask = 0x00FFFFFF; break; case CAIRO_FORMAT_ARGB32: mask = 0xFFFFFFFF; break; default: g_assert_not_reached(); } if (buffer->height != cairo_image_surface_get_height (surface) || buffer->width != cairo_image_surface_get_width (surface) || buffer->stride != cairo_image_surface_get_stride (surface)) { g_test_message ("Metadata mismatch for %s", filename); return FALSE; } l = (guint32*)buffer->shm_data; r = (guint32*)cairo_image_surface_get_data (surface); g_assert_nonnull (r); ret = TRUE; for (int i = 0; i < buffer->height * buffer->stride / 4; i++) { if ((l[i] & mask) != (r[i] & mask)) { g_test_message ("Mismatch: %d: 0x%x 0x%x for %s", i, l[i], r[i], filename); ret = FALSE; } } return ret; } void phoc_test_buffer_free (PhocTestBuffer *buffer) { g_assert_nonnull (buffer); munmap(buffer->shm_data, buffer->stride * buffer->height); wl_buffer_destroy(buffer->wl_buffer); buffer->valid = FALSE; } phoc-v0.13.1/tests/testlib.h000066400000000000000000000116311422111650000156660ustar00rootroot00000000000000/* * Copyright (C) 2020 Purism SPC * SPDX-License-Identifier: GPL-3.0+ * Author: Guido Günther */ #include "server.h" #include #include "gtk-shell-client-protocol.h" #include "xdg-shell-client-protocol.h" #include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "wlr-screencopy-unstable-v1-client-protocol.h" #include "phosh-private-client-protocol.h" G_BEGIN_DECLS typedef struct _PhocTestWlGlobals PhocTestClientGlobals; typedef struct _PhocTestBuffer { struct wl_buffer *wl_buffer; guint8 *shm_data; guint32 width, height, stride; enum wl_shm_format format; gboolean valid; } PhocTestBuffer; typedef struct _PhocTestScreencopyFrame { struct zwlr_screencopy_frame_v1 *handle; PhocTestBuffer buffer; gboolean done; uint32_t flags; PhocTestClientGlobals *globals; } PhocTestScreencopyFrame; typedef struct _PhocTestOutput { struct wl_output *output; guint32 width, height; PhocTestScreencopyFrame screenshot; } PhocTestOutput; typedef struct _PhocTestWlGlobals { struct wl_display *display; struct wl_compositor *compositor; struct wl_shm *shm; struct xdg_wm_base *xdg_shell; struct zwlr_layer_shell_v1 *layer_shell; struct zwlr_screencopy_manager_v1 *screencopy_manager; struct zwlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; GSList *foreign_toplevels; struct phosh_private *phosh; struct gtk_shell1 *gtk_shell1; /* TODO: handle multiple outputs */ PhocTestOutput output; guint32 formats; } PhocTestClientGlobals; typedef struct _PhocTestForeignToplevel { char* title; struct zwlr_foreign_toplevel_handle_v1 *handle; PhocTestClientGlobals *globals; } PhocTestForeignToplevel; typedef gboolean (* PhocTestServerFunc) (PhocServer *server, gpointer data); typedef gboolean (* PhocTestClientFunc) (PhocTestClientGlobals *globals, gpointer data); typedef struct PhocTestClientIface { /* Prepare function runs in server context */ PhocTestServerFunc server_prepare; PhocTestClientFunc client_run; } PhocTestClientIface; /* Test client */ void phoc_test_client_run (gint timeout, PhocTestClientIface *iface, gpointer data); int phoc_test_client_create_shm_buffer (PhocTestClientGlobals *globals, PhocTestBuffer *buffer, int width, int height, guint32 format); PhocTestBuffer *phoc_test_client_capture_frame (PhocTestClientGlobals *globals, PhocTestScreencopyFrame *frame, struct zwlr_screencopy_frame_v1 *handle); PhocTestBuffer *phoc_test_client_capture_output (PhocTestClientGlobals *globals, PhocTestOutput *output); PhocTestForeignToplevel *phoc_test_client_get_foreign_toplevel_handle (PhocTestClientGlobals *globals, const char *title); /* Buffers */ gboolean phoc_test_buffer_equal (PhocTestBuffer *buf1, PhocTestBuffer *buf2); gboolean phoc_test_buffer_save (PhocTestBuffer *buffer, const gchar *filename); gboolean phoc_test_buffer_matches_screenshot (PhocTestBuffer *buffer, const gchar *filename); void phoc_test_buffer_free (PhocTestBuffer *buffer); #define _phoc_test_screenshot_name(l, f, n) (g_strdup_printf ("phoc-test-screenshot-%d-%s_%d.png", l, f, n)) /* * phoc_assert_screenshot: * @g: The client global object * @f: The screenshot to compare the current output to */ #define phoc_assert_screenshot(g, f) G_STMT_START { \ PhocTestClientGlobals *__g = (g); \ const gchar *__f = g_test_build_filename (G_TEST_DIST, "screenshots", f, NULL); \ PhocTestBuffer *__s = phoc_test_client_capture_output (__g, &__g->output); \ if (phoc_test_buffer_matches_screenshot (__s, __f)) ; else { \ g_autofree gchar *__name = _phoc_test_screenshot_name(__LINE__, G_STRFUNC, 0); \ phoc_test_buffer_save (&__g->output.screenshot.buffer, __name); \ g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ "Output content does not match " #f); \ } \ phoc_test_buffer_free (__s); \ } G_STMT_END /** * phoc_test_assert_buffer_equal: * @b1: A PhocClientBuffer * @b2: A PhocClientBuffer * * Debugging macro to compare two buffers. If the buffers don't match * screenshots are taken and saved as PNG. */ #define phoc_assert_buffer_equal(b1, b2) G_STMT_START { \ PhocTestBuffer *__b1 = (b1), *__b2 = (b2); \ if (phoc_test_buffer_equal (__b1 , __b2)) ; else { \ g_autofree gchar *__name1 = _phoc_test_screenshot_name(__LINE__, G_STRFUNC, 1); \ g_autofree gchar *__name2 = _phoc_test_screenshot_name(__LINE__, G_STRFUNC, 2); \ phoc_test_buffer_save (__b1, __name1); \ phoc_test_buffer_save (__b2, __name2); \ g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ "Buffer " #b1 " != " #b2); \ } \ } G_STMT_END G_END_DECLS