pax_global_header00006660000000000000000000000064145704430120014512gustar00rootroot0000000000000052 comment=59014e507175971e84af2b846b6b3a2c232cce7f labwc-0.7.1/000077500000000000000000000000001457044301200126075ustar00rootroot00000000000000labwc-0.7.1/.editorconfig000066400000000000000000000003431457044301200152640ustar00rootroot00000000000000root = true [*.{c,h}] end_of_line = lf insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true indent_style = tab indent_size = 8 max_line_length = 80 [*.{xml,build}] indent_style = space indent_size = 2 labwc-0.7.1/.gitattributes000066400000000000000000000002271457044301200155030ustar00rootroot00000000000000# Exclude checkpatch.pl from language stats # https://github.com/github/linguist/blob/master/docs/overrides.md scripts/checkpatch.pl linguist-vendored labwc-0.7.1/.github/000077500000000000000000000000001457044301200141475ustar00rootroot00000000000000labwc-0.7.1/.github/workflows/000077500000000000000000000000001457044301200162045ustar00rootroot00000000000000labwc-0.7.1/.github/workflows/build.yml000066400000000000000000000115751457044301200200370ustar00rootroot00000000000000# Void-musl images: # https://github.com/void-linux/void-containers/pkgs/container/void-musl # # Void dependencies based on: # https://github.com/void-linux/void-packages/blob/master/srcpkgs/labwc/template # # Recommended GH CI Void mirror based on # https://docs.voidlinux.org/xbps/repositories/mirrors/changing.html name: CI on: [pull_request] jobs: codestyle: name: CodeStyleCheck runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Code Style run: | ./scripts/check make -C scripts/helper ./scripts/find-banned.sh build: name: Build needs: codestyle timeout-minutes: 20 strategy: fail-fast: false matrix: name: [ Arch, Debian, FreeBSD, Void-musl ] include: - name: Arch os: ubuntu-latest container: archlinux:base-devel env: TARGET: 'sh -xe' - name: Debian os: ubuntu-latest container: debian:testing env: TARGET: 'sh -xe' - name: FreeBSD os: ubuntu-latest env: TARGET: 'ssh freebsd /bin/sh -xe' - name: Void-musl os: ubuntu-latest container: ghcr.io/void-linux/void-musl:latest env: TARGET: 'sh -xe' env: ${{ matrix.env }} runs-on: ${{ matrix.os }} container: ${{ matrix.container }} steps: - uses: actions/checkout@v1 - name: Install Arch Linux dependencies if: matrix.name == 'Arch' run: | pacman-key --init pacman -Syu --noconfirm pacman -S --noconfirm git meson clang wlroots libdrm libinput \ wayland-protocols cairo pango libxml2 xorg-xwayland librsvg libdisplay-info - name: Install Debian Testing dependencies if: matrix.name == 'Debian' run: | sed -i '/^Types/ s/deb/& deb-src/' /etc/apt/sources.list.d/debian.sources apt-get update apt-get upgrade -y apt-get install -y git gcc clang apt-get build-dep -y labwc - name: Install FreeBSD dependencies if: matrix.name == 'FreeBSD' uses: vmactions/freebsd-vm@v1 with: usesh: true prepare: | sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf pkg set -yn pkg:mesa-dri # hack to skip llvm dependency pkg install -y git meson gcc pkgconf cairo pango evdev-proto \ hwdata wayland-protocols wlroots libdisplay-info run: echo "setup done" - name: Install Void Linux dependencies if: matrix.name == 'Void-musl' run: | mkdir -p /etc/xbps.d cp /usr/share/xbps.d/*-repository-*.conf /etc/xbps.d/ sed -i "s:repo-default\.voidlinux\.org:repo-ci.voidlinux.org:g" \ /etc/xbps.d/*-repository-*.conf xbps-install -Syu || xbps-install -yu xbps xbps-install -Syu xbps-install -y git meson gcc clang pkg-config scdoc \ cairo-devel glib-devel libpng-devel librsvg-devel libxml2-devel \ pango-devel wlroots0.17-devel - name: Build with gcc run: | echo ' cd "$GITHUB_WORKSPACE" export CC=gcc meson setup build-gcc -Dxwayland=enabled --werror meson compile -C build-gcc ' | $TARGET - name: Build with clang run: | echo ' cd "$GITHUB_WORKSPACE" export CC=clang meson setup build-clang -Dxwayland=enabled --werror meson compile -C build-clang ' | $TARGET - name: Build with gcc - no-xwayland run: | echo ' cd "$GITHUB_WORKSPACE" export CC=gcc meson setup build-gcc-no-xwayland -Dxwayland=disabled --werror meson compile -C build-gcc-no-xwayland ' | $TARGET - name: Build with clang - no-xwayland run: | echo ' cd "$GITHUB_WORKSPACE" export CC=clang meson setup build-clang-no-xwayland -Dxwayland=disabled --werror meson compile -C build-clang-no-xwayland ' | $TARGET - name: Build with gcc - release run: | echo ' cd "$GITHUB_WORKSPACE" export CC=gcc meson setup build-gcc-release -Dxwayland=enabled \ -Dbuildtype=release -Db_ndebug=true --werror meson compile -C build-gcc-release ' | $TARGET - name: Build with clang - release run: | echo ' cd "$GITHUB_WORKSPACE" export CC=clang meson setup build-clang-release -Dxwayland=enabled \ -Dbuildtype=release -Db_ndebug=true --werror meson compile -C build-clang-release ' | $TARGET labwc-0.7.1/.github/workflows/irc.yml000066400000000000000000000101061457044301200175020ustar00rootroot00000000000000name: "IRC Notifications" on: create: pull_request: types: [opened, closed, reopened] issues: types: [opened, closed, reopened] push: branches: - 'master_disabled' - 'v0.5_disabled' jobs: test: runs-on: ubuntu-latest steps: - name: irc push uses: rectalogic/notify-irc@v1 if: github.event_name == 'push' with: server: "irc.libera.chat" channel: "#labwc" nickname: "labwc" notice: true message: "[${{ github.event.ref }}] ${{ github.actor }} pushed new commits: ${{ github.event.compare }}" - name: irc issue opened uses: rectalogic/notify-irc@v1 if: github.event_name == 'issues' && github.event.action == 'opened' with: server: "irc.libera.chat" channel: "#labwc" nickname: "labwc" notice: true message: "${{ github.actor }} opened issue '${{ github.event.issue.title }}' (${{ github.event.issue.html_url }})" - name: irc issue reopened uses: rectalogic/notify-irc@v1 if: github.event_name == 'issues' && github.event.action == 'reopened' with: server: "irc.libera.chat" channel: "#labwc" nickname: "labwc" notice: true message: "${{ github.actor }} reopened issue: '${{ github.event.issue.title }}' (${{ github.event.issue.html_url }})" - name: irc issue closed uses: rectalogic/notify-irc@v1 if: github.event_name == 'issues' && github.event.action == 'closed' with: server: "irc.libera.chat" channel: "#labwc" nickname: "labwc" notice: true message: "${{ github.actor }} closed issue '${{ github.event.issue.title }}' (${{ github.event.issue.html_url }})" - name: irc pull request opened uses: rectalogic/notify-irc@v1 if: github.event_name == 'pull_request' && github.event.action == 'opened' with: server: "irc.libera.chat" channel: "#labwc" nickname: "labwc" notice: true message: "[${{ github.event.pull_request.base.ref }}] ${{ github.actor }} opened PR '${{ github.event.pull_request.title }}' (${{ github.event.pull_request.html_url }})" - name: irc pull request reopened uses: rectalogic/notify-irc@v1 if: github.event_name == 'pull_request' && github.event.action == 'reopened' with: server: "irc.libera.chat" channel: "#labwc" nickname: "labwc" notice: true message: "[${{ github.event.pull_request.base.ref }}] ${{ github.actor }} reopened PR '${{ github.event.pull_request.title }}' (${{ github.event.pull_request.html_url }})" - name: irc pull request merged uses: rectalogic/notify-irc@v1 if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true with: server: "irc.libera.chat" channel: "#labwc" nickname: "labwc" notice: true message: "[${{ github.event.pull_request.base.ref }}] ${{ github.actor }} merged PR '${{ github.event.pull_request.title }}' (${{ github.event.pull_request.html_url }})" - name: irc pull request closed uses: rectalogic/notify-irc@v1 if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == false with: server: "irc.libera.chat" channel: "#labwc" nickname: "labwc" notice: true message: "[${{ github.event.pull_request.base.ref }}] ${{ github.actor }} closed PR '${{ github.event.pull_request.title }}' (${{ github.event.pull_request.html_url }})" - name: irc tag created uses: rectalogic/notify-irc@v1 if: github.event_name == 'create' && github.event.ref_type == 'tag' with: server: "irc.libera.chat" channel: "#labwc" nickname: "labwc" notice: true message: "${{ github.actor }} tagged ${{ github.repository }}: ${{ github.event.ref }}" labwc-0.7.1/.github/workflows/labwc.github.io.yml000066400000000000000000000007751457044301200217170ustar00rootroot00000000000000# Triggers a rebuild and deploy of labwc.github.io when man pages change # # https://stackoverflow.com/a/65514259 name: "labwc.github.io" on: push: branches: - 'master' - 'v0.5_disabled' paths: - 'docs/*.scd' jobs: notify: runs-on: ubuntu-latest steps: - name: labwc.github.io env: GITHUB_TOKEN: ${{ secrets.WEB_DEPLOY_TOKEN }} run: | gh api repos/labwc/labwc.github.io/dispatches \ --raw-field event_type=rebuild labwc-0.7.1/.gitignore000066400000000000000000000000141457044301200145720ustar00rootroot00000000000000*.swp *.swo labwc-0.7.1/CONTRIBUTING.md000066400000000000000000000357251457044301200150540ustar00rootroot00000000000000- [1. How to Contribute](#how-to-contribute) - [2. Debugging](#debugging) - [2.1 Backtraces](#backtraces) - [2.2 Debug Logs](#debug-logs) - [2.3 Output](#output) - [2.4 Input](#input) - [3. Packaging](#packaging) - [4. Coding Style](#coding-style) - [4.1 Linux Kernel Style Basics](#linux-kernel-style-basics) - [4.2 Devault Deviations](#devault-deviations) - [4.3 Labwc Specifics](#labwc-specifics) - [4.3.1 API](#api) - [4.3.2 The Use of glib](#the-use-of-glib) - [4.3.3 The use of GNU extensions](#the-use-of-gnu-extensions) - [4.3.4 Naming Conventions](#naming-conventions) - [5. Commit Messages](#commit-messages) - [6. Submitting Patches](#submitting-patches) - [7. Native Language Support](#native-language-support) - [8. Upversion](#upversion) # How to Contribute 1. Report bugs as github issues. We don't use a template, but try to provide some sensible information such as what happened, what you expected to happen and steps to reproduce. If applicable try with default configuration. If you are able to, try to do some debugging (guidelines below). 2. Submit patches as github pull-requests. If you wish to introduces significant changes or new features, consult the [scope document], discuss on IRC or via a github issue first. # Debugging There is no one-way-fits-all method for debugging, so you have to use your antennae and do some detective work. This section contains some approaches which may prove useful. ## Backtraces If the compositor crashes, a good starting point is to produce a backtrace by building with ASAN/UBSAN: ``` meson setup -Db_sanitize=address,undefined build/ meson compile -C build/ ``` ## Debug Logs Get debug log with `labwc -d`. The log can be directed to a file with `labwc -d 2>log.txt` To see what is happening on the wayland protocol for a specific client, run it with environment variable `WAYLAND_DEBUG` set to 1, for example `WAYLAND_DEBUG=1 foot`. To see what the compositor is doing on the protocol run `labwc` nested (i.e. start labwc from a terminal in another instance of labwc or some other compositor) with `WAYLAND_DEBUG=server`. This filters out anything from clients. For wayland clients, you can get a live view of some useful info using [wlhax]. ## Output If you think you've got a damage issue, you can run labwc like this: `WLR_SCENE_DEBUG_DAMAGE=highlight labwc` to get a visual indication of damage regions. To emulate multiple outputs (even if you only have one physical monitor), run with `WLR_WL_OUTPUTS=2 labwc` or similar. See [`wlroots/docs/env_vars.md`] for more options. For some types of bugs, it might be useful to find out which mesa driver (.so) you are using. This can be done with `EGL_LOG_LEVEL=debug labwc 2>&1 | grep MESA-LOADER` To rule out driver issues you can run with `WLR_RENDERER=pixman labwc` You can also get some useful system info with [drm_info]. ## Input Use `sudo libinput debug-events` to show input events. From a terminal you can use `xev -event keyboard` and `wev -f wl_keyboard:key` to analyse keyboard events # Packaging Some distributions carry labwc in their repositories or user repositories. - @ptrcnull (Alpine) - @narrat (Arch) - @b1rger (Debian) - @jbeich (FreeBSD) - @AndersonTorres (NixOS) - @adcdam (Slackware) - @bdantas (Tiny Core Linux) - @Visone-Selektah (Venom Linux) - @tranzystorekk (Void Linux) kindly maintain the packages in their respective distro. Let's keep them informed of new releases and any changes that relate to packaging. If you are maintaining a labwc package for another distro feel free to open an issue so we can add you to this list. # Coding Style labwc is written in the [Linux kernel coding style] with a small number of deviations to align with [Drew Devault's preferred coding style] namely: 1. [Function Declaration](https://git.sr.ht/~sircmpwn/cstyle#function-declarations) 2. [Braces for one-line statement](https://git.sr.ht/~sircmpwn/cstyle#brace-placement) 3. [Organisation of header #include statements](https://git.sr.ht/~sircmpwn/cstyle#header-files) 4. [Breaking of long lines](https://git.sr.ht/~sircmpwn/cstyle#splitting-long-lines) The reasons for specifying a style is not that we enjoy creating rules, but because it makes reading/maintaining the code and spotting problems much easier. If you are new to this style and want to get going quickly, either just imitate the style around you, or read the summary below and use `./scripts/check` to run some formatting checks. ## Linux Kernel Style Basics The preferred line length limit is 80 columns, although this is a bit soft. Tabs are 8 columns wide. Indentation with spaces is not used. Opening braces go on the same line, except for function definitions. Put `*` with the identifier when defining pointers, for example `char *foo` Spaces are placed around binary operators but not unary, like this: ``` int x = y * (2 + z); foo(--a, -b); ``` `sizeof(*foo)` is preferred over `sizeof(struct foo)` Use `if (!p)` instead of `if (p == 0)` or `if (p == NULL)` Comments are written as follows: ``` /* This is a single-line comment */ /* * This is a multi-line comment which is much much much much much much * longer. */ ``` When documenting functions in header files we use the [kernel-doc format]: ``` /** * function_name() - Brief description of function. * @arg1: Describe the first argument. * @arg2: Describe the second argument. * One can provide multiple line descriptions * for arguments. * * A longer description, with more discussion of the function function_name() * that might be useful to those using or modifying it. Begins with an * empty comment line, and may include additional embedded empty * comment lines. * * The longer description may have multiple paragraphs. * * Return: Describe the return value of function_name. * * The return value description can also have multiple paragraphs, and should * be placed at the end of the comment block. */ ``` ## Devault Deviations Functions are defined as below with `name` on a new line: ``` return type name(parameters...) { body } ``` Braces are mandatory even for one-line statements. ``` if (cond) { ... } ``` `#include` statements at the top of the file are organized by locality (`<>` first, then `""`), then alphabetized. When breaking a statement onto several lines, indent the subsequent lines once. If the statement declares a `{}` block, indent twice instead. Also, place operators (for example `&&`) on the next line. ``` if (seat->pressed.surface && ctx->surface != seat->pressed.surface && !update_pressed_surface(seat, ctx) && !seat->drag_icon) { if (cursor_has_moved) { process_cursor_motion_out_of_surface(server, time_msec); } return; } ``` ## Labwc Specifics ### API We have a very small, modest API and encourage you to use it. 1. `znew()` - as a shorthand for calloc(1, sizeof()) with type checking [common/mem.h] 2. `zfree()` to zero after free - [common/mem.h] 3. `wl_list_append()` to add elements at end of lists - [common/list.h] 4. `wl_array_len()` to get number of elements in a `wl_array` [common/array.h] 5. `ARRAY_SIZE()` to get number of elements in visible array [common/macros.h] [common/mem.h]: https://github.com/labwc/labwc/blob/master/include/common/mem.h [common/list.h]: https://github.com/labwc/labwc/blob/master/include/common/list.h [common/array.h]: https://github.com/labwc/labwc/blob/master/include/common/array.h [common/macros.h]: https://github.com/labwc/labwc/blob/master/include/common/macros.h ### The Use of glib We try to keep the use of glib pretty minimal for the following reasons: - The use of glib has been known to make AddressSanitiser diagnose false positives and negatives. - Log messages coming from glib functions look inconsistent. - The use of glib functions, naming-conventions and iterators in a code base that is predominantly ANSI C creates a clash which makes readability and maintainability harder. - Mixing gmalloc()/malloc() and respective free()s can create problems with memory pools [^1] Having said that, with our use of cairo and pango we depend on glib-2.0 anyway so linking with it and making use of some of its helper functions comes for free and can keep the code simpler. For example, if we were going to carry out extensive string manipulation, GString and utf8 helpers would be okay. Some functions such as `g_utf8_casefold()` would be pretty hard to write from scratch and are fine to use. Having said that, labwc does not do much string-mangling. The following functions are used today and are deemed acceptable by the core devs: - `g_shell_parse_argv()` - `g_strsplit()` - `g_pattern_match_simple()` When using these types of functions it is often desirable to support with some glib code, which is okay provided it is kept local and self-contained. See example from `src/theme.c`: ``` static bool match(const gchar *pattern, const gchar *string) { GString *p = g_string_new(pattern); g_string_ascii_down(p); bool ret = (bool)g_pattern_match_simple(p->str, string); g_string_free(p, true); return ret; } ``` ### The use of GNU extensions We avoid [GNU C extensions] because we want to fit into the eco-system (wayland and wlroots) we live in. We do use `__typeof__` which strictly speaking is a GNU C extension (`typeof`) but through the use of `__` is supported by gcc and clang without defining `_GNU_SOURCE`. The justification for this is that Wayland uses it, for example in the [`wl_container_of()`] macro which is needed in `wl_list*` and it does provide pretty big benefits in terms of type safety. We compile with `-std=c11` because that's what 'wlroots' uses and we do not want to increase the entry-level for OSs without good reason (and currently we can't think of one). ### Naming Conventions There are three types of coordinate systems: surface, output and layout - for which the variables (sx, sy), (ox, oy) and (lx, ly) are used respectively in line with wlroots. With the introduction of the scene-graph API, some wlroots functions also use node coordinates (nx, ny) but we prefer (sx, sy) where possible. We do not worry about namespace issues too much and we try to not make the code a pain to use just to uniquify names. If we were writing a library we would prefix public functions and structs with `lab_`, but we are not. We do however prefix public function names with the filename of the translation unit. For example, public functions in `view.c` begin with `view_`. We do start enums with `LAB_` We use the prefix `handle_` for signal-handler-functions in order to be consistent with sway and rootston. For example `view->request_resize.notify = handle_request_resize` # Commit Messages The log messages that explain changes are just as important as the changes themselves. Try to describe the 'why' to help future developers. Write [commit messages] like so, keeping the top line to this sort of syntax: ``` cursor: add special feature ``` This first line should: - Be a short description - In most cases be prefixed with "area: " where area refers to a filename or identifier for the general area of the code being modified. - Not capitalize the first word following the "area: " prefix, unless it's a name, acronym or similar. - Skip the full stop And please wrap the commit message at max 74 characters, otherwise `git log` and similar look so weird. URLs and other references are exempt. # Submitting patches Base both bugfixes and new features on `master`. # Native Language Support Translators can add their `MY_LOCALE.po` files to the `po` directory based on `po/labwc.pot` and issue a pull request. To do this they can generate their `MY_LOCALE.po` file in a few steps: 1. Edit the `po/LINGUAS` file to add their locale name by adding a space to the end of the field and typing the locale code. 2. Copy the po/labwc.pot to po/MY_LOCALE.po 3. Edit the newly generated MY_LOCALE.po file with some of their contact and locale details in the header of the file then add the translation strings under each English string. [See this tutorial for further guidance](https://www.labri.fr/perso/fleury/posts/programming/a-quick-gettext-tutorial.html) Code contributors may need to update relevant files if their additions affect UI elements (at the moment only `src/menu/menu.c`). In this case the `po/labwc.pot` file needs to be updated so that translators can update their translations. Remember, many translators are _not_ coders! The process is fairly trivial however does involve some manual steps. 1. After adding and testing your code additions to satisfaction, backup `po/labwc.pot`. You need the custom header from that file for the newly generated .pot file in the next step. 2. From the root of the repository run this: ``` xgettext --keyword=_ --language=C --add-comments -o po/labwc.pot src/menu/menu.c ``` This generates a new pot file at `po/labwc.pot` 3. Copy the header from the original `labwc.pot` to the new one, check for sanity and commit. # Upversion It is generally only the lead-maintainer who will upversion, but in order not to forget any key step or in case someone else needs to do it, here follow the steps to be taken: 1. If appropriate, update `revision` in `subprojects/wlroots.wrap` and run `git commit -m 'wlroots.wrap: use A.B.C'` 2. Update `NEWS.md` with the release details and run `git commit -m 'NEWS.md: update notes for X.Y.Z'` Note: If new dependencies are needed, make this clear. 3. In `meson.build` update the version and (if required) the wlroots dependency version. Then run `git commit -m 'build: bump version to X.Y.Z'` 4. Run `git tag -a X.Y.Z`. The first line of the commit message should be "labwc X.Y.Z" and the body should be the `NEWS.md` additions removing hash characters (#) from the headings as these will otherwise be ignored by git. 5. On github, create a 'Release' as some distros use this as a trigger. Set it as 'latest release'. [scope document]: https://github.com/labwc/labwc-scope#readme [`wlroots/docs/env_vars.md`]: https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/master/docs/env_vars.md [wlhax]: https://git.sr.ht/~kennylevinsen/wlhax [drm_info]: https://github.com/ascent12/drm_info [Drew Devault's preferred coding style]: https://git.sr.ht/~sircmpwn/cstyle [Linux kernel coding style]: https://www.kernel.org/doc/html/v4.10/process/coding-style.html [kernel-doc format]: https://docs.kernel.org/doc-guide/kernel-doc.html [commit messages]: https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/master/CONTRIBUTING.md#commit-messages [GNU C extensions]: https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html [`wl_container_of()`]: https://github.com/wayland-project/wayland/blob/985ab55d59db45ea62795c76dff5949343e86b2f/src/wayland-util.h#L409 [^1]: The reference documentation for glib notes that: "It's important to match g_malloc() with g_free(), plain malloc() with free(), and (if you're using C++) new with delete and new[] with delete[]. Otherwise bad things can happen, since these allocators may use different memory pools (and new/delete call constructors and destructors)." See: https://developer.gimp.org/api/2.0/glib/glib-Memory-Allocation.html labwc-0.7.1/LICENSE000066400000000000000000000432541457044301200136240ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE 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. 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 convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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. labwc-0.7.1/NEWS.md000066400000000000000000001365161457044301200137210ustar00rootroot00000000000000# Introduction This file contains significant user-visible changes for each version. For full changelog, use `git log`. The format is based on [Keep a Changelog] # Summary of Releases | Date | All Changes | wlroots version | lines-of-code | |------------|---------------|-----------------|---------------| | 2024-03-01 | [0.7.1] | 0.17.1 | 18624 | | 2023-12-22 | [0.7.0] | 0.17.1 | 16576 | | 2023-11-25 | [0.6.6] | 0.16.2 | 15796 | | 2023-09-23 | [0.6.5] | 0.16.2 | 14809 | | 2023-07-14 | [0.6.4] | 0.16.2 | 13675 | | 2023-05-08 | [0.6.3] | 0.16.2 | 13050 | | 2023-03-20 | [0.6.2] | 0.16.2 | 12157 | | 2023-01-29 | [0.6.1] | 0.16.1 | 11828 | | 2022-11-17 | [0.6.0] | 0.16.0 | 10830 | | 2022-07-15 | [0.5.3] | 0.15.1 | 9216 | | 2022-05-17 | [0.5.2] | 0.15.1 | 8829 | | 2022-04-08 | [0.5.1] | 0.15.1 | 8829 | | 2022-02-18 | [0.5.0] | 0.15.1 | 8766 | | 2021-12-31 | [0.4.0] | 0.15.0 | 8159 | | 2021-06-28 | [0.3.0] | 0.14.0 | 5051 | | 2021-04-15 | [0.2.0] | 0.13.0 | 5011 | | 2021-03-05 | [0.1.0] | 0.12.0 | 4627 | ## [0.7.1] ### Added - Support libinput option sendEventsMode to allow enabling/disabling devices. Co-Authored-By: @Sachin-Bhat ```xml yes|no|disabledOnExternalMouse ``` - Add click method libinput option. Written-by: @datMaffin ```xml none|buttonAreas|clickfinger ``` - Add `data/labwc.svg` & `data/labwc-symbolic.svg`, and specify icon name in labwc.desktop to enable Display Managers to show an icons for labwc. - Expose output configuration test to clients. For example, this enables `wlr-randr --dryrun` - Add window-edge resistance for interactive moves/resizes and support negative strengths to indicate attractive snapping. Written-by: @ahesford ```xml -20 -20 ``` - Set keyboard layout on reconfigure. Issue #1407 - Reset keyboard-layout group (index) for each window on reconfigure if the keymap has changed. - Support merging multiple config files with the --merge-config command line option. Issue #1406 - Add config option to map touch events to a named output (display). Optionally, make this only apply to specific named devices. Written-by: @jp7677 ```xml ``` - Add tablet support including: - Mapping of tablet to output (display) - Emulation of cursor movement and button press/release - Configuration of area and rotation Written-by: @jp7677 @Consolatis ```xml ``` - Add tearing support. #1390. Written-by: @Ph42oN @ahesford - Add configuration support for mouse buttons `Side`, `Extra`, `Forward`, `Back` and `Task`. Written-by: @jp7677 - config: allow `` without category attribute to define a `default` profile because it is more user-friendly and intuitive. - Add a configuration option to enable adaptive sync only when an application is in fullscreen mode. Written-by: @Ph42oN - Add `touchpad` libinput device type to increase configuration flexibility, for example allowing `naturalScroll` on touchpads, but not on regular pointer devices such as mice. Written-by: @jmbaur - Add actions: - `AutoPlace` (by @ahesford) - `MoveToOutput`, `FitToOutput` (by @jp7677) - `Shade`, `Unshade`, `ToggleShade` (by @ahesford @Consolatis) - Add config option `` with supported values `center`, `cursor` and `automatic`. The latter minimizes overlap with other windows already on screen and is similar to Openbox's smart window placement. The placement policies honour ``. Written-by: @ahesford #1312 ```xml center|automatic|cursor ``` ### Fixed - Delay popup-unconstrain until after first commit in response to a changed wlroots 0.17 interface and to get rid of the error message below. Issue #1372 [types/xdg_shell/wlr_xdg_surface.c:169] A configure is scheduled for an uninitialized xdg_surface - Notify clients about configuration errors when changing output settings. Issue #1528. - Fix output configuration bug causing compositor crash when refresh rate is zero. Issue #1458 - Fix disappearing cursor bug on view destruction. Issue #1393 - Use used specified config-file (using -c command line option) on reconfigure. - Assign outputs to new views on surface creation instead of mapping, and notify the client of the preferred output scale when doing so. This fixes an issue with foot: https://codeberg.org/dnkl/foot/issues/1579 Written-by: @ahesford - Cancel key repeat on vt change to fix crash on VT change on FreeBSD. Issue #1424 - Fix crash when a minimized fullscreen window closes. Written-by: @bi4k8 - Execute menu actions after closing menus so that menu entries can issue `wtype` commands to the surface with keyboard-focus. Issue #1366 - Try to honor original window geometry on layout changes. - Fix virtual keyboard bug experienced with `wlrctl keyboard type xyz`. Do not process virtual keyboard keycodes (just the keysyms). Issue #1367 - Sync xdg-shell client `view->pending` when applying geometry to fix issue caused by applications choosing not respond to pending resize requests either by ignoring them or substituting alternative sizes (for example, when mpv constrains resizes to keep its aspect ratio fixed). Written-by: @ahesford ### Changed - Make `MoveToCursor` honour ``. Issue #1494 - Add `Roll Up/Down` client-menu entry for `ToggleShade` - When a Wayland-native window is snapped to a screen edges or user-defined region, labwc will notify the application that it is "tiled", allowing the application to better adapt its rendering to constrained layouts. Windows with client-side decorations may respond to these notices by squaring off corners and, in some cases, disabling resize abilities. This can be disabled with: ```xml never ``` or limited to only edge-snapped or only region-snapped windows. See the labwc-config(5) manual page for more information. - When a window is dragged from a snapped position (either a screen edge or a user-defined region), the snapped state is now discarded as soon as the dragging begins. This means that dragging from a snapped position to a maximized state (with the `topMaximize` option enabled) and then un-maxmimizing the window will restore the window to its size and position *before* it was snapped. In previous releases, un-maximizing would restore the window to its snapped state. To preserve the snapped state of a window when maximized, use the Maximize window button or the `ToggleMaximize` action. - The new windowEdgeStrength setting makes windows resist interactive moves and resizes across the edges of other windows. This can be disabled with: ```xml 0 ``` - Run menu actions on button release intead of press. - Constrain window size to that of usable area when an application is started. Issue #1399 - Support showing the full `app_id` in the window switcher. Users with a custom `windowSwitcher` configuration should use the `trimmed_identifier` field label to preserve existing behavior; the `identifier` field now refers to the full `app_id`. Consult the labwc-config(5) manual page for more details. Issue #1309 ## [0.7.0] - 2023-12-22 The main effort in this release has gone into porting labwc to wlroots 0.17 and tidying up regressions. Nonetheless, it contains a significant number of additions and fixes as described below. Should bug fixes be required against `0.6.6` (built with wlroots `0.16`), a `0.6` branch will be created. ### Added - Support titlebar hover icons. Written-by: @spl237 - Add theme options osd.workspace-switcher.boxes.{width,height} Written-by: @kyak - Add actions `VirtualOutputAdd` and `VirtualOutputRemove` to control virtual outputs. Written-by: @kyak (#1287) - Teach MoveToEdge to move windows to adjacent outputs. Written-by: @ahesford - Implement ``. Written-by: @ludg1e (#1292) - Implement cursor-shape-v1 protocol to allow Wayland clients to request a buffer for a cursor shape from a compositor. Written-by: @heroin-moose - Implement fractional-scale-v1 protocol to allow Wayland clients to properly scale on outputs with fractional scale factor. Written-by: @heroin-moose - Add ResizeTo action (#1261) - Allow going backwards in window-switcher OSD by using arrow-up or arrow-left. Written-by: @jp7677 - Add `ToggleOmnipresent` action and add an "Always on Visible Workspace" entry for it in the client-menu under the Workspaces submenu. Written-by: @bnason - Account for space taken up by XWayland clients with `_NET_WM_STRUT_PARTIAL` property in the `usable_area` calculation. This increases inter-operability with X11 desktop componenets. - Set XWayland's `_NET_WORKAREA` property based on usable area. XWayland clients use the `_NET_WORKAREA` root window property to determine how much of the screen is not covered by panels/docks. The property is used for example by Qt to determine areas of the screen that popup menus should not overlap. ### Fixed - Fix xwayland.c null pointer dereference causing crash with JetBrains CLion. (#1352) - Fix issue with XWayland surfaces completely offscreen not generating commit events and therefore preventing them from moving onscreen. - Do not de-active windows when layer-shell client takes keyboard focus, to fix sfwbar minimize action. (#1342) - Move layer-shell popups from the background layer to the top layer to render them above normal windows. Previously this was only done for the bottom layer. In support of Raspberry Pi's `pcmanfm --desktop`. (#1293) - Calculate `usable_area` before positioning clients to ensure it is correct before non exclusive-zone layer-shell clients are positioned or resized. (#1285) - Prevent overriding XWayland maximized/fullscreen/tiled geometry to fix an issue where some XWayland views (example: xfce4-terminal) do not end up with exactly the correct geometry when tiled. ### Changed - Treat XWayland panel windows as if fixedPosition rule is set - Use the GTK3 notebook header color as the default active title color (small change from `#dddad6` to `#e1dedb`). Written-by: @dimkr ## [0.6.6] - 2023-11-25 We do not normally call out contributions by core devs in the changelog, but a special thanks goes to @jlindgren90 in this release for lots of work relating to surface focus and keyboard issues, amongst others. ### Added - Add `fixedPosition` window-rule property to avoid re-positioning windows on reserved-output-space changes (determined by ** settings or exclusive layer-shell clients) and to disallow interactive move or resize, for example by alt+press. - Add `Unfocus` action to enable unfocusing windows on desktop click. Issue: #1230 - Add config option `` to use per-window keyboard layout. Issue #1076 - Support separate horizontal and vertical maximize by adding a `direction` option to actions Maximize and ToggleMaximize. - Add actions GrowToEdge and ShrinkToEdge. Written-by: @digint - Add `snapWindows` option to MoveToEdge action. Written-by: @digint - Add MoveToCursor action. Written-by: @Arnaudv6 - Add config option `` to enable Num Lock on startup. - Support Meta (M), Hyper (H), Mod1, Mod3, Mod4 and Mod5 modifiers in keybind definitions. Fixes: #1061 - Add themerc 'titlebar.height' option. Written-by: @mozlima - Add If and ForEach actions. Written-by: @consus - Allow referencing the current workspace in actions, for example: ```xml ``` ### Fixed - Do not reset XWayland window SSD on unminimize - Keep XWayland stacking order in sync when switching workspaces - Update top-layer visiblity on workspace-switch in order to show top-layer layer-shell clients correctly when there is a window in fullscreen mode on another workspace. Issues: #1040 #1158 - Make interactive window snapping with mouse more intuitive in multi-output setups. Written-by: @tokyo4j - Try to handle missing `set_window_geometry` with Qt apps which occasionally fail to call `set_window_geometry` after a configure request, but correctly update the actual surface extent. Issue: #1194 - Update XWayland stacking order when moving a window to the front/back. - Prevent switching workspaces for always-on-bottom windows. Fixes: #1170 - Fix invisible cursor after wlopm --off && wlopm --on. - When a session is locked using 'session-lock' protocol, reconfigure for output layout changes to avoid incorrect positioning - Account for window base size in resize indicator so that the displayed size exactly matches the terminal grid, for example 80x25. - The following focus related issues: - Allow re-focusing xwayland-unmanaged surfaces in response to pointer action (click or movement if focus-follow-mouse is enabled). This enables clients such as dmenu, rofi and jgmenu to regain keyboard-focus if it was lost to another client. - Fix code paths which could lead to a lock-screen losing focus, making the session impossible to unlock or another surface to gain focus thus breaching the session lock. - Only focus topmost view on unmap if unmapped view was focused. - Fix `xwayland_surface->data` bug relating to unmanaged surfaces. - Fix layer subsurface focus bug to make waybar's minimize-raise work. Fixes: #1131 - Ignore focus change to unmanaged surface belonging to same PID to fix an issue with menus immediately closing in some X11 apps. - Avoid focusing xwayland views that do not want focus using the ICCCM "Globally Active" input model. - Allow re-focus between "globally active" XWayland views of the same PID. - Assume that views that want decorations also want focus - The following keyboard and keybind related issues: - Send pressed keys correctly when focusing new surface. - Refactor handling of pressed/bound keys to send (to client) the release events for any pressed key that was not part of a keybind, typically because an unrelated non-modifier key was pressed before and held during a keybind invocation. Fixes #1091 #1245 - Fix keyboard release event bug after session lock. Fixes: #1114 - Raise xdg and xwayland sub-views correctly relative to other sub-views, by letting the relative stacking order between them change. - Honor initially maximized requests for XWayland views via `_NET_WM_STATE`. - For initially maximized XWayland views, set the stored natural geometry to be output-centered. - Fix regions rounding error sometimes resulting in incorrect gaps between regions. ### Changed - Move floating windows in response to changes in reserved output space (determined by ** settings or exclusive layer-shell clients such as panels). Users with window-rules for panels and/or desktops should add the `fixedPosition` property to avoid regression. Issue: #1235 - Restore `SIGPIPE` default handler before exec. Fixes: #1209 - With the introduction of directional Maximize, right-click on the maximize button now toggles horizontal maximize, while middle-click toggles vertical maximize. - Make MoveToEdge snap to the next window edge by default rather than just the screen edge. - Comment out variables in `docs/environment` to avoid users using the file without editing it and ending up with unwanted settings. Fixes: #1011 - Set `_JAVA_AWT_WM_NONREPARENTING=1` unless already set. - This release has seen significant refactoring and minor improvements with respect to window and surface focus (particular thanks to @jlindgren). This work has helped uncover and fix some hard-to-find bugs. We don't believe that there are any regressions, but can't say for sure. - Set Num Lock to enabled by default on start up - Allow switching VT when locked - Use `fnmatch()` for pattern matching instead of `g_pattern_match_simple()` because it is a POSIX-compliant function which has a glob(7) manual page for reference. - Title context is used instead of TitleBar for the default client-menu on click. This means that if a button is right-clicked, the client-menu will not appear anymore. - Always switch to the workspace containing the view being focused. ## [0.6.5] - 2023-09-23 ### Added - Support png and svg titlebar buttons - Support on/off boolean configuration values (in addition to true, false, yes and no). Written-by: @redtide - keybinds - Allow non-english based keybinds - Make keybind agnostic to keyboard layout. Fixes #1069 - Add optional layoutDependent argument to only trigger if the configured key exists in the currently active keyboard layout. `` - Fallback on raw keysyms (as if there were no pressed modifier) for bindings which do not match against translated keysyms. This allows users to define keybinds such as "S-1" rather than "S-exclam". It also supports "W-S-Tab". Fixes #163 #365 #992 - window-rules: add ignoreFocusRequest property - config: support libinput `` and ``. Written-by: @tokyo4j - Handle keyboard input for menus. Fixes #1058 - Server-side decoration: - Make corners square on maximize - Disable border on maximize. Fixes #1044 - Add window resize indicator and associated `` config option - Add `` to give `ToggleDecoration` three states: (1) disable titlebar; (2) disable whole SSD; and (3) enables whole SSD When the keepBorder action is disabled, the old two-state behavior is restored. Fixes #813 - Minimize whole window hierarchy from top to bottom regardless of which window requested the minimize. For example, if an 'About' or 'Open File' dialog is minimized, its toplevel is minimized also, and vice versa. - Move window's stacking order with dialogs so that other window cannot be positioned between them. Also position xdg popups above their parent windows.This is consistent with Gtk3 and Qt5. Fixes #823 ### Fixed - Clarify in labwc-config(5) that keyboard modifiers can be used for mousebinds. Fixes #1075 - Ensure interactive move/resize ends correctly for CSD clients. Fixes #1053 - Fix invalid value in `` falling back as "flat" - Fix touch bug to avoid jumping when a touch point moves off of a surface Written-by: @bi4k8 - Prevent crash with theme setting `osd.window-switcher.width: 0`. Fixes #1050 - Cancel cursor popup grab on mouse-press outside client itself, for example on any part of the server side decoration or the desktop. Fixes #949 - Prevent cursor press on layer-subsurface from cancelling popup grab Fixes #1030 - xwayland: fix client request-unmap bug relating to foreign-toplevel handle - xwayland: fix race condition resulting in map view without surface - Limit SSD corner radius to the height of the titlebar - Fix rounded-corner bug producing weird artefacts when very large border thickness is used. Fixes #988 - Ensure `string_prop()` handlers deal with destroying views. Fixes #1082 - Fix SSD thickness calculation bug relating to titlebar. Fixes #1083 - common/buf.c: - Do not expand `$()` in `buf_expand_shell_variables()` - Do not use memcpy for overlapping regions ### Changed - Use `identifier` for window-switcher field rather than `app_id` to be consistent with window rules. ```xml ``` - Do not expand environment variables in `Exec` action `` argument (but still resolve tilde). ## [0.6.4] - 2023-07-14 ### Added - Add support for `ext_idle_notify` protocol. - Window-switcher: #879 #969 - Set item-height based on font-heigth - Add theme option: - osd.window-switcher.width - osd.window-switcher.padding - osd.window-switcher.item.padding.x - osd.window-switcher.item.padding.y - osd.window-switcher.item.active.border.width - Actions: - Add `MoveTo`, `ToggleAlwaysOnBottom`. - Add `MoveRelative`, `ResizeRelative`. Written-by: @Ph42oN - Add option `wrap` for `GoToDesktop` and `SendToDesktop` - Add config options `` to override usable area for panels/docks which do not support layer-shell protocol. - Add `number` attribute to `` to simplify configuration. Written-by: @Sachin-Bhat - Window rules: #787 #933 - Add properties: `skipTaskbar` and `skipWindowSwitcher` - Add criteria `title` and `matchOnce` ### Fixed - Support XML CDATA for `` in order to provide backward compatibility with obmenu-generator #972 - Call `wlr_xwayland_surface_set_minimized()` on xwayland window (un)minimize to fix blank surface after minimizing fullscreen Steam windows. #958 - Fix focus at the end of drag-and-drop operation respecting `` if enabled. #939 #976 - Render xdg-popups above always-on-top layer. - Do not render On-Screen-Displays on disabled outputs. #914 ### Changed - Make `ToggleKeybinds` applicable only to the window that has keyboard focus when the action is executed. ## [0.6.3] - 2023-05-08 ### Added - Add `focus.followMouseRequiresMovement` to allow a stricter focus-what-is-under-the-cursor configuration. #862 - Support window-rules including properties and on-first-map actions. Any actions in labwc-actions(5) can be used. Only 'serverDecoration' has been added as a property so far. Example config: ```xml ``` - Support configuration of window switcher field definitions. Issues #852 #855 #879 ```xml ``` - Add actions: - 'Lower' Written-by: @jech - 'Maximize' - Support ext-session-lock protocol. Helped-by: @heroin-moose - Handle XWayland unmanaged surface requests for 'activate' and 'override-redirect'. Fixes: #874 - Add config support for scroll-factor. Fixes #846 - Support 'follow' attribute for SendToDesktop action. Fixes #841 ### Fixed - Fix adaptive sync configuration. Helped-by: @heroin-moose #642 - Ignore SIGPIPE to fix crash caused by Wayland clients requesting X11 clipboard but closing the read-fd before/while the X11 clipboard is being written to. Fixes #890 - Ellipsize on-screen-display text - Validate PID before activating XWayland unmanaged surfaces to check that the surface trying to grab focus is actually a child of the topmost mapped window. - Respect cursor constraint hints when cursor movement occurs after unlocking the pointer. Written-by: @FuzzyQuills Fixes #872 - Fix invisible cursor on startup and output loss/restore. Reported-by: @Flrian Fixes #820 - Fix decoration protocol implementation - Respect earlier decoration negotiation results via the xdg-decoration protocol. Previously setting `` to `client` would cause applications which prefer server side decorations to not have any decorations at all. Fixes #297 #831 - Handle results of kde-server-decoration negotiations - Fix `` cursor glitches and issues with focus switching via Alt-Tab. Issue #830 #849 ### Changed - Make `` a toplevel element rather than a child of `` - Default to follow="true" for SendToDesktop action as per Openbox 3.6 specification. ## [0.6.2] - 2023-03-20 This release contains refactoring and simplification relating to view-output association and xdg/xwayland configure/map events. Unless otherwise stated all contributions are by the core-devs (@Consolatis, @jlindgren90 and @johanmalm). ### Added - Add config option `` to hide windowSwitcher (also known as On Screen Display) when switching windows. - Enable config option `` by default. - Add ToggleKeybinds action to disable/enable all keybinds (other than ToggleKeybinds itself). This can be used to better control Virtual Machines, VNC clients, nested compositors or similar. (#738 and #810) - Implement cursor constraints (Written-by: @Ph42oN) and lock confinement. - Support xdg-activation protocol to allow applications to activate themselves (e.g. raise to the top and get keyboard focus) if they provide a valid `xdg_activation token`. - Allow clearing key/mouse bindings by using the 'None' action. This enables the use of `` and then selectively removing keybinds. For example the following could be used to allow using A-Left/Right with Firefox. ```xml ``` ### Fixed - Prevent cursor based region-snapping when starting a move with Alt-Left. If region-snapping is wanted in this situation, just press the modifier again. (#761) - Prevent rare crash due to layering move/resize/menu operations. (#817) - Fully reset config default values on Reconfigure if not set in config file. - Fix visual glitch when resizing xfce4-terminal from left edge caused by windows not accepting their request size exactly. - Fix issue with havoc not having a valid size on map. - Save `natural_geometry.x/y` with initially maximized xdg-view to fix an issue where, if Thunar was started maximized, it would un-maximize to the top-left corner rather than the center. ### Changed - Change config option `` to ``. Use `` instead of: ```xml yes yes yes ``` ## [0.6.1] - 2023-01-29 As usual, this release contains lots of refactoring and bug fixes with particular thanks going to @Consolatis, @jlindgren90, @bi4k8, @Flrian and @Joshua-Ashton. ### Added - Add `` config option allowing the definition of regions to which windows can be snapped by keeping a keyboard modifier pressed while dragging or by using the SnapToRegion action. Written-by: @Consolatis - Add `` action to send SIGTERM to a client process. Written-by: @bi4k8 - Add config option `` to support flicker free boot (issue #724). Written-by: @Consolatis - Enable single-pixel-buffer-v1 - Support theme setting override by reading `/themerc-override` - Scale down SSD button icons if necessary to allow using larger ones for high and mixed DPI usecases. Issue #609. Written-by: @Consolatis - Handle client request for layer-change - Support setting color of client menu buttons. Written-by: @Flrian - Dynamically adjust menu width based on widest item. Written-by: @Consolatis - Add theme options menu.width.{min,max} and menu.items.padding.{x,y} ### Fixed - Scale cursor correctly at startup and on output scale-change. Written-by: @bi4k8 - Release layer tree when releasing output. Written-by: @yuanye - Ensure natural geometry is restored when no outputs available. Reported-by: @Flrian - Fixes memory leaks and prevent crashes associated with missing outputs Thanks to @Consolatis. - Update translations for new client menus strings. Thanks-to: @01micko and @ersen0 - On un-fullscreen, restore SSD before applying previous geometry to avoid rendering offscreen in some instances. Written-by: @Consolatis - Allow snapping to the same edge. Thanks-to: @Consolatis and @Flrian - Send enter event when new layer surface appears under pointer. Issue #667 - Prevent re-focus for always-on-top views when switching workspaces. Written-by: @Consolatis - Make sure a default libinput category always exists to avoid devices not being configured is some insances. Written-by: @jlindgren90 - Update cursor if it is within the OSD area when OSD appears/disappears. Written-by: @bi4k8 - Provide generic parsing of XML action arguments to enable the use of the `direction` argument in menu entries. Written-by: @Consolatis - Fix SSD margin computation. Written-by: @jlindgren90 - Hide SSD decorations for fullscreen views to avoid rendering them on adjacent outputs. Written-by: @jlindgren90 - Set inactive window button color correctly. Written-by: @ScarcelyThere - Fix positioning of initially-maximized XWayland views. Written-by: @jlindgren90 - Check for modifiers when merging mousebinds. Issue #630. - Handle layer-shell exclusive and on-demand keyboard-interactivity correctly, and thus support xfce4-panel better. Issues #704 and #725. - Only overwrite wlroots's automatic layout when necessary. ### Changed - Filter out `wp_drm_lease_device` from Xwayland to avoid Electron apps such as VS Code and Discord lagging over time. Issue #553. Written-by: @Joshua-Ashton - Do not switch output on SnapToEdge if view is maximized. Written-by: @Flrian ## [0.6.0] - 2022-11-17 This release contains significant refactoring to use the wlroots scene-graph API. This touches many areas of the code, particularly rendering, server-side-decoration, the layer-shell implementation and the menu. Many thanks to @Consolatis for doing most of the heavy lifting with this. Noteworthy, related changes include: - The use of a buffer implementation instead of using `wlr_texture`. It handles both images and fonts, and scales according to output scale. - The use of node-descriptors to assign roles to `wlr_scene_nodes` in order to simplify the code. - Improving the "Debug" action to print scene-graph trees A large number of bugs and regressions have been fixed following the re-factoring, too many to list here, but we are grateful to all who have reported, tested and fixed issues. Particular mentions go to @bi4k8, @flrian, @heroin-moose, @jlindgren90, @Joshua-Ashton, @01micko and @skerit ### Added - Set environment variable `LABWC_PID` to the pid of the compositor so that SIGHUP and SIGTERM can be sent to specific instances. - Add command line options --exit and --reconfigure. - Support setting keyboard repeat and delay at runtime. Written-by: @bi4k8 - Add support for mouse-wheel bindings. Set default bindings to switch workspaces when scrolling on the desktop. Written-by: @Arnaudv6 - Implement key repeat for keybindings. Written-by: @jlindgren90 - Support smooth scroll and horizontal scroll. Written-by: @bi4k8 - Implement virtual keyboard and pointer protocols, enabling the use of clients such as wtype and wayvnc. Written-by: @Joshua-Ashton - Add github workflow CI including Debian, FreeBSD, Arch and Void, including a build without xwayland. - Support keybind "None" action to clear other actions for a particular keybind context. Written-by: @jlindgren90 - Support font slant (itliacs) and weight (bold). Written-by: @jlindgren90 - Support `` mousebinds to load default mousebinds and provide a way to keep config files simpler whilst allowing user specific binds. Issue #416. Written-by: @Consolatis - Add config option `` to enable/disable preview of outlines. Written-by: @Flrian - Render submenu arrows - Allow highest level menu definitions - typically used for root-menu and client-menu - to be defined without label attritube, for example like this: `...`. Issue #472 - Allow xdg-desktop-portal-wlr to work out of the box by initializing dbus and systemd activation environment. This enables for example OBS Studio to work with no user configuration. If systemd or dbus is not available the environment update will fail gracefully. PR #461 Written-by: @Joshua-Ashton and @Consolatis - Workspaces. Written-by: @Consolatis - presentation-time protocol - Native language support for client-menus. Written-by: @01micko - Touch support. Written-by: @bi4k8 - `drm_lease_v1` for VR to work and leasing of desktop displays. Written-by: Joshua Ashton - ToggleAlwaysOnTop action. Written-by: @Consolatis - Command line option -C to specify config directory - Theme options osd.border.color and osd.border.width. Written-by: @Consolatis - Menu `` and associated theme options: menu.separator.width, menu.separator.padding.width, menu.separator.padding.height and menu.separator.color - Adjust maximized and tiled windows according to `usable_area` taking into account exclusive layer-shell clients. Written-by: @Consolatis - Restore natural geometry when moving tiled/maximized window Fixes #391. Written-by: @Consolatis - Improve action implementation to take a list of arguments in preparation for actions with multiple arguments. Written-by: @Consolatis ### Fixed - Remove unwanted gap when initially (on map) positioning windows larger than output usable area (issue #403). - Prevent setting cursor icon on drag. Written-by: @Consolatis (issue #549) - Fix bugs relating to sending matching pairs of press and release keycodes to clients when using keybinds. Also fix related key-repeat bug. (Issue #510) - Fix `wlr_output_cursor` initialization bug on new output. Written-by: @jlindgren90 - Show correct cursor for resize action triggered by keybind. Written-by: @jlindgren - Fix bug which manifest itself when keeping button pressed in GTK3 menu and firefox context menu. Written-by: @jlindgren90 - Enable tap be default on non-touch devices (which some laptop trackpads apparently are) - Handle missing cursor theme (issue #246). Written-by: @Consolatis - Fix various surface syncronization, stacking, positioning and focus issues, including those related to both xwayland, scroll/drag events and also #526 #483 - On first map, do not center xwayland views with explicitly specified position. Written-by: @jlindgren90 - Give keyboard focus back to topmost mapped view when unmapping topmost xwayland unmanaged surfaces, such as dmenu. Written-by: @Consolatis. - Fix mousebind ordering and replace earlier mousebinds by later ones Written-by: @Consolatis - Fix various bugs associated with destroying/disabling outputs, including issue #497 - Hide Alt-Tab switcher when canceling via Escape. @jlindgren90 - (Re)set seat when xwayland is ready (because wlroots reset the seat assigned to xwayland to NULL whenever Xwayland terminates). Issues #166 #444. Written-by: @Consolatis. Helped-by: @droc12345 - Increase File Descriptor (FD) limit to max because a compositor has to handle many: client connections, DMA-BUFs, `wl_data_device` pipes and so on. Fixes client freeze/crashes (swaywm/sway#6642). Written-by: @Joshua-Ashton - Fix crash when creating a cursor constraint and there is no currently focused view. - Gracefully handle dying client during interactive move. Written-by: @Consolatis - Dynamically adjust server-side-deccoration invisible resize areas based on `usable_area` to ensure that cursor events are sent to clients such as panels in preference to grabbing window edges. Fixes #265. Written-by: @Consolatis - Always position submenus inside output extents. Fixes #276 Written-by: @Consolatis - Do not crash when changing TTY. Written-by: @bi4k8 - Set wlroots.wrap to a specific commit rather than master because it enables labwc commits to be checked out and build without manually having to find the right wlroots commit if there are upstream breaking changes. - Increase accuracy of window center-alignment, taking into account `usable_area` and window decoration. Also, top/left align if window is bigger than usable area. - Handle view-destruction during alt-tab cycling. Written-by: @Joshua-Ashton - Survive all outputs being disabled - Check that double-clicks are on the same window. Written-by: yizixiao - Set xdg-shell window position before maximize on first map so that the unmaximized geometry is known when started in maximized mode. Fixes issue #305. Reported-by: @01micko - Support `` `` is a deprecated name for ``, but is supported for backward compatibility with old menu-generators. - Keep xwayland-shell SSD state on unmap/map cycle. Written-by: @Consolatis - Prevent segfault on missing direction arguments. Reported-by: @flrian - Fix keybind insertion order to restore intended behavior of keybinds set by ``. Written-by: @Consolatis - Ensure client-menu actions are always applied on window they belong to This fixes #380. Written-by: @Consolatis - Keep window margin in sync when toggling decorations. Written-by: @Consolatis - Fix handling of client-initiated configure requests. Written-by: @jlindgren90 - Always react to new output configuration. Reported-by @heroin-moose and Written-by: @Consolatis - Fix bug in environment variable expansion by allowing underscores to be part of the variable names. Issue #439 - Fix parsing bug of adaptiveSync setting and test for support ### Changed - src/config/rcxml.c: distinguish between no and unknown font places so that `` with no `place` attribute can be added after other font elements without over-writing their values. Written-by: @bi4k8 - theme: change window.label.text.justify default to center - Redefine the SSD "Title" context to cover the whole Titlebar area except the parts occupied by buttons. This allows "Drag" and "DoubleClick" actions to be de-coupled from buttons. As a result, "Drag" and "DoubleClick" actions previously defined against "TitleBar" should now come under the "Title" context, for example: `` - Remove default alt-escape keybind for Exit because too many people have exited the compositor by mistake trying to get out of alt-tab cycling or similar. ## [0.5.3] - 2022-07-15 ### Added - wlr-output-power-management protocol to enable clients such as wlopm Written-by: @bi4k8 ### Fixed - Call foreign-toplevel-destroy when unmapping xwayland surfaces because some xwayland clients leave unmapped child views around. Although `handle_destroy()` is not called for these, we have to call foreign-toplevel-destroy to avoid clients such as panels incorrecly showing them. - Handle xwayland `set_override_redirect` events to fix weird behaviour with gitk menus and rofi. - Re-focus parent surface on unmapping xwayland unmanaged surfaces Fixes #352 relating to JetBrains and Intellij focus issues Written-by: Jelle De Loecker - Do not segfault on missing drag icon. Written-by: @Consolatis - Fix windows irratically sticking to edges during move/resize. Fixes issues #331 and #309 ## [0.5.2] - 2022-05-17 This is a minor bugfix release mostly to ease packaging. ### Fixed - Properly use system provided wlroots. Written-by: @eli-schwartz ## [0.5.1] - 2022-04-08 ### Added - Honour size increments from `WM_SIZE_HINTS`, for example to allow xwayland terminal emulators to be resized to a width/height evenly divisible by the cell size. Written-by: @jlindgren90 - Implement cursor input for overlay popups. Written-by: @Consolatis ### Fixed - Do not raise xwayland windows when deactivating (issue #270). Written-by: @Consolatis - Restore drag mouse-bindings and proper double-click (issues #258 and #259). Written-by: @Consolatis - Implement cursor input for unmanaged xwayland surfaces outside their parent view. Without this menus extending outside the main application window do not receive mouse input. Written-by: @jlindgren90 - Allow dragging scrollbar or selecting text even when moving cursor outside of the window (issue #241). Written-by: @Consolatis - Fix positioning of xwayland views with multiple queued configure events. Written-by: @Consolatis - Force a pointer enter event on the surface below the cursor when cycling views (issue #162). Written-by: @Consolatis - Fix qt application crash on touchpad scroll (issue #225). Written-by: @Consolatis ## [0.5.0] - 2022-02-18 As usual, this release contains a bunch of fixes and improvements, of which the most notable feature-type changes are listed below. A big thank you to @ARDiDo, @Consolatis and @jlindgren90 for much of the hard work. ### Added - Render overlay layer popups to support sfwbar (issue #239) - Support HiDPI on-screen-display images for outputs with different scales - Reload environment variables on SIGHUP (issue #227) - Add client menu - Allow applications to start in fullscreen - Add config option `` to preview the contents of each view when cycling through them (for example using alt-tab). - Allow mouse movements to trigger SnapToEdge. When dragging a view, move the cursor outside an output to snap in that direction. - Unmaximize on Move - Support wlroots environment variable `WLR_{WL,X11}_OUTPUTS` for running in running nested in X11 or a wlroots compositor. - Support pointer gestures (pinch/swipe) - Adjust views to account for output layout changes ### Changed This release contains the following two breaking changes: - Disabling outputs now causes views to be re-arranged, so in the context of idle system power management (for example when using swaylock), it is no longer suitable to call wlr-randr {--off,--on} to enable/disable outputs. - The "Drag" mouse-event and the unmaximize-on-move feature require slightly different `` settings to feel natural, so suggest updating any local `rc.xml` settings in accordance with `docs/rc.xml.all` ## [0.4.0] - 2021-12-31 Compile with wlroots 0.15.0 This release contains lots of internal changes, fixes and new features. A big thank you goes out to @ARDiDo, @bi4k8, @Joshua-Ashton, @jlindgren90, @Consolatis, @telent and @apbryan. The most notable feature-type changes are listed below. ### Added - Add support for the following wayland protocols: - `pointer_constraints` and `relative_pointer` - mostly for gaming. Written-by: @Joshua-Ashton - `viewporter` - needed for some games to fake modesets. Written-by: @Joshua-Ashton - `wlr_input_inhibit`. This enables swaylock to be run. Written-by: @telent - `wlr_foreign_toplevel`. This enables controlling windows from clients such as waybar. - `idle` and `idle_inhibit` (Written-by: @ARDiDo) - Support fullscreen mode. - Support drag-and-drop. Written-by: @ARDiDo - Add the following config options: - Load default keybinds on `` - `` and `` - Specify distance between views and output edges with `` - `` - Set menu item font with `` - Allow `` without place="" attribute, thus enabling simpler config files - Support `` with `contexts` (e.g. `TitleBar`, `Left`, `TLCorner`, `Frame`), `buttons` (e.g. `left`, `right`), and `mouse actions` (e.g. `Press`, `DoubleClick`). Modifier keys are also supported to handle configurations such as `alt` + mouse button to move/resize windows. (Written-by: @bi4k8, @apbryan) - `` configuration. Written-by: @ARDiDo - `` - Support for primary selection. Written-by: @telent - Support 'alt-tab' on screen display when cycling between windows including going backwards by pressing `shift` (Written-by: @Joshua-Ashton) and cancelling with `escape` (Written-by: @jlindgren90) - Add the following theme options: - set buttons colors individually (for iconify, close and maximize) - `window.(in)active.label.text.color` - `window.label.text.justify` - OSD colors - Show application title in window decoration title bar - Handle double click on window decoration title bar - Support a 'resize-edges' area that is wider than than the visible window decoration. This makes it easier to grab edges to resize windows. - Add window actions 'MoveToEdge', 'ToggleMaximize', 'Close', 'Iconify', 'ToggleDecorations', 'ToggleFullscreen', 'SnapToEdge', 'Focus', 'Raise', 'Move', 'MoveToEdge', 'Resize', 'PreviousWindow', 'ShowMenu' - Add labwc.desktop for display managers - layer-shell: - Take into account exclusive areas of clients (such as panels) when maximizing windows - Support popups - Handle xwayland `set_decorations` and xdg-shell-decoration requests. Written-by: @Joshua-Ashton - Handle view min/max size better, including xwayland hint support. Written-by: @Joshua-Ashton - Handle xwayland move/resize events. Written-by: @Joshua-Ashton - Support audio and monitor-brightness keys by default - Catch ctrl-alt-F1 to F12 to switch tty - Support `XCURSOR_THEME` and `XCURSOR_SIZE` environment variables - Support submenus including inline definitions ### Changed - The config option `` has changed to `` (breaking change) ## [0.3.0] - 2021-06-28 Compile with wlroots 0.14.0 ### Added - Add config options `` and `` (provided-by: Mikhail Kshevetskiy) - Do not use Clearlooks-3.4 theme by default, just use built-in theme - Fix bug which triggered Qt application segfault ## [0.2.0] - 2021-04-15 Compile with wlroots 0.13.0 ### Added - Support wlr-output-management protocol for setting output position, scale and orientation with kanshi or similar - Support server side decoration rounded corners - Change built-in theme to match default GTK style - Add labwc-environment(5) - Call `wlr_output_enable_adaptive_sync()` if `LABWC_ADAPTIVE_SYNC` set ## [0.1.0] - 2021-03-05 Compile with wlroots 0.12.0 and wayland-server >=1.16 ### Added - Support xdg-shell and optionally xwayland-shell - Show xbm buttons for maximize, iconify and close - Support layer-shell protocol (partial) - Support damage tracking to reduce CPU usage - Support very basic root-menu implementation - Re-load config and theme on SIGHUP - Support simple configuration to auto-start applications, set environment variables and specify theme, font and keybinds. - Support some basic theme settings for window borders and title bars - Support basic actions including Execute, Exit, NextWindow, Reconfigure and ShowMenu [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ [unreleased]: https://github.com/labwc/labwc/compare/0.7.1...HEAD [0.7.1]: https://github.com/labwc/labwc/compare/0.7.0...0.7.1 [0.7.0]: https://github.com/labwc/labwc/compare/0.6.6...0.7.0 [0.6.6]: https://github.com/labwc/labwc/compare/0.6.5...0.6.6 [0.6.5]: https://github.com/labwc/labwc/compare/0.6.4...0.6.5 [0.6.4]: https://github.com/labwc/labwc/compare/0.6.3...0.6.4 [0.6.3]: https://github.com/labwc/labwc/compare/0.6.2...0.6.3 [0.6.2]: https://github.com/labwc/labwc/compare/0.6.1...0.6.2 [0.6.1]: https://github.com/labwc/labwc/compare/0.6.0...0.6.1 [0.6.0]: https://github.com/labwc/labwc/compare/0.5.0...0.6.0 [0.5.3]: https://github.com/labwc/labwc/compare/0.5.2...0.5.3 [0.5.2]: https://github.com/labwc/labwc/compare/0.5.1...0.5.2 [0.5.1]: https://github.com/labwc/labwc/compare/0.5.0...0.5.1 [0.5.0]: https://github.com/labwc/labwc/compare/0.4.0...0.5.0 [0.4.0]: https://github.com/labwc/labwc/compare/0.3.0...0.4.0 [0.3.0]: https://github.com/labwc/labwc/compare/0.2.0...0.3.0 [0.2.0]: https://github.com/labwc/labwc/compare/0.1.0...0.2.0 [0.1.0]: https://github.com/labwc/labwc/compare/081339e...0.1.0 labwc-0.7.1/README.md000066400000000000000000000256431457044301200141000ustar00rootroot00000000000000# labwc

[Website] [Scope] [IRC Channel] [Release Notes]

- [1. Project Description](#1-project-description) - [1.1 What Is This?](#11-what-is-this) - [1.2 Why](#12-why) - [1.3 Why The Openbox Theme Specification?](#13-why-the-openbox-theme-specification) - [1.4 Very High Level Scope](#14-very-high-level-scope) - [1.5 Videos](#15-videos) - [1.6 Screenshot](#16-screenshot) - [2. Build and Installation](#2-build-and-installation) - [3. Configuration](#3-configuration) - [4. Theming](#4-theming) - [5. Translations](#5-translations) - [6. Usage](#6-usage) - [6.1 Gaming](#61-gaming) - [7. Integration](#7-integration) ## 1. Project Description ### 1.1 What Is This? Labwc stands for Lab Wayland Compositor, where lab can mean any of the following: - sense of experimentation and treading new ground - inspired by BunsenLabs and ArchLabs - your favorite pet Labwc is a [wlroots]-based window-stacking compositor for [wayland], inspired by [openbox]. It is light-weight and independent with a focus on simply stacking windows well and rendering some window decorations. It takes a no-bling/frills approach and says no to features such as animations. It relies on clients for panels, screenshots, wallpapers and so on to create a full desktop environment. Labwc tries to stay in keeping with [wlroots] and [sway] in terms of general approach and coding style. Labwc has no reliance on any particular Desktop Environment, Desktop Shell or session. Nor does it depend on any UI toolkits such as Qt or GTK. ### 1.2 Why? Firstly, we believe that there is a need for a simple Wayland window-stacking compositor which strikes a balance between minimalism and bloat approximately at the level where Window Managers like Openbox reside in the X11 domain. Most of the core developers are accustomed to low resource Desktop Environments such as Mate/XFCE or standalone Window Managers such as Openbox under X11. Labwc aims to make a similar setup possible under Wayland, with small and independent components rather than a large, integrated software eco-system. Secondly, the Wayland community has achieved an amazing amount so far, and we want to help solve the unsolved problems to make Wayland viable for more people. We think that standardisation and de-fragmentation is a route to greater Wayland adoption, and wanting to play our part in this, Labwc only understands [wayland-protocols] & [wlr-protocols], and it cannot be controlled with dbus, sway/i3/custom-IPC or other technology. Thirdly, it is important to us that scope is tightly controlled so that the compositor matures to production quality. On the whole, we value robustness, reliability, stability and simplicity over new features. Coming up with new ideas and features is easy - maintaining and stabilising them is not. Fourthly, we are of the view that a compositor should be boring in order to do its job well. In this regard we follow in the footsteps of [metacity] which describes itself as a "Boring window manager for the adult in you. Many window managers are like Marshmallow Froot Loops; Metacity is like Cheerios." Finally, we think that an elegant solution to all of this does not need feel square and pixelated like something out of the 1990s, but should look contemporary and enable cutting-edge performance. ### 1.3 Why The Openbox Theme Specification? In order to avoid reinventing configuration and theme syntaxes, the [openbox] 3.6 specification is used. This does not mean that labwc is an openbox clone but rather that configuration files will look and feel familiar. Also, parsing GTK3+ and Qt themes for window decorations is very complicated, so using much simpler specs such as those used by openbox and xfwm makes sense for a compositor such as labwc, both in terms of implementation and for user modification. Openbox spec is somewhat of a stable standard considering how long it has remained unchanged for and how wide-spread its adoption is by lightweight distributions such as LXDE, LXQt, BunsenLabs, ArchLabs, Mabox and Raspian. Some widely used themes (for example Numix and Arc) have built-in support. We could have invented a whole new syntax, but that's not where we want to spend our effort. ### 1.4 Very High Level Scope A lot of emphasis is put on code simplicity when considering features. The main development effort is focused on producing a solid foundation for a stacking compositor rather than adding configuration and theming options. See [scope] for full details on implemented features. High-level summary of items that Labwc supports: - [x] Config files (rc.xml, autostart, environment, menu.xml) - [x] Theme files and xbm/png/svg icons - [x] Basic desktop and client menus - [x] HiDPI - [x] wlroots protocols such as `output-management`, `layer-shell` and `foreign-toplevel` - [x] Optionally xwayland ### 1.5 Videos | video link | date | content | -------------- | ------------| ------- | [Video (2:48)] | 31-Oct-2022 | 0.6.0 release video | [Video (1:10)] | 05-Aug-2021 | window gymnastics, theming and waybar | [Video (3:42)] | 25-Feb-2021 | setting background and themes; xwayland/xdg-shell windows ### 1.6 Screenshot The obligatory screenshot:
Screenshot description ## 2. Build and Installation To build, simply run: meson setup build/ meson compile -C build/ Run-time dependencies include: - wlroots, wayland, libinput, xkbcommon - libxml2, cairo, pango, glib-2.0 - libpng - librsvg >=2.46 (optional) - xwayland, xcb (optional) Build dependencies include: - meson, ninja, gcc/clang - wayland-protocols Disable xwayland with `meson -Dxwayland=disabled build/` For OS/distribution specific details see [wiki]. If the right version of `wlroots` is not found on the system, the build setup will automatically download the wlroots repo. If this fallback is not desired please use: meson setup --wrap-mode=nodownload build/ To enforce the supplied wlroots.wrap file, run: meson setup --force-fallback-for=wlroots build/ If installing after using the wlroots.wrap file, use the following to prevent installing the wlroots headers: meson install --skip-subprojects -C build/ ## 3. Configuration User config files are located at `${XDG_CONFIG_HOME:-$HOME/.config/labwc/}` with the following five files being used: [rc.xml], [menu.xml], [autostart], [environment] and [themerc-override]. Run `labwc --reconfigure` to reload configuration and theme. For a step-by-step initial configuration guide, see [getting-started]. ## 4. Theming Themes are located at `~/.local/share/themes/\/openbox-3/` or equivalent `XDG_DATA_{DIRS,HOME}` location in accordance with freedesktop XDG directory specification. For full theme options, see [labwc-theme(5)] or the [themerc] example file. For themes, search the internet for "openbox themes" and place them in `~/.local/share/themes/`. Some good starting points include: - https://github.com/addy-dclxvi/openbox-theme-collections - https://github.com/the-zero885/Lubuntu-Arc-Round-Openbox-Theme - https://bitbucket.org/archlabslinux/themes/ - https://github.com/BunsenLabs/bunsen-themes ## 5. Translations The default window bar menu can be translated on the [weblate platform](https://translate.lxqt-project.org/projects/labwc/labwc/). Translation status ## 6. Usage ./build/labwc [-s ] > **_NOTE:_** If you are running on **NVIDIA**, you will need the > `nvidia-drm.modeset=1` kernel parameter. If you have not created an rc.xml config file, default bindings will be: | combination | action | ------------------------ | ------ | `alt`-`tab` | activate next window | `super`-`return` | alacritty | `alt`-`F3` | bemenu | `alt`-`F4` | close window | `super`-`a` | toggle maximize | `alt`-`mouse-left` | move window | `alt`-`mouse-right` | resize window | `alt`-`arrow` | move window to edge | `super`-`arrow` | resize window to fill half the output | `XF86_AudioLowerVolume` | amixer sset Master 5%- | `XF86_AudioRaiseVolume` | amixer sset Master 5%+ | `XF86_AudioMute` | amixer sset Master toggle | `XF86_MonBrightnessUp` | brightnessctl set +10% | `XF86_MonBrightnessDown` | brightnessctl set 10%- A root-menu can be opened by clicking on the desktop. ### 6.1 Gaming Cursor confinement is supported from version `0.6.2`. If using older versions, use a nested [gamescope] instance for gaming. It can be added to steam via game launch option: `gamescope -f -- %command%`. ## 7. Integration Suggested apps to use with labwc: - Screen shooter: [grim] - Screen recorder: [wf-recorder] - Background image: [swaybg] - Panel: [waybar], [yambar], [lavalauncher], [sfwbar] - Launchers: [bemenu], [fuzzel], [wofi] - Output managers: [wlopm], [kanshi], [wlr-randr] - Screen locker: [swaylock] See [integration] for further details. [wayland]: https://wayland.freedesktop.org/ [openbox]: http://openbox.org/wiki/Help:Contents [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots [sway]: https://github.com/swaywm [wayland-protocols]: https://gitlab.freedesktop.org/wayland/wayland-protocols [wlr-protocols]: https://gitlab.freedesktop.org/wlroots/wlr-protocols [scope]: https://github.com/labwc/labwc-scope#readme [wiki]: https://github.com/labwc/labwc/wiki [getting-started]: https://labwc.github.io/getting-started.html [integration]: https://labwc.github.io/integration.html [metacity]: https://github.com/GNOME/metacity [rc.xml]: docs/rc.xml.all [menu.xml]: docs/menu.xml [autostart]: docs/autostart [environment]: docs/environment [themerc-override]: docs/themerc [themerc]: docs/themerc [labwc-theme(5)]: https://labwc.github.io/labwc-theme.5.html [gamescope]: https://github.com/Plagman/gamescope [grim]: https://github.com/emersion/grim [wf-recorder]: https://github.com/ammen99/wf-recorder [swaybg]: https://github.com/swaywm/swaybg [waybar]: https://github.com/Alexays/Waybar [yambar]: https://codeberg.org/dnkl/yambar [lavalauncher]: https://sr.ht/~leon_plickat/LavaLauncher [sfwbar]: https://github.com/LBCrion/sfwbar [bemenu]: https://github.com/Cloudef/bemenu [fuzzel]: https://codeberg.org/dnkl/fuzzel [wofi]: https://hg.sr.ht/~scoopta/wofi [wlopm]: https://git.sr.ht/~leon_plickat/wlopm [kanshi]: https://sr.ht/~emersion/kanshi/ [wlr-randr]: https://sr.ht/~emersion/wlr-randr/ [swaylock]: https://github.com/swaywm/swaylock [Video (2:48)]: https://youtu.be/guBnx18EQiA [Video (1:10)]: https://youtu.be/AU_M3n_FS-E [Video (3:42)]: https://youtu.be/rE1bQjSVJzg labwc-0.7.1/data/000077500000000000000000000000001457044301200135205ustar00rootroot00000000000000labwc-0.7.1/data/labwc-symbolic.svg000066400000000000000000000006561457044301200171570ustar00rootroot00000000000000 labwc-0.7.1/data/labwc.desktop000066400000000000000000000001751457044301200162060ustar00rootroot00000000000000[Desktop Entry] Name=labwc Comment=A wayland stacking compositor Exec=labwc Icon=labwc Type=Application DesktopNames=wlroots labwc-0.7.1/data/labwc.svg000066400000000000000000000006561457044301200153400ustar00rootroot00000000000000 labwc-0.7.1/docs/000077500000000000000000000000001457044301200135375ustar00rootroot00000000000000labwc-0.7.1/docs/README000066400000000000000000000002471457044301200144220ustar00rootroot00000000000000Config layout for ~/.config/labwc/ - autostart - environment - menu.xml - rc.xml - themerc-override See `man labwc-config and `man labwc-theme` for further details. labwc-0.7.1/docs/autostart000066400000000000000000000023421457044301200155110ustar00rootroot00000000000000# Example autostart file # Set background color. swaybg -c '#113344' >/dev/null 2>&1 & # Configure output directives such as mode, position, scale and transform. # Use wlr-randr to get your output names # Example ~/.config/kanshi/config below: # profile { # output HDMI-A-1 position 1366,0 # output eDP-1 position 0,0 # } kanshi >/dev/null 2>&1 & # Launch a panel such as yambar or waybar. waybar >/dev/null 2>&1 & # Enable notifications. Typically GNOME/KDE application notifications go # through the org.freedesktop.Notifications D-Bus API and require a client such # as mako to function correctly. Thunderbird is an example of this. mako >/dev/null 2>&1 & # Lock screen after 5 minutes; turn off display after another 5 minutes. # # Note that in the context of idle system power management, it is *NOT* a good # idea to turn off displays by 'disabling outputs' for example by # `wlr-randr --output --off` because this re-arranges views # (since a837fef). Instead use a wlr-output-power-management client such as # https://git.sr.ht/~leon_plickat/wlopm swayidle -w \ timeout 300 'swaylock -f -c 000000' \ timeout 600 'wlopm --off \*' \ resume 'wlopm --on \*' \ before-sleep 'swaylock -f -c 000000' >/dev/null 2>&1 & labwc-0.7.1/docs/environment000066400000000000000000000044201457044301200160260ustar00rootroot00000000000000## ## Example ~/.config/labwc/environment file. ## Uncomment lines starting with one '#' to suit your needs. ## ## ## Use the XKB_DEFAULT_LAYOUT variable to set the keyboard layout. For example ## to start with Swedish keyboard layout set it to 'se'. If you are unsure what ## your country code is, refer to the layout section of: ## /usr/share/X11/xkb/rules/evdev.lst ## ## Multiple keyboard layouts can be set by comma-separating the country codes. ## If a variant layout is needed, the syntax is layout(variant) ## If multiple layouts are used, specify the toggle-keybind using ## XKB_DEFAULT_OPTIONS as show below. ## ## For further details, see xkeyboard-config(7) ## # XKB_DEFAULT_LAYOUT=se # XKB_DEFAULT_LAYOUT=se,us(intl) # XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle # XKB_DEFAULT_OPTIONS=grp:shift_caps_toggle ## ## Force Mozilla software like Firefox and Thunderbird to use wayland backend. ## Firefox (>= v121) enables Wayland by default, but we include this note here ## for those on non-rolling distributions. ## # MOZ_ENABLE_WAYLAND=1 ## ## Set cursor theme and size. Find system icons themes with: ## `find /usr/share/icons/ -type d -name "cursors"` ## # XCURSOR_THEME=breeze_cursors # XCURSOR_THEME=capitaine-cursors # XCURSOR_SIZE=24 ## ## Disable hardware cursors. Most users would not want to do this, but if you ## are experiencing issues with disappearing cursors, this might fix it. ## # WLR_NO_HARDWARE_CURSORS=1 ## ## In order for labwc to work out of the box, the environment variable below ## is set to "1" by default to avoid menus with incorrect offset and blank ## windows with Java applications such as JetBrains and Intellij Idea. ## See https://github.com/swaywm/sway/issues/595 ## labwc will not override any already set environment variables, so if you for ## some reason do not want this, then just set it to "0" (not recommended, but ## mentioned here for completeness). ## # _JAVA_AWT_WM_NONREPARENTING=0 ## ## This allows xdg-desktop-portal-wlr to function (e.g. for screen-recording). ## It is automatically set to "wlroots" by labwc though, so it is only ## includeded here for completeness. Again, labwc will not over-write an ## already set environment variable, so if you need it set to something else, ## then uncomment and adjust. ## # XDG_CURRENT_DESKTOP=wlroots labwc-0.7.1/docs/labwc-actions.5.scd000066400000000000000000000244741457044301200171360ustar00rootroot00000000000000labwc-actions(5) # NAME labwc - actions # ACTIONS Actions are used in menus and keyboard/mouse bindings. ** Close top-most window. ** Kill the process associated with the current window by sending it the SIGTERM signal. ** Execute command. Note that in the interest of backward compatibility, labwc supports as an alternative to even though openbox documentation states that it is deprecated. Note: Tilde (~) is expanded in the command before passing to execvp(). ** Exit labwc. ** Give focus to window under cursor. ** Remove focus from the window that is currently focused. ** Restack the current window above other open windows. ** Restack the current window below other open windows. ** Iconify (minimize) focused window. ** Begin interactive move of window under cursor. ** Move window until it hits the next edge. *direction* [left|up|right|down] Direction in which to move. *snapWindows* [yes|no] Move window until it hits an edge of another window or screen edge. If set to "no", only move to the next screen edge. Default is yes. ** Begin interactive resize of window under cursor. ** Resize window relative to its current size. Values of left, right, top or bottom tell how much to resize on that edge of window, positive values grow window, negative shrink window. ** Resize window to fill the space between its edge and any other window edge. *direction* [left|up|right|down] Direction in which to grow. ** Reverse of GrowToEdge. Shrinks by a maximum of 50%. *direction* [left|up|right|down] Direction in which to shrink. ** Move to position (x, y). ** Resize window. *width* The width to resize the window to in pixels. *height* The height to resize the window to in pixels. ** Move to be centered on cursor. Tries to prevent any part of the window from going off-screen. ** Move window relative to its current position. Positive value of x moves it right, negative left. Positive value of y moves it down, negative up. ** Resize window to fill half the output in the given direction. Supports directions "left", "up", "right", "down" and "center". ** Resize and move active window according to the given region. See labwc-config(5) for further information on how to define regions. ** Cycle focus to next window. ** Cycle focus to previous window. ** Re-load configuration and theme files. ** Show menu. Valid menu names are "root-menu" and "client-menu". ** Toggle decorations of focused window. This is a 3-state action which can be executed multiple times: - Only the titlebar will be hidden, borders and resize area are kept - Remaining decorations will be disabled - Decorations will be shown normally By disabling the theme configuration 'keepBorder' the first step will be removed and the action only toggles between on and off. ** Toggle fullscreen state of focused window. ** Toggle maximize state of focused window. Supported directions are "both" (default), "horizontal", and "vertical". ** Maximize focused window. Supported directions are "both" (default), "horizontal", and "vertical". ** Toggle always-on-top of focused window. ** Toggle between layers 'always-on-bottom' and 'normal'. When a window is in the 'always-on-bottom' layer, it is rendered below all other top-level windows. It is anticipated that this action will be useful when defining window-rules for desktop-management tools that do not support the wlr-layer-shell protocol. ** Toggle omnipresent (visible on all workspaces / sticky) for the focused window. ** Stop handling keybinds other than ToggleKeybinds itself. This can be used to allow A-Tab and similar keybinds to be delivered to Virtual Machines, VNC clients or nested compositors. A second call will restore all original keybinds. This action will only affect the window that had keyboard focus when the binding was executed. Thus when switching to another window, all the usual keybinds will function again until switching back to the original window. There can be multiple windows with this mode set. ** Toggles tearing for the focused window. ** Give focus to topmost window on given output and warp the cursor to the center of the window. If the given output does not contain any windows, the cursor is centered on the given output. ** Moves active window to other output, unless the window state is fullscreen. If *name* is specified, the window will be sent directly to the specified output and *direction* is ignored. If *name* is omitted, *direction* may be one of "left", "right", "up" or "down" to indicate that the window should be moved to the next output in that direction (if one exists). ** Resizes active window size to width and height of the output when the window size exceeds the output size. ** Switch to workspace. *to* The workspace to switch to. Supported values are "current", "last", "left", "right" or the full name of a workspace or its index (starting at 1) as configured in rc.xml. *wrap* [yes|no] Wrap around from last desktop to first, and vice versa. Default yes. ** Send active window to workspace. *to* The workspace to send the window to. Supported values are the same as for GoToDesktop. *follow* [yes|no] Also switch to the specified workspace. Default yes. *wrap* [yes|no] Wrap around from last desktop to first, and vice versa. Default yes. ** Add virtual output (headless backend). For example, it can be used to overlay virtual output on real output, but with a different resolution (this can be done with `wlr-randr` or `wdisplays`). After that, virtual output can be selected for screen sharing (casting), effectively sharing only the region of the screen. It must be noted that overlaying virtual output and real output is not endorsed or explicitly supported by wlroots. For example, after configuring virtual output, real output must be reconfigured as well (for the overlay configuration to work correctly). This is the example configuration: ``` ``` Note that the vertical resolution of "ScreenCasting" output is just 50px smaller than "eDP-1" output to cut off bottom panel from screen sharing. Virtual output is also useful for extending the desktop to (maybe mobile) remote systems like tablets. E.g. simply adding a virtual output, attaching wayvnc to it and running a VNC client on the remote system. *output_name* The name of virtual output. Providing virtual output name is beneficial for further automation. Default is "HEADLESS-X". ** Remove virtual output (headless backend). *output_name* The name of virtual output. If not supplied, will remove the last virtual output added. ** Use the automatic placement policy to move the active window to a position on its output that will minimize overlap with other windows. **++ **++ ** Set, unset, or toggle, respectively, the "shaded" state of the active window. When shaded, window contents are hidden, leaving only the titlebar visible. Full-screen windows or those without server-side decorations (including those for which the server-side titlebar has been hidden) are not eligible for shading. ** If used as the only action for a binding: clear an earlier defined binding. # CONDITIONAL ACTIONS Actions that execute other actions. Used in keyboard/mouse bindings. ** This action will execute one set of actions if the focused window matches the criteria, or another if it does not. The arguments are as follows: ``` ``` *query* Define a query with zero or more conditions. All conditions must be evaluated as true in order for the window to match this query. Multiple queries can be defined. Pattern matching is done according to glob(7) and is case-insensitive. Conditions are as follows: *identifier* XDG shell app_id for Wayland clients, WM_CLASS for XWayland clients. *title* XDG shell title for Wayland clients, WM_NAME for XWayland clients. This argument is optional. *then* A list of actions to be executed if the window matches any query. This argument is optional. *else* A list of actions to be executed if the window does not match any query. This argument is optional. ** Identical to "If" action, but applies to all windows, not just the focused one. # SEE ALSO labwc(1), labwc-config(5), labwc-theme(5), glob(7) labwc-0.7.1/docs/labwc-config.5.scd000066400000000000000000000673211457044301200167410ustar00rootroot00000000000000labwc-config(5) # NAME labwc - configuration files # DESCRIPTION Labwc uses openbox-3.6 specification for configuration and theming, but does not support all options. The following files form the basis of the labwc configuration: rc.xml, menu.xml, autostart and environment. No configuration files are needed to start and run labwc. In accordance with XDG Base Directory Specification, configuration files are searched for in the following order: - ${XDG_CONFIG_HOME:-$HOME/.config}/labwc - ${XDG_CONFIG_DIRS:-/etc/xdg}/labwc When $XDG_CONFIG_HOME is defined, it replaces (rather than augments) $HOME/.config. The same is the case for $XDG_CONFIG_DIRS and /etc/xdg. The XDG Base Directory Specification does not specify whether or not programs should (a) allow the first-identified configuration file to supersede any others, or (b) define rules for merging the information from more than one file. By default, labwc uses option (a), reading only the first file identified. With the --merge-config option, the search order is reserved, but every configuration file encountered is processed in turn. Thus, user-specific files will augment system-wide configurations, with conflicts favoring the user-specific alternative. The configuration directory location can be override with the -C command line option. All configuration and theme files except autostart are re-loaded on receiving signal SIGHUP. The *autostart* file is executed as a shell script. This is the place for executing clients for handling background images, panels and similar. The *environment* file is parsed as *variable=value* and sets environment variables accordingly. It is recommended to specify keyboard layout settings and cursor size/theme here; see environment variable section below for details. Note that the environment file is treated differently by openbox where it is simply sourced prior to running openbox. Note: Tilde (~) and environment variables in the value are expanded, but subshell syntax and apostrophes are ignored. The *menu.xml* file defines the context/root-menus and is described in labwc-menu(5). There is a small section in rc.xml, for example to set rounded corners, but the remainder of the theme specification and associated files are described in labwc-theme(5). *rc.xml* is the main configuration file and all its options are described in detail below. # CONFIGURATION This section describes *rc.xml* configuration options. ## SYNTAX Configuration must be wrapped in a root-element like this: ``` ``` *labwc* parses XML in an element/attribute agnostic way. This is a design decision to increase config file flexibility and keep code simple. In practical terms, this means that `c` is equivalent to ``. The following three are therefore treated the same: ``` Execute foot ``` ``` foot ``` ``` ``` The benefit of the final one is brevity whereas the advantage of the first two is that you can add ' and " within the `` block, for example: ``` sh -c 'grim -g "`slurp`"' ``` Elements at the same level can have the same name whereas attributes cannot. Therefore, where multiple objects of the same kind are required (for example ** and **) the top-node of the object has to be an element. ## BOOLEANS Note that in this manual, Boolean values are listed as [yes|no] for simplicity, but it's also possible to use [true|false] and\/or [on|off]; this is for compatibility with Openbox. ## CORE ``` server 0 no no no ``` ** [server|client] Specify server or client side decorations for xdg-shell views. Note that it is not always possible to turn off client side decorations. Default is server. ** The distance in pixels between views and output edges when using movement actions, for example MoveToEdge. Default is 0. ** [yes|no|fullscreen] Enable adaptive sync. Default is no. *fullscreen* enables adaptive sync whenever a window is in fullscreen mode. ** [yes|no] Allow tearing to reduce input lag. Default is no. This option requires setting the environment variable WLR_DRM_NO_ATOMIC=1. *yes* allow tearing if requested by the active window. ** [yes|no] Try to re-use the existing output mode (resolution / refresh rate). This may prevent unnecessary screenblank delays when starting labwc (also known as flicker free boot). If the existing output mode can not be used with labwc the preferred mode of the monitor is used instead. Default is no. ## PLACEMENT ** [center|automatic|cursor] Specify a placement policy for new windows. The "center" policy will always place windows at the center of the active output. The "automatic" policy will try to place new windows in such a way that they will have minimal overlap with existing windows. The "cursor" policy will center new windows under the cursor. Default is "center". ## WINDOW SWITCHER ** *show* [yes|no] Draw the OnScreenDisplay when switching between windows. Default is yes. *preview* [yes|no] Preview the contents of the selected window when switching between windows. Default is yes. *outlines* [yes|no] Draw an outline around the selected window when switching between windows. Default is yes. ** Define window switcher fields. *content* defines what the field shows and can be any of: - *type* Show view type ("xdg-shell" or "xwayland") - *identifier* Show identifier (app_id for native Wayland windows and WM_CLASS for XWayland clients) - *trimmed_identifier* Show trimmed identifier. Trimming removes the first two nodes of 'org.' strings. - *title* Show window title if different to app_id *width* defines the width of the field expressed as a percentage of the overall window switcher width. The "%" character is required. ## RESISTANCE **++ ** Resist interactive moves and resizes of a window across screen edges or the edges of any other window, respectively. When an edge strength is positive, it indicates a distance, in pixels, that the cursor must move past any relevant encountered edge before an interactive move or resize operation will continue across that edge. When the strength is negative, any interactive move or resize operation that brings the cursor within the absolute value of the specified distance, in pixels, from any relevant edge will snap the operation to that edge. Thus, as a move or resize approaches an edge, it will "attract" the cursor to that edge within the specified distance. As the move or resize continues past the edge, it will provide resistance until the cursor has moved beyond the distance. A strength of zero disables the corresponding resistance effect. The default value for both parameters is 20 pixels. ## FOCUS ** [yes|no] Make focus follow mouse, i.e. focus is given to window under mouse cursor. Default is no. ** [yes|no] Requires cursor movement if followMouse is enabled. It is the same as the "underMouse" setting in Openbox. If set to "no", labwc will additionally focus the window under the cursor in all situations which change the position of a window (e.g. switching workspaces, opening/closing windows). Focusing a different window via A-Tab is still possible, even with this setting set to "no". Default is yes. ** [yes|no] Raise window to top when focused. Default is no. ## WINDOW SNAPPING Windows may be "snapped" to an edge or user-defined region of an output when activated with SnapToEdge actions or, optionally, by dragging windows to the edges of an output. Edge snapping causes a window to occupy half of its output, extending outward from the snapped edge. ** If an interactive move ends with the cursor a maximum distance *range*, (in pixels) from the edge of an output, the move will trigger a SnapToEdge action for that edge. A *range* of 0 disables snapping via interactive moves. Default is 1. ** [yes|no] If *yes*, an interactive move that snaps a window to the top edge will maximize the window. If *no*, snapping will behave as it does with other edges, causing the window to occupy the top half of an output. Default is yes. ** [always|region|edge|never] Snapping windows can trigger corresponding tiling events for native Wayland clients. Clients may use these events to alter their rendering based on knowledge that some edges of the view are confined to edges of a snapping region or output. For example, rounded corners may become square when tiled, or media players may letter-box or pillar-box video rather than imposing rigid aspect ratios on windows that will violate the constraints of window snapping. - When *always* is specified, any window that is snapped to either an output edge or a user-defined region will receive a tiling event. - When *region* is specified, only windows snapped to a user-defined region will receive an event. - When *edge* is specified, only windows snapped to an output edge will receive an event. - When *never* is specified, tiling events will never be triggered. The default is "always". ## REGIONS ** Define snap regions. The regions are calculated based on the usable area of each output. Usable area in this context means space not exclusively used by layershell clients like panels. The "%" character is required. Windows can either be snapped to regions by keeping a keyboard modifier pressed while moving a window (Ctrl, Alt, Shift, Logo) or by using the SnapToRegion action. By default there are no regions defined. ## WORKSPACES ** Define workspaces. A workspace covers all outputs. The OSD only shows windows on the current workspace. Workspaces can be switched to with GoToDesktop and windows can be moved with SendToDesktop. See labwc-actions(5) for more information about their arguments. The number attribute defines the minimum number of workspaces. Default is 1. The number attribute is optional. If the number attribute is specified, names.name is not required. ** Define the timeout after which to hide the workspace OSD. A setting of 0 disables the OSD. Default is 1000 ms. ## THEME ** The name of the Openbox theme to use. It is not set by default. ** The radius of server side decoration top corners. Default is 8. ** [yes|no] Even when disabling server side decorations via ToggleDecorations, keep a small border (and resize area) around the window. Default is yes. ** The font to use for a specific element of a window, menu or OSD. Places can be any of: - ActiveWindow - titlebar of active window - InactiveWindow - titlebar of all windows that aren't focused by the cursor - MenuItem - menu item (currently only root menu) - OnScreenDisplay - items in the on screen display If no place attribute is provided, the setting will be applied to all places. ** Describes font name. Default is sans. ** Font size in pixels. Default is 10. ** Font slant (normal or italic). Default is normal. ** Font weight (normal or bold). Default is normal. ## MARGIN ** Specify the number of pixels to reserve at the edges of an output (typically a dislay/screen/monitor). New, maximized and tiled windows will not be placed in these areas. The use-case for ** is as a workaround for clients such as panels that do NOT support the wlr-layer-shell protocol. *output* is optional; if this attribute is not provided (rather than leaving it an empty string) the margin will be applied to all outputs. ## RESIZE ** [Never|Always|Nonpixel] Show a small indicator on top of the window when resizing or moving. When the application sets size-hints (usually X11 terminal emulators), the indicator will show the dimensions divided by size hints instead. In the case of terminal emulators this usually means columns x rows. The different values mean: - *Never* Do not render the indicator - *Always* Render the indicator while moving and resizing windows - *Nonpixel* Only render the indicator during resize for windows using size-hints Default is Never. ## KEYBOARD ** [on|off] When recognizing a new keyboard enable or disable Num Lock. Default is on. ** [global|window] Stores the keyboard layout either globally or per window and restores it when switching back to the window. Default is global. ** Define a *key* binding in the format *modifier-key*, where supported modifiers are: - S (shift) - C (control) - A or Mod1 (alt) - H or Mod3 (hyper) - W or Mod4 (super / logo) - M or Mod5 (meta) Multiple modifiers can be combined like *A-S-f* for Alt-Shift-f. The key itself can be any unicode character or a keyname like *Return*. Unlike Openbox, multiple space-separated key combinations and key-chains are not supported. The application "wev" (wayland event viewer) is packaged in a lot of distributions and can be used to view all available keynames. *layoutDependent* [yes|no] Make this specific keybind depend on the currently active keyboard layout. If enabled, a keybind using a key which does not exist in the currently active layout will not be executed. The physical key to trigger a keybind may also change along with the active layout. If set to "no" (or is absent) the keybind will be layout agnostic. Default is no. ** Keybind action. See labwc-action(5). ** Load the default keybinds listed below. This is an addition to the openbox specification and provides a way to keep config files simpler whilst allowing your specific keybinds. Note that if no rc.xml is found, or if no entries exist, the same default keybinds will be loaded even if the element is not provided. ``` A-Tab - next window W-Return - alacritty A-F3 - run bemenu A-F4 - close window W-a - toggle maximize A- - move window to edge W- - resize window to fill half the output ``` Audio and MonBrightness keys are also bound to amixer and brightnessctl, respectively. ** Set the rate at which keypresses are repeated per second. Default is 25. ** Set the delay before keypresses are repeated in milliseconds. Default is 600. ## MOUSE ** Set double click time in milliseconds. Default is 500. ** Set scroll factor. Default is 1.0. ** Multiple ** can exist within one **; and multiple ** can exist within one **. Define a mouse binding. Supported context-names include: - TitleBar: The decoration on top of the window, where the window buttons and the window title are shown. - Title: The area of the titlebar (including blank space) between the window buttons, where the window title is displayed. - WindowMenu: The button on the left. - Iconify: The button that looks like an underline. - Maximize: The button that looks like a box. - Close: The button that looks like an X. - Top: The top edge of the window's border. - Bottom: The bottom edge of the window's border. - Left: The left edge of the window's border. - Right: The right edge of the window's border. - TRCorner: The top-right corner of the window's border. - TLCorner: The top-left corner of the window's border. - BLCorner: The bottom-left corner of the window's border. - BRCorner: The bottom-right edge of the window's border. - Client: The client area of a window, inside its decorations. Events bound to Client are also passed to applications. - Frame: Any part of a window, but events bound to Frame are not passed through to the application. - Desktop: The desktop background, where no windows are present. - Root: A synonym for Desktop (for compatibility). Supported mouse *buttons* are: - Left - Middle - Right - Side - Extra - Forward - Back - Task Supported scroll *directions* are: - Up - Down - Left - Right Mouse buttons and directions can be combined with modifier-keys (shift (S), super/logo (W), control (C), alt (A), meta (M) and hyper (H)), for example: Supported mouse *actions* include: - Press: Pressing the specified button down in the context. - Release: Releasing the specified button in the context. - Click: Pressing and then releasing inside of the the context. - DoubleClick: Two presses within the doubleClickTime. - Drag: Pressing the button within the context, then moving the cursor. - Scroll: Scrolling in specified *direction* in the context. ** Load default mousebinds. This is an addition to the openbox specification and provides a way to keep config files simpler whilst allowing user specific binds. Note that if no rc.xml is found, or if no entries exist, the same default mousebinds will be loaded even if the element is not provided. ## TOUCH ``` ``` ** A touch configuration can be bound to a specifc device. If device name is left empty, the touch configuration applies to all touch devices or functions as a fallback. Multiple touch configurations can exist. See the libinput device section for obtaining the device names. ** Direct cursor movement to a specified output. If the compositor is running in nested mode, this does not take effect. ## TABLET ``` ``` ** The tablet cursor movement can be restricted to a single output. If the output name is left empty or the output does not exists, the tablet will span all outputs. ** [0|90|180|270] The tablet orientation can be changed in 90 degree steps. Default is no rotation (0). Rotation will be applied after applying tablet area transformation. ** By default the complete tablet area is mapped to the full output. The *area* element can be used to truncate the active area of the tablet surface. By truncating the active area, it is e.g. possible to maintain the same aspect ratio between output and tablet. The active tablet area can be specified by setting the *top*/*left* coordinate (in mm) and/or *width*/*height* (in mm). If width or height are omitted or default (0.0), width/height will be set to the remaining width/height seen from top/left. Aspect ratio example: The dimensions of the tablet are 215mm x 115mm and the output has a resolution of 3440x1440. When setting height to "90", because 215 x 1440 / 3440 = 90, the responsive tablet area height will be truncated to match the 21:9 aspect ratio of the output. By additionally setting top to "12.5", the active area is centered vertically on the tablet surface. ** Tablet buttons emulate regular mouse buttons. If not specified otherwise, the tip (Tip) is mapped to left mouse click, the first pen button (Stylus) is mapped to right mouse button click and the second pen button (Stylus2) emulates a middle mouse button click. Supported map *buttons* are: - Tip - Stylus - Stylus2 - Stylus3 - Pad - Pad2..Pad9 See mouse section above for all supported *to* mouse buttons. ## LIBINPUT ``` yes ``` ** Define a new libinput configuration category (profile). *CATEGORY* Defines a category of devices (by type or name) to apply the settings that follow. The category attribute as optional. If no category attribute is provided, a 'default' device profile will created that will act as the fallback for all libinput devices. Category can be set to any of the following types: - *touch* - Devices which have a defined width/height, but do not support multitouch (i.e. they cannot track multiple locations where the screen has been touched). Drawing tablets typically fall into this type. - *touchpad* - Same as 'touch' but support multitouch. This typically includes laptop track pads with two-finger scroll and swipe gestures. - *non-touch* - Anything not described above, for example traditional mouse pointers. - *default* - Defines a device-category applicable to all devices not matched by anything else. This can be useful for a fallback, or if you want the same settings to be applied to all devices. If the provided category value is different from all of the above key words, it will be used to match the device name directly. A list of device names can be obtained by running *libinput list-devices* (you may need to be root or a part of the input group to perform this). ** [yes|no] Use natural scrolling for this category if available. ** [yes|no] Use your devices left-handed mode if available. ** [\-1.0 to 1.0] Set the pointer speed for this category. The speed is a number between \-1.0 and 1.0, with 0.0 being the default in most cases, and 1.0 being the fastest. ** [flat|adaptive] Set the pointer's acceleration profile for this category. Flat applies no acceleration (the pointers velocity is constant), while adaptive changes the pointers speed based the actual speed of your mouse or finger on your touchpad. ** [yes|no] Enable or disable tap-to-click for this category. This is enabled by default for all categories. ** [lrm|lmr] Set the buttons mapped to one-, two-, and three-finger taps to the left button, right button, and middle button, respectively (lrm) (the default), or to left button, middle button, and right button (lmr). ** [yes|no] Enable or disable tap-and-drag for this category. Tap-and-drag processes a tap immediately followed by a finger down as the start of a drag. ** [yes|no] Enable or disable drag lock for this category. Drag lock ignores a momentary release of a finger during tap-and-dragging. ** [yes|no] Enable or disable middle button emulation for this category. Middle emulation processes a simultaneous left and right click as a press of the middle mouse button (scroll wheel). ** [yes|no] Enable or disable disable while typing for this category. DWT ignores any motion events while a keyboard is typing, and for a short while after as well. ** [none|buttonAreas|clickfinger] Configure the method by which physical clicks on a touchpad are mapped to mouse-button events. The click methods available are: - *buttonAreas* - The bottom of the touchpad is divided into distinct regions corresponding to left, middle and right buttons; clicking within the region will trigger the corresponding event. Clicking the main area further up produces a left button event. - *clickfinger* - Clicking with one, two or three finger(s) will produce left, right or middle button event without regard to the location of a click. - *none* - Physical clicks will not produce button events. The default method depends on the touchpad hardware. ** [yes|no|disabledOnExternalMouse] Optionally enable or disable sending any device events. The options available are: - *yes* - Events are sent as usual - *no* - No events are sent from this device - *disabledOnExternalMouse* - This device does not send events if an external mouse has been detected. It is possible to prevent events from a device in the config and then do a Reconfigure to temporarily enable / disable specific devices. By default, this setting is not configured. ## WINDOW RULES Two types of window rules are supported, actions and properties. They are defined as shown below. ``` ``` *Criteria* ** Define a window rule for any window which matches the criteria defined by the attributes *identifier* or *title*. If both are defined, AND logic is used, so both have to match. Matching against patterns with '\*' (wildcard) and '?' (joker) is supported. Pattern matching is case-insensitive. *identifier* relates to app_id for native Wayland windows and WM_CLASS for XWayland clients. *title* is the title of the window. *matchOnce* can be true|false. If true, the rule will only apply to the first instance of the window with the specified identifier or title. *Properties* Property values can be *yes*, *no* or *default*. If a window matches criteria for multiple rules which set the same property, later config entries have higher priority. *default* can be useful in this situation. ** [yes|no|default] *serverDecoration* over-rules any other setting for server-side window decoration on first map. ** [yes|no|default] *skipTaskbar* removes window foreign-toplevel protocol handle so that it does not appear in clients such as panels and taskbars using that protocol. ** [yes|no|default] *skipWindowSwitcher* removes window from the Window Switcher (alt-tab on-screen-display). ** [yes|no|default] *ignoreFocusRequest* prevent window to activate itself. ** [yes|no|default] *fixedPosition* disallows interactive move/resize and prevents re-positioning in response to changes in reserved output space, which can be caused by ** settings or exclusive layer-shell clients such as panels. ## ENVIRONMENT VARIABLES *XCURSOR_THEME* and *XCURSOR_SIZE* are supported to set cursor theme and size respectively. The default size is 24. System cursor themes can typically be found with a command such as: ``` find /usr/share/icons/ -type d -name "cursors" ``` The following keyboard-configuration variables are supported: *XKB_DEFAULT_RULES*, *XKB_DEFAULT_MODEL*, *XKB_DEFAULT_LAYOUT*, *XKB_DEFAULT_VARIANT* and *XKB_DEFAULT_OPTIONS*. See xkeyboard-config(7) for details. # SEE ALSO labwc(1), labwc-actions(5), labwc-theme(5) labwc-0.7.1/docs/labwc-menu.5.scd000066400000000000000000000026671457044301200164420ustar00rootroot00000000000000labwc-menu(5) # NAME labwc - menu files # DESCRIPTION Static menus are built based on content of XML files located at "~/.config/labwc" and equivalent XDG Base Directories. # SYNTAX The menu file must be entirely enclosed within and tags. Inside these tags, menus are specified as follows: ``` ...some content... ``` *menu.id* Each menu must be given an id, which is a unique identifier of the menu. This id is used to refer to the menu in a ShowMenu action. Default identifiers are "client-menu" for the titlebar context menu and "root-menu" for the root window context menu. Available localisation for the default "client-menu" is only shown if no "client-menu" is present in menu.xml. *menu.label* The title of the menu, shown in its parent. A label must be given when defining a menu. *menu.item.label* The visible name of the menu item. *menu.item.action* See labwc-action(5). Note: XML CDATA is supported for this node in order to maintain compatibility with obmenu-generator. *menu.separator* Horizontal line. # SEE ALSO labwc(1), labwc-action(5), labwc-config(5), labwc-theme(5) labwc-0.7.1/docs/labwc-theme.5.scd000066400000000000000000000173421457044301200165740ustar00rootroot00000000000000labwc-theme(5) # NAME labwc - theme files # THEME The theme engine aims to be compatible with openbox and themes will be searched for in the following order: - ${XDG_DATA_HOME:-$HOME/.local/share}/themes//openbox-3/ - $HOME/.themes//openbox-3/ - /usr/share/themes//openbox-3/ - /usr/local/share/themes//openbox-3/ - /opt/share/themes//openbox-3/ When $XDG_DATA_HOME is defined, it replaces (rather than augments) $HOME/.local/share. The same is the case for $XDG_DATA_DIRS and /usr/share/. Choosing a theme is done by editing the key in the section of the rc.xml configuration file (labwc-config(5)). A theme consists of a themerc file and optionally some titlebar icons (referred to as buttons). Theme settings specified in themerc can be overridden by creating a 'themerc-override' file in the configuration directory, which is normally $HOME/.config/labwc/ but can be a few other locations as described in labwc-config(5). # DATA TYPES *color* Colors can be specified by either of the following: - #rrggbb (hexadecimal RGB values) - #rrggbb aaa (same but with decimal alpha value) *justification* Justification determines the horizontal alignment of text. Valid options are Left, Center and Right. # THEME ELEMENTS *border.width* Line width (integer) of border border drawn around window frames. Default is 1. *padding.height* Vertical padding size, used for spacing out elements in the window decorations. Default is 3. *titlebar.height* Window title bar height. Default equals the vertical font extents of the title plus 2x padding.height. *menu.items.padding.x* Horizontal padding of menu text entries in pixels. Default is 7. *menu.items.padding.y* Vertical padding of menu text entries in pixels. Default is 4. *menu.overlap.x* Horizontal overlap in pixels between submenus and their parents. A positive value move submenus over the top of their parents, whereas a negative value creates a gap between submenus and their parents. Default is 0. *menu.overlap.y* Vertical offset in pixels between submenus and their parents. Positive values for downwards and negative for upwards. Default is 0. *menu.width.min* Minimal width for menus. Default is 20. A fixed width can be achieved by setting .min and .max to the same value. *menu.width.max* Maximal width for menus. Default is 200. A fixed width can be achieved by setting .min and .max to the same value. *window.active.border.color* Border color of active window. *window.inactive.border.color* Border color of inactive window. *window.active.indicator.toggled-keybind.color* Status indicator for the ToggleKeybinds action. Can be set to the same value as set for window.active.border.color to disable the status indicator. *window.active.title.bg.color* Background color for the focused window's titlebar. *window.inactive.title.bg.color* Background color for non-focused windows' titlebars. *window.active.label.text.color* Text color for the focused window's titlebar. *window.inactive.label.text.color* Text color non-focused windows' titlebars. *window.label.text.justify* Specifies how window titles are aligned in the titlebar for both focused and unfocused windows. Type justification. Default Left. *window.active.button.unpressed.image.color* Color of the images in titlebar buttons in their default, unpressed, state. This element is for the focused window. *window.inactive.button.unpressed.image.color* Color of the images in titlebar buttons in their default, unpressed, state. This element is for non-focused windows. Note: The button elements (i.e. window.[in]active.button.\*) support defining different types of buttons individually by inserting the type ("menu", "iconify", "max" and "close") after the button node. For example: window.active.button.iconify.unpressed.image.color This syntax is not documented on the openbox.org wiki, but is supported by openbox and is used by many popular themes. For the sake of brevity, these elements are not listed here, but are supported. *menu.items.bg.color* Background color of inactive menu items. *menu.items.text.color* Text color of inactive menu item. *menu.items.active.bg.color* Background color of active menu items. *menu.items.active.text.color* Text color of active menu item. *menu.separator.width* Line thickness of menu separators. Default 1. *menu.separator.padding.width* Space on the left and right side of each separator line. Default 6. *menu.separator.padding.height* Space above and below each separator line. Default 3. *menu.separator.color* Menu separator color. Default #888888. *osd.bg.color* Background color of on-screen-display. *osd.border.color* Border color of on-screen-display. *osd.border.width* Border width of on-screen-display. Inherits *border.width* if not set. *osd.label.text.color* Text color of on-screen-display. *osd.window-switcher.width* Width of window switcher in pixels. Default is 600. *osd.window-switcher.padding* Padding of window switcher in pixels. This is the space between the window-switcher border and its items. Default is 4. *osd.window-switcher.item.padding.x* Horizontal padding of window switcher entries in pixels. Default is 10. *osd.window-switcher.item.padding.y* Vertical padding of window switcher entries in pixels. Default is 1. *osd.window-switcher.item.active.border.width* Border width of the selection box in the window switcher in pixels. Default is 2. *osd.workspace-switcher.boxes.width* Width of boxes in workspace switcher in pixels. Setting to 0 disables boxes. Default is 20. *osd.workspace-switcher.boxes.height* Height of boxes in workspace switcher in pixels. Setting to 0 disables boxes. Default is 20. *border.color* Set all border colors. This is obsolete, but supported for backward compatibility as some themes still contain it. # BUTTONS The images used for the titlebar icons are referred to as buttons. The image formats listed below are supported. They are listed in order of precedence, where the first format in the list is searched for first. - png - svg - xbm By default, buttons are 1-bit xbm (X Bitmaps). These are masks where 0=clear and 1=colored. The xbm image files are placed in the same directory as the themerc file within a particular theme. The following xbm buttons are supported: - max.xbm - iconify.xbm - close.xbm - menu.xbm - max_toggled.xbm Additional icons can be defined to be shown when the mouse pointer is hovering over the button in question: - max_hover.xbm - iconify_hover.xbm - close_hover.xbm - menu_hover.xbm - max_toggled_hover.xbm One advantage of xbm buttons over other formats is that they change color based on the theme. Other formats use the suffices "-active" and "-inactive" to align with the respective titlebar colors. For example: "close-active.png" For compatibility reasons, the following alternative names are supported for xbm files: - max_hover_toggled.xbm for max_toggled_hover.xbm When using png or svg icons, for a full theme experience all of the following icons should be added: - close-active.[png|svg] - close_hover-active.[png|svg] - close_hover-inactive.[png|svg] - close-inactive.[png|svg] - iconify-active.[png|svg] - iconify_hover-active.[png|svg] - iconify_hover-inactive.[png|svg] - iconify-inactive.[png|svg] - max-active.[png|svg] - max_hover-active.[png|svg] - max_hover-inactive.[png|svg] - max-inactive.[png|svg] - max_toggled-active.[png|svg] - max_toggled_hover-active.[png|svg] - max_toggled_hover-inactive.[png|svg] - max_toggled-inactive.[png|svg] - menu-active.[png|svg] - menu_hover-active.[png|svg] - menu_hover-inactive.[png|svg] - menu-inactive.[png|svg] # DEFINITIONS The handle is the window edge decoration at the bottom of the window. # SEE ALSO labwc(1), labwc-config(5), labwc-actions(5) labwc-0.7.1/docs/labwc.1.scd000066400000000000000000000025431457044301200154650ustar00rootroot00000000000000labwc(1) # NAME labwc - a wayland stacking compositor # SYNOPSIS *labwc* [options...] # DESCRIPTION Labwc is a wlroots-based stacking compositor for wayland. It is light-weight and independent with a focus on simply stacking windows well and rendering some window decorations. Where practicable it uses clients for wall-paper, panels, screenshots and so on. The compositor will exit or reload its configuration upon receiving SIGTERM and SIGHUP respectively. For example: ``` kill -s $LABWC_PID killall -s labwc ``` Each running instance of labwc sets the environment variable `LABWC_PID` to its PID. This is useful for sending signals to a specific instance and is what the `--exit` and `--reconfigure` options use. # OPTIONS *-c, --config* Specify a config file with path *-C, --config-dir* Specify a config directory *-d, --debug* Enable full logging, including debug information *-e, --exit* Exit the compositor *-h, --help* Show help message and quit *-m, --merge-config* Merge user config/theme files in all XDG Base Directories *-r, --reconfigure* Reload the compositor configuration *-s, --startup* Run command on startup *-v, --version* Show the version number and quit *-V, --verbose* Enable more verbose logging # SEE ALSO labwc-config(5), labwc-theme(5), labwc-actions(5) labwc-0.7.1/docs/menu.xml000066400000000000000000000033631457044301200152320ustar00rootroot00000000000000 labwc-0.7.1/docs/meson.build000066400000000000000000000013361457044301200157040ustar00rootroot00000000000000scdoc = find_program('scdoc', required: get_option('man-pages')) if scdoc.found() sections = [ '.1', '-actions.5', '-config.5', '-menu.5', '-theme.5', ] foreach s : sections markdown = 'labwc' + s + '.scd' manpage = 'labwc' + s custom_target( manpage, input: markdown, output: manpage, command: scdoc, feed: true, capture: true, install: true, install_dir: join_paths(get_option('mandir'), 'man' + s.split('.')[-1]) ) endforeach endif install_data( [ 'autostart', 'environment', 'menu.xml', 'README', 'themerc', 'rc.xml', 'rc.xml.all' ], install_dir: get_option('datadir') / 'doc' / meson.project_name() ) labwc-0.7.1/docs/rc.xml000066400000000000000000000016411457044301200146670ustar00rootroot00000000000000 8 labwc-0.7.1/docs/rc.xml.all000066400000000000000000000420401457044301200154340ustar00rootroot00000000000000 server 0 no no no center 8 yes sans 10 normal normal sans 10 normal normal sans 10 normal normal sans 10 normal normal 20 20 no yes no 1 yes always 1000 Default on global 25 600 500 1.0 yes labwc-0.7.1/docs/themerc000066400000000000000000000040451457044301200151140ustar00rootroot00000000000000# This file contains all themerc options with default values # # System-wide and local themes can be overridden by creating a copy of this # file and renaming it to $HOME/.config/labwc/themerc-override. Be careful # though - if you only want to override a small number of specific options, # make sure all other lines are commented out or deleted. # general border.width: 1 padding.height: 3 # The following options has no default, but fallbacks back to # font-height + 2x padding.height if not set. # titlebar.height: # window border window.active.border.color: #e1dedb window.inactive.border.color: #f6f5f4 # ToggleKeybinds status indicator window.active.indicator.toggled-keybind.color: #ff0000 # window titlebar background window.active.title.bg.color: #e1dedb window.inactive.title.bg.color: #f6f5f4 # window titlebar text window.active.label.text.color: #000000 window.inactive.label.text.color: #000000 window.label.text.justify: center # window buttons window.active.button.unpressed.image.color: #000000 window.inactive.button.unpressed.image.color: #000000 # Note that "menu", "iconify", "max", "close" buttons colors can be defined # individually by inserting the type after the button node, for example: # # window.active.button.iconify.unpressed.image.color: #333333 # menu menu.overlap.x: 0 menu.overlap.y: 0 menu.width.min: 20 menu.width.max: 200 menu.items.bg.color: #fcfbfa menu.items.text.color: #000000 menu.items.active.bg.color: #e1dedb menu.items.active.text.color: #000000 menu.items.padding.x: 7 menu.items.padding.y: 4 menu.separator.width: 1 menu.separator.padding.width: 6 menu.separator.padding.height: 3 menu.separator.color: #888888 # on screen display (window-cycle dialog) osd.bg.color: #dddda6 osd.border.color: #000000 osd.border.width: 1 osd.label.text.color: #000000 osd.window-switcher.width: 600 osd.window-switcher.padding: 4 osd.window-switcher.item.padding.x: 10 osd.window-switcher.item.padding.y: 1 osd.window-switcher.item.active.border.width: 2 osd.workspace-switcher.boxes.width: 20 osd.workspace-switcher.boxes.height: 20 labwc-0.7.1/include/000077500000000000000000000000001457044301200142325ustar00rootroot00000000000000labwc-0.7.1/include/action.h000066400000000000000000000024311457044301200156600ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_ACTION_H #define LABWC_ACTION_H #include struct view; struct server; struct action { struct wl_list link; /* * struct keybinding.actions * struct mousebinding.actions * struct menuitem.actions */ uint32_t type; /* enum action_type */ struct wl_list args; /* struct action_arg.link */ }; struct action *action_create(const char *action_name); bool action_is_valid(struct action *action); void action_arg_add_str(struct action *action, const char *key, const char *value); void action_arg_add_actionlist(struct action *action, const char *key); void action_arg_add_querylist(struct action *action, const char *key); struct wl_list *action_get_actionlist(struct action *action, const char *key); struct wl_list *action_get_querylist(struct action *action, const char *key); void action_arg_from_xml_node(struct action *action, const char *nodename, const char *content); bool actions_contain_toggle_keybinds(struct wl_list *action_list); void actions_run(struct view *activator, struct server *server, struct wl_list *actions, uint32_t resize_edges); void action_free(struct action *action); void action_list_free(struct wl_list *action_list); #endif /* LABWC_ACTION_H */ labwc-0.7.1/include/buffer.h000066400000000000000000000036751457044301200156670ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ /* * Based on wlroots/include/types/wlr_buffer.c * * Copyright (c) 2017, 2018 Drew DeVault * Copyright (c) 2018-2021 Simon Ser, Simon Zeni * 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. */ #ifndef LABWC_BUFFER_H #define LABWC_BUFFER_H #include #include struct lab_data_buffer { struct wlr_buffer base; cairo_t *cairo; void *data; uint32_t format; size_t stride; bool free_on_destroy; uint32_t unscaled_width; uint32_t unscaled_height; }; /* Create a buffer which creates a new cairo CAIRO_FORMAT_ARGB32 surface */ struct lab_data_buffer *buffer_create_cairo(uint32_t width, uint32_t height, float scale, bool free_on_destroy); /* Create a buffer which wraps a given DRM_FORMAT_ARGB8888 pointer */ struct lab_data_buffer *buffer_create_wrap(void *pixel_data, uint32_t width, uint32_t height, uint32_t stride, bool free_on_destroy); #endif /* LABWC_BUFFER_H */ labwc-0.7.1/include/button/000077500000000000000000000000001457044301200155455ustar00rootroot00000000000000labwc-0.7.1/include/button/button-png.h000066400000000000000000000003551457044301200200160ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_BUTTON_PNG_H #define LABWC_BUTTON_PNG_H struct lab_data_buffer; void button_png_load(const char *button_name, struct lab_data_buffer **buffer); #endif /* LABWC_BUTTON_PNG_H */ labwc-0.7.1/include/button/button-svg.h000066400000000000000000000003701457044301200200260ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_BUTTON_SVG_H #define LABWC_BUTTON_SVG_H struct lab_data_buffer; void button_svg_load(const char *button_name, struct lab_data_buffer **buffer, int size); #endif /* LABWC_BUTTON_SVG_H */ labwc-0.7.1/include/button/button-xbm.h000066400000000000000000000012571457044301200200220ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_BUTTON_XBM_H #define LABWC_BUTTON_XBM_H struct lab_data_buffer; /** * button_xbm_from_bitmap() - create button from monochrome bitmap * @bitmap: bitmap data array in hexadecimal xbm format * @buffer: cairo-surface-buffer to create * @rgba: color * * Example bitmap: char button[6] = { 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f }; */ void button_xbm_from_bitmap(const char *bitmap, struct lab_data_buffer **buffer, float *rgba); /* button_xbm_load - Convert xbm file to buffer with cairo surface */ void button_xbm_load(const char *button_name, struct lab_data_buffer **buffer, float *rgba); #endif /* LABWC_BUTTON_XBM_H */ labwc-0.7.1/include/button/common.h000066400000000000000000000007331457044301200172110ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_BUTTON_COMMON_H #define LABWC_BUTTON_COMMON_H /** * button_filename() - Get full filename for button. * @name: The name of the button (for example 'iconify.xbm'). * @buf: Buffer to fill with the full filename * @len: Length of buffer * * Example return value: /usr/share/themes/Numix/openbox-3/iconfify.xbm */ void button_filename(const char *name, char *buf, size_t len); #endif /* LABWC_BUTTON_COMMON_H */ labwc-0.7.1/include/common/000077500000000000000000000000001457044301200155225ustar00rootroot00000000000000labwc-0.7.1/include/common/array.h000066400000000000000000000025041457044301200170120ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_ARRAY_H #define LABWC_ARRAY_H #include /* * Wayland's wl_array API is a bit sparse consisting only of * - init * - release * - add * - copy * - for_each * * The purpose of this header is the gather any generic wl_array helpers we * create. * * We take the liberty of using the wl_ suffix here to make it look a bit * prettier. If Wayland extend the API in future, we will sort the clash then. */ /** * wl_array_len() - return length of wl_array * @array: wl_array for which to calculate length * Note: The pointer type might not be 'char' but this is the approach that * wl_array_for_each() takes, so we align with their style. */ static inline size_t wl_array_len(struct wl_array *array) { return array->size / sizeof(const char *); } /** * Iterates in reverse over an array. * @pos: pointer that each array element will be assigned to * @array: wl_array to iterate over */ #define wl_array_for_each_reverse(pos, array) \ for (pos = !(array)->data ? NULL \ : (void *)((const char *)(array)->data + (array)->size - sizeof(pos)); \ pos && (const char *)pos >= (const char *)(array)->data; \ (pos)--) #endif /* LABWC_ARRAY_H */ labwc-0.7.1/include/common/border.h000066400000000000000000000002711457044301200171500ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_BORDER_H #define LABWC_BORDER_H struct border { int top; int right; int bottom; int left; }; #endif /* LABWC_BORDER_H */ labwc-0.7.1/include/common/buf.h000066400000000000000000000015271457044301200164540ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ /* * Very simple C string buffer implementation * * Copyright Johan Malm 2020 */ #ifndef LABWC_BUF_H #define LABWC_BUF_H #include #include #include struct buf { char *buf; int alloc; int len; }; /** * buf_expand_tilde - expand ~ in buffer * @s: buffer */ void buf_expand_tilde(struct buf *s); /** * buf_expand_shell_variables - expand $foo and ${foo} in buffer * @s: buffer * Note: $$ is not handled */ void buf_expand_shell_variables(struct buf *s); /** * buf_init - allocate NULL-terminated C string buffer * @s: buffer * Note: use free(s->buf) to free it */ void buf_init(struct buf *s); /** * buf_add - add data to C string buffer * @s: buffer * @data: data to be added */ void buf_add(struct buf *s, const char *data); #endif /* LABWC_BUF_H */ labwc-0.7.1/include/common/dir.h000066400000000000000000000010041457044301200164440ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_DIR_H #define LABWC_DIR_H #include struct path { char *string; struct wl_list link; }; struct wl_list *paths_get_prev(struct wl_list *elm); struct wl_list *paths_get_next(struct wl_list *elm); void paths_config_create(struct wl_list *paths, const char *filename); void paths_theme_create(struct wl_list *paths, const char *theme_name, const char *filename); void paths_destroy(struct wl_list *paths); #endif /* LABWC_DIR_H */ labwc-0.7.1/include/common/fd_util.h000066400000000000000000000002761457044301200173260ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_FD_UTIL_H #define LABWC_FD_UTIL_H void increase_nofile_limit(void); void restore_nofile_limit(void); #endif /* LABWC_FD_UTIL_H */ labwc-0.7.1/include/common/file-helpers.h000066400000000000000000000004331457044301200202520ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_FILE_HELPERS_H #define LABWC_FILE_HELPERS_H #include /** * file_exists() - Test if file exists. * @filename: Name of file to test. */ bool file_exists(const char *filename); #endif /* LABWC_FILE_HELPERS_H */ labwc-0.7.1/include/common/font.h000066400000000000000000000025101457044301200166370ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_FONT_H #define LABWC_FONT_H struct lab_data_buffer; enum font_slant { FONT_SLANT_NORMAL = 0, FONT_SLANT_ITALIC }; enum font_weight { FONT_WEIGHT_NORMAL = 0, FONT_WEIGHT_BOLD }; struct font { char *name; int size; enum font_slant slant; enum font_weight weight; }; struct _PangoFontDescription *font_to_pango_desc(struct font *font); /** * font_height - get font vertical extents * @font: description of font including family name and size */ int font_height(struct font *font); /** * font_width - get font horizontal extents * @font: description of font including family name and size */ int font_width(struct font *font, const char *string); /** * font_buffer_create - Create ARGB8888 lab_data_buffer using pango * @buffer: buffer pointer * @max_width: max allowable width; will be ellipsized if longer * @text: text to be generated as texture * @font: font description * @color: foreground color in rgba format * @arrow: arrow (utf8) character to show or NULL for none */ void font_buffer_create(struct lab_data_buffer **buffer, int max_width, const char *text, struct font *font, float *color, const char *arrow, double scale); /** * font_finish - free some font related resources * Note: use on exit */ void font_finish(void); #endif /* LABWC_FONT_H */ labwc-0.7.1/include/common/grab-file.h000066400000000000000000000005501457044301200175230ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ /* * Read file into memory * * Copyright Johan Malm 2020 */ #ifndef LABWC_GRAB_FILE_H #define LABWC_GRAB_FILE_H /** * grab_file - read file into memory buffer * @filename: file to read * Returns pointer to buffer. Free with free(). */ char *grab_file(const char *filename); #endif /* LABWC_GRAB_FILE_H */ labwc-0.7.1/include/common/graphic-helpers.h000066400000000000000000000031611457044301200207510ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_GRAPHIC_HELPERS_H #define LABWC_GRAPHIC_HELPERS_H #include #include struct wlr_scene_tree; struct wlr_scene_rect; struct wlr_fbox; struct multi_rect { struct wlr_scene_tree *tree; int line_width; /* read-only */ /* Private */ struct wlr_scene_rect *top[3]; struct wlr_scene_rect *bottom[3]; struct wlr_scene_rect *left[3]; struct wlr_scene_rect *right[3]; struct wl_listener destroy; }; /** * Create a new multi_rect. * A multi_rect consists of 3 nested rectangular outlines. * Each of the rectangular outlines is using the same @line_width * but its own color based on the @colors argument. * * The multi-rect can be positioned by positioning multi_rect->tree->node. * * It can be destroyed by destroying its tree node (or one of its * parent nodes). Once the tree node has been destroyed the struct * will be free'd automatically. */ struct multi_rect *multi_rect_create(struct wlr_scene_tree *parent, float *colors[3], int line_width); void multi_rect_set_size(struct multi_rect *rect, int width, int height); /** * Sets the cairo color. * Splits a float[4] single color array into its own arguments */ void set_cairo_color(cairo_t *cairo, float *color); /* Draws a border with a specified line width */ void draw_cairo_border(cairo_t *cairo, struct wlr_fbox fbox, double line_width); struct lab_data_buffer; struct surface_context { bool is_duplicate; cairo_surface_t *surface; }; struct surface_context get_cairo_surface_from_lab_data_buffer( struct lab_data_buffer *buffer); #endif /* LABWC_GRAPHIC_HELPERS_H */ labwc-0.7.1/include/common/list.h000066400000000000000000000014141457044301200166460ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_LIST_H #define LABWC_LIST_H #include /** * wl_list_append() - add a new element to the end of a list * @list: list head to add it before * @elm: new element to be added (link of the containing struct to be precise) * * Note: In labwc, most lists are queues where we want to add new elements to * the end of the list. As wl_list_insert() adds elements at the front of the * list (like a stack) - without this helper-function - we have to use * wl_list_insert(list.prev, element) which is verbose and not intuitive to * anyone new to this API. */ static inline void wl_list_append(struct wl_list *list, struct wl_list *elm) { wl_list_insert(list->prev, elm); } #endif /* LABWC_LIST_H */ labwc-0.7.1/include/common/macros.h000066400000000000000000000032561457044301200171650ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_MACROS_H #define LABWC_MACROS_H #include /** * ARRAY_SIZE() - Get the number of elements in array. * @arr: array to be sized * * This does not work on pointers. * * Recent versions of GCC and clang support -Werror=sizeof-pointer-div * and thus avoids using constructs such as: * * #define same_type(a, b) (__builtin_types_compatible_p(typeof(a), typeof(b)) == 1) * #define ARRAY_SIZE(a) ({ static_assert(!same_type(a, &(a)[0])); sizeof(a) / sizeof(a[0]); }) */ #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) /** * CONNECT_SIGNAL() - Connect a signal handler function to a wl_signal. * * @param src Signal emitter (struct containing wl_signal) * @param dest Signal receiver (struct containing wl_listener) * @param name Signal name * * This assumes that the common pattern is followed where: * - the wl_signal is (*src).events. * - the wl_listener is (*dest). * - the signal handler function is named handle_ */ #define CONNECT_SIGNAL(src, dest, name) \ (dest)->name.notify = handle_##name; \ wl_signal_add(&(src)->events.name, &(dest)->name) /** * MIN() - Minimum of two values. * * @note Arguments may be evaluated twice. */ #ifndef MIN #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif /** * MAX() - Maximum of two values. * * @note Arguments may be evaluated twice. */ #ifndef MAX #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #endif /** * BOUNDED_INT() - Returns true if an integer is not INT_MAX or INT_MIN * * @param val Value to test (integer) */ #ifndef BOUNDED_INT #define BOUNDED_INT(a) ((a) < INT_MAX && (a) > INT_MIN) #endif #endif /* LABWC_MACROS_H */ labwc-0.7.1/include/common/match.h000066400000000000000000000005741457044301200167750ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_MATCH_H #define LABWC_MATCH_H #include /** * match_glob() - Pattern match using '*' wildcards and '?' jokers. * @pattern: Pattern to match against. * @string: String to search. * Note: Comparison case-insensitive. */ bool match_glob(const char *pattern, const char *string); #endif /* LABWC_MATCH_H */ labwc-0.7.1/include/common/mem.h000066400000000000000000000026521457044301200164560ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_MEM_H #define LABWC_MEM_H #include /* * As defined in busybox, weston, etc. * Allocates zero-filled memory; calls exit() on error. * Returns NULL only if (size == 0). */ void *xzalloc(size_t size); /* * Type-safe macros in the style of C++ new/new[]. * Both allocate zero-filled memory for object(s) the same size as * , which may be either a type name or value expression. * * znew() allocates space for one object. * znew_n() allocates space for an array of objects. * * Examples: * struct wlr_box *box = znew(*box); * char *buf = znew_n(char, 80); */ #define znew(expr) ((__typeof__(expr) *)xzalloc(sizeof(expr))) #define znew_n(expr, n) ((__typeof__(expr) *)xzalloc((n) * sizeof(expr))) /* * As defined in FreeBSD. * Like realloc(), but calls exit() on error. * Returns NULL only if (size == 0). * Does NOT zero-fill memory. */ void *xrealloc(void *ptr, size_t size); /* malloc() is a specific case of realloc() */ #define xmalloc(size) xrealloc(NULL, (size)) /* * As defined in FreeBSD. * Allocates a copy of ; calls exit() on error. * Requires (str != NULL) and never returns NULL. */ char *xstrdup(const char *str); /* * Frees memory pointed to by and sets to NULL. * Does nothing if is already NULL. */ #define zfree(ptr) do { \ free(ptr); (ptr) = NULL; \ } while (0) #endif /* LABWC_MEM_H */ labwc-0.7.1/include/common/nodename.h000066400000000000000000000007251457044301200174650ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_NODENAME_H #define LABWC_NODENAME_H #include #include #include /** * nodename - give xml node an ascii name * @node: xml-node * @buf: buffer to receive the name * @len: size of buffer * * For example, the xml structure would return the * name c.b.a */ char *nodename(xmlNode * node, char *buf, int len); #endif /* LABWC_NODENAME_H */ labwc-0.7.1/include/common/parse-bool.h000066400000000000000000000014171457044301200177410ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_PARSE_BOOL_H #define LABWC_PARSE_BOOL_H #include /** * parse_bool() - Parse boolean value of string. * @string: String to interpret. This check is case-insensitive. * @default_value: Default value to use if string is not a recognised boolean. * Use -1 to avoid setting a default value. * * Return: 0 for false; 1 for true; -1 for non-boolean */ int parse_bool(const char *str, int default_value); /** * set_bool() - Parse boolean text and set variable iff text is valid boolean * @string: Boolean text to interpret. * @variable: Variable to set. */ void set_bool(const char *str, bool *variable); void set_bool_as_int(const char *str, int *variable); #endif /* LABWC_PARSE_BOOL_H */ labwc-0.7.1/include/common/scaled_font_buffer.h000066400000000000000000000035251457044301200215120ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_SCALED_FONT_BUFFER_H #define LABWC_SCALED_FONT_BUFFER_H #include "common/font.h" struct wlr_scene_tree; struct wlr_scene_buffer; struct scaled_scene_buffer; struct scaled_font_buffer { struct wlr_scene_buffer *scene_buffer; int width; /* unscaled, read only */ int height; /* unscaled, read only */ /* Private */ char *text; int max_width; float color[4]; char *arrow; struct font font; struct scaled_scene_buffer *scaled_buffer; }; /** * Create an auto scaling font buffer, providing a wlr_scene_buffer node for * display. It gets destroyed automatically when the backing scaled_scene_buffer * is being destroyed which in turn happens automatically when the backing * wlr_scene_buffer (or one of its parents) is being destroyed. * * To actually show some text, scaled_font_buffer_update() has to be called. * */ struct scaled_font_buffer *scaled_font_buffer_create(struct wlr_scene_tree *parent); /** * Update an existing auto scaling font buffer. * * No steps are taken to detect if its actually required to render a new buffer. * This should be done by the caller to prevent useless recreation of the same * buffer in case nothing actually changed. * * Some basic checks could be something like * - truncated = buffer->width == max_width * - text_changed = strcmp(old_text, new_text) * - font and color the same */ void scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, int max_width, struct font *font, float *color, const char *arrow); /** * Update the max width of an existing auto scaling font buffer * and force a new render. * * No steps are taken to detect if its actually required to render a new buffer. */ void scaled_font_buffer_set_max_width(struct scaled_font_buffer *self, int max_width); #endif /* LABWC_SCALED_FONT_BUFFER_H */ labwc-0.7.1/include/common/scaled_scene_buffer.h000066400000000000000000000071131457044301200216360ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_SCALED_SCENE_BUFFER_H #define LABWC_SCALED_SCENE_BUFFER_H #define LAB_SCALED_BUFFER_MAX_CACHE 2 struct wl_list; struct wlr_buffer; struct wl_listener; struct wlr_scene_tree; struct lab_data_buffer; struct scaled_scene_buffer; struct scaled_scene_buffer_impl { /* Return a new buffer optimized for the new scale */ struct lab_data_buffer *(*create_buffer) (struct scaled_scene_buffer *scaled_buffer, double scale); /* Might be NULL or used for cleaning up */ void (*destroy)(struct scaled_scene_buffer *scaled_buffer); }; struct scaled_scene_buffer { struct wlr_scene_buffer *scene_buffer; int width; /* unscaled, read only */ int height; /* unscaled, read only */ void *data; /* opaque user data */ /* Private */ bool drop_buffer; double active_scale; struct wl_list cache; /* struct scaled_buffer_cache_entry.link */ struct wl_listener destroy; struct wl_listener output_enter; struct wl_listener output_leave; const struct scaled_scene_buffer_impl *impl; }; /** * Create an auto scaling buffer that creates a wlr_scene_buffer * and subscribes to its output_enter and output_leave signals. * * If the maximal scale changes, it either sets an already existing buffer * that was rendered for the current scale or - if there is none - calls * implementation->create_buffer(self, scale) to get a new lab_data_buffer * optimized for the new scale. * * Up to LAB_SCALED_BUFFER_MAX_CACHE (2) buffers are cached in an LRU fashion * to handle the majority of use cases where a view is moved between no more * than two different scales. * * scaled_scene_buffer will clean up automatically once the internal * wlr_scene_buffer is being destroyed. If implementation->destroy is set * it will also get called so a consumer of this API may clean up its own * allocations. * * All requested lab_data_buffers via impl->create_buffer() will be locked * during the lifetime of the buffer in the internal cache and unlocked * when being evacuated from the cache (due to LAB_SCALED_BUFFER_MAX_CACHE * or the internal wlr_scene_buffer being destroyed). * * If drop_buffer was set during creation of the scaled_scene_buffer, the * backing wlr_buffer behind a lab_data_buffer will also get dropped * (via wlr_buffer_drop). If there are no more locks (consumers) of the * respective buffer this will then cause the lab_data_buffer to be free'd. * * In the case of the buffer provider dropping the buffer itself (due to * for example a Reconfigure event) the lock prevents the buffer from being * destroyed until the buffer is evacuated from the internal cache and thus * unlocked. * * This allows using scaled_scene_buffer for an autoscaling font_buffer * (which gets free'd automatically) and also for theme components like * rounded corner images or button icons whose buffers only exist once but * are references by multiple windows with their own scaled_scene_buffers. * * The rough idea is: use drop_buffer = true for one-shot buffers and false * for buffers that should outlive the scaled_scene_buffer instance itself. */ struct scaled_scene_buffer *scaled_scene_buffer_create( struct wlr_scene_tree *parent, const struct scaled_scene_buffer_impl *implementation, bool drop_buffer); /* Clear the cache of existing buffers, useful in case the content changes */ void scaled_scene_buffer_invalidate_cache(struct scaled_scene_buffer *self); /* Private */ struct scaled_scene_buffer_cache_entry { struct wl_list link; /* struct scaled_scene_buffer.cache */ struct wlr_buffer *buffer; double scale; }; #endif /* LABWC_SCALED_SCENE_BUFFER_H */ labwc-0.7.1/include/common/scene-helpers.h000066400000000000000000000013301457044301200204250ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_SCENE_HELPERS_H #define LABWC_SCENE_HELPERS_H #include struct wlr_scene_node; struct wlr_surface; struct wlr_scene_output; struct wlr_surface *lab_wlr_surface_from_node(struct wlr_scene_node *node); /** * lab_get_prev_node - return previous (sibling) node * @node: node to find the previous node from * Return NULL if previous link is list-head which means node is bottom-most */ struct wlr_scene_node *lab_wlr_scene_get_prev_node(struct wlr_scene_node *node); /* A variant of wlr_scene_output_commit() that respects wlr_output->pending */ bool lab_wlr_scene_output_commit(struct wlr_scene_output *scene_output); #endif /* LABWC_SCENE_HELPERS_H */ labwc-0.7.1/include/common/spawn.h000066400000000000000000000004011457044301200170160ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_SPAWN_H #define LABWC_SPAWN_H /** * spawn_async_no_shell - execute asyncronously * @command: command to be executed */ void spawn_async_no_shell(char const *command); #endif /* LABWC_SPAWN_H */ labwc-0.7.1/include/common/string-helpers.h000066400000000000000000000024601457044301200206430ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_STRING_HELPERS_H #define LABWC_STRING_HELPERS_H #include /** * string_null_or_empty() - Check if string is NULL or empty * @s: string to check */ bool string_null_or_empty(const char *s); /** * trim_last_field() - Trim last field of string splitting on provided delim * @buf: string to trim * @delim: delimitator * * Example: With delim='_' and buf="foo_bar_baz" the return value is "foo_bar" */ void trim_last_field(char *buf, char delim); /** * string_strip - strip white space left and right * Note: this function does a left skip, so the returning pointer cannot be * used to free any allocated memory */ char *string_strip(char *s); /** * string_truncate_at_pattern - remove pattern and everything after it * @buf: pointer to buffer * @pattern: string to remove */ void string_truncate_at_pattern(char *buf, const char *pattern); /** * strdup_printf - allocate and write to buffer in printf format * @fmt: printf-style format. * * Similar to the standard C sprintf() function but safer as it calculates the * maximum space required and allocates memory to hold the output. * The user must free the returned string. * Returns NULL on error. */ char *strdup_printf(const char *fmt, ...); #endif /* LABWC_STRING_HELPERS_H */ labwc-0.7.1/include/config/000077500000000000000000000000001457044301200154775ustar00rootroot00000000000000labwc-0.7.1/include/config/keybind.h000066400000000000000000000020371457044301200172770ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_KEYBIND_H #define LABWC_KEYBIND_H #include #include #define MAX_KEYSYMS 32 #define MAX_KEYCODES 16 struct server; struct keybind { uint32_t modifiers; xkb_keysym_t *keysyms; size_t keysyms_len; bool use_syms_only; xkb_keycode_t keycodes[MAX_KEYCODES]; size_t keycodes_len; int keycodes_layout; struct wl_list actions; /* struct action.link */ struct wl_list link; /* struct rcxml.keybinds */ }; /** * keybind_create - parse keybind and add to linked list * @keybind: key combination */ struct keybind *keybind_create(const char *keybind); /** * parse_modifier - parse a string containing a single modifier name (e.g. "S") * into the represented modifier value. returns 0 for invalid modifier names. * @symname: modifier name */ uint32_t parse_modifier(const char *symname); bool keybind_the_same(struct keybind *a, struct keybind *b); void keybind_update_keycodes(struct server *server); #endif /* LABWC_KEYBIND_H */ labwc-0.7.1/include/config/libinput.h000066400000000000000000000024101457044301200174730ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_LIBINPUT_H #define LABWC_LIBINPUT_H #include #include #include enum lab_libinput_device_type { LAB_LIBINPUT_DEVICE_NONE = 0, LAB_LIBINPUT_DEVICE_DEFAULT, LAB_LIBINPUT_DEVICE_TOUCH, LAB_LIBINPUT_DEVICE_TOUCHPAD, LAB_LIBINPUT_DEVICE_NON_TOUCH, }; struct libinput_category { enum lab_libinput_device_type type; char *name; struct wl_list link; float pointer_speed; int natural_scroll; int left_handed; enum libinput_config_tap_state tap; enum libinput_config_tap_button_map tap_button_map; int tap_and_drag; /* -1 or libinput_config_drag_state */ int drag_lock; /* -1 or libinput_config_drag_lock_state */ int accel_profile; /* -1 or libinput_config_accel_profile */ int middle_emu; /* -1 or libinput_config_middle_emulation_state */ int dwt; /* -1 or libinput_config_dwt_state */ int click_method; /* -1 or libinput_config_click_method */ int send_events_mode; /* -1 or libinput_config_send_events_mode */ }; enum lab_libinput_device_type get_device_type(const char *s); struct libinput_category *libinput_category_create(void); struct libinput_category *libinput_category_get_default(void); #endif /* LABWC_LIBINPUT_H */ labwc-0.7.1/include/config/mousebind.h000066400000000000000000000025551457044301200176440ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_MOUSEBIND_H #define LABWC_MOUSEBIND_H #include #include "ssd.h" #include "config/keybind.h" enum mouse_event { MOUSE_ACTION_NONE = 0, MOUSE_ACTION_DOUBLECLICK, MOUSE_ACTION_CLICK, MOUSE_ACTION_PRESS, MOUSE_ACTION_RELEASE, MOUSE_ACTION_DRAG, MOUSE_ACTION_SCROLL, }; enum direction { LAB_DIRECTION_INVALID = 0, LAB_DIRECTION_LEFT, LAB_DIRECTION_RIGHT, LAB_DIRECTION_UP, LAB_DIRECTION_DOWN, }; struct mousebind { enum ssd_part_type context; /* ex: BTN_LEFT, BTN_RIGHT from linux/input_event_codes.h */ uint32_t button; /* scroll direction; considered instead of button for scroll events */ enum direction direction; /* ex: WLR_MODIFIER_SHIFT | WLR_MODIFIER_LOGO */ uint32_t modifiers; /* ex: doubleclick, press, drag */ enum mouse_event mouse_event; struct wl_list actions; /* struct action.link */ struct wl_list link; /* struct rcxml.mousebinds */ bool pressed_in_context; /* used in click events */ }; enum mouse_event mousebind_event_from_str(const char *str); uint32_t mousebind_button_from_str(const char *str, uint32_t *modifiers); enum direction mousebind_direction_from_str(const char *str, uint32_t *modifiers); struct mousebind *mousebind_create(const char *context); bool mousebind_the_same(struct mousebind *a, struct mousebind *b); #endif /* LABWC_MOUSEBIND_H */ labwc-0.7.1/include/config/rcxml.h000066400000000000000000000065051457044301200170030ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_RCXML_H #define LABWC_RCXML_H #include #include #include #include "common/border.h" #include "common/buf.h" #include "common/font.h" #include "config/touch.h" #include "config/tablet.h" #include "config/libinput.h" #include "resize_indicator.h" #include "theme.h" enum window_switcher_field_content { LAB_FIELD_NONE = 0, LAB_FIELD_TYPE, LAB_FIELD_IDENTIFIER, LAB_FIELD_TRIMMED_IDENTIFIER, LAB_FIELD_TITLE, }; enum view_placement_policy { LAB_PLACE_CENTER = 0, LAB_PLACE_CURSOR, LAB_PLACE_AUTOMATIC }; enum adaptive_sync_mode { LAB_ADAPTIVE_SYNC_DISABLED, LAB_ADAPTIVE_SYNC_ENABLED, LAB_ADAPTIVE_SYNC_FULLSCREEN, }; enum tiling_events_mode { LAB_TILING_EVENTS_NEVER = 0, LAB_TILING_EVENTS_REGION = 1 << 0, LAB_TILING_EVENTS_EDGE = 1 << 1, LAB_TILING_EVENTS_ALWAYS = (LAB_TILING_EVENTS_REGION | LAB_TILING_EVENTS_EDGE), }; struct usable_area_override { struct border margin; char *output; struct wl_list link; /* struct rcxml.usable_area_overrides */ }; struct window_switcher_field { enum window_switcher_field_content content; int width; struct wl_list link; /* struct rcxml.window_switcher.fields */ }; struct rcxml { /* from command line */ char *config_dir; char *config_file; bool merge_config; /* core */ bool xdg_shell_server_side_deco; int gap; enum adaptive_sync_mode adaptive_sync; bool allow_tearing; bool reuse_output_mode; enum view_placement_policy placement_policy; /* focus */ bool focus_follow_mouse; bool focus_follow_mouse_requires_movement; bool raise_on_focus; /* theme */ char *theme_name; int corner_radius; bool ssd_keep_border; struct font font_activewindow; struct font font_inactivewindow; struct font font_menuitem; struct font font_osd; /* Pointer to current theme */ struct theme *theme; /* */ struct wl_list usable_area_overrides; /* keyboard */ int repeat_rate; int repeat_delay; bool kb_numlock_enable; bool kb_layout_per_window; struct wl_list keybinds; /* struct keybind.link */ /* mouse */ long doubleclick_time; /* in ms */ struct wl_list mousebinds; /* struct mousebind.link */ double scroll_factor; /* touch tablet */ struct wl_list touch_configs; /* graphics tablet */ struct tablet_config { char *output_name; struct wlr_fbox box; enum rotation rotation; uint16_t button_map_count; struct button_map_entry button_map[BUTTON_MAP_MAX]; } tablet; /* libinput */ struct wl_list libinput_categories; /* resistance */ int screen_edge_strength; int window_edge_strength; /* window snapping */ int snap_edge_range; bool snap_top_maximize; enum tiling_events_mode snap_tiling_events_mode; enum resize_indicator_mode resize_indicator; struct { int popuptime; int min_nr_workspaces; struct wl_list workspaces; /* struct workspace.link */ } workspace_config; /* Regions */ struct wl_list regions; /* struct region.link */ struct { bool show; bool preview; bool outlines; struct wl_list fields; /* struct window_switcher_field.link */ } window_switcher; struct wl_list window_rules; /* struct window_rule.link */ }; extern struct rcxml rc; void rcxml_parse_xml(struct buf *b); void rcxml_read(const char *filename); void rcxml_finish(void); #endif /* LABWC_RCXML_H */ labwc-0.7.1/include/config/session.h000066400000000000000000000010531457044301200173320ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_SESSION_H #define LABWC_SESSION_H /** * session_environment_init - set enrivonment variables based on = * pairs in `${XDG_CONFIG_DIRS:-/etc/xdg}/lawbc/environment` with user override * in `${XDG_CONFIG_HOME:-$HOME/.config}` */ void session_environment_init(void); /** * session_autostart_init - run autostart file as shell script * Note: Same as `sh ~/.config/labwc/autostart` (or equivalent XDG config dir) */ void session_autostart_init(void); #endif /* LABWC_SESSION_H */ labwc-0.7.1/include/config/tablet.h000066400000000000000000000012471457044301200171270ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_TABLET_CONFIG_H #define LABWC_TABLET_CONFIG_H #include enum rotation { LAB_ROTATE_NONE = 0, LAB_ROTATE_90, LAB_ROTATE_180, LAB_ROTATE_270, }; #define BUTTON_MAP_MAX 16 struct button_map_entry { uint32_t from; uint32_t to; }; double tablet_get_dbl_if_positive(const char *content, const char *name); enum rotation tablet_parse_rotation(int value); uint32_t tablet_button_from_str(const char *button); void tablet_button_mapping_add(uint32_t from, uint32_t to); void tablet_load_default_button_mappings(void); uint32_t tablet_get_mapped_button(uint32_t src_button); #endif /* LABWC_TABLET_CONFIG_H */ labwc-0.7.1/include/config/touch.h000066400000000000000000000005571457044301200170010ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_TOUCH_CONFIG_H #define LABWC_TOUCH_CONFIG_H #include struct touch_config_entry { char *device_name; char *output_name; struct wl_list link; /* struct rcxml.touch_configs */ }; struct touch_config_entry *touch_find_config_for_device(char *device_name); #endif /* LABWC_TOUCH_CONFIG_H */ labwc-0.7.1/include/debug.h000066400000000000000000000002631457044301200154720ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_DEBUG_H #define LABWC_DEBUG_H struct server; void debug_dump_scene(struct server *server); #endif /* LABWC_DEBUG_H */ labwc-0.7.1/include/decorations.h000066400000000000000000000006571457044301200167250ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_DECORATIONS_H #define LABWC_DECORATIONS_H struct server; struct view; struct wlr_surface; void kde_server_decoration_init(struct server *server); void xdg_server_decoration_init(struct server *server); void kde_server_decoration_update_default(void); void kde_server_decoration_set_view(struct view *view, struct wlr_surface *surface); #endif /* LABWC_DECORATIONS_H */ labwc-0.7.1/include/dnd.h000066400000000000000000000005241457044301200151510ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_DND_H #define LABWC_DND_H #include struct seat; void dnd_init(struct seat *seat); void dnd_icons_show(struct seat *seat, bool show); void dnd_icons_move(struct seat *seat, double x, double y); void dnd_finish(struct seat *seat); #endif /* LABWC_DND_H */ labwc-0.7.1/include/edges.h000066400000000000000000000100271457044301200154720ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_EDGES_H #define LABWC_EDGES_H #include #include #include #include "common/macros.h" struct border; struct output; struct server; struct view; struct wlr_box; static inline int clipped_add(int a, int b) { if (b > 0) { return a >= (INT_MAX - b) ? INT_MAX : (a + b); } else if (b < 0) { return a <= (INT_MIN - b) ? INT_MIN : (a + b); } return a; } static inline int clipped_sub(int a, int b) { if (b > 0) { return a <= (INT_MIN + b) ? INT_MIN : (a - b); } else if (b < 0) { return a >= (INT_MAX + b) ? INT_MAX : (a - b); } return a; } static inline int edge_get_best(int next, int edge, bool decreasing) { if (!BOUNDED_INT(next)) { /* Any bounded edge beats an unbounded next */ return BOUNDED_INT(edge) ? edge : next; } /* No unbounded edge ever beats next */ if (!BOUNDED_INT(edge)) { return next; } /* Max edge wins for decreasing moves, min edge for increasing */ return decreasing ? MAX(next, edge) : MIN(next, edge); } struct edge { /* Position of an edge along the axis perpendicular to it */ int offset; /* Limits of edge along axis parallel to it */ int min; int max; }; /* * edge_validator_t - edge validator signature * @best: pointer to the current "best" edge * @current: current position of a moving edge * @target: position to which the moving edge will be moved * @oppose: opposing edge of encountered region * @align: aligned edge of encountered region * @lesser: true if the moving edge is top or left, false otherwise * * This function will be used by edge_find_neighbors and edge_find_outputs to * validate and select the "best" output or neighbor edge against which a * moving edge should be snapped. The moving edge has current position * "current" and desired position "target". The validator should determine * whether motion of the crosses the given opposed and aligned edges of a trial * region and should be considered a snap point. * * Opposing edges are on the opposite side of the target region from the moving * edge (i.e., left <-> right, top <-> bottom). When the moving edge snaps to * an opposing edge, the view should maintain the configured gap. Aligned edges * are on the same side of the target region from the moving edge (i.e., * left <-> left, right <-> right, top <-> top, bottom <-> bottom). When the * moving edge snaps to an aligned edge, the view should *not* include a gap. * * If window gaps are configured, all edges will be offset as appropriate to * reflect the desired padding. Thus, the validator should generally compare * the given current or target values directly to the opposing and aligned edge * without regard for rc.gap. * * Any edge may take the values INT_MIN or INT_MAX to indicate that the edge * should be effectively ignored. Should the validator decide that a given * region edge (oppose or align) should be a preferred snap point, it should * update the value of *best accordingly. */ typedef void (*edge_validator_t)(int *best, struct edge current, struct edge target, struct edge oppose, struct edge align, bool lesser); void edges_initialize(struct border *edges); void edges_adjust_geom(struct view *view, struct border edges, uint32_t resize_edges, struct wlr_box *geom); void edges_find_neighbors(struct border *nearest_edges, struct view *view, struct wlr_box target, struct output *output, edge_validator_t validator, bool use_pending, bool ignore_hidden); void edges_find_outputs(struct border *nearest_edges, struct view *view, struct wlr_box target, struct output *output, edge_validator_t validator, bool use_pending); void edges_adjust_move_coords(struct view *view, struct border edges, int *x, int *y, bool use_pending); void edges_adjust_resize_geom(struct view *view, struct border edges, uint32_t resize_edges, struct wlr_box *geom, bool use_pending); bool edges_traverse_edge(struct edge current, struct edge target, struct edge edge); void edges_calculate_visibility(struct server *server, struct view *ignored_view); #endif /* LABWC_EDGES_H */ labwc-0.7.1/include/idle.h000066400000000000000000000004421457044301200153200ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_IDLE_H #define LABWC_IDLE_H struct wl_display; struct wlr_seat; void idle_manager_create(struct wl_display *display, struct wlr_seat *wlr_seat); void idle_manager_notify_activity(struct wlr_seat *seat); #endif /* LABWC_IDLE_H */ labwc-0.7.1/include/input/000077500000000000000000000000001457044301200153715ustar00rootroot00000000000000labwc-0.7.1/include/input/cursor.h000066400000000000000000000076221457044301200170660ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_CURSOR_H #define LABWC_CURSOR_H #include #include #include "ssd.h" struct view; struct seat; struct server; struct wlr_surface; struct wlr_scene_node; enum wlr_button_state; /* Cursors used internally by labwc */ enum lab_cursors { LAB_CURSOR_CLIENT = 0, LAB_CURSOR_DEFAULT, LAB_CURSOR_GRAB, LAB_CURSOR_RESIZE_NW, LAB_CURSOR_RESIZE_N, LAB_CURSOR_RESIZE_NE, LAB_CURSOR_RESIZE_E, LAB_CURSOR_RESIZE_SE, LAB_CURSOR_RESIZE_S, LAB_CURSOR_RESIZE_SW, LAB_CURSOR_RESIZE_W, LAB_CURSOR_COUNT }; struct cursor_context { struct view *view; struct wlr_scene_node *node; struct wlr_surface *surface; enum ssd_part_type type; double sx, sy; }; /** * get_cursor_context - find view and scene_node at cursor * * Behavior if node points to a surface: * - If surface is a layer-surface, type will be * set to LAB_SSD_LAYER_SURFACE and view will be NULL. * * - If surface is a 'lost' unmanaged xsurface (one * with a never-mapped parent view), type will * be set to LAB_SSD_UNMANAGED and view will be NULL. * * 'Lost' unmanaged xsurfaces are usually caused by * X11 applications opening popups without setting * the main window as parent. Example: VLC submenus. * * - Any other surface will cause type to be set to * LAB_SSD_CLIENT and return the attached view. * * Behavior if node points to internal elements: * - type will be set to the appropriate enum value * and view will be NULL if the node is not part of the SSD. * * If no node is found for the given layout coordinates, * type will be set to LAB_SSD_ROOT and view will be NULL. * */ struct cursor_context get_cursor_context(struct server *server); /** * cursor_set - set cursor icon * @seat - current seat * @cursor - name of cursor, for example LAB_CURSOR_DEFAULT or LAB_CURSOR_GRAB */ void cursor_set(struct seat *seat, enum lab_cursors cursor); /** * cursor_get_resize_edges - calculate resize edge based on cursor position * @cursor - the current cursor (usually server->seat.cursor) * @cursor_context - result of get_cursor_context() * * Calculates the resize edge combination that is most appropriate based * on the current view and cursor position in relation to each other. * * This is mostly important when either resizing a window using a * keyboard modifier or when using the Resize action from a keybind. */ uint32_t cursor_get_resize_edges(struct wlr_cursor *cursor, struct cursor_context *ctx); /** * cursor_get_from_edge - translate wlroots edge enum to lab_cursor enum * @resize_edges - WLR_EDGE_ combination like WLR_EDGE_TOP | WLR_EDGE_RIGHT * * Returns LAB_CURSOR_DEFAULT on WLR_EDGE_NONE * Returns the appropriate lab_cursors enum if @resize_edges * is one of the 4 corners or one of the 4 edges. * * Asserts on invalid edge combinations like WLR_EDGE_LEFT | WLR_EDGE_RIGHT */ enum lab_cursors cursor_get_from_edge(uint32_t resize_edges); /** * cursor_update_focus - update cursor focus, may update the cursor icon * @server - server * * This can be used to give the mouse focus to the surface under the cursor * or to force an update of the cursor icon by sending an exit and enter * event to an already focused surface. */ void cursor_update_focus(struct server *server); /** * cursor_update_image - re-set the labwc cursor image * @seat - seat * * This can be used to update the cursor image on output scale changes. * If the current cursor image was not set by labwc but some client * this is a no-op. */ void cursor_update_image(struct seat *seat); void cursor_init(struct seat *seat); void cursor_emulate_move_absolute(struct seat *seat, struct wlr_input_device *device, double x, double y, uint32_t time_msec); void cursor_emulate_button(struct seat *seat, uint32_t button, enum wlr_button_state state, uint32_t time_msec); void cursor_finish(struct seat *seat); #endif /* LABWC_CURSOR_H */ labwc-0.7.1/include/input/gestures.h000066400000000000000000000003341457044301200174030ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_GESTURES_H #define LABWC_GESTURES_H struct seat; void gestures_init(struct seat *seat); void gestures_finish(struct seat *seat); #endif /* LABWC_GESTURES_H */ labwc-0.7.1/include/input/input.h000066400000000000000000000003371457044301200167040ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_INPUT_H #define LABWC_INPUT_H struct seat; void input_handlers_init(struct seat *seat); void input_handlers_finish(struct seat *seat); #endif /* LABWC_INPUT_H */ labwc-0.7.1/include/input/key-state.h000066400000000000000000000020501457044301200174450ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_KEY_STATE_H #define LABWC_KEY_STATE_H #include #include /* * All keycodes in these functions are (Linux) libinput evdev scancodes which is * what 'wlr_keyboard' uses (e.g. 'seat->keyboard_group->keyboard->keycodes'). * Note: These keycodes are different to XKB scancodes by a value of 8. */ /** * key_state_pressed_sent_keycodes - generate array of pressed+sent keys * Note: The array is generated by subtracting any bound keys from _all_ pressed * keys (because bound keys were not forwarded to clients). */ uint32_t *key_state_pressed_sent_keycodes(void); int key_state_nr_pressed_sent_keycodes(void); void key_state_set_pressed(uint32_t keycode, bool is_pressed, bool is_modifier); void key_state_store_pressed_key_as_bound(uint32_t keycode); bool key_state_corresponding_press_event_was_bound(uint32_t keycode); void key_state_bound_key_remove(uint32_t keycode); int key_state_nr_bound_keys(void); int key_state_nr_pressed_keys(void); #endif /* LABWC_KEY_STATE_H */ labwc-0.7.1/include/input/keyboard.h000066400000000000000000000013401457044301200173400ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_KEYBOARD_H #define LABWC_KEYBOARD_H #include #include struct seat; struct keyboard; struct wlr_keyboard; void keyboard_configure(struct seat *seat, struct wlr_keyboard *kb, bool is_virtual); void keyboard_group_init(struct seat *seat); void keyboard_group_finish(struct seat *seat); void keyboard_setup_handlers(struct keyboard *keyboard); void keyboard_set_numlock(struct wlr_keyboard *keyboard); void keyboard_update_layout(struct seat *seat, xkb_layout_index_t layout); void keyboard_cancel_keybind_repeat(struct keyboard *keyboard); bool keyboard_any_modifiers_pressed(struct wlr_keyboard *keyboard); #endif /* LABWC_KEYBOARD_H */ labwc-0.7.1/include/input/tablet.h000066400000000000000000000010521457044301200170130ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_TABLET_H #define LABWC_TABLET_H #include struct seat; struct wlr_device; struct wlr_input_device; struct drawing_tablet { struct seat *seat; struct wlr_tablet *tablet; double x, y; struct { struct wl_listener axis; struct wl_listener tip; struct wl_listener button; struct wl_listener destroy; // no interest in proximity events } handlers; }; void tablet_init(struct seat *seat, struct wlr_input_device *wlr_input_device); #endif /* LABWC_TABLET_H */ labwc-0.7.1/include/input/tablet_pad.h000066400000000000000000000012731457044301200176440ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_TABLET_PAD_H #define LABWC_TABLET_PAD_H #include struct seat; struct wlr_device; struct wlr_input_device; #define LAB_BTN_PAD 0x0 #define LAB_BTN_PAD2 0x1 #define LAB_BTN_PAD3 0x2 #define LAB_BTN_PAD4 0x3 #define LAB_BTN_PAD5 0x4 #define LAB_BTN_PAD6 0x5 #define LAB_BTN_PAD7 0x6 #define LAB_BTN_PAD8 0x7 #define LAB_BTN_PAD9 0x8 struct drawing_tablet_pad { struct seat *seat; struct wlr_tablet_pad *tablet; struct { struct wl_listener button; struct wl_listener destroy; } handlers; }; void tablet_pad_init(struct seat *seat, struct wlr_input_device *wlr_input_device); #endif /* LABWC_TABLET_PAD_H */ labwc-0.7.1/include/input/touch.h000066400000000000000000000003151457044301200166630ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_TOUCH_H #define LABWC_TOUCH_H struct seat; void touch_init(struct seat *seat); void touch_finish(struct seat *seat); #endif /* LABWC_TOUCH_H */ labwc-0.7.1/include/labwc.h000066400000000000000000000402401457044301200154730ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_H #define LABWC_H #include "config.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config/keybind.h" #include "config/rcxml.h" #include "input/cursor.h" #include "regions.h" #include "session-lock.h" #if HAVE_NLS #include #include #define _ gettext #else #define _(s) (s) #endif #define XCURSOR_DEFAULT "left_ptr" #define XCURSOR_SIZE 24 enum input_mode { LAB_INPUT_STATE_PASSTHROUGH = 0, LAB_INPUT_STATE_MOVE, LAB_INPUT_STATE_RESIZE, LAB_INPUT_STATE_MENU, }; struct input { struct wlr_input_device *wlr_input_device; struct seat *seat; struct wl_listener destroy; struct wl_list link; /* seat.inputs */ }; /* * Virtual keyboards should not belong to seat->keyboard_group. As a result we * need to be able to ascertain which wlr_keyboard key/modifier events come from * and we achieve that by using `struct keyboard` which inherits `struct input` * and adds keybord specific listeners and a wlr_keyboard pointer. */ struct keyboard { struct input base; struct wlr_keyboard *wlr_keyboard; bool is_virtual; struct wl_listener modifier; struct wl_listener key; /* key repeat for compositor keybinds */ uint32_t keybind_repeat_keycode; int32_t keybind_repeat_rate; struct wl_event_source *keybind_repeat; }; struct seat { struct wlr_seat *seat; struct server *server; struct wlr_keyboard_group *keyboard_group; struct wl_list touch_points; /* struct touch_point.link */ /* * Enum of most recent server-side cursor image. Set by * cursor_set(). Cleared when a client surface is entered * (in that case the client is expected to set its own cursor image). */ enum lab_cursors server_cursor; struct wlr_cursor *cursor; struct wlr_xcursor_manager *xcursor_manager; struct { double x, y; } smooth_scroll_offset; struct wlr_pointer_constraint_v1 *current_constraint; /* In support for ToggleKeybinds */ uint32_t nr_inhibited_keybind_views; /* Used to hide the workspace OSD after switching workspaces */ struct wl_event_source *workspace_osd_timer; bool workspace_osd_shown_by_modifier; /* if set, views cannot receive focus */ struct wlr_layer_surface_v1 *focused_layer; /** * pressed view/surface/node will usually be NULL and is only set on * button press while the mouse is over a view or surface, and reset * to NULL on button release. * It is used to send cursor motion events to a surface even though * the cursor has left the surface in the meantime. * * This allows to keep dragging a scrollbar or selecting text even * when moving outside of the window. * * Both (view && !surface) and (surface && !view) are possible. */ struct { struct view *view; struct wlr_scene_node *node; struct wlr_surface *surface; struct wlr_surface *toplevel; uint32_t resize_edges; } pressed; struct { bool active; struct { struct wl_listener request; struct wl_listener start; struct wl_listener destroy; } events; struct wlr_scene_tree *icons; } drag; /* Private use by regions.c */ struct region *region_active; struct region_overlay region_overlay; /* Used to prevent region snapping when starting a move with A-Left */ bool region_prevent_snap; struct wl_client *active_client_while_inhibited; struct wl_list inputs; struct wl_listener new_input; struct wl_listener focus_change; struct wl_listener cursor_motion; struct wl_listener cursor_motion_absolute; struct wl_listener cursor_button; struct wl_listener cursor_axis; struct wl_listener cursor_frame; struct wlr_pointer_gestures_v1 *pointer_gestures; struct wl_listener pinch_begin; struct wl_listener pinch_update; struct wl_listener pinch_end; struct wl_listener swipe_begin; struct wl_listener swipe_update; struct wl_listener swipe_end; struct wl_listener request_cursor; struct wl_listener request_set_shape; struct wl_listener request_set_selection; struct wl_listener request_set_primary_selection; struct wl_listener touch_down; struct wl_listener touch_up; struct wl_listener touch_motion; struct wl_listener touch_frame; struct wl_listener constraint_commit; struct wl_listener pressed_surface_destroy; struct wlr_virtual_pointer_manager_v1 *virtual_pointer; struct wl_listener virtual_pointer_new; struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; struct wl_listener virtual_keyboard_new; }; struct lab_data_buffer; struct workspace; struct server { struct wl_display *wl_display; struct wl_event_loop *wl_event_loop; /* Can be used for timer events */ struct wlr_renderer *renderer; struct wlr_allocator *allocator; struct wlr_backend *backend; struct headless { struct wlr_backend *backend; char pending_output_name[4096]; } headless; struct wlr_session *session; struct wlr_xdg_shell *xdg_shell; struct wlr_layer_shell_v1 *layer_shell; struct wl_listener new_xdg_surface; struct wl_listener new_layer_surface; struct wl_listener kde_server_decoration; struct wl_listener xdg_toplevel_decoration; #if HAVE_XWAYLAND struct wlr_xwayland *xwayland; struct wl_listener xwayland_server_ready; struct wl_listener xwayland_xwm_ready; struct wl_listener xwayland_new_surface; #endif struct wlr_input_inhibit_manager *input_inhibit; struct wl_listener input_inhibit_activate; struct wl_listener input_inhibit_deactivate; struct wlr_xdg_activation_v1 *xdg_activation; struct wl_listener xdg_activation_request; struct wl_list views; struct wl_list unmanaged_surfaces; struct seat seat; struct wlr_scene *scene; struct wlr_scene_output_layout *scene_layout; /* cursor interactive */ enum input_mode input_mode; struct view *grabbed_view; double grab_x, grab_y; struct wlr_box grab_box; uint32_t resize_edges; /* * 'active_view' is generally the view with keyboard-focus, updated with * each "focus change". This view is drawn with "active" SSD coloring. * * The exception is when a layer-shell client takes keyboard-focus in * which case the currently active view stays active. This is important * for foreign-toplevel protocol. */ struct view *active_view; /* * Most recently raised view. Used to avoid unnecessarily * raising the same view over and over. */ struct view *last_raised_view; struct ssd_hover_state *ssd_hover_state; /* Tree for all non-layer xdg/xwayland-shell surfaces */ struct wlr_scene_tree *view_tree; /* * Popups need to be rendered above always-on-top views, so we reparent * them to this dedicated tree */ struct wlr_scene_tree *xdg_popup_tree; /* Tree for all non-layer xdg/xwayland-shell surfaces with always-on-top/below */ struct wlr_scene_tree *view_tree_always_on_top; struct wlr_scene_tree *view_tree_always_on_bottom; #if HAVE_XWAYLAND /* Tree for unmanaged xsurfaces without initialized view (usually popups) */ struct wlr_scene_tree *unmanaged_tree; #endif /* Tree for built in menu */ struct wlr_scene_tree *menu_tree; /* Workspaces */ struct wl_list workspaces; /* struct workspace.link */ struct workspace *workspace_current; struct workspace *workspace_last; struct wl_list outputs; struct wl_listener new_output; struct wlr_output_layout *output_layout; struct wl_listener output_layout_change; struct wlr_output_manager_v1 *output_manager; struct wl_listener output_manager_test; struct wl_listener output_manager_apply; /* * While an output layout change is in process, this counter is * non-zero and causes change-events from the wlr_output_layout * to be ignored (to prevent, for example, moving views in a * transitory layout state). Once the counter reaches zero, * do_output_layout_change() must be called explicitly. */ int pending_output_layout_change; struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1; struct wl_listener gamma_control_set_gamma; struct session_lock *session_lock; struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; struct wlr_drm_lease_v1_manager *drm_lease_manager; struct wl_listener drm_lease_request; struct wlr_output_power_manager_v1 *output_power_manager_v1; struct wl_listener output_power_manager_set_mode; struct wlr_relative_pointer_manager_v1 *relative_pointer_manager; struct wlr_pointer_constraints_v1 *constraints; struct wl_listener new_constraint; struct wlr_tearing_control_manager_v1 *tearing_control; struct wl_listener tearing_new_object; /* Set when in cycle (alt-tab) mode */ struct osd_state { struct view *cycle_view; bool preview_was_enabled; struct wlr_scene_node *preview_node; struct wlr_scene_node *preview_anchor; struct multi_rect *preview_outline; } osd_state; struct theme *theme; struct menu *menu_current; struct wl_list menus; }; #define LAB_NR_LAYERS (4) struct output { struct wl_list link; /* server.outputs */ struct server *server; struct wlr_output *wlr_output; struct wlr_scene_output *scene_output; struct wlr_scene_tree *layer_tree[LAB_NR_LAYERS]; struct wlr_scene_tree *layer_popup_tree; struct wlr_scene_tree *osd_tree; struct wlr_scene_tree *session_lock_tree; struct wlr_scene_buffer *workspace_osd; struct wlr_box usable_area; struct wl_list regions; /* struct region.link */ struct lab_data_buffer *osd_buffer; struct wl_listener destroy; struct wl_listener frame; struct wl_listener request_state; bool leased; bool gamma_lut_changed; }; #undef LAB_NR_LAYERS struct constraint { struct seat *seat; struct wlr_pointer_constraint_v1 *constraint; struct wl_listener destroy; }; void xdg_popup_create(struct view *view, struct wlr_xdg_popup *wlr_popup); void xdg_shell_init(struct server *server); void foreign_toplevel_handle_create(struct view *view); void foreign_toplevel_update_outputs(struct view *view); /* * desktop.c routines deal with a collection of views * * Definition of a few keywords used in desktop.c * raise - Bring view to front. * focus - Give keyboard focus to view. * activate - Set view surface as active so that client window decorations * are painted to show that the window is active,typically by * using a different color. Although xdg-shell protocol says you * cannot assume this means that the window actually has keyboard * or pointer focus, in this compositor are they called together. */ /** * desktop_focus_view() - do multiple things to make a view "active" and * ready to use: * - unminimize * - switch to the workspace it's on * - give input (keyboard) focus * - optionally raise above other views * * It's okay to call this function even if the view isn't mapped or the * session is locked/input is inhibited; it will simply do nothing. */ void desktop_focus_view(struct view *view, bool raise); /** * desktop_focus_view_or_surface() - like desktop_focus_view() but can * also focus other (e.g. xwayland-unmanaged) surfaces */ void desktop_focus_view_or_surface(struct seat *seat, struct view *view, struct wlr_surface *surface, bool raise); void desktop_arrange_all_views(struct server *server); void desktop_focus_output(struct output *output); struct view *desktop_topmost_focusable_view(struct server *server); /** * Toggles the (output local) visibility of the layershell top layer * based on the existence of a fullscreen window on the current workspace. */ void desktop_update_top_layer_visiblity(struct server *server); enum lab_cycle_dir { LAB_CYCLE_DIR_NONE, LAB_CYCLE_DIR_FORWARD, LAB_CYCLE_DIR_BACKWARD, }; /** * desktop_cycle_view - return view to 'cycle' to * @start_view: reference point for finding next view to cycle to * Note: If !start_view, the second focusable view is returned */ struct view *desktop_cycle_view(struct server *server, struct view *start_view, enum lab_cycle_dir dir); /** * desktop_focus_topmost_view() - focus the topmost view on the current * workspace, skipping views that claim not to want focus (those can * still be focused by explicit request, e.g. by clicking in them). * * This function is typically called when the focused view is hidden * (closes, is minimized, etc.) to focus the "next" view underneath. */ void desktop_focus_topmost_view(struct server *server); void seat_init(struct server *server); void seat_finish(struct server *server); void seat_reconfigure(struct server *server); void seat_focus_surface(struct seat *seat, struct wlr_surface *surface); /** * seat_focus_lock_surface() - ONLY to be called from session-lock.c to * focus lock screen surfaces. Use seat_focus_surface() otherwise. */ void seat_focus_lock_surface(struct seat *seat, struct wlr_surface *surface); void seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer); void seat_set_pressed(struct seat *seat, struct view *view, struct wlr_scene_node *node, struct wlr_surface *surface, struct wlr_surface *toplevel, uint32_t resize_edges); void seat_reset_pressed(struct seat *seat); void seat_output_layout_changed(struct seat *seat); void interactive_begin(struct view *view, enum input_mode mode, uint32_t edges); void interactive_finish(struct view *view); void interactive_cancel(struct view *view); void output_init(struct server *server); void output_manager_init(struct server *server); struct output *output_from_wlr_output(struct server *server, struct wlr_output *wlr_output); struct output *output_from_name(struct server *server, const char *name); struct output *output_nearest_to(struct server *server, int lx, int ly); struct output *output_nearest_to_cursor(struct server *server); bool output_is_usable(struct output *output); void output_update_usable_area(struct output *output); void output_update_all_usable_areas(struct server *server, bool layout_changed); struct wlr_box output_usable_area_in_layout_coords(struct output *output); struct wlr_box output_usable_area_scaled(struct output *output); void handle_output_power_manager_set_mode(struct wl_listener *listener, void *data); void output_add_virtual(struct server *server, const char *output_name); void output_remove_virtual(struct server *server, const char *output_name); void output_enable_adaptive_sync(struct wlr_output *output, bool enabled); void new_tearing_hint(struct wl_listener *listener, void *data); void server_init(struct server *server); void server_start(struct server *server); void server_finish(struct server *server); /* Updates onscreen display 'alt-tab' buffer */ void osd_update(struct server *server); /* Closes the OSD */ void osd_finish(struct server *server); /* Moves preview views back into their original stacking order and state */ void osd_preview_restore(struct server *server); /* Notify OSD about a destroying view */ void osd_on_view_destroy(struct view *view); /* * wlroots "input inhibitor" extension (required for swaylock) blocks * any client other than the requesting client from receiving events */ bool input_inhibit_blocks_surface(struct seat *seat, struct wl_resource *resource); void create_constraint(struct wl_listener *listener, void *data); void constrain_cursor(struct server *server, struct wlr_pointer_constraint_v1 *constraint); #endif /* LABWC_H */ labwc-0.7.1/include/layers.h000066400000000000000000000016341457044301200157060ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_LAYERS_H #define LABWC_LAYERS_H #include #include struct server; struct output; struct lab_layer_surface { struct wlr_scene_layer_surface_v1 *scene_layer_surface; struct server *server; bool mapped; struct wl_listener map; struct wl_listener unmap; struct wl_listener surface_commit; struct wl_listener output_destroy; struct wl_listener node_destroy; struct wl_listener new_popup; }; struct lab_layer_popup { struct wlr_xdg_popup *wlr_popup; struct wlr_scene_tree *scene_tree; /* To simplify moving popup nodes from the bottom to the top layer */ struct wlr_box output_toplevel_sx_box; struct wl_listener commit; struct wl_listener destroy; struct wl_listener new_popup; }; void layers_init(struct server *server); void layers_arrange(struct output *output); #endif /* LABWC_LAYERS_H */ labwc-0.7.1/include/menu/000077500000000000000000000000001457044301200151765ustar00rootroot00000000000000labwc-0.7.1/include/menu/menu.h000066400000000000000000000061341457044301200163170ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_MENU_H #define LABWC_MENU_H #include /* forward declare arguments */ struct view; struct server; struct wl_list; struct wlr_scene_tree; struct wlr_scene_node; struct scaled_font_buffer; enum menu_align { LAB_MENU_OPEN_AUTO = 0, LAB_MENU_OPEN_LEFT = 1 << 0, LAB_MENU_OPEN_RIGHT = 1 << 1, LAB_MENU_OPEN_TOP = 1 << 2, LAB_MENU_OPEN_BOTTOM = 1 << 3, }; struct menu_scene { struct wlr_scene_tree *tree; struct wlr_scene_node *text; struct wlr_scene_node *background; struct scaled_font_buffer *buffer; }; struct menuitem { struct wl_list actions; struct menu *parent; struct menu *submenu; bool selectable; int height; int native_width; struct wlr_scene_tree *tree; struct menu_scene normal; struct menu_scene selected; struct wl_list link; /* menu.menuitems */ }; /* This could be the root-menu or a submenu */ struct menu { char *id; char *label; int item_height; struct menu *parent; struct { int width; int height; } size; struct wl_list menuitems; struct server *server; struct { struct menu *menu; struct menuitem *item; } selection; struct wlr_scene_tree *scene_tree; /* Used to match a window-menu to the view that triggered it. */ struct view *triggered_by_view; /* may be NULL */ struct wl_list link; /* server.menus */ }; /* For keyboard support */ void menu_item_select_next(struct server *server); void menu_item_select_previous(struct server *server); void menu_submenu_enter(struct server *server); void menu_submenu_leave(struct server *server); bool menu_call_selected_actions(struct server *server); void menu_init(struct server *server); void menu_finish(struct server *server); /** * menu_get_by_id - get menu by id * * @id id string defined in menu.xml like "root-menu" */ struct menu *menu_get_by_id(struct server *server, const char *id); /** * menu_open - open menu on position (x, y) * * This function will close server->menu_current, open the * new menu and assign @menu to server->menu_current. * * Additionally, server->input_mode wil be set to LAB_INPUT_STATE_MENU. */ void menu_open(struct menu *menu, int x, int y); /** * menu_process_cursor_motion * * - handles hover effects * - may open/close submenus */ void menu_process_cursor_motion(struct wlr_scene_node *node); /** * menu_call_actions - call actions associated with a menu node * * If menuitem connected to @node does not just open a submenu: * - associated actions will be called * - server->menu_current will be closed * - server->menu_current will be set to NULL * * Returns true if actions have actually been executed */ bool menu_call_actions(struct wlr_scene_node *node); /** * menu_close_root- close root menu * * This function will close server->menu_current and set it to NULL. * Asserts that server->input_mode is set to LAB_INPUT_STATE_MENU. * * Additionally, server->input_mode wil be set to LAB_INPUT_STATE_PASSTHROUGH. */ void menu_close_root(struct server *server); /* menu_reconfigure - reload theme and content */ void menu_reconfigure(struct server *server); #endif /* LABWC_MENU_H */ labwc-0.7.1/include/meson.build000066400000000000000000000000761457044301200163770ustar00rootroot00000000000000configure_file(output: 'config.h', configuration: conf_data) labwc-0.7.1/include/node.h000066400000000000000000000047031457044301200153340ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_NODE_DESCRIPTOR_H #define LABWC_NODE_DESCRIPTOR_H #include struct view; struct lab_layer_surface; struct lab_layer_popup; struct menuitem; struct ssd_button; enum node_descriptor_type { LAB_NODE_DESC_NODE = 0, LAB_NODE_DESC_VIEW, LAB_NODE_DESC_XDG_POPUP, LAB_NODE_DESC_LAYER_SURFACE, LAB_NODE_DESC_LAYER_POPUP, LAB_NODE_DESC_MENUITEM, LAB_NODE_DESC_TREE, LAB_NODE_DESC_SSD_BUTTON, }; struct node_descriptor { enum node_descriptor_type type; void *data; struct wl_listener destroy; }; /** * node_descriptor_create - create node descriptor for wlr_scene_node user_data * * The node_descriptor will be destroyed automatically * once the scene_node it is attached to is destroyed. * * @scene_node: wlr_scene_node to attached node_descriptor to * @type: node descriptor type * @data: struct to point to as follows: * - LAB_NODE_DESC_VIEW struct view * - LAB_NODE_DESC_XDG_POPUP struct view * - LAB_NODE_DESC_LAYER_SURFACE struct lab_layer_surface * - LAB_NODE_DESC_LAYER_POPUP struct lab_layer_popup * - LAB_NODE_DESC_MENUITEM struct menuitem * - LAB_NODE_DESC_SSD_BUTTON struct ssd_button */ void node_descriptor_create(struct wlr_scene_node *scene_node, enum node_descriptor_type type, void *data); /** * node_view_from_node - return view struct from node * @wlr_scene_node: wlr_scene_node from which to return data */ struct view *node_view_from_node(struct wlr_scene_node *wlr_scene_node); /** * node_lab_surface_from_node - return lab_layer_surface struct from node * @wlr_scene_node: wlr_scene_node from which to return data */ struct lab_layer_surface *node_layer_surface_from_node( struct wlr_scene_node *wlr_scene_node); /** * node_layer_popup_from_node - return lab_layer_popup struct from node * @wlr_scene_node: wlr_scene_node from which to return data */ struct lab_layer_popup *node_layer_popup_from_node( struct wlr_scene_node *wlr_scene_node); /** * node_menuitem_from_node - return menuitem struct from node * @wlr_scene_node: wlr_scene_node from which to return data */ struct menuitem *node_menuitem_from_node( struct wlr_scene_node *wlr_scene_node); /** * node_ssd_button_from_node - return ssd_button struct from node * @wlr_scene_node: wlr_scene_node from which to return data */ struct ssd_button *node_ssd_button_from_node( struct wlr_scene_node *wlr_scene_node); #endif /* LABWC_NODE_DESCRIPTOR_H */ labwc-0.7.1/include/placement.h000066400000000000000000000004121457044301200163500ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_PLACEMENT_H #define LABWC_PLACEMENT_H #include #include #include "view.h" bool placement_find_best(struct view *view, struct wlr_box *geometry); #endif /* LABWC_PLACEMENT_H */ labwc-0.7.1/include/regions.h000066400000000000000000000050021457044301200160460ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_REGIONS_H #define LABWC_REGIONS_H #include struct seat; struct view; struct server; struct output; struct wl_list; struct wlr_box; struct multi_rect; /* Double use: rcxml.c for config and output.c for usage */ struct region { struct wl_list link; /* struct rcxml.regions, struct output.regions */ struct output *output; char *name; struct wlr_box geo; struct wlr_box percentage; struct { int x; int y; } center; }; struct region_overlay { struct wlr_scene_tree *tree; union { struct wlr_scene_rect *overlay; struct multi_rect *pixman_overlay; }; }; /* Returns true if we should show the region overlay or snap to region */ bool regions_should_snap(struct server *server); /** * regions_reconfigure*() - re-initializes all regions from struct rc. * * - all views are evacuated from the given output (or all of them) * - all output local regions are destroyed * - new output local regions are created from struct rc * - the region geometry is re-calculated */ void regions_reconfigure(struct server *server); void regions_reconfigure_output(struct output *output); /* re-calculate the geometry based on usable area */ void regions_update_geometry(struct output *output); /** * Mark all views which are currently region-tiled to the given output as * evacuated. This means that the view->tiled_region pointer is reset to * NULL but view->tiled_region_evacuate is set to a copy of the region name. * * The next time desktop_arrange_all_views() causes a call to * view_apply_region_geometry() it will try to find a new output and then * search for a region with the same name. If found, view->tiled_region will * be set to the new region and view->tiled_region_evacuate will be free'd. * * If no region with the old name is found (e.g. the user deleted or renamed * the region in rc.xml and caused a Reconfigure) the view will be reset to * non-tiled state and view->tiled_region_evacuate will be free'd. */ void regions_evacuate_output(struct output *output); /* Free all regions in given wl_list pointer */ void regions_destroy(struct seat *seat, struct wl_list *regions); /* Get output local region from cursor or name, may be NULL */ struct region *regions_from_cursor(struct server *server); struct region *regions_from_name(const char *region_name, struct output *output); void regions_show_overlay(struct view *view, struct seat *seat, struct region *region); void regions_hide_overlay(struct seat *seat); #endif /* LABWC_REGIONS_H */ labwc-0.7.1/include/resistance.h000066400000000000000000000004531457044301200165450ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_RESISTANCE_H #define LABWC_RESISTANCE_H #include "labwc.h" void resistance_move_apply(struct view *view, double *x, double *y); void resistance_resize_apply(struct view *view, struct wlr_box *new_view_geo); #endif /* LABWC_RESISTANCE_H */ labwc-0.7.1/include/resize_indicator.h000066400000000000000000000007741457044301200177500ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_RESIZE_INDICATOR_H #define LABWC_RESIZE_INDICATOR_H struct server; struct view; enum resize_indicator_mode { LAB_RESIZE_INDICATOR_NEVER = 0, LAB_RESIZE_INDICATOR_ALWAYS, LAB_RESIZE_INDICATOR_NON_PIXEL }; void resize_indicator_reconfigure(struct server *server); void resize_indicator_show(struct view *view); void resize_indicator_update(struct view *view); void resize_indicator_hide(struct view *view); #endif /* LABWC_RESIZE_INDICATOR_H */ labwc-0.7.1/include/session-lock.h000066400000000000000000000011671457044301200170210ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_SESSION_LOCK_H #define LABWC_SESSION_LOCK_H #include struct output; struct server; struct session_lock { struct wlr_session_lock_v1 *lock; struct wlr_surface *focused; bool abandoned; struct wl_list session_lock_outputs; struct wl_listener new_surface; struct wl_listener unlock; struct wl_listener destroy; }; void session_lock_init(struct server *server); void session_lock_output_create(struct session_lock *lock, struct output *output); void session_lock_update_for_layout_change(void); #endif /* LABWC_SESSION_LOCK_H */ labwc-0.7.1/include/snap.h000066400000000000000000000007431457044301200153500ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_SNAP_H #define LABWC_SNAP_H #include "common/border.h" #include "view.h" struct wlr_box; void snap_move_to_edge(struct view *view, enum view_edge direction, bool snap_to_windows, int *dx, int *dy); void snap_grow_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo); void snap_shrink_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo); #endif /* LABWC_SNAP_H */ labwc-0.7.1/include/ssd-internal.h000066400000000000000000000101051457044301200170030ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_SSD_INTERNAL_H #define LABWC_SSD_INTERNAL_H #include #include "common/macros.h" #include "ssd.h" #include "view.h" #define FOR_EACH(tmp, ...) \ { \ __typeof__(tmp) _x[] = { __VA_ARGS__, NULL }; \ size_t _i = 0; \ for ((tmp) = _x[_i]; _i < ARRAY_SIZE(_x) - 1; (tmp) = _x[++_i]) #define FOR_EACH_END } struct ssd_button { struct view *view; enum ssd_part_type type; struct wlr_scene_node *normal; struct wlr_scene_node *hover; struct wlr_scene_node *toggled; struct wlr_scene_node *toggled_hover; struct wlr_scene_node *background; struct wlr_scene_tree *icon_tree; struct wlr_scene_tree *hover_tree; struct wl_listener destroy; }; struct ssd_sub_tree { struct wlr_scene_tree *tree; struct wl_list parts; /* ssd_part.link */ }; struct ssd_state_title_width { int width; bool truncated; }; struct ssd { struct view *view; struct wlr_scene_tree *tree; /* * Cache for current values. * Used to detect actual changes so we * don't update things we don't have to. */ struct { bool was_maximized; /* To un-round corner buttons and toggle icon on maximize */ struct wlr_box geometry; struct ssd_state_title { char *text; struct ssd_state_title_width active; struct ssd_state_title_width inactive; } title; } state; /* An invisible area around the view which allows resizing */ struct ssd_sub_tree extents; /* The top of the view, containing buttons, title, .. */ struct { int height; struct wlr_scene_tree *tree; struct ssd_sub_tree active; struct ssd_sub_tree inactive; } titlebar; /* Borders allow resizing as well */ struct { struct wlr_scene_tree *tree; struct ssd_sub_tree active; struct ssd_sub_tree inactive; } border; /* * Space between the extremities of the view's wlr_surface * and the max extents of the server-side decorations. * For xdg-shell views with CSD, this margin is zero. */ struct border margin; }; struct ssd_part { enum ssd_part_type type; /* Buffer pointer. May be NULL */ struct scaled_font_buffer *buffer; /* This part represented in scene graph */ struct wlr_scene_node *node; /* Targeted geometry. May be NULL */ struct wlr_box *geometry; struct wl_list link; }; struct ssd_hover_state { struct view *view; struct ssd_button *button; }; struct wlr_buffer; struct wlr_scene_tree; /* SSD internal helpers to create various SSD elements */ /* TODO: Replace some common args with a struct */ struct ssd_part *add_scene_part( struct wl_list *part_list, enum ssd_part_type type); struct ssd_part *add_scene_rect( struct wl_list *list, enum ssd_part_type type, struct wlr_scene_tree *parent, int width, int height, int x, int y, float color[4]); struct ssd_part *add_scene_buffer( struct wl_list *list, enum ssd_part_type type, struct wlr_scene_tree *parent, struct wlr_buffer *buffer, int x, int y); struct ssd_part *add_scene_button( struct wl_list *part_list, enum ssd_part_type type, struct wlr_scene_tree *parent, float *bg_color, struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer, int x, struct view *view); void add_toggled_icon(struct wl_list *part_list, enum ssd_part_type type, struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer); struct ssd_part *add_scene_button_corner( struct wl_list *part_list, enum ssd_part_type type, enum ssd_part_type corner_type, struct wlr_scene_tree *parent, struct wlr_buffer *corner_buffer, struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer, int x, struct view *view); /* SSD internal helpers */ struct ssd_part *ssd_get_part( struct wl_list *part_list, enum ssd_part_type type); void ssd_destroy_parts(struct wl_list *list); /* SSD internal */ void ssd_titlebar_create(struct ssd *ssd); void ssd_titlebar_update(struct ssd *ssd); void ssd_titlebar_destroy(struct ssd *ssd); void ssd_border_create(struct ssd *ssd); void ssd_border_update(struct ssd *ssd); void ssd_border_destroy(struct ssd *ssd); void ssd_extents_create(struct ssd *ssd); void ssd_extents_update(struct ssd *ssd); void ssd_extents_destroy(struct ssd *ssd); #endif /* LABWC_SSD_INTERNAL_H */ labwc-0.7.1/include/ssd.h000066400000000000000000000056211457044301200152000ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_SSD_H #define LABWC_SSD_H #include #include "common/border.h" #define SSD_BUTTON_COUNT 4 #define SSD_BUTTON_WIDTH 26 #define SSD_EXTENDED_AREA 8 /* * Sequence these according to the order they should be processed for * press and hover events. Bear in mind that some of their respective * interactive areas overlap, so for example buttons need to come before title. */ enum ssd_part_type { LAB_SSD_NONE = 0, LAB_SSD_BUTTON_CLOSE, LAB_SSD_BUTTON_MAXIMIZE, LAB_SSD_BUTTON_ICONIFY, LAB_SSD_BUTTON_WINDOW_MENU, LAB_SSD_PART_TITLEBAR, LAB_SSD_PART_TITLE, LAB_SSD_PART_CORNER_TOP_LEFT, LAB_SSD_PART_CORNER_TOP_RIGHT, LAB_SSD_PART_CORNER_BOTTOM_RIGHT, LAB_SSD_PART_CORNER_BOTTOM_LEFT, LAB_SSD_PART_TOP, LAB_SSD_PART_RIGHT, LAB_SSD_PART_BOTTOM, LAB_SSD_PART_LEFT, LAB_SSD_CLIENT, LAB_SSD_FRAME, LAB_SSD_ROOT, LAB_SSD_MENU, LAB_SSD_OSD, LAB_SSD_LAYER_SURFACE, LAB_SSD_LAYER_SUBSURFACE, LAB_SSD_UNMANAGED, LAB_SSD_END_MARKER }; /* Forward declare arguments */ struct ssd; struct ssd_button; struct ssd_hover_state; struct view; struct wlr_scene; struct wlr_scene_node; /* * Public SSD API * * For convenience in dealing with non-SSD views, this API allows NULL * ssd/button/node arguments and attempts to do something sensible in * that case (e.g. no-op/return default values). * * NULL scene/view arguments are not allowed. */ struct ssd *ssd_create(struct view *view, bool active); struct border ssd_get_margin(const struct ssd *ssd); void ssd_update_margin(struct ssd *ssd); void ssd_set_active(struct ssd *ssd, bool active); void ssd_update_title(struct ssd *ssd); void ssd_update_geometry(struct ssd *ssd); void ssd_destroy(struct ssd *ssd); void ssd_titlebar_hide(struct ssd *ssd); void ssd_enable_keybind_inhibit_indicator(struct ssd *ssd, bool enable); void ssd_enable_shade(struct ssd *ssd, bool enable); struct ssd_hover_state *ssd_hover_state_new(void); void ssd_update_button_hover(struct wlr_scene_node *node, struct ssd_hover_state *hover_state); enum ssd_part_type ssd_button_get_type(const struct ssd_button *button); struct view *ssd_button_get_view(const struct ssd_button *button); /* Public SSD helpers */ enum ssd_part_type ssd_at(const struct ssd *ssd, struct wlr_scene *scene, double lx, double ly); enum ssd_part_type ssd_get_part_type(const struct ssd *ssd, struct wlr_scene_node *node); uint32_t ssd_resize_edges(enum ssd_part_type type); bool ssd_is_button(enum ssd_part_type type); bool ssd_part_contains(enum ssd_part_type whole, enum ssd_part_type candidate); /* TODO: clean up / update */ struct border ssd_thickness(struct view *view); struct wlr_box ssd_max_extents(struct view *view); /* SSD debug helpers */ bool ssd_debug_is_root_node(const struct ssd *ssd, struct wlr_scene_node *node); const char *ssd_debug_get_node_name(const struct ssd *ssd, struct wlr_scene_node *node); #endif /* LABWC_SSD_H */ labwc-0.7.1/include/theme.h000066400000000000000000000077441457044301200155210ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ /* * Theme engine for labwc * * Copyright Johan Malm 2020-2021 */ #ifndef LABWC_THEME_H #define LABWC_THEME_H #include #include enum lab_justification { LAB_JUSTIFY_LEFT, LAB_JUSTIFY_CENTER, LAB_JUSTIFY_RIGHT, }; struct theme { int border_width; int padding_height; int title_height; int menu_overlap_x; int menu_overlap_y; /* colors */ float window_active_border_color[4]; float window_inactive_border_color[4]; float window_toggled_keybinds_color[4]; float window_active_title_bg_color[4]; float window_inactive_title_bg_color[4]; float window_active_label_text_color[4]; float window_inactive_label_text_color[4]; enum lab_justification window_label_text_justify; /* button colors */ float window_active_button_menu_unpressed_image_color[4]; float window_active_button_iconify_unpressed_image_color[4]; float window_active_button_max_unpressed_image_color[4]; float window_active_button_close_unpressed_image_color[4]; float window_inactive_button_menu_unpressed_image_color[4]; float window_inactive_button_iconify_unpressed_image_color[4]; float window_inactive_button_max_unpressed_image_color[4]; float window_inactive_button_close_unpressed_image_color[4]; /* TODO: add pressed and hover colors for buttons */ int menu_item_padding_x; int menu_item_padding_y; float menu_items_bg_color[4]; float menu_items_text_color[4]; float menu_items_active_bg_color[4]; float menu_items_active_text_color[4]; int menu_min_width; int menu_max_width; int menu_separator_line_thickness; int menu_separator_padding_width; int menu_separator_padding_height; float menu_separator_color[4]; int osd_border_width; float osd_bg_color[4]; float osd_border_color[4]; float osd_label_text_color[4]; int osd_window_switcher_width; int osd_window_switcher_padding; int osd_window_switcher_item_padding_x; int osd_window_switcher_item_padding_y; int osd_window_switcher_item_active_border_width; int osd_workspace_switcher_boxes_width; int osd_workspace_switcher_boxes_height; /* textures */ struct lab_data_buffer *button_close_active_unpressed; struct lab_data_buffer *button_maximize_active_unpressed; struct lab_data_buffer *button_restore_active_unpressed; struct lab_data_buffer *button_iconify_active_unpressed; struct lab_data_buffer *button_menu_active_unpressed; struct lab_data_buffer *button_close_inactive_unpressed; struct lab_data_buffer *button_maximize_inactive_unpressed; struct lab_data_buffer *button_restore_inactive_unpressed; struct lab_data_buffer *button_iconify_inactive_unpressed; struct lab_data_buffer *button_menu_inactive_unpressed; /* hover variants are optional and may be NULL */ struct lab_data_buffer *button_close_active_hover; struct lab_data_buffer *button_maximize_active_hover; struct lab_data_buffer *button_restore_active_hover; struct lab_data_buffer *button_iconify_active_hover; struct lab_data_buffer *button_menu_active_hover; struct lab_data_buffer *button_close_inactive_hover; struct lab_data_buffer *button_maximize_inactive_hover; struct lab_data_buffer *button_restore_inactive_hover; struct lab_data_buffer *button_iconify_inactive_hover; struct lab_data_buffer *button_menu_inactive_hover; struct lab_data_buffer *corner_top_left_active_normal; struct lab_data_buffer *corner_top_right_active_normal; struct lab_data_buffer *corner_top_left_inactive_normal; struct lab_data_buffer *corner_top_right_inactive_normal; /* not set in rc.xml/themerc, but derived from font & padding_height */ int osd_window_switcher_item_height; }; /** * theme_init - read openbox theme and generate button textures * @theme: theme data * @theme_name: theme-name in //openbox-3/themerc * Note is obtained in theme-dir.c */ void theme_init(struct theme *theme, const char *theme_name); /** * theme_finish - free button textures * @theme: theme data */ void theme_finish(struct theme *theme); #endif /* LABWC_THEME_H */ labwc-0.7.1/include/view-impl-common.h000066400000000000000000000014031457044301200176000ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_VIEW_IMPL_COMMON_H #define LABWC_VIEW_IMPL_COMMON_H /* * Common code for view->impl functions * * Please note: only xdg-shell-toplevel-view and xwayland-view view_impl * functions should call these functions. */ struct view; void view_impl_move_to_front(struct view *view); void view_impl_move_to_back(struct view *view); void view_impl_map(struct view *view); void view_impl_unmap(struct view *view); /* * Updates view geometry at commit based on current position/size, * pending move/resize, and committed surface size. The computed * position may not match pending.x/y exactly in some cases. */ void view_impl_apply_geometry(struct view *view, int w, int h); #endif /* LABWC_VIEW_IMPL_COMMON_H */ labwc-0.7.1/include/view.h000066400000000000000000000407041457044301200153620ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_VIEW_H #define LABWC_VIEW_H #include "config.h" #include #include #include #include #include #define LAB_MIN_VIEW_WIDTH 100 #define LAB_MIN_VIEW_HEIGHT 60 /* * In labwc, a view is a container for surfaces which can be moved around by * the user. In practice this means XDG toplevel and XWayland windows. */ enum view_type { LAB_XDG_SHELL_VIEW, #if HAVE_XWAYLAND LAB_XWAYLAND_VIEW, #endif }; enum ssd_preference { LAB_SSD_PREF_UNSPEC = 0, LAB_SSD_PREF_CLIENT, LAB_SSD_PREF_SERVER, }; /** * Directions in which a view can be maximized. "None" is used * internally to mean "not maximized" but is not valid in rc.xml. * Therefore when parsing rc.xml, "None" means "Invalid". */ enum view_axis { VIEW_AXIS_NONE = 0, VIEW_AXIS_HORIZONTAL = (1 << 0), VIEW_AXIS_VERTICAL = (1 << 1), VIEW_AXIS_BOTH = (VIEW_AXIS_HORIZONTAL | VIEW_AXIS_VERTICAL), }; enum view_edge { VIEW_EDGE_INVALID = 0, VIEW_EDGE_LEFT, VIEW_EDGE_RIGHT, VIEW_EDGE_UP, VIEW_EDGE_DOWN, VIEW_EDGE_CENTER, }; enum view_wants_focus { /* View does not want focus */ VIEW_WANTS_FOCUS_NEVER = 0, /* View wants focus */ VIEW_WANTS_FOCUS_ALWAYS, /* * View should be offered focus and may accept or decline * (a.k.a. ICCCM Globally Active input model). Labwc generally * avoids focusing these views automatically (e.g. when another * view on top is closed) but they may be focused by user action * (e.g. mouse click). */ VIEW_WANTS_FOCUS_OFFER, }; struct view; struct wlr_surface; /* Common to struct view and struct xwayland_unmanaged */ struct mappable { bool connected; struct wl_listener map; struct wl_listener unmap; }; /* Basic size hints (subset of XSizeHints from X11) */ struct view_size_hints { int min_width; int min_height; int width_inc; int height_inc; int base_width; int base_height; }; struct view_impl { void (*configure)(struct view *view, struct wlr_box geo); void (*close)(struct view *view); const char *(*get_string_prop)(struct view *view, const char *prop); void (*map)(struct view *view); void (*set_activated)(struct view *view, bool activated); void (*set_fullscreen)(struct view *view, bool fullscreen); void (*notify_tiled)(struct view *view); /* * client_request is true if the client unmapped its own * surface; false if we are just minimizing the view. The two * cases are similar but have subtle differences (e.g., when * minimizing we don't destroy the foreign toplevel handle). */ void (*unmap)(struct view *view, bool client_request); void (*maximize)(struct view *view, bool maximize); void (*minimize)(struct view *view, bool minimize); void (*move_to_front)(struct view *view); void (*move_to_back)(struct view *view); struct view *(*get_root)(struct view *self); void (*append_children)(struct view *self, struct wl_array *children); /* determines if view and surface are owned by the same process */ bool (*is_related)(struct view *self, struct wlr_surface *surface); struct view_size_hints (*get_size_hints)(struct view *self); /* if not implemented, VIEW_WANTS_FOCUS_ALWAYS is assumed */ enum view_wants_focus (*wants_focus)(struct view *self); /* returns true if view reserves space at screen edge */ bool (*has_strut_partial)(struct view *self); }; struct view { struct server *server; enum view_type type; const struct view_impl *impl; struct wl_list link; /* * The primary output that the view is displayed on. Specifically: * * - For floating views, this is the output nearest to the * center of the view. It is computed automatically when the * view is moved or the output layout changes. * * - For fullscreen/maximized/tiled views, this is the output * used to compute the view's geometry. The view remains on * the same output unless it is disabled or disconnected. * * Many view functions (e.g. view_center(), view_fullscreen(), * view_maximize(), etc.) allow specifying a particular output * by calling view_set_output() beforehand. */ struct output *output; /* * The outputs that the view is displayed on. * This is used to notify the foreign toplevel * implementation and to update the SSD invisible * resize area. * It is a bitset of output->scene_output->index. */ uint64_t outputs; struct workspace *workspace; struct wlr_surface *surface; struct wlr_scene_tree *scene_tree; struct wlr_scene_node *scene_node; bool mapped; bool been_mapped; bool ssd_enabled; bool ssd_titlebar_hidden; enum ssd_preference ssd_preference; bool shaded; bool minimized; enum view_axis maximized; bool fullscreen; bool tearing_hint; bool visible_on_all_workspaces; enum view_edge tiled; uint32_t edges_visible; /* enum wlr_edges bitset */ bool inhibits_keybinds; xkb_layout_index_t keyboard_layout; /* Pointer to an output owned struct region, may be NULL */ struct region *tiled_region; /* Set to region->name when tiled_region is free'd by a destroying output */ char *tiled_region_evacuate; /* * Geometry of the wlr_surface contained within the view, as * currently displayed. Should be kept in sync with the * scene-graph at all times. */ struct wlr_box current; /* * Expected geometry after any pending move/resize requests * have been processed. Should match current geometry when no * move/resize requests are pending. */ struct wlr_box pending; /* * Saved geometry which will be restored when the view returns * to normal/floating state after being maximized/fullscreen/ * tiled. Values are undefined/out-of-date when the view is not * maximized/fullscreen/tiled. */ struct wlr_box natural_geometry; /* * Whenever an output layout change triggers a view relocation, the * last pending position (or natural geometry) will be saved so the * view may be restored to its original location on a subsequent layout * change. */ struct wlr_box last_layout_geometry; /* used by xdg-shell views */ uint32_t pending_configure_serial; struct wl_event_source *pending_configure_timeout; struct ssd *ssd; struct resize_indicator { int width, height; struct wlr_scene_tree *tree; struct wlr_scene_rect *border; struct wlr_scene_rect *background; struct scaled_font_buffer *text; } resize_indicator; struct foreign_toplevel { struct wlr_foreign_toplevel_handle_v1 *handle; struct wl_listener maximize; struct wl_listener minimize; struct wl_listener fullscreen; struct wl_listener activate; struct wl_listener close; struct wl_listener destroy; } toplevel; struct mappable mappable; struct wl_listener destroy; struct wl_listener surface_destroy; struct wl_listener commit; struct wl_listener request_move; struct wl_listener request_resize; struct wl_listener request_minimize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; struct wl_listener set_title; }; struct view_query { struct wl_list link; char *identifier; char *title; }; struct xdg_toplevel_view { struct view base; struct wlr_xdg_surface *xdg_surface; /* Events unique to xdg-toplevel views */ struct wl_listener set_app_id; struct wl_listener new_popup; }; /* All criteria is applied in AND logic */ enum lab_view_criteria { /* No filter -> all focusable views */ LAB_VIEW_CRITERIA_NONE = 0, /* * Includes always-on-top views, e.g. * what is visible on the current workspace */ LAB_VIEW_CRITERIA_CURRENT_WORKSPACE = 1 << 0, /* Positive criteria */ LAB_VIEW_CRITERIA_FULLSCREEN = 1 << 1, LAB_VIEW_CRITERIA_ALWAYS_ON_TOP = 1 << 2, /* Negative criteria */ LAB_VIEW_CRITERIA_NO_ALWAYS_ON_TOP = 1 << 6, LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER = 1 << 7, }; /** * view_from_wlr_surface() - returns the view associated with a * wlr_surface, or NULL if the surface has no associated view. */ struct view *view_from_wlr_surface(struct wlr_surface *surface); /** * view_query_free() - Free a given view query * @query: Query to be freed. */ void view_query_free(struct view_query *view); /** * view_matches_query() - Check if view matches the given criteria * @view: View to checked. * @query: Criteria to match against. * * Returns true if %view matches all of the criteria given in %query, false * otherwise. */ bool view_matches_query(struct view *view, struct view_query *query); /** * for_each_view() - iterate over all views which match criteria * @view: Iterator. * @head: Head of list to iterate over. * @criteria: Criteria to match against. * Example: * struct view *view; * for_each_view(view, &server->views, LAB_VIEW_CRITERIA_NONE) { * printf("%s\n", view_get_string_prop(view, "app_id")); * } */ #define for_each_view(view, head, criteria) \ for (view = view_next(head, NULL, criteria); \ view; \ view = view_next(head, view, criteria)) /** * view_next() - Get next view which matches criteria. * @head: Head of list to iterate over. * @view: Current view from which to find the next one. If NULL is provided as * the view argument, the start of the list will be used. * @criteria: Criteria to match against. * * Returns NULL if there are no views matching the criteria. */ struct view *view_next(struct wl_list *head, struct view *view, enum lab_view_criteria criteria); /** * view_array_append() - Append views that match criteria to array * @server: server context * @views: arrays to append to * @criteria: criteria to match against * * This function is useful in cases where the calling function may change the * stacking order or where it needs to iterate over the views multiple times, * for example to get the number of views before processing them. * * Note: This array has a very short shelf-life so it is intended to be used * with a single-use-throw-away approach. * * Example usage: * struct view **view; * struct wl_array views; * wl_array_init(&views); * view_array_append(server, &views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE); * wl_array_for_each(view, &views) { * // Do something with *view * } * wl_array_release(&views); */ void view_array_append(struct server *server, struct wl_array *views, enum lab_view_criteria criteria); enum view_wants_focus view_wants_focus(struct view *view); /** * view_is_focusable_from() - variant of view_is_focusable() * that takes into account the previously focused surface * @view: view to be checked * @prev_surface: previously focused surface */ bool view_is_focusable_from(struct view *view, struct wlr_surface *prev); /** * view_edge_invert() - select the opposite of a provided edge * * VIEW_EDGE_CENTER and VIEW_EDGE_INVALID both map to VIEW_EDGE_INVALID. * * @edge: edge to be inverted */ enum view_edge view_edge_invert(enum view_edge edge); /** * view_is_focusable() - Check whether or not a view can be focused * @view: view to be checked * * The purpose of this test is to filter out views (generally Xwayland) which * are not meant to be focused such as those with surfaces * a. that have been created but never mapped; * b. set to NULL after client minimize-request. * * The only views that are allowed to be focusd are those that have a surface * and have been mapped at some point since creation. */ static inline bool view_is_focusable(struct view *view) { return view_is_focusable_from(view, NULL); } void mappable_connect(struct mappable *mappable, struct wlr_surface *surface, wl_notify_func_t notify_map, wl_notify_func_t notify_unmap); void mappable_disconnect(struct mappable *mappable); void view_toggle_keybinds(struct view *view); void view_set_activated(struct view *view, bool activated); void view_set_output(struct view *view, struct output *output); void view_close(struct view *view); /** * view_move_resize - resize and move view * @view: view to be resized and moved * @geo: the new geometry * NOTE: Only use this when the view actually changes width and/or height * otherwise the serials might cause a delay in moving xdg-shell clients. * For move only, use view_move() */ void view_move_resize(struct view *view, struct wlr_box geo); void view_resize_relative(struct view *view, int left, int right, int top, int bottom); void view_move_relative(struct view *view, int x, int y); void view_move(struct view *view, int x, int y); void view_move_to_cursor(struct view *view); void view_moved(struct view *view); void view_minimize(struct view *view, bool minimized); bool view_compute_centered_position(struct view *view, const struct wlr_box *ref, int w, int h, int *x, int *y); void view_store_natural_geometry(struct view *view); /** * view_effective_height - effective height of view, with respect to shaded state * @view: view for which effective height is desired * @use_pending: if false, report current height; otherwise, report pending height */ int view_effective_height(struct view *view, bool use_pending); /** * view_center - center view within some region * @view: view to be centered * @ref: optional reference region (in layout coordinates) to center * within; if NULL, view is centered within usable area of its output */ void view_center(struct view *view, const struct wlr_box *ref); /** * view_place_initial - apply initial placement strategy to view * @view: view to be placed */ void view_place_initial(struct view *view, bool allow_cursor); void view_constrain_size_to_that_of_usable_area(struct view *view); void view_restore_to(struct view *view, struct wlr_box geometry); void view_set_untiled(struct view *view); void view_maximize(struct view *view, enum view_axis axis, bool store_natural_geometry); void view_set_fullscreen(struct view *view, bool fullscreen); void view_toggle_maximize(struct view *view, enum view_axis axis); void view_toggle_decorations(struct view *view); bool view_is_always_on_top(struct view *view); bool view_is_always_on_bottom(struct view *view); bool view_is_omnipresent(struct view *view); void view_toggle_always_on_top(struct view *view); void view_toggle_always_on_bottom(struct view *view); void view_toggle_visible_on_all_workspaces(struct view *view); bool view_is_tiled(struct view *view); bool view_is_floating(struct view *view); void view_move_to_workspace(struct view *view, struct workspace *workspace); void view_set_decorations(struct view *view, bool decorations); void view_toggle_fullscreen(struct view *view); void view_invalidate_last_layout_geometry(struct view *view); void view_adjust_for_layout_change(struct view *view); void view_move_to_edge(struct view *view, enum view_edge direction, bool snap_to_windows); void view_grow_to_edge(struct view *view, enum view_edge direction); void view_shrink_to_edge(struct view *view, enum view_edge direction); void view_snap_to_edge(struct view *view, enum view_edge direction, bool across_outputs, bool store_natural_geometry); void view_snap_to_region(struct view *view, struct region *region, bool store_natural_geometry); void view_move_to_output(struct view *view, struct output *output); void view_move_to_front(struct view *view); void view_move_to_back(struct view *view); struct view *view_get_root(struct view *view); void view_append_children(struct view *view, struct wl_array *children); bool view_on_output(struct view *view, struct output *output); /** * view_is_related() - determine if view and surface are owned by the * same application/process. Currently only implemented for xwayland * views/surfaces. */ bool view_is_related(struct view *view, struct wlr_surface *surface); /** * view_has_strut_partial() - returns true for views that reserve space * at a screen edge (e.g. panels). These views are treated as if they * have the fixedPosition window rule: i.e. they are not restricted to * the usable area and cannot be moved/resized interactively. */ bool view_has_strut_partial(struct view *view); const char *view_get_string_prop(struct view *view, const char *prop); void view_update_title(struct view *view); void view_update_app_id(struct view *view); void view_reload_ssd(struct view *view); void view_set_shade(struct view *view, bool shaded); struct view_size_hints view_get_size_hints(struct view *view); void view_adjust_size(struct view *view, int *w, int *h); void view_evacuate_region(struct view *view); void view_on_output_destroy(struct view *view); void view_connect_map(struct view *view, struct wlr_surface *surface); void view_destroy(struct view *view); struct output *view_get_adjacent_output(struct view *view, enum view_edge edge); enum view_axis view_axis_parse(const char *direction); enum view_edge view_edge_parse(const char *direction); /* xdg.c */ struct wlr_xdg_surface *xdg_surface_from_view(struct view *view); #endif /* LABWC_VIEW_H */ labwc-0.7.1/include/window-rules.h000066400000000000000000000016651457044301200170520ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_WINDOW_RULES_H #define LABWC_WINDOW_RULES_H enum window_rule_event { LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP = 0, }; enum property { LAB_PROP_UNSPECIFIED = 0, LAB_PROP_UNSET, LAB_PROP_FALSE, LAB_PROP_TRUE, }; /* * 'identifier' represents: * - 'app_id' for native Wayland windows * - 'WM_CLASS' for XWayland clients */ struct window_rule { char *identifier; char *title; bool match_once; enum window_rule_event event; struct wl_list actions; enum property server_decoration; enum property skip_taskbar; enum property skip_window_switcher; enum property ignore_focus_request; enum property fixed_position; struct wl_list link; /* struct rcxml.window_rules */ }; struct view; void window_rules_apply(struct view *view, enum window_rule_event event); enum property window_rules_get_property(struct view *view, const char *property); #endif /* LABWC_WINDOW_RULES_H */ labwc-0.7.1/include/workspaces.h000066400000000000000000000014731457044301200165710ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_WORKSPACES_H #define LABWC_WORKSPACES_H #include #include struct seat; struct server; struct wlr_scene_tree; /* Double use: as config in config/rcxml.c and as instance in workspaces.c */ struct workspace { struct wl_list link; /* * struct server.workspaces * struct rcxml.workspace_config.workspaces */ struct server *server; char *name; struct wlr_scene_tree *tree; }; void workspaces_init(struct server *server); void workspaces_switch_to(struct workspace *target, bool update_focus); void workspaces_destroy(struct server *server); void workspaces_osd_hide(struct seat *seat); struct workspace *workspaces_find(struct workspace *anchor, const char *name, bool wrap); #endif /* LABWC_WORKSPACES_H */ labwc-0.7.1/include/xwayland.h000066400000000000000000000064131457044301200162360ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef LABWC_XWAYLAND_H #define LABWC_XWAYLAND_H #include "config.h" #if HAVE_XWAYLAND #include #include #include #include "common/macros.h" #include "view.h" struct wlr_compositor; struct wlr_output; struct wlr_output_layout; enum atom { /* https://specifications.freedesktop.org/wm-spec/wm-spec-1.4.html#idm45649101374512 */ NET_WM_WINDOW_TYPE_DESKTOP = 0, NET_WM_WINDOW_TYPE_DOCK, NET_WM_WINDOW_TYPE_TOOLBAR, NET_WM_WINDOW_TYPE_MENU, NET_WM_WINDOW_TYPE_UTILITY, NET_WM_WINDOW_TYPE_SPLASH, NET_WM_WINDOW_TYPE_DIALOG, NET_WM_WINDOW_TYPE_DROPDOWN_MENU, NET_WM_WINDOW_TYPE_POPUP_MENU, NET_WM_WINDOW_TYPE_TOOLTIP, NET_WM_WINDOW_TYPE_NOTIFICATION, NET_WM_WINDOW_TYPE_COMBO, NET_WM_WINDOW_TYPE_DND, NET_WM_WINDOW_TYPE_NORMAL, ATOM_LEN }; static const char * const atom_names[] = { "_NET_WM_WINDOW_TYPE_DESKTOP", "_NET_WM_WINDOW_TYPE_DOCK", "_NET_WM_WINDOW_TYPE_TOOLBAR", "_NET_WM_WINDOW_TYPE_MENU", "_NET_WM_WINDOW_TYPE_UTILITY", "_NET_WM_WINDOW_TYPE_SPLASH", "_NET_WM_WINDOW_TYPE_DIALOG", "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", "_NET_WM_WINDOW_TYPE_POPUP_MENU", "_NET_WM_WINDOW_TYPE_TOOLTIP", "_NET_WM_WINDOW_TYPE_NOTIFICATION", "_NET_WM_WINDOW_TYPE_COMBO", "_NET_WM_WINDOW_TYPE_DND", "_NET_WM_WINDOW_TYPE_NORMAL", }; static_assert( ARRAY_SIZE(atom_names) == ATOM_LEN, "Xwayland atoms out of sync"); extern xcb_atom_t atoms[ATOM_LEN]; struct xwayland_unmanaged { struct server *server; struct wlr_xwayland_surface *xwayland_surface; struct wlr_scene_node *node; struct wl_list link; struct mappable mappable; struct wl_listener associate; struct wl_listener dissociate; struct wl_listener request_activate; struct wl_listener request_configure; /* struct wl_listener request_fullscreen; */ struct wl_listener set_geometry; struct wl_listener destroy; struct wl_listener set_override_redirect; }; struct xwayland_view { struct view base; struct wlr_xwayland_surface *xwayland_surface; /* Events unique to XWayland views */ struct wl_listener associate; struct wl_listener dissociate; struct wl_listener request_activate; struct wl_listener request_configure; struct wl_listener set_class; struct wl_listener set_decorations; struct wl_listener set_override_redirect; struct wl_listener set_strut_partial; struct wl_listener set_window_type; /* Not (yet) implemented */ /* struct wl_listener set_role; */ /* struct wl_listener set_hints; */ }; void xwayland_unmanaged_create(struct server *server, struct wlr_xwayland_surface *xsurface, bool mapped); void xwayland_view_create(struct server *server, struct wlr_xwayland_surface *xsurface, bool mapped); void xwayland_adjust_stacking_order(struct server *server); struct wlr_xwayland_surface *xwayland_surface_from_view(struct view *view); bool xwayland_surface_contains_window_type( struct wlr_xwayland_surface *surface, enum atom window_type); void xwayland_server_init(struct server *server, struct wlr_compositor *compositor); void xwayland_server_finish(struct server *server); void xwayland_adjust_usable_area(struct view *view, struct wlr_output_layout *layout, struct wlr_output *output, struct wlr_box *usable); void xwayland_update_workarea(struct server *server); #endif /* HAVE_XWAYLAND */ #endif /* LABWC_XWAYLAND_H */ labwc-0.7.1/meson.build000066400000000000000000000064131457044301200147550ustar00rootroot00000000000000project( 'labwc', 'c', version: '0.7.1', license: 'GPL-2.0-only', meson_version: '>=0.59.0', default_options: [ 'c_std=c11', 'warning_level=2', ], ) add_project_arguments( [ '-DWLR_USE_UNSTABLE', ], language: 'c', ) cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments([ '-Wundef', '-Wlogical-op', '-Wmissing-include-dirs', '-Wold-style-definition', '-Wpointer-arith', '-Winit-self', '-Wstrict-prototypes', '-Wimplicit-fallthrough=2', '-Wendif-labels', '-Wstrict-aliasing=2', '-Woverflow', '-Wmissing-prototypes', '-Walloca', '-Wunused-macros', '-Wno-unused-parameter', ]), language: 'c') version='"@0@"'.format(meson.project_version()) git = find_program('git', native: true, required: false) if git.found() git_commit = run_command([git, 'describe', '--dirty'], check: false) if git_commit.returncode() == 0 version = '"@0@"'.format(git_commit.stdout().strip()) endif endif add_project_arguments('-DLABWC_VERSION=@0@'.format(version), language: 'c') wlroots = dependency( 'wlroots', default_options: ['default_library=static', 'examples=false'], version: ['>=0.17.0', '<0.18.0'], ) wlroots_has_xwayland = wlroots.get_variable('have_xwayland') == 'true' wayland_server = dependency('wayland-server', version: '>=1.19.0') wayland_protos = dependency('wayland-protocols') xkbcommon = dependency('xkbcommon') xcb = dependency('xcb', required: get_option('xwayland')) xcb_icccm = dependency('xcb-icccm', required: get_option('xwayland')) drm_full = dependency('libdrm') drm = drm_full.partial_dependency(compile_args: true, includes: true) xml2 = dependency('libxml-2.0') glib = dependency('glib-2.0') cairo = dependency('cairo') pangocairo = dependency('pangocairo') input = dependency('libinput', version: '>=1.14') pixman = dependency('pixman-1') math = cc.find_library('m') png = dependency('libpng') svg = dependency('librsvg-2.0', version: '>=2.46', required: false) if get_option('xwayland').enabled() and not wlroots_has_xwayland error('no wlroots Xwayland support') endif have_xwayland = xcb.found() and wlroots_has_xwayland conf_data = configuration_data() conf_data.set10('HAVE_XWAYLAND', have_xwayland) if get_option('svg').disabled() have_rsvg = false else have_rsvg = svg.found() endif conf_data.set10('HAVE_RSVG', have_rsvg) msgfmt = find_program('msgfmt', required: get_option('nls')) if msgfmt.found() source_root = meson.current_source_dir() conf_data.set('HAVE_NLS', 1) subdir('po') else conf_data.set('HAVE_NLS', 0) endif labwc_inc = include_directories('include') subdir('protocols') labwc_deps = [ server_protos, wayland_server, wlroots, xkbcommon, xcb_icccm, xml2, glib, cairo, drm, pangocairo, input, pixman, math, png, ] if have_rsvg labwc_deps += [ svg, ] endif subdir('include') subdir('src') subdir('docs') executable( meson.project_name(), labwc_sources, include_directories: [labwc_inc], dependencies: labwc_deps, install: true, ) install_data('data/labwc.desktop', install_dir: get_option('datadir') / 'wayland-sessions') icons = ['labwc-symbolic.svg', 'labwc.svg'] foreach icon : icons icon_path = join_paths('data', icon) install_data(icon_path, install_dir: get_option('datadir') / 'icons/hicolor/scalable/apps') endforeach labwc-0.7.1/meson_options.txt000066400000000000000000000006021457044301200162420ustar00rootroot00000000000000option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications') option('svg', type: 'feature', value: 'enabled', description: 'Enable svg window buttons') option('nls', type: 'feature', value: 'auto', description: 'Enable native language support') labwc-0.7.1/po/000077500000000000000000000000001457044301200132255ustar00rootroot00000000000000labwc-0.7.1/po/LINGUAS000066400000000000000000000001021457044301200142430ustar00rootroot00000000000000de es et eu fi gl hu id it ja ka lt nl pa pl pt ru sv tr uk zh_CN labwc-0.7.1/po/POTFILES.in000066400000000000000000000000201457044301200147720ustar00rootroot00000000000000src/menu/menu.c labwc-0.7.1/po/de.po000066400000000000000000000030401457044301200141520ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # Consolatis , 2022. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-01-07 14:23+0000\n" "Last-Translator: Ettore Atalan \n" "Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Rekonfigurieren" #: src/menu/menu.c:699 msgid "Exit" msgstr "Beenden" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Minimieren" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Maximieren" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Vollbild" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Dekorationen" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Immer im Vordergrund" #: src/menu/menu.c:730 msgid "Move left" msgstr "nach links" #: src/menu/menu.c:737 msgid "Move right" msgstr "nach rechts" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Immer auf aktiver Arbeitsfläche" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Arbeitsfläche" #: src/menu/menu.c:748 msgid "Close" msgstr "Schließen" labwc-0.7.1/po/es.po000066400000000000000000000030421457044301200141730ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # Mick Amadio <01micko@gmail.com>, 2022. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2023-12-22 14:01+0000\n" "Last-Translator: Roberalz \n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Reconfigurar" #: src/menu/menu.c:699 msgid "Exit" msgstr "Salir" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Minimizar" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Maximizar" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Pantalla completa" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Decoraciones" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Siempre encima" #: src/menu/menu.c:730 msgid "Move left" msgstr "Mover a la izquierda" #: src/menu/menu.c:737 msgid "Move right" msgstr "Mover a la derecha" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Siempre en un espacio de trabajo visible" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Espacio de trabajo" #: src/menu/menu.c:748 msgid "Close" msgstr "Cerrar" labwc-0.7.1/po/et.po000066400000000000000000000030371457044301200142000ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-01-23 09:23+0000\n" "Last-Translator: Priit Jõerüüt \n" "Language-Team: Estonian \n" "Language: et\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Seadista uuesti" #: src/menu/menu.c:699 msgid "Exit" msgstr "Välju" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Vähenda" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Suurenda" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Täisekraanivaade" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "Keri üles/alla" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Akende välimus" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Alati kõige peal" #: src/menu/menu.c:730 msgid "Move left" msgstr "Tõsta vasakule" #: src/menu/menu.c:737 msgid "Move right" msgstr "Tõsta paremale" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Alati nähtav töölaual" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Töölaud" #: src/menu/menu.c:748 msgid "Close" msgstr "Sulge" labwc-0.7.1/po/eu.po000066400000000000000000000030051457044301200141740ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2023-12-22 14:01+0000\n" "Last-Translator: Roberalz \n" "Language-Team: Basque \n" "Language: eu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Berriz konfiguratu" #: src/menu/menu.c:699 msgid "Exit" msgstr "Irten" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Minimizatu" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Maximizatu" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Pantaila osoa" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Apaingarriak" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Beti gainean" #: src/menu/menu.c:730 msgid "Move left" msgstr "Mugitu ezkerrera" #: src/menu/menu.c:737 msgid "Move right" msgstr "Mugitu eskuinera" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Beti ikusgai dagoen lan-eremuan" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Langunea" #: src/menu/menu.c:748 msgid "Close" msgstr "Itxi" labwc-0.7.1/po/fi.po000066400000000000000000000025721457044301200141710ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-02-29 14:23+0000\n" "Last-Translator: Jouni Järvinen \n" "Language-Team: Finnish \n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "" #: src/menu/menu.c:699 msgid "Exit" msgstr "Poistu" #: src/menu/menu.c:715 msgid "Minimize" msgstr "" #: src/menu/menu.c:717 msgid "Maximize" msgstr "" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "" #: src/menu/menu.c:723 msgid "Decorations" msgstr "" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "" #: src/menu/menu.c:730 msgid "Move left" msgstr "" #: src/menu/menu.c:737 msgid "Move right" msgstr "" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "" #: src/menu/menu.c:745 msgid "Workspace" msgstr "" #: src/menu/menu.c:748 msgid "Close" msgstr "" labwc-0.7.1/po/gl.po000066400000000000000000000030261457044301200141700ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2023-12-22 14:01+0000\n" "Last-Translator: Roberalz \n" "Language-Team: Galician \n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Reconfigurar" #: src/menu/menu.c:699 msgid "Exit" msgstr "Saír" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Minimizar" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Maximizar" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Pantalla completa" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Decoracións" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Sempre enriba" #: src/menu/menu.c:730 msgid "Move left" msgstr "Mover á esquerda" #: src/menu/menu.c:737 msgid "Move right" msgstr "Mover á dereita" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Sempre no espazo de traballo visible" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Espazo de traballo" #: src/menu/menu.c:748 msgid "Close" msgstr "Pechar" labwc-0.7.1/po/hu.po000066400000000000000000000030221457044301200141760ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2024 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-02-19 21:23+0000\n" "Last-Translator: winerysearch \n" "Language-Team: Hungarian \n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Rekonfigurál" #: src/menu/menu.c:699 msgid "Exit" msgstr "Kilépés" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Kis méret" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Teljes méret" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Teljes képernyő" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "Felhúz / Legördül" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Dekorációk" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Mindig felül" #: src/menu/menu.c:730 msgid "Move left" msgstr "Balra dokkol" #: src/menu/menu.c:737 msgid "Move right" msgstr "Jobbra dokkol" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Kitűz" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Munkaasztal" #: src/menu/menu.c:748 msgid "Close" msgstr "Bezárás" labwc-0.7.1/po/id.po000066400000000000000000000026501457044301200141640ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # May Mantari , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2023-04-14 20:40+0700\n" "Last-Translator: May Mantari \n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Atur Ulang" #: src/menu/menu.c:699 msgid "Exit" msgstr "Keluar" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Sembunyikan" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Perluas Jendela" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Tampil Menyeluruh" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Dekorasi" #: src/menu/menu.c:725 #, fuzzy #| msgid "Always On Top" msgid "Always on Top" msgstr "Selalu di Muka" #: src/menu/menu.c:730 msgid "Move left" msgstr "Geser ke Kiri" #: src/menu/menu.c:737 msgid "Move right" msgstr "Geser ke Kanan" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Ruang Kerja" #: src/menu/menu.c:748 msgid "Close" msgstr "Tutup" labwc-0.7.1/po/it.po000066400000000000000000000030361457044301200142030ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # Mick Amadio <01micko@gmail.com>, 2022. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-01-23 09:23+0000\n" "Last-Translator: Standreas \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Riconfigura" #: src/menu/menu.c:699 msgid "Exit" msgstr "Esci" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Minimizza" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Massimizza" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Schermo intero" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "Arrotola/srotola" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Decorazioni" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Sempre sopra" #: src/menu/menu.c:730 msgid "Move left" msgstr "Sposta a sinistra" #: src/menu/menu.c:737 msgid "Move right" msgstr "Sposta a destra" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Sempre sull'area di lavoro visibile" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Area di lavoro" #: src/menu/menu.c:748 msgid "Close" msgstr "Chiudi" labwc-0.7.1/po/ja.po000066400000000000000000000025761457044301200141710ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-01-02 15:23+0000\n" "Last-Translator: Masamichi Ito \n" "Language-Team: Japanese \n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "" #: src/menu/menu.c:699 msgid "Exit" msgstr "終了" #: src/menu/menu.c:715 msgid "Minimize" msgstr "" #: src/menu/menu.c:717 msgid "Maximize" msgstr "" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "" #: src/menu/menu.c:723 msgid "Decorations" msgstr "" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "" #: src/menu/menu.c:730 msgid "Move left" msgstr "" #: src/menu/menu.c:737 msgid "Move right" msgstr "" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "" #: src/menu/menu.c:745 msgid "Workspace" msgstr "" #: src/menu/menu.c:748 msgid "Close" msgstr "閉じる" labwc-0.7.1/po/ka.po000066400000000000000000000033741457044301200141670ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # Temuri Doghonadze , 2023. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2023-02-22 09:43+0100\n" "Last-Translator: Temuri Doghonadze \n" "Language-Team: Georgian \n" "Language: ka\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.2.2\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "თავიდან მორგება" #: src/menu/menu.c:699 msgid "Exit" msgstr "გასვლა" #: src/menu/menu.c:715 msgid "Minimize" msgstr "ჩაკეცვა" #: src/menu/menu.c:717 msgid "Maximize" msgstr "გადიდება" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "მთელ ეკრანზე" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "" #: src/menu/menu.c:723 msgid "Decorations" msgstr "დეკორაციები" #: src/menu/menu.c:725 #, fuzzy #| msgid "Always On Top" msgid "Always on Top" msgstr "ყოველთვისყველაზეზემოდან" #: src/menu/menu.c:730 msgid "Move left" msgstr "მარცხნივ გაწევა" #: src/menu/menu.c:737 msgid "Move right" msgstr "მარჯვნივ გაწევა" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "" #: src/menu/menu.c:745 msgid "Workspace" msgstr "სამუშაო ადგილი" #: src/menu/menu.c:748 msgid "Close" msgstr "დახურვა" labwc-0.7.1/po/labwc.pot000066400000000000000000000023551457044301200150460ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2024 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "" #: src/menu/menu.c:699 msgid "Exit" msgstr "" #: src/menu/menu.c:715 msgid "Minimize" msgstr "" #: src/menu/menu.c:717 msgid "Maximize" msgstr "" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "" #: src/menu/menu.c:723 msgid "Decorations" msgstr "" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "" #: src/menu/menu.c:730 msgid "Move left" msgstr "" #: src/menu/menu.c:737 msgid "Move right" msgstr "" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "" #: src/menu/menu.c:745 msgid "Workspace" msgstr "" #: src/menu/menu.c:748 msgid "Close" msgstr "" labwc-0.7.1/po/lt.po000066400000000000000000000032461457044301200142110ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-02-29 14:23+0000\n" "Last-Translator: Moo \n" "Language-Team: Lithuanian \n" "Language: lt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n % 10 == 1 && (n % 100 < 11 || n % 100 > " "19)) ? 0 : ((n % 10 >= 2 && n % 10 <= 9 && (n % 100 < 11 || n % 100 > 19)) ? " "1 : 2);\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Konfigūruoti iš naujo" #: src/menu/menu.c:699 msgid "Exit" msgstr "Išeiti" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Suskleisti" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Išskleisti" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Visas ekranas" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "Užraityti/atraityti" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Dekoracijos" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Visada viršuje" #: src/menu/menu.c:730 msgid "Move left" msgstr "Perkelti kairėn" #: src/menu/menu.c:737 msgid "Move right" msgstr "Perkelti dešinėn" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Visada matomoje darbo srityje" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Darbo sritis" #: src/menu/menu.c:748 msgid "Close" msgstr "Užverti" labwc-0.7.1/po/meson.build000066400000000000000000000006111457044301200153650ustar00rootroot00000000000000i18n = import('i18n') add_project_arguments('-DGETTEXT_PACKAGE="' + meson.project_name() + '"', '-DLOCALEDIR="' + get_option('prefix') / get_option('localedir') + '"', language:'c') i18n.gettext(meson.project_name(), args: ['--directory=' + source_root, '--add-comments=TRANSLATORS', '--keyword=_', '--msgid-bugs=https://github.com/labwc/labwc/issues'], preset: 'glib' ) labwc-0.7.1/po/nl.po000066400000000000000000000030731457044301200142010ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-01-23 09:23+0000\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: Dutch \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Opnieuw instellen" #: src/menu/menu.c:699 msgid "Exit" msgstr "Afsluiten" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Minimaliseren" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Maximaliseren" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Schermvullende weergave" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "Op-/Afrollen" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Decoraties" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Altijd bovenaan" #: src/menu/menu.c:730 msgid "Move left" msgstr "Naar links verplaatsen" #: src/menu/menu.c:737 msgid "Move right" msgstr "Naar rechts verplaatsen" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Altijd op zichtbaar werkblad" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Werkblad" #: src/menu/menu.c:748 msgid "Close" msgstr "Sluiten" labwc-0.7.1/po/pa.po000066400000000000000000000033631457044301200141720ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-02-21 14:23+0000\n" "Last-Translator: A S Alam \n" "Language-Team: Punjabi \n" "Language: pa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "ਮੁੜ-ਸੰਰਚਨਾ ਕਰੋ" #: src/menu/menu.c:699 msgid "Exit" msgstr "ਬਾਹਰ" #: src/menu/menu.c:715 msgid "Minimize" msgstr "ਘੱਟੋ-ਘੱਟ" #: src/menu/menu.c:717 msgid "Maximize" msgstr "ਵੱਧ ਤੋਂ ਵੱਧ" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "ਪੂਰੀ ਸਕਰੀਨ" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "ਉੱਤੇ/ਹੇਠਾਂ ਸਕਰਾਓ" #: src/menu/menu.c:723 msgid "Decorations" msgstr "ਸਜਾਵਟ" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "ਹਮੇਸ਼ਾਂ ਉੱਤੇ ਰੱਖੋ" #: src/menu/menu.c:730 msgid "Move left" msgstr "ਖੱਬੇ ਭੇਜੋ" #: src/menu/menu.c:737 msgid "Move right" msgstr "ਸੱਜੇ ਭੇਜੋ" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "ਹਮੇਸ਼ਾਂ ਦਿੱਖ ਵਰਕਸਪੇਸ ਉੱਤੇ" #: src/menu/menu.c:745 msgid "Workspace" msgstr "ਵਰਕਸਪੇਸ" #: src/menu/menu.c:748 msgid "Close" msgstr "ਬੰਦ ਕਰੋ" labwc-0.7.1/po/pl.po000066400000000000000000000031771457044301200142100ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # Marcin Puc , 2023. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-01-23 09:23+0000\n" "Last-Translator: Jan Rolski \n" "Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Rekonfiguruj" #: src/menu/menu.c:699 msgid "Exit" msgstr "Wyjdź" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Minimalizuj" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Maksymalizuj" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Pełny ekran" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "Zwiń w górę/w dół" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Dekoracje" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Zawsze na wierzchu" #: src/menu/menu.c:730 msgid "Move left" msgstr "Przenieś w lewo" #: src/menu/menu.c:737 msgid "Move right" msgstr "Przenieś w prawo" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Zawsze na widocznym obszarze roboczym" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Przestrzeń robocza" #: src/menu/menu.c:748 msgid "Close" msgstr "Zamknij" labwc-0.7.1/po/pt.po000066400000000000000000000030751457044301200142150ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # Daniel , 2023. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-01-23 09:23+0000\n" "Last-Translator: daniel \n" "Language-Team: Portuguese \n" "Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Reconfigurar" #: src/menu/menu.c:699 msgid "Exit" msgstr "Sair" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Minimizar" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Maximizar" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Tela Cheia" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "Rolar para cima/baixo" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Decorações" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Sempre no Topo" #: src/menu/menu.c:730 msgid "Move left" msgstr "Mover para a esquerda" #: src/menu/menu.c:737 msgid "Move right" msgstr "Mover para a direita" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Sempre Visível na Área de Trabalho" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Área de Trabalho" #: src/menu/menu.c:748 msgid "Close" msgstr "Fechar" labwc-0.7.1/po/ru.po000066400000000000000000000035261457044301200142210ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # Aleksey Samoilov , 2023. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-02-26 12:23+0000\n" "Last-Translator: Alice Ventus \n" "Language-Team: Russian \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Перенастроить" #: src/menu/menu.c:699 msgid "Exit" msgstr "Выход" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Свернуть" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Развернуть" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "На весь экран" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "Свернуть/развернуть в заголовок" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Декорации" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Всегда на переднем плане" #: src/menu/menu.c:730 msgid "Move left" msgstr "Переместить влево" #: src/menu/menu.c:737 msgid "Move right" msgstr "Переместить вправо" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "На всех рабочих пространствах" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Рабочее пространство" #: src/menu/menu.c:748 msgid "Close" msgstr "Закрыть" labwc-0.7.1/po/sv.po000066400000000000000000000030131457044301200142120ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # Johan Malm , 2022. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-01-24 11:23+0000\n" "Last-Translator: bittin \n" "Language-Team: Swedish \n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Konfigurera om" #: src/menu/menu.c:699 msgid "Exit" msgstr "Avsluta" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Minimera" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Maximera" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Fullskärm" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "Rulla upp/ned" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Dekorationer" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Alltid överst" #: src/menu/menu.c:730 msgid "Move left" msgstr "Flytta till vänster" #: src/menu/menu.c:737 msgid "Move right" msgstr "Flytta till höger" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Alltid på aktiv arbetsyta" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Arbetsyta" #: src/menu/menu.c:748 msgid "Close" msgstr "Stäng" labwc-0.7.1/po/tr.po000066400000000000000000000030541457044301200142140ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # Oğuz Ersen , 2022. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-02-24 22:23+0000\n" "Last-Translator: Sabri Ünal \n" "Language-Team: Turkish \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Yeniden Yapılandır" #: src/menu/menu.c:699 msgid "Exit" msgstr "Çıkış" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Küçült" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Büyüt" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "Tam Ekran" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "Yukarı/aşağı katla" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Süslemeler" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Her Zaman Üstte" #: src/menu/menu.c:730 msgid "Move left" msgstr "Sola taşı" #: src/menu/menu.c:737 msgid "Move right" msgstr "Sağa taşı" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Her Zaman Görünür Çalışma Alanında" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Çalışma Alanı" #: src/menu/menu.c:748 msgid "Close" msgstr "Kapat" labwc-0.7.1/po/uk.po000066400000000000000000000034601457044301200142070ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-01-28 20:10+0000\n" "Last-Translator: Ihor Hordiichuk \n" "Language-Team: Ukrainian \n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "Переналаштувати" #: src/menu/menu.c:699 msgid "Exit" msgstr "Вихід" #: src/menu/menu.c:715 msgid "Minimize" msgstr "Згорнути" #: src/menu/menu.c:717 msgid "Maximize" msgstr "Розгорнути" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "На весь екран" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "Згорнути/розгорнути" #: src/menu/menu.c:723 msgid "Decorations" msgstr "Декорації" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "Завжди зверху" #: src/menu/menu.c:730 msgid "Move left" msgstr "Перемістити ліворуч" #: src/menu/menu.c:737 msgid "Move right" msgstr "Перемістити праворуч" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "Завжди на видимому робочому просторі" #: src/menu/menu.c:745 msgid "Workspace" msgstr "Робочий простір" #: src/menu/menu.c:748 msgid "Close" msgstr "Закрити" labwc-0.7.1/po/zh_CN.po000066400000000000000000000027751457044301200146010ustar00rootroot00000000000000# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-01-15 16:00-0500\n" "PO-Revision-Date: 2024-01-30 08:23+0000\n" "Last-Translator: kmephistoh \n" "Language-Team: Chinese (Simplified) \n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:697 msgid "Reconfigure" msgstr "配置重载" #: src/menu/menu.c:699 msgid "Exit" msgstr "退出" #: src/menu/menu.c:715 msgid "Minimize" msgstr "最小化" #: src/menu/menu.c:717 msgid "Maximize" msgstr "最大化" #: src/menu/menu.c:719 msgid "Fullscreen" msgstr "全屏" #: src/menu/menu.c:721 msgid "Roll up/down" msgstr "滚动 上/下" #: src/menu/menu.c:723 msgid "Decorations" msgstr "装饰" #: src/menu/menu.c:725 msgid "Always on Top" msgstr "最上层显示" #: src/menu/menu.c:730 msgid "Move left" msgstr "左移" #: src/menu/menu.c:737 msgid "Move right" msgstr "右移" #: src/menu/menu.c:742 msgid "Always on Visible Workspace" msgstr "始终在可见工作区" #: src/menu/menu.c:745 msgid "Workspace" msgstr "工作区" #: src/menu/menu.c:748 msgid "Close" msgstr "关闭" labwc-0.7.1/protocols/000077500000000000000000000000001457044301200146335ustar00rootroot00000000000000labwc-0.7.1/protocols/meson.build000066400000000000000000000024521457044301200170000ustar00rootroot00000000000000wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') wayland_scanner = find_program('wayland-scanner') wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', arguments: ['private-code', '@INPUT@', '@OUTPUT@'], ) wayland_scanner_server = generator( wayland_scanner, output: '@BASENAME@-protocol.h', arguments: ['server-header', '@INPUT@', '@OUTPUT@'], ) 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 / 'staging/cursor-shape/cursor-shape-v1.xml', wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', 'wlr-layer-shell-unstable-v1.xml', 'wlr-input-inhibitor-unstable-v1.xml', 'wlr-output-power-management-unstable-v1.xml', ] server_protos_src = [] server_protos_headers = [] foreach xml : server_protocols server_protos_src += wayland_scanner_code.process(xml) server_protos_headers += wayland_scanner_server.process(xml) endforeach lib_server_protos = static_library( 'server_protos', server_protos_src + server_protos_headers, dependencies: [wayland_server] ) server_protos = declare_dependency( link_with: lib_server_protos, sources: server_protos_headers, ) labwc-0.7.1/protocols/wlr-input-inhibitor-unstable-v1.xml000066400000000000000000000061241457044301200234450ustar00rootroot00000000000000 Copyright © 2018 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 prevent input events from being sent to any surfaces but its own, which is useful for example in lock screen software. It is assumed that access to this interface will be locked down to whitelisted clients by the compositor. Activates the input inhibitor. As long as the inhibitor is active, the compositor will not send input events to other clients. While this resource exists, input to clients other than the owner of the inhibitor resource will not receive input events. The client that owns this resource will receive all input events normally. The compositor will also disable all of its own input processing (such as keyboard shortcuts) while the inhibitor is active. The compositor may continue to send input events to selected clients, such as an on-screen keyboard (via the input-method protocol). Destroy the inhibitor and allow other clients to receive input. labwc-0.7.1/protocols/wlr-layer-shell-unstable-v1.xml000066400000000000000000000440361457044301200225460ustar00rootroot00000000000000 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. labwc-0.7.1/protocols/wlr-output-power-management-unstable-v1.xml000066400000000000000000000127351457044301200251320ustar00rootroot00000000000000 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 an 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. labwc-0.7.1/scripts/000077500000000000000000000000001457044301200142765ustar00rootroot00000000000000labwc-0.7.1/scripts/.gitignore000066400000000000000000000000201457044301200162560ustar00rootroot00000000000000*.o find-banned labwc-0.7.1/scripts/README.md000066400000000000000000000010061457044301200155520ustar00rootroot00000000000000These scripts are intended to be run from the project top-level directory like this: `scripts/foo.sh` - `scripts/check`: wrapper to check all files in `src/` and `include/` - `scripts/checkpatch.pl`: Quick hack on the Linux kernel [checkpatch.pl] to lint C files written according to the labwc coding style. Run like this: `./checkpatch.pl --no-tree --terse --strict --file ` [checkpatch.pl]: https://raw.githubusercontent.com/torvalds/linux/4ce9f970457899defdf68e26e0502c7245002eb3/scripts/checkpatch.pl labwc-0.7.1/scripts/check000077500000000000000000000014751457044301200153100ustar00rootroot00000000000000#!/bin/sh file= usage_message="Usage: check [OPTIONS] OPTIONS: --file= Specify file to check. If none specified, all files in src/ and include/ will be checked. " run_checkpatch() { nice scripts/checkpatch.pl --terse --no-tree --strict --file "$1" return $? } run_checks () { if [ ! -z "$file" ]; then run_checkpatch "${file}" return $? fi find src/ include/ \( -name "*.c" -o -name "*.h" \) -type f | { errors=0 while IFS= read -r file; do run_checkpatch "$file" || errors=1 done return ${errors} } } main () { for arg do opt=${arg%%=*} var=${arg#*=} case "$opt" in --file) file="$var" ;; -h|--help) printf '%b' "$usage_message"; exit 1 ;; *) printf '%b\n' "warn: unknown option $opt" >&2 ;; esac done run_checks } main "$@" labwc-0.7.1/scripts/checkpatch.pl000077500000000000000000006745241457044301200167550ustar00rootroot00000000000000#!/usr/bin/env perl # SPDX-License-Identifier: GPL-2.0 # # (c) 2001, Dave Jones. (the file handling bit) # (c) 2005, Joel Schopp (the ugly bit) # (c) 2007,2008, Andy Whitcroft (new conditions, test suite) # (c) 2008-2010 Andy Whitcroft # (c) 2010-2018 Joe Perches use strict; use warnings; use POSIX; use File::Basename; use Cwd 'abs_path'; use Term::ANSIColor qw(:constants); use Encode qw(decode encode); my $P = $0; my $D = dirname(abs_path($P)); my $V = '0.32'; use Getopt::Long qw(:config no_auto_abbrev); my $quiet = 0; my $verbose = 0; my %verbose_messages = (); my %verbose_emitted = (); my $tree = 1; my $chk_signoff = 1; my $chk_patch = 1; my $tst_only; my $emacs = 0; my $terse = 0; my $showfile = 0; my $file = 0; my $git = 0; my %git_commits = (); my $check = 0; my $check_orig = 0; my $summary = 1; my $mailback = 0; my $summary_file = 0; my $show_types = 0; my $list_types = 0; my $fix = 0; my $fix_inplace = 0; my $root; my $gitroot = $ENV{'GIT_DIR'}; $gitroot = ".git" if !defined($gitroot); my %debug; my %camelcase = (); my %use_type = (); my @use = (); my %ignore_type = (); my @ignore = ( "SPLIT_STRING", "COMPLEX_MACRO", "PREFER_KERNEL_TYPES", "LOGICAL_CONTINUATIONS", "PARENTHESIS_ALIGNMENT", "OPEN_ENDED_LINE", "MACRO_ARG_REUSE", "PREFER_FALLTHROUGH", "INITIALISED_STATIC", "UNNECESSARY_ELSE", "MACRO_ARG_PRECEDENCE", ); my $help = 0; my $configuration_file = ".checkpatch.conf"; my $max_line_length = 100; my $ignore_perl_version = 0; my $minimum_perl_version = 5.10.0; my $min_conf_desc_length = 4; my $spelling_file = "$D/spelling.txt"; my $codespell = 0; my $codespellfile = "/usr/share/codespell/dictionary.txt"; my $conststructsfile = "$D/const_structs.checkpatch"; my $docsfile = "$D/../Documentation/dev-tools/checkpatch.rst"; my $typedefsfile; my $color = "auto"; my $allow_c99_comments = 1; # Can be overridden by --ignore C99_COMMENT_TOLERANCE # git output parsing needs US English output, so first set backtick child process LANGUAGE my $git_command ='export LANGUAGE=en_US.UTF-8; git'; my $tabsize = 8; my ${CONFIG_} = "CONFIG_"; sub help { my ($exitcode) = @_; print << "EOM"; Usage: $P [OPTION]... [FILE]... Version: $V Options: -q, --quiet quiet -v, --verbose verbose mode --no-tree run without a kernel tree --no-signoff do not check for 'Signed-off-by' line --patch treat FILE as patchfile (default) --emacs emacs compile window format --terse one line per report --showfile emit diffed file position, not input file position -g, --git treat FILE as a single commit or git revision range single git commit with: ^ ~n multiple git commits with: .. ... - git merges are ignored -f, --file treat FILE as regular source file --subjective, --strict enable more subjective tests --list-types list the possible message types --types TYPE(,TYPE2...) show only these comma separated message types --ignore TYPE(,TYPE2...) ignore various comma separated message types --show-types show the specific message type in the output --max-line-length=n set the maximum line length, (default $max_line_length) if exceeded, warn on patches requires --strict for use with --file --min-conf-desc-length=n set the min description length, if shorter, warn --tab-size=n set the number of spaces for tab (default $tabsize) --root=PATH PATH to the kernel tree root --no-summary suppress the per-file summary --mailback only produce a report in case of warnings/errors --summary-file include the filename in summary --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of 'values', 'possible', 'type', and 'attr' (default is all off) --test-only=WORD report only warnings/errors containing WORD literally --fix EXPERIMENTAL - may create horrible results If correctable single-line errors exist, create ".EXPERIMENTAL-checkpatch-fixes" with potential errors corrected to the preferred checkpatch style --fix-inplace EXPERIMENTAL - may create horrible results Is the same as --fix, but overwrites the input file. It's your fault if there's no backup or git --ignore-perl-version override checking of perl version. expect runtime errors. --codespell Use the codespell dictionary for spelling/typos (default:/usr/share/codespell/dictionary.txt) --codespellfile Use this codespell dictionary --typedefsfile Read additional types from this file --color[=WHEN] Use colors 'always', 'never', or only when output is a terminal ('auto'). Default is 'auto'. --kconfig-prefix=WORD use WORD as a prefix for Kconfig symbols (default ${CONFIG_}) -h, --help, --version display this help and exit When FILE is - read standard input. EOM exit($exitcode); } sub uniq { my %seen; return grep { !$seen{$_}++ } @_; } sub list_types { my ($exitcode) = @_; my $count = 0; local $/ = undef; open(my $script, '<', abs_path($P)) or die "$P: Can't read '$P' $!\n"; my $text = <$script>; close($script); my %types = (); # Also catch when type or level is passed through a variable while ($text =~ /(?:(\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) { if (defined($1)) { if (exists($types{$2})) { $types{$2} .= ",$1" if ($types{$2} ne $1); } else { $types{$2} = $1; } } else { $types{$2} = "UNDETERMINED"; } } print("#\tMessage type\n\n"); if ($color) { print(" ( Color coding: "); print(RED . "ERROR" . RESET); print(" | "); print(YELLOW . "WARNING" . RESET); print(" | "); print(GREEN . "CHECK" . RESET); print(" | "); print("Multiple levels / Undetermined"); print(" )\n\n"); } foreach my $type (sort keys %types) { my $orig_type = $type; if ($color) { my $level = $types{$type}; if ($level eq "ERROR") { $type = RED . $type . RESET; } elsif ($level eq "WARN") { $type = YELLOW . $type . RESET; } elsif ($level eq "CHK") { $type = GREEN . $type . RESET; } } print(++$count . "\t" . $type . "\n"); if ($verbose && exists($verbose_messages{$orig_type})) { my $message = $verbose_messages{$orig_type}; $message =~ s/\n/\n\t/g; print("\t" . $message . "\n\n"); } } exit($exitcode); } my $conf = which_conf($configuration_file); if (-f $conf) { my @conf_args; open(my $conffile, '<', "$conf") or warn "$P: Can't find a readable $configuration_file file $!\n"; while (<$conffile>) { my $line = $_; $line =~ s/\s*\n?$//g; $line =~ s/^\s*//g; $line =~ s/\s+/ /g; next if ($line =~ m/^\s*#/); next if ($line =~ m/^\s*$/); my @words = split(" ", $line); foreach my $word (@words) { last if ($word =~ m/^#/); push (@conf_args, $word); } } close($conffile); unshift(@ARGV, @conf_args) if @conf_args; } sub load_docs { open(my $docs, '<', "$docsfile") or warn "$P: Can't read the documentation file $docsfile $!\n"; my $type = ''; my $desc = ''; my $in_desc = 0; while (<$docs>) { chomp; my $line = $_; $line =~ s/\s+$//; if ($line =~ /^\s*\*\*(.+)\*\*$/) { if ($desc ne '') { $verbose_messages{$type} = trim($desc); } $type = $1; $desc = ''; $in_desc = 1; } elsif ($in_desc) { if ($line =~ /^(?:\s{4,}|$)/) { $line =~ s/^\s{4}//; $desc .= $line; $desc .= "\n"; } else { $verbose_messages{$type} = trim($desc); $type = ''; $desc = ''; $in_desc = 0; } } } if ($desc ne '') { $verbose_messages{$type} = trim($desc); } close($docs); } # Perl's Getopt::Long allows options to take optional arguments after a space. # Prevent --color by itself from consuming other arguments foreach (@ARGV) { if ($_ eq "--color" || $_ eq "-color") { $_ = "--color=$color"; } } GetOptions( 'q|quiet+' => \$quiet, 'v|verbose!' => \$verbose, 'tree!' => \$tree, 'signoff!' => \$chk_signoff, 'patch!' => \$chk_patch, 'emacs!' => \$emacs, 'terse!' => \$terse, 'showfile!' => \$showfile, 'f|file!' => \$file, 'g|git!' => \$git, 'subjective!' => \$check, 'strict!' => \$check, 'ignore=s' => \@ignore, 'types=s' => \@use, 'show-types!' => \$show_types, 'list-types!' => \$list_types, 'max-line-length=i' => \$max_line_length, 'min-conf-desc-length=i' => \$min_conf_desc_length, 'tab-size=i' => \$tabsize, 'root=s' => \$root, 'summary!' => \$summary, 'mailback!' => \$mailback, 'summary-file!' => \$summary_file, 'fix!' => \$fix, 'fix-inplace!' => \$fix_inplace, 'ignore-perl-version!' => \$ignore_perl_version, 'debug=s' => \%debug, 'test-only=s' => \$tst_only, 'codespell!' => \$codespell, 'codespellfile=s' => \$codespellfile, 'typedefsfile=s' => \$typedefsfile, 'color=s' => \$color, 'no-color' => \$color, #keep old behaviors of -nocolor 'nocolor' => \$color, #keep old behaviors of -nocolor 'kconfig-prefix=s' => \${CONFIG_}, 'h|help' => \$help, 'version' => \$help ) or help(1); help(0) if ($help); die "$P: --git cannot be used with --file or --fix\n" if ($git && ($file || $fix)); die "$P: --verbose cannot be used with --terse\n" if ($verbose && $terse); if ($color =~ /^[01]$/) { $color = !$color; } elsif ($color =~ /^always$/i) { $color = 1; } elsif ($color =~ /^never$/i) { $color = 0; } elsif ($color =~ /^auto$/i) { $color = (-t STDOUT); } else { die "$P: Invalid color mode: $color\n"; } load_docs() if ($verbose); list_types(0) if ($list_types); $fix = 1 if ($fix_inplace); $check_orig = $check; my $exit = 0; my $perl_version_ok = 1; if ($^V && $^V lt $minimum_perl_version) { $perl_version_ok = 0; printf "$P: requires at least perl version %vd\n", $minimum_perl_version; exit(1) if (!$ignore_perl_version); } #if no filenames are given, push '-' to read patch from stdin if ($#ARGV < 0) { push(@ARGV, '-'); } # skip TAB size 1 to avoid additional checks on $tabsize - 1 die "$P: Invalid TAB size: $tabsize\n" if ($tabsize < 2); sub hash_save_array_words { my ($hashRef, $arrayRef) = @_; my @array = split(/,/, join(',', @$arrayRef)); foreach my $word (@array) { $word =~ s/\s*\n?$//g; $word =~ s/^\s*//g; $word =~ s/\s+/ /g; $word =~ tr/[a-z]/[A-Z]/; next if ($word =~ m/^\s*#/); next if ($word =~ m/^\s*$/); $hashRef->{$word}++; } } sub hash_show_words { my ($hashRef, $prefix) = @_; if (keys %$hashRef) { print "\nNOTE: $prefix message types:"; foreach my $word (sort keys %$hashRef) { print " $word"; } print "\n"; } } hash_save_array_words(\%ignore_type, \@ignore); hash_save_array_words(\%use_type, \@use); my $dbg_values = 0; my $dbg_possible = 0; my $dbg_type = 0; my $dbg_attr = 0; for my $key (keys %debug) { ## no critic eval "\${dbg_$key} = '$debug{$key}';"; die "$@" if ($@); } my $rpt_cleaners = 0; if ($terse) { $emacs = 1; $quiet++; } if ($tree) { if (defined $root) { if (!top_of_kernel_tree($root)) { die "$P: $root: --root does not point at a valid tree\n"; } } else { if (top_of_kernel_tree('.')) { $root = '.'; } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ && top_of_kernel_tree($1)) { $root = $1; } } if (!defined $root) { print "Must be run from the top-level dir. of a kernel tree\n"; exit(2); } } my $emitted_corrupt = 0; our $Ident = qr{ [A-Za-z_][A-Za-z\d_]* (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)* }x; our $Storage = qr{extern|static|asmlinkage}; our $Sparse = qr{ __user| __kernel| __force| __iomem| __must_check| __kprobes| __ref| __refconst| __refdata| __rcu| __private }x; our $InitAttributePrefix = qr{__(?:mem|cpu|dev|net_|)}; our $InitAttributeData = qr{$InitAttributePrefix(?:initdata\b)}; our $InitAttributeConst = qr{$InitAttributePrefix(?:initconst\b)}; our $InitAttributeInit = qr{$InitAttributePrefix(?:init\b)}; our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeInit}; # Notes to $Attribute: # We need \b after 'init' otherwise 'initconst' will cause a false positive in a check our $Attribute = qr{ const| volatile| __percpu| __nocast| __safe| __bitwise| __packed__| __packed2__| __naked| __maybe_unused| __always_unused| __noreturn| __used| __cold| __pure| __noclone| __deprecated| __read_mostly| __ro_after_init| __kprobes| $InitAttribute| ____cacheline_aligned| ____cacheline_aligned_in_smp| ____cacheline_internodealigned_in_smp| __weak }x; our $Modifier; our $Inline = qr{inline|__always_inline|noinline|__inline|__inline__}; our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]}; our $Lval = qr{$Ident(?:$Member)*}; our $Int_type = qr{(?i)llu|ull|ll|lu|ul|l|u}; our $Binary = qr{(?i)0b[01]+$Int_type?}; our $Hex = qr{(?i)0x[0-9a-f]+$Int_type?}; our $Int = qr{[0-9]+$Int_type?}; our $Octal = qr{0[0-7]+$Int_type?}; our $String = qr{(?:\b[Lu])?"[X\t]*"}; our $Float_hex = qr{(?i)0x[0-9a-f]+p-?[0-9]+[fl]?}; our $Float_dec = qr{(?i)(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:e-?[0-9]+)?[fl]?}; our $Float_int = qr{(?i)[0-9]+e-?[0-9]+[fl]?}; our $Float = qr{$Float_hex|$Float_dec|$Float_int}; our $Constant = qr{$Float|$Binary|$Octal|$Hex|$Int}; our $Assignment = qr{\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=}; our $Compare = qr{<=|>=|==|!=|<|(?}; our $Arithmetic = qr{\+|-|\*|\/|%}; our $Operators = qr{ <=|>=|==|!=| =>|->|<<|>>|<|>|!|~| &&|\|\||,|\^|\+\+|--|&|\||$Arithmetic }x; our $c90_Keywords = qr{do|for|while|if|else|return|goto|continue|switch|default|case|break}x; our $BasicType; our $NonptrType; our $NonptrTypeMisordered; our $NonptrTypeWithAttr; our $Type; our $TypeMisordered; our $Declare; our $DeclareMisordered; our $NON_ASCII_UTF8 = qr{ [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 }x; our $UTF8 = qr{ [\x09\x0A\x0D\x20-\x7E] # ASCII | $NON_ASCII_UTF8 }x; our $typeC99Typedefs = qr{(?:__)?(?:[us]_?)?int_?(?:8|16|32|64)_t}; our $typeOtherOSTypedefs = qr{(?x: u_(?:char|short|int|long) | # bsd u(?:nchar|short|int|long) # sysv )}; our $typeKernelTypedefs = qr{(?x: (?:__)?(?:u|s|be|le)(?:8|16|32|64)| atomic_t )}; our $typeTypedefs = qr{(?x: $typeC99Typedefs\b| $typeOtherOSTypedefs\b| $typeKernelTypedefs\b )}; our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b}; our $logFunctions = qr{(?x: printk(?:_ratelimited|_once|_deferred_once|_deferred|)| (?:[a-z0-9]+_){1,2}(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)| TP_printk| WARN(?:_RATELIMIT|_ONCE|)| panic| MODULE_[A-Z_]+| seq_vprintf|seq_printf|seq_puts )}; our $allocFunctions = qr{(?x: (?:(?:devm_)? (?:kv|k|v)[czm]alloc(?:_array)?(?:_node)? | kstrdup(?:_const)? | kmemdup(?:_nul)?) | (?:\w+)?alloc_skb(?:_ip_align)? | # dev_alloc_skb/netdev_alloc_skb, et al dma_alloc_coherent )}; our $signature_tags = qr{(?xi: Signed-off-by:| Co-developed-by:| Acked-by:| Tested-by:| Reviewed-by:| Reported-by:| Suggested-by:| To:| Cc: )}; our $tracing_logging_tags = qr{(?xi: [=-]*> | <[=-]* | \[ | \] | start | called | entered | entry | enter | in | inside | here | begin | exit | end | done | leave | completed | out | return | [\.\!:\s]* )}; sub edit_distance_min { my (@arr) = @_; my $len = scalar @arr; if ((scalar @arr) < 1) { # if underflow, return return; } my $min = $arr[0]; for my $i (0 .. ($len-1)) { if ($arr[$i] < $min) { $min = $arr[$i]; } } return $min; } sub get_edit_distance { my ($str1, $str2) = @_; $str1 = lc($str1); $str2 = lc($str2); $str1 =~ s/-//g; $str2 =~ s/-//g; my $len1 = length($str1); my $len2 = length($str2); # two dimensional array storing minimum edit distance my @distance; for my $i (0 .. $len1) { for my $j (0 .. $len2) { if ($i == 0) { $distance[$i][$j] = $j; } elsif ($j == 0) { $distance[$i][$j] = $i; } elsif (substr($str1, $i-1, 1) eq substr($str2, $j-1, 1)) { $distance[$i][$j] = $distance[$i - 1][$j - 1]; } else { my $dist1 = $distance[$i][$j - 1]; #insert distance my $dist2 = $distance[$i - 1][$j]; # remove my $dist3 = $distance[$i - 1][$j - 1]; #replace $distance[$i][$j] = 1 + edit_distance_min($dist1, $dist2, $dist3); } } } return $distance[$len1][$len2]; } sub find_standard_signature { my ($sign_off) = @_; my @standard_signature_tags = ( 'Signed-off-by:', 'Co-developed-by:', 'Acked-by:', 'Tested-by:', 'Reviewed-by:', 'Reported-by:', 'Suggested-by:' ); foreach my $signature (@standard_signature_tags) { return $signature if (get_edit_distance($sign_off, $signature) <= 2); } return ""; } our @typeListMisordered = ( qr{char\s+(?:un)?signed}, qr{int\s+(?:(?:un)?signed\s+)?short\s}, qr{int\s+short(?:\s+(?:un)?signed)}, qr{short\s+int(?:\s+(?:un)?signed)}, qr{(?:un)?signed\s+int\s+short}, qr{short\s+(?:un)?signed}, qr{long\s+int\s+(?:un)?signed}, qr{int\s+long\s+(?:un)?signed}, qr{long\s+(?:un)?signed\s+int}, qr{int\s+(?:un)?signed\s+long}, qr{int\s+(?:un)?signed}, qr{int\s+long\s+long\s+(?:un)?signed}, qr{long\s+long\s+int\s+(?:un)?signed}, qr{long\s+long\s+(?:un)?signed\s+int}, qr{long\s+long\s+(?:un)?signed}, qr{long\s+(?:un)?signed}, ); our @typeList = ( qr{void}, qr{(?:(?:un)?signed\s+)?char}, qr{(?:(?:un)?signed\s+)?short\s+int}, qr{(?:(?:un)?signed\s+)?short}, qr{(?:(?:un)?signed\s+)?int}, qr{(?:(?:un)?signed\s+)?long\s+int}, qr{(?:(?:un)?signed\s+)?long\s+long\s+int}, qr{(?:(?:un)?signed\s+)?long\s+long}, qr{(?:(?:un)?signed\s+)?long}, qr{(?:un)?signed}, qr{float}, qr{double}, qr{bool}, qr{struct\s+$Ident}, qr{union\s+$Ident}, qr{enum\s+$Ident}, qr{${Ident}_t}, qr{${Ident}_handler}, qr{${Ident}_handler_fn}, @typeListMisordered, ); our $C90_int_types = qr{(?x: long\s+long\s+int\s+(?:un)?signed| long\s+long\s+(?:un)?signed\s+int| long\s+long\s+(?:un)?signed| (?:(?:un)?signed\s+)?long\s+long\s+int| (?:(?:un)?signed\s+)?long\s+long| int\s+long\s+long\s+(?:un)?signed| int\s+(?:(?:un)?signed\s+)?long\s+long| long\s+int\s+(?:un)?signed| long\s+(?:un)?signed\s+int| long\s+(?:un)?signed| (?:(?:un)?signed\s+)?long\s+int| (?:(?:un)?signed\s+)?long| int\s+long\s+(?:un)?signed| int\s+(?:(?:un)?signed\s+)?long| int\s+(?:un)?signed| (?:(?:un)?signed\s+)?int )}; our @typeListFile = (); our @typeListWithAttr = ( @typeList, qr{struct\s+$InitAttribute\s+$Ident}, qr{union\s+$InitAttribute\s+$Ident}, ); our @modifierList = ( qr{fastcall}, ); our @modifierListFile = (); our @mode_permission_funcs = ( ["module_param", 3], ["module_param_(?:array|named|string)", 4], ["module_param_array_named", 5], ["debugfs_create_(?:file|u8|u16|u32|u64|x8|x16|x32|x64|size_t|atomic_t|bool|blob|regset32|u32_array)", 2], ["proc_create(?:_data|)", 2], ["(?:CLASS|DEVICE|SENSOR|SENSOR_DEVICE|IIO_DEVICE)_ATTR", 2], ["IIO_DEV_ATTR_[A-Z_]+", 1], ["SENSOR_(?:DEVICE_|)ATTR_2", 2], ["SENSOR_TEMPLATE(?:_2|)", 3], ["__ATTR", 2], ); my $word_pattern = '\b[A-Z]?[a-z]{2,}\b'; #Create a search pattern for all these functions to speed up a loop below our $mode_perms_search = ""; foreach my $entry (@mode_permission_funcs) { $mode_perms_search .= '|' if ($mode_perms_search ne ""); $mode_perms_search .= $entry->[0]; } $mode_perms_search = "(?:${mode_perms_search})"; our %deprecated_apis = ( "synchronize_rcu_bh" => "synchronize_rcu", "synchronize_rcu_bh_expedited" => "synchronize_rcu_expedited", "call_rcu_bh" => "call_rcu", "rcu_barrier_bh" => "rcu_barrier", "synchronize_sched" => "synchronize_rcu", "synchronize_sched_expedited" => "synchronize_rcu_expedited", "call_rcu_sched" => "call_rcu", "rcu_barrier_sched" => "rcu_barrier", "get_state_synchronize_sched" => "get_state_synchronize_rcu", "cond_synchronize_sched" => "cond_synchronize_rcu", ); #Create a search pattern for all these strings to speed up a loop below our $deprecated_apis_search = ""; foreach my $entry (keys %deprecated_apis) { $deprecated_apis_search .= '|' if ($deprecated_apis_search ne ""); $deprecated_apis_search .= $entry; } $deprecated_apis_search = "(?:${deprecated_apis_search})"; our $mode_perms_world_writable = qr{ S_IWUGO | S_IWOTH | S_IRWXUGO | S_IALLUGO | 0[0-7][0-7][2367] }x; our %mode_permission_string_types = ( "S_IRWXU" => 0700, "S_IRUSR" => 0400, "S_IWUSR" => 0200, "S_IXUSR" => 0100, "S_IRWXG" => 0070, "S_IRGRP" => 0040, "S_IWGRP" => 0020, "S_IXGRP" => 0010, "S_IRWXO" => 0007, "S_IROTH" => 0004, "S_IWOTH" => 0002, "S_IXOTH" => 0001, "S_IRWXUGO" => 0777, "S_IRUGO" => 0444, "S_IWUGO" => 0222, "S_IXUGO" => 0111, ); #Create a search pattern for all these strings to speed up a loop below our $mode_perms_string_search = ""; foreach my $entry (keys %mode_permission_string_types) { $mode_perms_string_search .= '|' if ($mode_perms_string_search ne ""); $mode_perms_string_search .= $entry; } our $single_mode_perms_string_search = "(?:${mode_perms_string_search})"; our $multi_mode_perms_string_search = qr{ ${single_mode_perms_string_search} (?:\s*\|\s*${single_mode_perms_string_search})* }x; sub perms_to_octal { my ($string) = @_; return trim($string) if ($string =~ /^\s*0[0-7]{3,3}\s*$/); my $val = ""; my $oval = ""; my $to = 0; my $curpos = 0; my $lastpos = 0; while ($string =~ /\b(($single_mode_perms_string_search)\b(?:\s*\|\s*)?\s*)/g) { $curpos = pos($string); my $match = $2; my $omatch = $1; last if ($lastpos > 0 && ($curpos - length($omatch) != $lastpos)); $lastpos = $curpos; $to |= $mode_permission_string_types{$match}; $val .= '\s*\|\s*' if ($val ne ""); $val .= $match; $oval .= $omatch; } $oval =~ s/^\s*\|\s*//; $oval =~ s/\s*\|\s*$//; return sprintf("%04o", $to); } our $allowed_asm_includes = qr{(?x: irq| memory| time| reboot )}; # memory.h: ARM has a custom one # Load common spelling mistakes and build regular expression list. my $misspellings; my %spelling_fix; if (open(my $spelling, '<', $spelling_file)) { while (<$spelling>) { my $line = $_; $line =~ s/\s*\n?$//g; $line =~ s/^\s*//g; next if ($line =~ m/^\s*#/); next if ($line =~ m/^\s*$/); my ($suspect, $fix) = split(/\|\|/, $line); $spelling_fix{$suspect} = $fix; } close($spelling); } if ($codespell) { if (open(my $spelling, '<', $codespellfile)) { while (<$spelling>) { my $line = $_; $line =~ s/\s*\n?$//g; $line =~ s/^\s*//g; next if ($line =~ m/^\s*#/); next if ($line =~ m/^\s*$/); next if ($line =~ m/, disabled/i); $line =~ s/,.*$//; my ($suspect, $fix) = split(/->/, $line); $spelling_fix{$suspect} = $fix; } close($spelling); } else { warn "No codespell typos will be found - file '$codespellfile': $!\n"; } } $misspellings = join("|", sort keys %spelling_fix) if keys %spelling_fix; sub read_words { my ($wordsRef, $file) = @_; if (open(my $words, '<', $file)) { while (<$words>) { my $line = $_; $line =~ s/\s*\n?$//g; $line =~ s/^\s*//g; next if ($line =~ m/^\s*#/); next if ($line =~ m/^\s*$/); if ($line =~ /\s/) { print("$file: '$line' invalid - ignored\n"); next; } $$wordsRef .= '|' if (defined $$wordsRef); $$wordsRef .= $line; } close($file); return 1; } return 0; } my $const_structs; if (defined($typedefsfile)) { my $typeOtherTypedefs; read_words(\$typeOtherTypedefs, $typedefsfile) or warn "No additional types will be considered - file '$typedefsfile': $!\n"; $typeTypedefs .= '|' . $typeOtherTypedefs if (defined $typeOtherTypedefs); } sub build_types { my $mods = "(?x: \n" . join("|\n ", (@modifierList, @modifierListFile)) . "\n)"; my $all = "(?x: \n" . join("|\n ", (@typeList, @typeListFile)) . "\n)"; my $Misordered = "(?x: \n" . join("|\n ", @typeListMisordered) . "\n)"; my $allWithAttr = "(?x: \n" . join("|\n ", @typeListWithAttr) . "\n)"; $Modifier = qr{(?:$Attribute|$Sparse|$mods)}; $BasicType = qr{ (?:$typeTypedefs\b)| (?:${all}\b) }x; $NonptrType = qr{ (?:$Modifier\s+|const\s+)* (?: (?:typeof|__typeof__)\s*\([^\)]*\)| (?:$typeTypedefs\b)| (?:${all}\b) ) (?:\s+$Modifier|\s+const)* }x; $NonptrTypeMisordered = qr{ (?:$Modifier\s+|const\s+)* (?: (?:${Misordered}\b) ) (?:\s+$Modifier|\s+const)* }x; $NonptrTypeWithAttr = qr{ (?:$Modifier\s+|const\s+)* (?: (?:typeof|__typeof__)\s*\([^\)]*\)| (?:$typeTypedefs\b)| (?:${allWithAttr}\b) ) (?:\s+$Modifier|\s+const)* }x; $Type = qr{ $NonptrType (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} (?:\s+$Inline|\s+$Modifier)* }x; $TypeMisordered = qr{ $NonptrTypeMisordered (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} (?:\s+$Inline|\s+$Modifier)* }x; $Declare = qr{(?:$Storage\s+(?:$Inline\s+)?)?$Type}; $DeclareMisordered = qr{(?:$Storage\s+(?:$Inline\s+)?)?$TypeMisordered}; } build_types(); our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*}; # Using $balanced_parens, $LvalOrFunc, or $FuncArg # requires at least perl version v5.10.0 # Any use must be runtime checked with $^V our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/; our $LvalOrFunc = qr{((?:[\&\*]\s*)?$Lval)\s*($balanced_parens{0,1})\s*}; our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)}; our $declaration_macros = qr{(?x: (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(| (?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(| (?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_STACK\s*\( )}; our %allow_repeated_words = ( add => '', added => '', bad => '', be => '', ); sub deparenthesize { my ($string) = @_; return "" if (!defined($string)); while ($string =~ /^\s*\(.*\)\s*$/) { $string =~ s@^\s*\(\s*@@; $string =~ s@\s*\)\s*$@@; } $string =~ s@\s+@ @g; return $string; } sub seed_camelcase_file { my ($file) = @_; return if (!(-f $file)); local $/; open(my $include_file, '<', "$file") or warn "$P: Can't read '$file' $!\n"; my $text = <$include_file>; close($include_file); my @lines = split('\n', $text); foreach my $line (@lines) { next if ($line !~ /(?:[A-Z][a-z]|[a-z][A-Z])/); if ($line =~ /^[ \t]*(?:#[ \t]*define|typedef\s+$Type)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)/) { $camelcase{$1} = 1; } elsif ($line =~ /^\s*$Declare\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[\(\[,;]/) { $camelcase{$1} = 1; } elsif ($line =~ /^\s*(?:union|struct|enum)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[;\{]/) { $camelcase{$1} = 1; } } } our %maintained_status = (); sub is_maintained_obsolete { my ($filename) = @_; return 0 if (!$tree || !(-e "$root/scripts/get_maintainer.pl")); if (!exists($maintained_status{$filename})) { $maintained_status{$filename} = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`; } return $maintained_status{$filename} =~ /obsolete/i; } sub is_SPDX_License_valid { my ($license) = @_; return 1 if (!$tree || which("python3") eq "" || !(-x "$root/scripts/spdxcheck.py") || !(-e "$gitroot")); my $root_path = abs_path($root); my $status = `cd "$root_path"; echo "$license" | scripts/spdxcheck.py -`; return 0 if ($status ne ""); return 1; } my $camelcase_seeded = 0; sub seed_camelcase_includes { return if ($camelcase_seeded); my $files; my $camelcase_cache = ""; my @include_files = (); $camelcase_seeded = 1; if (-e "$gitroot") { my $git_last_include_commit = `${git_command} log --no-merges --pretty=format:"%h%n" -1 -- include`; chomp $git_last_include_commit; $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit"; } else { my $last_mod_date = 0; $files = `find $root/include -name "*.h"`; @include_files = split('\n', $files); foreach my $file (@include_files) { my $date = POSIX::strftime("%Y%m%d%H%M", localtime((stat $file)[9])); $last_mod_date = $date if ($last_mod_date < $date); } $camelcase_cache = ".checkpatch-camelcase.date.$last_mod_date"; } if ($camelcase_cache ne "" && -f $camelcase_cache) { open(my $camelcase_file, '<', "$camelcase_cache") or warn "$P: Can't read '$camelcase_cache' $!\n"; while (<$camelcase_file>) { chomp; $camelcase{$_} = 1; } close($camelcase_file); return; } if (-e "$gitroot") { $files = `${git_command} ls-files "include/*.h"`; @include_files = split('\n', $files); } foreach my $file (@include_files) { seed_camelcase_file($file); } if ($camelcase_cache ne "") { unlink glob ".checkpatch-camelcase.*"; open(my $camelcase_file, '>', "$camelcase_cache") or warn "$P: Can't write '$camelcase_cache' $!\n"; foreach (sort { lc($a) cmp lc($b) } keys(%camelcase)) { print $camelcase_file ("$_\n"); } close($camelcase_file); } } sub git_is_single_file { my ($filename) = @_; return 0 if ((which("git") eq "") || !(-e "$gitroot")); my $output = `${git_command} ls-files -- $filename 2>/dev/null`; my $count = $output =~ tr/\n//; return $count eq 1 && $output =~ m{^${filename}$}; } sub git_commit_info { my ($commit, $id, $desc) = @_; return ($id, $desc) if ((which("git") eq "") || !(-e "$gitroot")); my $output = `${git_command} log --no-color --format='%H %s' -1 $commit 2>&1`; $output =~ s/^\s*//gm; my @lines = split("\n", $output); return ($id, $desc) if ($#lines < 0); if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous/) { # Maybe one day convert this block of bash into something that returns # all matching commit ids, but it's very slow... # # echo "checking commits $1..." # git rev-list --remotes | grep -i "^$1" | # while read line ; do # git log --format='%H %s' -1 $line | # echo "commit $(cut -c 1-12,41-)" # done } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./ || $lines[0] =~ /^fatal: bad object $commit/) { $id = undef; } else { $id = substr($lines[0], 0, 12); $desc = substr($lines[0], 41); } return ($id, $desc); } $chk_signoff = 0 if ($file); my @rawlines = (); my @lines = (); my @fixed = (); my @fixed_inserted = (); my @fixed_deleted = (); my $fixlinenr = -1; # If input is git commits, extract all commits from the commit expressions. # For example, HEAD-3 means we need check 'HEAD, HEAD~1, HEAD~2'. die "$P: No git repository found\n" if ($git && !-e "$gitroot"); if ($git) { my @commits = (); foreach my $commit_expr (@ARGV) { my $git_range; if ($commit_expr =~ m/^(.*)-(\d+)$/) { $git_range = "-$2 $1"; } elsif ($commit_expr =~ m/\.\./) { $git_range = "$commit_expr"; } else { $git_range = "-1 $commit_expr"; } my $lines = `${git_command} log --no-color --no-merges --pretty=format:'%H %s' $git_range`; foreach my $line (split(/\n/, $lines)) { $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/; next if (!defined($1) || !defined($2)); my $sha1 = $1; my $subject = $2; unshift(@commits, $sha1); $git_commits{$sha1} = $subject; } } die "$P: no git commits after extraction!\n" if (@commits == 0); @ARGV = @commits; } my $vname; $allow_c99_comments = !defined $ignore_type{"C99_COMMENT_TOLERANCE"}; for my $filename (@ARGV) { my $FILE; my $is_git_file = git_is_single_file($filename); my $oldfile = $file; $file = 1 if ($is_git_file); if ($git) { open($FILE, '-|', "git format-patch -M --stdout -1 $filename") || die "$P: $filename: git format-patch failed - $!\n"; } elsif ($file) { open($FILE, '-|', "diff -u /dev/null $filename") || die "$P: $filename: diff failed - $!\n"; } elsif ($filename eq '-') { open($FILE, '<&STDIN'); } else { open($FILE, '<', "$filename") || die "$P: $filename: open failed - $!\n"; } if ($filename eq '-') { $vname = 'Your patch'; } elsif ($git) { $vname = "Commit " . substr($filename, 0, 12) . ' ("' . $git_commits{$filename} . '")'; } else { $vname = $filename; } while (<$FILE>) { chomp; push(@rawlines, $_); $vname = qq("$1") if ($filename eq '-' && $_ =~ m/^Subject:\s+(.+)/i); } close($FILE); if ($#ARGV > 0 && $quiet == 0) { print '-' x length($vname) . "\n"; print "$vname\n"; print '-' x length($vname) . "\n"; } if (!process($filename)) { $exit = 1; } @rawlines = (); @lines = (); @fixed = (); @fixed_inserted = (); @fixed_deleted = (); $fixlinenr = -1; @modifierListFile = (); @typeListFile = (); build_types(); $file = $oldfile if ($is_git_file); } if (!$quiet) { hash_show_words(\%use_type, "Used"); hash_show_words(\%ignore_type, "Ignored"); if (!$perl_version_ok) { print << "EOM" NOTE: perl $^V is not modern enough to detect all possible issues. An upgrade to at least perl $minimum_perl_version is suggested. EOM } if ($exit) { print << "EOM" NOTE: If any of the errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. EOM } } exit($exit); sub top_of_kernel_tree { my ($root) = @_; my @tree_check = ( "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", "README", "Documentation", "arch", "include", "drivers", "fs", "init", "ipc", "kernel", "lib", "scripts", ); foreach my $check (@tree_check) { if (! -e $root . '/' . $check) { return 0; } } return 1; } sub parse_email { my ($formatted_email) = @_; my $name = ""; my $quoted = ""; my $name_comment = ""; my $address = ""; my $comment = ""; if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) { $name = $1; $address = $2; $comment = $3 if defined $3; } elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) { $address = $1; $comment = $2 if defined $2; } elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) { $address = $1; $comment = $2 if defined $2; $formatted_email =~ s/\Q$address\E.*$//; $name = $formatted_email; $name = trim($name); $name =~ s/^\"|\"$//g; # If there's a name left after stripping spaces and # leading quotes, and the address doesn't have both # leading and trailing angle brackets, the address # is invalid. ie: # "joe smith joe@smith.com" bad # "joe smith ]+>$/) { $name = ""; $address = ""; $comment = ""; } } # Extract comments from names excluding quoted parts # "John D. (Doe)" - Do not extract if ($name =~ s/\"(.+)\"//) { $quoted = $1; } while ($name =~ s/\s*($balanced_parens)\s*/ /) { $name_comment .= trim($1); } $name =~ s/^[ \"]+|[ \"]+$//g; $name = trim("$quoted $name"); $address = trim($address); $address =~ s/^\<|\>$//g; $comment = trim($comment); if ($name =~ /[^\w \-]/i) { ##has "must quote" chars $name =~ s/(?"; } $formatted_email .= "$comment"; return $formatted_email; } sub reformat_email { my ($email) = @_; my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); return format_email($email_name, $name_comment, $email_address, $comment); } sub same_email_addresses { my ($email1, $email2) = @_; my ($email1_name, $name1_comment, $email1_address, $comment1) = parse_email($email1); my ($email2_name, $name2_comment, $email2_address, $comment2) = parse_email($email2); return $email1_name eq $email2_name && $email1_address eq $email2_address && $name1_comment eq $name2_comment && $comment1 eq $comment2; } sub which { my ($bin) = @_; foreach my $path (split(/:/, $ENV{PATH})) { if (-e "$path/$bin") { return "$path/$bin"; } } return ""; } sub which_conf { my ($conf) = @_; foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) { if (-e "$path/$conf") { return "$path/$conf"; } } return ""; } sub expand_tabs { my ($str) = @_; my $res = ''; my $n = 0; for my $c (split(//, $str)) { if ($c eq "\t") { $res .= ' '; $n++; for (; ($n % $tabsize) != 0; $n++) { $res .= ' '; } next; } $res .= $c; $n++; } return $res; } sub copy_spacing { (my $res = shift) =~ tr/\t/ /c; return $res; } sub line_stats { my ($line) = @_; # Drop the diff line leader and expand tabs $line =~ s/^.//; $line = expand_tabs($line); # Pick the indent from the front of the line. my ($white) = ($line =~ /^(\s*)/); return (length($line), length($white)); } my $sanitise_quote = ''; sub sanitise_line_reset { my ($in_comment) = @_; if ($in_comment) { $sanitise_quote = '*/'; } else { $sanitise_quote = ''; } } sub sanitise_line { my ($line) = @_; my $res = ''; my $l = ''; my $qlen = 0; my $off = 0; my $c; # Always copy over the diff marker. $res = substr($line, 0, 1); for ($off = 1; $off < length($line); $off++) { $c = substr($line, $off, 1); # Comments we are whacking completely including the begin # and end, all to $;. if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') { $sanitise_quote = '*/'; substr($res, $off, 2, "$;$;"); $off++; next; } if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') { $sanitise_quote = ''; substr($res, $off, 2, "$;$;"); $off++; next; } if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') { $sanitise_quote = '//'; substr($res, $off, 2, $sanitise_quote); $off++; next; } # A \ in a string means ignore the next character. if (($sanitise_quote eq "'" || $sanitise_quote eq '"') && $c eq "\\") { substr($res, $off, 2, 'XX'); $off++; next; } # Regular quotes. if ($c eq "'" || $c eq '"') { if ($sanitise_quote eq '') { $sanitise_quote = $c; substr($res, $off, 1, $c); next; } elsif ($sanitise_quote eq $c) { $sanitise_quote = ''; } } #print "c<$c> SQ<$sanitise_quote>\n"; if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") { substr($res, $off, 1, $;); } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") { substr($res, $off, 1, $;); } elsif ($off != 0 && $sanitise_quote && $c ne "\t") { substr($res, $off, 1, 'X'); } else { substr($res, $off, 1, $c); } } if ($sanitise_quote eq '//') { $sanitise_quote = ''; } # The pathname on a #include may be surrounded by '<' and '>'. if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) { my $clean = 'X' x length($1); $res =~ s@\<.*\>@<$clean>@; # The whole of a #error is a string. } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) { my $clean = 'X' x length($1); $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@; } if ($allow_c99_comments && $res =~ m@(//.*$)@) { my $match = $1; $res =~ s/\Q$match\E/"$;" x length($match)/e; } return $res; } sub get_quoted_string { my ($line, $rawline) = @_; return "" if (!defined($line) || !defined($rawline)); return "" if ($line !~ m/($String)/g); return substr($rawline, $-[0], $+[0] - $-[0]); } sub ctx_statement_block { my ($linenr, $remain, $off) = @_; my $line = $linenr - 1; my $blk = ''; my $soff = $off; my $coff = $off - 1; my $coff_set = 0; my $loff = 0; my $type = ''; my $level = 0; my @stack = (); my $p; my $c; my $len = 0; my $remainder; while (1) { @stack = (['', 0]) if ($#stack == -1); #warn "CSB: blk<$blk> remain<$remain>\n"; # If we are about to drop off the end, pull in more # context. if ($off >= $len) { for (; $remain > 0; $line++) { last if (!defined $lines[$line]); next if ($lines[$line] =~ /^-/); $remain--; $loff = $len; $blk .= $lines[$line] . "\n"; $len = length($blk); $line++; last; } # Bail if there is no further context. #warn "CSB: blk<$blk> off<$off> len<$len>\n"; if ($off >= $len) { last; } if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) { $level++; $type = '#'; } } $p = $c; $c = substr($blk, $off, 1); $remainder = substr($blk, $off); #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n"; # Handle nested #if/#else. if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) { push(@stack, [ $type, $level ]); } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) { ($type, $level) = @{$stack[$#stack - 1]}; } elsif ($remainder =~ /^#\s*endif\b/) { ($type, $level) = @{pop(@stack)}; } # Statement ends at the ';' or a close '}' at the # outermost level. if ($level == 0 && $c eq ';') { last; } # An else is really a conditional as long as its not else if if ($level == 0 && $coff_set == 0 && (!defined($p) || $p =~ /(?:\s|\}|\+)/) && $remainder =~ /^(else)(?:\s|{)/ && $remainder !~ /^else\s+if\b/) { $coff = $off + length($1) - 1; $coff_set = 1; #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n"; #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n"; } if (($type eq '' || $type eq '(') && $c eq '(') { $level++; $type = '('; } if ($type eq '(' && $c eq ')') { $level--; $type = ($level != 0)? '(' : ''; if ($level == 0 && $coff < $soff) { $coff = $off; $coff_set = 1; #warn "CSB: mark coff<$coff>\n"; } } if (($type eq '' || $type eq '{') && $c eq '{') { $level++; $type = '{'; } if ($type eq '{' && $c eq '}') { $level--; $type = ($level != 0)? '{' : ''; if ($level == 0) { if (substr($blk, $off + 1, 1) eq ';') { $off++; } last; } } # Preprocessor commands end at the newline unless escaped. if ($type eq '#' && $c eq "\n" && $p ne "\\") { $level--; $type = ''; $off++; last; } $off++; } # We are truly at the end, so shuffle to the next line. if ($off == $len) { $loff = $len + 1; $line++; $remain--; } my $statement = substr($blk, $soff, $off - $soff + 1); my $condition = substr($blk, $soff, $coff - $soff + 1); #warn "STATEMENT<$statement>\n"; #warn "CONDITION<$condition>\n"; #print "coff<$coff> soff<$off> loff<$loff>\n"; return ($statement, $condition, $line, $remain + 1, $off - $loff + 1, $level); } sub statement_lines { my ($stmt) = @_; # Strip the diff line prefixes and rip blank lines at start and end. $stmt =~ s/(^|\n)./$1/g; $stmt =~ s/^\s*//; $stmt =~ s/\s*$//; my @stmt_lines = ($stmt =~ /\n/g); return $#stmt_lines + 2; } sub statement_rawlines { my ($stmt) = @_; my @stmt_lines = ($stmt =~ /\n/g); return $#stmt_lines + 2; } sub statement_block_size { my ($stmt) = @_; $stmt =~ s/(^|\n)./$1/g; $stmt =~ s/^\s*{//; $stmt =~ s/}\s*$//; $stmt =~ s/^\s*//; $stmt =~ s/\s*$//; my @stmt_lines = ($stmt =~ /\n/g); my @stmt_statements = ($stmt =~ /;/g); my $stmt_lines = $#stmt_lines + 2; my $stmt_statements = $#stmt_statements + 1; if ($stmt_lines > $stmt_statements) { return $stmt_lines; } else { return $stmt_statements; } } sub ctx_statement_full { my ($linenr, $remain, $off) = @_; my ($statement, $condition, $level); my (@chunks); # Grab the first conditional/block pair. ($statement, $condition, $linenr, $remain, $off, $level) = ctx_statement_block($linenr, $remain, $off); #print "F: c<$condition> s<$statement> remain<$remain>\n"; push(@chunks, [ $condition, $statement ]); if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) { return ($level, $linenr, @chunks); } # Pull in the following conditional/block pairs and see if they # could continue the statement. for (;;) { ($statement, $condition, $linenr, $remain, $off, $level) = ctx_statement_block($linenr, $remain, $off); #print "C: c<$condition> s<$statement> remain<$remain>\n"; last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s)); #print "C: push\n"; push(@chunks, [ $condition, $statement ]); } return ($level, $linenr, @chunks); } sub ctx_block_get { my ($linenr, $remain, $outer, $open, $close, $off) = @_; my $line; my $start = $linenr - 1; my $blk = ''; my @o; my @c; my @res = (); my $level = 0; my @stack = ($level); for ($line = $start; $remain > 0; $line++) { next if ($rawlines[$line] =~ /^-/); $remain--; $blk .= $rawlines[$line]; # Handle nested #if/#else. if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) { push(@stack, $level); } elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) { $level = $stack[$#stack - 1]; } elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) { $level = pop(@stack); } foreach my $c (split(//, $lines[$line])) { ##print "C<$c>L<$level><$open$close>O<$off>\n"; if ($off > 0) { $off--; next; } if ($c eq $close && $level > 0) { $level--; last if ($level == 0); } elsif ($c eq $open) { $level++; } } if (!$outer || $level <= 1) { push(@res, $rawlines[$line]); } last if ($level == 0); } return ($level, @res); } sub ctx_block_outer { my ($linenr, $remain) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0); return @r; } sub ctx_block { my ($linenr, $remain) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0); return @r; } sub ctx_statement { my ($linenr, $remain, $off) = @_; my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off); return @r; } sub ctx_block_level { my ($linenr, $remain) = @_; return ctx_block_get($linenr, $remain, 0, '{', '}', 0); } sub ctx_statement_level { my ($linenr, $remain, $off) = @_; return ctx_block_get($linenr, $remain, 0, '(', ')', $off); } sub ctx_locate_comment { my ($first_line, $end_line) = @_; # If c99 comment on the current line, or the line before or after my ($current_comment) = ($rawlines[$end_line - 1] =~ m@^\+.*(//.*$)@); return $current_comment if (defined $current_comment); ($current_comment) = ($rawlines[$end_line - 2] =~ m@^[\+ ].*(//.*$)@); return $current_comment if (defined $current_comment); ($current_comment) = ($rawlines[$end_line] =~ m@^[\+ ].*(//.*$)@); return $current_comment if (defined $current_comment); # Catch a comment on the end of the line itself. ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); return $current_comment if (defined $current_comment); # Look through the context and try and figure out if there is a # comment. my $in_comment = 0; $current_comment = ''; for (my $linenr = $first_line; $linenr < $end_line; $linenr++) { my $line = $rawlines[$linenr - 1]; #warn " $line\n"; if ($linenr == $first_line and $line =~ m@^.\s*\*@) { $in_comment = 1; } if ($line =~ m@/\*@) { $in_comment = 1; } if (!$in_comment && $current_comment ne '') { $current_comment = ''; } $current_comment .= $line . "\n" if ($in_comment); if ($line =~ m@\*/@) { $in_comment = 0; } } chomp($current_comment); return($current_comment); } sub ctx_has_comment { my ($first_line, $end_line) = @_; my $cmt = ctx_locate_comment($first_line, $end_line); ##print "LINE: $rawlines[$end_line - 1 ]\n"; ##print "CMMT: $cmt\n"; return ($cmt ne ''); } sub raw_line { my ($linenr, $cnt) = @_; my $offset = $linenr - 1; $cnt++; my $line; while ($cnt) { $line = $rawlines[$offset++]; next if (defined($line) && $line =~ /^-/); $cnt--; } return $line; } sub get_stat_real { my ($linenr, $lc) = @_; my $stat_real = raw_line($linenr, 0); for (my $count = $linenr + 1; $count <= $lc; $count++) { $stat_real = $stat_real . "\n" . raw_line($count, 0); } return $stat_real; } sub get_stat_here { my ($linenr, $cnt, $here) = @_; my $herectx = $here . "\n"; for (my $n = 0; $n < $cnt; $n++) { $herectx .= raw_line($linenr, $n) . "\n"; } return $herectx; } sub cat_vet { my ($vet) = @_; my ($res, $coded); $res = ''; while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) { $res .= $1; if ($2 ne '') { $coded = sprintf("^%c", unpack('C', $2) + 64); $res .= $coded; } } $res =~ s/$/\$/; return $res; } my $av_preprocessor = 0; my $av_pending; my @av_paren_type; my $av_pend_colon; sub annotate_reset { $av_preprocessor = 0; $av_pending = '_'; @av_paren_type = ('E'); $av_pend_colon = 'O'; } sub annotate_values { my ($stream, $type) = @_; my $res; my $var = '_' x length($stream); my $cur = $stream; print "$stream\n" if ($dbg_values > 1); while (length($cur)) { @av_paren_type = ('E') if ($#av_paren_type < 0); print " <" . join('', @av_paren_type) . "> <$type> <$av_pending>" if ($dbg_values > 1); if ($cur =~ /^(\s+)/o) { print "WS($1)\n" if ($dbg_values > 1); if ($1 =~ /\n/ && $av_preprocessor) { $type = pop(@av_paren_type); $av_preprocessor = 0; } } elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') { print "CAST($1)\n" if ($dbg_values > 1); push(@av_paren_type, $type); $type = 'c'; } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) { print "DECLARE($1)\n" if ($dbg_values > 1); $type = 'T'; } elsif ($cur =~ /^($Modifier)\s*/) { print "MODIFIER($1)\n" if ($dbg_values > 1); $type = 'T'; } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) { print "DEFINE($1,$2)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); if ($2 ne '') { $av_pending = 'N'; } $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) { print "UNDEF($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) { print "PRE_START($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $type); push(@av_paren_type, $type); $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) { print "PRE_RESTART($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; push(@av_paren_type, $av_paren_type[$#av_paren_type]); $type = 'E'; } elsif ($cur =~ /^(\#\s*(?:endif))/o) { print "PRE_END($1)\n" if ($dbg_values > 1); $av_preprocessor = 1; # Assume all arms of the conditional end as this # one does, and continue as if the #endif was not here. pop(@av_paren_type); push(@av_paren_type, $type); $type = 'E'; } elsif ($cur =~ /^(\\\n)/o) { print "PRECONT($1)\n" if ($dbg_values > 1); } elsif ($cur =~ /^(__attribute__)\s*\(?/o) { print "ATTR($1)\n" if ($dbg_values > 1); $av_pending = $type; $type = 'N'; } elsif ($cur =~ /^(sizeof)\s*(\()?/o) { print "SIZEOF($1)\n" if ($dbg_values > 1); if (defined $2) { $av_pending = 'V'; } $type = 'N'; } elsif ($cur =~ /^(if|while|for)\b/o) { print "COND($1)\n" if ($dbg_values > 1); $av_pending = 'E'; $type = 'N'; } elsif ($cur =~/^(case)/o) { print "CASE($1)\n" if ($dbg_values > 1); $av_pend_colon = 'C'; $type = 'N'; } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) { print "KEYWORD($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(\()/o) { print "PAREN('$1')\n" if ($dbg_values > 1); push(@av_paren_type, $av_pending); $av_pending = '_'; $type = 'N'; } elsif ($cur =~ /^(\))/o) { my $new_type = pop(@av_paren_type); if ($new_type ne '_') { $type = $new_type; print "PAREN('$1') -> $type\n" if ($dbg_values > 1); } else { print "PAREN('$1')\n" if ($dbg_values > 1); } } elsif ($cur =~ /^($Ident)\s*\(/o) { print "FUNC($1)\n" if ($dbg_values > 1); $type = 'V'; $av_pending = 'V'; } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) { if (defined $2 && $type eq 'C' || $type eq 'T') { $av_pend_colon = 'B'; } elsif ($type eq 'E') { $av_pend_colon = 'L'; } print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1); $type = 'V'; } elsif ($cur =~ /^($Ident|$Constant)/o) { print "IDENT($1)\n" if ($dbg_values > 1); $type = 'V'; } elsif ($cur =~ /^($Assignment)/o) { print "ASSIGN($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~/^(;|{|})/) { print "END($1)\n" if ($dbg_values > 1); $type = 'E'; $av_pend_colon = 'O'; } elsif ($cur =~/^(,)/) { print "COMMA($1)\n" if ($dbg_values > 1); $type = 'C'; } elsif ($cur =~ /^(\?)/o) { print "QUESTION($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(:)/o) { print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1); substr($var, length($res), 1, $av_pend_colon); if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') { $type = 'E'; } else { $type = 'N'; } $av_pend_colon = 'O'; } elsif ($cur =~ /^(\[)/o) { print "CLOSE($1)\n" if ($dbg_values > 1); $type = 'N'; } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) { my $variant; print "OPV($1)\n" if ($dbg_values > 1); if ($type eq 'V') { $variant = 'B'; } else { $variant = 'U'; } substr($var, length($res), 1, $variant); $type = 'N'; } elsif ($cur =~ /^($Operators)/o) { print "OP($1)\n" if ($dbg_values > 1); if ($1 ne '++' && $1 ne '--') { $type = 'N'; } } elsif ($cur =~ /(^.)/o) { print "C($1)\n" if ($dbg_values > 1); } if (defined $1) { $cur = substr($cur, length($1)); $res .= $type x length($1); } } return ($res, $var); } sub possible { my ($possible, $line) = @_; my $notPermitted = qr{(?: ^(?: $Modifier| $Storage| $Type| DEFINE_\S+ )$| ^(?: goto| return| case| else| asm|__asm__| do| \#| \#\#| )(?:\s|$)| ^(?:typedef|struct|enum)\b )}x; warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2); if ($possible !~ $notPermitted) { # Check for modifiers. $possible =~ s/\s*$Storage\s*//g; $possible =~ s/\s*$Sparse\s*//g; if ($possible =~ /^\s*$/) { } elsif ($possible =~ /\s/) { $possible =~ s/\s*$Type\s*//g; for my $modifier (split(' ', $possible)) { if ($modifier !~ $notPermitted) { warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible); push(@modifierListFile, $modifier); } } } else { warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible); push(@typeListFile, $possible); } build_types(); } else { warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1); } } my $prefix = ''; sub show_type { my ($type) = @_; $type =~ tr/[a-z]/[A-Z]/; return defined $use_type{$type} if (scalar keys %use_type > 0); return !defined $ignore_type{$type}; } sub report { my ($level, $type, $msg) = @_; if (!show_type($type) || (defined $tst_only && $msg !~ /\Q$tst_only\E/)) { return 0; } my $output = ''; if ($color) { if ($level eq 'ERROR') { $output .= RED; } elsif ($level eq 'WARNING') { $output .= YELLOW; } else { $output .= GREEN; } } $output .= $prefix . $level . ':'; if ($show_types) { $output .= BLUE if ($color); $output .= "$type:"; } $output .= RESET if ($color); $output .= ' ' . $msg . "\n"; if ($showfile) { my @lines = split("\n", $output, -1); splice(@lines, 1, 1); $output = join("\n", @lines); } if ($terse) { $output = (split('\n', $output))[0] . "\n"; } if ($verbose && exists($verbose_messages{$type}) && !exists($verbose_emitted{$type})) { $output .= $verbose_messages{$type} . "\n\n"; $verbose_emitted{$type} = 1; } push(our @report, $output); return 1; } sub report_dump { our @report; } sub fixup_current_range { my ($lineRef, $offset, $length) = @_; if ($$lineRef =~ /^\@\@ -\d+,\d+ \+(\d+),(\d+) \@\@/) { my $o = $1; my $l = $2; my $no = $o + $offset; my $nl = $l + $length; $$lineRef =~ s/\+$o,$l \@\@/\+$no,$nl \@\@/; } } sub fix_inserted_deleted_lines { my ($linesRef, $insertedRef, $deletedRef) = @_; my $range_last_linenr = 0; my $delta_offset = 0; my $old_linenr = 0; my $new_linenr = 0; my $next_insert = 0; my $next_delete = 0; my @lines = (); my $inserted = @{$insertedRef}[$next_insert++]; my $deleted = @{$deletedRef}[$next_delete++]; foreach my $old_line (@{$linesRef}) { my $save_line = 1; my $line = $old_line; #don't modify the array if ($line =~ /^(?:\+\+\+|\-\-\-)\s+\S+/) { #new filename $delta_offset = 0; } elsif ($line =~ /^\@\@ -\d+,\d+ \+\d+,\d+ \@\@/) { #new hunk $range_last_linenr = $new_linenr; fixup_current_range(\$line, $delta_offset, 0); } while (defined($deleted) && ${$deleted}{'LINENR'} == $old_linenr) { $deleted = @{$deletedRef}[$next_delete++]; $save_line = 0; fixup_current_range(\$lines[$range_last_linenr], $delta_offset--, -1); } while (defined($inserted) && ${$inserted}{'LINENR'} == $old_linenr) { push(@lines, ${$inserted}{'LINE'}); $inserted = @{$insertedRef}[$next_insert++]; $new_linenr++; fixup_current_range(\$lines[$range_last_linenr], $delta_offset++, 1); } if ($save_line) { push(@lines, $line); $new_linenr++; } $old_linenr++; } return @lines; } sub fix_insert_line { my ($linenr, $line) = @_; my $inserted = { LINENR => $linenr, LINE => $line, }; push(@fixed_inserted, $inserted); } sub fix_delete_line { my ($linenr, $line) = @_; my $deleted = { LINENR => $linenr, LINE => $line, }; push(@fixed_deleted, $deleted); } sub ERROR { my ($type, $msg) = @_; if (report("ERROR", $type, $msg)) { our $clean = 0; our $cnt_error++; return 1; } return 0; } sub WARN { my ($type, $msg) = @_; if (report("WARNING", $type, $msg)) { our $clean = 0; our $cnt_warn++; return 1; } return 0; } sub CHK { my ($type, $msg) = @_; if ($check && report("CHECK", $type, $msg)) { our $clean = 0; our $cnt_chk++; return 1; } return 0; } sub check_absolute_file { my ($absolute, $herecurr) = @_; my $file = $absolute; ##print "absolute<$absolute>\n"; # See if any suffix of this path is a path within the tree. while ($file =~ s@^[^/]*/@@) { if (-f "$root/$file") { ##print "file<$file>\n"; last; } } if (! -f _) { return 0; } # It is, so see if the prefix is acceptable. my $prefix = $absolute; substr($prefix, -length($file)) = ''; ##print "prefix<$prefix>\n"; if ($prefix ne ".../") { WARN("USE_RELATIVE_PATH", "use relative pathname instead of absolute in changelog text\n" . $herecurr); } } sub trim { my ($string) = @_; $string =~ s/^\s+|\s+$//g; return $string; } sub ltrim { my ($string) = @_; $string =~ s/^\s+//; return $string; } sub rtrim { my ($string) = @_; $string =~ s/\s+$//; return $string; } sub string_find_replace { my ($string, $find, $replace) = @_; $string =~ s/$find/$replace/g; return $string; } sub tabify { my ($leading) = @_; my $source_indent = $tabsize; my $max_spaces_before_tab = $source_indent - 1; my $spaces_to_tab = " " x $source_indent; #convert leading spaces to tabs 1 while $leading =~ s@^([\t]*)$spaces_to_tab@$1\t@g; #Remove spaces before a tab 1 while $leading =~ s@^([\t]*)( {1,$max_spaces_before_tab})\t@$1\t@g; return "$leading"; } sub pos_last_openparen { my ($line) = @_; my $pos = 0; my $opens = $line =~ tr/\(/\(/; my $closes = $line =~ tr/\)/\)/; my $last_openparen = 0; if (($opens == 0) || ($closes >= $opens)) { return -1; } my $len = length($line); for ($pos = 0; $pos < $len; $pos++) { my $string = substr($line, $pos); if ($string =~ /^($FuncArg|$balanced_parens)/) { $pos += length($1) - 1; } elsif (substr($line, $pos, 1) eq '(') { $last_openparen = $pos; } elsif (index($string, '(') == -1) { last; } } return length(expand_tabs(substr($line, 0, $last_openparen))) + 1; } sub get_raw_comment { my ($line, $rawline) = @_; my $comment = ''; for my $i (0 .. (length($line) - 1)) { if (substr($line, $i, 1) eq "$;") { $comment .= substr($rawline, $i, 1); } } return $comment; } sub exclude_global_initialisers { my ($realfile) = @_; # Do not check for BPF programs (tools/testing/selftests/bpf/progs/*.c, samples/bpf/*_kern.c, *.bpf.c). return $realfile =~ m@^tools/testing/selftests/bpf/progs/.*\.c$@ || $realfile =~ m@^samples/bpf/.*_kern\.c$@ || $realfile =~ m@/bpf/.*\.bpf\.c$@; } sub process { my $filename = shift; print($filename, "\n"); my $linenr=0; my $prevline=""; my $prevrawline=""; my $stashline=""; my $stashrawline=""; my $length; my $indent; my $previndent=0; my $stashindent=0; our $clean = 1; my $signoff = 0; my $author = ''; my $authorsignoff = 0; my $author_sob = ''; my $is_patch = 0; my $is_binding_patch = -1; my $in_header_lines = $file ? 0 : 1; my $in_commit_log = 0; #Scanning lines before patch my $has_patch_separator = 0; #Found a --- line my $has_commit_log = 0; #Encountered lines before patch my $commit_log_lines = 0; #Number of commit log lines my $commit_log_possible_stack_dump = 0; my $commit_log_long_line = 0; my $commit_log_has_diff = 0; my $reported_maintainer_file = 0; my $non_utf8_charset = 0; my $last_git_commit_id_linenr = -1; my $last_blank_line = 0; my $last_coalesced_string_linenr = -1; our @report = (); our $cnt_lines = 0; our $cnt_error = 0; our $cnt_warn = 0; our $cnt_chk = 0; # Trace the real file/line as we go. my $realfile = ''; my $realline = 0; my $realcnt = 0; my $here = ''; my $context_function; #undef'd unless there's a known function my $in_comment = 0; my $comment_edge = 0; my $first_line = 0; my $p1_prefix = ''; my $prev_values = 'E'; # suppression flags my %suppress_ifbraces; my %suppress_whiletrailers; my %suppress_export; my $suppress_statement = 0; my %signatures = (); # Pre-scan the patch sanitizing the lines. # Pre-scan the patch looking for any __setup documentation. # my @setup_docs = (); my $setup_docs = 0; my $camelcase_file_seeded = 0; my $checklicenseline = 1; sanitise_line_reset(); my $line; foreach my $rawline (@rawlines) { $linenr++; $line = $rawline; push(@fixed, $rawline) if ($fix); if ($rawline=~/^\+\+\+\s+(\S+)/) { $setup_docs = 0; if ($1 =~ m@Documentation/admin-guide/kernel-parameters.txt$@) { $setup_docs = 1; } #next; } if ($rawline =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { $realline=$1-1; if (defined $2) { $realcnt=$3+1; } else { $realcnt=1+1; } $in_comment = 0; # Guestimate if this is a continuing comment. Run # the context looking for a comment "edge". If this # edge is a close comment then we must be in a comment # at context start. my $edge; my $cnt = $realcnt; for (my $ln = $linenr + 1; $cnt > 0; $ln++) { next if (defined $rawlines[$ln - 1] && $rawlines[$ln - 1] =~ /^-/); $cnt--; #print "RAW<$rawlines[$ln - 1]>\n"; last if (!defined $rawlines[$ln - 1]); if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ && $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) { ($edge) = $1; last; } } if (defined $edge && $edge eq '*/') { $in_comment = 1; } # Guestimate if this is a continuing comment. If this # is the start of a diff block and this line starts # ' *' then it is very likely a comment. if (!defined $edge && $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@) { $in_comment = 1; } ##print "COMMENT:$in_comment edge<$edge> $rawline\n"; sanitise_line_reset($in_comment); } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) { # Standardise the strings and chars within the input to # simplify matching -- only bother with positive lines. $line = sanitise_line($rawline); } push(@lines, $line); if ($realcnt > 1) { $realcnt-- if ($line =~ /^(?:\+| |$)/); } else { $realcnt = 0; } #print "==>$rawline\n"; #print "-->$line\n"; if ($setup_docs && $line =~ /^\+/) { push(@setup_docs, $line); } } $prefix = ''; $realcnt = 0; $linenr = 0; $fixlinenr = -1; foreach my $line (@lines) { $linenr++; $fixlinenr++; my $sline = $line; #copy of $line $sline =~ s/$;/ /g; #with comments as spaces my $rawline = $rawlines[$linenr - 1]; my $raw_comment = get_raw_comment($line, $rawline); # check if it's a mode change, rename or start of a patch if (!$in_commit_log && ($line =~ /^ mode change [0-7]+ => [0-7]+ \S+\s*$/ || ($line =~ /^rename (?:from|to) \S+\s*$/ || $line =~ /^diff --git a\/[\w\/\.\_\-]+ b\/\S+\s*$/))) { $is_patch = 1; } #extract the line range in the file after the patch is applied if (!$in_commit_log && $line =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@(.*)/) { my $context = $4; $is_patch = 1; $first_line = $linenr + 1; $realline=$1-1; if (defined $2) { $realcnt=$3+1; } else { $realcnt=1+1; } annotate_reset(); $prev_values = 'E'; %suppress_ifbraces = (); %suppress_whiletrailers = (); %suppress_export = (); $suppress_statement = 0; if ($context =~ /\b(\w+)\s*\(/) { $context_function = $1; } else { undef $context_function; } next; # track the line number as we move through the hunk, note that # new versions of GNU diff omit the leading space on completely # blank context lines so we need to count that too. } elsif ($line =~ /^( |\+|$)/) { $realline++; $realcnt-- if ($realcnt != 0); # Measure the line length and indent. ($length, $indent) = line_stats($rawline); # Track the previous line. ($prevline, $stashline) = ($stashline, $line); ($previndent, $stashindent) = ($stashindent, $indent); ($prevrawline, $stashrawline) = ($stashrawline, $rawline); #warn "line<$line>\n"; } elsif ($realcnt == 1) { $realcnt--; } my $hunk_line = ($realcnt != 0); $here = "#$linenr: " if (!$file); $here = "#$realline: " if ($file); my $found_file = 0; # extract the filename as it passes if ($line =~ /^diff --git.*?(\S+)$/) { $realfile = $1; $realfile =~ s@^([^/]*)/@@ if (!$file); $in_commit_log = 0; $found_file = 1; } elsif ($line =~ /^\+\+\+\s+(\S+)/) { $realfile = $1; $realfile =~ s@^([^/]*)/@@ if (!$file); $in_commit_log = 0; $p1_prefix = $1; if (!$file && $tree && $p1_prefix ne '' && -e "$root/$p1_prefix") { WARN("PATCH_PREFIX", "patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n"); } if ($realfile =~ m@^include/asm/@) { ERROR("MODIFIED_INCLUDE_ASM", "do not modify files in include/asm, change architecture specific files in include/asm-\n" . "$here$rawline\n"); } $found_file = 1; } #make up the handle for any error we report on this line if ($showfile) { $prefix = "$realfile:$realline: " } elsif ($emacs) { if ($file) { $prefix = "$filename:$realline: "; } else { $prefix = "$filename:$linenr: "; } } if ($found_file) { if (is_maintained_obsolete($realfile)) { WARN("OBSOLETE", "$realfile is marked as 'obsolete' in the MAINTAINERS hierarchy. No unnecessary modifications please.\n"); } if ($realfile =~ m@^(?:drivers/net/|net/|drivers/staging/)@) { $check = 1; } else { $check = $check_orig; } $checklicenseline = 1; if ($realfile !~ /^MAINTAINERS/) { my $last_binding_patch = $is_binding_patch; $is_binding_patch = () = $realfile =~ m@^(?:Documentation/devicetree/|include/dt-bindings/)@; if (($last_binding_patch != -1) && ($last_binding_patch ^ $is_binding_patch)) { WARN("DT_SPLIT_BINDING_PATCH", "DT binding docs and includes should be a separate patch. See: Documentation/devicetree/bindings/submitting-patches.rst\n"); } } next; } $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); my $hereline = "$here\n$rawline\n"; my $herecurr = "$here\n$rawline\n"; my $hereprev = "$here\n$prevrawline\n$rawline\n"; $cnt_lines++ if ($realcnt != 0); # Verify the existence of a commit log if appropriate # 2 is used because a $signature is counted in $commit_log_lines if ($in_commit_log) { if ($line !~ /^\s*$/) { $commit_log_lines++; #could be a $signature } } elsif ($has_commit_log && $commit_log_lines < 2) { WARN("COMMIT_MESSAGE", "Missing commit description - Add an appropriate one\n"); $commit_log_lines = 2; #warn only once } # Check if the commit log has what seems like a diff which can confuse patch if ($in_commit_log && !$commit_log_has_diff && (($line =~ m@^\s+diff\b.*a/([\w/]+)@ && $line =~ m@^\s+diff\b.*a/[\w/]+\s+b/$1\b@) || $line =~ m@^\s*(?:\-\-\-\s+a/|\+\+\+\s+b/)@ || $line =~ m/^\s*\@\@ \-\d+,\d+ \+\d+,\d+ \@\@/)) { ERROR("DIFF_IN_COMMIT_MSG", "Avoid using diff content in the commit message - patch(1) might not work\n" . $herecurr); $commit_log_has_diff = 1; } # Check for incorrect file permissions if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) { my $permhere = $here . "FILE: $realfile\n"; if ($realfile !~ m@scripts/@ && $realfile !~ /\.(py|pl|awk|sh)$/) { ERROR("EXECUTE_PERMISSIONS", "do not set execute permissions for source files\n" . $permhere); } } # Check the patch for a From: if (decode("MIME-Header", $line) =~ /^From:\s*(.*)/) { $author = $1; my $curline = $linenr; while(defined($rawlines[$curline]) && ($rawlines[$curline++] =~ /^[ \t]\s*(.*)/)) { $author .= $1; } $author = encode("utf8", $author) if ($line =~ /=\?utf-8\?/i); $author =~ s/"//g; $author = reformat_email($author); } # Check the patch for a signoff: if ($line =~ /^\s*signed-off-by:\s*(.*)/i) { $signoff++; $in_commit_log = 0; if ($author ne '' && $authorsignoff != 1) { if (same_email_addresses($1, $author)) { $authorsignoff = 1; } else { my $ctx = $1; my ($email_name, $email_comment, $email_address, $comment1) = parse_email($ctx); my ($author_name, $author_comment, $author_address, $comment2) = parse_email($author); if (lc $email_address eq lc $author_address && $email_name eq $author_name) { $author_sob = $ctx; $authorsignoff = 2; } elsif (lc $email_address eq lc $author_address) { $author_sob = $ctx; $authorsignoff = 3; } elsif ($email_name eq $author_name) { $author_sob = $ctx; $authorsignoff = 4; my $address1 = $email_address; my $address2 = $author_address; if ($address1 =~ /(\S+)\+\S+(\@.*)/) { $address1 = "$1$2"; } if ($address2 =~ /(\S+)\+\S+(\@.*)/) { $address2 = "$1$2"; } if ($address1 eq $address2) { $authorsignoff = 5; } } } } } # Check for patch separator if ($line =~ /^---$/) { $has_patch_separator = 1; $in_commit_log = 0; } # Check if MAINTAINERS is being updated. If so, there's probably no need to # emit the "does MAINTAINERS need updating?" message on file add/move/delete if ($line =~ /^\s*MAINTAINERS\s*\|/) { $reported_maintainer_file = 1; } # Check signature styles if (!$in_header_lines && $line =~ /^(\s*)([a-z0-9_-]+by:|$signature_tags)(\s*)(.*)/i) { my $space_before = $1; my $sign_off = $2; my $space_after = $3; my $email = $4; my $ucfirst_sign_off = ucfirst(lc($sign_off)); if ($sign_off !~ /$signature_tags/) { my $suggested_signature = find_standard_signature($sign_off); if ($suggested_signature eq "") { WARN("BAD_SIGN_OFF", "Non-standard signature: $sign_off\n" . $herecurr); } else { if (WARN("BAD_SIGN_OFF", "Non-standard signature: '$sign_off' - perhaps '$suggested_signature'?\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/$sign_off/$suggested_signature/; } } } if (defined $space_before && $space_before ne "") { if (WARN("BAD_SIGN_OFF", "Do not use whitespace before $ucfirst_sign_off\n" . $herecurr) && $fix) { $fixed[$fixlinenr] = "$ucfirst_sign_off $email"; } } if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) { if (WARN("BAD_SIGN_OFF", "'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr) && $fix) { $fixed[$fixlinenr] = "$ucfirst_sign_off $email"; } } if (!defined $space_after || $space_after ne " ") { if (WARN("BAD_SIGN_OFF", "Use a single space after $ucfirst_sign_off\n" . $herecurr) && $fix) { $fixed[$fixlinenr] = "$ucfirst_sign_off $email"; } } my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); my $suggested_email = format_email(($email_name, $name_comment, $email_address, $comment)); if ($suggested_email eq "") { ERROR("BAD_SIGN_OFF", "Unrecognized email address: '$email'\n" . $herecurr); } else { my $dequoted = $suggested_email; $dequoted =~ s/^"//; $dequoted =~ s/" 1) { WARN("BAD_SIGN_OFF", "Use a single name comment in email: '$email'\n" . $herecurr); } # stable@vger.kernel.org or stable@kernel.org shouldn't # have an email name. In addition comments should strictly # begin with a # if ($email =~ /^.*stable\@(?:vger\.)?kernel\.org/i) { if (($comment ne "" && $comment !~ /^#.+/) || ($email_name ne "")) { my $cur_name = $email_name; my $new_comment = $comment; $cur_name =~ s/[a-zA-Z\s\-\"]+//g; # Remove brackets enclosing comment text # and # from start of comments to get comment text $new_comment =~ s/^\((.*)\)$/$1/; $new_comment =~ s/^\[(.*)\]$/$1/; $new_comment =~ s/^[\s\#]+|\s+$//g; $new_comment = trim("$new_comment $cur_name") if ($cur_name ne $new_comment); $new_comment = " # $new_comment" if ($new_comment ne ""); my $new_email = "$email_address$new_comment"; if (WARN("BAD_STABLE_ADDRESS_STYLE", "Invalid email format for stable: '$email', prefer '$new_email'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; } } } elsif ($comment ne "" && $comment !~ /^(?:#.+|\(.+\))$/) { my $new_comment = $comment; # Extract comment text from within brackets or # c89 style /*...*/ comments $new_comment =~ s/^\[(.*)\]$/$1/; $new_comment =~ s/^\/\*(.*)\*\/$/$1/; $new_comment = trim($new_comment); $new_comment =~ s/^[^\w]$//; # Single lettered comment with non word character is usually a typo $new_comment = "($new_comment)" if ($new_comment ne ""); my $new_email = format_email($email_name, $name_comment, $email_address, $new_comment); if (WARN("BAD_SIGN_OFF", "Unexpected content after email: '$email', should be: '$new_email'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; } } } # Check for duplicate signatures my $sig_nospace = $line; $sig_nospace =~ s/\s//g; $sig_nospace = lc($sig_nospace); if (defined $signatures{$sig_nospace}) { WARN("BAD_SIGN_OFF", "Duplicate signature\n" . $herecurr); } else { $signatures{$sig_nospace} = 1; } # Check Co-developed-by: immediately followed by Signed-off-by: with same name and email if ($sign_off =~ /^co-developed-by:$/i) { if ($email eq $author) { WARN("BAD_SIGN_OFF", "Co-developed-by: should not be used to attribute nominal patch author '$author'\n" . "$here\n" . $rawline); } if (!defined $lines[$linenr]) { WARN("BAD_SIGN_OFF", "Co-developed-by: must be immediately followed by Signed-off-by:\n" . "$here\n" . $rawline); } elsif ($rawlines[$linenr] !~ /^\s*signed-off-by:\s*(.*)/i) { WARN("BAD_SIGN_OFF", "Co-developed-by: must be immediately followed by Signed-off-by:\n" . "$here\n" . $rawline . "\n" .$rawlines[$linenr]); } elsif ($1 ne $email) { WARN("BAD_SIGN_OFF", "Co-developed-by and Signed-off-by: name/email do not match \n" . "$here\n" . $rawline . "\n" .$rawlines[$linenr]); } } } # Check email subject for common tools that don't need to be mentioned if ($in_header_lines && $line =~ /^Subject:.*\b(?:checkpatch|sparse|smatch)\b[^:]/i) { WARN("EMAIL_SUBJECT", "A patch subject line should describe the change not the tool that found it\n" . $herecurr); } # Check for Gerrit Change-Ids not in any patch context if ($realfile eq '' && !$has_patch_separator && $line =~ /^\s*change-id:/i) { if (ERROR("GERRIT_CHANGE_ID", "Remove Gerrit Change-Id's before submitting upstream\n" . $herecurr) && $fix) { fix_delete_line($fixlinenr, $rawline); } } # Check if the commit log is in a possible stack dump if ($in_commit_log && !$commit_log_possible_stack_dump && ($line =~ /^\s*(?:WARNING:|BUG:)/ || $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ || # timestamp $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/) || $line =~ /^(?:\s+\w+:\s+[0-9a-fA-F]+){3,3}/ || $line =~ /^\s*\#\d+\s*\[[0-9a-fA-F]+\]\s*\w+ at [0-9a-fA-F]+/) { # stack dump address styles $commit_log_possible_stack_dump = 1; } # Check for line lengths > 75 in commit log, warn once if ($in_commit_log && !$commit_log_long_line && length($line) > 75 && !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ || # file delta changes $line =~ /^\s*(?:[\w\.\-]+\/)++[\w\.\-]+:/ || # filename then : $line =~ /^\s*(?:Fixes:|Link:|$signature_tags)/i || # A Fixes: or Link: line or signature tag line $commit_log_possible_stack_dump)) { WARN("COMMIT_LOG_LONG_LINE", "Possible unwrapped commit description (prefer a maximum 75 chars per line)\n" . $herecurr); $commit_log_long_line = 1; } # Reset possible stack dump if a blank line is found if ($in_commit_log && $commit_log_possible_stack_dump && $line =~ /^\s*$/) { $commit_log_possible_stack_dump = 0; } # Check for lines starting with a # if ($in_commit_log && $line =~ /^#/) { if (WARN("COMMIT_COMMENT_SYMBOL", "Commit log lines starting with '#' are dropped by git as comments\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^/ /; } } # Check for git id commit length and improperly formed commit descriptions # A correctly formed commit description is: # commit ("Complete commit subject") # with the commit subject '("' prefix and '")' suffix # This is a fairly compilicated block as it tests for what appears to be # bare SHA-1 hash with minimum length of 5. It also avoids several types of # possible SHA-1 matches. # A commit match can span multiple lines so this block attempts to find a # complete typical commit on a maximum of 3 lines if ($perl_version_ok && $in_commit_log && !$commit_log_possible_stack_dump && $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink|base-commit):/i && $line !~ /^This reverts commit [0-9a-f]{7,40}/ && (($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i || ($line =~ /\bcommit\s*$/i && defined($rawlines[$linenr]) && $rawlines[$linenr] =~ /^\s*[0-9a-f]{5,}\b/i)) || ($line =~ /(?:\s|^)[0-9a-f]{12,40}(?:[\s"'\(\[]|$)/i && $line !~ /[\<\[][0-9a-f]{12,40}[\>\]]/i && $line !~ /\bfixes:\s*[0-9a-f]{12,40}/i))) { my $init_char = "c"; my $orig_commit = ""; my $short = 1; my $long = 0; my $case = 1; my $space = 1; my $id = '0123456789ab'; my $orig_desc = "commit description"; my $description = ""; my $herectx = $herecurr; my $has_parens = 0; my $has_quotes = 0; my $input = $line; if ($line =~ /(?:\bcommit\s+[0-9a-f]{5,}|\bcommit\s*$)/i) { for (my $n = 0; $n < 2; $n++) { if ($input =~ /\bcommit\s+[0-9a-f]{5,}\s*($balanced_parens)/i) { $orig_desc = $1; $has_parens = 1; # Always strip leading/trailing parens then double quotes if existing $orig_desc = substr($orig_desc, 1, -1); if ($orig_desc =~ /^".*"$/) { $orig_desc = substr($orig_desc, 1, -1); $has_quotes = 1; } last; } last if ($#lines < $linenr + $n); $input .= " " . trim($rawlines[$linenr + $n]); $herectx .= "$rawlines[$linenr + $n]\n"; } $herectx = $herecurr if (!$has_parens); } if ($input =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) { $init_char = $1; $orig_commit = lc($2); $short = 0 if ($input =~ /\bcommit\s+[0-9a-f]{12,40}/i); $long = 1 if ($input =~ /\bcommit\s+[0-9a-f]{41,}/i); $space = 0 if ($input =~ /\bcommit [0-9a-f]/i); $case = 0 if ($input =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/); } elsif ($input =~ /\b([0-9a-f]{12,40})\b/i) { $orig_commit = lc($1); } ($id, $description) = git_commit_info($orig_commit, $id, $orig_desc); if (defined($id) && ($short || $long || $space || $case || ($orig_desc ne $description) || !$has_quotes) && $last_git_commit_id_linenr != $linenr - 1) { ERROR("GIT_COMMIT_ID", "Please use git commit description style 'commit <12+ chars of sha1> (\"\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herectx); } #don't report the next line if this line ends in commit and the sha1 hash is the next line $last_git_commit_id_linenr = $linenr if ($line =~ /\bcommit\s*$/i); } # Check for added, moved or deleted files if (!$reported_maintainer_file && !$in_commit_log && ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ || $line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ || ($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ && (defined($1) || defined($2))))) { $is_patch = 1; $reported_maintainer_file = 1; WARN("FILE_PATH_CHANGES", "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr); } # Check for adding new DT bindings not in schema format if (!$in_commit_log && ($line =~ /^new file mode\s*\d+\s*$/) && ($realfile =~ m@^Documentation/devicetree/bindings/.*\.txt$@)) { WARN("DT_SCHEMA_BINDING_PATCH", "DT bindings should be in DT schema format. See: Documentation/devicetree/bindings/writing-schema.rst\n"); } # Check for wrappage within a valid hunk of the file if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { ERROR("CORRUPTED_PATCH", "patch seems to be corrupt (line wrapped?)\n" . $herecurr) if (!$emitted_corrupt++); } # UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php if (($realfile =~ /^$/ || $line =~ /^\+/) && $rawline !~ m/^$UTF8*$/) { my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/); my $blank = copy_spacing($rawline); my $ptr = substr($blank, 0, length($utf8_prefix)) . "^"; my $hereptr = "$hereline$ptr\n"; CHK("INVALID_UTF8", "Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr); } # Check if it's the start of a commit log # (not a header line and we haven't seen the patch filename) if ($in_header_lines && $realfile =~ /^$/ && !($rawline =~ /^\s+(?:\S|$)/ || $rawline =~ /^(?:commit\b|from\b|[\w-]+:)/i)) { $in_header_lines = 0; $in_commit_log = 1; $has_commit_log = 1; } # Check if there is UTF-8 in a commit log when a mail header has explicitly # declined it, i.e defined some charset where it is missing. if ($in_header_lines && $rawline =~ /^Content-Type:.+charset="(.+)".*$/ && $1 !~ /utf-8/i) { $non_utf8_charset = 1; } if ($in_commit_log && $non_utf8_charset && $realfile =~ /^$/ && $rawline =~ /$NON_ASCII_UTF8/) { WARN("UTF8_BEFORE_PATCH", "8-bit UTF-8 used in possible commit log\n" . $herecurr); } # Check for absolute kernel paths in commit message if ($tree && $in_commit_log) { while ($line =~ m{(?:^|\s)(/\S*)}g) { my $file = $1; if ($file =~ m{^(.*?)(?::\d+)+:?$} && check_absolute_file($1, $herecurr)) { # } else { check_absolute_file($file, $herecurr); } } } # Check for various typo / spelling mistakes if (defined($misspellings) && ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) { while ($rawline =~ /(?:^|[^\w\-'`])($misspellings)(?:[^\w\-'`]|$)/gi) { my $typo = $1; my $blank = copy_spacing($rawline); my $ptr = substr($blank, 0, $-[1]) . "^" x length($typo); my $hereptr = "$hereline$ptr\n"; my $typo_fix = $spelling_fix{lc($typo)}; $typo_fix = ucfirst($typo_fix) if ($typo =~ /^[A-Z]/); $typo_fix = uc($typo_fix) if ($typo =~ /^[A-Z]+$/); my $msg_level = \&WARN; $msg_level = \&CHK if ($file); if (&{$msg_level}("TYPO_SPELLING", "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $hereptr) && $fix) { $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/; } } } # check for invalid commit id if ($in_commit_log && $line =~ /(^fixes:|\bcommit)\s+([0-9a-f]{6,40})\b/i) { my $id; my $description; ($id, $description) = git_commit_info($2, undef, undef); if (!defined($id)) { WARN("UNKNOWN_COMMIT_ID", "Unknown commit id '$2', maybe rebased or not pulled?\n" . $herecurr); } } # check for repeated words separated by a single space # avoid false positive from list command eg, '-rw-r--r-- 1 root root' if (($rawline =~ /^\+/ || $in_commit_log) && $rawline !~ /[bcCdDlMnpPs\?-][rwxsStT-]{9}/) { pos($rawline) = 1 if (!$in_commit_log); while ($rawline =~ /\b($word_pattern) (?=($word_pattern))/g) { my $first = $1; my $second = $2; my $start_pos = $-[1]; my $end_pos = $+[2]; if ($first =~ /(?:struct|union|enum)/) { pos($rawline) += length($first) + length($second) + 1; next; } next if (lc($first) ne lc($second)); next if ($first eq 'long'); # check for character before and after the word matches my $start_char = ''; my $end_char = ''; $start_char = substr($rawline, $start_pos - 1, 1) if ($start_pos > ($in_commit_log ? 0 : 1)); $end_char = substr($rawline, $end_pos, 1) if ($end_pos < length($rawline)); next if ($start_char =~ /^\S$/); next if (index(" \t.,;?!", $end_char) == -1); # avoid repeating hex occurrences like 'ff ff fe 09 ...' if ($first =~ /\b[0-9a-f]{2,}\b/i) { next if (!exists($allow_repeated_words{lc($first)})); } if (WARN("REPEATED_WORD", "Possible repeated word: '$first'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b$first $second\b/$first/; } } # if it's a repeated word on consecutive lines in a comment block if ($prevline =~ /$;+\s*$/ && $prevrawline =~ /($word_pattern)\s*$/) { my $last_word = $1; if ($rawline =~ /^\+\s*\*\s*$last_word /) { if (WARN("REPEATED_WORD", "Possible repeated word: '$last_word'\n" . $hereprev) && $fix) { $fixed[$fixlinenr] =~ s/(\+\s*\*\s*)$last_word /$1/; } } } } # ignore non-hunk lines and lines being removed next if (!$hunk_line || $line =~ /^-/); #trailing whitespace if ($line =~ /^\+.*\015/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; if (ERROR("DOS_LINE_ENDINGS", "DOS line endings\n" . $herevet) && $fix) { $fixed[$fixlinenr] =~ s/[\s\015]+$//; } } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; if (ERROR("TRAILING_WHITESPACE", "trailing whitespace\n" . $herevet) && $fix) { $fixed[$fixlinenr] =~ s/\s+$//; } $rpt_cleaners = 1; } # Check for FSF mailing addresses. if ($rawline =~ /\bwrite to the Free/i || $rawline =~ /\b675\s+Mass\s+Ave/i || $rawline =~ /\b59\s+Temple\s+Pl/i || $rawline =~ /\b51\s+Franklin\s+St/i) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; my $msg_level = \&ERROR; $msg_level = \&CHK if ($file); &{$msg_level}("FSF_MAILING_ADDRESS", "Do not include the paragraph about writing to the Free Software Foundation's mailing address from the sample GPL notice. The FSF has changed addresses in the past, and may do so again. Linux already includes a copy of the GPL.\n" . $herevet) } # check for Kconfig help text having a real description # Only applies when adding the entry originally, after that we do not have # sufficient context to determine whether it is indeed long enough. if ($realfile =~ /Kconfig/ && # 'choice' is usually the last thing on the line (though # Kconfig supports named choices), so use a word boundary # (\b) rather than a whitespace character (\s) $line =~ /^\+\s*(?:config|menuconfig|choice)\b/) { my $length = 0; my $cnt = $realcnt; my $ln = $linenr + 1; my $f; my $is_start = 0; my $is_end = 0; for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) { $f = $lines[$ln - 1]; $cnt-- if ($lines[$ln - 1] !~ /^-/); $is_end = $lines[$ln - 1] =~ /^\+/; next if ($f =~ /^-/); last if (!$file && $f =~ /^\@\@/); if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) { $is_start = 1; } elsif ($lines[$ln - 1] =~ /^\+\s*(?:---)?help(?:---)?$/) { $length = -1; } $f =~ s/^.//; $f =~ s/#.*//; $f =~ s/^\s+//; next if ($f =~ /^$/); # This only checks context lines in the patch # and so hopefully shouldn't trigger false # positives, even though some of these are # common words in help texts if ($f =~ /^\s*(?:config|menuconfig|choice|endchoice| if|endif|menu|endmenu|source)\b/x) { $is_end = 1; last; } $length++; } if ($is_start && $is_end && $length < $min_conf_desc_length) { WARN("CONFIG_DESCRIPTION", "please write a paragraph that describes the config symbol fully\n" . $herecurr); } #print "is_start<$is_start> is_end<$is_end> length<$length>\n"; } # check MAINTAINERS entries if ($realfile =~ /^MAINTAINERS$/) { # check MAINTAINERS entries for the right form if ($rawline =~ /^\+[A-Z]:/ && $rawline !~ /^\+[A-Z]:\t\S/) { if (WARN("MAINTAINERS_STYLE", "MAINTAINERS entries use one tab after TYPE:\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(\+[A-Z]):\s*/$1:\t/; } } # check MAINTAINERS entries for the right ordering too my $preferred_order = 'MRLSWQBCPTFXNK'; if ($rawline =~ /^\+[A-Z]:/ && $prevrawline =~ /^[\+ ][A-Z]:/) { $rawline =~ /^\+([A-Z]):\s*(.*)/; my $cur = $1; my $curval = $2; $prevrawline =~ /^[\+ ]([A-Z]):\s*(.*)/; my $prev = $1; my $prevval = $2; my $curindex = index($preferred_order, $cur); my $previndex = index($preferred_order, $prev); if ($curindex < 0) { WARN("MAINTAINERS_STYLE", "Unknown MAINTAINERS entry type: '$cur'\n" . $herecurr); } else { if ($previndex >= 0 && $curindex < $previndex) { WARN("MAINTAINERS_STYLE", "Misordered MAINTAINERS entry - list '$cur:' before '$prev:'\n" . $hereprev); } elsif ((($prev eq 'F' && $cur eq 'F') || ($prev eq 'X' && $cur eq 'X')) && ($prevval cmp $curval) > 0) { WARN("MAINTAINERS_STYLE", "Misordered MAINTAINERS entry - list file patterns in alphabetic order\n" . $hereprev); } } } } if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) && ($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) { my $flag = $1; my $replacement = { 'EXTRA_AFLAGS' => 'asflags-y', 'EXTRA_CFLAGS' => 'ccflags-y', 'EXTRA_CPPFLAGS' => 'cppflags-y', 'EXTRA_LDFLAGS' => 'ldflags-y', }; WARN("DEPRECATED_VARIABLE", "Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag}); } # check for DT compatible documentation if (defined $root && (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) || ($realfile =~ /\.[ch]$/ && $line =~ /^\+.*\.compatible\s*=\s*\"/))) { my @compats = $rawline =~ /\"([a-zA-Z0-9\-\,\.\+_]+)\"/g; my $dt_path = $root . "/Documentation/devicetree/bindings/"; my $vp_file = $dt_path . "vendor-prefixes.yaml"; foreach my $compat (@compats) { my $compat2 = $compat; $compat2 =~ s/\,[a-zA-Z0-9]*\-/\,<\.\*>\-/; my $compat3 = $compat; $compat3 =~ s/\,([a-z]*)[0-9]*\-/\,$1<\.\*>\-/; `grep -Erq "$compat|$compat2|$compat3" $dt_path`; if ( $? >> 8 ) { WARN("UNDOCUMENTED_DT_STRING", "DT compatible string \"$compat\" appears un-documented -- check $dt_path\n" . $herecurr); } next if $compat !~ /^([a-zA-Z0-9\-]+)\,/; my $vendor = $1; `grep -Eq "\\"\\^\Q$vendor\E,\\.\\*\\":" $vp_file`; if ( $? >> 8 ) { WARN("UNDOCUMENTED_DT_STRING", "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_file\n" . $herecurr); } } } # check for using SPDX license tag at beginning of files if ($realline == $checklicenseline) { if ($rawline =~ /^[ \+]\s*\#\!\s*\//) { $checklicenseline = 2; } elsif ($rawline =~ /^\+/) { my $comment = ""; if ($realfile =~ /\.(h|s|S)$/) { $comment = '/*'; } elsif ($realfile =~ /\.(c|dts|dtsi)$/) { $comment = '//'; } elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc|yaml)$/) { $comment = '#'; } elsif ($realfile =~ /\.rst$/) { $comment = '..'; } # check SPDX comment style for .[chsS] files if ($realfile =~ /\.[chsS]$/ && $rawline =~ /SPDX-License-Identifier:/ && $rawline !~ m@^\+\s*\Q$comment\E\s*@) { WARN("SPDX_LICENSE_TAG", "Improper SPDX comment style for '$realfile', please use '$comment' instead\n" . $herecurr); } if ($comment !~ /^$/ && $rawline !~ m@^\+\Q$comment\E SPDX-License-Identifier: @) { WARN("SPDX_LICENSE_TAG", "Missing or malformed SPDX-License-Identifier tag in line $checklicenseline\n" . $herecurr); } elsif ($rawline =~ /(SPDX-License-Identifier: .*)/) { my $spdx_license = $1; if (!is_SPDX_License_valid($spdx_license)) { WARN("SPDX_LICENSE_TAG", "'$spdx_license' is not supported in LICENSES/...\n" . $herecurr); } if ($realfile =~ m@^Documentation/devicetree/bindings/@ && not $spdx_license =~ /GPL-2\.0.*BSD-2-Clause/) { my $msg_level = \&WARN; $msg_level = \&CHK if ($file); if (&{$msg_level}("SPDX_LICENSE_TAG", "DT binding documents should be licensed (GPL-2.0-only OR BSD-2-Clause)\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/SPDX-License-Identifier: .*/SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)/; } } } } } # check for embedded filenames if ($rawline =~ /^\+.*\Q$realfile\E/) { WARN("EMBEDDED_FILENAME", "It's generally not useful to have the filename in the file\n" . $herecurr); } # check we are in a valid source file if not then ignore this hunk next if ($realfile !~ /\.(h|c|s|S|sh|dtsi|dts)$/); # check for using SPDX-License-Identifier on the wrong line number if ($realline != $checklicenseline && $rawline =~ /\bSPDX-License-Identifier:/ && substr($line, @-, @+ - @-) eq "$;" x (@+ - @-)) { WARN("SPDX_LICENSE_TAG", "Misplaced SPDX-License-Identifier tag - use line $checklicenseline instead\n" . $herecurr); } # line length limit (with some exclusions) # # There are a few types of lines that may extend beyond $max_line_length: # logging functions like pr_info that end in a string # lines with a single string # #defines that are a single string # lines with an RFC3986 like URL # # There are 3 different line length message types: # LONG_LINE_COMMENT a comment starts before but extends beyond $max_line_length # LONG_LINE_STRING a string starts before but extends beyond $max_line_length # LONG_LINE all other lines longer than $max_line_length # # if LONG_LINE is ignored, the other 2 types are also ignored # if ($line =~ /^\+/ && $length > $max_line_length) { my $msg_type = "LONG_LINE"; # Check the allowed long line types first # logging functions that end in a string that starts # before $max_line_length if ($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(?:KERN_\S+\s*|[^"]*))?($String\s*(?:|,|\)\s*;)\s*)$/ && length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { $msg_type = ""; # lines with only strings (w/ possible termination) # #defines with only strings } elsif ($line =~ /^\+\s*$String\s*(?:\s*|,|\)\s*;)\s*$/ || $line =~ /^\+\s*#\s*define\s+\w+\s+$String$/) { $msg_type = ""; # More special cases } elsif ($line =~ /^\+.*\bEFI_GUID\s*\(/ || $line =~ /^\+\s*(?:\w+)?\s*DEFINE_PER_CPU/) { $msg_type = ""; # URL ($rawline is used in case the URL is in a comment) } elsif ($rawline =~ /^\+.*\b[a-z][\w\.\+\-]*:\/\/\S+/i) { $msg_type = ""; # Otherwise set the alternate message types # a comment starts before $max_line_length } elsif ($line =~ /($;[\s$;]*)$/ && length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { $msg_type = "LONG_LINE_COMMENT" # a quoted string starts before $max_line_length } elsif ($sline =~ /\s*($String(?:\s*(?:\\|,\s*|\)\s*;\s*))?)$/ && length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { $msg_type = "LONG_LINE_STRING" } if ($msg_type ne "" && (show_type("LONG_LINE") || show_type($msg_type))) { my $msg_level = \&WARN; $msg_level = \&CHK if ($file); &{$msg_level}($msg_type, "line length of $length exceeds $max_line_length columns\n" . $herecurr); } } # check for adding lines without a newline. if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { if (WARN("MISSING_EOF_NEWLINE", "adding a line without newline at end of file\n" . $herecurr) && $fix) { fix_delete_line($fixlinenr+1, "No newline at end of file"); } } # check for .L prefix local symbols in .S files if ($realfile =~ /\.S$/ && $line =~ /^\+\s*(?:[A-Z]+_)?SYM_[A-Z]+_(?:START|END)(?:_[A-Z_]+)?\s*\(\s*\.L/) { WARN("AVOID_L_PREFIX", "Avoid using '.L' prefixed local symbol names for denoting a range of code via 'SYM_*_START/END' annotations; see Documentation/asm-annotations.rst\n" . $herecurr); } # check we are in a valid source file C or perl if not then ignore this hunk next if ($realfile !~ /\.(h|c|pl|dtsi|dts)$/); # at the beginning of a line in a .c file there should be no # spaces unless it is a single space in front of a multiline comment if ($realfile =~ /\.c$/ && $rawline =~ /^\+\t* [^*]/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; WARN("CODE_INDENT", "code indent should never contain spaces\n" . $herevet); } # at the beginning of a line any tabs must come first and anything # more than $tabsize must use tabs. if ($rawline =~ /^\+\s* \t\s*\S/ || $rawline =~ /^\+\s* \s*/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; $rpt_cleaners = 1; if (ERROR("CODE_INDENT", "code indent should use tabs where possible\n" . $herevet) && $fix) { $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; } } # check for space before tabs. if ($rawline =~ /^\+/ && $rawline =~ / \t/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; if (WARN("SPACE_BEFORE_TAB", "please, no space before tabs\n" . $herevet) && $fix) { while ($fixed[$fixlinenr] =~ s/(^\+.*) {$tabsize,$tabsize}\t/$1\t\t/) {} while ($fixed[$fixlinenr] =~ s/(^\+.*) +\t/$1\t/) {} } } # check for assignments on the start of a line if ($sline =~ /^\+\s+($Assignment)[^=]/) { my $operator = $1; if (CHK("ASSIGNMENT_CONTINUATIONS", "Assignment operator '$1' should be on the previous line\n" . $hereprev) && $fix && $prevrawline =~ /^\+/) { # add assignment operator to the previous line, remove from current line $fixed[$fixlinenr - 1] .= " $operator"; $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; } } # check for && or || at the start of a line if ($rawline =~ /^\+\s*(&&|\|\|)/) { my $operator = $1; if (CHK("LOGICAL_CONTINUATIONS", "Logical continuations should be on the previous line\n" . $hereprev) && $fix && $prevrawline =~ /^\+/) { # insert logical operator at last non-comment, non-whitepsace char on previous line $prevline =~ /[\s$;]*$/; my $line_end = substr($prevrawline, $-[0]); $fixed[$fixlinenr - 1] =~ s/\Q$line_end\E$/ $operator$line_end/; $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; } } # check indentation starts on a tab stop if ($perl_version_ok && $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$)|$Declare\s*$Ident\s*[;=])/) { my $indent = length($1); if ($indent % $tabsize) { if (WARN("TABSTOP", "Statements should start on a tabstop\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/$tabsize)@e; } } } # check multi-line statement indentation matches previous line if ($perl_version_ok && $prevline =~ /^\+([ \t]*)((?:$c90_Keywords(?:\s+if)\s*)|(?:$Declare\s*)?(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*|(?:\*\s*)*$Lval\s*=\s*$Ident\s*)\(.*(\&\&|\|\||,)\s*$/) { $prevline =~ /^\+(\t*)(.*)$/; my $oldindent = $1; my $rest = $2; my $pos = pos_last_openparen($rest); if ($pos >= 0) { $line =~ /^(\+| )([ \t]*)/; my $newindent = $2; my $goodtabindent = $oldindent . "\t" x ($pos / $tabsize) . " " x ($pos % $tabsize); my $goodspaceindent = $oldindent . " " x $pos; if ($newindent ne $goodtabindent && $newindent ne $goodspaceindent) { if (CHK("PARENTHESIS_ALIGNMENT", "Alignment should match open parenthesis\n" . $hereprev) && $fix && $line =~ /^\+/) { $fixed[$fixlinenr] =~ s/^\+[ \t]*/\+$goodtabindent/; } } } } # check for space after cast like "(int) foo" or "(struct foo) bar" # avoid checking a few false positives: # "sizeof(<type>)" or "__alignof__(<type>)" # function pointer declarations like "(*foo)(int) = bar;" # structure definitions like "(struct foo) { 0 };" # multiline macros that define functions # known attributes or the __attribute__ keyword if ($line =~ /^\+(.*)\(\s*$Type\s*\)([ \t]++)((?![={]|\\$|$Attribute|__attribute__))/ && (!defined($1) || $1 !~ /\b(?:sizeof|__alignof__)\s*$/)) { if (CHK("SPACING", "No space is necessary after a cast\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(\(\s*$Type\s*\))[ \t]+/$1/; } } # Block comment styles # Networking with an initial /* if ($realfile =~ m@^(drivers/net/|net/)@ && $prevrawline =~ /^\+[ \t]*\/\*[ \t]*$/ && $rawline =~ /^\+[ \t]*\*/ && $realline > 3) { # Do not warn about the initial copyright comment block after SPDX-License-Identifier WARN("NETWORKING_BLOCK_COMMENT_STYLE", "networking block comments don't use an empty /* line, use /* Comment...\n" . $hereprev); } # Block comments use * on subsequent lines if ($prevline =~ /$;[ \t]*$/ && #ends in comment $prevrawline =~ /^\+.*?\/\*/ && #starting /* $prevrawline !~ /\*\/[ \t]*$/ && #no trailing */ $rawline =~ /^\+/ && #line is new $rawline !~ /^\+[ \t]*\*/) { #no leading * WARN("BLOCK_COMMENT_STYLE", "Block comments use * on subsequent lines\n" . $hereprev); } # Block comments use */ on trailing lines if ($rawline !~ m@^\+[ \t]*\*/[ \t]*$@ && #trailing */ $rawline !~ m@^\+.*/\*.*\*/[ \t]*$@ && #inline /*...*/ $rawline !~ m@^\+.*\*{2,}/[ \t]*$@ && #trailing **/ $rawline =~ m@^\+[ \t]*.+\*\/[ \t]*$@) { #non blank */ WARN("BLOCK_COMMENT_STYLE", "Block comments use a trailing */ on a separate line\n" . $herecurr); } # Block comment * alignment if ($prevline =~ /$;[ \t]*$/ && #ends in comment $line =~ /^\+[ \t]*$;/ && #leading comment $rawline =~ /^\+[ \t]*\*/ && #leading * (($prevrawline =~ /^\+.*?\/\*/ && #leading /* $prevrawline !~ /\*\/[ \t]*$/) || #no trailing */ $prevrawline =~ /^\+[ \t]*\*/)) { #leading * my $oldindent; $prevrawline =~ m@^\+([ \t]*/?)\*@; if (defined($1)) { $oldindent = expand_tabs($1); } else { $prevrawline =~ m@^\+(.*/?)\*@; $oldindent = expand_tabs($1); } $rawline =~ m@^\+([ \t]*)\*@; my $newindent = $1; $newindent = expand_tabs($newindent); if (length($oldindent) ne length($newindent)) { WARN("BLOCK_COMMENT_STYLE", "Block comments should align the * on each line\n" . $hereprev); } } # check for missing blank lines after struct/union declarations # with exceptions for various attributes and macros if ($prevline =~ /^[\+ ]};?\s*$/ && $line =~ /^\+/ && !($line =~ /^\+\s*$/ || $line =~ /^\+\s*EXPORT_SYMBOL/ || $line =~ /^\+\s*MODULE_/i || $line =~ /^\+\s*\#\s*(?:end|elif|else)/ || $line =~ /^\+[a-z_]*init/ || $line =~ /^\+\s*(?:static\s+)?[A-Z_]*ATTR/ || $line =~ /^\+\s*DECLARE/ || $line =~ /^\+\s*builtin_[\w_]*driver/ || $line =~ /^\+\s*__setup/)) { if (CHK("LINE_SPACING", "Please use a blank line after function/struct/union/enum declarations\n" . $hereprev) && $fix) { fix_insert_line($fixlinenr, "\+"); } } # check for multiple consecutive blank lines if ($prevline =~ /^[\+ ]\s*$/ && $line =~ /^\+\s*$/ && $last_blank_line != ($linenr - 1)) { if (CHK("LINE_SPACING", "Please don't use multiple blank lines\n" . $hereprev) && $fix) { fix_delete_line($fixlinenr, $rawline); } $last_blank_line = $linenr; } # check for spaces at the beginning of a line. # Exceptions: # 1) within comments # 2) indented preprocessor commands # 3) hanging labels if ($rawline =~ /^\+ / && $line !~ /^\+ *(?:$;|#|$Ident:)/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; if (WARN("LEADING_SPACE", "please, no spaces at the start of a line\n" . $herevet) && $fix) { $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; } } # check we are in a valid C source file if not then ignore this hunk next if ($realfile !~ /\.(h|c)$/); # check for unusual line ending [ or ( if ($line =~ /^\+.*([\[\(])\s*$/) { CHK("OPEN_ENDED_LINE", "Lines should not end with a '$1'\n" . $herecurr); } # check if this appears to be the start function declaration, save the name if ($sline =~ /^\+\{\s*$/ && $prevline =~ /^\+(?:(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*)?($Ident)\(/) { $context_function = $1; } # check if this appears to be the end of function declaration if ($sline =~ /^\+\}\s*$/) { undef $context_function; } # check indentation of any line with a bare else # (but not if it is a multiple line "if (foo) return bar; else return baz;") # if the previous line is a break or return and is indented 1 tab more... if ($sline =~ /^\+([\t]+)(?:}[ \t]*)?else(?:[ \t]*{)?\s*$/) { my $tabs = length($1) + 1; if ($prevline =~ /^\+\t{$tabs,$tabs}break\b/ || ($prevline =~ /^\+\t{$tabs,$tabs}return\b/ && defined $lines[$linenr] && $lines[$linenr] !~ /^[ \+]\t{$tabs,$tabs}return/)) { WARN("UNNECESSARY_ELSE", "else is not generally useful after a break or return\n" . $hereprev); } } # check indentation of a line with a break; # if the previous line is a goto, return or break # and is indented the same # of tabs if ($sline =~ /^\+([\t]+)break\s*;\s*$/) { my $tabs = $1; if ($prevline =~ /^\+$tabs(goto|return|break)\b/) { if (WARN("UNNECESSARY_BREAK", "break is not useful after a $1\n" . $hereprev) && $fix) { fix_delete_line($fixlinenr, $rawline); } } } # check for RCS/CVS revision markers if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) { WARN("CVS_KEYWORD", "CVS style keyword markers, these will _not_ be updated\n". $herecurr); } # check for old HOTPLUG __dev<foo> section markings if ($line =~ /\b(__dev(init|exit)(data|const|))\b/) { WARN("HOTPLUG_SECTION", "Using $1 is unnecessary\n" . $herecurr); } # Check for potential 'bare' types my ($stat, $cond, $line_nr_next, $remain_next, $off_next, $realline_next); #print "LINE<$line>\n"; if ($linenr > $suppress_statement && $realcnt && $sline =~ /.\s*\S/) { ($stat, $cond, $line_nr_next, $remain_next, $off_next) = ctx_statement_block($linenr, $realcnt, 0); $stat =~ s/\n./\n /g; $cond =~ s/\n./\n /g; #print "linenr<$linenr> <$stat>\n"; # If this statement has no statement boundaries within # it there is no point in retrying a statement scan # until we hit end of it. my $frag = $stat; $frag =~ s/;+\s*$//; if ($frag !~ /(?:{|;)/) { #print "skip<$line_nr_next>\n"; $suppress_statement = $line_nr_next; } # Find the real next line. $realline_next = $line_nr_next; if (defined $realline_next && (!defined $lines[$realline_next - 1] || substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) { $realline_next++; } my $s = $stat; $s =~ s/{.*$//s; # Ignore goto labels. if ($s =~ /$Ident:\*$/s) { # Ignore functions being called } elsif ($s =~ /^.\s*$Ident\s*\(/s) { } elsif ($s =~ /^.\s*else\b/s) { # declarations always start with types } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) { my $type = $1; $type =~ s/\s+/ /g; possible($type, "A:" . $s); # definitions in global scope can only start with types } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) { possible($1, "B:" . $s); } # any (foo ... *) is a pointer cast, and foo is a type while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) { possible($1, "C:" . $s); } # Check for any sort of function declaration. # int foo(something bar, other baz); # void (*store_gdt)(x86_descr_ptr *); if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) { my ($name_len) = length($1); my $ctx = $s; substr($ctx, 0, $name_len + 1, ''); $ctx =~ s/\)[^\)]*$//; for my $arg (split(/\s*,\s*/, $ctx)) { if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) { possible($1, "D:" . $s); } } } } # # Checks which may be anchored in the context. # # Check for switch () and associated case and default # statements should be at the same indent. if ($line=~/\bswitch\s*\(.*\)/) { my $err = ''; my $sep = ''; my @ctx = ctx_block_outer($linenr, $realcnt); shift(@ctx); for my $ctx (@ctx) { my ($clen, $cindent) = line_stats($ctx); if ($ctx =~ /^\+\s*(case\s+|default:)/ && $indent != $cindent) { $err .= "$sep$ctx\n"; $sep = ''; } else { $sep = "[...]\n"; } } if ($err ne '') { ERROR("SWITCH_CASE_INDENT_LEVEL", "switch and case should be at the same indent\n$hereline$err"); } } # if/while/etc brace do not go on next line, unless defining a do while loop, # or if that brace on the next line is for something else if ($line =~ /(.*)\b((?:if|while|for|switch|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) { my $pre_ctx = "$1$2"; my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0); if ($line =~ /^\+\t{6,}/) { WARN("DEEP_INDENTATION", "Too many leading tabs - consider code refactoring\n" . $herecurr); } my $ctx_cnt = $realcnt - $#ctx - 1; my $ctx = join("\n", @ctx); my $ctx_ln = $linenr; my $ctx_skip = $realcnt; while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt && defined $lines[$ctx_ln - 1] && $lines[$ctx_ln - 1] =~ /^-/)) { ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n"; $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/); $ctx_ln++; } #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n"; #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n"; if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ && $ctx =~ /\)\s*\;\s*$/ && defined $lines[$ctx_ln - 1]) { my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]); if ($nindent > $indent) { WARN("TRAILING_SEMICOLON", "trailing semicolon indicates no statements, indent implies otherwise\n" . "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); } } } # Check relative indent for conditionals and blocks. if ($line =~ /\b(?:(?:if|while|for|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|(?:do|else)\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) { ($stat, $cond, $line_nr_next, $remain_next, $off_next) = ctx_statement_block($linenr, $realcnt, 0) if (!defined $stat); my ($s, $c) = ($stat, $cond); substr($s, 0, length($c), ''); # remove inline comments $s =~ s/$;/ /g; $c =~ s/$;/ /g; # Find out how long the conditional actually is. my @newlines = ($c =~ /\n/gs); my $cond_lines = 1 + $#newlines; # Make sure we remove the line prefixes as we have # none on the first line, and are going to readd them # where necessary. $s =~ s/\n./\n/gs; while ($s =~ /\n\s+\\\n/) { $cond_lines += $s =~ s/\n\s+\\\n/\n/g; } # We want to check the first line inside the block # starting at the end of the conditional, so remove: # 1) any blank line termination # 2) any opening brace { on end of the line # 3) any do (...) { my $continuation = 0; my $check = 0; $s =~ s/^.*\bdo\b//; $s =~ s/^\s*{//; if ($s =~ s/^\s*\\//) { $continuation = 1; } if ($s =~ s/^\s*?\n//) { $check = 1; $cond_lines++; } # Also ignore a loop construct at the end of a # preprocessor statement. if (($prevline =~ /^.\s*#\s*define\s/ || $prevline =~ /\\\s*$/) && $continuation == 0) { $check = 0; } my $cond_ptr = -1; $continuation = 0; while ($cond_ptr != $cond_lines) { $cond_ptr = $cond_lines; # If we see an #else/#elif then the code # is not linear. if ($s =~ /^\s*\#\s*(?:else|elif)/) { $check = 0; } # Ignore: # 1) blank lines, they should be at 0, # 2) preprocessor lines, and # 3) labels. if ($continuation || $s =~ /^\s*?\n/ || $s =~ /^\s*#\s*?/ || $s =~ /^\s*$Ident\s*:/) { $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0; if ($s =~ s/^.*?\n//) { $cond_lines++; } } } my (undef, $sindent) = line_stats("+" . $s); my $stat_real = raw_line($linenr, $cond_lines); # Check if either of these lines are modified, else # this is not this patch's fault. if (!defined($stat_real) || $stat !~ /^\+/ && $stat_real !~ /^\+/) { $check = 0; } if (defined($stat_real) && $cond_lines > 1) { $stat_real = "[...]\n$stat_real"; } #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; if ($check && $s ne '' && (($sindent % $tabsize) != 0 || ($sindent < $indent) || ($sindent == $indent && ($s !~ /^\s*(?:\}|\{|else\b)/)) || ($sindent > $indent + $tabsize))) { WARN("SUSPECT_CODE_INDENT", "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); } } # Track the 'values' across context and added lines. my $opline = $line; $opline =~ s/^./ /; my ($curr_values, $curr_vars) = annotate_values($opline . "\n", $prev_values); $curr_values = $prev_values . $curr_values; if ($dbg_values) { my $outline = $opline; $outline =~ s/\t/ /g; print "$linenr > .$outline\n"; print "$linenr > $curr_values\n"; print "$linenr > $curr_vars\n"; } $prev_values = substr($curr_values, -1); #ignore lines not being added next if ($line =~ /^[^\+]/); # check for self assignments used to avoid compiler warnings # e.g.: int foo = foo, *bar = NULL; # struct foo bar = *(&(bar)); if ($line =~ /^\+\s*(?:$Declare)?([A-Za-z_][A-Za-z\d_]*)\s*=/) { my $var = $1; if ($line =~ /^\+\s*(?:$Declare)?$var\s*=\s*(?:$var|\*\s*\(?\s*&\s*\(?\s*$var\s*\)?\s*\)?)\s*[;,]/) { WARN("SELF_ASSIGNMENT", "Do not use self-assignments to avoid compiler warnings\n" . $herecurr); } } # check for dereferences that span multiple lines if ($prevline =~ /^\+.*$Lval\s*(?:\.|->)\s*$/ && $line =~ /^\+\s*(?!\#\s*(?!define\s+|if))\s*$Lval/) { $prevline =~ /($Lval\s*(?:\.|->))\s*$/; my $ref = $1; $line =~ /^.\s*($Lval)/; $ref .= $1; $ref =~ s/\s//g; WARN("MULTILINE_DEREFERENCE", "Avoid multiple line dereference - prefer '$ref'\n" . $hereprev); } # check for declarations of signed or unsigned without int while ($line =~ m{\b($Declare)\s*(?!char\b|short\b|int\b|long\b)\s*($Ident)?\s*[=,;\[\)\(]}g) { my $type = $1; my $var = $2; $var = "" if (!defined $var); if ($type =~ /^(?:(?:$Storage|$Inline|$Attribute)\s+)*((?:un)?signed)((?:\s*\*)*)\s*$/) { my $sign = $1; my $pointer = $2; $pointer = "" if (!defined $pointer); if (WARN("UNSPECIFIED_INT", "Prefer '" . trim($sign) . " int" . rtrim($pointer) . "' to bare use of '$sign" . rtrim($pointer) . "'\n" . $herecurr) && $fix) { my $decl = trim($sign) . " int "; my $comp_pointer = $pointer; $comp_pointer =~ s/\s//g; $decl .= $comp_pointer; $decl = rtrim($decl) if ($var eq ""); $fixed[$fixlinenr] =~ s@\b$sign\s*\Q$pointer\E\s*$var\b@$decl$var@; } } } # TEST: allow direct testing of the type matcher. if ($dbg_type) { if ($line =~ /^.\s*$Declare\s*$/) { ERROR("TEST_TYPE", "TEST: is type\n" . $herecurr); } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) { ERROR("TEST_NOT_TYPE", "TEST: is not type ($1 is)\n". $herecurr); } next; } # TEST: allow direct testing of the attribute matcher. if ($dbg_attr) { if ($line =~ /^.\s*$Modifier\s*$/) { ERROR("TEST_ATTR", "TEST: is attr\n" . $herecurr); } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) { ERROR("TEST_NOT_ATTR", "TEST: is not attr ($1 is)\n". $herecurr); } next; } # check for initialisation to aggregates open brace on the next line if ($line =~ /^.\s*{/ && $prevline =~ /(?:^|[^=])=\s*$/) { if (ERROR("OPEN_BRACE", "that open brace { should be on the previous line\n" . $hereprev) && $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { fix_delete_line($fixlinenr - 1, $prevrawline); fix_delete_line($fixlinenr, $rawline); my $fixedline = $prevrawline; $fixedline =~ s/\s*=\s*$/ = {/; fix_insert_line($fixlinenr, $fixedline); $fixedline = $line; $fixedline =~ s/^(.\s*)\{\s*/$1/; fix_insert_line($fixlinenr, $fixedline); } } # # Checks which are anchored on the added line. # # check for malformed paths in #include statements (uses RAW line) if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) { my $path = $1; if ($path =~ m{//}) { ERROR("MALFORMED_INCLUDE", "malformed #include filename\n" . $herecurr); } if ($path =~ "^uapi/" && $realfile =~ m@\binclude/uapi/@) { ERROR("UAPI_INCLUDE", "No #include in ...include/uapi/... should use a uapi/ path prefix\n" . $herecurr); } } # no C99 // comments if ($line =~ m{//}) { if (ERROR("C99_COMMENTS", "do not use C99 // comments\n" . $herecurr) && $fix) { my $line = $fixed[$fixlinenr]; if ($line =~ /\/\/(.*)$/) { my $comment = trim($1); $fixed[$fixlinenr] =~ s@\/\/(.*)$@/\* $comment \*/@; } } } # Remove C99 comments. $line =~ s@//.*@@; $opline =~ s@//.*@@; # EXPORT_SYMBOL should immediately follow the thing it is exporting, consider # the whole statement. #print "APW <$lines[$realline_next - 1]>\n"; if (defined $realline_next && exists $lines[$realline_next - 1] && !defined $suppress_export{$realline_next} && ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/)) { # Handle definitions which produce identifiers with # a prefix: # XXX(foo); # EXPORT_SYMBOL(something_foo); my $name = $1; if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ && $name =~ /^${Ident}_$2/) { #print "FOO C name<$name>\n"; $suppress_export{$realline_next} = 1; } elsif ($stat !~ /(?: \n.}\s*$| ^.DEFINE_$Ident\(\Q$name\E\)| ^.DECLARE_$Ident\(\Q$name\E\)| ^.LIST_HEAD\(\Q$name\E\)| ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(| \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\() )/x) { #print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n"; $suppress_export{$realline_next} = 2; } else { $suppress_export{$realline_next} = 1; } } if (!defined $suppress_export{$linenr} && $prevline =~ /^.\s*$/ && ($line =~ /EXPORT_SYMBOL.*\((.*)\)/)) { #print "FOO B <$lines[$linenr - 1]>\n"; $suppress_export{$linenr} = 2; } if (defined $suppress_export{$linenr} && $suppress_export{$linenr} == 2) { WARN("EXPORT_SYMBOL", "EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr); } # check for global initialisers. if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/ && !exclude_global_initialisers($realfile)) { if (ERROR("GLOBAL_INITIALISERS", "do not initialise globals to $1\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(^.$Type\s*$Ident(?:\s+$Modifier)*)\s*=\s*$zero_initializer\s*;/$1;/; } } # check for static initialisers. if ($line =~ /^\+.*\bstatic\s.*=\s*($zero_initializer)\s*;/) { if (ERROR("INITIALISED_STATIC", "do not initialise statics to $1\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(\bstatic\s.*?)\s*=\s*$zero_initializer\s*;/$1;/; } } # check for misordered declarations of char/short/int/long with signed/unsigned while ($sline =~ m{(\b$TypeMisordered\b)}g) { my $tmp = trim($1); WARN("MISORDERED_TYPE", "type '$tmp' should be specified in [[un]signed] [short|int|long|long long] order\n" . $herecurr); } # check for unnecessary <signed> int declarations of short/long/long long while ($sline =~ m{\b($TypeMisordered(\s*\*)*|$C90_int_types)\b}g) { my $type = trim($1); next if ($type !~ /\bint\b/); next if ($type !~ /\b(?:short|long\s+long|long)\b/); my $new_type = $type; $new_type =~ s/\b\s*int\s*\b/ /; $new_type =~ s/\b\s*(?:un)?signed\b\s*/ /; $new_type =~ s/^const\s+//; $new_type = "unsigned $new_type" if ($type =~ /\bunsigned\b/); $new_type = "const $new_type" if ($type =~ /^const\b/); $new_type =~ s/\s+/ /g; $new_type = trim($new_type); if (WARN("UNNECESSARY_INT", "Prefer '$new_type' over '$type' as the int is unnecessary\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b\Q$type\E\b/$new_type/; } } # check for static const char * arrays. if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) { WARN("STATIC_CONST_CHAR_ARRAY", "static const char * array should probably be static const char * const\n" . $herecurr); } # check for initialized const char arrays that should be static const if ($line =~ /^\+\s*const\s+(char|unsigned\s+char|_*u8|(?:[us]_)?int8_t)\s+\w+\s*\[\s*(?:\w+\s*)?\]\s*=\s*"/) { if (WARN("STATIC_CONST_CHAR_ARRAY", "const array should probably be static const\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(^.\s*)const\b/${1}static const/; } } # check for static char foo[] = "bar" declarations. if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) { WARN("STATIC_CONST_CHAR_ARRAY", "static char array declaration should probably be static const char\n" . $herecurr); } # check for const <foo> const where <foo> is not a pointer or array type if ($sline =~ /\bconst\s+($BasicType)\s+const\b/) { my $found = $1; if ($sline =~ /\bconst\s+\Q$found\E\s+const\b\s*\*/) { WARN("CONST_CONST", "'const $found const *' should probably be 'const $found * const'\n" . $herecurr); } elsif ($sline !~ /\bconst\s+\Q$found\E\s+const\s+\w+\s*\[/) { WARN("CONST_CONST", "'const $found const' should probably be 'const $found'\n" . $herecurr); } } # check for const static or static <non ptr type> const declarations # prefer 'static const <foo>' over 'const static <foo>' and 'static <foo> const' if ($sline =~ /^\+\s*const\s+static\s+($Type)\b/ || $sline =~ /^\+\s*static\s+($BasicType)\s+const\b/) { if (WARN("STATIC_CONST", "Move const after static - use 'static const $1'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bconst\s+static\b/static const/; $fixed[$fixlinenr] =~ s/\bstatic\s+($BasicType)\s+const\b/static const $1/; } } # check for non-global char *foo[] = {"bar", ...} declarations. if ($line =~ /^.\s+(?:static\s+|const\s+)?char\s+\*\s*\w+\s*\[\s*\]\s*=\s*\{/) { WARN("STATIC_CONST_CHAR_ARRAY", "char * array declaration might be better as static const\n" . $herecurr); } # check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo) if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) { my $array = $1; if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) { my $array_div = $1; if (WARN("ARRAY_SIZE", "Prefer ARRAY_SIZE($array)\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/; } } } # check for function declarations without arguments like "int foo()" if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) { if (ERROR("FUNCTION_WITHOUT_ARGS", "Bad function definition - $1() should probably be $1(void)\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(\b($Type)\s+($Ident))\s*\(\s*\)/$2 $3(void)/; } } # check for new typedefs, only function parameters and sparse annotations # make sense. if ($line =~ /\btypedef\s/ && $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ && $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ && $line !~ /\b$typeTypedefs\b/ && $line !~ /\b__bitwise\b/) { WARN("NEW_TYPEDEFS", "do not add new typedefs\n" . $herecurr); } # * goes on variable not on type # (char*[ const]) while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) { #print "AA<$1>\n"; my ($ident, $from, $to) = ($1, $2, $2); # Should start with a space. $to =~ s/^(\S)/ $1/; # Should not end with a space. $to =~ s/\s+$//; # '*'s should not have spaces between. while ($to =~ s/\*\s+\*/\*\*/) { } ## print "1: from<$from> to<$to> ident<$ident>\n"; if ($from ne $to) { if (ERROR("POINTER_LOCATION", "\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr) && $fix) { my $sub_from = $ident; my $sub_to = $ident; $sub_to =~ s/\Q$from\E/$to/; $fixed[$fixlinenr] =~ s@\Q$sub_from\E@$sub_to@; } } } while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) { #print "BB<$1>\n"; my ($match, $from, $to, $ident) = ($1, $2, $2, $3); # Should start with a space. $to =~ s/^(\S)/ $1/; # Should not end with a space. $to =~ s/\s+$//; # '*'s should not have spaces between. while ($to =~ s/\*\s+\*/\*\*/) { } # Modifiers should have spaces. $to =~ s/(\b$Modifier$)/$1 /; ## print "2: from<$from> to<$to> ident<$ident>\n"; if ($from ne $to && $ident !~ /^$Modifier$/) { if (ERROR("POINTER_LOCATION", "\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr) && $fix) { my $sub_from = $match; my $sub_to = $match; $sub_to =~ s/\Q$from\E/$to/; $fixed[$fixlinenr] =~ s@\Q$sub_from\E@$sub_to@; } } } # avoid BUG() or BUG_ON() if ($line =~ /\b(?:BUG|BUG_ON)\b/) { my $msg_level = \&WARN; $msg_level = \&CHK if ($file); &{$msg_level}("AVOID_BUG", "Avoid crashing the kernel - try using WARN_ON & recovery code rather than BUG() or BUG_ON()\n" . $herecurr); } # avoid LINUX_VERSION_CODE if ($line =~ /\bLINUX_VERSION_CODE\b/) { WARN("LINUX_VERSION_CODE", "LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr); } # check for uses of printk_ratelimit if ($line =~ /\bprintk_ratelimit\s*\(/) { WARN("PRINTK_RATELIMITED", "Prefer printk_ratelimited or pr_<level>_ratelimited to printk_ratelimit\n" . $herecurr); } # printk should use KERN_* levels if ($line =~ /\bprintk\s*\(\s*(?!KERN_[A-Z]+\b)/) { WARN("PRINTK_WITHOUT_KERN_LEVEL", "printk() should include KERN_<LEVEL> facility level\n" . $herecurr); } # prefer variants of (subsystem|netdev|dev|pr)_<level> to printk(KERN_<LEVEL> if ($line =~ /\b(printk(_once|_ratelimited)?)\s*\(\s*KERN_([A-Z]+)/) { my $printk = $1; my $modifier = $2; my $orig = $3; $modifier = "" if (!defined($modifier)); my $level = lc($orig); $level = "warn" if ($level eq "warning"); my $level2 = $level; $level2 = "dbg" if ($level eq "debug"); $level .= $modifier; $level2 .= $modifier; WARN("PREFER_PR_LEVEL", "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to $printk(KERN_$orig ...\n" . $herecurr); } # prefer dev_<level> to dev_printk(KERN_<LEVEL> if ($line =~ /\bdev_printk\s*\(\s*KERN_([A-Z]+)/) { my $orig = $1; my $level = lc($orig); $level = "warn" if ($level eq "warning"); $level = "dbg" if ($level eq "debug"); WARN("PREFER_DEV_LEVEL", "Prefer dev_$level(... to dev_printk(KERN_$orig, ...\n" . $herecurr); } # trace_printk should not be used in production code. if ($line =~ /\b(trace_printk|trace_puts|ftrace_vprintk)\s*\(/) { WARN("TRACE_PRINTK", "Do not use $1() in production code (this can be ignored if built only with a debug config option)\n" . $herecurr); } # ENOSYS means "bad syscall nr" and nothing else. This will have a small # number of false positives, but assembly files are not checked, so at # least the arch entry code will not trigger this warning. if ($line =~ /\bENOSYS\b/) { WARN("ENOSYS", "ENOSYS means 'invalid syscall nr' and nothing else\n" . $herecurr); } # ENOTSUPP is not a standard error code and should be avoided in new patches. # Folks usually mean EOPNOTSUPP (also called ENOTSUP), when they type ENOTSUPP. # Similarly to ENOSYS warning a small number of false positives is expected. if (!$file && $line =~ /\bENOTSUPP\b/) { if (WARN("ENOTSUPP", "ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bENOTSUPP\b/EOPNOTSUPP/; } } # function brace can't be on same line, except for #defines of do while, # or if closed on same line if ($perl_version_ok && $sline =~ /$Type\s*$Ident\s*$balanced_parens\s*\{/ && $sline !~ /\#\s*define\b.*do\s*\{/ && $sline !~ /}/) { if (ERROR("OPEN_BRACE", "open brace '{' following function definitions go on the next line\n" . $herecurr) && $fix) { fix_delete_line($fixlinenr, $rawline); my $fixed_line = $rawline; $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*)\{(.*)$/; my $line1 = $1; my $line2 = $2; fix_insert_line($fixlinenr, ltrim($line1)); fix_insert_line($fixlinenr, "\+{"); if ($line2 !~ /^\s*$/) { fix_insert_line($fixlinenr, "\+\t" . trim($line2)); } } } # open braces for enum, union and struct go on the same line. if ($line =~ /^.\s*{/ && $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) { if (ERROR("OPEN_BRACE", "open brace '{' following $1 go on the same line\n" . $hereprev) && $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { fix_delete_line($fixlinenr - 1, $prevrawline); fix_delete_line($fixlinenr, $rawline); my $fixedline = rtrim($prevrawline) . " {"; fix_insert_line($fixlinenr, $fixedline); $fixedline = $rawline; $fixedline =~ s/^(.\s*)\{\s*/$1\t/; if ($fixedline !~ /^\+\s*$/) { fix_insert_line($fixlinenr, $fixedline); } } } # missing space after union, struct or enum definition if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident){1,2}[=\{]/) { if (WARN("SPACING", "missing space after $1 definition\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(.\s*(?:typedef\s+)?(?:enum|union|struct)(?:\s+$Ident){1,2})([=\{])/$1 $2/; } } # Function pointer declarations # check spacing between type, funcptr, and args # canonical declaration is "type (*funcptr)(args...)" if ($line =~ /^.\s*($Declare)\((\s*)\*(\s*)($Ident)(\s*)\)(\s*)\(/) { my $declare = $1; my $pre_pointer_space = $2; my $post_pointer_space = $3; my $funcname = $4; my $post_funcname_space = $5; my $pre_args_space = $6; # the $Declare variable will capture all spaces after the type # so check it for a missing trailing missing space but pointer return types # don't need a space so don't warn for those. my $post_declare_space = ""; if ($declare =~ /(\s+)$/) { $post_declare_space = $1; $declare = rtrim($declare); } if ($declare !~ /\*$/ && $post_declare_space =~ /^$/) { WARN("SPACING", "missing space after return type\n" . $herecurr); $post_declare_space = " "; } # unnecessary space "type (*funcptr)(args...)" # This test is not currently implemented because these declarations are # equivalent to # int foo(int bar, ...) # and this is form shouldn't/doesn't generate a checkpatch warning. # # elsif ($declare =~ /\s{2,}$/) { # WARN("SPACING", # "Multiple spaces after return type\n" . $herecurr); # } # unnecessary space "type ( *funcptr)(args...)" if (defined $pre_pointer_space && $pre_pointer_space =~ /^\s/) { WARN("SPACING", "Unnecessary space after function pointer open parenthesis\n" . $herecurr); } # unnecessary space "type (* funcptr)(args...)" if (defined $post_pointer_space && $post_pointer_space =~ /^\s/) { WARN("SPACING", "Unnecessary space before function pointer name\n" . $herecurr); } # unnecessary space "type (*funcptr )(args...)" if (defined $post_funcname_space && $post_funcname_space =~ /^\s/) { WARN("SPACING", "Unnecessary space after function pointer name\n" . $herecurr); } # unnecessary space "type (*funcptr) (args...)" if (defined $pre_args_space && $pre_args_space =~ /^\s/) { WARN("SPACING", "Unnecessary space before function pointer arguments\n" . $herecurr); } if (show_type("SPACING") && $fix) { $fixed[$fixlinenr] =~ s/^(.\s*)$Declare\s*\(\s*\*\s*$Ident\s*\)\s*\(/$1 . $declare . $post_declare_space . '(*' . $funcname . ')('/ex; } } # check for spacing round square brackets; allowed: # 1. with a type on the left -- int [] a; # 2. at the beginning of a line for slice initialisers -- [0...10] = 5, # 3. inside a curly brace -- = { [0...10] = 5 } while ($line =~ /(.*?\s)\[/g) { my ($where, $prefix) = ($-[1], $1); if ($prefix !~ /$Type\s+$/ && ($where != 0 || $prefix !~ /^.\s+$/) && $prefix !~ /[{,:]\s+$/) { if (ERROR("BRACKET_SPACE", "space prohibited before open square bracket '['\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(\+.*?)\s+\[/$1\[/; } } } # check for spaces between functions and their parentheses. while ($line =~ /($Ident)\s+\(/g) { my $name = $1; my $ctx_before = substr($line, 0, $-[1]); my $ctx = "$ctx_before$name"; # Ignore those directives where spaces _are_ permitted. if ($name =~ /^(?: if|for|while|switch|return|case| volatile|__volatile__| __attribute__|format|__extension__| asm|__asm__)$/x) { # cpp #define statements have non-optional spaces, ie # if there is a space between the name and the open # parenthesis it is simply not a parameter group. } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) { # cpp #elif statement condition may start with a ( } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) { # If this whole things ends with a type its most # likely a typedef for a function. } elsif ($ctx =~ /$Type$/) { } else { if (WARN("SPACING", "space prohibited between function name and open parenthesis '('\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b$name\s+\(/$name\(/; } } } # Check operator spacing. if (!($line=~/\#\s*include/)) { my $fixed_line = ""; my $line_fixed = 0; my $ops = qr{ <<=|>>=|<=|>=|==|!=| \+=|-=|\*=|\/=|%=|\^=|\|=|&=| =>|->|<<|>>|<|>|=|!|~| &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%| \?:|\?|: }x; my @elements = split(/($ops|;)/, $opline); ## print("element count: <" . $#elements . ">\n"); ## foreach my $el (@elements) { ## print("el: <$el>\n"); ## } my @fix_elements = (); my $off = 0; foreach my $el (@elements) { push(@fix_elements, substr($rawline, $off, length($el))); $off += length($el); } $off = 0; my $blank = copy_spacing($opline); my $last_after = -1; for (my $n = 0; $n < $#elements; $n += 2) { my $good = $fix_elements[$n] . $fix_elements[$n + 1]; ## print("n: <$n> good: <$good>\n"); $off += length($elements[$n]); # Pick up the preceding and succeeding characters. my $ca = substr($opline, 0, $off); my $cc = ''; if (length($opline) >= ($off + length($elements[$n + 1]))) { $cc = substr($opline, $off + length($elements[$n + 1])); } my $cb = "$ca$;$cc"; my $a = ''; $a = 'V' if ($elements[$n] ne ''); $a = 'W' if ($elements[$n] =~ /\s$/); $a = 'C' if ($elements[$n] =~ /$;$/); $a = 'B' if ($elements[$n] =~ /(\[|\()$/); $a = 'O' if ($elements[$n] eq ''); $a = 'E' if ($ca =~ /^\s*$/); my $op = $elements[$n + 1]; my $c = ''; if (defined $elements[$n + 2]) { $c = 'V' if ($elements[$n + 2] ne ''); $c = 'W' if ($elements[$n + 2] =~ /^\s/); $c = 'C' if ($elements[$n + 2] =~ /^$;/); $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/); $c = 'O' if ($elements[$n + 2] eq ''); $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/); } else { $c = 'E'; } my $ctx = "${a}x${c}"; my $at = "(ctx:$ctx)"; my $ptr = substr($blank, 0, $off) . "^"; my $hereptr = "$hereline$ptr\n"; # Pull out the value of this operator. my $op_type = substr($curr_values, $off + 1, 1); # Get the full operator variant. my $opv = $op . substr($curr_vars, $off, 1); # Ignore operators passed as parameters. if ($op_type ne 'V' && $ca =~ /\s$/ && $cc =~ /^\s*[,\)]/) { # # Ignore comments # } elsif ($op =~ /^$;+$/) { # ; should have either the end of line or a space or \ after it } elsif ($op eq ';') { if ($ctx !~ /.x[WEBC]/ && $cc !~ /^\\/ && $cc !~ /^;/) { if (ERROR("SPACING", "space required after that '$op' $at\n" . $hereptr)) { $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; $line_fixed = 1; } } # // is a comment } elsif ($op eq '//') { # : when part of a bitfield } elsif ($opv eq ':B') { # skip the bitfield test for now # No spaces for: # -> } elsif ($op eq '->') { if ($ctx =~ /Wx.|.xW/) { if (ERROR("SPACING", "spaces prohibited around that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); if (defined $fix_elements[$n + 2]) { $fix_elements[$n + 2] =~ s/^\s+//; } $line_fixed = 1; } } # , must not have a space before and must have a space on the right. } elsif ($op eq ',') { my $rtrim_before = 0; my $space_after = 0; if ($ctx =~ /Wx./) { if (ERROR("SPACING", "space prohibited before that '$op' $at\n" . $hereptr)) { $line_fixed = 1; $rtrim_before = 1; } } if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) { if (ERROR("SPACING", "space required after that '$op' $at\n" . $hereptr)) { $line_fixed = 1; $last_after = $n; $space_after = 1; } } if ($rtrim_before || $space_after) { if ($rtrim_before) { $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); } else { $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); } if ($space_after) { $good .= " "; } } # '*' as part of a type definition -- reported already. } elsif ($opv eq '*_') { #warn "'*' is part of type\n"; # unary operators should have a space before and # none after. May be left adjacent to another # unary operator, or a cast } elsif ($op eq '!' || $op eq '~' || $opv eq '*U' || $opv eq '-U' || $opv eq '&U' || $opv eq '&&U') { if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) { if (ERROR("SPACING", "space required before that '$op' $at\n" . $hereptr)) { if ($n != $last_after + 2) { $good = $fix_elements[$n] . " " . ltrim($fix_elements[$n + 1]); $line_fixed = 1; } } } if ($op eq '*' && $cc =~/\s*$Modifier\b/) { # A unary '*' may be const } elsif ($ctx =~ /.xW/) { if (ERROR("SPACING", "space prohibited after that '$op' $at\n" . $hereptr)) { $good = $fix_elements[$n] . rtrim($fix_elements[$n + 1]); if (defined $fix_elements[$n + 2]) { $fix_elements[$n + 2] =~ s/^\s+//; } $line_fixed = 1; } } # unary ++ and unary -- are allowed no space on one side. } elsif ($op eq '++' or $op eq '--') { if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) { if (ERROR("SPACING", "space required one side of that '$op' $at\n" . $hereptr)) { $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; $line_fixed = 1; } } if ($ctx =~ /Wx[BE]/ || ($ctx =~ /Wx./ && $cc =~ /^;/)) { if (ERROR("SPACING", "space prohibited before that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); $line_fixed = 1; } } if ($ctx =~ /ExW/) { if (ERROR("SPACING", "space prohibited after that '$op' $at\n" . $hereptr)) { $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); if (defined $fix_elements[$n + 2]) { $fix_elements[$n + 2] =~ s/^\s+//; } $line_fixed = 1; } } # << and >> may either have or not have spaces both sides } elsif ($op eq '<<' or $op eq '>>' or $op eq '&' or $op eq '^' or $op eq '|' or $op eq '+' or $op eq '-' or $op eq '*' or $op eq '/' or $op eq '%') { if ($check) { if (defined $fix_elements[$n + 2] && $ctx !~ /[EW]x[EW]/) { # if (CHK("SPACING", # "spaces preferred around that '$op' $at\n" . $hereptr)) { # $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; # $fix_elements[$n + 2] =~ s/^\s+//; # $line_fixed = 1; # } } elsif (!defined $fix_elements[$n + 2] && $ctx !~ /Wx[OE]/) { if (CHK("SPACING", "space preferred before that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]); $line_fixed = 1; } } } # A colon needs no spaces before when it is # terminating a case value or a label. } elsif ($opv eq ':C' || $opv eq ':L') { if ($ctx =~ /Wx./ and $realfile !~ m@.*\.lds\.h$@) { if (ERROR("SPACING", "space prohibited before that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); $line_fixed = 1; } } # All the others need spaces both sides. } elsif ($ctx !~ /[EWC]x[CWE]/) { my $ok = 0; # Ignore email addresses <foo@bar> if (($op eq '<' && $cc =~ /^\S+\@\S+>/) || ($op eq '>' && $ca =~ /<\S+\@\S+$/)) { $ok = 1; } # for asm volatile statements # ignore a colon with another # colon immediately before or after if (($op eq ':') && ($ca =~ /:$/ || $cc =~ /^:/)) { $ok = 1; } # messages are ERROR, but ?: are CHK if ($ok == 0) { my $msg_level = \&ERROR; $msg_level = \&CHK if (($op eq '?:' || $op eq '?' || $op eq ':') && $ctx =~ /VxV/); if (&{$msg_level}("SPACING", "spaces required around that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; if (defined $fix_elements[$n + 2]) { $fix_elements[$n + 2] =~ s/^\s+//; } $line_fixed = 1; } } } $off += length($elements[$n + 1]); ## print("n: <$n> GOOD: <$good>\n"); $fixed_line = $fixed_line . $good; } if (($#elements % 2) == 0) { $fixed_line = $fixed_line . $fix_elements[$#elements]; } if ($fix && $line_fixed && $fixed_line ne $fixed[$fixlinenr]) { $fixed[$fixlinenr] = $fixed_line; } } # check for whitespace before a non-naked semicolon if ($line =~ /^\+.*\S\s+;\s*$/) { if (WARN("SPACING", "space prohibited before semicolon\n" . $herecurr) && $fix) { 1 while $fixed[$fixlinenr] =~ s/^(\+.*\S)\s+;/$1;/; } } # check for multiple assignments if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) { CHK("MULTIPLE_ASSIGNMENTS", "multiple assignments should be avoided\n" . $herecurr); } ## # check for multiple declarations, allowing for a function declaration ## # continuation. ## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ && ## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { ## ## # Remove any bracketed sections to ensure we do not ## # falsely report the parameters of functions. ## my $ln = $line; ## while ($ln =~ s/\([^\(\)]*\)//g) { ## } ## if ($ln =~ /,/) { ## WARN("MULTIPLE_DECLARATION", ## "declaring multiple variables together should be avoided\n" . $herecurr); ## } ## } #need space before brace following if, while, etc if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\)\{/) || $line =~ /\b(?:else|do)\{/) { if (ERROR("SPACING", "space required before the open brace '{'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(\+.*(?:do|else|\)))\{/$1 {/; } } ## # check for blank lines before declarations ## if ($line =~ /^.\t+$Type\s+$Ident(?:\s*=.*)?;/ && ## $prevrawline =~ /^.\s*$/) { ## WARN("SPACING", ## "No blank lines before declarations\n" . $hereprev); ## } ## # closing brace should have a space following it when it has anything # on the line if ($line =~ /}(?!(?:,|;|\)|\}))\S/) { if (ERROR("SPACING", "space required after that close brace '}'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/}((?!(?:,|;|\)))\S)/} $1/; } } # check spacing on square brackets if ($line =~ /\[\s/ && $line !~ /\[\s*$/) { if (ERROR("SPACING", "space prohibited after that open square bracket '['\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\[\s+/\[/; } } if ($line =~ /\s\]/) { if (ERROR("SPACING", "space prohibited before that close square bracket ']'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\s+\]/\]/; } } # check spacing on parentheses if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && $line !~ /for\s*\(\s+;/) { if (ERROR("SPACING", "space prohibited after that open parenthesis '('\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\(\s+/\(/; } } if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && $line !~ /for\s*\(.*;\s+\)/ && $line !~ /:\s+\)/) { if (ERROR("SPACING", "space prohibited before that close parenthesis ')'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\s+\)/\)/; } } # check unnecessary parentheses around addressof/dereference single $Lvals # ie: &(foo->bar) should be &foo->bar and *(foo->bar) should be *foo->bar while ($line =~ /(?:[^&]&\s*|\*)\(\s*($Ident\s*(?:$Member\s*)+)\s*\)/g) { my $var = $1; if (CHK("UNNECESSARY_PARENTHESES", "Unnecessary parentheses around $var\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\(\s*\Q$var\E\s*\)/$var/; } } # check for unnecessary parentheses around function pointer uses # ie: (foo->bar)(); should be foo->bar(); # but not "if (foo->bar) (" to avoid some false positives if ($line =~ /(\bif\s*|)(\(\s*$Ident\s*(?:$Member\s*)+\))[ \t]*\(/ && $1 !~ /^if/) { my $var = $2; if (CHK("UNNECESSARY_PARENTHESES", "Unnecessary parentheses around function pointer $var\n" . $herecurr) && $fix) { my $var2 = deparenthesize($var); $var2 =~ s/\s//g; $fixed[$fixlinenr] =~ s/\Q$var\E/$var2/; } } # check for unnecessary parentheses around comparisons in if uses # when !drivers/staging or command-line uses --strict if (($realfile !~ m@^(?:drivers/staging/)@ || $check_orig) && $perl_version_ok && defined($stat) && $stat =~ /(^.\s*if\s*($balanced_parens))/) { my $if_stat = $1; my $test = substr($2, 1, -1); my $herectx; while ($test =~ /(?:^|[^\w\&\!\~])+\s*\(\s*([\&\!\~]?\s*$Lval\s*(?:$Compare\s*$FuncArg)?)\s*\)/g) { my $match = $1; # avoid parentheses around potential macro args next if ($match =~ /^\s*\w+\s*$/); if (!defined($herectx)) { $herectx = $here . "\n"; my $cnt = statement_rawlines($if_stat); for (my $n = 0; $n < $cnt; $n++) { my $rl = raw_line($linenr, $n); $herectx .= $rl . "\n"; last if $rl =~ /^[ \+].*\{/; } } CHK("UNNECESSARY_PARENTHESES", "Unnecessary parentheses around '$match'\n" . $herectx); } } # check that goto labels aren't indented (allow a single space indentation) # and ignore bitfield definitions like foo:1 # Strictly, labels can have whitespace after the identifier and before the : # but this is not allowed here as many ?: uses would appear to be labels if ($sline =~ /^.\s+[A-Za-z_][A-Za-z\d_]*:(?!\s*\d+)/ && $sline !~ /^. [A-Za-z\d_][A-Za-z\d_]*:/ && $sline !~ /^.\s+default:/) { if (WARN("INDENTED_LABEL", "labels should not be indented\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(.)\s+/$1/; } } # check if a statement with a comma should be two statements like: # foo = bar(), /* comma should be semicolon */ # bar = baz(); if (defined($stat) && $stat =~ /^\+\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*,\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*;\s*$/) { my $cnt = statement_rawlines($stat); my $herectx = get_stat_here($linenr, $cnt, $here); WARN("SUSPECT_COMMA_SEMICOLON", "Possible comma where semicolon could be used\n" . $herectx); } # return is not a function if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) { my $spacing = $1; if ($perl_version_ok && $stat =~ /^.\s*return\s*($balanced_parens)\s*;\s*$/) { my $value = $1; $value = deparenthesize($value); if ($value =~ m/^\s*$FuncArg\s*(?:\?|$)/) { ERROR("RETURN_PARENTHESES", "return is not a function, parentheses are not required\n" . $herecurr); } } elsif ($spacing !~ /\s+/) { ERROR("SPACING", "space required before the open parenthesis '('\n" . $herecurr); } } # unnecessary return in a void function # at end-of-function, with the previous line a single leading tab, then return; # and the line before that not a goto label target like "out:" if ($sline =~ /^[ \+]}\s*$/ && $prevline =~ /^\+\treturn\s*;\s*$/ && $linenr >= 3 && $lines[$linenr - 3] =~ /^[ +]/ && $lines[$linenr - 3] !~ /^[ +]\s*$Ident\s*:/) { WARN("RETURN_VOID", "void function return statements are not generally useful\n" . $hereprev); } # if statements using unnecessary parentheses - ie: if ((foo == bar)) if ($perl_version_ok && $line =~ /\bif\s*((?:\(\s*){2,})/) { my $openparens = $1; my $count = $openparens =~ tr@\(@\(@; my $msg = ""; if ($line =~ /\bif\s*(?:\(\s*){$count,$count}$LvalOrFunc\s*($Compare)\s*$LvalOrFunc(?:\s*\)){$count,$count}/) { my $comp = $4; #Not $1 because of $LvalOrFunc $msg = " - maybe == should be = ?" if ($comp eq "=="); WARN("UNNECESSARY_PARENTHESES", "Unnecessary parentheses$msg\n" . $herecurr); } } # comparisons with a constant or upper case identifier on the left # avoid cases like "foo + BAR < baz" # only fix matches surrounded by parentheses to avoid incorrect # conversions like "FOO < baz() + 5" being "misfixed" to "baz() > FOO + 5" if ($perl_version_ok && $line =~ /^\+(.*)\b($Constant|[A-Z_][A-Z0-9_]*)\s*($Compare)\s*($LvalOrFunc)/) { my $lead = $1; my $const = $2; my $comp = $3; my $to = $4; my $newcomp = $comp; if ($lead !~ /(?:$Operators|\.)\s*$/ && $to !~ /^(?:Constant|[A-Z_][A-Z0-9_]*)$/ && WARN("CONSTANT_COMPARISON", "Comparisons should place the constant on the right side of the test\n" . $herecurr) && $fix) { if ($comp eq "<") { $newcomp = ">"; } elsif ($comp eq "<=") { $newcomp = ">="; } elsif ($comp eq ">") { $newcomp = "<"; } elsif ($comp eq ">=") { $newcomp = "<="; } $fixed[$fixlinenr] =~ s/\(\s*\Q$const\E\s*$Compare\s*\Q$to\E\s*\)/($to $newcomp $const)/; } } # Return of what appears to be an errno should normally be negative if ($sline =~ /\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\s*|\s*)[;:,]/) { my $name = $1; if ($name ne 'EOF' && $name ne 'ERROR' && $name !~ /^EPOLL/) { WARN("USE_NEGATIVE_ERRNO", "return of an errno should typically be negative (ie: return -$1)\n" . $herecurr); } } # Need a space before open parenthesis after if, while etc if ($line =~ /\b(if|while|for|switch)\(/) { if (ERROR("SPACING", "space required before the open parenthesis '('\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b(if|while|for|switch)\(/$1 \(/; } } # Check for illegal assignment in if conditional -- and check for trailing # statements after the conditional. if ($line =~ /do\s*(?!{)/) { ($stat, $cond, $line_nr_next, $remain_next, $off_next) = ctx_statement_block($linenr, $realcnt, 0) if (!defined $stat); my ($stat_next) = ctx_statement_block($line_nr_next, $remain_next, $off_next); $stat_next =~ s/\n./\n /g; ##print "stat<$stat> stat_next<$stat_next>\n"; if ($stat_next =~ /^\s*while\b/) { # If the statement carries leading newlines, # then count those as offsets. my ($whitespace) = ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s); my $offset = statement_rawlines($whitespace) - 1; $suppress_whiletrailers{$line_nr_next + $offset} = 1; } } if (!defined $suppress_whiletrailers{$linenr} && defined($stat) && defined($cond) && $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { my ($s, $c) = ($stat, $cond); if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { if (ERROR("ASSIGN_IN_IF", "do not use assignment in if condition\n" . $herecurr) && $fix && $perl_version_ok) { if ($rawline =~ /^\+(\s+)if\s*\(\s*(\!)?\s*\(\s*(($Lval)\s*=\s*$LvalOrFunc)\s*\)\s*(?:($Compare)\s*($FuncArg))?\s*\)\s*(\{)?\s*$/) { my $space = $1; my $not = $2; my $statement = $3; my $assigned = $4; my $test = $8; my $against = $9; my $brace = $15; fix_delete_line($fixlinenr, $rawline); fix_insert_line($fixlinenr, "$space$statement;"); my $newline = "${space}if ("; $newline .= '!' if defined($not); $newline .= '(' if (defined $not && defined($test) && defined($against)); $newline .= "$assigned"; $newline .= " $test $against" if (defined($test) && defined($against)); $newline .= ')' if (defined $not && defined($test) && defined($against)); $newline .= ')'; $newline .= " {" if (defined($brace)); fix_insert_line($fixlinenr + 1, $newline); } } } # We need this for the labwc-custom check below. It # avoids false positives with do { } while (); etc. my $starts_with_if_while_etc = 0; if ($s =~ /^\+\s*(if|while|for|switch).*/) { $starts_with_if_while_etc = 1; } # Find out what is on the end of the line after the # conditional. substr($s, 0, length($c), ''); $s =~ s/\n.*//g; $s =~ s/$;//g; # Remove any comments # Find if/while/for/switch without opening braces # because (as opposed to Linux coding style) we use # braces for single statement blocks. # # We ignore a couple of header-files which contain # macros that we cannot deal with. # if ($starts_with_if_while_etc && !length($s) && $filename ne "include/view.h" && $filename ne "include/common/array.h" && $filename ne "include/ssd-internal.h") { CHK("BRACES", "[labwc-custom] open brace { expected after if/while/for/switch - even with single statement blocks"); } if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && $c !~ /}\s*while\s*/) { # Find out how long the conditional actually is. my @newlines = ($c =~ /\n/gs); my $cond_lines = 1 + $#newlines; my $stat_real = ''; $stat_real = raw_line($linenr, $cond_lines) . "\n" if ($cond_lines); if (defined($stat_real) && $cond_lines > 1) { $stat_real = "[...]\n$stat_real"; } ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr . $stat_real); } } # Check for bitwise tests written as boolean if ($line =~ / (?: (?:\[|\(|\&\&|\|\|) \s*0[xX][0-9]+\s* (?:\&\&|\|\|) | (?:\&\&|\|\|) \s*0[xX][0-9]+\s* (?:\&\&|\|\||\)|\]) )/x) { WARN("HEXADECIMAL_BOOLEAN_TEST", "boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr); } # if and else should not have general statements after it if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { my $s = $1; $s =~ s/$;//g; # Remove any comments if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr); } } # if should not continue a brace if ($line =~ /}\s*if\b/) { ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line (or did you mean 'else if'?)\n" . $herecurr); } # case and default should not have general statements after them if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g && $line !~ /\G(?: (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$| \s*return\s+ )/xg) { ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr); } # Check for }<nl>else {, these must be at the same # indent level to be relevant to each other. if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ && $previndent == $indent) { if (ERROR("ELSE_AFTER_BRACE", "else should follow close brace '}'\n" . $hereprev) && $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { fix_delete_line($fixlinenr - 1, $prevrawline); fix_delete_line($fixlinenr, $rawline); my $fixedline = $prevrawline; $fixedline =~ s/}\s*$//; if ($fixedline !~ /^\+\s*$/) { fix_insert_line($fixlinenr, $fixedline); } $fixedline = $rawline; $fixedline =~ s/^(.\s*)else/$1} else/; fix_insert_line($fixlinenr, $fixedline); } } if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ && $previndent == $indent) { my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0); # Find out what is on the end of the line after the # conditional. substr($s, 0, length($c), ''); $s =~ s/\n.*//g; if ($s =~ /^\s*;/) { if (ERROR("WHILE_AFTER_BRACE", "while should follow close brace '}'\n" . $hereprev) && $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { fix_delete_line($fixlinenr - 1, $prevrawline); fix_delete_line($fixlinenr, $rawline); my $fixedline = $prevrawline; my $trailing = $rawline; $trailing =~ s/^\+//; $trailing = trim($trailing); $fixedline =~ s/}\s*$/} $trailing/; fix_insert_line($fixlinenr, $fixedline); } } } #Specific variable tests while ($line =~ m{($Constant|$Lval)}g) { my $var = $1; #CamelCase if ($var !~ /^$Constant$/ && $var =~ /[A-Z][a-z]|[a-z][A-Z]/ && #Ignore some autogenerated defines and enum values $var !~ /^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]/ && #Ignore Page<foo> variants $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && #labwc-custom check to ignore some pango/libxml2/etc CamelCase variants $var !~ /^(?:PangoLayout|PangoFontDescription)/ && $var !~ /^(?:PangoTabArray|PangoRectangle)/ && $var !~ /^(?:PangoWeight|_PangoFontDescription)/ && $var !~ /^(?:xmlNode|xmlIsBlankNode|xmlAttr)/ && $var !~ /^(?:xmlGetProp|xmlChar|xmlDoc)/ && $var !~ /^(?:xmlReadFile|xmlDocGetRootElement)/ && $var !~ /^(?:xmlFreeDoc|xmlCleanupParser)/ && $var !~ /^(?:xmlParseMemory)/ && $var !~ /^(?:xmlFree)/ && $var !~ /^(?:GString|GError)/ && $var !~ /^(?:RsvgRectangle|RsvgHandle)/ && $var !~ /^(?:XKB_KEY_XF86Switch_VT_1)/ && #Ignore SI style variants like nS, mV and dB #(ie: max_uV, regulator_min_uA_show, RANGE_mA_VALUE) $var !~ /^(?:[a-z0-9_]*|[A-Z0-9_]*)?_?[a-z][A-Z](?:_[a-z0-9_]+|_[A-Z0-9_]+)?$/ && #Ignore some three character SI units explicitly, like MiB and KHz $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { while ($var =~ m{($Ident)}g) { my $word = $1; next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/); if ($check) { seed_camelcase_includes(); if (!$file && !$camelcase_file_seeded) { seed_camelcase_file($realfile); $camelcase_file_seeded = 1; } } if (!defined $camelcase{$word}) { $camelcase{$word} = 1; CHK("CAMELCASE", "Avoid CamelCase: <$word>\n" . $herecurr); } } } } #no spaces allowed after \ in define if ($line =~ /\#\s*define.*\\\s+$/) { if (WARN("WHITESPACE_AFTER_LINE_CONTINUATION", "Whitespace after \\ makes next lines useless\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\s+$//; } } # warn if <asm/foo.h> is #included and <linux/foo.h> is available and includes # itself <asm/foo.h> (uses RAW line) if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\<asm\/(.*)\.h\>}) { my $file = "$1.h"; my $checkfile = "include/linux/$file"; if (-f "$root/$checkfile" && $realfile ne $checkfile && $1 !~ /$allowed_asm_includes/) { my $asminclude = `grep -Ec "#include\\s+<asm/$file>" $root/$checkfile`; if ($asminclude > 0) { if ($realfile =~ m{^arch/}) { CHK("ARCH_INCLUDE_LINUX", "Consider using #include <linux/$file> instead of <asm/$file>\n" . $herecurr); } else { WARN("INCLUDE_LINUX", "Use #include <linux/$file> instead of <asm/$file>\n" . $herecurr); } } } } # multi-statement macros should be enclosed in a do while loop, grab the # first statement and ensure its the whole macro if its not enclosed # in a known good container if ($realfile !~ m@/vmlinux.lds.h$@ && $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { my $ln = $linenr; my $cnt = $realcnt; my ($off, $dstat, $dcond, $rest); my $ctx = ''; my $has_flow_statement = 0; my $has_arg_concat = 0; ($dstat, $dcond, $ln, $cnt, $off) = ctx_statement_block($linenr, $realcnt, 0); $ctx = $dstat; #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n"; #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n"; $has_flow_statement = 1 if ($ctx =~ /\b(goto|return)\b/); $has_arg_concat = 1 if ($ctx =~ /\#\#/ && $ctx !~ /\#\#\s*(?:__VA_ARGS__|args)\b/); $dstat =~ s/^.\s*\#\s*define\s+$Ident(\([^\)]*\))?\s*//; my $define_args = $1; my $define_stmt = $dstat; my @def_args = (); if (defined $define_args && $define_args ne "") { $define_args = substr($define_args, 1, length($define_args) - 2); $define_args =~ s/\s*//g; $define_args =~ s/\\\+?//g; @def_args = split(",", $define_args); } $dstat =~ s/$;//g; $dstat =~ s/\\\n.//g; $dstat =~ s/^\s*//s; $dstat =~ s/\s*$//s; # Flatten any parentheses and braces while ($dstat =~ s/\([^\(\)]*\)/1u/ || $dstat =~ s/\{[^\{\}]*\}/1u/ || $dstat =~ s/.\[[^\[\]]*\]/1u/) { } # Flatten any obvious string concatenation. while ($dstat =~ s/($String)\s*$Ident/$1/ || $dstat =~ s/$Ident\s*($String)/$1/) { } # Make asm volatile uses seem like a generic function $dstat =~ s/\b_*asm_*\s+_*volatile_*\b/asm_volatile/g; my $exceptions = qr{ $Declare| module_param_named| MODULE_PARM_DESC| DECLARE_PER_CPU| DEFINE_PER_CPU| __typeof__\(| union| struct| \.$Ident\s*=\s*| ^\"|\"$| ^\[ }x; #print "REST<$rest> dstat<$dstat> ctx<$ctx>\n"; $ctx =~ s/\n*$//; my $stmt_cnt = statement_rawlines($ctx); my $herectx = get_stat_here($linenr, $stmt_cnt, $here); if ($dstat ne '' && $dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(), $dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo(); $dstat !~ /^[!~-]?(?:$Lval|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo // foo->bar // foo.bar->baz $dstat !~ /^'X'$/ && $dstat !~ /^'XX'$/ && # character constants $dstat !~ /$exceptions/ && $dstat !~ /^\.$Ident\s*=/ && # .foo = $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) $dstat !~ /^while\s*$Constant\s*$Constant\s*$/ && # while (...) {...} $dstat !~ /^for\s*$Constant$/ && # for (...) $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar() $dstat !~ /^do\s*{/ && # do {... $dstat !~ /^\(\{/ && # ({... $ctx !~ /^.\s*#\s*define\s+TRACE_(?:SYSTEM|INCLUDE_FILE|INCLUDE_PATH)\b/) { if ($dstat =~ /^\s*if\b/) { ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", "Macros starting with if should be enclosed by a do - while loop to avoid possible if/else logic defects\n" . "$herectx"); } elsif ($dstat =~ /;/) { ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", "Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx"); } else { ERROR("COMPLEX_MACRO", "Macros with complex values should be enclosed in parentheses\n" . "$herectx"); } } # Make $define_stmt single line, comment-free, etc my @stmt_array = split('\n', $define_stmt); my $first = 1; $define_stmt = ""; foreach my $l (@stmt_array) { $l =~ s/\\$//; if ($first) { $define_stmt = $l; $first = 0; } elsif ($l =~ /^[\+ ]/) { $define_stmt .= substr($l, 1); } } $define_stmt =~ s/$;//g; $define_stmt =~ s/\s+/ /g; $define_stmt = trim($define_stmt); # check if any macro arguments are reused (ignore '...' and 'type') foreach my $arg (@def_args) { next if ($arg =~ /\.\.\./); next if ($arg =~ /^type$/i); my $tmp_stmt = $define_stmt; $tmp_stmt =~ s/\b(__must_be_array|offsetof|sizeof|sizeof_field|__stringify|typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g; $tmp_stmt =~ s/\#+\s*$arg\b//g; $tmp_stmt =~ s/\b$arg\s*\#\#//g; my $use_cnt = () = $tmp_stmt =~ /\b$arg\b/g; if ($use_cnt > 1) { CHK("MACRO_ARG_REUSE", "Macro argument reuse '$arg' - possible side-effects?\n" . "$herectx"); } # check if any macro arguments may have other precedence issues if ($tmp_stmt =~ m/($Operators)?\s*\b$arg\b\s*($Operators)?/m && ((defined($1) && $1 ne ',') || (defined($2) && $2 ne ','))) { CHK("MACRO_ARG_PRECEDENCE", "Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx"); } } # check for macros with flow control, but without ## concatenation # ## concatenation is commonly a macro that defines a function so ignore those if ($has_flow_statement && !$has_arg_concat) { my $cnt = statement_rawlines($ctx); my $herectx = get_stat_here($linenr, $cnt, $here); WARN("MACRO_WITH_FLOW_CONTROL", "Macros with flow control statements should be avoided\n" . "$herectx"); } # check for line continuations outside of #defines, preprocessor #, and asm } else { if ($prevline !~ /^..*\\$/ && $line !~ /^\+\s*\#.*\\$/ && # preprocessor $line !~ /^\+.*\b(__asm__|asm)\b.*\\$/ && # asm $line =~ /^\+.*\\$/) { WARN("LINE_CONTINUATIONS", "Avoid unnecessary line continuations\n" . $herecurr); } } # do {} while (0) macro tests: # single-statement macros do not need to be enclosed in do while (0) loop, # macro should not end with a semicolon if ($perl_version_ok && $realfile !~ m@/vmlinux.lds.h$@ && $line =~ /^.\s*\#\s*define\s+$Ident(\()?/) { my $ln = $linenr; my $cnt = $realcnt; my ($off, $dstat, $dcond, $rest); my $ctx = ''; ($dstat, $dcond, $ln, $cnt, $off) = ctx_statement_block($linenr, $realcnt, 0); $ctx = $dstat; $dstat =~ s/\\\n.//g; $dstat =~ s/$;/ /g; if ($dstat =~ /^\+\s*#\s*define\s+$Ident\s*${balanced_parens}\s*do\s*{(.*)\s*}\s*while\s*\(\s*0\s*\)\s*([;\s]*)\s*$/) { my $stmts = $2; my $semis = $3; $ctx =~ s/\n*$//; my $cnt = statement_rawlines($ctx); my $herectx = get_stat_here($linenr, $cnt, $here); if (($stmts =~ tr/;/;/) == 1 && $stmts !~ /^\s*(if|while|for|switch)\b/) { WARN("SINGLE_STATEMENT_DO_WHILE_MACRO", "Single statement macros should not use a do {} while (0) loop\n" . "$herectx"); } if (defined $semis && $semis ne "") { WARN("DO_WHILE_MACRO_WITH_TRAILING_SEMICOLON", "do {} while (0) macros should not be semicolon terminated\n" . "$herectx"); } } elsif ($dstat =~ /^\+\s*#\s*define\s+$Ident.*;\s*$/) { $ctx =~ s/\n*$//; my $cnt = statement_rawlines($ctx); my $herectx = get_stat_here($linenr, $cnt, $here); WARN("TRAILING_SEMICOLON", "macros should not use a trailing semicolon\n" . "$herectx"); } } # check for redundant bracing round if etc if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { my ($level, $endln, @chunks) = ctx_statement_full($linenr, $realcnt, 1); #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n"; #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n"; if ($#chunks > 0 && $level == 0) { my @allowed = (); my $allow = 0; my $seen = 0; my $herectx = $here . "\n"; my $ln = $linenr - 1; for my $chunk (@chunks) { my ($cond, $block) = @{$chunk}; # If the condition carries leading newlines, then count those as offsets. my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s); my $offset = statement_rawlines($whitespace) - 1; $allowed[$allow] = 0; #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n"; # We have looked at and allowed this specific line. $suppress_ifbraces{$ln + $offset} = 1; $herectx .= "$rawlines[$ln + $offset]\n[...]\n"; $ln += statement_rawlines($block) - 1; substr($block, 0, length($cond), ''); $seen++ if ($block =~ /^\s*{/); #print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n"; if (statement_lines($cond) > 1) { #print "APW: ALLOWED: cond<$cond>\n"; $allowed[$allow] = 1; } if ($block =~/\b(?:if|for|while)\b/) { #print "APW: ALLOWED: block<$block>\n"; $allowed[$allow] = 1; } if (statement_block_size($block) > 1) { #print "APW: ALLOWED: lines block<$block>\n"; $allowed[$allow] = 1; } $allow++; } if ($seen) { my $sum_allowed = 0; foreach (@allowed) { $sum_allowed += $_; } if ($sum_allowed == 0) { # do nothing } elsif ($sum_allowed != $allow && $seen != $allow) { CHK("BRACES", "braces {} should be used on all arms of this statement\n" . $herectx); } } } } if (!defined $suppress_ifbraces{$linenr - 1} && $line =~ /\b(if|while|for|else)\b/) { my $allowed = 0; # Check the pre-context. if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) { #print "APW: ALLOWED: pre<$1>\n"; $allowed = 1; } my ($level, $endln, @chunks) = ctx_statement_full($linenr, $realcnt, $-[0]); # Check the condition. my ($cond, $block) = @{$chunks[0]}; #print "CHECKING<$linenr> cond<$cond> block<$block>\n"; if (defined $cond) { substr($block, 0, length($cond), ''); } if (statement_lines($cond) > 1) { #print "APW: ALLOWED: cond<$cond>\n"; $allowed = 1; } if ($block =~/\b(?:if|for|while)\b/) { #print "APW: ALLOWED: block<$block>\n"; $allowed = 1; } if (statement_block_size($block) > 1) { #print "APW: ALLOWED: lines block<$block>\n"; $allowed = 1; } # Check the post-context. if (defined $chunks[1]) { my ($cond, $block) = @{$chunks[1]}; if (defined $cond) { substr($block, 0, length($cond), ''); } if ($block =~ /^\s*\{/) { #print "APW: ALLOWED: chunk-1 block<$block>\n"; $allowed = 1; } } } # check for single line unbalanced braces if ($sline =~ /^.\s*\}\s*else\s*$/ || $sline =~ /^.\s*else\s*\{\s*$/) { CHK("BRACES", "Unbalanced braces around else statement\n" . $herecurr); } # check for unnecessary blank lines around braces if (($line =~ /^.\s*}\s*$/ && $prevrawline =~ /^.\s*$/)) { if (CHK("BRACES", "Blank lines aren't necessary before a close brace '}'\n" . $hereprev) && $fix && $prevrawline =~ /^\+/) { fix_delete_line($fixlinenr - 1, $prevrawline); } } if (($rawline =~ /^.\s*$/ && $prevline =~ /^..*{\s*$/)) { if (CHK("BRACES", "Blank lines aren't necessary after an open brace '{'\n" . $hereprev) && $fix) { fix_delete_line($fixlinenr, $rawline); } } # no volatiles please my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b}; if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) { WARN("VOLATILE", "Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst\n" . $herecurr); } # Check for user-visible strings broken across lines, which breaks the ability # to grep for the string. Make exceptions when the previous string ends in a # newline (multiple lines in one string constant) or '\t', '\r', ';', or '{' # (common in inline assembly) or is a octal \123 or hexadecimal \xaf value if ($line =~ /^\+\s*$String/ && $prevline =~ /"\s*$/ && $prevrawline !~ /(?:\\(?:[ntr]|[0-7]{1,3}|x[0-9a-fA-F]{1,2})|;\s*|\{\s*)"\s*$/) { if (WARN("SPLIT_STRING", "quoted string split across lines\n" . $hereprev) && $fix && $prevrawline =~ /^\+.*"\s*$/ && $last_coalesced_string_linenr != $linenr - 1) { my $extracted_string = get_quoted_string($line, $rawline); my $comma_close = ""; if ($rawline =~ /\Q$extracted_string\E(\s*\)\s*;\s*$|\s*,\s*)/) { $comma_close = $1; } fix_delete_line($fixlinenr - 1, $prevrawline); fix_delete_line($fixlinenr, $rawline); my $fixedline = $prevrawline; $fixedline =~ s/"\s*$//; $fixedline .= substr($extracted_string, 1) . trim($comma_close); fix_insert_line($fixlinenr - 1, $fixedline); $fixedline = $rawline; $fixedline =~ s/\Q$extracted_string\E\Q$comma_close\E//; if ($fixedline !~ /\+\s*$/) { fix_insert_line($fixlinenr, $fixedline); } $last_coalesced_string_linenr = $linenr; } } # check for missing a space in a string concatenation if ($prevrawline =~ /[^\\]\w"$/ && $rawline =~ /^\+[\t ]+"\w/) { WARN('MISSING_SPACE', "break quoted strings at a space character\n" . $hereprev); } # check for an embedded function name in a string when the function is known # This does not work very well for -f --file checking as it depends on patch # context providing the function name or a single line form for in-file # function declarations if ($line =~ /^\+.*$String/ && defined($context_function) && get_quoted_string($line, $rawline) =~ /\b$context_function\b/ && length(get_quoted_string($line, $rawline)) != (length($context_function) + 2)) { WARN("EMBEDDED_FUNCTION_NAME", "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr); } # check for unnecessary function tracing like uses # This does not use $logFunctions because there are many instances like # 'dprintk(FOO, "%s()\n", __func__);' which do not match $logFunctions if ($rawline =~ /^\+.*\([^"]*"$tracing_logging_tags{0,3}%s(?:\s*\(\s*\)\s*)?$tracing_logging_tags{0,3}(?:\\n)?"\s*,\s*__func__\s*\)\s*;/) { if (WARN("TRACING_LOGGING", "Unnecessary ftrace-like logging - prefer using ftrace\n" . $herecurr) && $fix) { fix_delete_line($fixlinenr, $rawline); } } # check for spaces before a quoted newline if ($rawline =~ /^.*\".*\s\\n/) { if (WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE", "unnecessary whitespace before a quoted newline\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(\+.*\".*)\s+\\n/$1\\n/; } } # concatenated string without spaces between elements if ($line =~ /$String[A-Z_]/ || ($line =~ /([A-Za-z0-9_]+)$String/ && $1 !~ /^[Lu]$/)) { if (CHK("CONCATENATED_STRING", "Concatenated strings should use spaces between elements\n" . $herecurr) && $fix) { while ($line =~ /($String)/g) { my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); $fixed[$fixlinenr] =~ s/\Q$extracted_string\E([A-Za-z0-9_])/$extracted_string $1/; $fixed[$fixlinenr] =~ s/([A-Za-z0-9_])\Q$extracted_string\E/$1 $extracted_string/; } } } # uncoalesced string fragments if ($line =~ /$String\s*[Lu]?"/) { if (WARN("STRING_FRAGMENTS", "Consecutive strings are generally better as a single string\n" . $herecurr) && $fix) { while ($line =~ /($String)(?=\s*")/g) { my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); $fixed[$fixlinenr] =~ s/\Q$extracted_string\E\s*"/substr($extracted_string, 0, -1)/e; } } } # check for non-standard and hex prefixed decimal printf formats my $show_L = 1; #don't show the same defect twice my $show_Z = 1; while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { my $string = substr($rawline, $-[1], $+[1] - $-[1]); $string =~ s/%%/__/g; # check for %L if ($show_L && $string =~ /%[\*\d\.\$]*L([diouxX])/) { WARN("PRINTF_L", "\%L$1 is non-standard C, use %ll$1\n" . $herecurr); $show_L = 0; } # check for %Z if ($show_Z && $string =~ /%[\*\d\.\$]*Z([diouxX])/) { WARN("PRINTF_Z", "%Z$1 is non-standard C, use %z$1\n" . $herecurr); $show_Z = 0; } # check for 0x<decimal> if ($string =~ /0x%[\*\d\.\$\Llzth]*[diou]/) { ERROR("PRINTF_0XDECIMAL", "Prefixing 0x with decimal output is defective\n" . $herecurr); } } # check for line continuations in quoted strings with odd counts of " if ($rawline =~ /\\$/ && $sline =~ tr/"/"/ % 2) { WARN("LINE_CONTINUATIONS", "Avoid line continuations in quoted strings\n" . $herecurr); } # warn about #if 0 if ($line =~ /^.\s*\#\s*if\s+0\b/) { WARN("IF_0", "Consider removing the code enclosed by this #if 0 and its #endif\n" . $herecurr); } # warn about #if 1 if ($line =~ /^.\s*\#\s*if\s+1\b/) { WARN("IF_1", "Consider removing the #if 1 and its #endif\n" . $herecurr); } # check for needless "if (<foo>) fn(<foo>)" uses if ($prevline =~ /\bif\s*\(\s*($Lval)\s*\)/) { my $tested = quotemeta($1); my $expr = '\s*\(\s*' . $tested . '\s*\)\s*;'; if ($line =~ /\b(kfree|usb_free_urb|debugfs_remove(?:_recursive)?|(?:kmem_cache|mempool|dma_pool)_destroy)$expr/) { my $func = $1; if (WARN('NEEDLESS_IF', "$func(NULL) is safe and this check is probably not required\n" . $hereprev) && $fix) { my $do_fix = 1; my $leading_tabs = ""; my $new_leading_tabs = ""; if ($lines[$linenr - 2] =~ /^\+(\t*)if\s*\(\s*$tested\s*\)\s*$/) { $leading_tabs = $1; } else { $do_fix = 0; } if ($lines[$linenr - 1] =~ /^\+(\t+)$func\s*\(\s*$tested\s*\)\s*;\s*$/) { $new_leading_tabs = $1; if (length($leading_tabs) + 1 ne length($new_leading_tabs)) { $do_fix = 0; } } else { $do_fix = 0; } if ($do_fix) { fix_delete_line($fixlinenr - 1, $prevrawline); $fixed[$fixlinenr] =~ s/^\+$new_leading_tabs/\+$leading_tabs/; } } } } # check for unnecessary "Out of Memory" messages if ($line =~ /^\+.*\b$logFunctions\s*\(/ && $prevline =~ /^[ \+]\s*if\s*\(\s*(\!\s*|NULL\s*==\s*)?($Lval)(\s*==\s*NULL\s*)?\s*\)/ && (defined $1 || defined $3) && $linenr > 3) { my $testval = $2; my $testline = $lines[$linenr - 3]; my ($s, $c) = ctx_statement_block($linenr - 3, $realcnt, 0); # print("line: <$line>\nprevline: <$prevline>\ns: <$s>\nc: <$c>\n\n\n"); if ($s =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*$allocFunctions\s*\(/ && $s !~ /\b__GFP_NOWARN\b/ ) { WARN("OOM_MESSAGE", "Possible unnecessary 'out of memory' message\n" . $hereprev); } } # check for logging functions with KERN_<LEVEL> if ($line !~ /printk(?:_ratelimited|_once)?\s*\(/ && $line =~ /\b$logFunctions\s*\(.*\b(KERN_[A-Z]+)\b/) { my $level = $1; if (WARN("UNNECESSARY_KERN_LEVEL", "Possible unnecessary $level\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\s*$level\s*//; } } # check for logging continuations if ($line =~ /\bprintk\s*\(\s*KERN_CONT\b|\bpr_cont\s*\(/) { WARN("LOGGING_CONTINUATION", "Avoid logging continuation uses where feasible\n" . $herecurr); } # check for unnecessary use of %h[xudi] and %hh[xudi] in logging functions if (defined $stat && $line =~ /\b$logFunctions\s*\(/ && index($stat, '"') >= 0) { my $lc = $stat =~ tr@\n@@; $lc = $lc + $linenr; my $stat_real = get_stat_real($linenr, $lc); pos($stat_real) = index($stat_real, '"'); while ($stat_real =~ /[^\"%]*(%[\#\d\.\*\-]*(h+)[idux])/g) { my $pspec = $1; my $h = $2; my $lineoff = substr($stat_real, 0, $-[1]) =~ tr@\n@@; if (WARN("UNNECESSARY_MODIFIER", "Integer promotion: Using '$h' in '$pspec' is unnecessary\n" . "$here\n$stat_real\n") && $fix && $fixed[$fixlinenr + $lineoff] =~ /^\+/) { my $nspec = $pspec; $nspec =~ s/h//g; $fixed[$fixlinenr + $lineoff] =~ s/\Q$pspec\E/$nspec/; } } } # check for mask then right shift without a parentheses if ($perl_version_ok && $line =~ /$LvalOrFunc\s*\&\s*($LvalOrFunc)\s*>>/ && $4 !~ /^\&/) { # $LvalOrFunc may be &foo, ignore if so WARN("MASK_THEN_SHIFT", "Possible precedence defect with mask then right shift - may need parentheses\n" . $herecurr); } # check for pointer comparisons to NULL if ($perl_version_ok) { while ($line =~ /\b$LvalOrFunc\s*(==|\!=)\s*NULL\b/g) { my $val = $1; my $equal = "!"; $equal = "" if ($4 eq "!="); if (CHK("COMPARISON_TO_NULL", "Comparison to NULL could be written \"${equal}${val}\"\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b\Q$val\E\s*(?:==|\!=)\s*NULL\b/$equal$val/; } } } # check for bad placement of section $InitAttribute (e.g.: __initdata) if ($line =~ /(\b$InitAttribute\b)/) { my $attr = $1; if ($line =~ /^\+\s*static\s+(?:const\s+)?(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*[=;]/) { my $ptr = $1; my $var = $2; if ((($ptr =~ /\b(union|struct)\s+$attr\b/ && ERROR("MISPLACED_INIT", "$attr should be placed after $var\n" . $herecurr)) || ($ptr !~ /\b(union|struct)\s+$attr\b/ && WARN("MISPLACED_INIT", "$attr should be placed after $var\n" . $herecurr))) && $fix) { $fixed[$fixlinenr] =~ s/(\bstatic\s+(?:const\s+)?)(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*([=;])\s*/"$1" . trim(string_find_replace($2, "\\s*$attr\\s*", " ")) . " " . trim(string_find_replace($3, "\\s*$attr\\s*", "")) . " $attr" . ("$4" eq ";" ? ";" : " = ")/e; } } } # check for $InitAttributeData (ie: __initdata) with const if ($line =~ /\bconst\b/ && $line =~ /($InitAttributeData)/) { my $attr = $1; $attr =~ /($InitAttributePrefix)(.*)/; my $attr_prefix = $1; my $attr_type = $2; if (ERROR("INIT_ATTRIBUTE", "Use of const init definition must use ${attr_prefix}initconst\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/$InitAttributeData/${attr_prefix}initconst/; } } # check for $InitAttributeConst (ie: __initconst) without const if ($line !~ /\bconst\b/ && $line =~ /($InitAttributeConst)/) { my $attr = $1; if (ERROR("INIT_ATTRIBUTE", "Use of $attr requires a separate use of const\n" . $herecurr) && $fix) { my $lead = $fixed[$fixlinenr] =~ /(^\+\s*(?:static\s+))/; $lead = rtrim($1); $lead = "$lead " if ($lead !~ /^\+$/); $lead = "${lead}const "; $fixed[$fixlinenr] =~ s/(^\+\s*(?:static\s+))/$lead/; } } # check for __read_mostly with const non-pointer (should just be const) if ($line =~ /\b__read_mostly\b/ && $line =~ /($Type)\s*$Ident/ && $1 !~ /\*\s*$/ && $1 =~ /\bconst\b/) { if (ERROR("CONST_READ_MOSTLY", "Invalid use of __read_mostly with const type\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\s+__read_mostly\b//; } } # don't use __constant_<foo> functions outside of include/uapi/ if ($realfile !~ m@^include/uapi/@ && $line =~ /(__constant_(?:htons|ntohs|[bl]e(?:16|32|64)_to_cpu|cpu_to_[bl]e(?:16|32|64)))\s*\(/) { my $constant_func = $1; my $func = $constant_func; $func =~ s/^__constant_//; if (WARN("CONSTANT_CONVERSION", "$constant_func should be $func\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b$constant_func\b/$func/g; } } # prefer usleep_range over udelay if ($line =~ /\budelay\s*\(\s*(\d+)\s*\)/) { my $delay = $1; # ignore udelay's < 10, however if (! ($delay < 10) ) { CHK("USLEEP_RANGE", "usleep_range is preferred over udelay; see Documentation/timers/timers-howto.rst\n" . $herecurr); } if ($delay > 2000) { WARN("LONG_UDELAY", "long udelay - prefer mdelay; see arch/arm/include/asm/delay.h\n" . $herecurr); } } # warn about unexpectedly long msleep's if ($line =~ /\bmsleep\s*\((\d+)\);/) { if ($1 < 20) { WARN("MSLEEP", "msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.rst\n" . $herecurr); } } # check for comparisons of jiffies if ($line =~ /\bjiffies\s*$Compare|$Compare\s*jiffies\b/) { WARN("JIFFIES_COMPARISON", "Comparing jiffies is almost always wrong; prefer time_after, time_before and friends\n" . $herecurr); } # check for comparisons of get_jiffies_64() if ($line =~ /\bget_jiffies_64\s*\(\s*\)\s*$Compare|$Compare\s*get_jiffies_64\s*\(\s*\)/) { WARN("JIFFIES_COMPARISON", "Comparing get_jiffies_64() is almost always wrong; prefer time_after64, time_before64 and friends\n" . $herecurr); } # warn about #ifdefs in C files # if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { # print "#ifdef in C files should be avoided\n"; # print "$herecurr"; # $clean = 0; # } # warn about spacing in #ifdefs if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) { if (ERROR("SPACING", "exactly one space required after that #$1\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/^(.\s*\#\s*(ifdef|ifndef|elif))\s{2,}/$1 /; } } # check for spinlock_t definitions without a comment. if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ || $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) { my $which = $1; if (!ctx_has_comment($first_line, $linenr)) { CHK("UNCOMMENTED_DEFINITION", "$1 definition without comment\n" . $herecurr); } } # check for memory barriers without a comment. my $barriers = qr{ mb| rmb| wmb }x; my $barrier_stems = qr{ mb__before_atomic| mb__after_atomic| store_release| load_acquire| store_mb| (?:$barriers) }x; my $all_barriers = qr{ (?:$barriers)| smp_(?:$barrier_stems)| virt_(?:$barrier_stems) }x; if ($line =~ /\b(?:$all_barriers)\s*\(/) { if (!ctx_has_comment($first_line, $linenr)) { WARN("MEMORY_BARRIER", "memory barrier without comment\n" . $herecurr); } } my $underscore_smp_barriers = qr{__smp_(?:$barrier_stems)}x; if ($realfile !~ m@^include/asm-generic/@ && $realfile !~ m@/barrier\.h$@ && $line =~ m/\b(?:$underscore_smp_barriers)\s*\(/ && $line !~ m/^.\s*\#\s*define\s+(?:$underscore_smp_barriers)\s*\(/) { WARN("MEMORY_BARRIER", "__smp memory barriers shouldn't be used outside barrier.h and asm-generic\n" . $herecurr); } # check for waitqueue_active without a comment. if ($line =~ /\bwaitqueue_active\s*\(/) { if (!ctx_has_comment($first_line, $linenr)) { WARN("WAITQUEUE_ACTIVE", "waitqueue_active without comment\n" . $herecurr); } } # check for data_race without a comment. if ($line =~ /\bdata_race\s*\(/) { if (!ctx_has_comment($first_line, $linenr)) { WARN("DATA_RACE", "data_race without comment\n" . $herecurr); } } # check of hardware specific defines if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) { CHK("ARCH_DEFINES", "architecture specific defines should be avoided\n" . $herecurr); } # check that the storage class is not after a type if ($line =~ /\b($Type)\s+($Storage)\b/) { WARN("STORAGE_CLASS", "storage class '$2' should be located before type '$1'\n" . $herecurr); } # Check that the storage class is at the beginning of a declaration if ($line =~ /\b$Storage\b/ && $line !~ /^.\s*$Storage/ && $line =~ /^.\s*(.+?)\$Storage\s/ && $1 !~ /[\,\)]\s*$/) { WARN("STORAGE_CLASS", "storage class should be at the beginning of the declaration\n" . $herecurr); } # check the location of the inline attribute, that it is between # storage class and type. if ($line =~ /\b$Type\s+$Inline\b/ || $line =~ /\b$Inline\s+$Storage\b/) { ERROR("INLINE_LOCATION", "inline keyword should sit between storage class and type\n" . $herecurr); } # Check for __inline__ and __inline, prefer inline if ($realfile !~ m@\binclude/uapi/@ && $line =~ /\b(__inline__|__inline)\b/) { if (WARN("INLINE", "plain inline is preferred over $1\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b(__inline__|__inline)\b/inline/; } } # Check for compiler attributes if ($realfile !~ m@\binclude/uapi/@ && $rawline =~ /\b__attribute__\s*\(\s*($balanced_parens)\s*\)/) { my $attr = $1; $attr =~ s/\s*\(\s*(.*)\)\s*/$1/; my %attr_list = ( "alias" => "__alias", "aligned" => "__aligned", "always_inline" => "__always_inline", "assume_aligned" => "__assume_aligned", "cold" => "__cold", "const" => "__attribute_const__", "copy" => "__copy", "designated_init" => "__designated_init", "externally_visible" => "__visible", "format" => "printf|scanf", "gnu_inline" => "__gnu_inline", "malloc" => "__malloc", "mode" => "__mode", "no_caller_saved_registers" => "__no_caller_saved_registers", "noclone" => "__noclone", "noinline" => "noinline", "nonstring" => "__nonstring", "noreturn" => "__noreturn", "packed" => "__packed", "pure" => "__pure", "section" => "__section", "used" => "__used", "weak" => "__weak" ); while ($attr =~ /\s*(\w+)\s*(${balanced_parens})?/g) { my $orig_attr = $1; my $params = ''; $params = $2 if defined($2); my $curr_attr = $orig_attr; $curr_attr =~ s/^[\s_]+|[\s_]+$//g; if (exists($attr_list{$curr_attr})) { my $new = $attr_list{$curr_attr}; if ($curr_attr eq "format" && $params) { $params =~ /^\s*\(\s*(\w+)\s*,\s*(.*)/; $new = "__$1\($2"; } else { $new = "$new$params"; } if (WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", "Prefer $new over __attribute__(($orig_attr$params))\n" . $herecurr) && $fix) { my $remove = "\Q$orig_attr\E" . '\s*' . "\Q$params\E" . '(?:\s*,\s*)?'; $fixed[$fixlinenr] =~ s/$remove//; $fixed[$fixlinenr] =~ s/\b__attribute__/$new __attribute__/; $fixed[$fixlinenr] =~ s/\}\Q$new\E/} $new/; $fixed[$fixlinenr] =~ s/ __attribute__\s*\(\s*\(\s*\)\s*\)//; } } } # Check for __attribute__ unused, prefer __always_unused or __maybe_unused if ($attr =~ /^_*unused/) { WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", "__always_unused or __maybe_unused is preferred over __attribute__((__unused__))\n" . $herecurr); } } # Check for __attribute__ weak, or __weak declarations (may have link issues) if ($perl_version_ok && $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ && ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ || $line =~ /\b__weak\b/)) { ERROR("WEAK_DECLARATION", "Using weak declarations can have unintended link defects\n" . $herecurr); } # check for c99 types like uint8_t used outside of uapi/ and tools/ if ($realfile !~ m@\binclude/uapi/@ && $realfile !~ m@\btools/@ && $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) { my $type = $1; if ($type =~ /\b($typeC99Typedefs)\b/) { $type = $1; my $kernel_type = 'u'; $kernel_type = 's' if ($type =~ /^_*[si]/); $type =~ /(\d+)/; $kernel_type .= $1; if (CHK("PREFER_KERNEL_TYPES", "Prefer kernel type '$kernel_type' over '$type'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b$type\b/$kernel_type/; } } } # check for cast of C90 native int or longer types constants if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) { my $cast = $1; my $const = $2; my $suffix = ""; my $newconst = $const; $newconst =~ s/${Int_type}$//; $suffix .= 'U' if ($cast =~ /\bunsigned\b/); if ($cast =~ /\blong\s+long\b/) { $suffix .= 'LL'; } elsif ($cast =~ /\blong\b/) { $suffix .= 'L'; } if (WARN("TYPECAST_INT_CONSTANT", "Unnecessary typecast of c90 int constant - '$cast$const' could be '$const$suffix'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$cast\E$const\b/$newconst$suffix/; } } # check for sizeof(&) if ($line =~ /\bsizeof\s*\(\s*\&/) { WARN("SIZEOF_ADDRESS", "sizeof(& should be avoided\n" . $herecurr); } # check for sizeof without parenthesis if ($line =~ /\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/) { if (WARN("SIZEOF_PARENTHESIS", "sizeof $1 should be sizeof($1)\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/"sizeof(" . trim($1) . ")"/ex; } } # check for struct spinlock declarations if ($line =~ /^.\s*\bstruct\s+spinlock\s+\w+\s*;/) { WARN("USE_SPINLOCK_T", "struct spinlock should be spinlock_t\n" . $herecurr); } # check for seq_printf uses that could be seq_puts if ($sline =~ /\bseq_printf\s*\(.*"\s*\)\s*;\s*$/) { my $fmt = get_quoted_string($line, $rawline); $fmt =~ s/%%//g; if ($fmt !~ /%/) { if (WARN("PREFER_SEQ_PUTS", "Prefer seq_puts to seq_printf\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bseq_printf\b/seq_puts/; } } } # check for vsprintf extension %p<foo> misuses if ($perl_version_ok && defined $stat && $stat =~ /^\+(?![^\{]*\{\s*).*\b(\w+)\s*\(.*$String\s*,/s && $1 !~ /^_*volatile_*$/) { my $stat_real; my $lc = $stat =~ tr@\n@@; $lc = $lc + $linenr; for (my $count = $linenr; $count <= $lc; $count++) { my $specifier; my $extension; my $qualifier; my $bad_specifier = ""; my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0)); $fmt =~ s/%%//g; while ($fmt =~ /(\%[\*\d\.]*p(\w)(\w*))/g) { $specifier = $1; $extension = $2; $qualifier = $3; if ($extension !~ /[4SsBKRraEehMmIiUDdgVCbGNOxtf]/ || ($extension eq "f" && defined $qualifier && $qualifier !~ /^w/) || ($extension eq "4" && defined $qualifier && $qualifier !~ /^cc/)) { $bad_specifier = $specifier; last; } if ($extension eq "x" && !defined($stat_real)) { if (!defined($stat_real)) { $stat_real = get_stat_real($linenr, $lc); } WARN("VSPRINTF_SPECIFIER_PX", "Using vsprintf specifier '\%px' potentially exposes the kernel memory layout, if you don't really need the address please consider using '\%p'.\n" . "$here\n$stat_real\n"); } } if ($bad_specifier ne "") { my $stat_real = get_stat_real($linenr, $lc); my $ext_type = "Invalid"; my $use = ""; if ($bad_specifier =~ /p[Ff]/) { $use = " - use %pS instead"; $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/); } WARN("VSPRINTF_POINTER_EXTENSION", "$ext_type vsprintf pointer extension '$bad_specifier'$use\n" . "$here\n$stat_real\n"); } } } # Check for misused memsets if ($perl_version_ok && defined $stat && $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/) { my $ms_addr = $2; my $ms_val = $7; my $ms_size = $12; if ($ms_size =~ /^(0x|)0$/i) { ERROR("MEMSET", "memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n"); } elsif ($ms_size =~ /^(0x|)1$/i) { WARN("MEMSET", "single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n"); } } # Check for memcpy(foo, bar, ETH_ALEN) that could be ether_addr_copy(foo, bar) # if ($perl_version_ok && # defined $stat && # $stat =~ /^\+(?:.*?)\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { # if (WARN("PREFER_ETHER_ADDR_COPY", # "Prefer ether_addr_copy() over memcpy() if the Ethernet addresses are __aligned(2)\n" . "$here\n$stat\n") && # $fix) { # $fixed[$fixlinenr] =~ s/\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/ether_addr_copy($2, $7)/; # } # } # Check for memcmp(foo, bar, ETH_ALEN) that could be ether_addr_equal*(foo, bar) # if ($perl_version_ok && # defined $stat && # $stat =~ /^\+(?:.*?)\bmemcmp\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { # WARN("PREFER_ETHER_ADDR_EQUAL", # "Prefer ether_addr_equal() or ether_addr_equal_unaligned() over memcmp()\n" . "$here\n$stat\n") # } # check for memset(foo, 0x0, ETH_ALEN) that could be eth_zero_addr # check for memset(foo, 0xFF, ETH_ALEN) that could be eth_broadcast_addr # if ($perl_version_ok && # defined $stat && # $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { # # my $ms_val = $7; # # if ($ms_val =~ /^(?:0x|)0+$/i) { # if (WARN("PREFER_ETH_ZERO_ADDR", # "Prefer eth_zero_addr over memset()\n" . "$here\n$stat\n") && # $fix) { # $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_zero_addr($2)/; # } # } elsif ($ms_val =~ /^(?:0xff|255)$/i) { # if (WARN("PREFER_ETH_BROADCAST_ADDR", # "Prefer eth_broadcast_addr() over memset()\n" . "$here\n$stat\n") && # $fix) { # $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_broadcast_addr($2)/; # } # } # } # strlcpy uses that should likely be strscpy if ($line =~ /\bstrlcpy\s*\(/) { WARN("STRLCPY", "Prefer strscpy over strlcpy - see: https://lore.kernel.org/r/CAHk-=wgfRnXz0W3D37d01q3JFkr_i_uTL=V6A6G1oUZcprmknw\@mail.gmail.com/\n" . $herecurr); } # typecasts on min/max could be min_t/max_t if ($perl_version_ok && defined $stat && $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { if (defined $2 || defined $7) { my $call = $1; my $cast1 = deparenthesize($2); my $arg1 = $3; my $cast2 = deparenthesize($7); my $arg2 = $8; my $cast; if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) { $cast = "$cast1 or $cast2"; } elsif ($cast1 ne "") { $cast = $cast1; } else { $cast = $cast2; } WARN("MINMAX", "$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n"); } } # check usleep_range arguments if ($perl_version_ok && defined $stat && $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) { my $min = $1; my $max = $7; if ($min eq $max) { WARN("USLEEP_RANGE", "usleep_range should not use min == max args; see Documentation/timers/timers-howto.rst\n" . "$here\n$stat\n"); } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && $min > $max) { WARN("USLEEP_RANGE", "usleep_range args reversed, use min then max; see Documentation/timers/timers-howto.rst\n" . "$here\n$stat\n"); } } # check for naked sscanf if ($perl_version_ok && defined $stat && $line =~ /\bsscanf\b/ && ($stat !~ /$Ident\s*=\s*sscanf\s*$balanced_parens/ && $stat !~ /\bsscanf\s*$balanced_parens\s*(?:$Compare)/ && $stat !~ /(?:$Compare)\s*\bsscanf\s*$balanced_parens/)) { my $lc = $stat =~ tr@\n@@; $lc = $lc + $linenr; my $stat_real = get_stat_real($linenr, $lc); WARN("NAKED_SSCANF", "unchecked sscanf return value\n" . "$here\n$stat_real\n"); } # check for simple sscanf that should be kstrto<foo> if ($perl_version_ok && defined $stat && $line =~ /\bsscanf\b/) { my $lc = $stat =~ tr@\n@@; $lc = $lc + $linenr; my $stat_real = get_stat_real($linenr, $lc); if ($stat_real =~ /\bsscanf\b\s*\(\s*$FuncArg\s*,\s*("[^"]+")/) { my $format = $6; my $count = $format =~ tr@%@%@; if ($count == 1 && $format =~ /^"\%(?i:ll[udxi]|[udxi]ll|ll|[hl]h?[udxi]|[udxi][hl]h?|[hl]h?|[udxi])"$/) { WARN("SSCANF_TO_KSTRTO", "Prefer kstrto<type> to single variable sscanf\n" . "$here\n$stat_real\n"); } } } # check for new externs in .h files. if ($realfile =~ /\.h$/ && $line =~ /^\+\s*(extern\s+)$Type\s*$Ident\s*\(/s) { if (CHK("AVOID_EXTERNS", "extern prototypes should be avoided in .h files\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(.*)\bextern\b\s*(.*)/$1$2/; } } # check for new externs in .c files. if ($realfile =~ /\.c$/ && defined $stat && $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s) { my $function_name = $1; my $paren_space = $2; my $s = $stat; if (defined $cond) { substr($s, 0, length($cond), ''); } if ($s =~ /^\s*;/) { WARN("AVOID_EXTERNS", "externs should be avoided in .c files\n" . $herecurr); } if ($paren_space =~ /\n/) { WARN("FUNCTION_ARGUMENTS", "arguments for function declarations should follow identifier\n" . $herecurr); } } elsif ($realfile =~ /\.c$/ && defined $stat && $stat =~ /^.\s*extern\s+/) { WARN("AVOID_EXTERNS", "externs should be avoided in .c files\n" . $herecurr); } # check for function declarations that have arguments without identifier names if (defined $stat && $stat =~ /^.\s*(?:extern\s+)?$Type\s*(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*\(\s*([^{]+)\s*\)\s*;/s && $1 ne "void") { my $args = trim($1); while ($args =~ m/\s*($Type\s*(?:$Ident|\(\s*\*\s*$Ident?\s*\)\s*$balanced_parens)?)/g) { my $arg = trim($1); if ($arg =~ /^$Type$/ && $arg !~ /enum\s+$Ident$/) { WARN("FUNCTION_ARGUMENTS", "function definition argument '$arg' should also have an identifier name\n" . $herecurr); } } } # check for function definitions if ($perl_version_ok && defined $stat && $stat =~ /^.\s*(?:$Storage\s+)?$Type\s*($Ident)\s*$balanced_parens\s*{/s) { $context_function = $1; # check for multiline function definition with misplaced open brace my $ok = 0; my $cnt = statement_rawlines($stat); my $herectx = $here . "\n"; for (my $n = 0; $n < $cnt; $n++) { my $rl = raw_line($linenr, $n); $herectx .= $rl . "\n"; $ok = 1 if ($rl =~ /^[ \+]\{/); $ok = 1 if ($rl =~ /\{/ && $n == 0); last if $rl =~ /^[ \+].*\{/; } if (!$ok) { ERROR("OPEN_BRACE", "open brace '{' following function definitions go on the next line\n" . $herectx); } } # checks for new __setup's if ($rawline =~ /\b__setup\("([^"]*)"/) { my $name = $1; if (!grep(/$name/, @setup_docs)) { CHK("UNDOCUMENTED_SETUP", "__setup appears un-documented -- check Documentation/admin-guide/kernel-parameters.txt\n" . $herecurr); } } # check for pointless casting of alloc functions if ($line =~ /\*\s*\)\s*$allocFunctions\b/) { WARN("UNNECESSARY_CASTS", "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); } # alloc style # p = alloc(sizeof(struct foo), ...) should be p = alloc(sizeof(*p), ...) if ($perl_version_ok && $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k|v)[mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) { CHK("ALLOC_SIZEOF_STRUCT", "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr); } # check for k[mz]alloc with multiplies that could be kmalloc_array/kcalloc if ($perl_version_ok && defined $stat && $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { my $oldfunc = $3; my $a1 = $4; my $a2 = $10; my $newfunc = "kmalloc_array"; $newfunc = "kcalloc" if ($oldfunc eq "kzalloc"); my $r1 = $a1; my $r2 = $a2; if ($a1 =~ /^sizeof\s*\S/) { $r1 = $a2; $r2 = $a1; } if ($r1 !~ /^sizeof\b/ && $r2 =~ /^sizeof\s*\S/ && !($r1 =~ /^$Constant$/ || $r1 =~ /^[A-Z_][A-Z0-9_]*$/)) { my $cnt = statement_rawlines($stat); my $herectx = get_stat_here($linenr, $cnt, $here); if (WARN("ALLOC_WITH_MULTIPLY", "Prefer $newfunc over $oldfunc with multiply\n" . $herectx) && $cnt == 1 && $fix) { $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . trim($r1) . ', ' . trim($r2)/e; } } } # check for krealloc arg reuse if ($perl_version_ok && $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*($Lval)\s*,/ && $1 eq $3) { WARN("KREALLOC_ARG_REUSE", "Reusing the krealloc arg is almost always a bug\n" . $herecurr); } # check for alloc argument mismatch if ($line =~ /\b((?:devm_)?(?:kcalloc|kmalloc_array))\s*\(\s*sizeof\b/) { WARN("ALLOC_ARRAY_ARGS", "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr); } # check for multiple semicolons if ($line =~ /;\s*;\s*$/) { if (WARN("ONE_SEMICOLON", "Statements terminations use 1 semicolon\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/(\s*;\s*){2,}$/;/g; } } # check for #defines like: 1 << <digit> that could be BIT(digit), it is not exported to uapi if ($realfile !~ m@^include/uapi/@ && $line =~ /#\s*define\s+\w+\s+\(?\s*1\s*([ulUL]*)\s*\<\<\s*(?:\d+|$Ident)\s*\)?/) { my $ull = ""; $ull = "_ULL" if (defined($1) && $1 =~ /ll/i); if (CHK("BIT_MACRO", "Prefer using the BIT$ull macro\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\(?\s*1\s*[ulUL]*\s*<<\s*(\d+|$Ident)\s*\)?/BIT${ull}($1)/; } } # check for IS_ENABLED() without CONFIG_<FOO> ($rawline for comments too) if ($rawline =~ /\bIS_ENABLED\s*\(\s*(\w+)\s*\)/ && $1 !~ /^${CONFIG_}/) { WARN("IS_ENABLED_CONFIG", "IS_ENABLED($1) is normally used as IS_ENABLED(${CONFIG_}$1)\n" . $herecurr); } # check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(${CONFIG_}[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) { my $config = $1; if (WARN("PREFER_IS_ENABLED", "Prefer IS_ENABLED(<FOO>) to ${CONFIG_}<FOO> || ${CONFIG_}<FOO>_MODULE\n" . $herecurr) && $fix) { $fixed[$fixlinenr] = "\+#if IS_ENABLED($config)"; } } # check for /* fallthrough */ like comment, prefer fallthrough; my @fallthroughs = ( 'fallthrough', '@fallthrough@', 'lint -fallthrough[ \t]*', 'intentional(?:ly)?[ \t]*fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)', '(?:else,?\s*)?FALL(?:S | |-)?THR(?:OUGH|U|EW)[ \t.!]*(?:-[^\n\r]*)?', 'Fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', 'fall(?:s | |-)?thr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', ); if ($raw_comment ne '') { foreach my $ft (@fallthroughs) { if ($raw_comment =~ /$ft/) { my $msg_level = \&WARN; $msg_level = \&CHK if ($file); &{$msg_level}("PREFER_FALLTHROUGH", "Prefer 'fallthrough;' over fallthrough comment\n" . $herecurr); last; } } } # check for switch/default statements without a break; if ($perl_version_ok && defined $stat && $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) { my $cnt = statement_rawlines($stat); my $herectx = get_stat_here($linenr, $cnt, $here); WARN("DEFAULT_NO_BREAK", "switch default: should use break\n" . $herectx); } # check for gcc specific __FUNCTION__ if ($line =~ /\b__FUNCTION__\b/) { if (WARN("USE_FUNC", "__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\b__FUNCTION__\b/__func__/g; } } # check for uses of __DATE__, __TIME__, __TIMESTAMP__ while ($line =~ /\b(__(?:DATE|TIME|TIMESTAMP)__)\b/g) { ERROR("DATE_TIME", "Use of the '$1' macro makes the build non-deterministic\n" . $herecurr); } # check for use of yield() if ($line =~ /\byield\s*\(\s*\)/) { WARN("YIELD", "Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr); } # check for comparisons against true and false if ($line =~ /\+\s*(.*?)\b(true|false|$Lval)\s*(==|\!=)\s*(true|false|$Lval)\b(.*)$/i) { my $lead = $1; my $arg = $2; my $test = $3; my $otype = $4; my $trail = $5; my $op = "!"; ($arg, $otype) = ($otype, $arg) if ($arg =~ /^(?:true|false)$/i); my $type = lc($otype); if ($type =~ /^(?:true|false)$/) { if (("$test" eq "==" && "$type" eq "true") || ("$test" eq "!=" && "$type" eq "false")) { $op = ""; } CHK("BOOL_COMPARISON", "Using comparison to $otype is error prone\n" . $herecurr); ## maybe suggesting a correct construct would better ## "Using comparison to $otype is error prone. Perhaps use '${lead}${op}${arg}${trail}'\n" . $herecurr); } } # check for semaphores initialized locked if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) { WARN("CONSIDER_COMPLETION", "consider using a completion\n" . $herecurr); } # recommend kstrto* over simple_strto* and strict_strto* if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) { WARN("CONSIDER_KSTRTO", "$1 is obsolete, use k$3 instead\n" . $herecurr); } # check for __initcall(), use device_initcall() explicitly or more appropriate function please if ($line =~ /^.\s*__initcall\s*\(/) { WARN("USE_DEVICE_INITCALL", "please use device_initcall() or more appropriate function instead of __initcall() (see include/linux/init.h)\n" . $herecurr); } # check for spin_is_locked(), suggest lockdep instead if ($line =~ /\bspin_is_locked\(/) { WARN("USE_LOCKDEP", "Where possible, use lockdep_assert_held instead of assertions based on spin_is_locked\n" . $herecurr); } # check for deprecated apis if ($line =~ /\b($deprecated_apis_search)\b\s*\(/) { my $deprecated_api = $1; my $new_api = $deprecated_apis{$deprecated_api}; WARN("DEPRECATED_API", "Deprecated use of '$deprecated_api', prefer '$new_api' instead\n" . $herecurr); } # check for various structs that are normally const (ops, kgdb, device_tree) # and avoid what seem like struct definitions 'struct foo {' if (defined($const_structs) && $line !~ /\bconst\b/ && $line =~ /\bstruct\s+($const_structs)\b(?!\s*\{)/) { WARN("CONST_STRUCT", "struct $1 should normally be const\n" . $herecurr); } # use of NR_CPUS is usually wrong # ignore definitions of NR_CPUS and usage to define arrays as likely right # ignore designated initializers using NR_CPUS if ($line =~ /\bNR_CPUS\b/ && $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/ && $line !~ /^.\s*\.\w+\s*=\s*.*\bNR_CPUS\b/) { WARN("NR_CPUS", "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); } # Use of __ARCH_HAS_<FOO> or ARCH_HAVE_<BAR> is wrong. if ($line =~ /\+\s*#\s*define\s+((?:__)?ARCH_(?:HAS|HAVE)\w*)\b/) { ERROR("DEFINE_ARCH_HAS", "#define of '$1' is wrong - use Kconfig variables or standard guards instead\n" . $herecurr); } # likely/unlikely comparisons similar to "(likely(foo) > 0)" if ($perl_version_ok && $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) { WARN("LIKELY_MISUSE", "Using $1 should generally have parentheses around the comparison\n" . $herecurr); } # return sysfs_emit(foo, fmt, ...) fmt without newline if ($line =~ /\breturn\s+sysfs_emit\s*\(\s*$FuncArg\s*,\s*($String)/ && substr($rawline, $-[6], $+[6] - $-[6]) !~ /\\n"$/) { my $offset = $+[6] - 1; if (WARN("SYSFS_EMIT", "return sysfs_emit(...) formats should include a terminating newline\n" . $herecurr) && $fix) { substr($fixed[$fixlinenr], $offset, 0) = '\\n'; } } # nested likely/unlikely calls if ($line =~ /\b(?:(?:un)?likely)\s*\(\s*!?\s*(IS_ERR(?:_OR_NULL|_VALUE)?|WARN)/) { WARN("LIKELY_MISUSE", "nested (un)?likely() calls, $1 already uses unlikely() internally\n" . $herecurr); } # whine mightly about in_atomic if ($line =~ /\bin_atomic\s*\(/) { if ($realfile =~ m@^drivers/@) { ERROR("IN_ATOMIC", "do not use in_atomic in drivers\n" . $herecurr); } elsif ($realfile !~ m@^kernel/@) { WARN("IN_ATOMIC", "use of in_atomic() is incorrect outside core kernel code\n" . $herecurr); } } # check for lockdep_set_novalidate_class if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ || $line =~ /__lockdep_no_validate__\s*\)/ ) { if ($realfile !~ m@^kernel/lockdep@ && $realfile !~ m@^include/linux/lockdep@ && $realfile !~ m@^drivers/base/core@) { ERROR("LOCKDEP", "lockdep_no_validate class is reserved for device->mutex.\n" . $herecurr); } } if ($line =~ /debugfs_create_\w+.*\b$mode_perms_world_writable\b/ || $line =~ /DEVICE_ATTR.*\b$mode_perms_world_writable\b/) { WARN("EXPORTED_WORLD_WRITABLE", "Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr); } # check for DEVICE_ATTR uses that could be DEVICE_ATTR_<FOO> # and whether or not function naming is typical and if # DEVICE_ATTR permissions uses are unusual too if ($perl_version_ok && defined $stat && $stat =~ /\bDEVICE_ATTR\s*\(\s*(\w+)\s*,\s*\(?\s*(\s*(?:${multi_mode_perms_string_search}|0[0-7]{3,3})\s*)\s*\)?\s*,\s*(\w+)\s*,\s*(\w+)\s*\)/) { my $var = $1; my $perms = $2; my $show = $3; my $store = $4; my $octal_perms = perms_to_octal($perms); if ($show =~ /^${var}_show$/ && $store =~ /^${var}_store$/ && $octal_perms eq "0644") { if (WARN("DEVICE_ATTR_RW", "Use DEVICE_ATTR_RW\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*$store\s*\)/DEVICE_ATTR_RW(${var})/; } } elsif ($show =~ /^${var}_show$/ && $store =~ /^NULL$/ && $octal_perms eq "0444") { if (WARN("DEVICE_ATTR_RO", "Use DEVICE_ATTR_RO\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*NULL\s*\)/DEVICE_ATTR_RO(${var})/; } } elsif ($show =~ /^NULL$/ && $store =~ /^${var}_store$/ && $octal_perms eq "0200") { if (WARN("DEVICE_ATTR_WO", "Use DEVICE_ATTR_WO\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*NULL\s*,\s*$store\s*\)/DEVICE_ATTR_WO(${var})/; } } elsif ($octal_perms eq "0644" || $octal_perms eq "0444" || $octal_perms eq "0200") { my $newshow = "$show"; $newshow = "${var}_show" if ($show ne "NULL" && $show ne "${var}_show"); my $newstore = $store; $newstore = "${var}_store" if ($store ne "NULL" && $store ne "${var}_store"); my $rename = ""; if ($show ne $newshow) { $rename .= " '$show' to '$newshow'"; } if ($store ne $newstore) { $rename .= " '$store' to '$newstore'"; } WARN("DEVICE_ATTR_FUNCTIONS", "Consider renaming function(s)$rename\n" . $herecurr); } else { WARN("DEVICE_ATTR_PERMS", "DEVICE_ATTR unusual permissions '$perms' used\n" . $herecurr); } } # Mode permission misuses where it seems decimal should be octal # This uses a shortcut match to avoid unnecessary uses of a slow foreach loop # o Ignore module_param*(...) uses with a decimal 0 permission as that has a # specific definition of not visible in sysfs. # o Ignore proc_create*(...) uses with a decimal 0 permission as that means # use the default permissions if ($perl_version_ok && defined $stat && $line =~ /$mode_perms_search/) { foreach my $entry (@mode_permission_funcs) { my $func = $entry->[0]; my $arg_pos = $entry->[1]; my $lc = $stat =~ tr@\n@@; $lc = $lc + $linenr; my $stat_real = get_stat_real($linenr, $lc); my $skip_args = ""; if ($arg_pos > 1) { $arg_pos--; $skip_args = "(?:\\s*$FuncArg\\s*,\\s*){$arg_pos,$arg_pos}"; } my $test = "\\b$func\\s*\\(${skip_args}($FuncArg(?:\\|\\s*$FuncArg)*)\\s*[,\\)]"; if ($stat =~ /$test/) { my $val = $1; $val = $6 if ($skip_args ne ""); if (!($func =~ /^(?:module_param|proc_create)/ && $val eq "0") && (($val =~ /^$Int$/ && $val !~ /^$Octal$/) || ($val =~ /^$Octal$/ && length($val) ne 4))) { ERROR("NON_OCTAL_PERMISSIONS", "Use 4 digit octal (0777) not decimal permissions\n" . "$here\n" . $stat_real); } if ($val =~ /^$Octal$/ && (oct($val) & 02)) { ERROR("EXPORTED_WORLD_WRITABLE", "Exporting writable files is usually an error. Consider more restrictive permissions.\n" . "$here\n" . $stat_real); } } } } # check for uses of S_<PERMS> that could be octal for readability while ($line =~ m{\b($multi_mode_perms_string_search)\b}g) { my $oval = $1; my $octal = perms_to_octal($oval); if (WARN("SYMBOLIC_PERMS", "Symbolic permissions '$oval' are not preferred. Consider using octal permissions '$octal'.\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$oval\E/$octal/; } } # validate content of MODULE_LICENSE against list from include/linux/module.h if ($line =~ /\bMODULE_LICENSE\s*\(\s*($String)\s*\)/) { my $extracted_string = get_quoted_string($line, $rawline); my $valid_licenses = qr{ GPL| GPL\ v2| GPL\ and\ additional\ rights| Dual\ BSD/GPL| Dual\ MIT/GPL| Dual\ MPL/GPL| Proprietary }x; if ($extracted_string !~ /^"(?:$valid_licenses)"$/x) { WARN("MODULE_LICENSE", "unknown module license " . $extracted_string . "\n" . $herecurr); } } # check for sysctl duplicate constants if ($line =~ /\.extra[12]\s*=\s*&(zero|one|int_max)\b/) { WARN("DUPLICATED_SYSCTL_CONST", "duplicated sysctl range checking value '$1', consider using the shared one in include/linux/sysctl.h\n" . $herecurr); } } # If we have no input at all, then there is nothing to report on # so just keep quiet. if ($#rawlines == -1) { exit(0); } # In mailback mode only produce a report in the negative, for # things that appear to be patches. if ($mailback && ($clean == 1 || !$is_patch)) { exit(0); } # This is not a patch, and we are in 'no-patch' mode so # just keep quiet. if (!$chk_patch && !$is_patch) { exit(0); } if (!$is_patch && $filename !~ /cover-letter\.patch$/) { ERROR("NOT_UNIFIED_DIFF", "Does not appear to be a unified-diff format patch\n"); } if ($is_patch && $has_commit_log && $chk_signoff) { if ($signoff == 0) { ERROR("MISSING_SIGN_OFF", "Missing Signed-off-by: line(s)\n"); } elsif ($authorsignoff != 1) { # authorsignoff values: # 0 -> missing sign off # 1 -> sign off identical # 2 -> names and addresses match, comments mismatch # 3 -> addresses match, names different # 4 -> names match, addresses different # 5 -> names match, addresses excluding subaddress details (refer RFC 5233) match my $sob_msg = "'From: $author' != 'Signed-off-by: $author_sob'"; if ($authorsignoff == 0) { ERROR("NO_AUTHOR_SIGN_OFF", "Missing Signed-off-by: line by nominal patch author '$author'\n"); } elsif ($authorsignoff == 2) { CHK("FROM_SIGN_OFF_MISMATCH", "From:/Signed-off-by: email comments mismatch: $sob_msg\n"); } elsif ($authorsignoff == 3) { WARN("FROM_SIGN_OFF_MISMATCH", "From:/Signed-off-by: email name mismatch: $sob_msg\n"); } elsif ($authorsignoff == 4) { WARN("FROM_SIGN_OFF_MISMATCH", "From:/Signed-off-by: email address mismatch: $sob_msg\n"); } elsif ($authorsignoff == 5) { WARN("FROM_SIGN_OFF_MISMATCH", "From:/Signed-off-by: email subaddress mismatch: $sob_msg\n"); } } } print report_dump(); if ($quiet == 0) { # If there were any defects found and not already fixing them if (!$clean and !$fix) { print << "EOM" NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. EOM } # If there were whitespace errors which cleanpatch can fix # then suggest that. if ($rpt_cleaners) { $rpt_cleaners = 0; print << "EOM" NOTE: Whitespace errors detected. You may wish to use scripts/cleanpatch or scripts/cleanfile EOM } } if ($clean == 0 && $fix && ("@rawlines" ne "@fixed" || $#fixed_inserted >= 0 || $#fixed_deleted >= 0)) { my $newfile = $filename; $newfile .= ".EXPERIMENTAL-checkpatch-fixes" if (!$fix_inplace); my $linecount = 0; my $f; @fixed = fix_inserted_deleted_lines(\@fixed, \@fixed_inserted, \@fixed_deleted); open($f, '>', $newfile) or die "$P: Can't open $newfile for write\n"; foreach my $fixed_line (@fixed) { $linecount++; if ($file) { if ($linecount > 3) { $fixed_line =~ s/^\+//; print $f $fixed_line . "\n"; } } else { print $f $fixed_line . "\n"; } } close($f); if (!$quiet) { print << "EOM"; Wrote EXPERIMENTAL --fix correction(s) to '$newfile' Do _NOT_ trust the results written to this file. Do _NOT_ submit these changes without inspecting them for correctness. This EXPERIMENTAL file is simply a convenience to help rewrite patches. No warranties, expressed or implied... EOM } } if ($quiet == 0) { print "\n"; if ($clean == 1) { print "$vname has no obvious style problems and is ready for submission.\n"; } else { print "$vname has style problems, please review.\n"; } } return $clean; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/scripts/find-banned.sh������������������������������������������������������������������0000775�0000000�0000000�00000000306�14570443012�0017001�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh banned="malloc,g_strcmp0,sprintf,vsprintf,strcpy,strncpy,strcat,strncat" find src/ include/ \( -name "*.c" -o -name "*.h" \) -type f \ | ./scripts/helper/find-idents --tokens=$banned - ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/scripts/helper/�������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14570443012�0015555�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/scripts/helper/.gitignore���������������������������������������������������������������0000664�0000000�0000000�00000000020�14570443012�0017535�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������find-idents *.o ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/scripts/helper/Makefile�����������������������������������������������������������������0000664�0000000�0000000�00000000253�14570443012�0017215�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������CFLAGS += -g -Wall -O0 -std=c11 LDFLAGS += -fsanitize=address PROGS = find-idents all: $(PROGS) find-idents: find-idents.o $(CC) -o $@ $^ clean : $(RM) $(PROGS) *.o �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/scripts/helper/find-idents.c������������������������������������������������������������0000664�0000000�0000000�00000022656�14570443012�0020140�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * Helper to find identifier names in C files * * Copyright (C) Johan Malm 2023 * * It tokenizes the specified C file and searches all identifier-tokens against * the specified patterns. * * An identifier in this context is any alphanumeric/underscore string starting * with a letter [A-Za-z] or underscore. It represents entities such as * functions, variables, user-defined data types and C language keywords. * Alphanumeric strings within comments are ignored, but not parsing of tokens * is carried out to understand their semantic meaning. */ #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <ctype.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> struct buf { char *buf; int alloc; int len; }; enum token_kind { TOKEN_NONE = 0, TOKEN_IDENTIFIER, /* For example: static extern if while */ TOKEN_LITERAL, /* For example: 0xff 42 "foo" */ TOKEN_SPECIAL, /* For example: ++ -= ! ... */ }; struct token { int line; enum token_kind kind; struct buf name; unsigned int special; }; enum { SPECIAL_ELLIPSIS = 256, SPECIAL_ASSIGN, SPECIAL_BIT_OP, SPECIAL_INC_OP, SPECIAL_DEC_OP, SPECIAL_PTR_OP, SPECIAL_AND_OP, SPECIAL_OR_OP, SPECIAL_COMPARISON_OP, SPECIAL_COMMENT_BEGIN, SPECIAL_COMMENT_END, SPECIAL_COMMENT_LINE_BEGIN, }; static char *current_buffer_position; static struct token *tokens; static int nr_tokens, alloc_tokens; static int current_line; static char **argv_tokens; static int found_token; static const char find_banned_usage[] = "Usage: find-banned [OPTIONS...] FILE\n" "When FILE is -, read stdin\n" "OPTIONS:\n" " --tokens=<tokens> Comma-separated string of idents to grep for\n"; static void usage(void) { printf("%s", find_banned_usage); exit(0); } char ** split(char *str, char delim) { if (!str) { return NULL; } int argc = 1; char *p = str; while (*p) { if (*p == delim) { argc++; } ++p; } char **argv = calloc(argc + 1, sizeof(*argv)); char **argvp = argv; p = str; while (*str) { if (*str == delim) { *argvp++ = strndup(p, str-p); p = str + 1; } ++str; } *argvp++ = strndup(p, str-p); *argvp = NULL; return argv; } void buf_init(struct buf *s) { s->alloc = 256; s->buf = malloc(s->alloc); s->buf[0] = '\0'; s->len = 0; } void buf_add(struct buf *s, const char *data, size_t len) { if (!data || data[0] == '\0') { return; } if (s->alloc <= s->len + len + 1) { s->alloc = s->alloc + len; s->buf = realloc(s->buf, s->alloc); } memcpy(s->buf + s->len, data, len); s->len += len; s->buf[s->len] = 0; } void buf_add_char(struct buf *s, char ch) { if (s->alloc <= s->len + 1) { s->alloc = s->alloc * 2 + 16; s->buf = realloc(s->buf, s->alloc); } s->buf[s->len++] = ch; s->buf[s->len] = 0; } static struct token * add_token(void) { if (nr_tokens == alloc_tokens) { alloc_tokens = (alloc_tokens + 16) * 2; tokens = realloc(tokens, alloc_tokens * sizeof(struct token)); } struct token *token = tokens + nr_tokens; memset(token, 0, sizeof(*token)); nr_tokens++; buf_init(&token->name); token->line = current_line; return token; } static void get_identifier_token(struct token *token) { buf_add_char(&token->name, current_buffer_position[0]); current_buffer_position++; if (isspace(current_buffer_position[0])) { return; } switch (current_buffer_position[0]) { case '\0': break; case 'a' ... 'z': case 'A' ... 'Z': case '0' ... '9': case '_': case '#': get_identifier_token(token); break; default: break; } } static void get_number_token(struct token *token) { buf_add_char(&token->name, current_buffer_position[0]); current_buffer_position++; if (isspace(current_buffer_position[0])) { return; } switch (current_buffer_position[0]) { case '\0': break; case '0' ... '9': case 'a' ... 'f': case 'A' ... 'F': case 'x': get_number_token(token); break; default: break; } } struct { const char *combo; unsigned int special; } specials[] = { { "...", SPECIAL_ELLIPSIS }, { ">>=", SPECIAL_ASSIGN }, { "<<=", SPECIAL_ASSIGN }, { "+=", SPECIAL_ASSIGN }, { "-=", SPECIAL_ASSIGN }, { "*=", SPECIAL_ASSIGN }, { "/=", SPECIAL_ASSIGN }, { "%=", SPECIAL_ASSIGN }, { "&=", SPECIAL_ASSIGN }, { "^=", SPECIAL_ASSIGN }, { "|=", SPECIAL_ASSIGN }, { ">>", SPECIAL_BIT_OP }, { "<<", SPECIAL_BIT_OP }, { "++", SPECIAL_INC_OP }, { "--", SPECIAL_DEC_OP }, { "->", SPECIAL_PTR_OP }, { "&&", SPECIAL_AND_OP }, { "||", SPECIAL_OR_OP }, { "<=", SPECIAL_COMPARISON_OP }, { ">=", SPECIAL_COMPARISON_OP }, { "==", SPECIAL_COMPARISON_OP }, { "!=", SPECIAL_COMPARISON_OP }, { "/*", SPECIAL_COMMENT_BEGIN }, { "*/", SPECIAL_COMMENT_END }, { "//", SPECIAL_COMMENT_LINE_BEGIN }, { ";", ';' }, { "{", '{' }, { "}", '}' }, { ",", ',' }, { ":", ':' }, { "=", '=' }, { "(", '(' }, { ")", ')' }, { "[", '[' }, { "]", ']' }, { ".", '.' }, { "&", '&' }, { "!", '!' }, { "~", '~' }, { "-", '-' }, { "+", '+' }, { "*", '*' }, { "/", '/' }, { "%", '%' }, { "<", '<' }, { ">", '>' }, { "^", '^' }, { "|", '|' }, { "?", '?' }, }; static void get_special_token(struct token *token) { #define MAX_SPECIAL_LEN (3) /* Peek up to MAX_SPECIAL_LEN-1 characters ahead */ char buf[MAX_SPECIAL_LEN + 1] = { 0 }; for (int i = 0; i < MAX_SPECIAL_LEN; i++) { buf[i] = current_buffer_position[i]; if (!current_buffer_position[i]) { break; } } #undef MAX_SPECIAL_LEN /* Compare with longest special tokens first */ int k; for (k = strlen(buf); k > 0; k--) { for (size_t j = 0; j < sizeof(specials) / sizeof(specials[0]); j++) { if (strlen(specials[j].combo) < k) { break; } if (!strcmp(specials[j].combo, buf)) { buf_add(&token->name, buf, k); token->special = specials[j].special; goto done; } } buf[k - 1] = '\0'; } done: current_buffer_position += token->name.len; } static void handle_preprocessor_directive(void) { /* We just ignore preprocessor lines */ for (;;) { ++current_buffer_position; if (current_buffer_position[0] == '\0') { return; } if (current_buffer_position[0] == '\n') { ++current_line; return; } } } struct token * lex(char *buffer) { tokens = NULL; nr_tokens = 0; alloc_tokens = 0; bool in_single_comment = false; current_buffer_position = buffer; for (;;) { struct token *token = NULL; switch (current_buffer_position[0]) { case '\0': goto out; case 'a' ... 'z': case 'A' ... 'Z': case '_': token = add_token(); get_identifier_token(token); token->kind = TOKEN_IDENTIFIER; continue; case '0' ... '9': token = add_token(); get_number_token(token); token->kind = TOKEN_LITERAL; continue; case '+': case '-': case '*': case '/': case '%': case '.': case '>': case '<': case '=': case '!': case '&': case '|': case '^': case '{': case '}': case '(': case ')': case ',': case ';': case ':': case '[': case ']': case '~': case '?': token = add_token(); get_special_token(token); token->kind = TOKEN_SPECIAL; if (token->special == SPECIAL_COMMENT_LINE_BEGIN) { token->special = SPECIAL_COMMENT_BEGIN; in_single_comment = true; } continue; case '#': handle_preprocessor_directive(); break; case '\n': if (in_single_comment) { token = add_token(); token->kind = TOKEN_SPECIAL; token->special = SPECIAL_COMMENT_END; in_single_comment = false; } ++current_line; break; default: break; } ++current_buffer_position; } out: add_token(); /* end marker */ return tokens; } char * read_file(const char *filename) { char *line = NULL; size_t len = 0; FILE *stream = fopen(filename, "r"); if (!stream) { fprintf(stderr, "warn: cannot read '%s'\n", filename); return NULL; } struct buf buffer; buf_init(&buffer); while ((getline(&line, &len, stream) != -1)) { buf_add(&buffer, line, strlen(line)); } free(line); fclose(stream); return buffer.buf; } static bool grep(struct token *tokens, const char *filename, const char *pattern) { bool found = false; unsigned int in_comment = 0; for (struct token *t = tokens; t->kind; t++) { if (t->kind == TOKEN_SPECIAL) { if (t->special == SPECIAL_COMMENT_BEGIN) { ++in_comment; } else if (t->special == SPECIAL_COMMENT_END) { --in_comment; } } if (in_comment) { continue; } if (t->kind == TOKEN_IDENTIFIER) { if (!pattern || !strcmp(t->name.buf, pattern)) { found = true; printf("%s:%d\t%s\n", filename, t->line, t->name.buf); } } } return found; } static void process_one_file(const char *filename) { struct token *tokens; char *buffer = read_file(filename); if (!buffer) { exit(EXIT_FAILURE); } current_line = 1; tokens = lex(buffer); free(buffer); if (!argv_tokens) { /* Dump all idents */ grep(tokens, filename, NULL); } else { for (char **p = argv_tokens; *p; p++) { found_token |= grep(tokens, filename, *p); } } } int main(int argc, char **argv) { if (argc < 2) { usage(); } for (int i = 1; i < argc; ++i) { char *arg = argv[i]; if (!strncmp(arg, "--tokens=", 9)) { argv_tokens = split(arg + 9, ','); } if (!strcmp(arg, "-")) { char *line = NULL; size_t len = 0; while ((getline(&line, &len, stdin) != -1)) { char *p = strrchr(line, '\n'); if (p) { *p = '\0'; } process_one_file(line); } free(line); break; } if (arg[0] != '-') { process_one_file(arg); break; } } if (argv_tokens) { for (char **p = argv_tokens; *p; p++) { free(*p); } free(argv_tokens); } /* return failure (1) if we have found a banned identifier */ return found_token; } ����������������������������������������������������������������������������������labwc-0.7.1/src/������������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14570443012�0013376�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/action.c����������������������������������������������������������������������������0000664�0000000�0000000�00000063403�14570443012�0015025�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <signal.h> #include <string.h> #include <strings.h> #include <unistd.h> #include <wlr/util/log.h> #include "action.h" #include "common/macros.h" #include "common/list.h" #include "common/mem.h" #include "common/parse-bool.h" #include "common/spawn.h" #include "common/string-helpers.h" #include "debug.h" #include "labwc.h" #include "menu/menu.h" #include "placement.h" #include "regions.h" #include "ssd.h" #include "view.h" #include "workspaces.h" enum action_arg_type { LAB_ACTION_ARG_STR = 0, LAB_ACTION_ARG_BOOL, LAB_ACTION_ARG_INT, LAB_ACTION_ARG_QUERY_LIST, LAB_ACTION_ARG_ACTION_LIST, }; struct action_arg { struct wl_list link; /* struct action.args */ char *key; /* May be NULL if there is just one arg */ enum action_arg_type type; }; struct action_arg_str { struct action_arg base; char *value; }; struct action_arg_bool { struct action_arg base; bool value; }; struct action_arg_int { struct action_arg base; int value; }; struct action_arg_list { struct action_arg base; struct wl_list value; }; enum action_type { ACTION_TYPE_INVALID = 0, ACTION_TYPE_NONE, ACTION_TYPE_CLOSE, ACTION_TYPE_KILL, ACTION_TYPE_DEBUG, ACTION_TYPE_EXECUTE, ACTION_TYPE_EXIT, ACTION_TYPE_MOVE_TO_EDGE, ACTION_TYPE_SNAP_TO_EDGE, ACTION_TYPE_GROW_TO_EDGE, ACTION_TYPE_SHRINK_TO_EDGE, ACTION_TYPE_NEXT_WINDOW, ACTION_TYPE_PREVIOUS_WINDOW, ACTION_TYPE_RECONFIGURE, ACTION_TYPE_SHOW_MENU, ACTION_TYPE_TOGGLE_MAXIMIZE, ACTION_TYPE_MAXIMIZE, ACTION_TYPE_TOGGLE_FULLSCREEN, ACTION_TYPE_TOGGLE_DECORATIONS, ACTION_TYPE_TOGGLE_ALWAYS_ON_TOP, ACTION_TYPE_TOGGLE_ALWAYS_ON_BOTTOM, ACTION_TYPE_TOGGLE_OMNIPRESENT, ACTION_TYPE_FOCUS, ACTION_TYPE_UNFOCUS, ACTION_TYPE_ICONIFY, ACTION_TYPE_MOVE, ACTION_TYPE_RAISE, ACTION_TYPE_LOWER, ACTION_TYPE_RESIZE, ACTION_TYPE_RESIZE_RELATIVE, ACTION_TYPE_MOVETO, ACTION_TYPE_RESIZETO, ACTION_TYPE_MOVETO_CURSOR, ACTION_TYPE_MOVE_RELATIVE, ACTION_TYPE_SEND_TO_DESKTOP, ACTION_TYPE_GO_TO_DESKTOP, ACTION_TYPE_SNAP_TO_REGION, ACTION_TYPE_TOGGLE_KEYBINDS, ACTION_TYPE_FOCUS_OUTPUT, ACTION_TYPE_MOVE_TO_OUTPUT, ACTION_TYPE_FIT_TO_OUTPUT, ACTION_TYPE_IF, ACTION_TYPE_FOR_EACH, ACTION_TYPE_VIRTUAL_OUTPUT_ADD, ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE, ACTION_TYPE_AUTO_PLACE, ACTION_TYPE_TOGGLE_TEARING, ACTION_TYPE_SHADE, ACTION_TYPE_UNSHADE, ACTION_TYPE_TOGGLE_SHADE, }; const char *action_names[] = { "INVALID", "None", "Close", "Kill", "Debug", "Execute", "Exit", "MoveToEdge", "SnapToEdge", "GrowToEdge", "ShrinkToEdge", "NextWindow", "PreviousWindow", "Reconfigure", "ShowMenu", "ToggleMaximize", "Maximize", "ToggleFullscreen", "ToggleDecorations", "ToggleAlwaysOnTop", "ToggleAlwaysOnBottom", "ToggleOmnipresent", "Focus", "Unfocus", "Iconify", "Move", "Raise", "Lower", "Resize", "ResizeRelative", "MoveTo", "ResizeTo", "MoveToCursor", "MoveRelative", "SendToDesktop", "GoToDesktop", "SnapToRegion", "ToggleKeybinds", "FocusOutput", "MoveToOutput", "FitToOutput", "If", "ForEach", "VirtualOutputAdd", "VirtualOutputRemove", "AutoPlace", "ToggleTearing", "Shade", "Unshade", "ToggleShade", NULL }; void action_arg_add_str(struct action *action, const char *key, const char *value) { assert(action); assert(key); assert(value && "Tried to add NULL action string argument"); struct action_arg_str *arg = znew(*arg); arg->base.type = LAB_ACTION_ARG_STR; arg->base.key = xstrdup(key); arg->value = xstrdup(value); wl_list_append(&action->args, &arg->base.link); } static void action_arg_add_bool(struct action *action, const char *key, bool value) { assert(action); assert(key); struct action_arg_bool *arg = znew(*arg); arg->base.type = LAB_ACTION_ARG_BOOL; arg->base.key = xstrdup(key); arg->value = value; wl_list_append(&action->args, &arg->base.link); } static void action_arg_add_int(struct action *action, const char *key, int value) { assert(action); assert(key); struct action_arg_int *arg = znew(*arg); arg->base.type = LAB_ACTION_ARG_INT; arg->base.key = xstrdup(key); arg->value = value; wl_list_append(&action->args, &arg->base.link); } static void action_arg_add_list(struct action *action, const char *key, enum action_arg_type type) { assert(action); assert(key); struct action_arg_list *arg = znew(*arg); arg->base.type = type; arg->base.key = xstrdup(key); wl_list_init(&arg->value); wl_list_append(&action->args, &arg->base.link); } void action_arg_add_querylist(struct action *action, const char *key) { action_arg_add_list(action, key, LAB_ACTION_ARG_QUERY_LIST); } void action_arg_add_actionlist(struct action *action, const char *key) { action_arg_add_list(action, key, LAB_ACTION_ARG_ACTION_LIST); } static void * action_get_arg(struct action *action, const char *key, enum action_arg_type type) { assert(action); assert(key); struct action_arg *arg; wl_list_for_each(arg, &action->args, link) { if (!strcasecmp(key, arg->key) && arg->type == type) { return arg; } } return NULL; } static const char * action_get_str(struct action *action, const char *key, const char *default_value) { struct action_arg_str *arg = action_get_arg(action, key, LAB_ACTION_ARG_STR); return arg ? arg->value : default_value; } static bool action_get_bool(struct action *action, const char *key, bool default_value) { struct action_arg_bool *arg = action_get_arg(action, key, LAB_ACTION_ARG_BOOL); return arg ? arg->value : default_value; } static int action_get_int(struct action *action, const char *key, int default_value) { struct action_arg_int *arg = action_get_arg(action, key, LAB_ACTION_ARG_INT); return arg ? arg->value : default_value; } struct wl_list * action_get_querylist(struct action *action, const char *key) { struct action_arg_list *arg = action_get_arg(action, key, LAB_ACTION_ARG_QUERY_LIST); return arg ? &arg->value : NULL; } struct wl_list * action_get_actionlist(struct action *action, const char *key) { struct action_arg_list *arg = action_get_arg(action, key, LAB_ACTION_ARG_ACTION_LIST); return arg ? &arg->value : NULL; } void action_arg_from_xml_node(struct action *action, const char *nodename, const char *content) { assert(action); char *argument = xstrdup(nodename); string_truncate_at_pattern(argument, ".action"); switch (action->type) { case ACTION_TYPE_EXECUTE: /* * <action name="Execute"> with an <execute> child is * deprecated, but we support it anyway for backward * compatibility with old openbox-menu generators */ if (!strcmp(argument, "command") || !strcmp(argument, "execute")) { action_arg_add_str(action, "command", content); goto cleanup; } break; case ACTION_TYPE_MOVE_TO_EDGE: if (!strcasecmp(argument, "snapWindows")) { action_arg_add_bool(action, argument, parse_bool(content, true)); goto cleanup; } /* Falls through */ case ACTION_TYPE_SNAP_TO_EDGE: case ACTION_TYPE_GROW_TO_EDGE: case ACTION_TYPE_SHRINK_TO_EDGE: if (!strcmp(argument, "direction")) { enum view_edge edge = view_edge_parse(content); if ((edge == VIEW_EDGE_CENTER && action->type != ACTION_TYPE_SNAP_TO_EDGE) || edge == VIEW_EDGE_INVALID) { wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", action_names[action->type], argument, content); } else { action_arg_add_int(action, argument, edge); } goto cleanup; } break; case ACTION_TYPE_SHOW_MENU: if (!strcmp(argument, "menu")) { action_arg_add_str(action, argument, content); goto cleanup; } break; case ACTION_TYPE_TOGGLE_MAXIMIZE: case ACTION_TYPE_MAXIMIZE: if (!strcmp(argument, "direction")) { enum view_axis axis = view_axis_parse(content); if (axis == VIEW_AXIS_NONE) { wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", action_names[action->type], argument, content); } else { action_arg_add_int(action, argument, axis); } goto cleanup; } break; case ACTION_TYPE_RESIZE_RELATIVE: if (!strcmp(argument, "left") || !strcmp(argument, "right") || !strcmp(argument, "top") || !strcmp(argument, "bottom")) { action_arg_add_int(action, argument, atoi(content)); goto cleanup; } break; case ACTION_TYPE_MOVETO: case ACTION_TYPE_MOVE_RELATIVE: if (!strcmp(argument, "x") || !strcmp(argument, "y")) { action_arg_add_int(action, argument, atoi(content)); goto cleanup; } break; case ACTION_TYPE_RESIZETO: if (!strcmp(argument, "width") || !strcmp(argument, "height")) { action_arg_add_int(action, argument, atoi(content)); goto cleanup; } break; case ACTION_TYPE_SEND_TO_DESKTOP: if (!strcmp(argument, "follow")) { action_arg_add_bool(action, argument, parse_bool(content, true)); goto cleanup; } /* Falls through to GoToDesktop */ case ACTION_TYPE_GO_TO_DESKTOP: if (!strcmp(argument, "to")) { action_arg_add_str(action, argument, content); goto cleanup; } if (!strcmp(argument, "wrap")) { action_arg_add_bool(action, argument, parse_bool(content, true)); goto cleanup; } break; case ACTION_TYPE_SNAP_TO_REGION: if (!strcmp(argument, "region")) { action_arg_add_str(action, argument, content); goto cleanup; } break; case ACTION_TYPE_FOCUS_OUTPUT: if (!strcmp(argument, "output")) { action_arg_add_str(action, argument, content); goto cleanup; } break; case ACTION_TYPE_MOVE_TO_OUTPUT: if (!strcmp(argument, "name")) { action_arg_add_str(action, argument, content); goto cleanup; } if (!strcmp(argument, "direction")) { enum view_edge edge = view_edge_parse(content); if (edge == VIEW_EDGE_CENTER) { wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", action_names[action->type], argument, content); } else { action_arg_add_int(action, argument, edge); } goto cleanup; } break; case ACTION_TYPE_VIRTUAL_OUTPUT_ADD: case ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE: if (!strcmp(argument, "output_name")) { action_arg_add_str(action, argument, content); goto cleanup; } break; } wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s'", action_names[action->type], argument); cleanup: free(argument); } static enum action_type action_type_from_str(const char *action_name) { for (size_t i = 1; action_names[i]; i++) { if (!strcasecmp(action_name, action_names[i])) { return i; } } wlr_log(WLR_ERROR, "Invalid action: %s", action_name); return ACTION_TYPE_INVALID; } struct action * action_create(const char *action_name) { if (!action_name) { wlr_log(WLR_ERROR, "action name not specified"); return NULL; } enum action_type action_type = action_type_from_str(action_name); if (action_type == ACTION_TYPE_NONE) { return NULL; } struct action *action = znew(*action); action->type = action_type; wl_list_init(&action->args); return action; } bool actions_contain_toggle_keybinds(struct wl_list *action_list) { struct action *action; wl_list_for_each(action, action_list, link) { if (action->type == ACTION_TYPE_TOGGLE_KEYBINDS) { return true; } } return false; } static bool action_list_is_valid(struct wl_list *actions) { assert(actions); struct action *action; wl_list_for_each(action, actions, link) { if (!action_is_valid(action)) { return false; } } return true; } /* Checks for *required* arguments */ bool action_is_valid(struct action *action) { const char *arg_name = NULL; enum action_arg_type arg_type = LAB_ACTION_ARG_STR; switch (action->type) { case ACTION_TYPE_EXECUTE: arg_name = "command"; break; case ACTION_TYPE_MOVE_TO_EDGE: case ACTION_TYPE_SNAP_TO_EDGE: case ACTION_TYPE_GROW_TO_EDGE: case ACTION_TYPE_SHRINK_TO_EDGE: arg_name = "direction"; arg_type = LAB_ACTION_ARG_INT; break; case ACTION_TYPE_SHOW_MENU: arg_name = "menu"; break; case ACTION_TYPE_GO_TO_DESKTOP: case ACTION_TYPE_SEND_TO_DESKTOP: arg_name = "to"; break; case ACTION_TYPE_SNAP_TO_REGION: arg_name = "region"; break; case ACTION_TYPE_FOCUS_OUTPUT: arg_name = "output"; break; case ACTION_TYPE_IF: case ACTION_TYPE_FOR_EACH: ; /* works around "a label can only be part of a statement" */ static const char * const branches[] = { "then", "else" }; for (size_t i = 0; i < ARRAY_SIZE(branches); i++) { struct wl_list *children = action_get_actionlist(action, branches[i]); if (children && !action_list_is_valid(children)) { wlr_log(WLR_ERROR, "Invalid action in %s '%s' branch", action_names[action->type], branches[i]); return false; } } return true; default: /* No arguments required */ return true; } if (action_get_arg(action, arg_name, arg_type)) { return true; } wlr_log(WLR_ERROR, "Missing required argument for %s: %s", action_names[action->type], arg_name); return false; } void action_free(struct action *action) { /* Free args */ struct action_arg *arg, *arg_tmp; wl_list_for_each_safe(arg, arg_tmp, &action->args, link) { wl_list_remove(&arg->link); zfree(arg->key); if (arg->type == LAB_ACTION_ARG_STR) { struct action_arg_str *str_arg = (struct action_arg_str *)arg; zfree(str_arg->value); } else if (arg->type == LAB_ACTION_ARG_ACTION_LIST) { struct action_arg_list *list_arg = (struct action_arg_list *)arg; action_list_free(&list_arg->value); } else if (arg->type == LAB_ACTION_ARG_QUERY_LIST) { struct action_arg_list *list_arg = (struct action_arg_list *)arg; struct view_query *elm, *next; wl_list_for_each_safe(elm, next, &list_arg->value, link) { view_query_free(elm); } } zfree(arg); } zfree(action); } void action_list_free(struct wl_list *action_list) { struct action *action, *action_tmp; wl_list_for_each_safe(action, action_tmp, action_list, link) { wl_list_remove(&action->link); action_free(action); } } static void show_menu(struct server *server, struct view *view, const char *menu_name) { if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH && server->input_mode != LAB_INPUT_STATE_MENU) { /* Prevent opening a menu while resizing / moving a view */ return; } bool force_menu_top_left = false; struct menu *menu = menu_get_by_id(server, menu_name); if (!menu) { return; } if (!strcasecmp(menu_name, "client-menu")) { if (!view) { return; } enum ssd_part_type type = ssd_at(view->ssd, server->scene, server->seat.cursor->x, server->seat.cursor->y); if (type == LAB_SSD_BUTTON_WINDOW_MENU) { force_menu_top_left = true; } else if (ssd_part_contains(LAB_SSD_PART_TITLEBAR, type)) { force_menu_top_left = false; } else { force_menu_top_left = true; } } int x, y; if (force_menu_top_left) { x = view->current.x; y = view->current.y; } else { x = server->seat.cursor->x; y = server->seat.cursor->y; } /* Replaced by next show_menu() or cleaned on view_destroy() */ menu->triggered_by_view = view; menu_open(menu, x, y); } static struct view * view_for_action(struct view *activator, struct server *server, struct action *action, uint32_t *resize_edges) { /* View is explicitly specified for mousebinds */ if (activator) { return activator; } /* Select view based on action type for keybinds */ switch (action->type) { case ACTION_TYPE_FOCUS: case ACTION_TYPE_MOVE: case ACTION_TYPE_RESIZE: { struct cursor_context ctx = get_cursor_context(server); if (action->type == ACTION_TYPE_RESIZE) { /* Select resize edges for the keybind case */ *resize_edges = cursor_get_resize_edges( server->seat.cursor, &ctx); } return ctx.view; } default: return server->active_view; } } static void run_if_action(struct view *view, struct server *server, struct action *action) { struct view_query *query; struct wl_list *queries, *actions; const char *branch = "then"; queries = action_get_querylist(action, "query"); if (queries) { branch = "else"; /* All queries are OR'ed */ wl_list_for_each(query, queries, link) { if (view_matches_query(view, query)) { branch = "then"; break; } } } actions = action_get_actionlist(action, branch); if (actions) { actions_run(view, server, actions, 0); } } void actions_run(struct view *activator, struct server *server, struct wl_list *actions, uint32_t resize_edges) { if (!actions) { wlr_log(WLR_ERROR, "empty actions"); return; } struct view *view; struct action *action; wl_list_for_each(action, actions, link) { wlr_log(WLR_DEBUG, "Handling action %u: %s", action->type, action_names[action->type]); /* * Refetch view because it may have been changed due to the * previous action */ view = view_for_action(activator, server, action, &resize_edges); switch (action->type) { case ACTION_TYPE_CLOSE: if (view) { view_close(view); } break; case ACTION_TYPE_KILL: if (view && view->surface) { /* Send SIGTERM to the process associated with the surface */ pid_t pid = -1; struct wl_client *client = view->surface->resource->client; wl_client_get_credentials(client, &pid, NULL, NULL); if (pid != -1) { kill(pid, SIGTERM); } } break; case ACTION_TYPE_DEBUG: debug_dump_scene(server); break; case ACTION_TYPE_EXECUTE: { struct buf cmd; buf_init(&cmd); buf_add(&cmd, action_get_str(action, "command", NULL)); buf_expand_tilde(&cmd); spawn_async_no_shell(cmd.buf); free(cmd.buf); } break; case ACTION_TYPE_EXIT: wl_display_terminate(server->wl_display); break; case ACTION_TYPE_MOVE_TO_EDGE: if (view) { /* Config parsing makes sure that direction is a valid direction */ enum view_edge edge = action_get_int(action, "direction", 0); bool snap_to_windows = action_get_bool(action, "snapWindows", true); view_move_to_edge(view, edge, snap_to_windows); } break; case ACTION_TYPE_SNAP_TO_EDGE: if (view) { /* Config parsing makes sure that direction is a valid direction */ enum view_edge edge = action_get_int(action, "direction", 0); view_snap_to_edge(view, edge, /*across_outputs*/ true, /*store_natural_geometry*/ true); } break; case ACTION_TYPE_GROW_TO_EDGE: if (view) { /* Config parsing makes sure that direction is a valid direction */ enum view_edge edge = action_get_int(action, "direction", 0); view_grow_to_edge(view, edge); } break; case ACTION_TYPE_SHRINK_TO_EDGE: if (view) { /* Config parsing makes sure that direction is a valid direction */ enum view_edge edge = action_get_int(action, "direction", 0); view_shrink_to_edge(view, edge); } break; case ACTION_TYPE_NEXT_WINDOW: server->osd_state.cycle_view = desktop_cycle_view(server, server->osd_state.cycle_view, LAB_CYCLE_DIR_FORWARD); osd_update(server); break; case ACTION_TYPE_PREVIOUS_WINDOW: server->osd_state.cycle_view = desktop_cycle_view(server, server->osd_state.cycle_view, LAB_CYCLE_DIR_BACKWARD); osd_update(server); break; case ACTION_TYPE_RECONFIGURE: kill(getpid(), SIGHUP); break; case ACTION_TYPE_SHOW_MENU: show_menu(server, view, action_get_str(action, "menu", NULL)); break; case ACTION_TYPE_TOGGLE_MAXIMIZE: if (view) { enum view_axis axis = action_get_int(action, "direction", VIEW_AXIS_BOTH); view_toggle_maximize(view, axis); } break; case ACTION_TYPE_MAXIMIZE: if (view) { enum view_axis axis = action_get_int(action, "direction", VIEW_AXIS_BOTH); view_maximize(view, axis, /*store_natural_geometry*/ true); } break; case ACTION_TYPE_TOGGLE_FULLSCREEN: if (view) { view_toggle_fullscreen(view); } break; case ACTION_TYPE_TOGGLE_DECORATIONS: if (view) { view_toggle_decorations(view); } break; case ACTION_TYPE_TOGGLE_ALWAYS_ON_TOP: if (view) { view_toggle_always_on_top(view); } break; case ACTION_TYPE_TOGGLE_ALWAYS_ON_BOTTOM: if (view) { view_toggle_always_on_bottom(view); } break; case ACTION_TYPE_TOGGLE_OMNIPRESENT: if (view) { view_toggle_visible_on_all_workspaces(view); } break; case ACTION_TYPE_FOCUS: if (view) { desktop_focus_view(view, /*raise*/ false); } break; case ACTION_TYPE_UNFOCUS: seat_focus_surface(&server->seat, NULL); break; case ACTION_TYPE_ICONIFY: if (view) { view_minimize(view, true); } break; case ACTION_TYPE_MOVE: if (view) { interactive_begin(view, LAB_INPUT_STATE_MOVE, 0); } break; case ACTION_TYPE_RAISE: if (view) { view_move_to_front(view); } break; case ACTION_TYPE_LOWER: if (view) { view_move_to_back(view); } break; case ACTION_TYPE_RESIZE: if (view) { interactive_begin(view, LAB_INPUT_STATE_RESIZE, resize_edges); } break; case ACTION_TYPE_RESIZE_RELATIVE: if (view) { int left = action_get_int(action, "left", 0); int right = action_get_int(action, "right", 0); int top = action_get_int(action, "top", 0); int bottom = action_get_int(action, "bottom", 0); view_resize_relative(view, left, right, top, bottom); } break; case ACTION_TYPE_MOVETO: if (view) { int x = action_get_int(action, "x", 0); int y = action_get_int(action, "y", 0); view_move(view, x, y); } break; case ACTION_TYPE_RESIZETO: if (view) { int width = action_get_int(action, "width", 0); int height = action_get_int(action, "height", 0); /* * To support only setting one of width/height * in <action name="ResizeTo" width="" height=""/> * we fall back to current dimension when unset. */ struct wlr_box box = { .x = view->pending.x, .y = view->pending.y, .width = width ? : view->pending.width, .height = height ? : view->pending.height, }; view_set_shade(view, false); view_move_resize(view, box); } break; case ACTION_TYPE_MOVE_RELATIVE: if (view) { int x = action_get_int(action, "x", 0); int y = action_get_int(action, "y", 0); view_move_relative(view, x, y); } break; case ACTION_TYPE_MOVETO_CURSOR: if (view) { view_move_to_cursor(view); } break; case ACTION_TYPE_SEND_TO_DESKTOP: if (!view) { break; } /* Falls through to GoToDesktop */ case ACTION_TYPE_GO_TO_DESKTOP: { bool follow = true; bool wrap = action_get_bool(action, "wrap", true); const char *to = action_get_str(action, "to", NULL); /* * `to` is always != NULL here because otherwise we would have * removed the action during the initial parsing step as it is * a required argument for both SendToDesktop and GoToDesktop. */ struct workspace *target = workspaces_find( server->workspace_current, to, wrap); if (!target) { break; } if (action->type == ACTION_TYPE_SEND_TO_DESKTOP) { view_move_to_workspace(view, target); follow = action_get_bool(action, "follow", true); } if (follow) { workspaces_switch_to(target, /*update_focus*/ true); } } break; case ACTION_TYPE_MOVE_TO_OUTPUT: if (!view) { break; } const char *name = action_get_str(action, "name", NULL); struct output *target = NULL; if (name) { target = output_from_name(view->server, name); } else { /* Config parsing makes sure that direction is a valid direction */ enum view_edge edge = action_get_int(action, "direction", 0); target = view_get_adjacent_output(view, edge); } if (!target) { wlr_log(WLR_ERROR, "Invalid output."); break; } view_move_to_output(view, target); break; case ACTION_TYPE_FIT_TO_OUTPUT: if (!view) { break; } view_constrain_size_to_that_of_usable_area(view); break; case ACTION_TYPE_SNAP_TO_REGION: if (!view) { break; } struct output *output = view->output; if (!output) { break; } const char *region_name = action_get_str(action, "region", NULL); struct region *region = regions_from_name(region_name, output); if (region) { view_snap_to_region(view, region, /*store_natural_geometry*/ true); } else { wlr_log(WLR_ERROR, "Invalid SnapToRegion id: '%s'", region_name); } break; case ACTION_TYPE_TOGGLE_KEYBINDS: if (view) { view_toggle_keybinds(view); } break; case ACTION_TYPE_FOCUS_OUTPUT: { const char *output_name = action_get_str(action, "output", NULL); desktop_focus_output(output_from_name(server, output_name)); } break; case ACTION_TYPE_IF: if (view) { run_if_action(view, server, action); } break; case ACTION_TYPE_FOR_EACH: { struct wl_array views; struct view **item; wl_array_init(&views); view_array_append(server, &views, LAB_VIEW_CRITERIA_NONE); wl_array_for_each(item, &views) { run_if_action(*item, server, action); } wl_array_release(&views); } break; case ACTION_TYPE_VIRTUAL_OUTPUT_ADD: { const char *output_name = action_get_str(action, "output_name", NULL); output_add_virtual(server, output_name); } break; case ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE: { const char *output_name = action_get_str(action, "output_name", NULL); output_remove_virtual(server, output_name); } break; case ACTION_TYPE_AUTO_PLACE: if (view) { struct wlr_box geometry = view->pending; if (placement_find_best(view, &geometry)) { view_move(view, geometry.x, geometry.y); } } break; case ACTION_TYPE_TOGGLE_TEARING: if (view) { view->tearing_hint = !view->tearing_hint; wlr_log(WLR_DEBUG, "tearing %sabled", view->tearing_hint ? "en" : "dis"); } break; case ACTION_TYPE_TOGGLE_SHADE: if (view) { view_set_shade(view, !view->shaded); } break; case ACTION_TYPE_SHADE: if (view) { view_set_shade(view, true); } break; case ACTION_TYPE_UNSHADE: if (view) { view_set_shade(view, false); } break; case ACTION_TYPE_INVALID: wlr_log(WLR_ERROR, "Not executing unknown action"); break; default: /* * If we get here it must be a BUG caused most likely by * action_names and action_type being out of sync or by * adding a new action without installing a handler here. */ wlr_log(WLR_ERROR, "Not executing invalid action (%u)" " This is a BUG. Please report.", action->type); } } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/buffer.c����������������������������������������������������������������������������0000664�0000000�0000000�00000010376�14570443012�0015022�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * Based on wlroots/types/wlr_buffer.c * * Copyright (c) 2017, 2018 Drew DeVault * Copyright (c) 2018-2021 Simon Ser, Simon Zeni * 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. */ #include <assert.h> #include <stdlib.h> #include <drm_fourcc.h> #include <wlr/interfaces/wlr_buffer.h> #include "buffer.h" #include "common/mem.h" static const struct wlr_buffer_impl data_buffer_impl; static struct lab_data_buffer * data_buffer_from_buffer(struct wlr_buffer *buffer) { assert(buffer->impl == &data_buffer_impl); return (struct lab_data_buffer *)buffer; } static void data_buffer_destroy(struct wlr_buffer *wlr_buffer) { struct lab_data_buffer *buffer = data_buffer_from_buffer(wlr_buffer); if (!buffer->free_on_destroy) { free(buffer); return; } if (buffer->cairo) { cairo_surface_t *surf = cairo_get_target(buffer->cairo); cairo_destroy(buffer->cairo); cairo_surface_destroy(surf); } else if (buffer->data) { free(buffer->data); buffer->data = NULL; } free(buffer); } static bool data_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, uint32_t flags, void **data, uint32_t *format, size_t *stride) { struct lab_data_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); assert(buffer->data); *data = (void *)buffer->data; *format = buffer->format; *stride = buffer->stride; return true; } static void data_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { /* noop */ } static const struct wlr_buffer_impl data_buffer_impl = { .destroy = data_buffer_destroy, .begin_data_ptr_access = data_buffer_begin_data_ptr_access, .end_data_ptr_access = data_buffer_end_data_ptr_access, }; struct lab_data_buffer * buffer_create_cairo(uint32_t width, uint32_t height, float scale, bool free_on_destroy) { struct lab_data_buffer *buffer = znew(*buffer); buffer->unscaled_width = width; buffer->unscaled_height = height; width *= scale; height *= scale; /* Allocate the buffer with the scaled size */ wlr_buffer_init(&buffer->base, &data_buffer_impl, width, height); cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); /** * Tell cairo about the device scale so we can keep drawing in unscaled * coordinate space. Pango will automatically use the cairo scale attribute * as well when creating text on this surface. * * For a more complete explanation see PR #389 */ cairo_surface_set_device_scale(surf, scale, scale); buffer->cairo = cairo_create(surf); buffer->data = cairo_image_surface_get_data(surf); buffer->format = DRM_FORMAT_ARGB8888; buffer->stride = cairo_image_surface_get_stride(surf); buffer->free_on_destroy = free_on_destroy; if (!buffer->data) { cairo_destroy(buffer->cairo); cairo_surface_destroy(surf); free(buffer); buffer = NULL; } return buffer; } struct lab_data_buffer * buffer_create_wrap(void *pixel_data, uint32_t width, uint32_t height, uint32_t stride, bool free_on_destroy) { struct lab_data_buffer *buffer = znew(*buffer); wlr_buffer_init(&buffer->base, &data_buffer_impl, width, height); buffer->unscaled_width = width; buffer->unscaled_height = height; buffer->data = pixel_data; buffer->format = DRM_FORMAT_ARGB8888; buffer->stride = stride; buffer->free_on_destroy = free_on_destroy; return buffer; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/button/�����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14570443012�0014711�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/button/button-png.c�����������������������������������������������������������������0000664�0000000�0000000�00000003540�14570443012�0017154�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) Johan Malm 2023 */ #define _POSIX_C_SOURCE 200809L #include <cairo.h> #include <png.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <wlr/util/log.h> #include "buffer.h" #include "button/button-png.h" #include "button/common.h" #include "common/string-helpers.h" #include "labwc.h" /* * cairo_image_surface_create_from_png() does not gracefully handle non-png * files, so we verify the header before trying to read the rest of the file. */ #define PNG_BYTES_TO_CHECK (4) static bool ispng(const char *filename) { unsigned char header[PNG_BYTES_TO_CHECK]; FILE *fp = fopen(filename, "rb"); if (!fp) { return false; } if (fread(header, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK) { fclose(fp); return false; } if (png_sig_cmp(header, (png_size_t)0, PNG_BYTES_TO_CHECK)) { wlr_log(WLR_ERROR, "file '%s' is not a recognised png file", filename); fclose(fp); return false; } fclose(fp); return true; } #undef PNG_BYTES_TO_CHECK void button_png_load(const char *button_name, struct lab_data_buffer **buffer) { if (*buffer) { wlr_buffer_drop(&(*buffer)->base); *buffer = NULL; } if (string_null_or_empty(button_name)) { return; } char path[4096] = { 0 }; button_filename(button_name, path, sizeof(path)); if (!ispng(path)) { return; } cairo_surface_t *image = cairo_image_surface_create_from_png(path); if (cairo_surface_status(image)) { wlr_log(WLR_ERROR, "error reading png button '%s'", path); cairo_surface_destroy(image); return; } cairo_surface_flush(image); double w = cairo_image_surface_get_width(image); double h = cairo_image_surface_get_height(image); *buffer = buffer_create_cairo((int)w, (int)h, 1.0, true); cairo_t *cairo = (*buffer)->cairo; cairo_set_source_surface(cairo, image, 0, 0); cairo_paint_with_alpha(cairo, 1.0); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/button/button-svg.c�����������������������������������������������������������������0000664�0000000�0000000�00000003575�14570443012�0017177�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) Johan Malm 2023 */ #define _POSIX_C_SOURCE 200809L #include <cairo.h> #include <librsvg/rsvg.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <wlr/util/log.h> #include "buffer.h" #include "button/button-svg.h" #include "button/common.h" #include "common/string-helpers.h" #include "labwc.h" void button_svg_load(const char *button_name, struct lab_data_buffer **buffer, int size) { if (*buffer) { wlr_buffer_drop(&(*buffer)->base); *buffer = NULL; } if (string_null_or_empty(button_name)) { return; } char filename[4096] = { 0 }; button_filename(button_name, filename, sizeof(filename)); GError *err = NULL; RsvgRectangle viewport = { .width = size, .height = size }; RsvgHandle *svg = rsvg_handle_new_from_file(filename, &err); if (err) { wlr_log(WLR_DEBUG, "error reading svg %s-%s\n", filename, err->message); g_error_free(err); /* * rsvg_handle_new_from_file() returns NULL if an error occurs, * so there is no need to free svg here. */ return; } cairo_surface_t *image = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size); cairo_t *cr = cairo_create(image); rsvg_handle_render_document(svg, cr, &viewport, &err); if (err) { wlr_log(WLR_ERROR, "error rendering svg %s-%s\n", filename, err->message); g_error_free(err); goto error; } if (cairo_surface_status(image)) { wlr_log(WLR_ERROR, "error reading svg button '%s'", filename); goto error; } cairo_surface_flush(image); double w = cairo_image_surface_get_width(image); double h = cairo_image_surface_get_height(image); *buffer = buffer_create_cairo((int)w, (int)h, 1.0, /* free_on_destroy */ true); cairo_t *cairo = (*buffer)->cairo; cairo_set_source_surface(cairo, image, 0, 0); cairo_paint_with_alpha(cairo, 1.0); error: cairo_destroy(cr); cairo_surface_destroy(image); g_object_unref(svg); } �����������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/button/button-xbm.c�����������������������������������������������������������������0000664�0000000�0000000�00000014270�14570443012�0017160�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * Convert xbm file to buffer with cairo surface * * Copyright Johan Malm 2020-2023 */ #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <drm_fourcc.h> #include "button/button-xbm.h" #include "button/common.h" #include "common/grab-file.h" #include "common/mem.h" #include "common/string-helpers.h" #include "buffer.h" enum token_type { TOKEN_NONE = 0, TOKEN_IDENT, TOKEN_INT, TOKEN_SPECIAL, TOKEN_OTHER, }; #define MAX_TOKEN_SIZE (256) struct token { char name[MAX_TOKEN_SIZE]; int value; size_t pos; enum token_type type; }; struct pixmap { uint32_t *data; int width; int height; }; static uint32_t color; static char *current_buffer_position; static struct token *tokens; static int nr_tokens, alloc_tokens; static void add_token(enum token_type token_type) { if (nr_tokens == alloc_tokens) { alloc_tokens = (alloc_tokens + 16) * 2; tokens = xrealloc(tokens, alloc_tokens * sizeof(struct token)); } struct token *token = tokens + nr_tokens; memset(token, 0, sizeof(*token)); nr_tokens++; token->type = token_type; } static void get_identifier_token(void) { struct token *token = tokens + nr_tokens - 1; token->name[token->pos] = current_buffer_position[0]; token->pos++; if (token->pos == MAX_TOKEN_SIZE - 1) { return; } current_buffer_position++; switch (current_buffer_position[0]) { case '\0': return; case 'a' ... 'z': case 'A' ... 'Z': case '0' ... '9': case '_': case '#': get_identifier_token(); break; default: break; } } static void get_number_token(void) { struct token *token = tokens + nr_tokens - 1; token->name[token->pos] = current_buffer_position[0]; token->pos++; if (token->pos == MAX_TOKEN_SIZE - 1) { return; } current_buffer_position++; switch (current_buffer_position[0]) { case '\0': return; case '0' ... '9': case 'a' ... 'f': case 'A' ... 'F': case 'x': get_number_token(); break; default: break; } } static void get_special_char_token(void) { struct token *token = tokens + nr_tokens - 1; token->name[0] = current_buffer_position[0]; current_buffer_position++; } /** * tokenize_xbm - tokenize xbm file * @buffer: buffer containing xbm file * return token vector */ static struct token * tokenize_xbm(char *buffer) { tokens = NULL; nr_tokens = 0; alloc_tokens = 0; current_buffer_position = buffer; for (;;) { switch (current_buffer_position[0]) { case '\0': goto out; case 'a' ... 'z': case 'A' ... 'Z': case '_': case '#': add_token(TOKEN_IDENT); get_identifier_token(); continue; case '0' ... '9': add_token(TOKEN_INT); get_number_token(); struct token *token = tokens + nr_tokens - 1; token->value = (int)strtol(token->name, NULL, 0); continue; case '{': add_token(TOKEN_SPECIAL); get_special_char_token(); continue; default: break; } ++current_buffer_position; } out: add_token(TOKEN_NONE); /* vector end marker */ return tokens; } static uint32_t argb32(float *rgba) { uint32_t r[4] = { 0 }; for (int i = 0; i < 4; i++) { r[i] = rgba[i] * 255; } return ((r[3] & 0xff) << 24) | ((r[0] & 0xff) << 16) | ((r[1] & 0xff) << 8) | (r[2] & 0xff); } static void process_bytes(struct pixmap *pixmap, struct token *tokens) { pixmap->data = znew_n(uint32_t, pixmap->width * pixmap->height); struct token *t = tokens; for (int row = 0; row < pixmap->height; row++) { int byte = 1; for (int col = 0; col < pixmap->width; col++) { if (col == byte * 8) { ++byte; ++t; } if (!t->type) { return; } if (t->type != TOKEN_INT) { return; } int bit = 1 << (col % 8); if (t->value & bit) { pixmap->data[row * pixmap->width + col] = color; } } ++t; } } /** * parse_xbm_tokens - parse xbm tokens and create pixmap * @tokens: token vector */ static struct pixmap parse_xbm_tokens(struct token *tokens) { struct pixmap pixmap = { 0 }; for (struct token *t = tokens; t->type; t++) { if (pixmap.width && pixmap.height) { if (t->type != TOKEN_INT) { continue; } process_bytes(&pixmap, t); goto out; } if (strstr(t->name, "width")) { pixmap.width = atoi((++t)->name); } else if (strstr(t->name, "height")) { pixmap.height = atoi((++t)->name); } } out: return pixmap; } /* * Openbox built-in icons are not bigger than 8x8, so have only written this * function to cope wit that max size */ #define LABWC_BUILTIN_ICON_MAX_SIZE (8) /** * parse_xbm_builtin - parse builtin xbm button and create pixmap * @button: button byte array (xbm format) */ static struct pixmap parse_xbm_builtin(const char *button, int size) { struct pixmap pixmap = { 0 }; assert(size <= LABWC_BUILTIN_ICON_MAX_SIZE); pixmap.width = size; pixmap.height = size; struct token t[LABWC_BUILTIN_ICON_MAX_SIZE + 1]; for (int i = 0; i < size; i++) { t[i].value = button[i]; t[i].type = TOKEN_INT; } t[size].type = 0; process_bytes(&pixmap, t); return pixmap; } void button_xbm_from_bitmap(const char *bitmap, struct lab_data_buffer **buffer, float *rgba) { struct pixmap pixmap = {0}; if (*buffer) { wlr_buffer_drop(&(*buffer)->base); *buffer = NULL; } color = argb32(rgba); pixmap = parse_xbm_builtin(bitmap, 6); *buffer = buffer_create_wrap(pixmap.data, pixmap.width, pixmap.height, pixmap.width * 4, /* free_on_destroy */ true); } void button_xbm_load(const char *button_name, struct lab_data_buffer **buffer, float *rgba) { struct pixmap pixmap = {0}; if (*buffer) { wlr_buffer_drop(&(*buffer)->base); *buffer = NULL; } if (string_null_or_empty(button_name)) { return; } color = argb32(rgba); /* Read file into memory as it's easier to tokenize that way */ char filename[4096] = { 0 }; button_filename(button_name, filename, sizeof(filename)); char *token_buffer = grab_file(filename); if (token_buffer) { struct token *tokens = tokenize_xbm(token_buffer); free(token_buffer); pixmap = parse_xbm_tokens(tokens); if (tokens) { free(tokens); } } if (!pixmap.data) { return; } /* Create buffer with free_on_destroy being true */ if (pixmap.data) { *buffer = buffer_create_wrap(pixmap.data, pixmap.width, pixmap.height, pixmap.width * 4, true); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/button/common.c���������������������������������������������������������������������0000664�0000000�0000000�00000001140�14570443012�0016341�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <stdio.h> #include <unistd.h> #include "button/common.h" #include "common/dir.h" #include "config/rcxml.h" #include "labwc.h" void button_filename(const char *name, char *buf, size_t len) { struct wl_list paths; paths_theme_create(&paths, rc.theme_name, name); /* * You can't really merge buttons, so let's just iterate forwards * and stop on the first hit */ struct path *path; wl_list_for_each(path, &paths, link) { if (access(path->string, R_OK) == 0) { snprintf(buf, len, "%s", path->string); break; } } paths_destroy(&paths); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/button/meson.build������������������������������������������������������������������0000664�0000000�0000000�00000000223�14570443012�0017050�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc_sources += files( 'button-png.c', 'button-xbm.c', 'common.c', ) if have_rsvg labwc_sources += files( 'button-svg.c', ) endif �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/�����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14570443012�0014666�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/buf.c������������������������������������������������������������������������0000664�0000000�0000000�00000004110�14570443012�0015602�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <ctype.h> #include <stdbool.h> #include "common/buf.h" #include "common/mem.h" static void buf_add_one_char(struct buf *s, char ch) { if (s->alloc <= s->len + 1) { s->alloc = s->alloc * 3 / 2 + 16; s->buf = xrealloc(s->buf, s->alloc); } s->buf[s->len++] = ch; s->buf[s->len] = '\0'; } void buf_expand_tilde(struct buf *s) { struct buf new; buf_init(&new); for (int i = 0 ; i < s->len ; i++) { if (s->buf[i] == '~') { buf_add(&new, getenv("HOME")); } else { buf_add_one_char(&new, s->buf[i]); } } free(s->buf); s->buf = new.buf; s->len = new.len; s->alloc = new.alloc; } static void strip_curly_braces(char *s) { size_t len = strlen(s); if (s[0] != '{' || s[len - 1] != '}') { return; } len -= 2; memmove(s, s + 1, len); s[len] = 0; } static bool isvalid(char p) { return isalnum(p) || p == '_' || p == '{' || p == '}'; } void buf_expand_shell_variables(struct buf *s) { struct buf new; struct buf environment_variable; buf_init(&new); buf_init(&environment_variable); for (int i = 0 ; i < s->len ; i++) { if (s->buf[i] == '$' && isvalid(s->buf[i+1])) { /* expand environment variable */ environment_variable.len = 0; buf_add(&environment_variable, s->buf + i + 1); char *p = environment_variable.buf; while (isvalid(*p)) { ++p; } *p = '\0'; i += strlen(environment_variable.buf); strip_curly_braces(environment_variable.buf); p = getenv(environment_variable.buf); if (p) { buf_add(&new, p); } } else { buf_add_one_char(&new, s->buf[i]); } } free(environment_variable.buf); free(s->buf); s->buf = new.buf; s->len = new.len; s->alloc = new.alloc; } void buf_init(struct buf *s) { s->alloc = 256; s->buf = xmalloc(s->alloc); s->buf[0] = '\0'; s->len = 0; } void buf_add(struct buf *s, const char *data) { if (!data || data[0] == '\0') { return; } int len = strlen(data); if (s->alloc <= s->len + len + 1) { s->alloc = s->alloc + len; s->buf = xrealloc(s->buf, s->alloc); } memcpy(s->buf + s->len, data, len); s->len += len; s->buf[s->len] = 0; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/dir.c������������������������������������������������������������������������0000664�0000000�0000000�00000010320�14570443012�0015604�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * Find the configuration and theme directories * * Copyright Johan Malm 2020 */ #include <assert.h> #include <glib.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include "common/dir.h" #include "common/buf.h" #include "common/list.h" #include "common/mem.h" #include "common/string-helpers.h" #include "labwc.h" struct dir { const char *prefix; const char *default_prefix; const char *path; }; static struct dir config_dirs[] = { { .prefix = "XDG_CONFIG_HOME", .default_prefix = "$HOME/.config", .path = "labwc" }, { .prefix = "XDG_CONFIG_DIRS", .default_prefix = "/etc/xdg", .path = "labwc", }, { .path = NULL, } }; static struct dir theme_dirs[] = { { .prefix = "XDG_DATA_HOME", .default_prefix = "$HOME/.local/share", .path = "themes", }, { .prefix = "HOME", .path = ".themes", }, { .prefix = "XDG_DATA_DIRS", .default_prefix = "/usr/share:/usr/local/share:/opt/share", .path = "themes", }, { .path = NULL, } }; struct ctx { void (*build_path_fn)(struct ctx *ctx, char *prefix, const char *path); const char *filename; char *buf; size_t len; struct dir *dirs; const char *theme_name; struct wl_list *list; }; struct wl_list *paths_get_prev(struct wl_list *elm) { return elm->prev; } struct wl_list *paths_get_next(struct wl_list *elm) { return elm->next; } static void build_config_path(struct ctx *ctx, char *prefix, const char *path) { assert(prefix); snprintf(ctx->buf, ctx->len, "%s/%s/%s", prefix, path, ctx->filename); } static void build_theme_path(struct ctx *ctx, char *prefix, const char *path) { assert(prefix); snprintf(ctx->buf, ctx->len, "%s/%s/%s/openbox-3/%s", prefix, path, ctx->theme_name, ctx->filename); } static void find_dir(struct ctx *ctx) { char *debug = getenv("LABWC_DEBUG_DIR_CONFIG_AND_THEME"); for (int i = 0; ctx->dirs[i].path; i++) { struct dir d = ctx->dirs[i]; struct buf prefix; buf_init(&prefix); /* * Replace (rather than augment) $HOME/.config with * $XDG_CONFIG_HOME if defined, and so on for the other * XDG Base Directories. */ char *pfxenv = getenv(d.prefix); buf_add(&prefix, pfxenv ? pfxenv : d.default_prefix); if (!prefix.len) { free(prefix.buf); continue; } /* Handle .default_prefix shell variables such as $HOME */ buf_expand_shell_variables(&prefix); /* * Respect that $XDG_DATA_DIRS can contain multiple colon * separated paths and that we have structured the * .default_prefix in the same way. */ gchar * *prefixes; prefixes = g_strsplit(prefix.buf, ":", -1); for (gchar * *p = prefixes; *p; p++) { ctx->build_path_fn(ctx, *p, d.path); if (debug) { fprintf(stderr, "%s\n", ctx->buf); } /* * TODO: We could stat() and continue here if we really * wanted to only respect only the first hit, but feels * like it is probably overkill. */ struct path *path = znew(*path); path->string = xstrdup(ctx->buf); wl_list_append(ctx->list, &path->link); } g_strfreev(prefixes); free(prefix.buf); } } void paths_config_create(struct wl_list *paths, const char *filename) { char buf[4096] = { 0 }; wl_list_init(paths); /* * If user provided a config directory with the -C command line option, * then that trumps everything else and we do not create the * XDG-Base-Dir list. */ if (rc.config_dir) { struct path *path = znew(*path); path->string = strdup_printf("%s/%s", rc.config_dir, filename); wl_list_append(paths, &path->link); return; } struct ctx ctx = { .build_path_fn = build_config_path, .filename = filename, .buf = buf, .len = sizeof(buf), .dirs = config_dirs, .list = paths, }; find_dir(&ctx); } void paths_theme_create(struct wl_list *paths, const char *theme_name, const char *filename) { static char buf[4096] = { 0 }; wl_list_init(paths); struct ctx ctx = { .build_path_fn = build_theme_path, .filename = filename, .buf = buf, .len = sizeof(buf), .dirs = theme_dirs, .theme_name = theme_name, .list = paths, }; find_dir(&ctx); } void paths_destroy(struct wl_list *paths) { struct path *path, *next; wl_list_for_each_safe(path, next, paths, link) { free(path->string); wl_list_remove(&path->link); free(path); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/fd_util.c��������������������������������������������������������������������0000664�0000000�0000000�00000001776�14570443012�0016473�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <sys/resource.h> #include <wlr/util/log.h> #include "common/fd_util.h" static struct rlimit original_nofile_rlimit = {0}; void increase_nofile_limit(void) { if (getrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { wlr_log_errno(WLR_ERROR, "Failed to bump max open files limit: getrlimit(NOFILE) failed"); return; } struct rlimit new_rlimit = original_nofile_rlimit; new_rlimit.rlim_cur = new_rlimit.rlim_max; if (setrlimit(RLIMIT_NOFILE, &new_rlimit) != 0) { wlr_log_errno(WLR_ERROR, "Failed to bump max open files limit: setrlimit(NOFILE) failed"); wlr_log(WLR_INFO, "Running with %d max open files", (int)original_nofile_rlimit.rlim_cur); } } void restore_nofile_limit(void) { if (original_nofile_rlimit.rlim_cur == 0) { return; } if (setrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { wlr_log_errno(WLR_ERROR, "Failed to restore max open files limit: setrlimit(NOFILE) failed"); } } ��labwc-0.7.1/src/common/file-helpers.c���������������������������������������������������������������0000664�0000000�0000000�00000000275�14570443012�0017415�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <sys/stat.h> #include "common/file-helpers.h" bool file_exists(const char *filename) { struct stat st; return (!stat(filename, &st)); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/font.c�����������������������������������������������������������������������0000664�0000000�0000000�00000007656�14570443012�0016016�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <cairo.h> #include <drm_fourcc.h> #include <pango/pangocairo.h> #include <wlr/render/wlr_renderer.h> #include <wlr/util/box.h> #include <wlr/util/log.h> #include "common/font.h" #include "common/graphic-helpers.h" #include "common/string-helpers.h" #include "labwc.h" #include "buffer.h" PangoFontDescription * font_to_pango_desc(struct font *font) { PangoFontDescription *desc = pango_font_description_new(); pango_font_description_set_family(desc, font->name); pango_font_description_set_size(desc, font->size * PANGO_SCALE); if (font->slant == FONT_SLANT_ITALIC) { pango_font_description_set_style(desc, PANGO_STYLE_ITALIC); } if (font->weight == FONT_WEIGHT_BOLD) { pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD); } return desc; } static PangoRectangle font_extents(struct font *font, const char *string) { PangoRectangle rect = { 0 }; if (!string) { return rect; } cairo_surface_t *surface; cairo_t *c; PangoLayout *layout; surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); c = cairo_create(surface); layout = pango_cairo_create_layout(c); PangoFontDescription *desc = font_to_pango_desc(font); pango_layout_set_font_description(layout, desc); pango_layout_set_text(layout, string, -1); pango_layout_set_single_paragraph_mode(layout, TRUE); pango_layout_set_width(layout, -1); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_MIDDLE); pango_layout_get_extents(layout, NULL, &rect); pango_extents_to_pixels(&rect, NULL); /* we put a 2 px edge on each side - because Openbox does it :) */ /* TODO: remove the 4 pixel addition and always do the padding by the caller */ rect.width += 4; cairo_destroy(c); cairo_surface_destroy(surface); pango_font_description_free(desc); g_object_unref(layout); return rect; } int font_height(struct font *font) { PangoRectangle rectangle = font_extents(font, "abcdefg"); return rectangle.height; } int font_width(struct font *font, const char *string) { PangoRectangle rectangle = font_extents(font, string); return rectangle.width; } void font_buffer_create(struct lab_data_buffer **buffer, int max_width, const char *text, struct font *font, float *color, const char *arrow, double scale) { /* Allow a minimum of one pixel each for text and arrow */ if (max_width < 2) { max_width = 2; } if (string_null_or_empty(text)) { return; } PangoRectangle text_extents = font_extents(font, text); PangoRectangle arrow_extents = font_extents(font, arrow); if (arrow) { if (arrow_extents.width >= max_width - 1) { /* It would be weird to get here, but just in case */ arrow_extents.width = max_width - 1; text_extents.width = 1; } else { text_extents.width = max_width - arrow_extents.width; } } else if (text_extents.width > max_width) { text_extents.width = max_width; } *buffer = buffer_create_cairo(text_extents.width + arrow_extents.width, text_extents.height, scale, true); if (!*buffer) { wlr_log(WLR_ERROR, "Failed to create font buffer"); return; } cairo_t *cairo = (*buffer)->cairo; cairo_surface_t *surf = cairo_get_target(cairo); set_cairo_color(cairo, color); cairo_move_to(cairo, 0, 0); PangoLayout *layout = pango_cairo_create_layout(cairo); pango_layout_set_width(layout, text_extents.width * PANGO_SCALE); pango_layout_set_text(layout, text, -1); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); PangoFontDescription *desc = font_to_pango_desc(font); pango_layout_set_font_description(layout, desc); pango_font_description_free(desc); pango_cairo_update_layout(cairo, layout); pango_cairo_show_layout(cairo, layout); if (arrow) { cairo_move_to(cairo, text_extents.width, 0); pango_layout_set_width(layout, arrow_extents.width * PANGO_SCALE); pango_layout_set_text(layout, arrow, -1); pango_cairo_show_layout(cairo, layout); } g_object_unref(layout); cairo_surface_flush(surf); } void font_finish(void) { pango_cairo_font_map_set_default(NULL); } ����������������������������������������������������������������������������������labwc-0.7.1/src/common/grab-file.c������������������������������������������������������������������0000664�0000000�0000000�00000001122�14570443012�0016656�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * Read file into memory * * Copyright Johan Malm 2020 */ #define _POSIX_C_SOURCE 200809L #include "common/grab-file.h" #include "common/buf.h" #include <stdio.h> char * grab_file(const char *filename) { char *line = NULL; size_t len = 0; FILE *stream = fopen(filename, "r"); if (!stream) { return NULL; } struct buf buffer; buf_init(&buffer); while ((getline(&line, &len, stream) != -1)) { char *p = strrchr(line, '\n'); if (p) { *p = '\0'; } buf_add(&buffer, line); } free(line); fclose(stream); return buffer.buf; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/graphic-helpers.c������������������������������������������������������������0000664�0000000�0000000�00000006660�14570443012�0020117�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <cairo.h> #include <stdlib.h> #include <string.h> #include <wlr/types/wlr_scene.h> #include <wlr/util/box.h> #include "buffer.h" #include "common/graphic-helpers.h" #include "common/mem.h" static void multi_rect_destroy_notify(struct wl_listener *listener, void *data) { struct multi_rect *rect = wl_container_of(listener, rect, destroy); free(rect); } struct multi_rect * multi_rect_create(struct wlr_scene_tree *parent, float *colors[3], int line_width) { struct multi_rect *rect = znew(*rect); rect->line_width = line_width; rect->tree = wlr_scene_tree_create(parent); rect->destroy.notify = multi_rect_destroy_notify; wl_signal_add(&rect->tree->node.events.destroy, &rect->destroy); for (size_t i = 0; i < 3; i++) { rect->top[i] = wlr_scene_rect_create(rect->tree, 0, 0, colors[i]); rect->right[i] = wlr_scene_rect_create(rect->tree, 0, 0, colors[i]); rect->bottom[i] = wlr_scene_rect_create(rect->tree, 0, 0, colors[i]); rect->left[i] = wlr_scene_rect_create(rect->tree, 0, 0, colors[i]); wlr_scene_node_set_position(&rect->top[i]->node, i * line_width, i * line_width); wlr_scene_node_set_position(&rect->left[i]->node, i * line_width, i * line_width); } return rect; } void multi_rect_set_size(struct multi_rect *rect, int width, int height) { assert(rect); int line_width = rect->line_width; for (size_t i = 0; i < 3; i++) { /* Reposition, top and left don't ever change */ wlr_scene_node_set_position(&rect->right[i]->node, width - (i + 1) * line_width, i * line_width); wlr_scene_node_set_position(&rect->bottom[i]->node, i * line_width, height - (i + 1) * line_width); /* Update sizes */ wlr_scene_rect_set_size(rect->top[i], width - i * line_width * 2, line_width); wlr_scene_rect_set_size(rect->bottom[i], width - i * line_width * 2, line_width); wlr_scene_rect_set_size(rect->left[i], line_width, height - i * line_width * 2); wlr_scene_rect_set_size(rect->right[i], line_width, height - i * line_width * 2); } } /* Draws a border with a specified line width */ void draw_cairo_border(cairo_t *cairo, struct wlr_fbox fbox, double line_width) { cairo_save(cairo); /* The anchor point of a line is in the center */ fbox.x += line_width / 2.0; fbox.y += line_width / 2.0; fbox.width -= line_width; fbox.height -= line_width; cairo_set_line_width(cairo, line_width); cairo_rectangle(cairo, fbox.x, fbox.y, fbox.width, fbox.height); cairo_stroke(cairo); cairo_restore(cairo); } /* Sets the cairo color. Splits the single color channels */ void set_cairo_color(cairo_t *cairo, float *c) { cairo_set_source_rgba(cairo, c[0], c[1], c[2], c[3]); } struct surface_context get_cairo_surface_from_lab_data_buffer(struct lab_data_buffer *buffer) { /* Handle CAIRO_FORMAT_ARGB32 buffers */ if (buffer->cairo) { return (struct surface_context){ .is_duplicate = false, .surface = cairo_get_target(buffer->cairo), }; } /* Handle DRM_FORMAT_ARGB8888 buffers */ int w = buffer->unscaled_width; int h = buffer->unscaled_height; cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); if (!surface) { return (struct surface_context){0}; } unsigned char *data = cairo_image_surface_get_data(surface); cairo_surface_flush(surface); memcpy(data, buffer->data, h * buffer->stride); cairo_surface_mark_dirty(surface); return (struct surface_context){ .is_duplicate = true, .surface = surface, }; } ��������������������������������������������������������������������������������labwc-0.7.1/src/common/match.c����������������������������������������������������������������������0000664�0000000�0000000�00000000314�14570443012�0016124�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <fnmatch.h> #include "common/match.h" bool match_glob(const char *pattern, const char *string) { return fnmatch(pattern, string, FNM_CASEFOLD) == 0; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/mem.c������������������������������������������������������������������������0000664�0000000�0000000�00000001225�14570443012�0015610�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <stdio.h> #include <string.h> #include "common/mem.h" static void die_if_null(void *ptr) { if (!ptr) { perror("Failed to allocate memory"); exit(EXIT_FAILURE); } } void * xzalloc(size_t size) { if (!size) { return NULL; } void *ptr = calloc(1, size); die_if_null(ptr); return ptr; } void * xrealloc(void *ptr, size_t size) { if (!size) { free(ptr); return NULL; } ptr = realloc(ptr, size); die_if_null(ptr); return ptr; } char * xstrdup(const char *str) { assert(str); char *copy = strdup(str); die_if_null(copy); return copy; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/meson.build������������������������������������������������������������������0000664�0000000�0000000�00000000456�14570443012�0017035�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc_sources += files( 'buf.c', 'dir.c', 'fd_util.c', 'file-helpers.c', 'font.c', 'grab-file.c', 'graphic-helpers.c', 'match.c', 'mem.c', 'nodename.c', 'parse-bool.c', 'scaled_font_buffer.c', 'scaled_scene_buffer.c', 'scene-helpers.c', 'spawn.c', 'string-helpers.c', ) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/nodename.c�������������������������������������������������������������������0000664�0000000�0000000�00000001236�14570443012�0016622�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <ctype.h> #include <string.h> #include "common/nodename.h" char * nodename(xmlNode *node, char *buf, int len) { if (!node || !node->name) { return NULL; } /* Ignore superfluous 'text.' in node name */ if (node->parent && !strcmp((char *)node->name, "text")) { node = node->parent; } char *p = buf; p[--len] = 0; for (;;) { const char *name = (char *)node->name; char c; while ((c = *name++) != 0) { *p++ = tolower(c); if (!--len) { return buf; } } *p = 0; node = node->parent; if (!node || !node->name) { return buf; } *p++ = '.'; if (!--len) { return buf; } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/parse-bool.c�����������������������������������������������������������������0000664�0000000�0000000�00000001624�14570443012�0017100�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <strings.h> #include <wlr/util/log.h> #include "common/parse-bool.h" int parse_bool(const char *str, int default_value) { if (!str) { goto error_not_a_boolean; } else if (!strcasecmp(str, "yes")) { return true; } else if (!strcasecmp(str, "true")) { return true; } else if (!strcasecmp(str, "on")) { return true; } else if (!strcasecmp(str, "no")) { return false; } else if (!strcasecmp(str, "false")) { return false; } else if (!strcasecmp(str, "off")) { return false; } error_not_a_boolean: wlr_log(WLR_ERROR, "(%s) is not a boolean value", str); return default_value; } void set_bool(const char *str, bool *variable) { int ret = parse_bool(str, -1); if (ret < 0) { return; } *variable = ret; } void set_bool_as_int(const char *str, int *variable) { int ret = parse_bool(str, -1); if (ret < 0) { return; } *variable = ret; } ������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/scaled_font_buffer.c���������������������������������������������������������0000664�0000000�0000000�00000005070�14570443012�0020646�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <stdlib.h> #include <string.h> #include <wayland-server-core.h> #include <wlr/util/log.h> #include "buffer.h" #include "common/font.h" #include "common/mem.h" #include "common/scaled_scene_buffer.h" #include "common/scaled_font_buffer.h" static struct lab_data_buffer * _create_buffer(struct scaled_scene_buffer *scaled_buffer, double scale) { struct lab_data_buffer *buffer; struct scaled_font_buffer *self = scaled_buffer->data; /* Buffer gets free'd automatically along the backing wlr_buffer */ font_buffer_create(&buffer, self->max_width, self->text, &self->font, self->color, self->arrow, scale); self->width = buffer ? buffer->unscaled_width : 0; self->height = buffer ? buffer->unscaled_height : 0; return buffer; } static void _destroy(struct scaled_scene_buffer *scaled_buffer) { struct scaled_font_buffer *self = scaled_buffer->data; scaled_buffer->data = NULL; zfree(self->text); zfree(self->font.name); zfree(self->arrow); free(self); } static const struct scaled_scene_buffer_impl impl = { .create_buffer = _create_buffer, .destroy = _destroy }; /* Public API */ struct scaled_font_buffer * scaled_font_buffer_create(struct wlr_scene_tree *parent) { assert(parent); struct scaled_font_buffer *self = znew(*self); struct scaled_scene_buffer *scaled_buffer = scaled_scene_buffer_create(parent, &impl, /* drop_buffer */ true); if (!scaled_buffer) { free(self); return NULL; } scaled_buffer->data = self; self->scaled_buffer = scaled_buffer; self->scene_buffer = scaled_buffer->scene_buffer; return self; } void scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, int max_width, struct font *font, float *color, const char *arrow) { assert(self); assert(text); assert(font); assert(color); /* Clean up old internal state */ zfree(self->text); zfree(self->font.name); zfree(self->arrow); /* Update internal state */ self->text = xstrdup(text); self->max_width = max_width; if (font->name) { self->font.name = xstrdup(font->name); } self->font.size = font->size; self->font.slant = font->slant; self->font.weight = font->weight; memcpy(self->color, color, sizeof(self->color)); self->arrow = arrow ? xstrdup(arrow) : NULL; /* Invalidate cache and force a new render */ scaled_scene_buffer_invalidate_cache(self->scaled_buffer); } void scaled_font_buffer_set_max_width(struct scaled_font_buffer *self, int max_width) { self->max_width = max_width; scaled_scene_buffer_invalidate_cache(self->scaled_buffer); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/scaled_scene_buffer.c��������������������������������������������������������0000664�0000000�0000000�00000015166�14570443012�0021004�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <stdlib.h> #include <wayland-server-core.h> #include <wlr/types/wlr_buffer.h> #include <wlr/types/wlr_output.h> #include <wlr/types/wlr_scene.h> #include <wlr/util/log.h> #include "buffer.h" #include "common/mem.h" #include "common/scaled_scene_buffer.h" /** * TODO * * This implementation does not react to output scale changes itself but only * to movement of scene nodes to different outputs. To also handle output scale * changes we'd need to keep a list of struct wlr_outputs * in sync and listen * to their commit signals. * * The detection of the max output scale is also not 100% robust for all use * cases as on output_enter we only compare the new output scale with the * primary_output scale. In specific conditions the primary output may be the * same as the new output even though a smaller part of the buffer node is * still visible on an output with a bigger scale. This could be solved the * same way as the previous point in keeping a list of outputs in sync. * * Most of this would be easier when wlroots would instead provide a * max_scale_changed event because it already touches all the relevant parts * when calculating the primary_output and inform wlr_scene_buffers about * output changes. * * See wlroots/types/scene/wlr_scene.c scene_buffer_update_outputs() */ /* Internal API */ static void _cache_entry_destroy(struct scaled_scene_buffer_cache_entry *cache_entry, bool drop_buffer) { wl_list_remove(&cache_entry->link); if (cache_entry->buffer) { /* Allow the buffer to get dropped if there are no further consumers */ wlr_buffer_unlock(cache_entry->buffer); if (drop_buffer) { wlr_buffer_drop(cache_entry->buffer); } } free(cache_entry); } static void _update_buffer(struct scaled_scene_buffer *self, double scale) { self->active_scale = scale; /* Search for cached buffer of specified scale */ struct scaled_scene_buffer_cache_entry *cache_entry, *cache_entry_tmp; wl_list_for_each_safe(cache_entry, cache_entry_tmp, &self->cache, link) { if (cache_entry->scale == scale) { /* LRU cache, recently used in front */ wl_list_remove(&cache_entry->link); wl_list_insert(&self->cache, &cache_entry->link); wlr_scene_buffer_set_buffer(self->scene_buffer, cache_entry->buffer); return; } } /* Create new buffer, will get destroyed along the backing wlr_buffer */ struct lab_data_buffer *buffer = self->impl->create_buffer(self, scale); if (buffer) { /* Ensure the buffer doesn't get deleted behind our back */ wlr_buffer_lock(&buffer->base); } self->width = buffer ? buffer->unscaled_width : 0; self->height = buffer ? buffer->unscaled_height : 0; /* Create or reuse cache entry */ if (wl_list_length(&self->cache) < LAB_SCALED_BUFFER_MAX_CACHE) { cache_entry = znew(*cache_entry); } else { cache_entry = wl_container_of(self->cache.prev, cache_entry, link); if (cache_entry->buffer) { /* Allow the old buffer to get dropped if there are no further consumers */ wlr_buffer_unlock(cache_entry->buffer); if (self->drop_buffer) { wlr_buffer_drop(cache_entry->buffer); } } wl_list_remove(&cache_entry->link); } /* Update the cache entry */ cache_entry->scale = scale; cache_entry->buffer = buffer ? &buffer->base : NULL; wl_list_insert(&self->cache, &cache_entry->link); /* And finally update the wlr_scene_buffer itself */ wlr_scene_buffer_set_buffer(self->scene_buffer, cache_entry->buffer); wlr_scene_buffer_set_dest_size(self->scene_buffer, self->width, self->height); } /* Internal event handlers */ static void _handle_node_destroy(struct wl_listener *listener, void *data) { struct scaled_scene_buffer_cache_entry *cache_entry, *cache_entry_tmp; struct scaled_scene_buffer *self = wl_container_of(listener, self, destroy); wl_list_remove(&self->destroy.link); wl_list_remove(&self->output_enter.link); wl_list_remove(&self->output_leave.link); wl_list_for_each_safe(cache_entry, cache_entry_tmp, &self->cache, link) { _cache_entry_destroy(cache_entry, self->drop_buffer); } assert(wl_list_empty(&self->cache)); if (self->impl->destroy) { self->impl->destroy(self); } free(self); } static void _handle_output_enter(struct wl_listener *listener, void *data) { struct scaled_scene_buffer *self = wl_container_of(listener, self, output_enter); /* primary_output is the output most of the node area is in */ struct wlr_scene_output *primary = self->scene_buffer->primary_output; /* scene_output is the output we just entered */ struct wlr_scene_output *scene_output = data; double max_scale = scene_output->output->scale; if (primary && primary->output->scale > max_scale) { max_scale = primary->output->scale; } if (self->active_scale != max_scale) { _update_buffer(self, max_scale); } } static void _handle_output_leave(struct wl_listener *listener, void *data) { struct scaled_scene_buffer *self = wl_container_of(listener, self, output_leave); /* primary_output is the output most of the node area is in */ struct wlr_scene_output *primary = self->scene_buffer->primary_output; if (primary && primary->output->scale != self->active_scale) { _update_buffer(self, primary->output->scale); } } /* Public API */ struct scaled_scene_buffer * scaled_scene_buffer_create(struct wlr_scene_tree *parent, const struct scaled_scene_buffer_impl *impl, bool drop_buffer) { assert(parent); assert(impl); assert(impl->create_buffer); struct scaled_scene_buffer *self = znew(*self); self->scene_buffer = wlr_scene_buffer_create(parent, NULL); if (!self->scene_buffer) { wlr_log(WLR_ERROR, "Failed to create scene buffer"); free(self); return NULL; } self->impl = impl; self->active_scale = 1; self->drop_buffer = drop_buffer; wl_list_init(&self->cache); /* Listen to output enter/leave so we get notified about scale changes */ self->output_enter.notify = _handle_output_enter; wl_signal_add(&self->scene_buffer->events.output_enter, &self->output_enter); self->output_leave.notify = _handle_output_leave; wl_signal_add(&self->scene_buffer->events.output_leave, &self->output_leave); /* Let it destroy automatically when the scene node destroys */ self->destroy.notify = _handle_node_destroy; wl_signal_add(&self->scene_buffer->node.events.destroy, &self->destroy); return self; } void scaled_scene_buffer_invalidate_cache(struct scaled_scene_buffer *self) { assert(self); struct scaled_scene_buffer_cache_entry *cache_entry, *cache_entry_tmp; wl_list_for_each_safe(cache_entry, cache_entry_tmp, &self->cache, link) { _cache_entry_destroy(cache_entry, self->drop_buffer); } assert(wl_list_empty(&self->cache)); _update_buffer(self, self->active_scale); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/scene-helpers.c��������������������������������������������������������������0000664�0000000�0000000�00000003505�14570443012�0017572�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <wlr/types/wlr_output.h> #include <wlr/types/wlr_scene.h> #include <wlr/util/log.h> #include "common/scene-helpers.h" struct wlr_surface * lab_wlr_surface_from_node(struct wlr_scene_node *node) { struct wlr_scene_buffer *buffer; struct wlr_scene_surface *scene_surface; if (node && node->type == WLR_SCENE_NODE_BUFFER) { buffer = wlr_scene_buffer_from_node(node); scene_surface = wlr_scene_surface_try_from_buffer(buffer); if (scene_surface) { return scene_surface->surface; } } return NULL; } struct wlr_scene_node * lab_wlr_scene_get_prev_node(struct wlr_scene_node *node) { assert(node); struct wlr_scene_node *prev; prev = wl_container_of(node->link.prev, node, link); if (&prev->link == &node->parent->children) { return NULL; } return prev; } /* * This is a copy of wlr_scene_output_commit() * as it doesn't use the pending state at all. */ bool lab_wlr_scene_output_commit(struct wlr_scene_output *scene_output) { assert(scene_output); struct wlr_output *wlr_output = scene_output->output; struct wlr_output_state *state = &wlr_output->pending; if (!wlr_output->needs_frame && !pixman_region32_not_empty( &scene_output->damage_ring.current)) { return false; } if (!wlr_scene_output_build_state(scene_output, state, NULL)) { wlr_log(WLR_ERROR, "Failed to build output state for %s", wlr_output->name); return false; } if (!wlr_output_commit(wlr_output)) { wlr_log(WLR_ERROR, "Failed to commit output %s", wlr_output->name); return false; } /* * FIXME: Remove the following line as soon as * https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4253 * is merged. At that point wlr_scene handles damage tracking internally * again. */ wlr_damage_ring_rotate(&scene_output->damage_ring); return true; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/spawn.c����������������������������������������������������������������������0000664�0000000�0000000�00000002435�14570443012�0016166�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <glib.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> #include <wlr/util/log.h> #include "common/spawn.h" #include "common/fd_util.h" void spawn_async_no_shell(char const *command) { GError *err = NULL; gchar **argv = NULL; assert(command); /* Use glib's shell-parse to mimic Openbox's behaviour */ g_shell_parse_argv((gchar *)command, NULL, &argv, &err); if (err) { g_message("%s", err->message); g_error_free(err); return; } /* * Avoid zombie processes by using a double-fork, whereby the * grandchild becomes orphaned & the responsibility of the OS. */ pid_t child = 0, grandchild = 0; child = fork(); switch (child) { case -1: wlr_log(WLR_ERROR, "unable to fork()"); goto out; case 0: restore_nofile_limit(); setsid(); sigset_t set; sigemptyset(&set); sigprocmask(SIG_SETMASK, &set, NULL); /* Restore ignored signals */ signal(SIGPIPE, SIG_DFL); grandchild = fork(); if (grandchild == 0) { execvp(argv[0], argv); _exit(0); } else if (grandchild < 0) { wlr_log(WLR_ERROR, "unable to fork()"); } _exit(0); default: break; } waitpid(child, NULL, 0); out: g_strfreev(argv); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/common/string-helpers.c�������������������������������������������������������������0000664�0000000�0000000�00000002150�14570443012�0017776�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <ctype.h> #include <stdarg.h> #include <stdio.h> #include <string.h> #include "common/mem.h" #include "common/string-helpers.h" bool string_null_or_empty(const char *s) { return !s || !*s; } void trim_last_field(char *buf, char delim) { char *p = strrchr(buf, delim); if (p) { *p = '\0'; } } static void rtrim(char **s) { size_t len = strlen(*s); if (!len) { return; } char *end = *s + len - 1; while (end >= *s && isspace(*end)) { end--; } *(end + 1) = '\0'; } char * string_strip(char *s) { rtrim(&s); while (isspace(*s)) { s++; } return s; } void string_truncate_at_pattern(char *buf, const char *pattern) { char *p = strstr(buf, pattern); if (!p) { return; } *p = '\0'; } char * strdup_printf(const char *fmt, ...) { size_t size = 0; char *p = NULL; va_list ap; va_start(ap, fmt); int n = vsnprintf(p, size, fmt, ap); va_end(ap); if (n < 0) { return NULL; } size = (size_t)n + 1; p = xzalloc(size); va_start(ap, fmt); n = vsnprintf(p, size, fmt, ap); va_end(ap); if (n < 0) { free(p); return NULL; } return p; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/config/�����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14570443012�0014643�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/config/keybind.c��������������������������������������������������������������������0000664�0000000�0000000�00000011230�14570443012�0016431�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <glib.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <wlr/util/log.h> #include "common/list.h" #include "common/mem.h" #include "config/keybind.h" #include "config/rcxml.h" #include "labwc.h" uint32_t parse_modifier(const char *symname) { /* Mod2 == NumLock */ if (!strcmp(symname, "S")) { return WLR_MODIFIER_SHIFT; } else if (!strcmp(symname, "C")) { return WLR_MODIFIER_CTRL; } else if (!strcmp(symname, "A") || !strcmp(symname, "Mod1")) { return WLR_MODIFIER_ALT; } else if (!strcmp(symname, "W") || !strcmp(symname, "Mod4")) { return WLR_MODIFIER_LOGO; } else if (!strcmp(symname, "M") || !strcmp(symname, "Mod5")) { return WLR_MODIFIER_MOD5; } else if (!strcmp(symname, "H") || !strcmp(symname, "Mod3")) { return WLR_MODIFIER_MOD3; } else { return 0; } } bool keybind_the_same(struct keybind *a, struct keybind *b) { assert(a && b); if (a->modifiers != b->modifiers || a->keysyms_len != b->keysyms_len) { return false; } for (size_t i = 0; i < a->keysyms_len; i++) { if (a->keysyms[i] != b->keysyms[i]) { return false; } } return true; } static void update_keycodes_iter(struct xkb_keymap *keymap, xkb_keycode_t key, void *data) { struct keybind *keybind; const xkb_keysym_t *syms; xkb_layout_index_t layout = *(xkb_layout_index_t *)data; int nr_syms = xkb_keymap_key_get_syms_by_level(keymap, key, layout, 0, &syms); if (!nr_syms) { return; } wl_list_for_each(keybind, &rc.keybinds, link) { if (keybind->keycodes_layout >= 0 && (xkb_layout_index_t)keybind->keycodes_layout != layout) { /* Prevent storing keycodes from multiple layouts */ continue; } if (keybind->use_syms_only) { continue; } for (int i = 0; i < nr_syms; i++) { xkb_keysym_t sym = syms[i]; for (size_t j = 0; j < keybind->keysyms_len; j++) { if (sym != keybind->keysyms[j]) { continue; } /* Found keycode for sym */ if (keybind->keycodes_len == MAX_KEYCODES) { wlr_log(WLR_ERROR, "Already stored %lu keycodes for keybind", keybind->keycodes_len); break; } bool keycode_exists = false; for (size_t k = 0; k < keybind->keycodes_len; k++) { if (keybind->keycodes[k] == key) { keycode_exists = true; break; } } if (keycode_exists) { continue; } keybind->keycodes[keybind->keycodes_len++] = key; keybind->keycodes_layout = layout; } } } } void keybind_update_keycodes(struct server *server) { struct xkb_state *state = server->seat.keyboard_group->keyboard.xkb_state; struct xkb_keymap *keymap = xkb_state_get_keymap(state); struct keybind *keybind; wl_list_for_each(keybind, &rc.keybinds, link) { keybind->keycodes_len = 0; keybind->keycodes_layout = -1; } xkb_layout_index_t layouts = xkb_keymap_num_layouts(keymap); for (xkb_layout_index_t i = 0; i < layouts; i++) { wlr_log(WLR_DEBUG, "Found layout %s", xkb_keymap_layout_get_name(keymap, i)); xkb_keymap_key_for_each(keymap, update_keycodes_iter, &i); } } struct keybind * keybind_create(const char *keybind) { xkb_keysym_t sym; struct keybind *k = znew(*k); xkb_keysym_t keysyms[MAX_KEYSYMS]; gchar **symnames = g_strsplit(keybind, "-", -1); for (size_t i = 0; symnames[i]; i++) { char *symname = symnames[i]; uint32_t modifier = parse_modifier(symname); if (modifier != 0) { k->modifiers |= modifier; } else { sym = xkb_keysym_from_name(symname, XKB_KEYSYM_CASE_INSENSITIVE); if (sym == XKB_KEY_NoSymbol && g_utf8_strlen(symname, -1) == 1) { /* * xkb_keysym_from_name() only handles a legacy set of single * characters. Thus we try to get the unicode codepoint here * and try a direct translation instead. * * This allows using keybinds like 'W-ö' and similar. */ gunichar codepoint = g_utf8_get_char_validated(symname, -1); if (codepoint != (gunichar)-1) { sym = xkb_utf32_to_keysym(codepoint); } } sym = xkb_keysym_to_lower(sym); if (sym == XKB_KEY_NoSymbol) { wlr_log(WLR_ERROR, "unknown keybind (%s)", symname); free(k); k = NULL; break; } keysyms[k->keysyms_len] = sym; k->keysyms_len++; if (k->keysyms_len == MAX_KEYSYMS) { wlr_log(WLR_ERROR, "There are a lot of fingers involved. " "We stopped counting at %u.", MAX_KEYSYMS); wlr_log(WLR_ERROR, "Offending keybind was %s", keybind); break; } } } g_strfreev(symnames); if (!k) { return NULL; } wl_list_append(&rc.keybinds, &k->link); k->keysyms = xmalloc(k->keysyms_len * sizeof(xkb_keysym_t)); memcpy(k->keysyms, keysyms, k->keysyms_len * sizeof(xkb_keysym_t)); wl_list_init(&k->actions); return k; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/config/libinput.c�������������������������������������������������������������������0000664�0000000�0000000�00000003315�14570443012�0016637�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <string.h> #include <strings.h> #include "common/mem.h" #include "common/list.h" #include "common/string-helpers.h" #include "config/libinput.h" #include "config/rcxml.h" static void libinput_category_init(struct libinput_category *l) { l->type = LAB_LIBINPUT_DEVICE_DEFAULT; l->name = NULL; l->pointer_speed = -2; l->natural_scroll = -1; l->left_handed = -1; l->tap = LIBINPUT_CONFIG_TAP_ENABLED; l->tap_button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; l->tap_and_drag = -1; l->drag_lock = -1; l->accel_profile = -1; l->middle_emu = -1; l->dwt = -1; l->click_method = -1; l->send_events_mode = -1; } enum lab_libinput_device_type get_device_type(const char *s) { if (string_null_or_empty(s)) { return LAB_LIBINPUT_DEVICE_NONE; } if (!strcasecmp(s, "default")) { return LAB_LIBINPUT_DEVICE_DEFAULT; } if (!strcasecmp(s, "touch")) { return LAB_LIBINPUT_DEVICE_TOUCH; } if (!strcasecmp(s, "touchpad")) { return LAB_LIBINPUT_DEVICE_TOUCHPAD; } if (!strcasecmp(s, "non-touch")) { return LAB_LIBINPUT_DEVICE_NON_TOUCH; } return LAB_LIBINPUT_DEVICE_NONE; } struct libinput_category * libinput_category_create(void) { struct libinput_category *l = znew(*l); libinput_category_init(l); wl_list_append(&rc.libinput_categories, &l->link); return l; } /* After rcxml_read(), a default category always exists. */ struct libinput_category * libinput_category_get_default(void) { struct libinput_category *l; /* * Iterate in reverse to get the last one added in case multiple * 'default' profiles were created. */ wl_list_for_each_reverse(l, &rc.libinput_categories, link) { if (l->type == LAB_LIBINPUT_DEVICE_DEFAULT) { return l; } } return NULL; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/config/meson.build������������������������������������������������������������������0000664�0000000�0000000�00000000201�14570443012�0016776�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc_sources += files( 'rcxml.c', 'keybind.c', 'session.c', 'mousebind.c', 'touch.c', 'tablet.c', 'libinput.c', ) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/config/mousebind.c������������������������������������������������������������������0000664�0000000�0000000�00000011037�14570443012�0016776�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <linux/input-event-codes.h> #include <strings.h> #include <unistd.h> #include <wlr/util/log.h> #include "common/list.h" #include "common/mem.h" #include "config/mousebind.h" #include "config/rcxml.h" uint32_t mousebind_button_from_str(const char *str, uint32_t *modifiers) { assert(str); if (modifiers) { *modifiers = 0; while (strlen(str) >= 2 && str[1] == '-') { char modname[2] = {str[0], 0}; uint32_t parsed_modifier = parse_modifier(modname); if (!parsed_modifier) { goto invalid; } *modifiers |= parsed_modifier; str += 2; } } if (!strcasecmp(str, "Left")) { return BTN_LEFT; } else if (!strcasecmp(str, "Right")) { return BTN_RIGHT; } else if (!strcasecmp(str, "Middle")) { return BTN_MIDDLE; } else if (!strcasecmp(str, "Side")) { return BTN_SIDE; } else if (!strcasecmp(str, "Extra")) { return BTN_EXTRA; } else if (!strcasecmp(str, "Forward")) { return BTN_FORWARD; } else if (!strcasecmp(str, "Back")) { return BTN_BACK; } else if (!strcasecmp(str, "Task")) { return BTN_TASK; } invalid: wlr_log(WLR_ERROR, "unknown button (%s)", str); return UINT32_MAX; } enum direction mousebind_direction_from_str(const char *str, uint32_t *modifiers) { assert(str); if (modifiers) { *modifiers = 0; while (strlen(str) >= 2 && str[1] == '-') { char modname[2] = {str[0], 0}; uint32_t parsed_modifier = parse_modifier(modname); if (!parsed_modifier) { goto invalid; } *modifiers |= parsed_modifier; str += 2; } } if (!strcasecmp(str, "Left")) { return LAB_DIRECTION_LEFT; } else if (!strcasecmp(str, "Right")) { return LAB_DIRECTION_RIGHT; } else if (!strcasecmp(str, "Up")) { return LAB_DIRECTION_UP; } else if (!strcasecmp(str, "Down")) { return LAB_DIRECTION_DOWN; } invalid: wlr_log(WLR_ERROR, "unknown direction (%s)", str); return LAB_DIRECTION_INVALID; } enum mouse_event mousebind_event_from_str(const char *str) { assert(str); if (!strcasecmp(str, "doubleclick")) { return MOUSE_ACTION_DOUBLECLICK; } else if (!strcasecmp(str, "click")) { return MOUSE_ACTION_CLICK; } else if (!strcasecmp(str, "press")) { return MOUSE_ACTION_PRESS; } else if (!strcasecmp(str, "release")) { return MOUSE_ACTION_RELEASE; } else if (!strcasecmp(str, "drag")) { return MOUSE_ACTION_DRAG; } else if (!strcasecmp(str, "scroll")) { return MOUSE_ACTION_SCROLL; } wlr_log(WLR_ERROR, "unknown mouse action (%s)", str); return MOUSE_ACTION_NONE; } static enum ssd_part_type context_from_str(const char *str) { if (!strcasecmp(str, "Close")) { return LAB_SSD_BUTTON_CLOSE; } else if (!strcasecmp(str, "Maximize")) { return LAB_SSD_BUTTON_MAXIMIZE; } else if (!strcasecmp(str, "Iconify")) { return LAB_SSD_BUTTON_ICONIFY; } else if (!strcasecmp(str, "WindowMenu")) { return LAB_SSD_BUTTON_WINDOW_MENU; } else if (!strcasecmp(str, "Titlebar")) { return LAB_SSD_PART_TITLEBAR; } else if (!strcasecmp(str, "Title")) { return LAB_SSD_PART_TITLE; } else if (!strcasecmp(str, "TLCorner")) { return LAB_SSD_PART_CORNER_TOP_LEFT; } else if (!strcasecmp(str, "TRCorner")) { return LAB_SSD_PART_CORNER_TOP_RIGHT; } else if (!strcasecmp(str, "BRCorner")) { return LAB_SSD_PART_CORNER_BOTTOM_RIGHT; } else if (!strcasecmp(str, "BLCorner")) { return LAB_SSD_PART_CORNER_BOTTOM_LEFT; } else if (!strcasecmp(str, "Top")) { return LAB_SSD_PART_TOP; } else if (!strcasecmp(str, "Right")) { return LAB_SSD_PART_RIGHT; } else if (!strcasecmp(str, "Bottom")) { return LAB_SSD_PART_BOTTOM; } else if (!strcasecmp(str, "Left")) { return LAB_SSD_PART_LEFT; } else if (!strcasecmp(str, "Frame")) { return LAB_SSD_FRAME; } else if (!strcasecmp(str, "Client")) { return LAB_SSD_CLIENT; } else if (!strcasecmp(str, "Desktop")) { return LAB_SSD_ROOT; } else if (!strcasecmp(str, "Root")) { return LAB_SSD_ROOT; } wlr_log(WLR_ERROR, "unknown mouse context (%s)", str); return LAB_SSD_NONE; } bool mousebind_the_same(struct mousebind *a, struct mousebind *b) { assert(a && b); return a->context == b->context && a->button == b->button && a->direction == b->direction && a->mouse_event == b->mouse_event && a->modifiers == b->modifiers; } struct mousebind * mousebind_create(const char *context) { if (!context) { wlr_log(WLR_ERROR, "mousebind context not specified"); return NULL; } struct mousebind *m = znew(*m); m->context = context_from_str(context); if (m->context != LAB_SSD_NONE) { wl_list_append(&rc.mousebinds, &m->link); } wl_list_init(&m->actions); return m; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/config/rcxml.c����������������������������������������������������������������������0000664�0000000�0000000�00000146340�14570443012�0016144�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <fcntl.h> #include <libxml/parser.h> #include <libxml/tree.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <unistd.h> #include <wayland-server-core.h> #include <wlr/util/box.h> #include <wlr/util/log.h> #include "action.h" #include "common/dir.h" #include "common/list.h" #include "common/macros.h" #include "common/mem.h" #include "common/nodename.h" #include "common/parse-bool.h" #include "common/string-helpers.h" #include "config/keybind.h" #include "config/libinput.h" #include "config/mousebind.h" #include "config/tablet.h" #include "config/rcxml.h" #include "labwc.h" #include "regions.h" #include "view.h" #include "window-rules.h" #include "workspaces.h" static bool in_regions; static bool in_usable_area_override; static bool in_keybind; static bool in_mousebind; static bool in_touch; static bool in_libinput_category; static bool in_window_switcher_field; static bool in_window_rules; static bool in_action_query; static bool in_action_then_branch; static bool in_action_else_branch; static struct usable_area_override *current_usable_area_override; static struct keybind *current_keybind; static struct mousebind *current_mousebind; static struct touch_config_entry *current_touch; static struct libinput_category *current_libinput_category; static const char *current_mouse_context; static struct action *current_keybind_action; static struct action *current_mousebind_action; static struct region *current_region; static struct window_switcher_field *current_field; static struct window_rule *current_window_rule; static struct action *current_window_rule_action; static struct view_query *current_view_query; static struct action *current_child_action; enum font_place { FONT_PLACE_NONE = 0, FONT_PLACE_UNKNOWN, FONT_PLACE_ACTIVEWINDOW, FONT_PLACE_INACTIVEWINDOW, FONT_PLACE_MENUITEM, FONT_PLACE_OSD, /* TODO: Add all places based on Openbox's rc.xml */ }; static void load_default_key_bindings(void); static void load_default_mouse_bindings(void); static void fill_usable_area_override(char *nodename, char *content) { if (!strcasecmp(nodename, "margin")) { current_usable_area_override = znew(*current_usable_area_override); wl_list_append(&rc.usable_area_overrides, ¤t_usable_area_override->link); return; } string_truncate_at_pattern(nodename, ".margin"); if (!content) { /* nop */ } else if (!current_usable_area_override) { wlr_log(WLR_ERROR, "no usable-area-override object"); } else if (!strcmp(nodename, "output")) { free(current_usable_area_override->output); current_usable_area_override->output = xstrdup(content); } else if (!strcmp(nodename, "left")) { current_usable_area_override->margin.left = atoi(content); } else if (!strcmp(nodename, "right")) { current_usable_area_override->margin.right = atoi(content); } else if (!strcmp(nodename, "top")) { current_usable_area_override->margin.top = atoi(content); } else if (!strcmp(nodename, "bottom")) { current_usable_area_override->margin.bottom = atoi(content); } else { wlr_log(WLR_ERROR, "Unexpected data usable-area-override parser: %s=\"%s\"", nodename, content); } } /* Does a boolean-parse but also allows 'default' */ static void set_property(const char *str, enum property *variable) { if (!str || !strcasecmp(str, "default")) { *variable = LAB_PROP_UNSET; return; } int ret = parse_bool(str, -1); if (ret < 0) { return; } *variable = ret ? LAB_PROP_TRUE : LAB_PROP_FALSE; } static void fill_window_rule(char *nodename, char *content) { if (!strcasecmp(nodename, "windowRule.windowRules")) { current_window_rule = znew(*current_window_rule); wl_list_append(&rc.window_rules, ¤t_window_rule->link); wl_list_init(¤t_window_rule->actions); return; } string_truncate_at_pattern(nodename, ".windowrule.windowrules"); if (!content) { /* nop */ } else if (!current_window_rule) { wlr_log(WLR_ERROR, "no window-rule"); /* Criteria */ } else if (!strcmp(nodename, "identifier")) { free(current_window_rule->identifier); current_window_rule->identifier = xstrdup(content); } else if (!strcmp(nodename, "title")) { free(current_window_rule->title); current_window_rule->title = xstrdup(content); } else if (!strcasecmp(nodename, "matchOnce")) { set_bool(content, ¤t_window_rule->match_once); /* Event */ } else if (!strcmp(nodename, "event")) { /* * This is just in readiness for adding any other types of * events in the future. We default to onFirstMap anyway. */ if (!strcasecmp(content, "onFirstMap")) { current_window_rule->event = LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP; } /* Properties */ } else if (!strcasecmp(nodename, "serverDecoration")) { set_property(content, ¤t_window_rule->server_decoration); } else if (!strcasecmp(nodename, "skipTaskbar")) { set_property(content, ¤t_window_rule->skip_taskbar); } else if (!strcasecmp(nodename, "skipWindowSwitcher")) { set_property(content, ¤t_window_rule->skip_window_switcher); } else if (!strcasecmp(nodename, "ignoreFocusRequest")) { set_property(content, ¤t_window_rule->ignore_focus_request); } else if (!strcasecmp(nodename, "fixedPosition")) { set_property(content, ¤t_window_rule->fixed_position); /* Actions */ } else if (!strcmp(nodename, "name.action")) { current_window_rule_action = action_create(content); if (current_window_rule_action) { wl_list_append(¤t_window_rule->actions, ¤t_window_rule_action->link); } } else if (!current_window_rule_action) { wlr_log(WLR_ERROR, "expect <action name=\"\"> element first. " "nodename: '%s' content: '%s'", nodename, content); } else { action_arg_from_xml_node(current_window_rule_action, nodename, content); } } static void fill_window_switcher_field(char *nodename, char *content) { if (!strcasecmp(nodename, "field.fields.windowswitcher")) { current_field = znew(*current_field); wl_list_append(&rc.window_switcher.fields, ¤t_field->link); return; } string_truncate_at_pattern(nodename, ".field.fields.windowswitcher"); if (!content) { /* intentionally left empty */ } else if (!current_field) { wlr_log(WLR_ERROR, "no <field>"); } else if (!strcmp(nodename, "content")) { if (!strcmp(content, "type")) { current_field->content = LAB_FIELD_TYPE; } else if (!strcmp(content, "identifier")) { current_field->content = LAB_FIELD_IDENTIFIER; } else if (!strcmp(content, "app_id")) { wlr_log(WLR_ERROR, "window-switcher field 'app_id' is deprecated"); current_field->content = LAB_FIELD_IDENTIFIER; } else if (!strcmp(content, "trimmed_identifier")) { current_field->content = LAB_FIELD_TRIMMED_IDENTIFIER; } else if (!strcmp(content, "title")) { current_field->content = LAB_FIELD_TITLE; } else { wlr_log(WLR_ERROR, "bad windowSwitcher field '%s'", content); } } else if (!strcmp(nodename, "width") && !strchr(content, '%')) { wlr_log(WLR_ERROR, "Removing invalid field, %s='%s' misses" " trailing %%", nodename, content); wl_list_remove(¤t_field->link); zfree(current_field); } else if (!strcmp(nodename, "width")) { current_field->width = atoi(content); } else { wlr_log(WLR_ERROR, "Unexpected data in field parser: %s=\"%s\"", nodename, content); } } static void fill_region(char *nodename, char *content) { string_truncate_at_pattern(nodename, ".region.regions"); if (!strcasecmp(nodename, "region.regions")) { current_region = znew(*current_region); wl_list_append(&rc.regions, ¤t_region->link); } else if (!content) { /* intentionally left empty */ } else if (!current_region) { wlr_log(WLR_ERROR, "Expecting <region name=\"\" before %s='%s'", nodename, content); } else if (!strcasecmp(nodename, "name")) { /* Prevent leaking memory if config contains multiple names */ if (!current_region->name) { current_region->name = xstrdup(content); } } else if (strstr("xywidtheight", nodename) && !strchr(content, '%')) { wlr_log(WLR_ERROR, "Removing invalid region '%s': %s='%s' misses" " a trailing %%", current_region->name, nodename, content); wl_list_remove(¤t_region->link); zfree(current_region->name); zfree(current_region); } else if (!strcmp(nodename, "x")) { current_region->percentage.x = atoi(content); } else if (!strcmp(nodename, "y")) { current_region->percentage.y = atoi(content); } else if (!strcmp(nodename, "width")) { current_region->percentage.width = atoi(content); } else if (!strcmp(nodename, "height")) { current_region->percentage.height = atoi(content); } else { wlr_log(WLR_ERROR, "Unexpected data in region parser: %s=\"%s\"", nodename, content); } } static void fill_action_query(char *nodename, char *content, struct action *action) { string_truncate_at_pattern(nodename, ".keybind.keyboard"); string_truncate_at_pattern(nodename, ".mousebind.context.mouse"); if (!strcasecmp(nodename, "query.action")) { current_view_query = NULL; } string_truncate_at_pattern(nodename, ".query.action"); if (!content) { return; } if (!current_view_query) { struct wl_list *queries = action_get_querylist(action, "query"); if (!queries) { action_arg_add_querylist(action, "query"); queries = action_get_querylist(action, "query"); } current_view_query = znew(*current_view_query); wl_list_append(queries, ¤t_view_query->link); } if (!strcasecmp(nodename, "identifier")) { current_view_query->identifier = xstrdup(content); } else if (!strcasecmp(nodename, "title")) { current_view_query->title = xstrdup(content); } } static void fill_child_action(char *nodename, char *content, struct action *parent, const char *branch_name) { string_truncate_at_pattern(nodename, ".keybind.keyboard"); string_truncate_at_pattern(nodename, ".mousebind.context.mouse"); string_truncate_at_pattern(nodename, ".then.action"); string_truncate_at_pattern(nodename, ".else.action"); if (!strcasecmp(nodename, "action")) { current_child_action = NULL; } if (!content) { return; } struct wl_list *siblings = action_get_actionlist(parent, branch_name); if (!siblings) { action_arg_add_actionlist(parent, branch_name); siblings = action_get_actionlist(parent, branch_name); } if (!strcasecmp(nodename, "name.action")) { if (!strcasecmp(content, "If") || !strcasecmp(content, "ForEach")) { wlr_log(WLR_ERROR, "action '%s' cannot be a child action", content); return; } current_child_action = action_create(content); if (current_child_action) { wl_list_append(siblings, ¤t_child_action->link); } } else if (!current_child_action) { wlr_log(WLR_ERROR, "expect <action name=\"\"> element first. " "nodename: '%s' content: '%s'", nodename, content); } else { action_arg_from_xml_node(current_child_action, nodename, content); } } static void fill_keybind(char *nodename, char *content) { if (!content) { return; } string_truncate_at_pattern(nodename, ".keybind.keyboard"); if (!strcmp(nodename, "key")) { current_keybind = keybind_create(content); current_keybind_action = NULL; /* * If an invalid keybind has been provided, * keybind_create() complains. */ if (!current_keybind) { wlr_log(WLR_ERROR, "Invalid keybind: %s", content); return; } } else if (!current_keybind) { wlr_log(WLR_ERROR, "expect <keybind key=\"\"> element first. " "nodename: '%s' content: '%s'", nodename, content); } else if (!strcasecmp(nodename, "layoutDependent")) { set_bool(content, ¤t_keybind->use_syms_only); } else if (!strcmp(nodename, "name.action")) { current_keybind_action = action_create(content); if (current_keybind_action) { wl_list_append(¤t_keybind->actions, ¤t_keybind_action->link); } } else if (!current_keybind_action) { wlr_log(WLR_ERROR, "expect <action name=\"\"> element first. " "nodename: '%s' content: '%s'", nodename, content); } else { /* * Here we deal with action sub-elements such as <to>, <output>, * <region>, <direction> and so on. This is common to key- and * mousebinds. */ action_arg_from_xml_node(current_keybind_action, nodename, content); } } static void fill_mousebind(char *nodename, char *content) { /* * Example of what we are parsing: * <mousebind button="Left" action="DoubleClick"> * <action name="Focus"/> * <action name="Raise"/> * <action name="ToggleMaximize"/> * </mousebind> */ if (!current_mouse_context) { wlr_log(WLR_ERROR, "expect <context name=\"\"> element first. " "nodename: '%s' content: '%s'", nodename, content); return; } else if (!strcmp(nodename, "mousebind.context.mouse")) { wlr_log(WLR_INFO, "create mousebind for %s", current_mouse_context); current_mousebind = mousebind_create(current_mouse_context); current_mousebind_action = NULL; return; } else if (!content) { return; } string_truncate_at_pattern(nodename, ".mousebind.context.mouse"); if (!current_mousebind) { wlr_log(WLR_ERROR, "expect <mousebind button=\"\" action=\"\"> element first. " "nodename: '%s' content: '%s'", nodename, content); } else if (!strcmp(nodename, "button")) { current_mousebind->button = mousebind_button_from_str(content, ¤t_mousebind->modifiers); } else if (!strcmp(nodename, "direction")) { current_mousebind->direction = mousebind_direction_from_str(content, ¤t_mousebind->modifiers); } else if (!strcmp(nodename, "action")) { /* <mousebind button="" action="EVENT"> */ current_mousebind->mouse_event = mousebind_event_from_str(content); } else if (!strcmp(nodename, "name.action")) { current_mousebind_action = action_create(content); if (current_mousebind_action) { wl_list_append(¤t_mousebind->actions, ¤t_mousebind_action->link); } } else if (!current_mousebind_action) { wlr_log(WLR_ERROR, "expect <action name=\"\"> element first. " "nodename: '%s' content: '%s'", nodename, content); } else { action_arg_from_xml_node(current_mousebind_action, nodename, content); } } static void fill_touch(char *nodename, char *content) { if (!strcasecmp(nodename, "touch")) { current_touch = znew(*current_touch); wl_list_append(&rc.touch_configs, ¤t_touch->link); } else if (!strcasecmp(nodename, "deviceName.touch")) { current_touch->device_name = xstrdup(content); } else if (!strcasecmp(nodename, "mapToOutput.touch")) { current_touch->output_name = xstrdup(content); } else { wlr_log(WLR_ERROR, "Unexpected data in touch parser: %s=\"%s\"", nodename, content); } } static int get_accel_profile(const char *s) { if (!s) { return -1; } if (!strcasecmp(s, "flat")) { return LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; } if (!strcasecmp(s, "adaptive")) { return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; } return -1; } static int get_send_events_mode(const char *s) { if (!s) { goto err; } int ret = parse_bool(s, -1); if (ret >= 0) { return ret ? LIBINPUT_CONFIG_SEND_EVENTS_ENABLED : LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; } if (!strcasecmp(s, "disabledOnExternalMouse")) { return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; } err: wlr_log(WLR_INFO, "Not a recognised send events mode"); return -1; } static void fill_libinput_category(char *nodename, char *content) { /* * Create a new profile (libinput-category) on `<libinput><device>` * so that the 'default' profile can be created without even providing a * category="" attribute (same as <device category="default">...) */ if (!strcmp(nodename, "device.libinput")) { current_libinput_category = libinput_category_create(); } if (!content) { return; } if (!current_libinput_category) { return; } string_truncate_at_pattern(nodename, ".device.libinput"); if (!strcmp(nodename, "category")) { /* * First we try to get a type based on a number of pre-defined * terms, for example: 'default', 'touch', 'touchpad' and * 'non-touch' */ current_libinput_category->type = get_device_type(content); /* * If we couldn't match against any of those terms, we use the * provided value to define the device name that the settings * should be applicable to. */ if (current_libinput_category->type == LAB_LIBINPUT_DEVICE_NONE) { current_libinput_category->name = xstrdup(content); } } else if (!strcasecmp(nodename, "naturalScroll")) { set_bool_as_int(content, ¤t_libinput_category->natural_scroll); } else if (!strcasecmp(nodename, "leftHanded")) { set_bool_as_int(content, ¤t_libinput_category->left_handed); } else if (!strcasecmp(nodename, "pointerSpeed")) { current_libinput_category->pointer_speed = atof(content); if (current_libinput_category->pointer_speed < -1) { current_libinput_category->pointer_speed = -1; } else if (current_libinput_category->pointer_speed > 1) { current_libinput_category->pointer_speed = 1; } } else if (!strcasecmp(nodename, "tap")) { int ret = parse_bool(content, -1); if (ret < 0) { return; } current_libinput_category->tap = ret ? LIBINPUT_CONFIG_TAP_ENABLED : LIBINPUT_CONFIG_TAP_DISABLED; } else if (!strcasecmp(nodename, "tapButtonMap")) { if (!strcmp(content, "lrm")) { current_libinput_category->tap_button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; } else if (!strcmp(content, "lmr")) { current_libinput_category->tap_button_map = LIBINPUT_CONFIG_TAP_MAP_LMR; } else { wlr_log(WLR_ERROR, "invalid tapButtonMap"); } } else if (!strcasecmp(nodename, "tapAndDrag")) { int ret = parse_bool(content, -1); if (ret < 0) { return; } current_libinput_category->tap_and_drag = ret ? LIBINPUT_CONFIG_DRAG_ENABLED : LIBINPUT_CONFIG_DRAG_DISABLED; } else if (!strcasecmp(nodename, "dragLock")) { int ret = parse_bool(content, -1); if (ret < 0) { return; } current_libinput_category->drag_lock = ret ? LIBINPUT_CONFIG_DRAG_LOCK_ENABLED : LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; } else if (!strcasecmp(nodename, "accelProfile")) { current_libinput_category->accel_profile = get_accel_profile(content); } else if (!strcasecmp(nodename, "middleEmulation")) { int ret = parse_bool(content, -1); if (ret < 0) { return; } current_libinput_category->middle_emu = ret ? LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED : LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; } else if (!strcasecmp(nodename, "disableWhileTyping")) { int ret = parse_bool(content, -1); if (ret < 0) { return; } current_libinput_category->dwt = ret ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED; } else if (!strcasecmp(nodename, "clickMethod")) { if (!strcasecmp(content, "none")) { current_libinput_category->click_method = LIBINPUT_CONFIG_CLICK_METHOD_NONE; } else if (!strcasecmp(content, "clickfinger")) { current_libinput_category->click_method = LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; } else if (!strcasecmp(content, "buttonAreas")) { current_libinput_category->click_method = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; } else { wlr_log(WLR_ERROR, "invalid clickMethod"); } } else if (!strcasecmp(nodename, "sendEventsMode")) { current_libinput_category->send_events_mode = get_send_events_mode(content); } } static void set_font_attr(struct font *font, const char *nodename, const char *content) { if (!strcmp(nodename, "name")) { zfree(font->name); font->name = xstrdup(content); } else if (!strcmp(nodename, "size")) { font->size = atoi(content); } else if (!strcmp(nodename, "slant")) { font->slant = !strcasecmp(content, "italic") ? FONT_SLANT_ITALIC : FONT_SLANT_NORMAL; } else if (!strcmp(nodename, "weight")) { font->weight = !strcasecmp(content, "bold") ? FONT_WEIGHT_BOLD : FONT_WEIGHT_NORMAL; } } static void fill_font(char *nodename, char *content, enum font_place place) { if (!content) { return; } string_truncate_at_pattern(nodename, ".font.theme"); switch (place) { case FONT_PLACE_NONE: /* * If <theme><font></font></theme> is used without a place="" * attribute, we set all font variables */ set_font_attr(&rc.font_activewindow, nodename, content); set_font_attr(&rc.font_inactivewindow, nodename, content); set_font_attr(&rc.font_menuitem, nodename, content); set_font_attr(&rc.font_osd, nodename, content); break; case FONT_PLACE_ACTIVEWINDOW: set_font_attr(&rc.font_activewindow, nodename, content); break; case FONT_PLACE_INACTIVEWINDOW: set_font_attr(&rc.font_inactivewindow, nodename, content); break; case FONT_PLACE_MENUITEM: set_font_attr(&rc.font_menuitem, nodename, content); break; case FONT_PLACE_OSD: set_font_attr(&rc.font_osd, nodename, content); break; /* TODO: implement for all font places */ default: break; } } static enum font_place enum_font_place(const char *place) { if (!place || place[0] == '\0') { return FONT_PLACE_NONE; } if (!strcasecmp(place, "ActiveWindow")) { return FONT_PLACE_ACTIVEWINDOW; } else if (!strcasecmp(place, "InactiveWindow")) { return FONT_PLACE_INACTIVEWINDOW; } else if (!strcasecmp(place, "MenuItem")) { return FONT_PLACE_MENUITEM; } else if (!strcasecmp(place, "OnScreenDisplay") || !strcasecmp(place, "OSD")) { return FONT_PLACE_OSD; } return FONT_PLACE_UNKNOWN; } static void set_adaptive_sync_mode(const char *str, enum adaptive_sync_mode *variable) { if (!strcasecmp(str, "fullscreen")) { *variable = LAB_ADAPTIVE_SYNC_FULLSCREEN; } else { int ret = parse_bool(str, -1); if (ret == 1) { *variable = LAB_ADAPTIVE_SYNC_ENABLED; } else { *variable = LAB_ADAPTIVE_SYNC_DISABLED; } } } static void entry(xmlNode *node, char *nodename, char *content) { /* current <theme><font place=""></font></theme> */ static enum font_place font_place = FONT_PLACE_NONE; static uint32_t button_map_from; if (!nodename) { return; } string_truncate_at_pattern(nodename, ".openbox_config"); string_truncate_at_pattern(nodename, ".labwc_config"); if (getenv("LABWC_DEBUG_CONFIG_NODENAMES")) { printf("%s: %s\n", nodename, content); } if (in_usable_area_override) { fill_usable_area_override(nodename, content); } if (in_keybind) { if (in_action_query) { fill_action_query(nodename, content, current_keybind_action); } else if (in_action_then_branch) { fill_child_action(nodename, content, current_keybind_action, "then"); } else if (in_action_else_branch) { fill_child_action(nodename, content, current_keybind_action, "else"); } else { fill_keybind(nodename, content); } } if (in_mousebind) { if (in_action_query) { fill_action_query(nodename, content, current_mousebind_action); } else if (in_action_then_branch) { fill_child_action(nodename, content, current_mousebind_action, "then"); } else if (in_action_else_branch) { fill_child_action(nodename, content, current_mousebind_action, "else"); } else { fill_mousebind(nodename, content); } } if (in_touch) { fill_touch(nodename, content); return; } if (in_libinput_category) { fill_libinput_category(nodename, content); return; } if (in_regions) { fill_region(nodename, content); return; } if (in_window_switcher_field) { fill_window_switcher_field(nodename, content); return; } if (in_window_rules) { fill_window_rule(nodename, content); return; } /* handle nodes without content, e.g. <keyboard><default /> */ if (!strcmp(nodename, "default.keyboard")) { load_default_key_bindings(); return; } if (!strcmp(nodename, "devault.mouse") || !strcmp(nodename, "default.mouse")) { load_default_mouse_bindings(); return; } if (!strcasecmp(nodename, "map.tablet")) { button_map_from = UINT32_MAX; return; } /* handle the rest */ if (!content) { return; } if (!strcmp(nodename, "place.font.theme")) { font_place = enum_font_place(content); if (font_place == FONT_PLACE_UNKNOWN) { wlr_log(WLR_ERROR, "invalid font place %s", content); } } if (!strcmp(nodename, "decoration.core")) { if (!strcmp(content, "client")) { rc.xdg_shell_server_side_deco = false; } else { rc.xdg_shell_server_side_deco = true; } } else if (!strcmp(nodename, "gap.core")) { rc.gap = atoi(content); } else if (!strcasecmp(nodename, "adaptiveSync.core")) { set_adaptive_sync_mode(content, &rc.adaptive_sync); } else if (!strcasecmp(nodename, "allowTearing.core")) { set_bool(content, &rc.allow_tearing); if (rc.allow_tearing) { char *no_atomic_env = getenv("WLR_DRM_NO_ATOMIC"); if (!no_atomic_env || strcmp(no_atomic_env, "1") != 0) { rc.allow_tearing = false; wlr_log(WLR_ERROR, "tearing requires WLR_DRM_NO_ATOMIC=1"); } } } else if (!strcasecmp(nodename, "reuseOutputMode.core")) { set_bool(content, &rc.reuse_output_mode); } else if (!strcmp(nodename, "policy.placement")) { if (!strcmp(content, "automatic")) { rc.placement_policy = LAB_PLACE_AUTOMATIC; } else if (!strcmp(content, "cursor")) { rc.placement_policy = LAB_PLACE_CURSOR; } else { rc.placement_policy = LAB_PLACE_CENTER; } } else if (!strcmp(nodename, "name.theme")) { rc.theme_name = xstrdup(content); } else if (!strcmp(nodename, "cornerradius.theme")) { rc.corner_radius = atoi(content); } else if (!strcasecmp(nodename, "keepBorder.theme")) { set_bool(content, &rc.ssd_keep_border); } else if (!strcmp(nodename, "name.font.theme")) { fill_font(nodename, content, font_place); } else if (!strcmp(nodename, "size.font.theme")) { fill_font(nodename, content, font_place); } else if (!strcmp(nodename, "slant.font.theme")) { fill_font(nodename, content, font_place); } else if (!strcmp(nodename, "weight.font.theme")) { fill_font(nodename, content, font_place); } else if (!strcasecmp(nodename, "followMouse.focus")) { set_bool(content, &rc.focus_follow_mouse); } else if (!strcasecmp(nodename, "followMouseRequiresMovement.focus")) { set_bool(content, &rc.focus_follow_mouse_requires_movement); } else if (!strcasecmp(nodename, "raiseOnFocus.focus")) { set_bool(content, &rc.raise_on_focus); } else if (!strcasecmp(nodename, "doubleClickTime.mouse")) { long doubleclick_time_parsed = strtol(content, NULL, 10); if (doubleclick_time_parsed > 0) { rc.doubleclick_time = doubleclick_time_parsed; } else { wlr_log(WLR_ERROR, "invalid doubleClickTime"); } } else if (!strcasecmp(nodename, "scrollFactor.mouse")) { rc.scroll_factor = atof(content); } else if (!strcasecmp(nodename, "name.context.mouse")) { current_mouse_context = content; current_mousebind = NULL; } else if (!strcasecmp(nodename, "repeatRate.keyboard")) { rc.repeat_rate = atoi(content); } else if (!strcasecmp(nodename, "repeatDelay.keyboard")) { rc.repeat_delay = atoi(content); } else if (!strcasecmp(nodename, "numlock.keyboard")) { set_bool(content, &rc.kb_numlock_enable); } else if (!strcasecmp(nodename, "layoutScope.keyboard")) { /* * This can be changed to an enum later on * if we decide to also support "application". */ rc.kb_layout_per_window = !strcasecmp(content, "window"); } else if (!strcasecmp(nodename, "screenEdgeStrength.resistance")) { rc.screen_edge_strength = atoi(content); } else if (!strcasecmp(nodename, "windowEdgeStrength.resistance")) { rc.window_edge_strength = atoi(content); } else if (!strcasecmp(nodename, "range.snapping")) { rc.snap_edge_range = atoi(content); } else if (!strcasecmp(nodename, "topMaximize.snapping")) { set_bool(content, &rc.snap_top_maximize); } else if (!strcasecmp(nodename, "notifyClient.snapping")) { if (!strcasecmp(content, "always")) { rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS; } else if (!strcasecmp(content, "region")) { rc.snap_tiling_events_mode = LAB_TILING_EVENTS_REGION; } else if (!strcasecmp(content, "edge")) { rc.snap_tiling_events_mode = LAB_TILING_EVENTS_EDGE; } else if (!strcasecmp(content, "never")) { rc.snap_tiling_events_mode = LAB_TILING_EVENTS_NEVER; } else { wlr_log(WLR_ERROR, "ignoring invalid value for notifyClient"); } /* <windowSwitcher show="" preview="" outlines="" /> */ } else if (!strcasecmp(nodename, "show.windowSwitcher")) { set_bool(content, &rc.window_switcher.show); } else if (!strcasecmp(nodename, "preview.windowSwitcher")) { set_bool(content, &rc.window_switcher.preview); } else if (!strcasecmp(nodename, "outlines.windowSwitcher")) { set_bool(content, &rc.window_switcher.outlines); /* Remove this long term - just a friendly warning for now */ } else if (strstr(nodename, "windowswitcher.core")) { wlr_log(WLR_ERROR, "<windowSwitcher> should not be child of <core>"); /* The following three are for backward compatibility only */ } else if (!strcasecmp(nodename, "show.windowSwitcher.core")) { set_bool(content, &rc.window_switcher.show); } else if (!strcasecmp(nodename, "preview.windowSwitcher.core")) { set_bool(content, &rc.window_switcher.preview); } else if (!strcasecmp(nodename, "outlines.windowSwitcher.core")) { set_bool(content, &rc.window_switcher.outlines); /* The following three are for backward compatibility only */ } else if (!strcasecmp(nodename, "cycleViewOSD.core")) { set_bool(content, &rc.window_switcher.show); wlr_log(WLR_ERROR, "<cycleViewOSD> is deprecated." " Use <windowSwitcher show=\"\" />"); } else if (!strcasecmp(nodename, "cycleViewPreview.core")) { set_bool(content, &rc.window_switcher.preview); wlr_log(WLR_ERROR, "<cycleViewPreview> is deprecated." " Use <windowSwitcher preview=\"\" />"); } else if (!strcasecmp(nodename, "cycleViewOutlines.core")) { set_bool(content, &rc.window_switcher.outlines); wlr_log(WLR_ERROR, "<cycleViewOutlines> is deprecated." " Use <windowSwitcher outlines=\"\" />"); } else if (!strcasecmp(nodename, "name.names.desktops")) { struct workspace *workspace = znew(*workspace); workspace->name = xstrdup(content); wl_list_append(&rc.workspace_config.workspaces, &workspace->link); } else if (!strcasecmp(nodename, "popupTime.desktops")) { rc.workspace_config.popuptime = atoi(content); } else if (!strcasecmp(nodename, "number.desktops")) { rc.workspace_config.min_nr_workspaces = MAX(1, atoi(content)); } else if (!strcasecmp(nodename, "popupShow.resize")) { if (!strcasecmp(content, "Always")) { rc.resize_indicator = LAB_RESIZE_INDICATOR_ALWAYS; } else if (!strcasecmp(content, "Never")) { rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER; } else if (!strcasecmp(content, "Nonpixel")) { rc.resize_indicator = LAB_RESIZE_INDICATOR_NON_PIXEL; } else { wlr_log(WLR_ERROR, "Invalid value for <resize popupShow />"); } } else if (!strcasecmp(nodename, "mapToOutput.tablet")) { rc.tablet.output_name = xstrdup(content); } else if (!strcasecmp(nodename, "rotate.tablet")) { rc.tablet.rotation = tablet_parse_rotation(atoi(content)); } else if (!strcasecmp(nodename, "left.area.tablet")) { rc.tablet.box.x = tablet_get_dbl_if_positive(content, "left"); } else if (!strcasecmp(nodename, "top.area.tablet")) { rc.tablet.box.y = tablet_get_dbl_if_positive(content, "top"); } else if (!strcasecmp(nodename, "width.area.tablet")) { rc.tablet.box.width = tablet_get_dbl_if_positive(content, "width"); } else if (!strcasecmp(nodename, "height.area.tablet")) { rc.tablet.box.height = tablet_get_dbl_if_positive(content, "height"); } else if (!strcasecmp(nodename, "button.map.tablet")) { button_map_from = tablet_button_from_str(content); } else if (!strcasecmp(nodename, "to.map.tablet")) { if (button_map_from != UINT32_MAX) { uint32_t button_map_to = mousebind_button_from_str(content, NULL); if (button_map_to != UINT32_MAX) { tablet_button_mapping_add(button_map_from, button_map_to); } } else { wlr_log(WLR_ERROR, "Missing 'button' argument for tablet button mapping"); } } } static void process_node(xmlNode *node) { char *content; static char buffer[256]; char *name; content = (char *)node->content; if (xmlIsBlankNode(node)) { return; } name = nodename(node, buffer, sizeof(buffer)); entry(node, name, content); } static void xml_tree_walk(xmlNode *node); static void traverse(xmlNode *n) { xmlAttr *attr; process_node(n); for (attr = n->properties; attr; attr = attr->next) { xml_tree_walk(attr->children); } xml_tree_walk(n->children); } static void xml_tree_walk(xmlNode *node) { for (xmlNode *n = node; n && n->name; n = n->next) { if (!strcasecmp((char *)n->name, "comment")) { continue; } if (!strcasecmp((char *)n->name, "margin")) { in_usable_area_override = true; traverse(n); in_usable_area_override = false; continue; } if (!strcasecmp((char *)n->name, "keybind")) { in_keybind = true; traverse(n); in_keybind = false; continue; } if (!strcasecmp((char *)n->name, "mousebind")) { in_mousebind = true; traverse(n); in_mousebind = false; continue; } if (!strcasecmp((char *)n->name, "touch")) { in_touch = true; traverse(n); in_touch = false; continue; } if (!strcasecmp((char *)n->name, "device")) { in_libinput_category = true; traverse(n); in_libinput_category = false; continue; } if (!strcasecmp((char *)n->name, "regions")) { in_regions = true; traverse(n); in_regions = false; continue; } if (!strcasecmp((char *)n->name, "fields")) { in_window_switcher_field = true; traverse(n); in_window_switcher_field = false; continue; } if (!strcasecmp((char *)n->name, "windowRules")) { in_window_rules = true; traverse(n); in_window_rules = false; continue; } if (!strcasecmp((char *)n->name, "query")) { in_action_query = true; traverse(n); in_action_query = false; continue; } if (!strcasecmp((char *)n->name, "then")) { in_action_then_branch = true; traverse(n); in_action_then_branch = false; continue; } if (!strcasecmp((char *)n->name, "else")) { in_action_else_branch = true; traverse(n); in_action_else_branch = false; continue; } traverse(n); } } /* Exposed in header file to allow unit tests to parse buffers */ void rcxml_parse_xml(struct buf *b) { xmlDoc *d = xmlParseMemory(b->buf, b->len); if (!d) { wlr_log(WLR_ERROR, "error parsing config file"); return; } xml_tree_walk(xmlDocGetRootElement(d)); xmlFreeDoc(d); xmlCleanupParser(); } static void init_font_defaults(struct font *font) { font->size = 10; font->slant = FONT_SLANT_NORMAL; font->weight = FONT_WEIGHT_NORMAL; } static void rcxml_init(void) { static bool has_run; if (!has_run) { wl_list_init(&rc.usable_area_overrides); wl_list_init(&rc.keybinds); wl_list_init(&rc.mousebinds); wl_list_init(&rc.libinput_categories); wl_list_init(&rc.workspace_config.workspaces); wl_list_init(&rc.regions); wl_list_init(&rc.window_switcher.fields); wl_list_init(&rc.window_rules); wl_list_init(&rc.touch_configs); } has_run = true; rc.placement_policy = LAB_PLACE_CENTER; rc.xdg_shell_server_side_deco = true; rc.ssd_keep_border = true; rc.corner_radius = 8; init_font_defaults(&rc.font_activewindow); init_font_defaults(&rc.font_inactivewindow); init_font_defaults(&rc.font_menuitem); init_font_defaults(&rc.font_osd); rc.focus_follow_mouse = false; rc.focus_follow_mouse_requires_movement = true; rc.raise_on_focus = false; rc.doubleclick_time = 500; rc.scroll_factor = 1.0; rc.tablet.output_name = NULL; rc.tablet.rotation = 0; rc.tablet.box = (struct wlr_fbox){0}; tablet_load_default_button_mappings(); rc.repeat_rate = 25; rc.repeat_delay = 600; rc.kb_numlock_enable = true; rc.kb_layout_per_window = false; rc.screen_edge_strength = 20; rc.window_edge_strength = 20; rc.snap_edge_range = 1; rc.snap_top_maximize = true; rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS; rc.window_switcher.show = true; rc.window_switcher.preview = true; rc.window_switcher.outlines = true; rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER; rc.workspace_config.popuptime = INT_MIN; rc.workspace_config.min_nr_workspaces = 1; } static struct { const char *binding, *action, *attribute, *value; } key_combos[] = { { "A-Tab", "NextWindow", NULL, NULL }, { "W-Return", "Execute", "command", "alacritty" }, { "A-F3", "Execute", "command", "bemenu-run" }, { "A-F4", "Close", NULL, NULL }, { "W-a", "ToggleMaximize", NULL, NULL }, { "A-Left", "MoveToEdge", "direction", "left" }, { "A-Right", "MoveToEdge", "direction", "right" }, { "A-Up", "MoveToEdge", "direction", "up" }, { "A-Down", "MoveToEdge", "direction", "down" }, { "W-Left", "SnapToEdge", "direction", "left" }, { "W-Right", "SnapToEdge", "direction", "right" }, { "W-Up", "SnapToEdge", "direction", "up" }, { "W-Down", "SnapToEdge", "direction", "down" }, { "A-Space", "ShowMenu", "menu", "client-menu"}, { "XF86_AudioLowerVolume", "Execute", "command", "amixer sset Master 5%-" }, { "XF86_AudioRaiseVolume", "Execute", "command", "amixer sset Master 5%+" }, { "XF86_AudioMute", "Execute", "command", "amixer sset Master toggle" }, { "XF86_MonBrightnessUp", "Execute", "command", "brightnessctl set +10%" }, { "XF86_MonBrightnessDown", "Execute", "command", "brightnessctl set 10%-" }, { NULL, NULL, NULL, NULL }, }; static void load_default_key_bindings(void) { struct keybind *k; struct action *action; for (int i = 0; key_combos[i].binding; i++) { k = keybind_create(key_combos[i].binding); if (!k) { continue; } action = action_create(key_combos[i].action); wl_list_append(&k->actions, &action->link); if (key_combos[i].attribute && key_combos[i].value) { action_arg_from_xml_node(action, key_combos[i].attribute, key_combos[i].value); } } } /* * `struct mouse_combo` variable description and examples: * * | Variable | Description | Examples * |-----------|----------------------------|--------------------- * | context | context name | Maximize, Root * | button | mousebind button/direction | Left, Up * | event | mousebind action | Click, Scroll * | action | action name | ToggleMaximize, GoToDesktop * | attribute | action attribute | to * | value | action attribute value | left * * <mouse> * <context name="Maximize"> * <mousebind button="Left" action="Click"> * <action name="Focus"/> * <action name="Raise"/> * <action name="ToggleMaximize"/> * </mousebind> * </context> * <context name="Root"> * <mousebind direction="Up" action="Scroll"> * <action name="GoToDesktop" to="left" wrap="yes"/> * </mousebind> * </context> * </mouse> */ static struct mouse_combos { const char *context, *button, *event, *action, *attribute, *value; } mouse_combos[] = { { "Left", "Left", "Drag", "Resize", NULL, NULL}, { "Top", "Left", "Drag", "Resize", NULL, NULL}, { "Bottom", "Left", "Drag", "Resize", NULL, NULL}, { "Right", "Left", "Drag", "Resize", NULL, NULL}, { "TLCorner", "Left", "Drag", "Resize", NULL, NULL}, { "TRCorner", "Left", "Drag", "Resize", NULL, NULL}, { "BRCorner", "Left", "Drag", "Resize", NULL, NULL}, { "BLCorner", "Left", "Drag", "Resize", NULL, NULL}, { "Frame", "A-Left", "Press", "Focus", NULL, NULL}, { "Frame", "A-Left", "Press", "Raise", NULL, NULL}, { "Frame", "A-Left", "Drag", "Move", NULL, NULL}, { "Frame", "A-Right", "Press", "Focus", NULL, NULL}, { "Frame", "A-Right", "Press", "Raise", NULL, NULL}, { "Frame", "A-Right", "Drag", "Resize", NULL, NULL}, { "Titlebar", "Left", "Press", "Focus", NULL, NULL}, { "Titlebar", "Left", "Press", "Raise", NULL, NULL}, { "Titlebar", "Up", "Scroll", "Unfocus", NULL, NULL}, { "Titlebar", "Up", "Scroll", "Shade", NULL, NULL}, { "Titlebar", "Down", "Scroll", "Unshade", NULL, NULL}, { "Titlebar", "Down", "Scroll", "Focus", NULL, NULL}, { "Title", "Left", "Drag", "Move", NULL, NULL }, { "Title", "Left", "DoubleClick", "ToggleMaximize", NULL, NULL }, { "TitleBar", "Right", "Click", "Focus", NULL, NULL}, { "TitleBar", "Right", "Click", "Raise", NULL, NULL}, { "Title", "Right", "Click", "ShowMenu", "menu", "client-menu"}, { "Close", "Left", "Click", "Close", NULL, NULL }, { "Iconify", "Left", "Click", "Iconify", NULL, NULL}, { "Maximize", "Left", "Click", "ToggleMaximize", NULL, NULL}, { "Maximize", "Right", "Click", "ToggleMaximize", "direction", "horizontal"}, { "Maximize", "Middle", "Click", "ToggleMaximize", "direction", "vertical"}, { "WindowMenu", "Left", "Click", "ShowMenu", "menu", "client-menu"}, { "WindowMenu", "Right", "Click", "ShowMenu", "menu", "client-menu"}, { "Root", "Left", "Press", "ShowMenu", "menu", "root-menu"}, { "Root", "Right", "Press", "ShowMenu", "menu", "root-menu"}, { "Root", "Middle", "Press", "ShowMenu", "menu", "root-menu"}, { "Root", "Up", "Scroll", "GoToDesktop", "to", "left"}, { "Root", "Down", "Scroll", "GoToDesktop", "to", "right"}, { "Client", "Left", "Press", "Focus", NULL, NULL}, { "Client", "Left", "Press", "Raise", NULL, NULL}, { "Client", "Right", "Press", "Focus", NULL, NULL}, { "Client", "Right", "Press", "Raise", NULL, NULL}, { "Client", "Middle", "Press", "Focus", NULL, NULL}, { "Client", "Middle", "Press", "Raise", NULL, NULL}, { NULL, NULL, NULL, NULL, NULL, NULL }, }; static void load_default_mouse_bindings(void) { uint32_t count = 0; struct mousebind *m; struct action *action; struct mouse_combos *current; for (int i = 0; mouse_combos[i].context; i++) { current = &mouse_combos[i]; if (i == 0 || strcmp(current->context, mouse_combos[i - 1].context) || strcmp(current->button, mouse_combos[i - 1].button) || strcmp(current->event, mouse_combos[i - 1].event)) { /* Create new mousebind */ m = mousebind_create(current->context); m->mouse_event = mousebind_event_from_str(current->event); if (m->mouse_event == MOUSE_ACTION_SCROLL) { m->direction = mousebind_direction_from_str(current->button, &m->modifiers); } else { m->button = mousebind_button_from_str(current->button, &m->modifiers); } count++; } action = action_create(current->action); wl_list_append(&m->actions, &action->link); /* * Only one attribute/value (of string type) is required for the * built-in binds. If more are required in the future, a * slightly more sophisticated approach will be needed. */ if (current->attribute && current->value) { action_arg_from_xml_node(action, current->attribute, current->value); } } wlr_log(WLR_DEBUG, "Loaded %u merged mousebinds", count); } static void deduplicate_mouse_bindings(void) { uint32_t replaced = 0; uint32_t cleared = 0; struct mousebind *current, *tmp, *existing; wl_list_for_each_safe(existing, tmp, &rc.mousebinds, link) { wl_list_for_each_reverse(current, &rc.mousebinds, link) { if (existing == current) { break; } if (mousebind_the_same(existing, current)) { wl_list_remove(&existing->link); action_list_free(&existing->actions); free(existing); replaced++; break; } } } wl_list_for_each_safe(current, tmp, &rc.mousebinds, link) { if (wl_list_empty(¤t->actions)) { wl_list_remove(¤t->link); free(current); cleared++; } } if (replaced) { wlr_log(WLR_DEBUG, "Replaced %u mousebinds", replaced); } if (cleared) { wlr_log(WLR_DEBUG, "Cleared %u mousebinds", cleared); } } static void deduplicate_key_bindings(void) { uint32_t replaced = 0; uint32_t cleared = 0; struct keybind *current, *tmp, *existing; wl_list_for_each_safe(existing, tmp, &rc.keybinds, link) { wl_list_for_each_reverse(current, &rc.keybinds, link) { if (existing == current) { break; } if (keybind_the_same(existing, current)) { wl_list_remove(&existing->link); action_list_free(&existing->actions); free(existing); replaced++; break; } } } wl_list_for_each_safe(current, tmp, &rc.keybinds, link) { if (wl_list_empty(¤t->actions)) { wl_list_remove(¤t->link); free(current); cleared++; } } if (replaced) { wlr_log(WLR_DEBUG, "Replaced %u keybinds", replaced); } if (cleared) { wlr_log(WLR_DEBUG, "Cleared %u keybinds", cleared); } } static struct { enum window_switcher_field_content content; int width; } fields[] = { { LAB_FIELD_TYPE, 25 }, { LAB_FIELD_TRIMMED_IDENTIFIER, 25 }, { LAB_FIELD_TITLE, 50 }, { LAB_FIELD_NONE, 0 }, }; static void load_default_window_switcher_fields(void) { struct window_switcher_field *field; for (int i = 0; fields[i].content != LAB_FIELD_NONE; i++) { field = znew(*field); field->content = fields[i].content; field->width = fields[i].width; wl_list_append(&rc.window_switcher.fields, &field->link); } } static void post_processing(void) { if (!wl_list_length(&rc.keybinds)) { wlr_log(WLR_INFO, "load default key bindings"); load_default_key_bindings(); } if (!wl_list_length(&rc.mousebinds)) { wlr_log(WLR_INFO, "load default mouse bindings"); load_default_mouse_bindings(); } /* * Replace all earlier bindings by later ones * and clear the ones with an empty action list. * * This is required so users are able to remove * a default binding by using the "None" action. */ deduplicate_key_bindings(); deduplicate_mouse_bindings(); if (!rc.font_activewindow.name) { rc.font_activewindow.name = xstrdup("sans"); } if (!rc.font_inactivewindow.name) { rc.font_inactivewindow.name = xstrdup("sans"); } if (!rc.font_menuitem.name) { rc.font_menuitem.name = xstrdup("sans"); } if (!rc.font_osd.name) { rc.font_osd.name = xstrdup("sans"); } if (!libinput_category_get_default()) { /* So we still allow tap to click by default */ struct libinput_category *l = libinput_category_create(); /* Prevents unused variable warning when compiled without asserts */ (void)l; assert(l && libinput_category_get_default() == l); } int nr_workspaces = wl_list_length(&rc.workspace_config.workspaces); if (nr_workspaces < rc.workspace_config.min_nr_workspaces) { struct workspace *workspace; for (int i = nr_workspaces; i < rc.workspace_config.min_nr_workspaces; i++) { workspace = znew(*workspace); workspace->name = strdup_printf("Workspace %d", i + 1); wl_list_append(&rc.workspace_config.workspaces, &workspace->link); } } if (rc.workspace_config.popuptime == INT_MIN) { rc.workspace_config.popuptime = 1000; } if (!wl_list_length(&rc.window_switcher.fields)) { wlr_log(WLR_INFO, "load default window switcher fields"); load_default_window_switcher_fields(); } } static void rule_destroy(struct window_rule *rule) { wl_list_remove(&rule->link); zfree(rule->identifier); zfree(rule->title); action_list_free(&rule->actions); zfree(rule); } static void validate_actions(void) { struct action *action, *action_tmp; struct keybind *keybind; wl_list_for_each(keybind, &rc.keybinds, link) { wl_list_for_each_safe(action, action_tmp, &keybind->actions, link) { if (!action_is_valid(action)) { wl_list_remove(&action->link); action_free(action); wlr_log(WLR_ERROR, "Removed invalid keybind action"); } } } struct mousebind *mousebind; wl_list_for_each(mousebind, &rc.mousebinds, link) { wl_list_for_each_safe(action, action_tmp, &mousebind->actions, link) { if (!action_is_valid(action)) { wl_list_remove(&action->link); action_free(action); wlr_log(WLR_ERROR, "Removed invalid mousebind action"); } } } struct window_rule *rule; wl_list_for_each(rule, &rc.window_rules, link) { wl_list_for_each_safe(action, action_tmp, &rule->actions, link) { if (!action_is_valid(action)) { wl_list_remove(&action->link); action_free(action); wlr_log(WLR_ERROR, "Removed invalid window rule action"); } } } } static void validate(void) { /* Regions */ struct region *region, *region_tmp; wl_list_for_each_safe(region, region_tmp, &rc.regions, link) { struct wlr_box box = region->percentage; bool invalid = !region->name || box.x < 0 || box.x > 100 || box.y < 0 || box.y > 100 || box.width <= 0 || box.width > 100 || box.height <= 0 || box.height > 100; if (invalid) { wlr_log(WLR_ERROR, "Removing invalid region '%s': %d%% x %d%% @ %d%%,%d%%", region->name, box.width, box.height, box.x, box.y); wl_list_remove(®ion->link); zfree(region->name); free(region); } } /* Window-rule criteria */ struct window_rule *rule, *rule_tmp; wl_list_for_each_safe(rule, rule_tmp, &rc.window_rules, link) { if (!rule->identifier && !rule->title) { wlr_log(WLR_ERROR, "Deleting rule %p as it has no criteria", rule); rule_destroy(rule); } } validate_actions(); } void rcxml_read(const char *filename) { rcxml_init(); struct wl_list paths; if (filename) { /* Honour command line argument -c <filename> */ wl_list_init(&paths); struct path *path = znew(*path); path->string = xstrdup(filename); wl_list_append(&paths, &path->link); } else { paths_config_create(&paths, "rc.xml"); } /* Reading file into buffer before parsing - better for unit tests */ struct buf b; bool should_merge_config = rc.merge_config; struct wl_list *(*iter)(struct wl_list *list); iter = should_merge_config ? paths_get_prev : paths_get_next; /* * This is the equivalent of a wl_list_for_each() which optionally * iterates in reverse depending on 'should_merge_config' * * If not merging, we iterate forwards and break after the first * iteration. * * If merging, we iterate backwards (least important XDG Base Dir first) * and keep going. */ for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) { struct path *path = wl_container_of(elm, path, link); FILE *stream = fopen(path->string, "r"); if (!stream) { continue; } wlr_log(WLR_INFO, "read config file %s", path->string); buf_init(&b); char *line = NULL; size_t len = 0; while (getline(&line, &len, stream) != -1) { char *p = strrchr(line, '\n'); if (p) { *p = '\0'; } buf_add(&b, line); } zfree(line); fclose(stream); rcxml_parse_xml(&b); zfree(b.buf); if (!should_merge_config) { break; } }; paths_destroy(&paths); post_processing(); validate(); } void rcxml_finish(void) { zfree(rc.font_activewindow.name); zfree(rc.font_inactivewindow.name); zfree(rc.font_menuitem.name); zfree(rc.font_osd.name); zfree(rc.theme_name); struct usable_area_override *area, *area_tmp; wl_list_for_each_safe(area, area_tmp, &rc.usable_area_overrides, link) { wl_list_remove(&area->link); zfree(area->output); zfree(area); } struct keybind *k, *k_tmp; wl_list_for_each_safe(k, k_tmp, &rc.keybinds, link) { wl_list_remove(&k->link); action_list_free(&k->actions); zfree(k->keysyms); zfree(k); } struct mousebind *m, *m_tmp; wl_list_for_each_safe(m, m_tmp, &rc.mousebinds, link) { wl_list_remove(&m->link); action_list_free(&m->actions); zfree(m); } struct touch_config_entry *touch_config, *touch_config_tmp; wl_list_for_each_safe(touch_config, touch_config_tmp, &rc.touch_configs, link) { wl_list_remove(&touch_config->link); zfree(touch_config->device_name); zfree(touch_config->output_name); zfree(touch_config); } zfree(rc.tablet.output_name); struct libinput_category *l, *l_tmp; wl_list_for_each_safe(l, l_tmp, &rc.libinput_categories, link) { wl_list_remove(&l->link); zfree(l->name); zfree(l); } struct workspace *w, *w_tmp; wl_list_for_each_safe(w, w_tmp, &rc.workspace_config.workspaces, link) { wl_list_remove(&w->link); zfree(w->name); zfree(w); } regions_destroy(NULL, &rc.regions); struct window_switcher_field *field, *field_tmp; wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) { wl_list_remove(&field->link); zfree(field); } struct window_rule *rule, *rule_tmp; wl_list_for_each_safe(rule, rule_tmp, &rc.window_rules, link) { rule_destroy(rule); } /* Reset state vars for starting fresh when Reload is triggered */ current_usable_area_override = NULL; current_keybind = NULL; current_mousebind = NULL; current_touch = NULL; current_libinput_category = NULL; current_mouse_context = NULL; current_keybind_action = NULL; current_mousebind_action = NULL; current_child_action = NULL; current_view_query = NULL; current_region = NULL; current_field = NULL; current_window_rule = NULL; current_window_rule_action = NULL; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/config/session.c��������������������������������������������������������������������0000664�0000000�0000000�00000007422�14570443012�0016477�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <wlr/util/log.h> #include "common/buf.h" #include "common/dir.h" #include "common/file-helpers.h" #include "common/spawn.h" #include "common/string-helpers.h" #include "config/session.h" #include "labwc.h" static void process_line(char *line) { if (string_null_or_empty(line) || line[0] == '#') { return; } char *key = NULL; char *p = strchr(line, '='); if (!p) { return; } *p = '\0'; key = string_strip(line); struct buf value; buf_init(&value); buf_add(&value, string_strip(++p)); buf_expand_shell_variables(&value); buf_expand_tilde(&value); if (string_null_or_empty(key) || !value.len) { goto error; } setenv(key, value.buf, 1); error: free(value.buf); } /* return true on successful read */ static bool read_environment_file(const char *filename) { char *line = NULL; size_t len = 0; FILE *stream = fopen(filename, "r"); if (!stream) { return false; } wlr_log(WLR_INFO, "read environment file %s", filename); while (getline(&line, &len, stream) != -1) { char *p = strrchr(line, '\n'); if (p) { *p = '\0'; } process_line(line); } free(line); fclose(stream); return true; } static void update_activation_env(const char *env_keys) { if (!getenv("DBUS_SESSION_BUS_ADDRESS")) { /* Prevent accidentally auto-launching a dbus session */ wlr_log(WLR_INFO, "Not updating dbus execution environment: " "DBUS_SESSION_BUS_ADDRESS not set"); return; } wlr_log(WLR_INFO, "Updating dbus execution environment"); char *cmd = strdup_printf("dbus-update-activation-environment %s", env_keys); spawn_async_no_shell(cmd); free(cmd); cmd = strdup_printf("systemctl --user import-environment %s", env_keys); spawn_async_no_shell(cmd); free(cmd); } void session_environment_init(void) { /* * Set default for XDG_CURRENT_DESKTOP so xdg-desktop-portal-wlr is happy. * May be overridden either by already having a value set or by the user * supplied environment file. */ setenv("XDG_CURRENT_DESKTOP", "wlroots", 0); /* * Set default for _JAVA_AWT_WM_NONREPARENTING so that Java applications * such as JetBrains/Intellij Idea do render blank windows and menus * with incorrect offset. See https://github.com/swaywm/sway/issues/595 * May be overridden either by already having a value set or by the user * supplied environment file. */ setenv("_JAVA_AWT_WM_NONREPARENTING", "1", 0); struct wl_list paths; paths_config_create(&paths, "environment"); bool should_merge_config = rc.merge_config; struct wl_list *(*iter)(struct wl_list *list); iter = should_merge_config ? paths_get_prev : paths_get_next; for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) { struct path *path = wl_container_of(elm, path, link); bool success = read_environment_file(path->string); if (success && !should_merge_config) { break; } } paths_destroy(&paths); } void session_autostart_init(void) { /* Update dbus and systemd user environment, each may fail gracefully */ update_activation_env("DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP"); struct wl_list paths; paths_config_create(&paths, "autostart"); bool should_merge_config = rc.merge_config; struct wl_list *(*iter)(struct wl_list *list); iter = should_merge_config ? paths_get_prev : paths_get_next; for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) { struct path *path = wl_container_of(elm, path, link); if (!file_exists(path->string)) { continue; } wlr_log(WLR_INFO, "run autostart file %s", path->string); char *cmd = strdup_printf("sh %s", path->string); spawn_async_no_shell(cmd); free(cmd); if (!should_merge_config) { break; } } paths_destroy(&paths); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/config/tablet.c���������������������������������������������������������������������0000664�0000000�0000000�00000006074�14570443012�0016271�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <linux/input-event-codes.h> #include <stdint.h> #include <strings.h> #include <wlr/util/log.h> #include "config/tablet.h" #include "config/rcxml.h" #include "input/tablet_pad.h" double tablet_get_dbl_if_positive(const char *content, const char *name) { double value = atof(content); if (value < 0) { wlr_log(WLR_ERROR, "Invalid value for tablet area %s", name); return 0; } return value; } enum rotation tablet_parse_rotation(int value) { switch (value) { case 0: return LAB_ROTATE_NONE; case 90: return LAB_ROTATE_90; case 180: return LAB_ROTATE_180; case 270: return LAB_ROTATE_270; default: wlr_log(WLR_ERROR, "Invalid value for tablet rotation: %d", value); break; } return LAB_ROTATE_NONE; } uint32_t tablet_button_from_str(const char *button) { if (!strcasecmp(button, "Tip")) { return BTN_TOOL_PEN; } else if (!strcasecmp(button, "Stylus")) { return BTN_STYLUS; } else if (!strcasecmp(button, "Stylus2")) { return BTN_STYLUS2; } else if (!strcasecmp(button, "Stylus3")) { return BTN_STYLUS3; } else if (!strcasecmp(button, "Pad")) { return LAB_BTN_PAD; } else if (!strcasecmp(button, "Pad2")) { return LAB_BTN_PAD2; } else if (!strcasecmp(button, "Pad3")) { return LAB_BTN_PAD3; } else if (!strcasecmp(button, "Pad4")) { return LAB_BTN_PAD4; } else if (!strcasecmp(button, "Pad5")) { return LAB_BTN_PAD5; } else if (!strcasecmp(button, "Pad6")) { return LAB_BTN_PAD6; } else if (!strcasecmp(button, "Pad7")) { return LAB_BTN_PAD7; } else if (!strcasecmp(button, "Pad8")) { return LAB_BTN_PAD8; } else if (!strcasecmp(button, "Pad9")) { return LAB_BTN_PAD9; } wlr_log(WLR_ERROR, "Invalid value for tablet button: %s", button); return UINT32_MAX; } void tablet_button_mapping_add(uint32_t from, uint32_t to) { struct button_map_entry *entry; for (size_t i = 0; i < rc.tablet.button_map_count; i++) { entry = &rc.tablet.button_map[i]; if (entry->from == from) { entry->to = to; wlr_log(WLR_INFO, "Overwriting button map for 0x%x with 0x%x", from, to); return; } } if (rc.tablet.button_map_count == BUTTON_MAP_MAX) { wlr_log(WLR_ERROR, "Failed to add button mapping: only supporting up to %u mappings", BUTTON_MAP_MAX); return; } wlr_log(WLR_INFO, "Adding button map for 0x%x with 0x%x", from, to); entry = &rc.tablet.button_map[rc.tablet.button_map_count]; entry->from = from; entry->to = to; rc.tablet.button_map_count++; } void tablet_load_default_button_mappings(void) { rc.tablet.button_map_count = 0; tablet_button_mapping_add(BTN_TOOL_PEN, BTN_LEFT); /* Used for the pen tip */ tablet_button_mapping_add(BTN_STYLUS, BTN_RIGHT); tablet_button_mapping_add(BTN_STYLUS2, BTN_MIDDLE); } uint32_t tablet_get_mapped_button(uint32_t src_button) { struct button_map_entry *map_entry; for (size_t i = 0; i < rc.tablet.button_map_count; i++) { map_entry = &rc.tablet.button_map[i]; if (map_entry->from == src_button) { return map_entry->to; } } wlr_log(WLR_DEBUG, "no button map target for 0x%x", src_button); return 0; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/config/touch.c����������������������������������������������������������������������0000664�0000000�0000000�00000001574�14570443012�0016140�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <strings.h> #include <wlr/util/log.h> #include "common/list.h" #include "config/rcxml.h" static struct touch_config_entry * find_default_config(void) { struct touch_config_entry *entry; wl_list_for_each(entry, &rc.touch_configs, link) { if (!entry->device_name) { wlr_log(WLR_INFO, "found default touch configuration"); return entry; } } return NULL; } struct touch_config_entry * touch_find_config_for_device(char *device_name) { wlr_log(WLR_INFO, "find touch configuration for %s\n", device_name); struct touch_config_entry *entry; wl_list_for_each(entry, &rc.touch_configs, link) { if (entry->device_name && !strcasecmp(entry->device_name, device_name)) { wlr_log(WLR_INFO, "found touch configuration for %s\n", device_name); return entry; } } return find_default_config(); } ������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/debug.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000013760�14570443012�0014637�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <wlr/types/wlr_layer_shell_v1.h> #include <wlr/types/wlr_scene.h> #include "common/graphic-helpers.h" #include "common/scene-helpers.h" #include "debug.h" #include "labwc.h" #include "node.h" #include "ssd.h" #include "view.h" #include "workspaces.h" #define HEADER_CHARS "------------------------------" #define INDENT_SIZE 3 #define LEFT_COL_SPACE 35 #define IGNORE_SSD true #define IGNORE_MENU true #define IGNORE_OSD_PREVIEW_OUTLINE true static struct view *last_view; static const char * get_node_type(struct wlr_scene_node *node) { switch (node->type) { case WLR_SCENE_NODE_TREE: if (!node->parent) { return "root"; } return "tree"; case WLR_SCENE_NODE_RECT: return "rect"; case WLR_SCENE_NODE_BUFFER: if (lab_wlr_surface_from_node(node)) { return "surface"; } return "buffer"; } return "error"; } static const char * get_layer_name(uint32_t layer) { switch (layer) { case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: return "output->layer-background"; case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: return "output->layer-bottom"; case ZWLR_LAYER_SHELL_V1_LAYER_TOP: return "output->layer-top"; case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: return "output->layer-overlay"; default: abort(); } } static const char * get_view_part(struct view *view, struct wlr_scene_node *node) { static char view_name[LEFT_COL_SPACE]; if (!view) { return NULL; } if (node == &view->scene_tree->node) { const char *app_id = view_get_string_prop(view, "app_id"); if (!app_id) { return "view"; } snprintf(view_name, sizeof(view_name), "view (%s)", app_id); return view_name; } if (node == view->scene_node) { return "view->scene_node"; } if (view->resize_indicator.tree && node == &view->resize_indicator.tree->node) { /* Created on-demand */ return "view->resize_indicator"; } return ssd_debug_get_node_name(view->ssd, node); } static const char * get_special(struct server *server, struct wlr_scene_node *node) { if (node == &server->scene->tree.node) { return "server->scene"; } if (node == &server->menu_tree->node) { return "server->menu_tree"; } if (node == &server->view_tree->node) { return "server->view_tree"; } if (node == &server->view_tree_always_on_bottom->node) { return "server->always_on_bottom"; } if (node == &server->view_tree_always_on_top->node) { return "server->always_on_top"; } if (node->parent == server->view_tree) { struct workspace *workspace; wl_list_for_each(workspace, &server->workspaces, link) { if (&workspace->tree->node == node) { return workspace->name; } } return "unknown workspace"; } if (node->parent == &server->scene->tree) { struct output *output; wl_list_for_each(output, &server->outputs, link) { if (node == &output->osd_tree->node) { return "output->osd_tree"; } if (node == &output->layer_popup_tree->node) { return "output->layer_popup_tree"; } for (int i = 0; i < 4; i++) { if (node == &output->layer_tree[i]->node) { return get_layer_name(i); } } if (node == &output->session_lock_tree->node) { return "output->session_lock_tree"; } } } if (node == &server->xdg_popup_tree->node) { return "server->xdg_popup_tree"; } if (node == &server->seat.drag.icons->node) { return "seat->drag.icons"; } if (server->seat.region_overlay.tree && node == &server->seat.region_overlay.tree->node) { /* Created on-demand */ return "seat->region_overlay"; } if (server->osd_state.preview_outline && node == &server->osd_state.preview_outline->tree->node) { /* Created on-demand */ return "osd_state->preview_outline"; } #if HAVE_XWAYLAND if (node == &server->unmanaged_tree->node) { return "server->unmanaged_tree"; } #endif struct wlr_scene_tree *grand_parent = node->parent ? node->parent->node.parent : NULL; if (grand_parent == server->view_tree && node->data) { last_view = node_view_from_node(node); } if (node->parent == server->view_tree_always_on_top && node->data) { last_view = node_view_from_node(node); } const char *view_part = get_view_part(last_view, node); if (view_part) { return view_part; } return get_node_type(node); } struct pad { uint8_t left; uint8_t right; }; static struct pad get_center_padding(const char *text, uint8_t max_width) { struct pad pad; size_t text_len = strlen(text); pad.left = (double)(max_width - text_len) / 2 + 0.5f; pad.right = max_width - pad.left - text_len; return pad; } static void dump_tree(struct server *server, struct wlr_scene_node *node, int pos, int x, int y) { const char *type = get_special(server, node); if (pos) { printf("%*c+-- ", pos, ' '); } else { struct pad node_pad = get_center_padding("Node", 16); printf(" %*c %4s %4s %*c%s\n", LEFT_COL_SPACE + 4, ' ', "X", "Y", node_pad.left, ' ', "Node"); printf(" %*c %.4s %.4s %.16s\n", LEFT_COL_SPACE + 4, ' ', HEADER_CHARS, HEADER_CHARS, HEADER_CHARS); printf(" "); } int max_width = LEFT_COL_SPACE - pos; int padding = max_width - strlen(type); if (padding < 0) { padding = 0; } if (!pos) { padding += 3; } printf("%.*s %*c %4d %4d [%p]\n", max_width - 1, type, padding, ' ', x, y, node); if ((IGNORE_MENU && node == &server->menu_tree->node) || (IGNORE_SSD && last_view && ssd_debug_is_root_node(last_view->ssd, node)) || (IGNORE_OSD_PREVIEW_OUTLINE && server->osd_state.preview_outline && node == &server->osd_state.preview_outline->tree->node)) { printf("%*c%s\n", pos + 4 + INDENT_SIZE, ' ', "<skipping children>"); return; } if (node->type == WLR_SCENE_NODE_TREE) { struct wlr_scene_node *child; struct wlr_scene_tree *tree = wlr_scene_tree_from_node(node); wl_list_for_each(child, &tree->children, link) { dump_tree(server, child, pos + INDENT_SIZE, x + child->x, y + child->y); } } } void debug_dump_scene(struct server *server) { printf("\n"); dump_tree(server, &server->scene->tree.node, 0, 0, 0); printf("\n"); /* * Reset last_view so we don't access a * potentially free'd pointer on the next call */ last_view = NULL; } ����������������labwc-0.7.1/src/decorations/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14570443012�0015710�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/decorations/kde-deco.c��������������������������������������������������������������0000664�0000000�0000000�00000007217�14570443012�0017536�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <wlr/types/wlr_server_decoration.h> #include "common/list.h" #include "common/mem.h" #include "decorations.h" #include "labwc.h" #include "view.h" static struct wl_list decorations; static struct wlr_server_decoration_manager *kde_deco_mgr; struct kde_deco { struct wl_list link; /* decorations */ struct wlr_server_decoration *wlr_kde_decoration; struct view *view; struct wl_listener mode; struct wl_listener destroy; }; static void handle_destroy(struct wl_listener *listener, void *data) { struct kde_deco *kde_deco = wl_container_of(listener, kde_deco, destroy); wl_list_remove(&kde_deco->destroy.link); wl_list_remove(&kde_deco->mode.link); wl_list_remove(&kde_deco->link); free(kde_deco); } static void handle_mode(struct wl_listener *listener, void *data) { struct kde_deco *kde_deco = wl_container_of(listener, kde_deco, mode); if (!kde_deco->view) { return; } enum wlr_server_decoration_manager_mode client_mode = kde_deco->wlr_kde_decoration->mode; switch (client_mode) { case WLR_SERVER_DECORATION_MANAGER_MODE_SERVER: kde_deco->view->ssd_preference = LAB_SSD_PREF_SERVER; break; case WLR_SERVER_DECORATION_MANAGER_MODE_NONE: case WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT: kde_deco->view->ssd_preference = LAB_SSD_PREF_CLIENT; break; default: wlr_log(WLR_ERROR, "Unspecified kde decoration variant " "requested: %u", client_mode); } view_set_decorations(kde_deco->view, kde_deco->view->ssd_preference == LAB_SSD_PREF_SERVER); } static void handle_new_server_decoration(struct wl_listener *listener, void *data) { struct wlr_server_decoration *wlr_deco = data; struct kde_deco *kde_deco = znew(*kde_deco); kde_deco->wlr_kde_decoration = wlr_deco; if (wlr_deco->surface) { /* * Depending on the application event flow, the supplied * wlr_surface may already have been set up as a xdg_surface * or not (e.g. for GTK4). In the second case, the xdg.c * new_surface handler will try to set the view via * kde_server_decoration_set_view(). */ struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(wlr_deco->surface); if (xdg_surface && xdg_surface->data) { kde_deco->view = (struct view *)xdg_surface->data; handle_mode(&kde_deco->mode, wlr_deco); } } wl_signal_add(&wlr_deco->events.destroy, &kde_deco->destroy); kde_deco->destroy.notify = handle_destroy; wl_signal_add(&wlr_deco->events.mode, &kde_deco->mode); kde_deco->mode.notify = handle_mode; wl_list_append(&decorations, &kde_deco->link); } void kde_server_decoration_set_view(struct view *view, struct wlr_surface *surface) { struct kde_deco *kde_deco; wl_list_for_each(kde_deco, &decorations, link) { if (kde_deco->wlr_kde_decoration->surface == surface) { if (!kde_deco->view) { kde_deco->view = view; handle_mode(&kde_deco->mode, kde_deco->wlr_kde_decoration); } return; } } } void kde_server_decoration_update_default(void) { assert(kde_deco_mgr); wlr_server_decoration_manager_set_default_mode(kde_deco_mgr, rc.xdg_shell_server_side_deco ? WLR_SERVER_DECORATION_MANAGER_MODE_SERVER : WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT); } void kde_server_decoration_init(struct server *server) { assert(!kde_deco_mgr); kde_deco_mgr = wlr_server_decoration_manager_create(server->wl_display); if (!kde_deco_mgr) { wlr_log(WLR_ERROR, "unable to create the kde server deco manager"); exit(EXIT_FAILURE); } wl_list_init(&decorations); kde_server_decoration_update_default(); wl_signal_add(&kde_deco_mgr->events.new_decoration, &server->kde_server_decoration); server->kde_server_decoration.notify = handle_new_server_decoration; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/decorations/meson.build�������������������������������������������������������������0000664�0000000�0000000�00000000072�14570443012�0020051�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc_sources += files( 'kde-deco.c', 'xdg-deco.c', ) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/decorations/xdg-deco.c��������������������������������������������������������������0000664�0000000�0000000�00000005665�14570443012�0017562�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <wlr/types/wlr_xdg_decoration_v1.h> #include "common/mem.h" #include "decorations.h" #include "labwc.h" #include "view.h" struct xdg_deco { struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_decoration; struct view *view; struct wl_listener destroy; struct wl_listener request_mode; }; static void xdg_deco_destroy(struct wl_listener *listener, void *data) { struct xdg_deco *xdg_deco = wl_container_of(listener, xdg_deco, destroy); wl_list_remove(&xdg_deco->destroy.link); wl_list_remove(&xdg_deco->request_mode.link); free(xdg_deco); } static void xdg_deco_request_mode(struct wl_listener *listener, void *data) { struct xdg_deco *xdg_deco = wl_container_of(listener, xdg_deco, request_mode); enum wlr_xdg_toplevel_decoration_v1_mode client_mode = xdg_deco->wlr_xdg_decoration->requested_mode; switch (client_mode) { case WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: xdg_deco->view->ssd_preference = LAB_SSD_PREF_SERVER; break; case WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: xdg_deco->view->ssd_preference = LAB_SSD_PREF_CLIENT; break; case WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE: xdg_deco->view->ssd_preference = LAB_SSD_PREF_UNSPEC; client_mode = rc.xdg_shell_server_side_deco ? WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; break; default: wlr_log(WLR_ERROR, "Unspecified xdg decoration variant " "requested: %u", client_mode); } wlr_xdg_toplevel_decoration_v1_set_mode(xdg_deco->wlr_xdg_decoration, client_mode); view_set_decorations(xdg_deco->view, client_mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); } static void xdg_toplevel_decoration(struct wl_listener *listener, void *data) { struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_decoration = data; struct wlr_xdg_surface *xdg_surface = wlr_xdg_decoration->toplevel->base; if (!xdg_surface || !xdg_surface->data) { wlr_log(WLR_ERROR, "Invalid surface supplied for xdg decorations"); return; } struct xdg_deco *xdg_deco = znew(*xdg_deco); xdg_deco->wlr_xdg_decoration = wlr_xdg_decoration; xdg_deco->view = (struct view *)xdg_surface->data; wl_signal_add(&wlr_xdg_decoration->events.destroy, &xdg_deco->destroy); xdg_deco->destroy.notify = xdg_deco_destroy; wl_signal_add(&wlr_xdg_decoration->events.request_mode, &xdg_deco->request_mode); xdg_deco->request_mode.notify = xdg_deco_request_mode; xdg_deco_request_mode(&xdg_deco->request_mode, wlr_xdg_decoration); } void xdg_server_decoration_init(struct server *server) { struct wlr_xdg_decoration_manager_v1 *xdg_deco_mgr = NULL; xdg_deco_mgr = wlr_xdg_decoration_manager_v1_create(server->wl_display); if (!xdg_deco_mgr) { wlr_log(WLR_ERROR, "unable to create the XDG deco manager"); exit(EXIT_FAILURE); } wl_signal_add(&xdg_deco_mgr->events.new_toplevel_decoration, &server->xdg_toplevel_decoration); server->xdg_toplevel_decoration.notify = xdg_toplevel_decoration; } ���������������������������������������������������������������������������labwc-0.7.1/src/desktop.c���������������������������������������������������������������������������0000664�0000000�0000000�00000030016�14570443012�0015213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include "config.h" #include <assert.h> #include "common/scene-helpers.h" #include "dnd.h" #include "labwc.h" #include "layers.h" #include "node.h" #include "ssd.h" #include "view.h" #include "window-rules.h" #include "workspaces.h" #include "xwayland.h" #if HAVE_XWAYLAND #include <wlr/xwayland.h> #endif void desktop_arrange_all_views(struct server *server) { /* * Adjust window positions/sizes. Skip views with no size since * we can't do anything useful with them; they will presumably * be initialized with valid positions/sizes later. * * We do not simply check view->mapped/been_mapped here because * views can have maximized/fullscreen geometry applied while * still unmapped. We do want to adjust the geometry of those * views. */ struct view *view; wl_list_for_each(view, &server->views, link) { if (!wlr_box_empty(&view->pending)) { view_adjust_for_layout_change(view); } } } void desktop_focus_view(struct view *view, bool raise) { assert(view); /* * Guard against views with no mapped surfaces when handling * 'request_activate' and 'request_minimize'. */ if (!view->surface) { return; } if (view->minimized) { /* * Unminimizing will map the view which triggers a call to this * function again (with raise=true). */ view_minimize(view, false); return; } if (!view->mapped) { return; } /* * Switch workspace if necessary to make the view visible * (unnecessary for "always on {top,bottom}" views). */ if (!view_is_always_on_top(view) && !view_is_always_on_bottom(view)) { workspaces_switch_to(view->workspace, /*update_focus*/ false); } /* * Give input focus, even if the view claims not to want it (see * view->impl->wants_focus). This is a workaround for so-called * "globally active" X11 views (MATLAB known to be one such) * that expect to be able to control focus themselves, but can't * under labwc since it's disallowed at the wlroots level. */ struct seat *seat = &view->server->seat; if (view->surface != seat->seat->keyboard_state.focused_surface) { seat_focus_surface(seat, view->surface); } if (raise) { view_move_to_front(view); } } /* TODO: focus layer-shell surfaces also? */ void desktop_focus_view_or_surface(struct seat *seat, struct view *view, struct wlr_surface *surface, bool raise) { assert(view || surface); if (view) { desktop_focus_view(view, raise); #if HAVE_XWAYLAND } else { struct wlr_xwayland_surface *xsurface = wlr_xwayland_surface_try_from_wlr_surface(surface); if (xsurface && wlr_xwayland_or_surface_wants_focus(xsurface)) { seat_focus_surface(seat, surface); } #endif } } static struct wl_list * get_prev_item(struct wl_list *item) { return item->prev; } static struct wl_list * get_next_item(struct wl_list *item) { return item->next; } static struct view * first_view(struct server *server) { struct wlr_scene_node *node; struct wl_list *list_head = &server->workspace_current->tree->children; wl_list_for_each_reverse(node, list_head, link) { if (!node->data) { /* We found some non-view, most likely the region overlay */ continue; } struct view *view = node_view_from_node(node); if (view_is_focusable(view)) { return view; } } return NULL; } struct view * desktop_cycle_view(struct server *server, struct view *start_view, enum lab_cycle_dir dir) { /* * Views are listed in stacking order, topmost first. Usually * the topmost view is already focused, so we pre-select the * view second from the top: * * View #1 (on top, currently focused) * View #2 (pre-selected) * View #3 * ... * * This assumption doesn't always hold with XWayland views, * where a main application window may be focused but an * focusable sub-view (e.g. an about dialog) may still be on * top of it. In that case, we pre-select the sub-view: * * Sub-view of #1 (on top, pre-selected) * Main view #1 (currently focused) * Main view #2 * ... * * The general rule is: * * - Pre-select the top view if NOT already focused * - Otherwise select the view second from the top */ /* Make sure to have all nodes in their actual ordering */ osd_preview_restore(server); if (!start_view) { start_view = first_view(server); if (!start_view || start_view != server->active_view) { return start_view; /* may be NULL */ } } struct view *view = start_view; struct wlr_scene_node *node = &view->scene_tree->node; assert(node->parent); struct wl_list *list_head = &node->parent->children; struct wl_list *list_item = &node->link; struct wl_list *(*iter)(struct wl_list *list); /* Scene nodes are ordered like last node == displayed topmost */ iter = dir == LAB_CYCLE_DIR_FORWARD ? get_prev_item : get_next_item; do { list_item = iter(list_item); if (list_item == list_head) { /* Start / End of list reached. Roll over */ list_item = iter(list_item); } node = wl_container_of(list_item, node, link); if (!node->data) { /* We found some non-view, most likely the region overlay */ view = NULL; continue; } view = node_view_from_node(node); enum property skip = window_rules_get_property(view, "skipWindowSwitcher"); if (view_is_focusable(view) && skip != LAB_PROP_TRUE) { return view; } } while (view != start_view); /* No focusable views found, including the one we started with */ return NULL; } struct view * desktop_topmost_focusable_view(struct server *server) { struct wlr_surface *prev = server->seat.seat->keyboard_state.focused_surface; struct view *view; struct wl_list *node_list; struct wlr_scene_node *node; node_list = &server->workspace_current->tree->children; wl_list_for_each_reverse(node, node_list, link) { if (!node->data) { /* We found some non-view, most likely the region overlay */ continue; } view = node_view_from_node(node); if (view->mapped && view_is_focusable_from(view, prev)) { return view; } } return NULL; } void desktop_focus_topmost_view(struct server *server) { struct view *view = desktop_topmost_focusable_view(server); if (view) { desktop_focus_view(view, /*raise*/ true); } else { /* * Defocus previous focused surface/view if no longer * focusable (e.g. unmapped or on a different workspace). */ seat_focus_surface(&server->seat, NULL); } } void desktop_focus_output(struct output *output) { if (!output_is_usable(output) || output->server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { return; } struct view *view; struct wlr_scene_node *node; struct wlr_output_layout *layout = output->server->output_layout; struct wl_list *list_head = &output->server->workspace_current->tree->children; wl_list_for_each_reverse(node, list_head, link) { if (!node->data) { continue; } view = node_view_from_node(node); if (!view_is_focusable(view)) { continue; } if (wlr_output_layout_intersects(layout, output->wlr_output, &view->current)) { desktop_focus_view(view, /*raise*/ false); wlr_cursor_warp(output->server->seat.cursor, NULL, view->current.x + view->current.width / 2, view->current.y + view->current.height / 2); cursor_update_focus(output->server); return; } } /* No view found on desired output */ struct wlr_box layout_box; wlr_output_layout_get_box(output->server->output_layout, output->wlr_output, &layout_box); wlr_cursor_warp(output->server->seat.cursor, NULL, layout_box.x + output->usable_area.x + output->usable_area.width / 2, layout_box.y + output->usable_area.y + output->usable_area.height / 2); cursor_update_focus(output->server); } void desktop_update_top_layer_visiblity(struct server *server) { struct view *view; struct output *output; uint32_t top = ZWLR_LAYER_SHELL_V1_LAYER_TOP; /* Enable all top layers */ wl_list_for_each(output, &server->outputs, link) { if (!output_is_usable(output)) { continue; } wlr_scene_node_set_enabled(&output->layer_tree[top]->node, true); } /* And disable them again when there is a view in fullscreen */ enum lab_view_criteria criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE | LAB_VIEW_CRITERIA_FULLSCREEN; for_each_view(view, &server->views, criteria) { if (!output_is_usable(view->output)) { continue; } wlr_scene_node_set_enabled(&view->output->layer_tree[top]->node, false); } } static struct wlr_surface * get_surface_from_layer_node(struct wlr_scene_node *node) { assert(node->data); struct node_descriptor *desc = (struct node_descriptor *)node->data; if (desc->type == LAB_NODE_DESC_LAYER_SURFACE) { struct lab_layer_surface *surface; surface = node_layer_surface_from_node(node); return surface->scene_layer_surface->layer_surface->surface; } else if (desc->type == LAB_NODE_DESC_LAYER_POPUP) { struct lab_layer_popup *popup; popup = node_layer_popup_from_node(node); return popup->wlr_popup->base->surface; } return NULL; } static bool is_layer_descendant(struct wlr_scene_node *node) { goto start; while (node) { struct node_descriptor *desc = node->data; if (desc && desc->type == LAB_NODE_DESC_LAYER_SURFACE) { return true; } start: node = node->parent ? &node->parent->node : NULL; } return false; } /* TODO: make this less big and scary */ struct cursor_context get_cursor_context(struct server *server) { struct cursor_context ret = {.type = LAB_SSD_NONE}; struct wlr_cursor *cursor = server->seat.cursor; /* Prevent drag icons to be on top of the hitbox detection */ if (server->seat.drag.active) { dnd_icons_show(&server->seat, false); } struct wlr_scene_node *node = wlr_scene_node_at(&server->scene->tree.node, cursor->x, cursor->y, &ret.sx, &ret.sy); if (server->seat.drag.active) { dnd_icons_show(&server->seat, true); } ret.node = node; if (!node) { ret.type = LAB_SSD_ROOT; return ret; } #if HAVE_XWAYLAND if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_surface *surface = lab_wlr_surface_from_node(node); if (node->parent == server->unmanaged_tree) { ret.type = LAB_SSD_UNMANAGED; ret.surface = surface; return ret; } } #endif while (node) { struct node_descriptor *desc = node->data; if (desc) { switch (desc->type) { case LAB_NODE_DESC_VIEW: case LAB_NODE_DESC_XDG_POPUP: ret.view = desc->data; ret.type = ssd_get_part_type(ret.view->ssd, ret.node); if (ret.type == LAB_SSD_CLIENT) { ret.surface = lab_wlr_surface_from_node(ret.node); } return ret; case LAB_NODE_DESC_SSD_BUTTON: { /* * Always return the top scene node for SSD * buttons */ struct ssd_button *button = node_ssd_button_from_node(node); ret.node = node; ret.type = ssd_button_get_type(button); ret.view = ssd_button_get_view(button); return ret; } case LAB_NODE_DESC_LAYER_SURFACE: ret.node = node; ret.type = LAB_SSD_LAYER_SURFACE; ret.surface = get_surface_from_layer_node(node); return ret; case LAB_NODE_DESC_LAYER_POPUP: ret.node = node; ret.type = LAB_SSD_CLIENT; ret.surface = get_surface_from_layer_node(node); return ret; case LAB_NODE_DESC_MENUITEM: /* Always return the top scene node for menu items */ ret.node = node; ret.type = LAB_SSD_MENU; return ret; case LAB_NODE_DESC_NODE: case LAB_NODE_DESC_TREE: break; } } /* Edge-case nodes without node-descriptors */ if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_surface *surface = lab_wlr_surface_from_node(node); if (surface) { if (wlr_layer_surface_v1_try_from_wlr_surface(surface)) { ret.type = LAB_SSD_LAYER_SURFACE; } if (is_layer_descendant(node)) { /* * layer-shell subsurfaces need to be * able to receive pointer actions. * * Test by running * `gtk-layer-demo -k exclusive`, then * open the 'set margin' dialog and try * setting the margin with the pointer. */ ret.surface = surface; ret.type = LAB_SSD_LAYER_SUBSURFACE; return ret; } } } /* node->parent is always a *wlr_scene_tree */ node = node->parent ? &node->parent->node : NULL; } /* * TODO: add node descriptors for the OSDs and reinstate * wlr_log(WLR_DEBUG, "Unknown node detected"); */ return ret; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/dnd.c�������������������������������������������������������������������������������0000664�0000000�0000000�00000006465�14570443012�0014322�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <wlr/types/wlr_data_device.h> #include <wlr/types/wlr_scene.h> #include <wlr/util/log.h> #include "dnd.h" #include "input/cursor.h" #include "labwc.h" /* for struct seat */ #include "view.h" /* Internal DnD handlers */ static void handle_drag_request(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, drag.events.request); 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); } else { wlr_data_source_destroy(event->drag->source); wlr_log(WLR_ERROR, "wrong source for drag request"); } } static void handle_drag_start(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, drag.events.start); assert(!seat->drag.active); struct wlr_drag *drag = data; seat->drag.active = true; seat_reset_pressed(seat); if (drag->icon) { /* Cleans up automatically on drag->icon->events.destroy */ wlr_scene_drag_icon_create(seat->drag.icons, drag->icon); wlr_scene_node_set_enabled(&seat->drag.icons->node, true); } wl_signal_add(&drag->events.destroy, &seat->drag.events.destroy); } static void handle_drag_destroy(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, drag.events.destroy); assert(seat->drag.active); seat->drag.active = false; wl_list_remove(&seat->drag.events.destroy.link); wlr_scene_node_set_enabled(&seat->drag.icons->node, false); /* * The default focus behaviour at the end of a dnd operation is that the * window that originally had keyboard-focus retains that focus. This is * consistent with the default behaviour of openbox and mutter. * * However, if the 'focus/followMouse' option is enabled we need to * refocus the current surface under the cursor because keyboard focus * is not changed during drag. */ if (!rc.focus_follow_mouse) { return; } struct cursor_context ctx = get_cursor_context(seat->server); if (!ctx.surface) { return; } seat_focus_surface(seat, NULL); seat_focus_surface(seat, ctx.surface); if (ctx.view && rc.raise_on_focus) { view_move_to_front(ctx.view); } } /* Public API */ void dnd_init(struct seat *seat) { seat->drag.icons = wlr_scene_tree_create(&seat->server->scene->tree); wlr_scene_node_set_enabled(&seat->drag.icons->node, false); seat->drag.events.request.notify = handle_drag_request; seat->drag.events.start.notify = handle_drag_start; seat->drag.events.destroy.notify = handle_drag_destroy; wl_signal_add(&seat->seat->events.request_start_drag, &seat->drag.events.request); wl_signal_add(&seat->seat->events.start_drag, &seat->drag.events.start); /* * destroy.notify is listened to in handle_drag_start() and reset in * handle_drag_destroy() */ } void dnd_icons_show(struct seat *seat, bool show) { wlr_scene_node_set_enabled(&seat->drag.icons->node, show); } void dnd_icons_move(struct seat *seat, double x, double y) { wlr_scene_node_set_position(&seat->drag.icons->node, x, y); } void dnd_finish(struct seat *seat) { wlr_scene_node_destroy(&seat->drag.icons->node); wl_list_remove(&seat->drag.events.request.link); wl_list_remove(&seat->drag.events.start.link); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/edges.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000044335�14570443012�0014642�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <limits.h> #include <pixman.h> #include <wlr/util/edges.h> #include <wlr/util/box.h> #include "common/border.h" #include "common/macros.h" #include "config/rcxml.h" #include "edges.h" #include "labwc.h" #include "view.h" #include "node.h" static void edges_for_target_geometry(struct border *edges, struct view *view, struct wlr_box target) { struct border border = ssd_get_margin(view->ssd); /* Use the effective height to properly handle shaded views */ int eff_height = view->shaded ? 0 : target.height; edges->left = target.x - border.left - rc.gap; edges->top = target.y - border.top - rc.gap; edges->right = target.x + target.width + border.right + rc.gap; edges->bottom = target.y + eff_height + border.bottom + rc.gap; } void edges_initialize(struct border *edges) { assert(edges); edges->top = INT_MIN; edges->right = INT_MAX; edges->bottom = INT_MAX; edges->left = INT_MIN; } static inline struct edge build_edge(struct border region, enum wlr_edges direction, int pad) { struct edge edge = { 0 }; switch (direction) { case WLR_EDGE_LEFT: edge.offset = clipped_sub(region.left, pad); edge.min = region.top; edge.max = region.bottom; break; case WLR_EDGE_RIGHT: edge.offset = clipped_add(region.right, pad); edge.min = region.top; edge.max = region.bottom; break; case WLR_EDGE_TOP: edge.offset = clipped_sub(region.top, pad); edge.min = region.left; edge.max = region.right; break; case WLR_EDGE_BOTTOM: edge.offset = clipped_add(region.bottom, pad); edge.min = region.left; edge.max = region.right; break; case WLR_EDGE_NONE: /* Should never be reached */ wlr_log(WLR_ERROR, "invalid direction"); abort(); } return edge; } static inline bool is_lesser(enum wlr_edges direction) { return direction == WLR_EDGE_LEFT || direction == WLR_EDGE_TOP; } static inline struct edge build_visible_edge(struct border region, enum wlr_edges direction, int pad, uint32_t edges_visible) { struct edge edge = build_edge(region, direction, pad); if (!(edges_visible & direction)) { edge.offset = is_lesser(direction) ? INT_MIN : INT_MAX; } return edge; } static void validate_single_region_edge(int *valid_edge, struct border view, struct border target, struct border region, edge_validator_t validator, enum wlr_edges direction, uint32_t edges_visible) { /* * When a view snaps to another while moving to its target, it can do * so in two ways: a view edge can snap to an "opposing" edge of the * region (left <-> right, top <-> bottom) or to an "aligned" edge * (left <-> left, right <-> right, top <-> top, bottom <-> bottom). * * When a view hits the opposing edge of a region, it should be * separated by any configured gap and will resist *entry* into the * region; when a view hits the aligned edge, it should not be * separated by a gap and will resist *departure* from the region. The * view and its target already include necessary padding to reflect the * gap. The region does not. To make sure the "aligned" edges are * properly aligned with respect to the configured gap, add padding to * the region borders for aligned edges only. */ enum wlr_edges opposing = WLR_EDGE_NONE; switch (direction) { case WLR_EDGE_TOP: opposing = WLR_EDGE_BOTTOM; break; case WLR_EDGE_BOTTOM: opposing = WLR_EDGE_TOP; break; case WLR_EDGE_LEFT: opposing = WLR_EDGE_RIGHT; break; case WLR_EDGE_RIGHT: opposing = WLR_EDGE_LEFT; break; case WLR_EDGE_NONE: /* Should never be reached */ assert(false); return; } validator(valid_edge, build_edge(view, direction, 0), build_edge(target, direction, 0), build_visible_edge(region, opposing, 0, edges_visible), build_visible_edge(region, direction, rc.gap, edges_visible), is_lesser(direction)); } static void validate_edges(struct border *valid_edges, struct border view, struct border target, struct border region, uint32_t edges_visible, edge_validator_t validator) { /* Check for edges encountered during movement of left edge */ validate_single_region_edge(&valid_edges->left, view, target, region, validator, WLR_EDGE_LEFT, edges_visible); /* Check for edges encountered during movement of right edge */ validate_single_region_edge(&valid_edges->right, view, target, region, validator, WLR_EDGE_RIGHT, edges_visible); /* Check for edges encountered during movement of top edge */ validate_single_region_edge(&valid_edges->top, view, target, region, validator, WLR_EDGE_TOP, edges_visible); /* Check for edges encountered during movement of bottom edge */ validate_single_region_edge(&valid_edges->bottom, view, target, region, validator, WLR_EDGE_BOTTOM, edges_visible); } static void validate_single_output_edge(int *valid_edge, struct border view, struct border target, struct border region, edge_validator_t validator, enum wlr_edges direction) { static struct border unbounded = { .top = INT_MIN, .right = INT_MAX, .bottom = INT_MAX, .left = INT_MIN, }; validator(valid_edge, build_edge(view, direction, 0), build_edge(target, direction, 0), build_edge(region, direction, 0), build_edge(unbounded, direction, 0), is_lesser(direction)); } static void validate_output_edges(struct border *valid_edges, struct border view, struct border target, struct wlr_box usable, edge_validator_t validator) { /* * When a view snaps to an output that contains it, it can be * transformed into either of two equivalent problems: * * 1. The output region can be treated as if it were bounded by four * half-planes, one sharing each edge of the view and extending * infinitely *away* from the output. The moving view should then be * tested as it encounters the "opposing" edge of each external region. * * 2. The output region can be treated as if it were composed of four * half-planes, one sharing each edge of the view and extending * infinitely to *overlap* the output. The moving view should then be * tested as it encounters the "aligned" edge of each overlapping * region. * * Either one of these problems can be realized by four calls to * validate_edges with suitably defined half-plane regions, but most of * the work in those validations will just be comparing invalid * infinite edges. * * To save a bit of effort, just choose Problem 1 and directly validate * only the non-infinite edges. */ struct border output = { .top = usable.y, .right = usable.x + usable.width, .bottom = usable.y + usable.height, .left = usable.x, }; /* Left edge encounters a half-infinite region to the left of the output */ validate_single_output_edge(&valid_edges->left, view, target, output, validator, WLR_EDGE_LEFT); /* Right edge encounters a half-infinite region to the right of the output */ validate_single_output_edge(&valid_edges->right, view, target, output, validator, WLR_EDGE_RIGHT); /* Top edge encounters a half-infinite region above the output */ validate_single_output_edge(&valid_edges->top, view, target, output, validator, WLR_EDGE_TOP); /* Bottom edge encounters a half-infinite region below the output */ validate_single_output_edge(&valid_edges->bottom, view, target, output, validator, WLR_EDGE_BOTTOM); } /* Test if parts of the current view is covered by the remaining space in the region */ static void subtract_view_from_space(struct view *view, pixman_region32_t *available) { struct wlr_box view_size = ssd_max_extents(view); pixman_box32_t view_rect = { .x1 = view_size.x, .x2 = view_size.x + view_size.width, .y1 = view_size.y, .y2 = view_size.y + view_size.height }; pixman_region_overlap_t overlap = pixman_region32_contains_rectangle(available, &view_rect); switch (overlap) { case PIXMAN_REGION_IN: view->edges_visible = WLR_EDGE_TOP | WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT; break; case PIXMAN_REGION_OUT: view->edges_visible = 0; return; case PIXMAN_REGION_PART: ; /* works around "a label can only be part of a statement" */ pixman_region32_t intersection; pixman_region32_init(&intersection); pixman_region32_intersect_rect(&intersection, available, view_size.x, view_size.y, view_size.width, view_size.height); int nrects; const pixman_box32_t *rects = pixman_region32_rectangles(&intersection, &nrects); view->edges_visible = 0; for (int i = 0; i < nrects; i++) { if (rects[i].x1 == view_rect.x1) { view->edges_visible |= WLR_EDGE_LEFT; } if (rects[i].y1 == view_rect.y1) { view->edges_visible |= WLR_EDGE_TOP; } if (rects[i].x2 == view_rect.x2) { view->edges_visible |= WLR_EDGE_RIGHT; } if (rects[i].y2 == view_rect.y2) { view->edges_visible |= WLR_EDGE_BOTTOM; } } pixman_region32_fini(&intersection); break; } /* Subtract the view geometry from the available region for the next check */ pixman_region32_t view_region; pixman_region32_init_rects(&view_region, &view_rect, 1); pixman_region32_subtract(available, available, &view_region); pixman_region32_fini(&view_region); } static void subtract_node_tree(struct wlr_scene_tree *tree, pixman_region32_t *available, struct view *ignored_view) { struct view *view; struct wlr_scene_node *node; struct node_descriptor *node_desc; wl_list_for_each_reverse(node, &tree->children, link) { if (!node->enabled) { /* * This skips everything that is not being * rendered, including minimized / unmapped * windows and workspaces other than the * current one. */ continue; } node_desc = node->data; if (node_desc && node_desc->type == LAB_NODE_DESC_VIEW) { view = node_view_from_node(node); if (view != ignored_view) { subtract_view_from_space(view, available); } } else if (node->type == WLR_SCENE_NODE_TREE) { subtract_node_tree(wlr_scene_tree_from_node(node), available, ignored_view); } } } void edges_calculate_visibility(struct server *server, struct view *ignored_view) { /* * The region stores the available output layout space * and subtracts the window geometries in reverse rendering * order, e.g. a window rendered on top is subtracted first. * * This allows to detect if a window is actually visible. * If there is no overlap of its geometry and the remaining * region it must be completely covered by other windows. * */ pixman_region32_t region; pixman_region32_init(®ion); /* * Initialize the region with each individual output. * * If we were to use NULL for the reference output we * would get a single combined wlr_box of the whole * layout which could cover actual invisible areas * in case the output resolutions differ. */ struct output *output; struct wlr_box layout_box; wl_list_for_each(output, &server->outputs, link) { if (!output_is_usable(output)) { continue; } wlr_output_layout_get_box(server->output_layout, output->wlr_output, &layout_box); pixman_region32_union_rect(®ion, ®ion, layout_box.x, layout_box.y, layout_box.width, layout_box.height); } subtract_node_tree(&server->scene->tree, ®ion, ignored_view); pixman_region32_fini(®ion); } void edges_find_neighbors(struct border *nearest_edges, struct view *view, struct wlr_box target, struct output *output, edge_validator_t validator, bool use_pending, bool ignore_hidden) { assert(view); assert(validator); assert(nearest_edges); if (!output_is_usable(view->output)) { wlr_log(WLR_DEBUG, "ignoring edge search for view on unsable output"); return; } struct border view_edges = { 0 }; struct border target_edges = { 0 }; struct wlr_box *view_geom = use_pending ? &view->pending : &view->current; edges_for_target_geometry(&view_edges, view, *view_geom); edges_for_target_geometry(&target_edges, view, target); struct view *v; for_each_view(v, &view->server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) { if (v == view || v->minimized || !output_is_usable(v->output)) { continue; } uint32_t edges_visible = ignore_hidden ? v->edges_visible : WLR_EDGE_TOP | WLR_EDGE_LEFT | WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT; if (edges_visible == 0) { continue; } if (output && output != v->output && !view_on_output(v, output)) { continue; } /* Both view and v must share a common output */ if (view->output != v->output && !(view->outputs & v->outputs)) { continue; } struct border border = ssd_get_margin(v->ssd); struct border win_edges = { .top = v->current.y - border.top, .left = v->current.x - border.left, .bottom = v->current.y + border.bottom + view_effective_height(v, /* use_pending */ false), .right = v->current.x + v->current.width + border.right, }; validate_edges(nearest_edges, view_edges, target_edges, win_edges, edges_visible, validator); } } void edges_find_outputs(struct border *nearest_edges, struct view *view, struct wlr_box target, struct output *output, edge_validator_t validator, bool use_pending) { assert(view); assert(validator); assert(nearest_edges); if (!output_is_usable(view->output)) { wlr_log(WLR_DEBUG, "ignoring edge search for view on unsable output"); return; } struct border view_edges = { 0 }; struct border target_edges = { 0 }; struct wlr_box *view_geom = use_pending ? &view->pending : &view->current; edges_for_target_geometry(&view_edges, view, *view_geom); edges_for_target_geometry(&target_edges, view, target); struct output *o; wl_list_for_each(o, &view->server->outputs, link) { if (!output_is_usable(o)) { continue; } if (output && o != output) { continue; } struct wlr_box usable = output_usable_area_in_layout_coords(o); struct wlr_box ol; if (!wlr_box_intersection(&ol, view_geom, &usable) && !wlr_box_intersection(&ol, &target, &usable)) { continue; } validate_output_edges(nearest_edges, view_edges, target_edges, usable, validator); } } static void adjust_move_coords_1d(int *edge, int lesser, int lesser_offset, int greater, int greater_offset, bool decreasing) { /* Default best candidate is not valid */ int best = INT_MAX; if (BOUNDED_INT(lesser)) { /* A valid lesser edge is the always the first candidate */ best = clipped_add(lesser, lesser_offset); } if (BOUNDED_INT(greater)) { /* Check if a valid greater edge is a better candidate */ best = edge_get_best(best, clipped_sub(greater, greater_offset), decreasing); } if (BOUNDED_INT(best)) { /* Replace the edge if a valid candidate was found */ *edge = best; } } void edges_adjust_move_coords(struct view *view, struct border edges, int *x, int *y, bool use_pending) { assert(view); struct border border = ssd_get_margin(view->ssd); struct wlr_box *view_geom = use_pending ? &view->pending : &view->current; /* When moving, limit motion to the best valid, intervening edge */ if (view_geom->x != *x) { int lshift = border.left + rc.gap; int rshift = border.right + rc.gap + view->pending.width; adjust_move_coords_1d(x, edges.left, lshift, edges.right, rshift, *x < view_geom->x); } if (view_geom->y != *y) { int tshift = border.top + rc.gap; int bshift = border.bottom + rc.gap + view_effective_height(view, /* use_pending */ true); adjust_move_coords_1d(y, edges.top, tshift, edges.bottom, bshift, *y < view_geom->y); } } void edges_adjust_resize_geom(struct view *view, struct border edges, uint32_t resize_edges, struct wlr_box *geom, bool use_pending) { assert(view); struct border border = ssd_get_margin(view->ssd); struct wlr_box *view_geom = use_pending ? &view->pending : &view->current; /* * When resizing along a given edge, limit the motion of that edge to * any valid nearest edge in the corresponding direction. */ if (resize_edges & WLR_EDGE_LEFT) { if (BOUNDED_INT(edges.left)) { geom->x = edges.left + border.left + rc.gap; geom->width = view_geom->width + view_geom->x - geom->x; } } else if (resize_edges & WLR_EDGE_RIGHT) { if (BOUNDED_INT(edges.right)) { geom->width = edges.right - view_geom->x - border.right - rc.gap; } } if (resize_edges & WLR_EDGE_TOP) { if (BOUNDED_INT(edges.top)) { geom->y = edges.top + border.top + rc.gap; geom->height = view_geom->height + view_geom->y - geom->y; } } else if (resize_edges & WLR_EDGE_BOTTOM) { if (BOUNDED_INT(edges.bottom)) { geom->height = edges.bottom - view_geom->y - border.bottom - rc.gap; } } } static double linear_interp(int x, int x1, int y1, int x2, int y2) { /* * For a line y = mx + b that passes through both (x1, y1) and * (x2, y2), find and return the value y for a given point x. * * The point x does not need to fall in the range [x1, x2]. */ /* No need to interpolate if line is horizontal */ int rise = y2 - y1; if (rise == 0) { return y2; } /* For degenerate line, just pick a midpoint */ int run = x2 - x1; if (run == 0) { return 0.5 * (y1 + y2); } /* Othewise, linearly interpolate */ int dx = x - x1; return y1 + dx * (rise / (double)run); } bool edges_traverse_edge(struct edge current, struct edge target, struct edge obstacle) { /* * Each edge structure defines a line segment that can be represented * in a local coordinate system as starting at (offset, min) and * finishing at (offset, max). * * The starting and ending points of the "current" edge trace * respective lines * * 1. (current.offset, current.min) -> (target.offset, target.min) * 2. (current.offset, current.max) -> (target.offset, target.max) * * as the segment transits from its current position to its target. * Hence, motion of the entire edge from current to target will sweep a * quadrilateral bounded by (locally) vertical lines at current.offset * and target.offset as well as the segments (1) and (2) above. * * To test if the motion will encounter the obstacle edge, we need to * test if any of the obstacle edge falls within this quadrilateral. * Thus, we need to find the extent of the quadrilateral at the same * offset as the obstacle: a segment with starting point * (obstacle.offset, lo) and ending point (obstacle.offset, hi). */ double lo = linear_interp(obstacle.offset, current.offset, current.min, target.offset, target.min); /* Motion misses when obstacle ends above start of quad segment */ if (obstacle.max <= lo) { return false; } double hi = linear_interp(obstacle.offset, current.offset, current.max, target.offset, target.max); /* Motion hits when obstacle starts above the end of quad segment */ return obstacle.min < hi; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/foreign.c���������������������������������������������������������������������������0000664�0000000�0000000�00000007535�14570443012�0015205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include "labwc.h" #include "view.h" #include "workspaces.h" static void handle_request_minimize(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, toplevel.minimize); struct wlr_foreign_toplevel_handle_v1_minimized_event *event = data; view_minimize(view, event->minimized); } static void handle_request_maximize(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, toplevel.maximize); struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data; view_maximize(view, event->maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE, /*store_natural_geometry*/ true); } static void handle_request_fullscreen(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, toplevel.fullscreen); struct wlr_foreign_toplevel_handle_v1_fullscreen_event *event = data; view_set_fullscreen(view, event->fullscreen); } static void handle_request_activate(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, toplevel.activate); // struct wlr_foreign_toplevel_handle_v1_activated_event *event = data; /* In a multi-seat world we would select seat based on event->seat here. */ desktop_focus_view(view, /*raise*/ true); } static void handle_request_close(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, toplevel.close); view_close(view); } static void handle_destroy(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, toplevel.destroy); struct foreign_toplevel *toplevel = &view->toplevel; wl_list_remove(&toplevel->maximize.link); wl_list_remove(&toplevel->minimize.link); wl_list_remove(&toplevel->fullscreen.link); wl_list_remove(&toplevel->activate.link); wl_list_remove(&toplevel->close.link); wl_list_remove(&toplevel->destroy.link); toplevel->handle = NULL; } void foreign_toplevel_handle_create(struct view *view) { struct foreign_toplevel *toplevel = &view->toplevel; toplevel->handle = wlr_foreign_toplevel_handle_v1_create( view->server->foreign_toplevel_manager); if (!toplevel->handle) { wlr_log(WLR_ERROR, "cannot create foreign toplevel handle for (%s)", view_get_string_prop(view, "title")); return; } toplevel->maximize.notify = handle_request_maximize; wl_signal_add(&toplevel->handle->events.request_maximize, &toplevel->maximize); toplevel->minimize.notify = handle_request_minimize; wl_signal_add(&toplevel->handle->events.request_minimize, &toplevel->minimize); toplevel->fullscreen.notify = handle_request_fullscreen; wl_signal_add(&toplevel->handle->events.request_fullscreen, &toplevel->fullscreen); toplevel->activate.notify = handle_request_activate; wl_signal_add(&toplevel->handle->events.request_activate, &toplevel->activate); toplevel->close.notify = handle_request_close; wl_signal_add(&toplevel->handle->events.request_close, &toplevel->close); toplevel->destroy.notify = handle_destroy; wl_signal_add(&toplevel->handle->events.destroy, &toplevel->destroy); } /* * Loop over all outputs and notify foreign_toplevel clients about changes. * wlr_foreign_toplevel_handle_v1_output_xxx() keeps track of the active * outputs internally and merges the events. It also listens to output * destroy events so its fine to just relay the current state and let * wlr_foreign_toplevel handle the rest. */ void foreign_toplevel_update_outputs(struct view *view) { assert(view->toplevel.handle); struct output *output; wl_list_for_each(output, &view->server->outputs, link) { if (view_on_output(view, output)) { wlr_foreign_toplevel_handle_v1_output_enter( view->toplevel.handle, output->wlr_output); } else { wlr_foreign_toplevel_handle_v1_output_leave( view->toplevel.handle, output->wlr_output); } } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/idle.c������������������������������������������������������������������������������0000664�0000000�0000000�00000005420�14570443012�0014460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <stdlib.h> #include <wlr/types/wlr_idle_notify_v1.h> #include <wlr/types/wlr_idle_inhibit_v1.h> #include "common/mem.h" #include "idle.h" struct lab_idle_inhibitor { struct wlr_idle_inhibitor_v1 *wlr_inhibitor; struct wl_listener on_destroy; }; struct lab_idle_manager { struct wlr_idle_notifier_v1 *ext; struct { struct wlr_idle_inhibit_manager_v1 *manager; struct wl_listener on_new_inhibitor; } inhibitor; struct wlr_seat *wlr_seat; struct wl_listener on_display_destroy; }; static struct lab_idle_manager *manager; static void handle_idle_inhibitor_destroy(struct wl_listener *listener, void *data) { struct lab_idle_inhibitor *idle_inhibitor = wl_container_of(listener, idle_inhibitor, on_destroy); if (manager) { /* * The display destroy event might have been triggered * already and thus the manager would be NULL. */ bool still_inhibited = wl_list_length(&manager->inhibitor.manager->inhibitors) > 1; wlr_idle_notifier_v1_set_inhibited(manager->ext, still_inhibited); } wl_list_remove(&idle_inhibitor->on_destroy.link); free(idle_inhibitor); } static void handle_idle_inhibitor_new(struct wl_listener *listener, void *data) { assert(manager); struct wlr_idle_inhibitor_v1 *wlr_inhibitor = data; struct lab_idle_inhibitor *inhibitor = znew(*inhibitor); inhibitor->wlr_inhibitor = wlr_inhibitor; inhibitor->on_destroy.notify = handle_idle_inhibitor_destroy; wl_signal_add(&wlr_inhibitor->events.destroy, &inhibitor->on_destroy); wlr_idle_notifier_v1_set_inhibited(manager->ext, true); } static void handle_display_destroy(struct wl_listener *listener, void *data) { /* * All the managers will react to the display * destroy signal as well and thus clean up. */ wl_list_remove(&manager->on_display_destroy.link); zfree(manager); } void idle_manager_create(struct wl_display *display, struct wlr_seat *wlr_seat) { assert(!manager); manager = znew(*manager); manager->wlr_seat = wlr_seat; manager->ext = wlr_idle_notifier_v1_create(display); manager->inhibitor.manager = wlr_idle_inhibit_v1_create(display); manager->inhibitor.on_new_inhibitor.notify = handle_idle_inhibitor_new; wl_signal_add(&manager->inhibitor.manager->events.new_inhibitor, &manager->inhibitor.on_new_inhibitor); manager->on_display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &manager->on_display_destroy); } void idle_manager_notify_activity(struct wlr_seat *seat) { /* * The display destroy event might have been triggered * already and thus the manager would be NULL. Due to * future code changes we might also get called before * the manager has been created. */ if (!manager) { return; } wlr_idle_notifier_v1_notify_activity(manager->ext, seat); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/input/������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14570443012�0014535�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/input/cursor.c����������������������������������������������������������������������0000664�0000000�0000000�00000116076�14570443012�0016231�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <linux/input-event-codes.h> #include <sys/time.h> #include <time.h> #include <wlr/types/wlr_cursor_shape_v1.h> #include <wlr/types/wlr_primary_selection.h> #include <wlr/util/region.h> #include "action.h" #include "common/macros.h" #include "common/mem.h" #include "common/scene-helpers.h" #include "config/mousebind.h" #include "dnd.h" #include "idle.h" #include "input/gestures.h" #include "input/touch.h" #include "labwc.h" #include "menu/menu.h" #include "regions.h" #include "resistance.h" #include "ssd.h" #include "view.h" #define LAB_CURSOR_SHAPE_V1_VERSION 1 static const char * const *cursor_names = NULL; /* Usual cursor names */ static const char * const cursors_xdg[] = { NULL, "default", "grab", "nw-resize", "n-resize", "ne-resize", "e-resize", "se-resize", "s-resize", "sw-resize", "w-resize" }; /* XCursor fallbacks */ static const char * const cursors_x11[] = { NULL, "left_ptr", "grabbing", "top_left_corner", "top_side", "top_right_corner", "right_side", "bottom_right_corner", "bottom_side", "bottom_left_corner", "left_side" }; static_assert( ARRAY_SIZE(cursors_xdg) == LAB_CURSOR_COUNT, "XDG cursor names are out of sync"); static_assert( ARRAY_SIZE(cursors_x11) == LAB_CURSOR_COUNT, "X11 cursor names are out of sync"); enum lab_cursors cursor_get_from_edge(uint32_t resize_edges) { switch (resize_edges) { case WLR_EDGE_NONE: return LAB_CURSOR_DEFAULT; case WLR_EDGE_TOP | WLR_EDGE_LEFT: return LAB_CURSOR_RESIZE_NW; case WLR_EDGE_TOP: return LAB_CURSOR_RESIZE_N; case WLR_EDGE_TOP | WLR_EDGE_RIGHT: return LAB_CURSOR_RESIZE_NE; case WLR_EDGE_RIGHT: return LAB_CURSOR_RESIZE_E; case WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT: return LAB_CURSOR_RESIZE_SE; case WLR_EDGE_BOTTOM: return LAB_CURSOR_RESIZE_S; case WLR_EDGE_BOTTOM | WLR_EDGE_LEFT: return LAB_CURSOR_RESIZE_SW; case WLR_EDGE_LEFT: return LAB_CURSOR_RESIZE_W; default: wlr_log(WLR_ERROR, "Failed to resolve wlroots edge %u to cursor name", resize_edges); assert(false); } return LAB_CURSOR_DEFAULT; } static enum lab_cursors cursor_get_from_ssd(enum ssd_part_type view_area) { uint32_t resize_edges = ssd_resize_edges(view_area); return cursor_get_from_edge(resize_edges); } static struct wlr_surface * get_toplevel(struct wlr_surface *surface) { while (surface) { struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface); if (!xdg_surface) { break; } switch (xdg_surface->role) { case WLR_XDG_SURFACE_ROLE_NONE: return NULL; case WLR_XDG_SURFACE_ROLE_TOPLEVEL: return surface; case WLR_XDG_SURFACE_ROLE_POPUP: surface = xdg_surface->popup->parent; continue; } } if (surface && wlr_layer_surface_v1_try_from_wlr_surface(surface)) { return surface; } return NULL; } static void request_cursor_notify(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, request_cursor); if (seat->server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { /* Prevent setting a cursor image when moving or resizing */ return; } /* * This event is raised by the seat when a client provides a cursor * image */ struct wlr_seat_pointer_request_set_cursor_event *event = data; struct wlr_seat_client *focused_client = seat->seat->pointer_state.focused_client; /* * This can be sent by any client, so we check to make sure this one * actually has pointer focus first. */ if (focused_client == event->seat_client) { /* * Once we've vetted the client, we can tell the cursor to use * the provided surface as the cursor image. It will set the * hardware cursor on the output that it's currently on and * continue to do so as the cursor moves between outputs. */ wlr_cursor_set_surface(seat->cursor, event->surface, event->hotspot_x, event->hotspot_y); } } static void request_set_shape_notify(struct wl_listener *listener, void *data) { struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data; const char *shape_name = wlr_cursor_shape_v1_name(event->shape); struct seat *seat = wl_container_of(listener, seat, request_set_shape); struct wlr_seat_client *focused_client = seat->seat->pointer_state.focused_client; /* Prevent setting a cursor image when moving or resizing */ if (seat->server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { return; } /* * This can be sent by any client, so we check to make sure this one * actually has pointer focus first. */ if (event->seat_client != focused_client) { wlr_log(WLR_INFO, "seat client %p != focused client %p", event->seat_client, focused_client); return; } wlr_log(WLR_DEBUG, "set xcursor to shape %s", shape_name); wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager, shape_name); } static void request_set_selection_notify(struct wl_listener *listener, void *data) { struct seat *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 request_set_primary_selection_notify(struct wl_listener *listener, void *data) { struct seat *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); } static void process_cursor_move(struct server *server, uint32_t time) { double dx = server->seat.cursor->x - server->grab_x; double dy = server->seat.cursor->y - server->grab_y; struct view *view = server->grabbed_view; /* Move the grabbed view to the new position. */ dx += server->grab_box.x; dy += server->grab_box.y; resistance_move_apply(view, &dx, &dy); view_move(view, dx, dy); /* Region overlay */ if (!regions_should_snap(server)) { return; } struct region *region = regions_from_cursor(server); if (region) { regions_show_overlay(view, &server->seat, region); } else { regions_hide_overlay(&server->seat); } } static void process_cursor_resize(struct server *server, uint32_t time) { double dx = server->seat.cursor->x - server->grab_x; double dy = server->seat.cursor->y - server->grab_y; struct view *view = server->grabbed_view; struct wlr_box new_view_geo = view->current; if (server->resize_edges & WLR_EDGE_TOP) { /* Shift y to anchor bottom edge when resizing top */ new_view_geo.y = server->grab_box.y + dy; new_view_geo.height = server->grab_box.height - dy; } else if (server->resize_edges & WLR_EDGE_BOTTOM) { new_view_geo.height = server->grab_box.height + dy; } if (server->resize_edges & WLR_EDGE_LEFT) { /* Shift x to anchor right edge when resizing left */ new_view_geo.x = server->grab_box.x + dx; new_view_geo.width = server->grab_box.width - dx; } else if (server->resize_edges & WLR_EDGE_RIGHT) { new_view_geo.width = server->grab_box.width + dx; } resistance_resize_apply(view, &new_view_geo); view_adjust_size(view, &new_view_geo.width, &new_view_geo.height); if (server->resize_edges & WLR_EDGE_TOP) { /* After size adjustments, make sure to anchor bottom edge */ new_view_geo.y = server->grab_box.y + server->grab_box.height - new_view_geo.height; } if (server->resize_edges & WLR_EDGE_LEFT) { /* After size adjustments, make sure to anchor bottom right */ new_view_geo.x = server->grab_box.x + server->grab_box.width - new_view_geo.width; } view_move_resize(view, new_view_geo); } void cursor_set(struct seat *seat, enum lab_cursors cursor) { assert(cursor > LAB_CURSOR_CLIENT && cursor < LAB_CURSOR_COUNT); /* Prevent setting the same cursor image twice */ if (seat->server_cursor == cursor) { return; } wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager, cursor_names[cursor]); seat->server_cursor = cursor; } void cursor_update_image(struct seat *seat) { enum lab_cursors cursor = seat->server_cursor; if (cursor == LAB_CURSOR_CLIENT) { /* * When we loose the output cursor while over a client * surface (e.g. output was destroyed and we now deal with * a new output instance), we have to force a re-enter of * the surface so the client sets its own cursor again. */ if (seat->seat->pointer_state.focused_surface) { seat->server_cursor = LAB_CURSOR_DEFAULT; cursor_update_focus(seat->server); } return; } /* * Call wlr_cursor_unset_image() first to force wlroots to * update the cursor (e.g. for a new output). Otherwise, * wlr_cursor_set_xcursor() may detect that we are setting the * same cursor as before, and do nothing. */ wlr_cursor_unset_image(seat->cursor); wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager, cursor_names[cursor]); } bool input_inhibit_blocks_surface(struct seat *seat, struct wl_resource *resource) { struct wl_client *inhibiting_client = seat->active_client_while_inhibited; return inhibiting_client && inhibiting_client != wl_resource_get_client(resource); } static bool update_pressed_surface(struct seat *seat, struct cursor_context *ctx) { /* * In most cases, we don't want to leave one surface and enter * another while a button is pressed. We only do so when * (1) there is a pointer grab active (e.g. XDG popup grab) and * (2) both surfaces belong to the same XDG toplevel. * * GTK/Wayland menus are known to use an XDG popup grab and to * rely on the leave/enter events to work properly. Firefox * context menus (in contrast) do not use an XDG popup grab and * do not work properly if we send leave/enter events. */ if (!wlr_seat_pointer_has_grab(seat->seat)) { return false; } if (seat->pressed.surface && ctx->surface != seat->pressed.surface) { struct wlr_surface *toplevel = get_toplevel(ctx->surface); if (toplevel && toplevel == seat->pressed.toplevel) { /* No need to recompute resize edges here */ seat_set_pressed(seat, ctx->view, ctx->node, ctx->surface, toplevel, seat->pressed.resize_edges); return true; } } return false; } static void process_cursor_motion_out_of_surface(struct server *server, uint32_t time) { struct view *view = server->seat.pressed.view; struct wlr_scene_node *node = server->seat.pressed.node; struct wlr_surface *surface = server->seat.pressed.surface; assert(surface); int lx, ly; if (view) { lx = view->current.x; ly = view->current.y; } else if (node && wlr_layer_surface_v1_try_from_wlr_surface(surface)) { wlr_scene_node_coords(node, &lx, &ly); #if HAVE_XWAYLAND } else if (node && node->parent == server->unmanaged_tree) { wlr_scene_node_coords(node, &lx, &ly); #endif } else { wlr_log(WLR_ERROR, "Can't detect surface for out-of-surface movement"); return; } double sx = server->seat.cursor->x - lx; double sy = server->seat.cursor->y - ly; /* Take into account invisible xdg-shell CSD borders */ if (view && view->type == LAB_XDG_SHELL_VIEW) { struct wlr_box geo; wlr_xdg_surface_get_geometry(xdg_surface_from_view(view), &geo); sx += geo.x; sy += geo.y; } wlr_seat_pointer_notify_motion(server->seat.seat, time, sx, sy); } /* * Common logic shared by cursor_update_focus() and process_cursor_motion() */ static void cursor_update_common(struct server *server, struct cursor_context *ctx, uint32_t time_msec, bool cursor_has_moved) { struct seat *seat = &server->seat; struct wlr_seat *wlr_seat = seat->seat; ssd_update_button_hover(ctx->node, server->ssd_hover_state); if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { /* * Prevent updating focus/cursor image during * interactive move/resize */ return; } /* TODO: verify drag_icon logic */ if (seat->pressed.surface && ctx->surface != seat->pressed.surface && !update_pressed_surface(seat, ctx) && !seat->drag.active) { if (cursor_has_moved) { /* * Button has been pressed while over another * surface and is still held down. Just send * the motion events to the focused surface so * we can keep scrolling or selecting text even * if the cursor moves outside of the surface. */ process_cursor_motion_out_of_surface(server, time_msec); } return; } if (ctx->surface && !input_inhibit_blocks_surface(seat, ctx->surface->resource)) { /* * Cursor is over an input-enabled client surface. The * cursor image will be set by request_cursor_notify() * in response to the enter event. */ bool has_focus = (ctx->surface == wlr_seat->pointer_state.focused_surface); if (!has_focus || seat->server_cursor != LAB_CURSOR_CLIENT) { /* * Enter the surface if necessary. Usually we * prevent re-entering an already focused * surface, because the extra leave and enter * events can confuse clients (e.g. break * double-click detection). * * We do however send a leave/enter event pair * if a server-side cursor was set and we need * to trigger a cursor image update. */ if (has_focus) { wlr_seat_pointer_notify_clear_focus(wlr_seat); } wlr_seat_pointer_notify_enter(wlr_seat, ctx->surface, ctx->sx, ctx->sy); seat->server_cursor = LAB_CURSOR_CLIENT; } if (cursor_has_moved) { wlr_seat_pointer_notify_motion(wlr_seat, time_msec, ctx->sx, ctx->sy); } } else { /* * Cursor is over a server (labwc) surface. Clear focus * from the focused client (if any, no-op otherwise) and * set the cursor image ourselves when not currently in * a drag operation. */ wlr_seat_pointer_notify_clear_focus(wlr_seat); if (!seat->drag.active) { enum lab_cursors cursor = cursor_get_from_ssd(ctx->type); if (ctx->view && ctx->view->shaded && cursor > LAB_CURSOR_GRAB) { /* Prevent resize cursor on borders for shaded SSD */ cursor = LAB_CURSOR_DEFAULT; } cursor_set(seat, cursor); } } } uint32_t cursor_get_resize_edges(struct wlr_cursor *cursor, struct cursor_context *ctx) { uint32_t resize_edges = ssd_resize_edges(ctx->type); if (ctx->view && !resize_edges) { struct wlr_box box = ctx->view->current; resize_edges |= (int)cursor->x < box.x + box.width / 2 ? WLR_EDGE_LEFT : WLR_EDGE_RIGHT; resize_edges |= (int)cursor->y < box.y + box.height / 2 ? WLR_EDGE_TOP : WLR_EDGE_BOTTOM; } return resize_edges; } static void process_cursor_motion(struct server *server, uint32_t time) { /* If the mode is non-passthrough, delegate to those functions. */ if (server->input_mode == LAB_INPUT_STATE_MOVE) { process_cursor_move(server, time); return; } else if (server->input_mode == LAB_INPUT_STATE_RESIZE) { process_cursor_resize(server, time); return; } /* Otherwise, find view under the pointer and send the event along */ struct cursor_context ctx = get_cursor_context(server); struct seat *seat = &server->seat; if (ctx.type == LAB_SSD_MENU) { menu_process_cursor_motion(ctx.node); cursor_set(&server->seat, LAB_CURSOR_DEFAULT); return; } if (seat->drag.active) { dnd_icons_move(seat, seat->cursor->x, seat->cursor->y); } if ((ctx.view || ctx.surface) && rc.focus_follow_mouse) { desktop_focus_view_or_surface(seat, ctx.view, ctx.surface, rc.raise_on_focus); } struct mousebind *mousebind; wl_list_for_each(mousebind, &rc.mousebinds, link) { if (mousebind->mouse_event == MOUSE_ACTION_DRAG && mousebind->pressed_in_context) { /* * Use view and resize edges from the press * event (not the motion event) to prevent * moving/resizing the wrong view */ mousebind->pressed_in_context = false; actions_run(seat->pressed.view, server, &mousebind->actions, seat->pressed.resize_edges); } } cursor_update_common(server, &ctx, time, /*cursor_has_moved*/ true); } static uint32_t msec(const struct timespec *t) { return t->tv_sec * 1000 + t->tv_nsec / 1000000; } static void _cursor_update_focus(struct server *server) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); /* Focus surface under cursor if it isn't already focused */ struct cursor_context ctx = get_cursor_context(server); if ((ctx.view || ctx.surface) && rc.focus_follow_mouse && !rc.focus_follow_mouse_requires_movement && !server->osd_state.cycle_view) { /* Prevents changing keyboard focus during A-Tab */ desktop_focus_view_or_surface(&server->seat, ctx.view, ctx.surface, rc.raise_on_focus); } cursor_update_common(server, &ctx, msec(&now), /*cursor_has_moved*/ false); } void cursor_update_focus(struct server *server) { /* Prevent recursion via view_move_to_front() */ static bool updating_focus = false; if (!updating_focus) { updating_focus = true; _cursor_update_focus(server); updating_focus = false; } } static void warp_cursor_to_constraint_hint(struct seat *seat, struct wlr_pointer_constraint_v1 *constraint) { if (!seat->server->active_view) { return; } if (constraint->current.committed & WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT) { double sx = constraint->current.cursor_hint.x; double sy = constraint->current.cursor_hint.y; wlr_cursor_warp(seat->cursor, NULL, seat->server->active_view->current.x + sx, seat->server->active_view->current.y + sy); /* Make sure we are not sending unnecessary surface movements */ wlr_seat_pointer_warp(seat->seat, sx, sy); } } static void handle_constraint_commit(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, constraint_commit); struct wlr_pointer_constraint_v1 *constraint = seat->current_constraint; /* Prevents unused variable warning when compiled without asserts */ (void)constraint; assert(constraint->surface = data); } static void destroy_constraint(struct wl_listener *listener, void *data) { struct constraint *constraint = wl_container_of(listener, constraint, destroy); struct wlr_pointer_constraint_v1 *wlr_constraint = data; struct seat *seat = constraint->seat; wl_list_remove(&constraint->destroy.link); if (seat->current_constraint == wlr_constraint) { warp_cursor_to_constraint_hint(seat, wlr_constraint); if (seat->constraint_commit.link.next) { wl_list_remove(&seat->constraint_commit.link); } wl_list_init(&seat->constraint_commit.link); seat->current_constraint = NULL; } free(constraint); } void create_constraint(struct wl_listener *listener, void *data) { struct wlr_pointer_constraint_v1 *wlr_constraint = data; struct server *server = wl_container_of(listener, server, new_constraint); struct constraint *constraint = znew(*constraint); constraint->constraint = wlr_constraint; constraint->seat = &server->seat; constraint->destroy.notify = destroy_constraint; wl_signal_add(&wlr_constraint->events.destroy, &constraint->destroy); struct view *view = server->active_view; if (view && view->surface == wlr_constraint->surface) { constrain_cursor(server, wlr_constraint); } } void constrain_cursor(struct server *server, struct wlr_pointer_constraint_v1 *constraint) { struct seat *seat = &server->seat; if (seat->current_constraint == constraint) { return; } wl_list_remove(&seat->constraint_commit.link); if (seat->current_constraint) { if (!constraint) { warp_cursor_to_constraint_hint(seat, seat->current_constraint); } wlr_pointer_constraint_v1_send_deactivated( seat->current_constraint); } seat->current_constraint = constraint; if (!constraint) { wl_list_init(&seat->constraint_commit.link); return; } wlr_pointer_constraint_v1_send_activated(constraint); seat->constraint_commit.notify = handle_constraint_commit; wl_signal_add(&constraint->surface->events.commit, &seat->constraint_commit); } static void apply_constraint(struct seat *seat, struct wlr_pointer *pointer, double *x, double *y) { if (!seat->current_constraint || pointer->base.type != WLR_INPUT_DEVICE_POINTER) { return; } assert(seat->current_constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED); double sx = seat->cursor->x; double sy = seat->cursor->y; sx -= seat->server->active_view->current.x; sy -= seat->server->active_view->current.y; double sx_confined, sy_confined; if (!wlr_region_confine(&seat->current_constraint->region, sx, sy, sx + *x, sy + *y, &sx_confined, &sy_confined)) { return; } *x = sx_confined - sx; *y = sy_confined - sy; } static bool cursor_locked(struct seat *seat, struct wlr_pointer *pointer) { return seat->current_constraint && pointer->base.type == WLR_INPUT_DEVICE_POINTER && seat->current_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED; } static void preprocess_cursor_motion(struct seat *seat, struct wlr_pointer *pointer, uint32_t time_msec, double dx, double dy) { if (cursor_locked(seat, pointer)) { return; } apply_constraint(seat, pointer, &dx, &dy); /* * The cursor doesn't move unless we tell it to. The cursor * automatically handles constraining the motion to the output * layout, as well as any special configuration applied for the * specific input device which generated the event. You can pass * NULL for the device if you want to move the cursor around * without any input. */ wlr_cursor_move(seat->cursor, &pointer->base, dx, dy); process_cursor_motion(seat->server, time_msec); } static void cursor_motion(struct wl_listener *listener, void *data) { /* * This event is forwarded by the cursor when a pointer emits a * _relative_ pointer motion event (i.e. a delta) */ struct seat *seat = wl_container_of(listener, seat, cursor_motion); struct server *server = seat->server; struct wlr_pointer_motion_event *event = data; idle_manager_notify_activity(seat->seat); wlr_relative_pointer_manager_v1_send_relative_motion( server->relative_pointer_manager, seat->seat, (uint64_t)event->time_msec * 1000, event->delta_x, event->delta_y, event->unaccel_dx, event->unaccel_dy); preprocess_cursor_motion(seat, event->pointer, event->time_msec, event->delta_x, event->delta_y); } static void cursor_motion_absolute(struct wl_listener *listener, void *data) { /* * This event is forwarded by the cursor when a pointer emits an * _absolute_ motion event, from 0..1 on each axis. This happens, for * example, when wlroots is running under a Wayland window rather than * KMS+DRM, and you move the mouse over the window. You could enter the * window from any edge, so we have to warp the mouse there. There is * also some hardware which emits these events. */ struct seat *seat = wl_container_of( listener, seat, cursor_motion_absolute); struct wlr_pointer_motion_absolute_event *event = data; idle_manager_notify_activity(seat->seat); double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->pointer->base, event->x, event->y, &lx, &ly); double dx = lx - seat->cursor->x; double dy = ly - seat->cursor->y; wlr_relative_pointer_manager_v1_send_relative_motion( seat->server->relative_pointer_manager, seat->seat, (uint64_t)event->time_msec * 1000, dx, dy, dx, dy); preprocess_cursor_motion(seat, event->pointer, event->time_msec, dx, dy); } static bool handle_release_mousebinding(struct server *server, struct cursor_context *ctx, uint32_t button) { struct mousebind *mousebind; bool consumed_by_frame_context = false; uint32_t modifiers = wlr_keyboard_get_modifiers( &server->seat.keyboard_group->keyboard); wl_list_for_each(mousebind, &rc.mousebinds, link) { if (ssd_part_contains(mousebind->context, ctx->type) && mousebind->button == button && modifiers == mousebind->modifiers) { switch (mousebind->mouse_event) { case MOUSE_ACTION_RELEASE: break; case MOUSE_ACTION_CLICK: if (mousebind->pressed_in_context) { break; } continue; case MOUSE_ACTION_DRAG: if (mousebind->pressed_in_context) { /* * Swallow the release event as well as * the press one */ consumed_by_frame_context |= mousebind->context == LAB_SSD_FRAME; } continue; default: continue; } consumed_by_frame_context |= mousebind->context == LAB_SSD_FRAME; actions_run(ctx->view, server, &mousebind->actions, /*resize_edges*/ 0); } } /* * Clear "pressed" status for all bindings of this mouse button, * regardless of whether handled or not */ wl_list_for_each(mousebind, &rc.mousebinds, link) { if (mousebind->button == button) { mousebind->pressed_in_context = false; } } return consumed_by_frame_context; } static bool is_double_click(long double_click_speed, uint32_t button, struct view *view) { static uint32_t last_button; static struct view *last_view; static struct timespec last_click; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); long ms = (now.tv_sec - last_click.tv_sec) * 1000 + (now.tv_nsec - last_click.tv_nsec) / 1000000; last_click = now; if (last_button != button || last_view != view) { last_button = button; last_view = view; return false; } if (ms < double_click_speed && ms >= 0) { /* * End sequence so that third click is not considered a * double-click */ last_button = 0; last_view = NULL; return true; } return false; } static bool handle_press_mousebinding(struct server *server, struct cursor_context *ctx, uint32_t button, uint32_t resize_edges) { struct mousebind *mousebind; bool double_click = is_double_click(rc.doubleclick_time, button, ctx->view); bool consumed_by_frame_context = false; uint32_t modifiers = wlr_keyboard_get_modifiers( &server->seat.keyboard_group->keyboard); wl_list_for_each(mousebind, &rc.mousebinds, link) { if (ssd_part_contains(mousebind->context, ctx->type) && mousebind->button == button && modifiers == mousebind->modifiers) { switch (mousebind->mouse_event) { case MOUSE_ACTION_DRAG: /* fallthrough */ case MOUSE_ACTION_CLICK: /* * DRAG and CLICK actions will be processed on * the release event, unless the press event is * counted as a DOUBLECLICK. */ if (!double_click) { /* * Swallow the press event as well as * the release one */ consumed_by_frame_context |= mousebind->context == LAB_SSD_FRAME; mousebind->pressed_in_context = true; } continue; case MOUSE_ACTION_DOUBLECLICK: if (!double_click) { continue; } break; case MOUSE_ACTION_PRESS: break; default: continue; } consumed_by_frame_context |= mousebind->context == LAB_SSD_FRAME; actions_run(ctx->view, server, &mousebind->actions, resize_edges); } } return consumed_by_frame_context; } /* Set in cursor_button_press(), used in cursor_button_release() */ static bool close_menu; static void cursor_button_press(struct seat *seat, uint32_t button, enum wlr_button_state button_state, uint32_t time_msec) { struct server *server = seat->server; struct cursor_context ctx = get_cursor_context(server); /* Determine closest resize edges in case action is Resize */ uint32_t resize_edges = cursor_get_resize_edges(seat->cursor, &ctx); if (ctx.view || ctx.surface) { /* Store resize edges for later action processing */ seat_set_pressed(seat, ctx.view, ctx.node, ctx.surface, get_toplevel(ctx.surface), resize_edges); } if (server->input_mode == LAB_INPUT_STATE_MENU) { /* * We close the menu on RELEASE to not leak a stray releases and * to be consistent with Openbox */ close_menu = true; return; } /* * On press, set focus to a non-view surface that wants it. * Action processing does not run for these surfaces and thus * the Focus action (used for normal views) does not work. */ if (ctx.type == LAB_SSD_LAYER_SURFACE) { struct wlr_layer_surface_v1 *layer = wlr_layer_surface_v1_try_from_wlr_surface(ctx.surface); if (layer && layer->current.keyboard_interactive) { seat_set_focus_layer(seat, layer); } #ifdef HAVE_XWAYLAND } else if (ctx.type == LAB_SSD_UNMANAGED) { desktop_focus_view_or_surface(seat, NULL, ctx.surface, /*raise*/ false); #endif } /* * TODO: We may need to handle press on layer-shell subsurfaces here, * but need to check keyboard interactivity before focusing them * otherwise we break waybar. See issue #1131 */ if (ctx.type != LAB_SSD_CLIENT && ctx.type != LAB_SSD_LAYER_SUBSURFACE && wlr_seat_pointer_has_grab(seat->seat)) { /* * If we have an active popup grab (an open popup) we want to * cancel that grab whenever the user presses on anything that * is not the client itself, for example the desktop or any * part of the server side decoration. * * Note: This does not work for XWayland clients */ wlr_seat_pointer_end_grab(seat->seat); return; } /* Bindings to the Frame context swallow mouse events if activated */ bool consumed_by_frame_context = handle_press_mousebinding(server, &ctx, button, resize_edges); if (ctx.surface && !consumed_by_frame_context) { /* Notify client with pointer focus of button press */ wlr_seat_pointer_notify_button(seat->seat, time_msec, button, button_state); } } static void cursor_button_release(struct seat *seat, uint32_t button, enum wlr_button_state button_state, uint32_t time_msec) { struct server *server = seat->server; struct cursor_context ctx = get_cursor_context(server); struct wlr_surface *pressed_surface = seat->pressed.surface; seat_reset_pressed(seat); if (server->input_mode == LAB_INPUT_STATE_MENU) { if (close_menu) { if (ctx.type == LAB_SSD_MENU) { menu_call_selected_actions(server); } else { menu_close_root(server); cursor_update_common(server, &ctx, time_msec, /*cursor_has_moved*/ false); } close_menu = false; } return; } if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { /* Exit interactive move/resize mode */ interactive_finish(server->grabbed_view); if (pressed_surface) { /* Ensure CSD clients see the release event */ wlr_seat_pointer_notify_button(seat->seat, time_msec, button, button_state); } return; } if (pressed_surface && ctx.surface != pressed_surface) { /* * Button released but originally pressed over a different surface. * Just send the release event to the still focused surface. */ wlr_seat_pointer_notify_button(seat->seat, time_msec, button, button_state); return; } /* Bindings to the Frame context swallow mouse events if activated */ bool consumed_by_frame_context = handle_release_mousebinding(server, &ctx, button); if (ctx.surface && !consumed_by_frame_context) { /* Notify client with pointer focus of button release */ wlr_seat_pointer_notify_button(seat->seat, time_msec, button, button_state); } } static void cursor_button(struct wl_listener *listener, void *data) { /* * This event is forwarded by the cursor when a pointer emits a button * event. */ struct seat *seat = wl_container_of(listener, seat, cursor_button); struct wlr_pointer_button_event *event = data; idle_manager_notify_activity(seat->seat); switch (event->state) { case WLR_BUTTON_PRESSED: cursor_button_press(seat, event->button, event->state, event->time_msec); break; case WLR_BUTTON_RELEASED: cursor_button_release(seat, event->button, event->state, event->time_msec); break; } } void cursor_emulate_move_absolute(struct seat *seat, struct wlr_input_device *device, double x, double y, uint32_t time_msec) { double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, device, x, y, &lx, &ly); double dx = lx - seat->cursor->x; double dy = ly - seat->cursor->y; if (!dx && !dy) { wlr_log(WLR_DEBUG, "dropping useless cursor_emulate: %.10f,%.10f", dx, dy); return; } wlr_relative_pointer_manager_v1_send_relative_motion( seat->server->relative_pointer_manager, seat->seat, (uint64_t)time_msec * 1000, dx, dy, dx, dy); wlr_cursor_move(seat->cursor, device, dx, dy); process_cursor_motion(seat->server, time_msec); wlr_seat_pointer_notify_frame(seat->seat); } void cursor_emulate_button(struct seat *seat, uint32_t button, enum wlr_button_state state, uint32_t time_msec) { idle_manager_notify_activity(seat->seat); switch (state) { case WLR_BUTTON_PRESSED: cursor_button_press(seat, button, state, time_msec); break; case WLR_BUTTON_RELEASED: cursor_button_release(seat, button, state, time_msec); break; } } static int compare_delta(const struct wlr_pointer_axis_event *event, double *accum) { /* * Smooth scroll deltas are in surface space, so treating each unit as a * scroll event would result in too-fast scrolling. * * This fudge factor (inherited from various historic projects, incl. Weston) * produces events at a more reasonable rate. * * For historic context, see: * https://lists.freedesktop.org/archives/wayland-devel/2019-April/040377.html */ const double SCROLL_THRESHOLD = 10.0; if (event->delta == 0.0) { /* Delta 0 marks the end of a scroll */ *accum = 0.0; } else { /* Accumulate smooth scrolling until we hit threshold */ *accum += event->delta; } if (event->delta_discrete < 0 || *accum < -SCROLL_THRESHOLD) { *accum = fmod(*accum, SCROLL_THRESHOLD); return -1; } else if (event->delta_discrete > 0 || *accum > SCROLL_THRESHOLD) { *accum = fmod(*accum, SCROLL_THRESHOLD); return 1; } return 0; } static bool handle_cursor_axis(struct server *server, struct cursor_context *ctx, struct wlr_pointer_axis_event *event) { struct mousebind *mousebind; bool handled = false; uint32_t modifiers = wlr_keyboard_get_modifiers( &server->seat.keyboard_group->keyboard); enum direction direction = LAB_DIRECTION_INVALID; if (event->orientation == WLR_AXIS_ORIENTATION_HORIZONTAL) { int rel = compare_delta(event, &server->seat.smooth_scroll_offset.x); if (rel < 0) { direction = LAB_DIRECTION_LEFT; } else if (rel > 0) { direction = LAB_DIRECTION_RIGHT; } } else if (event->orientation == WLR_AXIS_ORIENTATION_VERTICAL) { int rel = compare_delta(event, &server->seat.smooth_scroll_offset.y); if (rel < 0) { direction = LAB_DIRECTION_UP; } else if (rel > 0) { direction = LAB_DIRECTION_DOWN; } } else { wlr_log(WLR_DEBUG, "Failed to handle cursor axis event"); } if (direction == LAB_DIRECTION_INVALID) { return false; } wl_list_for_each(mousebind, &rc.mousebinds, link) { if (ssd_part_contains(mousebind->context, ctx->type) && mousebind->direction == direction && modifiers == mousebind->modifiers && mousebind->mouse_event == MOUSE_ACTION_SCROLL) { handled = true; actions_run(ctx->view, server, &mousebind->actions, /*resize_edges*/ 0); } } return handled; } static void cursor_axis(struct wl_listener *listener, void *data) { /* * This event is forwarded by the cursor when a pointer emits an axis * event, for example when you move the scroll wheel. */ struct seat *seat = wl_container_of(listener, seat, cursor_axis); struct wlr_pointer_axis_event *event = data; struct server *server = seat->server; struct cursor_context ctx = get_cursor_context(server); idle_manager_notify_activity(seat->seat); /* Bindings swallow mouse events if activated */ bool handled = handle_cursor_axis(server, &ctx, event); if (ctx.surface && !handled) { /* Make sure we are sending the events to the surface under the cursor */ cursor_update_common(server, &ctx, event->time_msec, false); /* Notify the client with pointer focus of the axis event. */ wlr_seat_pointer_notify_axis(seat->seat, event->time_msec, event->orientation, rc.scroll_factor * event->delta, round(rc.scroll_factor * event->delta_discrete), event->source); } } static void cursor_frame(struct wl_listener *listener, void *data) { /* * This event is forwarded by the cursor when a pointer emits an frame * event. Frame events are sent after regular pointer events to group * multiple events together. For instance, two axis events may happen * at the same time, in which case a frame event won't be sent in * between. */ struct seat *seat = wl_container_of(listener, seat, cursor_frame); /* Notify the client with pointer focus of the frame event. */ wlr_seat_pointer_notify_frame(seat->seat); } void cursor_init(struct seat *seat) { const char *xcursor_theme = getenv("XCURSOR_THEME"); const char *xcursor_size = getenv("XCURSOR_SIZE"); uint32_t size = xcursor_size ? atoi(xcursor_size) : 24; seat->xcursor_manager = wlr_xcursor_manager_create(xcursor_theme, size); wlr_xcursor_manager_load(seat->xcursor_manager, 1); /* * Wlroots provides integrated fallback cursor icons using * old-style X11 cursor names (cursors_x11) and additionally * (since wlroots 0.16.2) aliases them to cursor-spec names * (cursors_xdg). * * However, the aliasing does not include the "grab" cursor * icon which labwc uses when dragging a window. To fix that, * try to get the grab cursor icon from wlroots. If the user * supplied an appropriate cursor theme which includes the * "grab" cursor icon, we will keep using it. * * If no "grab" icon can be found we will fall back to the * old style cursor names and use "grabbing" instead which * is part of the X11 fallbacks and thus always available. * * Shipping the complete alias table for X11 cursor names * (and not just the "grab" cursor alias) makes sure that * this also works for wlroots versions before 0.16.2. * * See the cursor name alias table on the top of this file * for the actual cursor names used. */ if (wlr_xcursor_manager_get_xcursor(seat->xcursor_manager, cursors_xdg[LAB_CURSOR_GRAB], 1)) { cursor_names = cursors_xdg; } else { wlr_log(WLR_INFO, "Cursor theme is missing cursor names, using fallback"); cursor_names = cursors_x11; } /* Set the initial cursor image so the cursor is visible right away */ cursor_set(seat, LAB_CURSOR_DEFAULT); dnd_init(seat); seat->cursor_motion.notify = cursor_motion; wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion); seat->cursor_motion_absolute.notify = cursor_motion_absolute; wl_signal_add(&seat->cursor->events.motion_absolute, &seat->cursor_motion_absolute); seat->cursor_button.notify = cursor_button; wl_signal_add(&seat->cursor->events.button, &seat->cursor_button); seat->cursor_axis.notify = cursor_axis; wl_signal_add(&seat->cursor->events.axis, &seat->cursor_axis); seat->cursor_frame.notify = cursor_frame; wl_signal_add(&seat->cursor->events.frame, &seat->cursor_frame); gestures_init(seat); touch_init(seat); seat->request_cursor.notify = request_cursor_notify; wl_signal_add(&seat->seat->events.request_set_cursor, &seat->request_cursor); struct wlr_cursor_shape_manager_v1 *cursor_shape_manager = wlr_cursor_shape_manager_v1_create(seat->server->wl_display, LAB_CURSOR_SHAPE_V1_VERSION); if (!cursor_shape_manager) { wlr_log(WLR_ERROR, "unable to create cursor_shape interface"); exit(EXIT_FAILURE); } seat->request_set_shape.notify = request_set_shape_notify; wl_signal_add(&cursor_shape_manager->events.request_set_shape, &seat->request_set_shape); seat->request_set_selection.notify = request_set_selection_notify; wl_signal_add(&seat->seat->events.request_set_selection, &seat->request_set_selection); seat->request_set_primary_selection.notify = request_set_primary_selection_notify; wl_signal_add(&seat->seat->events.request_set_primary_selection, &seat->request_set_primary_selection); } void cursor_finish(struct seat *seat) { /* TODO: either clean up all the listeners or none of them */ wl_list_remove(&seat->cursor_motion.link); wl_list_remove(&seat->cursor_motion_absolute.link); wl_list_remove(&seat->cursor_button.link); wl_list_remove(&seat->cursor_axis.link); wl_list_remove(&seat->cursor_frame.link); gestures_finish(seat); touch_finish(seat); wl_list_remove(&seat->request_cursor.link); wl_list_remove(&seat->request_set_shape.link); wl_list_remove(&seat->request_set_selection.link); wlr_xcursor_manager_destroy(seat->xcursor_manager); wlr_cursor_destroy(seat->cursor); dnd_finish(seat); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/input/gestures.c��������������������������������������������������������������������0000664�0000000�0000000�00000006324�14570443012�0016547�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <wlr/types/wlr_pointer_gestures_v1.h> #include "input/gestures.h" #include "labwc.h" static void handle_pointer_pinch_begin(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, pinch_begin); struct wlr_pointer_pinch_begin_event *event = data; wlr_pointer_gestures_v1_send_pinch_begin(seat->pointer_gestures, seat->seat, event->time_msec, event->fingers); } static void handle_pointer_pinch_update(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, pinch_update); struct wlr_pointer_pinch_update_event *event = data; wlr_pointer_gestures_v1_send_pinch_update(seat->pointer_gestures, seat->seat, event->time_msec, event->dx, event->dy, event->scale, event->rotation); } static void handle_pointer_pinch_end(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, pinch_end); struct wlr_pointer_pinch_end_event *event = data; wlr_pointer_gestures_v1_send_pinch_end(seat->pointer_gestures, seat->seat, event->time_msec, event->cancelled); } static void handle_pointer_swipe_begin(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, swipe_begin); struct wlr_pointer_swipe_begin_event *event = data; wlr_pointer_gestures_v1_send_swipe_begin(seat->pointer_gestures, seat->seat, event->time_msec, event->fingers); } static void handle_pointer_swipe_update(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, swipe_update); struct wlr_pointer_swipe_update_event *event = data; wlr_pointer_gestures_v1_send_swipe_update(seat->pointer_gestures, seat->seat, event->time_msec, event->dx, event->dy); } static void handle_pointer_swipe_end(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, swipe_end); struct wlr_pointer_swipe_end_event *event = data; wlr_pointer_gestures_v1_send_swipe_end(seat->pointer_gestures, seat->seat, event->time_msec, event->cancelled); } void gestures_init(struct seat *seat) { seat->pointer_gestures = wlr_pointer_gestures_v1_create(seat->server->wl_display); seat->pinch_begin.notify = handle_pointer_pinch_begin; wl_signal_add(&seat->cursor->events.pinch_begin, &seat->pinch_begin); seat->pinch_update.notify = handle_pointer_pinch_update; wl_signal_add(&seat->cursor->events.pinch_update, &seat->pinch_update); seat->pinch_end.notify = handle_pointer_pinch_end; wl_signal_add(&seat->cursor->events.pinch_end, &seat->pinch_end); seat->swipe_begin.notify = handle_pointer_swipe_begin; wl_signal_add(&seat->cursor->events.swipe_begin, &seat->swipe_begin); seat->swipe_update.notify = handle_pointer_swipe_update; wl_signal_add(&seat->cursor->events.swipe_update, &seat->swipe_update); seat->swipe_end.notify = handle_pointer_swipe_end; wl_signal_add(&seat->cursor->events.swipe_end, &seat->swipe_end); } void gestures_finish(struct seat *seat) { wl_list_remove(&seat->pinch_begin.link); wl_list_remove(&seat->pinch_update.link); wl_list_remove(&seat->pinch_end.link); wl_list_remove(&seat->swipe_begin.link); wl_list_remove(&seat->swipe_update.link); wl_list_remove(&seat->swipe_end.link); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/input/input.c�����������������������������������������������������������������������0000664�0000000�0000000�00000000500�14570443012�0016033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include "input/cursor.h" #include "input/input.h" #include "input/keyboard.h" void input_handlers_init(struct seat *seat) { cursor_init(seat); keyboard_group_init(seat); } void input_handlers_finish(struct seat *seat) { cursor_finish(seat); keyboard_group_finish(seat); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/input/key-state.c�������������������������������������������������������������������0000664�0000000�0000000�00000005676�14570443012�0016625�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <wlr/util/log.h> #include "input/key-state.h" #define MAX_PRESSED_KEYS (16) struct key_array { uint32_t keys[MAX_PRESSED_KEYS]; int nr_keys; }; static struct key_array pressed, pressed_mods, bound, pressed_sent; static void report(struct key_array *array, const char *msg) { static char *should_print; static bool has_run; if (!has_run) { should_print = getenv("LABWC_DEBUG_KEY_STATE"); has_run = true; } if (!should_print) { return; } printf("%s", msg); for (int i = 0; i < array->nr_keys; ++i) { printf("%d,", array->keys[i]); } printf("\n"); } static bool key_present(struct key_array *array, uint32_t keycode) { for (int i = 0; i < array->nr_keys; ++i) { if (array->keys[i] == keycode) { return true; } } return false; } static void remove_key(struct key_array *array, uint32_t keycode) { bool shifting = false; for (int i = 0; i < MAX_PRESSED_KEYS; ++i) { if (array->keys[i] == keycode) { --array->nr_keys; shifting = true; } if (shifting) { array->keys[i] = i < MAX_PRESSED_KEYS - 1 ? array->keys[i + 1] : 0; } } } static void add_key(struct key_array *array, uint32_t keycode) { if (!key_present(array, keycode) && array->nr_keys < MAX_PRESSED_KEYS) { array->keys[array->nr_keys++] = keycode; } } uint32_t * key_state_pressed_sent_keycodes(void) { report(&pressed, "before - pressed:"); report(&bound, "before - bound:"); /* pressed_sent = pressed - bound */ memcpy(pressed_sent.keys, pressed.keys, MAX_PRESSED_KEYS * sizeof(uint32_t)); pressed_sent.nr_keys = pressed.nr_keys; for (int i = 0; i < bound.nr_keys; ++i) { remove_key(&pressed_sent, bound.keys[i]); } report(&pressed_sent, "after - pressed_sent:"); return pressed_sent.keys; } int key_state_nr_pressed_sent_keycodes(void) { return pressed_sent.nr_keys; } void key_state_set_pressed(uint32_t keycode, bool is_pressed, bool is_modifier) { if (is_pressed) { add_key(&pressed, keycode); if (is_modifier) { add_key(&pressed_mods, keycode); } } else { remove_key(&pressed, keycode); remove_key(&pressed_mods, keycode); } } void key_state_store_pressed_key_as_bound(uint32_t keycode) { add_key(&bound, keycode); /* * Also store any pressed modifiers as bound. This prevents * applications from seeing and handling the release event for * a modifier key that was part of a keybinding (e.g. Firefox * displays its menu bar for a lone Alt press + release). */ for (int i = 0; i < pressed_mods.nr_keys; ++i) { add_key(&bound, pressed_mods.keys[i]); } } bool key_state_corresponding_press_event_was_bound(uint32_t keycode) { return key_present(&bound, keycode); } void key_state_bound_key_remove(uint32_t keycode) { remove_key(&bound, keycode); } int key_state_nr_bound_keys(void) { return bound.nr_keys; } int key_state_nr_pressed_keys(void) { return pressed.nr_keys; } ������������������������������������������������������������������labwc-0.7.1/src/input/keyboard.c��������������������������������������������������������������������0000664�0000000�0000000�00000045546�14570443012�0016517�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <wlr/backend/multi.h> #include <wlr/backend/session.h> #include <wlr/interfaces/wlr_keyboard.h> #include "action.h" #include "idle.h" #include "input/keyboard.h" #include "input/key-state.h" #include "labwc.h" #include "menu/menu.h" #include "regions.h" #include "view.h" #include "workspaces.h" enum lab_key_handled { LAB_KEY_HANDLED_FALSE = 0, LAB_KEY_HANDLED_TRUE = 1, LAB_KEY_HANDLED_TRUE_AND_VT_CHANGED, }; struct keysyms { const xkb_keysym_t *syms; int nr_syms; }; struct keyinfo { xkb_keycode_t xkb_keycode; struct keysyms translated; struct keysyms raw; uint32_t modifiers; bool is_modifier; }; static bool should_cancel_cycling_on_next_key_release; static void change_vt(struct server *server, unsigned int vt) { wlr_session_change_vt(server->session, vt); } bool keyboard_any_modifiers_pressed(struct wlr_keyboard *keyboard) { xkb_mod_index_t i; for (i = 0; i < xkb_keymap_num_mods(keyboard->keymap); i++) { if (xkb_state_mod_index_is_active(keyboard->xkb_state, i, XKB_STATE_MODS_DEPRESSED)) { return true; } } return false; } static void end_cycling(struct server *server) { if (server->osd_state.cycle_view) { desktop_focus_view(server->osd_state.cycle_view, /*raise*/ true); } /* osd_finish() additionally resets cycle_view to NULL */ osd_finish(server); should_cancel_cycling_on_next_key_release = false; } static void keyboard_modifiers_notify(struct wl_listener *listener, void *data) { struct keyboard *keyboard = wl_container_of(listener, keyboard, modifier); struct seat *seat = keyboard->base.seat; struct server *server = seat->server; struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; if (server->input_mode == LAB_INPUT_STATE_MOVE) { /* Any change to the modifier state re-enable region snap */ seat->region_prevent_snap = false; } if (server->osd_state.cycle_view || server->grabbed_view || seat->workspace_osd_shown_by_modifier) { if (!keyboard_any_modifiers_pressed(wlr_keyboard)) { if (server->osd_state.cycle_view) { if (key_state_nr_bound_keys()) { should_cancel_cycling_on_next_key_release = true; } else { end_cycling(server); } } if (seat->workspace_osd_shown_by_modifier) { workspaces_osd_hide(seat); } if (server->grabbed_view) { regions_hide_overlay(seat); } } } wlr_seat_keyboard_notify_modifiers(seat->seat, &wlr_keyboard->modifiers); } static struct keybind * match_keybinding_for_sym(struct server *server, uint32_t modifiers, xkb_keysym_t sym, xkb_keycode_t xkb_keycode) { struct keybind *keybind; wl_list_for_each(keybind, &rc.keybinds, link) { if (modifiers ^ keybind->modifiers) { continue; } if (server->seat.nr_inhibited_keybind_views && server->active_view && server->active_view->inhibits_keybinds && !actions_contain_toggle_keybinds(&keybind->actions)) { continue; } if (sym == XKB_KEY_NoSymbol) { /* Use keycodes */ for (size_t i = 0; i < keybind->keycodes_len; i++) { if (keybind->keycodes[i] == xkb_keycode) { return keybind; } } } else { /* Use syms */ for (size_t i = 0; i < keybind->keysyms_len; i++) { if (xkb_keysym_to_lower(sym) == keybind->keysyms[i]) { return keybind; } } } } return NULL; } /* * When matching against keybinds, we process the input keys in the * following order of precedence: * a. Keycodes (of physical keys) (not if keybind is layoutDependent) * b. Translated keysyms (taking into account modifiers, so if Shift+1 * were pressed on a us keyboard, the keysym would be '!') * c. Raw keysyms (ignoring modifiers such as shift, so in the above * example the keysym would just be '1') * * The reasons for this approach are: * 1. To make keybinds keyboard-layout agnostic (by checking keycodes * before keysyms). This means that in a multi-layout situation, * keybinds work regardless of which layout is active at the time * of the key-press. * 2. To support keybinds relating to keysyms that are only available * in a particular layout, for example å, ä and ö. * 3. To support keybinds that are only valid with a modifier, for * example the numpad keys with NumLock enabled: KP_x. These would * only be matched by the translated keysyms. * 4. To support keybinds such as `S-1` (by checking raw keysyms). * * Reason 4 will also be satisfied by matching the keycodes. However, * when a keybind is configured to be layoutDependent we still need * the raw keysym fallback. */ static struct keybind * match_keybinding(struct server *server, struct keyinfo *keyinfo, bool is_virtual) { if (is_virtual) { goto process_syms; } /* First try keycodes */ struct keybind *keybind = match_keybinding_for_sym(server, keyinfo->modifiers, XKB_KEY_NoSymbol, keyinfo->xkb_keycode); if (keybind) { wlr_log(WLR_DEBUG, "keycode matched"); return keybind; } process_syms: /* Then fall back to keysyms */ for (int i = 0; i < keyinfo->translated.nr_syms; i++) { keybind = match_keybinding_for_sym(server, keyinfo->modifiers, keyinfo->translated.syms[i], keyinfo->xkb_keycode); if (keybind) { wlr_log(WLR_DEBUG, "translated keysym matched"); return keybind; } } /* And finally test for keysyms without modifier */ for (int i = 0; i < keyinfo->raw.nr_syms; i++) { keybind = match_keybinding_for_sym(server, keyinfo->modifiers, keyinfo->raw.syms[i], keyinfo->xkb_keycode); if (keybind) { wlr_log(WLR_DEBUG, "raw keysym matched"); return keybind; } } return NULL; } static bool is_modifier_key(xkb_keysym_t sym) { switch (sym) { case XKB_KEY_Shift_L: case XKB_KEY_Shift_R: case XKB_KEY_Alt_L: case XKB_KEY_Alt_R: case XKB_KEY_Control_L: case XKB_KEY_Control_R: case XKB_KEY_Super_L: case XKB_KEY_Super_R: case XKB_KEY_Hyper_L: case XKB_KEY_Hyper_R: case XKB_KEY_Meta_L: case XKB_KEY_Meta_R: case XKB_KEY_Mode_switch: case XKB_KEY_ISO_Level3_Shift: case XKB_KEY_ISO_Level5_Shift: return true; default: return false; } } static struct keyinfo get_keyinfo(struct wlr_keyboard *wlr_keyboard, uint32_t evdev_keycode) { struct keyinfo keyinfo = {0}; /* Translate evdev/libinput keycode -> xkb */ keyinfo.xkb_keycode = evdev_keycode + 8; /* Get a list of keysyms based on the keymap for this keyboard */ keyinfo.translated.nr_syms = xkb_state_key_get_syms( wlr_keyboard->xkb_state, keyinfo.xkb_keycode, &keyinfo.translated.syms); /* * Get keysyms from the keyboard as if there was no modifier * translations. For example, get Shift+1 rather than Shift+! * (with US keyboard layout). */ xkb_layout_index_t layout_index = xkb_state_key_get_layout( wlr_keyboard->xkb_state, keyinfo.xkb_keycode); keyinfo.raw.nr_syms = xkb_keymap_key_get_syms_by_level( wlr_keyboard->keymap, keyinfo.xkb_keycode, layout_index, 0, &keyinfo.raw.syms); /* * keyboard_key_notify() is called before keyboard_key_modifier(), * so 'modifiers' refers to modifiers that were pressed before the * key event in hand. Consequently, we use is_modifier_key() to * find out if the key event being processed is a modifier. * * Sway solves this differently by saving the 'modifiers' state * and checking if it has changed each time we get to the equivalent * of this function. If it has changed, it concludes that the last * key was a modifier and then deletes it from the buffer of pressed * keycodes. For us the equivalent would look something like this: * * static uint32_t last_modifiers; * bool last_key_was_a_modifier = last_modifiers != modifiers; * last_modifiers = modifiers; * if (last_key_was_a_modifier) { * key_state_remove_last_pressed_key(last_pressed_keycode); * } */ keyinfo.modifiers = wlr_keyboard_get_modifiers(wlr_keyboard); keyinfo.is_modifier = false; for (int i = 0; i < keyinfo.translated.nr_syms; i++) { keyinfo.is_modifier |= is_modifier_key(keyinfo.translated.syms[i]); } return keyinfo; } static bool handle_key_release(struct server *server, uint32_t evdev_keycode) { /* * Release events for keys that were not bound should always be * forwarded to clients to avoid stuck keys. */ if (!key_state_corresponding_press_event_was_bound(evdev_keycode)) { return false; } /* * If a user lets go of the modifier (e.g. alt) before the 'normal' * key (e.g. tab) when window-cycling, we do not end the cycling * until both keys have been released. If we end the window-cycling * on release of the modifier only, some XWayland clients such as * hexchat realise that tab is pressed (even though we did not * forward the event) and because we absorb the equivalent release * event it gets stuck on repeat. */ if (should_cancel_cycling_on_next_key_release) { end_cycling(server); } /* * If a press event was handled by a compositor binding, then do * not forward the corresponding release event to clients. */ key_state_bound_key_remove(evdev_keycode); return true; } static bool handle_change_vt_key(struct server *server, struct keyboard *keyboard, struct keysyms *translated) { for (int i = 0; i < translated->nr_syms; i++) { unsigned int vt = translated->syms[i] - XKB_KEY_XF86Switch_VT_1 + 1; if (vt >= 1 && vt <= 12) { keyboard_cancel_keybind_repeat(keyboard); change_vt(server, vt); return true; } } return false; } static void handle_menu_keys(struct server *server, struct keysyms *syms) { assert(server->input_mode == LAB_INPUT_STATE_MENU); for (int i = 0; i < syms->nr_syms; i++) { switch (syms->syms[i]) { case XKB_KEY_Down: menu_item_select_next(server); break; case XKB_KEY_Up: menu_item_select_previous(server); break; case XKB_KEY_Right: menu_submenu_enter(server); break; case XKB_KEY_Left: menu_submenu_leave(server); break; case XKB_KEY_Return: menu_call_selected_actions(server); break; case XKB_KEY_Escape: menu_close_root(server); cursor_update_focus(server); break; default: continue; } break; } } static void handle_cycle_view_key(struct server *server, struct keyinfo *keyinfo) { for (int i = 0; i < keyinfo->translated.nr_syms; i++) { if (keyinfo->translated.syms[i] == XKB_KEY_Escape) { /* cancel view-cycle */ osd_preview_restore(server); osd_finish(server); return; } } /* cycle to next */ if (!keyinfo->is_modifier) { bool back_key = false; for (int i = 0; i < keyinfo->translated.nr_syms; i++) { if (keyinfo->translated.syms[i] == XKB_KEY_Up || keyinfo->translated.syms[i] == XKB_KEY_Left) { back_key = true; break; } } bool backwards = (keyinfo->modifiers & WLR_MODIFIER_SHIFT) || back_key; enum lab_cycle_dir dir = backwards ? LAB_CYCLE_DIR_BACKWARD : LAB_CYCLE_DIR_FORWARD; server->osd_state.cycle_view = desktop_cycle_view(server, server->osd_state.cycle_view, dir); osd_update(server); } } static enum lab_key_handled handle_compositor_keybindings(struct keyboard *keyboard, struct wlr_keyboard_key_event *event) { struct seat *seat = keyboard->base.seat; struct server *server = seat->server; struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; struct keyinfo keyinfo = get_keyinfo(wlr_keyboard, event->keycode); key_state_set_pressed(event->keycode, event->state == WL_KEYBOARD_KEY_STATE_PRESSED, keyinfo.is_modifier); if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { return handle_key_release(server, event->keycode); } /* Catch C-A-F1 to C-A-F12 to change tty */ if (handle_change_vt_key(server, keyboard, &keyinfo.translated)) { key_state_store_pressed_key_as_bound(event->keycode); return LAB_KEY_HANDLED_TRUE_AND_VT_CHANGED; } /* * Ignore labwc keybindings if input is inhibited * It's important to do this after key_state_set_pressed() to ensure * _all_ key press/releases are registered */ if (seat->active_client_while_inhibited) { return false; } if (seat->server->session_lock) { return false; } if (server->input_mode == LAB_INPUT_STATE_MENU) { key_state_store_pressed_key_as_bound(event->keycode); handle_menu_keys(server, &keyinfo.translated); return true; } if (server->osd_state.cycle_view) { key_state_store_pressed_key_as_bound(event->keycode); handle_cycle_view_key(server, &keyinfo); return true; } /* * Handle compositor keybinds */ struct keybind *keybind = match_keybinding(server, &keyinfo, keyboard->is_virtual); if (keybind) { /* * Update key-state before action_run() because the action * might lead to seat_focus() in which case we pass the * 'pressed-sent' keys to the new surface. */ key_state_store_pressed_key_as_bound(event->keycode); actions_run(NULL, server, &keybind->actions, 0); return true; } return false; } static int handle_keybind_repeat(void *data) { struct keyboard *keyboard = data; assert(keyboard->keybind_repeat); assert(keyboard->keybind_repeat_rate > 0); /* synthesize event */ struct wlr_keyboard_key_event event = { .keycode = keyboard->keybind_repeat_keycode, .state = WL_KEYBOARD_KEY_STATE_PRESSED }; handle_compositor_keybindings(keyboard, &event); int next_repeat_ms = 1000 / keyboard->keybind_repeat_rate; wl_event_source_timer_update(keyboard->keybind_repeat, next_repeat_ms); return 0; /* ignored per wl_event_loop docs */ } static void start_keybind_repeat(struct server *server, struct keyboard *keyboard, struct wlr_keyboard_key_event *event) { struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; assert(!keyboard->keybind_repeat); if (wlr_keyboard->repeat_info.rate > 0 && wlr_keyboard->repeat_info.delay > 0) { keyboard->keybind_repeat_keycode = event->keycode; keyboard->keybind_repeat_rate = wlr_keyboard->repeat_info.rate; keyboard->keybind_repeat = wl_event_loop_add_timer( server->wl_event_loop, handle_keybind_repeat, keyboard); wl_event_source_timer_update(keyboard->keybind_repeat, wlr_keyboard->repeat_info.delay); } } void keyboard_cancel_keybind_repeat(struct keyboard *keyboard) { if (keyboard->keybind_repeat) { wl_event_source_remove(keyboard->keybind_repeat); keyboard->keybind_repeat = NULL; } } static void keyboard_key_notify(struct wl_listener *listener, void *data) { /* This event is raised when a key is pressed or released. */ struct keyboard *keyboard = wl_container_of(listener, keyboard, key); struct seat *seat = keyboard->base.seat; struct wlr_keyboard_key_event *event = data; struct wlr_seat *wlr_seat = seat->seat; idle_manager_notify_activity(seat->seat); /* any new press/release cancels current keybind repeat */ keyboard_cancel_keybind_repeat(keyboard); enum lab_key_handled handled = handle_compositor_keybindings(keyboard, event); if (handled == LAB_KEY_HANDLED_TRUE_AND_VT_CHANGED) { return; } if (handled) { if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { start_keybind_repeat(seat->server, keyboard, event); } } else { wlr_seat_set_keyboard(wlr_seat, keyboard->wlr_keyboard); wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, event->keycode, event->state); } } void keyboard_set_numlock(struct wlr_keyboard *keyboard) { xkb_mod_index_t num_idx = xkb_map_mod_get_index(keyboard->keymap, XKB_MOD_NAME_NUM); if (num_idx == XKB_MOD_INVALID) { wlr_log(WLR_INFO, "Failed to set Num Lock: not found in keymap"); return; } xkb_mod_mask_t locked = keyboard->modifiers.locked; if (rc.kb_numlock_enable) { locked |= (xkb_mod_mask_t)1 << num_idx; } else { locked &= ~((xkb_mod_mask_t)1 << num_idx); } /* * This updates the xkb-state + kb->modifiers and also triggers the * keyboard->events.modifiers signal (the signal has no effect in * current labwc usage since the keyboard is not part of a * keyboard-group yet). */ wlr_keyboard_notify_modifiers(keyboard, keyboard->modifiers.depressed, keyboard->modifiers.latched, locked, keyboard->modifiers.group); } void keyboard_update_layout(struct seat *seat, xkb_layout_index_t layout) { assert(seat); struct input *input; struct keyboard *keyboard; struct wlr_keyboard *kb = NULL; /* We are not using wlr_seat_get_keyboard() here because it might be a virtual one */ wl_list_for_each(input, &seat->inputs, link) { if (input->wlr_input_device->type != WLR_INPUT_DEVICE_KEYBOARD) { continue; } keyboard = (struct keyboard *)input; if (keyboard->is_virtual) { continue; } kb = keyboard->wlr_keyboard; break; } if (!kb) { wlr_log(WLR_INFO, "Restoring kb layout failed: no physical keyboard found"); return; } if (kb->modifiers.group == layout) { return; } /* By updating a member of the keyboard group, all members of the group will get updated */ wlr_log(WLR_DEBUG, "Updating group layout to %u", layout); wlr_keyboard_notify_modifiers(kb, kb->modifiers.depressed, kb->modifiers.latched, kb->modifiers.locked, layout); } static void reset_window_keyboard_layout_groups(struct server *server) { if (!rc.kb_layout_per_window) { return; } /* * Technically it would be possible to reconcile previous group indices * to new group ones if particular layouts exist in both old and new, * but let's keep it simple for now and just reset them all. */ struct view *view; for_each_view(view, &server->views, LAB_VIEW_CRITERIA_NONE) { view->keyboard_layout = 0; } struct view *active_view = server->active_view; if (!active_view) { return; } keyboard_update_layout(&server->seat, active_view->keyboard_layout); } /* * Set layout based on environment variables XKB_DEFAULT_LAYOUT, * XKB_DEFAULT_OPTIONS, and friends. */ static void set_layout(struct server *server, struct wlr_keyboard *kb) { struct xkb_rule_names rules = { 0 }; struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); if (keymap) { if (!wlr_keyboard_keymaps_match(kb->keymap, keymap)) { wlr_keyboard_set_keymap(kb, keymap); reset_window_keyboard_layout_groups(server); } xkb_keymap_unref(keymap); } else { wlr_log(WLR_ERROR, "Failed to create xkb keymap"); } xkb_context_unref(context); } void keyboard_configure(struct seat *seat, struct wlr_keyboard *kb, bool is_virtual) { if (!is_virtual) { set_layout(seat->server, kb); } wlr_keyboard_set_repeat_info(kb, rc.repeat_rate, rc.repeat_delay); keybind_update_keycodes(seat->server); } void keyboard_group_init(struct seat *seat) { if (seat->keyboard_group) { return; } seat->keyboard_group = wlr_keyboard_group_create(); keyboard_configure(seat, &seat->keyboard_group->keyboard, /* is_virtual */ false); } void keyboard_setup_handlers(struct keyboard *keyboard) { struct wlr_keyboard *wlr_kb = keyboard->wlr_keyboard; keyboard->key.notify = keyboard_key_notify; wl_signal_add(&wlr_kb->events.key, &keyboard->key); keyboard->modifier.notify = keyboard_modifiers_notify; wl_signal_add(&wlr_kb->events.modifiers, &keyboard->modifier); } void keyboard_group_finish(struct seat *seat) { /* * All keyboard listeners must be removed before this to avoid use after * free */ if (seat->keyboard_group) { wlr_keyboard_group_destroy(seat->keyboard_group); seat->keyboard_group = NULL; } } ����������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/input/meson.build�������������������������������������������������������������������0000664�0000000�0000000�00000000223�14570443012�0016674�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc_sources += files( 'cursor.c', 'tablet.c', 'tablet_pad.c', 'gestures.c', 'input.c', 'keyboard.c', 'key-state.c', 'touch.c', ) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/input/tablet.c����������������������������������������������������������������������0000664�0000000�0000000�00000007230�14570443012�0016156�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <stdlib.h> #include <linux/input-event-codes.h> #include <wlr/types/wlr_tablet_pad.h> #include <wlr/types/wlr_tablet_tool.h> #include <wlr/util/log.h> #include "common/macros.h" #include "common/mem.h" #include "config/rcxml.h" #include "input/cursor.h" #include "input/tablet.h" static void adjust_for_tablet_area(double tablet_width, double tablet_height, struct wlr_fbox box, double *x, double *y) { if ((!box.x && !box.y && !box.width && !box.height) || !tablet_width || !tablet_height) { return; } if (!box.width) { box.width = tablet_width - box.x; } if (!box.height) { box.height = tablet_height - box.y; } if (box.x + box.width <= tablet_width) { const double max_x = 1; double width_offset = max_x * box.x / tablet_width; *x = (*x - width_offset) * tablet_width / box.width; } if (box.y + box.height <= tablet_height) { const double max_y = 1; double height_offset = max_y * box.y / tablet_height; *y = (*y - height_offset) * tablet_height / box.height; } } static void adjust_for_rotation(enum rotation rotation, double *x, double *y) { double tmp; switch (rotation) { case LAB_ROTATE_NONE: break; case LAB_ROTATE_90: tmp = *x; *x = 1.0 - *y; *y = tmp; break; case LAB_ROTATE_180: *x = 1.0 - *x; *y = 1.0 - *y; break; case LAB_ROTATE_270: tmp = *x; *x = *y; *y = 1.0 - tmp; break; } } static void handle_axis(struct wl_listener *listener, void *data) { struct wlr_tablet_tool_axis_event *ev = data; struct drawing_tablet *tablet = ev->tablet->data; if (ev->updated_axes & (WLR_TABLET_TOOL_AXIS_X | WLR_TABLET_TOOL_AXIS_Y)) { if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_X) { tablet->x = ev->x; } if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_Y) { tablet->y = ev->y; } double x = tablet->x; double y = tablet->y; adjust_for_tablet_area(tablet->tablet->width_mm, tablet->tablet->height_mm, rc.tablet.box, &x, &y); adjust_for_rotation(rc.tablet.rotation, &x, &y); cursor_emulate_move_absolute(tablet->seat, &ev->tablet->base, x, y, ev->time_msec); } // Ignore other events } static void handle_tip(struct wl_listener *listener, void *data) { struct wlr_tablet_tool_tip_event *ev = data; struct drawing_tablet *tablet = ev->tablet->data; uint32_t button = tablet_get_mapped_button(BTN_TOOL_PEN); if (!button) { return; } cursor_emulate_button(tablet->seat, button, ev->state == WLR_TABLET_TOOL_TIP_DOWN ? WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED, ev->time_msec); } static void handle_button(struct wl_listener *listener, void *data) { struct wlr_tablet_tool_button_event *ev = data; struct drawing_tablet *tablet = ev->tablet->data; uint32_t button = tablet_get_mapped_button(ev->button); if (!button) { return; } cursor_emulate_button(tablet->seat, button, ev->state, ev->time_msec); } static void handle_destroy(struct wl_listener *listener, void *data) { struct drawing_tablet *tablet = wl_container_of(listener, tablet, handlers.destroy); free(tablet); } void tablet_init(struct seat *seat, struct wlr_input_device *wlr_device) { wlr_log(WLR_DEBUG, "setting up tablet"); struct drawing_tablet *tablet = znew(*tablet); tablet->seat = seat; tablet->tablet = wlr_tablet_from_input_device(wlr_device); tablet->tablet->data = tablet; tablet->x = 0.0; tablet->y = 0.0; wlr_log(WLR_INFO, "tablet dimensions: %.2fmm x %.2fmm", tablet->tablet->width_mm, tablet->tablet->height_mm); CONNECT_SIGNAL(tablet->tablet, &tablet->handlers, axis); CONNECT_SIGNAL(tablet->tablet, &tablet->handlers, tip); CONNECT_SIGNAL(tablet->tablet, &tablet->handlers, button); CONNECT_SIGNAL(wlr_device, &tablet->handlers, destroy); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/input/tablet_pad.c������������������������������������������������������������������0000664�0000000�0000000�00000002352�14570443012�0017002�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <stdlib.h> #include <wlr/types/wlr_tablet_pad.h> #include <wlr/util/log.h> #include "common/macros.h" #include "common/mem.h" #include "config/rcxml.h" #include "input/cursor.h" #include "input/tablet_pad.h" static void handle_button(struct wl_listener *listener, void *data) { struct drawing_tablet_pad *tablet_pad = wl_container_of(listener, tablet_pad, handlers.button); struct wlr_tablet_pad_button_event *ev = data; uint32_t button = tablet_get_mapped_button(ev->button); if (!button) { return; } cursor_emulate_button(tablet_pad->seat, button, ev->state, ev->time_msec); } static void handle_destroy(struct wl_listener *listener, void *data) { struct drawing_tablet_pad *tablet = wl_container_of(listener, tablet, handlers.destroy); free(tablet); } void tablet_pad_init(struct seat *seat, struct wlr_input_device *wlr_device) { wlr_log(WLR_DEBUG, "setting up tablet pad"); struct drawing_tablet_pad *tablet = znew(*tablet); tablet->seat = seat; tablet->tablet = wlr_tablet_pad_from_input_device(wlr_device); tablet->tablet->data = tablet; CONNECT_SIGNAL(tablet->tablet, &tablet->handlers, button); CONNECT_SIGNAL(wlr_device, &tablet->handlers, destroy); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/input/touch.c�����������������������������������������������������������������������0000664�0000000�0000000�00000010432�14570443012�0016023�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <wayland-util.h> #include <wlr/types/wlr_touch.h> #include "common/mem.h" #include "common/scene-helpers.h" #include "idle.h" #include "input/touch.h" #include "labwc.h" /* Holds layout -> surface offsets to report motion events in relative coords */ struct touch_point { int32_t touch_id; uint32_t x_offset; uint32_t y_offset; struct wl_list link; /* seat.touch_points */ }; static struct wlr_surface* touch_get_coords(struct seat *seat, struct wlr_touch *touch, double x, double y, double *x_offset, double *y_offset) { /* Convert coordinates: first [0, 1] => layout, then layout => surface */ double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, &touch->base, x, y, &lx, &ly); double sx, sy; struct wlr_scene_node *node = wlr_scene_node_at(&seat->server->scene->tree.node, lx, ly, &sx, &sy); *x_offset = lx - sx; *y_offset = ly - sy; /* Find the surface and return it if it accepts touch events */ struct wlr_surface *surface = lab_wlr_surface_from_node(node); if (surface && !wlr_surface_accepts_touch(seat->seat, surface)) { surface = NULL; } return surface; } static void touch_motion(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, touch_motion); struct wlr_touch_motion_event *event = data; idle_manager_notify_activity(seat->seat); /* Convert coordinates: first [0, 1] => layout, then apply offsets */ double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->touch->base, event->x, event->y, &lx, &ly); /* Find existing touch point to determine initial offsets to subtract */ struct touch_point *touch_point; wl_list_for_each(touch_point, &seat->touch_points, link) { if (touch_point->touch_id == event->touch_id) { double sx = lx - touch_point->x_offset; double sy = ly - touch_point->y_offset; wlr_seat_touch_notify_motion(seat->seat, event->time_msec, event->touch_id, sx, sy); return; } } } static void touch_frame(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, touch_frame); wlr_seat_touch_notify_frame(seat->seat); } static void touch_down(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, touch_down); struct wlr_touch_down_event *event = data; /* Compute layout => surface offset and save for this touch point */ double x_offset, y_offset; struct wlr_surface *surface = touch_get_coords(seat, event->touch, event->x, event->y, &x_offset, &y_offset); struct touch_point *touch_point = znew(*touch_point); touch_point->touch_id = event->touch_id; touch_point->x_offset = x_offset; touch_point->y_offset = y_offset; wl_list_insert(&seat->touch_points, &touch_point->link); double lx, ly; wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->touch->base, event->x, event->y, &lx, &ly); /* Apply offsets to get surface coords before reporting event */ double sx = lx - x_offset; double sy = ly - y_offset; if (surface) { wlr_seat_touch_notify_down(seat->seat, surface, event->time_msec, event->touch_id, sx, sy); } } static void touch_up(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, touch_up); struct wlr_touch_up_event *event = data; /* Remove the touch point from the seat */ struct touch_point *touch_point, *tmp; wl_list_for_each_safe(touch_point, tmp, &seat->touch_points, link) { if (touch_point->touch_id == event->touch_id) { wl_list_remove(&touch_point->link); zfree(touch_point); break; } } wlr_seat_touch_notify_up(seat->seat, event->time_msec, event->touch_id); } void touch_init(struct seat *seat) { seat->touch_down.notify = touch_down; wl_signal_add(&seat->cursor->events.touch_down, &seat->touch_down); seat->touch_up.notify = touch_up; wl_signal_add(&seat->cursor->events.touch_up, &seat->touch_up); seat->touch_motion.notify = touch_motion; wl_signal_add(&seat->cursor->events.touch_motion, &seat->touch_motion); seat->touch_frame.notify = touch_frame; wl_signal_add(&seat->cursor->events.touch_frame, &seat->touch_frame); } void touch_finish(struct seat *seat) { wl_list_remove(&seat->touch_down.link); wl_list_remove(&seat->touch_up.link); wl_list_remove(&seat->touch_motion.link); wl_list_remove(&seat->touch_frame.link); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/interactive.c�����������������������������������������������������������������������0000664�0000000�0000000�00000013767�14570443012�0016075�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include "edges.h" #include "input/keyboard.h" #include "labwc.h" #include "regions.h" #include "resize_indicator.h" #include "view.h" #include "window-rules.h" static int max_move_scale(double pos_cursor, double pos_current, double size_current, double size_orig) { double anchor_frac = (pos_cursor - pos_current) / size_current; int pos_new = pos_cursor - (size_orig * anchor_frac); if (pos_new < pos_current) { /* Clamp by using the old offsets of the maximized window */ pos_new = pos_current; } return pos_new; } void interactive_begin(struct view *view, enum input_mode mode, uint32_t edges) { /* * This function sets up an interactive move or resize operation, where * the compositor stops propagating pointer events to clients and * instead consumes them itself, to move or resize windows. */ struct server *server = view->server; struct seat *seat = &server->seat; struct wlr_box geometry = view->current; if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { return; } /* Prevent moving/resizing fixed-position and panel-like views */ if (window_rules_get_property(view, "fixedPosition") == LAB_PROP_TRUE || view_has_strut_partial(view)) { return; } switch (mode) { case LAB_INPUT_STATE_MOVE: if (view->fullscreen) { /** * We don't allow moving fullscreen windows. * * If you think there is a good reason to allow * it, feel free to open an issue explaining * your use-case. */ return; } if (!view_is_floating(view)) { /* * Un-maximize, unshade and restore natural * width/height. * Don't reset tiled state yet since we may want * to keep it (in the snap-to-maximize case). */ geometry = view->natural_geometry; geometry.x = max_move_scale(seat->cursor->x, view->current.x, view->current.width, geometry.width); geometry.y = max_move_scale(seat->cursor->y, view->current.y, view->current.height, geometry.height); view_set_shade(view, false); view_set_untiled(view); view_restore_to(view, geometry); } else { /* Store natural geometry at start of move */ view_store_natural_geometry(view); view_invalidate_last_layout_geometry(view); } /* Prevent region snapping when just moving via A-Left mousebind */ struct wlr_keyboard *keyboard = &seat->keyboard_group->keyboard; seat->region_prevent_snap = keyboard_any_modifiers_pressed(keyboard); cursor_set(seat, LAB_CURSOR_GRAB); break; case LAB_INPUT_STATE_RESIZE: if (view->shaded || view->fullscreen || view->maximized == VIEW_AXIS_BOTH) { /* * We don't allow resizing while shaded, * fullscreen or maximized in both directions. */ return; } /* * Resizing overrides any attempt to restore window * geometries altered by layout changes. */ view_invalidate_last_layout_geometry(view); /* * If tiled or maximized in only one direction, reset * tiled/maximized state but keep the same geometry as * the starting point for the resize. */ view_set_untiled(view); view_restore_to(view, view->pending); cursor_set(seat, cursor_get_from_edge(edges)); break; default: /* Should not be reached */ return; } server->input_mode = mode; server->grabbed_view = view; /* Remember view and cursor positions at start of move/resize */ server->grab_x = seat->cursor->x; server->grab_y = seat->cursor->y; server->grab_box = geometry; server->resize_edges = edges; if (rc.resize_indicator) { resize_indicator_show(view); } if (rc.window_edge_strength) { edges_calculate_visibility(server, view); } } /* Returns true if view was snapped to any edge */ static bool snap_to_edge(struct view *view) { int snap_range = rc.snap_edge_range; if (!snap_range) { return false; } struct output *output = output_nearest_to_cursor(view->server); if (!output_is_usable(output)) { wlr_log(WLR_ERROR, "output at cursor is unusable"); return false; } /* Translate into output local coordinates */ double cursor_x = view->server->seat.cursor->x; double cursor_y = view->server->seat.cursor->y; wlr_output_layout_output_coords(view->server->output_layout, output->wlr_output, &cursor_x, &cursor_y); struct wlr_box *area = &output->usable_area; enum view_edge edge; if (cursor_x <= area->x + snap_range) { edge = VIEW_EDGE_LEFT; } else if (cursor_x >= area->x + area->width - snap_range) { edge = VIEW_EDGE_RIGHT; } else if (cursor_y <= area->y + snap_range) { edge = VIEW_EDGE_UP; } else if (cursor_y >= area->y + area->height - snap_range) { edge = VIEW_EDGE_DOWN; } else { /* Not close to any edge */ return false; } view_set_output(view, output); /* * Don't store natural geometry here (it was * stored already in interactive_begin()) */ if (edge == VIEW_EDGE_UP && rc.snap_top_maximize) { view_maximize(view, VIEW_AXIS_BOTH, /*store_natural_geometry*/ false); } else { view_snap_to_edge(view, edge, /*across_outputs*/ false, /*store_natural_geometry*/ false); } return true; } static bool snap_to_region(struct view *view) { if (!regions_should_snap(view->server)) { return false; } struct region *region = regions_from_cursor(view->server); if (region) { view_snap_to_region(view, region, /*store_natural_geometry*/ false); return true; } return false; } void interactive_finish(struct view *view) { if (view->server->grabbed_view != view) { return; } if (view->server->input_mode == LAB_INPUT_STATE_MOVE) { if (!snap_to_region(view)) { snap_to_edge(view); } } interactive_cancel(view); } /* * Cancels interative move/resize without changing the state of the of * the view in any way. This may leave the tiled state inconsistent with * the actual geometry of the view. */ void interactive_cancel(struct view *view) { if (view->server->grabbed_view != view) { return; } regions_hide_overlay(&view->server->seat); resize_indicator_hide(view); view->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; view->server->grabbed_view = NULL; /* Update focus/cursor image */ cursor_update_focus(view->server); } ���������labwc-0.7.1/src/layers.c����������������������������������������������������������������������������0000664�0000000�0000000�00000035375�14570443012�0015056�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * layers.c - layer-shell implementation * * Based on https://github.com/swaywm/sway * Copyright (C) 2019 Drew DeVault and Sway developers */ #include <assert.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <wayland-server.h> #include <wlr/types/wlr_layer_shell_v1.h> #include <wlr/util/log.h> #include "common/macros.h" #include "common/mem.h" #include "config/rcxml.h" #include "layers.h" #include "labwc.h" #include "node.h" #define LAB_LAYERSHELL_VERSION 4 static void apply_override(struct output *output, struct wlr_box *usable_area) { struct usable_area_override *override; wl_list_for_each(override, &rc.usable_area_overrides, link) { if (override->output && strcasecmp(override->output, output->wlr_output->name)) { continue; } usable_area->x += override->margin.left; usable_area->y += override->margin.top; usable_area->width -= override->margin.left + override->margin.right; usable_area->height -= override->margin.top + override->margin.bottom; } } static void arrange_one_layer(const struct wlr_box *full_area, struct wlr_box *usable_area, struct wlr_scene_tree *tree, bool exclusive) { struct wlr_scene_node *node; wl_list_for_each(node, &tree->children, link) { struct lab_layer_surface *surface = node_layer_surface_from_node(node); struct wlr_scene_layer_surface_v1 *scene = surface->scene_layer_surface; if (!!scene->layer_surface->current.exclusive_zone != exclusive) { continue; } wlr_scene_layer_surface_v1_configure(scene, full_area, usable_area); } } /* * To ensure outputs/views are left in a consistent state, this * function should be called ONLY from output_update_usable_area() * or output_update_all_usable_areas(). */ void layers_arrange(struct output *output) { assert(output); struct wlr_box full_area = { 0 }; wlr_output_effective_resolution(output->wlr_output, &full_area.width, &full_area.height); struct wlr_box usable_area = full_area; apply_override(output, &usable_area); struct server *server = output->server; struct wlr_scene_output *scene_output = wlr_scene_get_scene_output(server->scene, output->wlr_output); if (!scene_output) { wlr_log(WLR_DEBUG, "no wlr_scene_output"); return; } for (int i = ARRAY_SIZE(output->layer_tree) - 1; i >= 0; i--) { struct wlr_scene_tree *layer = output->layer_tree[i]; /* * Process exclusive-zone clients before non-exclusive-zone * clients, so that the latter give way to the former regardless * of the order in which they were launched. * * Also start calculating the usable_area for exclusive-zone * clients from the Overlay layer down to the Background layer * to ensure that higher layers have a higher preference for * placement. * * The 'exclusive' boolean also matches -1 which means that * the layershell client wants to use the full screen rather * than the usable area. */ arrange_one_layer(&full_area, &usable_area, layer, /* exclusive */ true); } for (size_t i = 0; i < ARRAY_SIZE(output->layer_tree); i++) { struct wlr_scene_tree *layer = output->layer_tree[i]; arrange_one_layer(&full_area, &usable_area, layer, /* exclusive */ false); /* Set node position to account for output layout change */ wlr_scene_node_set_position(&layer->node, scene_output->x, scene_output->y); } output->usable_area = usable_area; } static void handle_output_destroy(struct wl_listener *listener, void *data) { struct lab_layer_surface *layer = wl_container_of(listener, layer, output_destroy); layer->scene_layer_surface->layer_surface->output = NULL; wlr_layer_surface_v1_destroy(layer->scene_layer_surface->layer_surface); } static void process_keyboard_interactivity(struct lab_layer_surface *layer) { struct wlr_layer_surface_v1 *layer_surface = layer->scene_layer_surface->layer_surface; struct seat *seat = &layer->server->seat; if (layer_surface->current.keyboard_interactive && layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { /* * Give keyboard focus to surface if * - keyboard-interactivity is 'exclusive' or 'on-demand'; and * - surface is in top/overlay layers; and * - currently focused layer has a lower precedence * * In other words, when dealing with two surfaces with * exclusive/on-demand keyboard-interactivity (firstly the * currently focused 'focused_layer' and secondly the * 'layer_surface' for which we're just responding to a * map/commit event), the following logic applies: * * | focused_layer | layer_surface | who gets keyboard focus | * |---------------|---------------|-------------------------| * | overlay | top | focused_layer | * | overlay | overlay | layer_surface | * | top | top | layer_surface | * | top | overlay | layer_surface | */ if (!seat->focused_layer || seat->focused_layer->current.layer <= layer_surface->current.layer) { seat_set_focus_layer(seat, layer_surface); } } else if (seat->focused_layer && !seat->focused_layer->current.keyboard_interactive) { /* * Clear focus if keyboard-interactivity has been set to * ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE */ seat_set_focus_layer(seat, NULL); } } static void handle_surface_commit(struct wl_listener *listener, void *data) { struct lab_layer_surface *layer = wl_container_of(listener, layer, surface_commit); struct wlr_layer_surface_v1 *layer_surface = layer->scene_layer_surface->layer_surface; struct wlr_output *wlr_output = layer->scene_layer_surface->layer_surface->output; if (!wlr_output) { return; } uint32_t committed = layer_surface->current.committed; struct output *output = (struct output *)wlr_output->data; /* Process layer change */ if (committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { wlr_scene_node_reparent(&layer->scene_layer_surface->tree->node, output->layer_tree[layer_surface->current.layer]); } /* Process keyboard-interactivity change */ if (committed & WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY) { process_keyboard_interactivity(layer); } if (committed || layer->mapped != layer_surface->surface->mapped) { layer->mapped = layer_surface->surface->mapped; output_update_usable_area(output); /* * Update cursor focus here to ensure we * enter a new/moved/resized layer surface. */ cursor_update_focus(layer->server); } } static void handle_node_destroy(struct wl_listener *listener, void *data) { struct lab_layer_surface *layer = wl_container_of(listener, layer, node_destroy); /* * TODO: Determine if this layer is being used by an exclusive client. * If it is, try and find another layer owned by this client to pass * focus to. */ wl_list_remove(&layer->map.link); wl_list_remove(&layer->unmap.link); wl_list_remove(&layer->surface_commit.link); wl_list_remove(&layer->new_popup.link); wl_list_remove(&layer->output_destroy.link); wl_list_remove(&layer->node_destroy.link); free(layer); } static void handle_unmap(struct wl_listener *listener, void *data) { struct lab_layer_surface *layer = wl_container_of(listener, layer, unmap); struct wlr_layer_surface_v1 *layer_surface = layer->scene_layer_surface->layer_surface; if (layer_surface->output) { output_update_usable_area(layer_surface->output->data); } struct seat *seat = &layer->server->seat; if (seat->focused_layer == layer_surface) { seat_set_focus_layer(seat, NULL); } } static void handle_map(struct wl_listener *listener, void *data) { struct lab_layer_surface *layer = wl_container_of(listener, layer, map); struct wlr_output *wlr_output = layer->scene_layer_surface->layer_surface->output; if (wlr_output) { output_update_usable_area(wlr_output->data); } /* * Since moving to the wlroots scene-graph API, there is no need to * call wlr_surface_send_enter() from here since that will be done * automatically based on the position of the surface and outputs in * the scene. See wlr_scene_surface_create() documentation. */ process_keyboard_interactivity(layer); } static void popup_handle_destroy(struct wl_listener *listener, void *data) { struct lab_layer_popup *popup = wl_container_of(listener, popup, destroy); wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->new_popup.link); /* Usually already removed unless there was no commit at all */ if (popup->commit.notify) { wl_list_remove(&popup->commit.link); } free(popup); } static void popup_handle_commit(struct wl_listener *listener, void *data) { struct lab_layer_popup *popup = wl_container_of(listener, popup, commit); if (popup->wlr_popup->base->initial_commit) { wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, &popup->output_toplevel_sx_box); /* Prevent getting called over and over again */ wl_list_remove(&popup->commit.link); popup->commit.notify = NULL; } } static void popup_handle_new_popup(struct wl_listener *listener, void *data); static struct lab_layer_popup * create_popup(struct wlr_xdg_popup *wlr_popup, struct wlr_scene_tree *parent) { struct lab_layer_popup *popup = znew(*popup); popup->wlr_popup = wlr_popup; popup->scene_tree = wlr_scene_xdg_surface_create(parent, wlr_popup->base); if (!popup->scene_tree) { free(popup); return NULL; } node_descriptor_create(&popup->scene_tree->node, LAB_NODE_DESC_LAYER_POPUP, popup); popup->destroy.notify = popup_handle_destroy; wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); popup->new_popup.notify = popup_handle_new_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); popup->commit.notify = popup_handle_commit; wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); return popup; } /* This popup's parent is a layer popup */ static void popup_handle_new_popup(struct wl_listener *listener, void *data) { struct lab_layer_popup *lab_layer_popup = wl_container_of(listener, lab_layer_popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; struct lab_layer_popup *new_popup = create_popup(wlr_popup, lab_layer_popup->scene_tree); new_popup->output_toplevel_sx_box = lab_layer_popup->output_toplevel_sx_box; } /* * We move popups from the bottom to the top layer so that they are * rendered above views. */ static void move_popup_to_top_layer(struct lab_layer_surface *toplevel, struct lab_layer_popup *popup) { struct server *server = toplevel->server; struct wlr_output *wlr_output = toplevel->scene_layer_surface->layer_surface->output; struct output *output = (struct output *)wlr_output->data; struct wlr_box box = { 0 }; wlr_output_layout_get_box(server->output_layout, wlr_output, &box); int lx = toplevel->scene_layer_surface->tree->node.x + box.x; int ly = toplevel->scene_layer_surface->tree->node.y + box.y; struct wlr_scene_node *node = &popup->scene_tree->node; wlr_scene_node_reparent(node, output->layer_popup_tree); /* FIXME: verify the whole tree should be repositioned */ wlr_scene_node_set_position(&output->layer_popup_tree->node, lx, ly); } /* This popup's parent is a shell-layer surface */ static void handle_new_popup(struct wl_listener *listener, void *data) { struct lab_layer_surface *toplevel = wl_container_of(listener, toplevel, new_popup); struct wlr_xdg_popup *wlr_popup = data; struct server *server = toplevel->server; struct wlr_scene_layer_surface_v1 *surface = toplevel->scene_layer_surface; struct output *output = surface->layer_surface->output->data; int lx, ly; wlr_scene_node_coords(&surface->tree->node, &lx, &ly); struct wlr_box output_box = { 0 }; wlr_output_layout_get_box(server->output_layout, output->wlr_output, &output_box); /* * Output geometry expressed in the coordinate system of the toplevel * parent of popup. We store this struct the lab_layer_popup struct * to make it easier to unconstrain children when we move popups from * the bottom to the top layer. */ struct wlr_box output_toplevel_sx_box = { .x = output_box.x - lx, .y = output_box.y - ly, .width = output_box.width, .height = output_box.height, }; struct lab_layer_popup *popup = create_popup(wlr_popup, surface->tree); popup->output_toplevel_sx_box = output_toplevel_sx_box; if (surface->layer_surface->current.layer <= ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) { move_popup_to_top_layer(toplevel, popup); } } static void handle_new_layer_surface(struct wl_listener *listener, void *data) { struct server *server = wl_container_of( listener, server, new_layer_surface); struct wlr_layer_surface_v1 *layer_surface = data; if (!layer_surface->output) { struct wlr_output *output = wlr_output_layout_output_at( server->output_layout, server->seat.cursor->x, server->seat.cursor->y); if (!output) { wlr_log(WLR_INFO, "No output available to assign layer surface"); wlr_layer_surface_v1_destroy(layer_surface); return; } layer_surface->output = output; } struct lab_layer_surface *surface = znew(*surface); struct output *output = layer_surface->output->data; struct wlr_scene_tree *selected_layer = output->layer_tree[layer_surface->current.layer]; surface->scene_layer_surface = wlr_scene_layer_surface_v1_create( selected_layer, layer_surface); if (!surface->scene_layer_surface) { wlr_layer_surface_v1_destroy(layer_surface); wlr_log(WLR_ERROR, "could not create layer surface"); return; } node_descriptor_create(&surface->scene_layer_surface->tree->node, LAB_NODE_DESC_LAYER_SURFACE, surface); surface->server = server; surface->scene_layer_surface->layer_surface = layer_surface; surface->surface_commit.notify = handle_surface_commit; wl_signal_add(&layer_surface->surface->events.commit, &surface->surface_commit); surface->map.notify = handle_map; wl_signal_add(&layer_surface->surface->events.map, &surface->map); surface->unmap.notify = handle_unmap; wl_signal_add(&layer_surface->surface->events.unmap, &surface->unmap); surface->new_popup.notify = handle_new_popup; wl_signal_add(&layer_surface->events.new_popup, &surface->new_popup); surface->output_destroy.notify = handle_output_destroy; wl_signal_add(&layer_surface->output->events.destroy, &surface->output_destroy); surface->node_destroy.notify = handle_node_destroy; wl_signal_add(&surface->scene_layer_surface->tree->node.events.destroy, &surface->node_destroy); /* * Temporarily set the layer's current state to pending so that * it can easily be arranged. */ struct wlr_layer_surface_v1_state old_state = layer_surface->current; layer_surface->current = layer_surface->pending; output_update_usable_area(output); layer_surface->current = old_state; } void layers_init(struct server *server) { server->layer_shell = wlr_layer_shell_v1_create(server->wl_display, LAB_LAYERSHELL_VERSION); server->new_layer_surface.notify = handle_new_layer_surface; wl_signal_add(&server->layer_shell->events.new_surface, &server->new_layer_surface); } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/main.c������������������������������������������������������������������������������0000664�0000000�0000000�00000010230�14570443012�0014462�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <signal.h> #include <string.h> #include <unistd.h> #include "common/dir.h" #include "common/fd_util.h" #include "common/font.h" #include "common/mem.h" #include "common/spawn.h" #include "config/session.h" #include "labwc.h" #include "theme.h" #include "menu/menu.h" struct rcxml rc = { 0 }; static const struct option long_options[] = { {"config", required_argument, NULL, 'c'}, {"config-dir", required_argument, NULL, 'C'}, {"debug", no_argument, NULL, 'd'}, {"exit", no_argument, NULL, 'e'}, {"help", no_argument, NULL, 'h'}, {"merge-config", no_argument, NULL, 'm'}, {"reconfigure", no_argument, NULL, 'r'}, {"startup", required_argument, NULL, 's'}, {"version", no_argument, NULL, 'v'}, {"verbose", no_argument, NULL, 'V'}, {0, 0, 0, 0} }; static const char labwc_usage[] = "Usage: labwc [options...]\n" " -c, --config <file> Specify config file (with path)\n" " -C, --config-dir <dir> Specify config directory\n" " -d, --debug Enable full logging, including debug information\n" " -e, --exit Exit the compositor\n" " -h, --help Show help message and quit\n" " -m, --merge-config Merge user config files/theme in all XDG Base Dirs\n" " -r, --reconfigure Reload the compositor configuration\n" " -s, --startup <command> Run command on startup\n" " -v, --version Show version number and quit\n" " -V, --verbose Enable more verbose logging\n"; static void usage(void) { printf("%s", labwc_usage); exit(0); } static void die_on_detecting_suid(void) { if (geteuid() != 0 && getegid() != 0) { return; } if (getuid() == geteuid() && getgid() == getegid()) { return; } wlr_log(WLR_ERROR, "SUID detected - aborting"); exit(EXIT_FAILURE); } static void send_signal_to_labwc_pid(int signal) { char *labwc_pid = getenv("LABWC_PID"); if (!labwc_pid) { wlr_log(WLR_ERROR, "LABWC_PID not set"); exit(EXIT_FAILURE); } int pid = atoi(labwc_pid); if (!pid) { wlr_log(WLR_ERROR, "should not send signal to pid 0"); exit(EXIT_FAILURE); } kill(pid, signal); } int main(int argc, char *argv[]) { #if HAVE_NLS setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); textdomain(GETTEXT_PACKAGE); #endif char *startup_cmd = NULL; enum wlr_log_importance verbosity = WLR_ERROR; int c; while (1) { int index = 0; c = getopt_long(argc, argv, "c:C:dehmrs:vV", long_options, &index); if (c == -1) { break; } switch (c) { case 'c': rc.config_file = optarg; break; case 'C': rc.config_dir = optarg; break; case 'd': verbosity = WLR_DEBUG; break; case 'e': send_signal_to_labwc_pid(SIGTERM); exit(0); case 'm': rc.merge_config = true; break; case 'r': send_signal_to_labwc_pid(SIGHUP); exit(0); case 's': startup_cmd = optarg; break; case 'v': printf("labwc " LABWC_VERSION "\n"); exit(0); case 'V': verbosity = WLR_INFO; break; case 'h': default: usage(); } } if (optind < argc) { usage(); } wlr_log_init(verbosity, NULL); die_on_detecting_suid(); session_environment_init(); rcxml_read(rc.config_file); /* * Set environment variable LABWC_PID to the pid of the compositor * so that SIGHUP and SIGTERM can be sent to specific instances using * `kill -s <signal> <pid>` rather than `killall -s <signal> labwc` */ char pid[32]; snprintf(pid, sizeof(pid), "%d", getpid()); if (setenv("LABWC_PID", pid, true) < 0) { wlr_log_errno(WLR_ERROR, "unable to set LABWC_PID"); } else { wlr_log(WLR_DEBUG, "LABWC_PID=%s", pid); } if (!getenv("XDG_RUNTIME_DIR")) { wlr_log(WLR_ERROR, "XDG_RUNTIME_DIR is unset"); exit(EXIT_FAILURE); } increase_nofile_limit(); struct server server = { 0 }; server_init(&server); server_start(&server); struct theme theme = { 0 }; theme_init(&theme, rc.theme_name); rc.theme = &theme; server.theme = &theme; menu_init(&server); session_autostart_init(); if (startup_cmd) { spawn_async_no_shell(startup_cmd); } wl_display_run(server.wl_display); server_finish(&server); menu_finish(&server); theme_finish(&theme); rcxml_finish(); font_finish(); return 0; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/menu/�������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14570443012�0014342�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/menu/menu.c�������������������������������������������������������������������������0000664�0000000�0000000�00000067462�14570443012�0015471�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <libxml/parser.h> #include <libxml/tree.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <wayland-server-core.h> #include <wlr/util/log.h> #include "action.h" #include "common/buf.h" #include "common/dir.h" #include "common/font.h" #include "common/list.h" #include "common/match.h" #include "common/mem.h" #include "common/nodename.h" #include "common/scaled_font_buffer.h" #include "common/scene-helpers.h" #include "common/string-helpers.h" #include "labwc.h" #include "menu/menu.h" #include "node.h" #include "theme.h" /* state-machine variables for processing <item></item> */ static bool in_item; static struct menuitem *current_item; static struct action *current_item_action; static int menu_level; static struct menu *current_menu; /* TODO: split this whole file into parser.c and actions.c*/ static struct menu * menu_create(struct server *server, const char *id, const char *label) { struct menu *menu = znew(*menu); wl_list_append(&server->menus, &menu->link); wl_list_init(&menu->menuitems); menu->id = xstrdup(id); menu->label = xstrdup(label ? label : id); menu->parent = current_menu; menu->server = server; menu->size.width = server->theme->menu_min_width; /* menu->size.height will be kept up to date by adding items */ menu->scene_tree = wlr_scene_tree_create(server->menu_tree); wlr_scene_node_set_enabled(&menu->scene_tree->node, false); return menu; } struct menu * menu_get_by_id(struct server *server, const char *id) { if (!id) { return NULL; } struct menu *menu; wl_list_for_each(menu, &server->menus, link) { if (!strcmp(menu->id, id)) { return menu; } } return NULL; } static void menu_update_width(struct menu *menu) { struct menuitem *item; struct theme *theme = menu->server->theme; int max_width = theme->menu_min_width; /* Get widest menu item, clamped by menu_max_width */ wl_list_for_each(item, &menu->menuitems, link) { if (item->native_width > max_width) { max_width = item->native_width < theme->menu_max_width ? item->native_width : theme->menu_max_width; } } menu->size.width = max_width + 2 * theme->menu_item_padding_x; /* Update all items for the new size */ wl_list_for_each(item, &menu->menuitems, link) { wlr_scene_rect_set_size( wlr_scene_rect_from_node(item->normal.background), menu->size.width, item->height); if (!item->selected.background) { /* This is a separator. They don't have a selected background. */ wlr_scene_rect_set_size( wlr_scene_rect_from_node(item->normal.text), menu->size.width - 2 * theme->menu_separator_padding_width, theme->menu_separator_line_thickness); } else { /* Usual menu item */ wlr_scene_rect_set_size( wlr_scene_rect_from_node(item->selected.background), menu->size.width, item->height); if (item->native_width > max_width || item->submenu) { scaled_font_buffer_set_max_width(item->normal.buffer, max_width); scaled_font_buffer_set_max_width(item->selected.buffer, max_width); } } } } static void post_processing(struct server *server) { struct menu *menu; wl_list_for_each(menu, &server->menus, link) { menu_update_width(menu); } } static void validate_menu(struct menu *menu) { struct menuitem *item; struct action *action, *action_tmp; wl_list_for_each(item, &menu->menuitems, link) { wl_list_for_each_safe(action, action_tmp, &item->actions, link) { if (!action_is_valid(action)) { wl_list_remove(&action->link); action_free(action); wlr_log(WLR_ERROR, "Removed invalid menu action"); } } } } static void validate(struct server *server) { struct menu *menu; wl_list_for_each(menu, &server->menus, link) { validate_menu(menu); } } static struct menuitem * item_create(struct menu *menu, const char *text, bool show_arrow) { struct menuitem *menuitem = znew(*menuitem); menuitem->parent = menu; menuitem->selectable = true; struct server *server = menu->server; struct theme *theme = server->theme; const char *arrow = show_arrow ? "›" : NULL; if (!menu->item_height) { menu->item_height = font_height(&rc.font_menuitem) + 2 * theme->menu_item_padding_y; } menuitem->height = menu->item_height; int x, y; menuitem->native_width = font_width(&rc.font_menuitem, text); if (arrow) { menuitem->native_width += font_width(&rc.font_menuitem, arrow); } /* Menu item root node */ menuitem->tree = wlr_scene_tree_create(menu->scene_tree); node_descriptor_create(&menuitem->tree->node, LAB_NODE_DESC_MENUITEM, menuitem); /* Tree for each state to hold background and text buffer */ menuitem->normal.tree = wlr_scene_tree_create(menuitem->tree); menuitem->selected.tree = wlr_scene_tree_create(menuitem->tree); /* Item background nodes */ menuitem->normal.background = &wlr_scene_rect_create( menuitem->normal.tree, menu->size.width, menu->item_height, theme->menu_items_bg_color)->node; menuitem->selected.background = &wlr_scene_rect_create( menuitem->selected.tree, menu->size.width, menu->item_height, theme->menu_items_active_bg_color)->node; /* Font nodes */ menuitem->normal.buffer = scaled_font_buffer_create(menuitem->normal.tree); menuitem->selected.buffer = scaled_font_buffer_create(menuitem->selected.tree); if (!menuitem->normal.buffer || !menuitem->selected.buffer) { wlr_log(WLR_ERROR, "Failed to create menu item '%s'", text); /* * Destroying the root node will destroy everything, * including the node descriptor and scaled_font_buffers. */ wlr_scene_node_destroy(&menuitem->tree->node); free(menuitem); return NULL; } menuitem->normal.text = &menuitem->normal.buffer->scene_buffer->node; menuitem->selected.text = &menuitem->selected.buffer->scene_buffer->node; /* Font buffers */ scaled_font_buffer_update(menuitem->normal.buffer, text, menuitem->native_width, &rc.font_menuitem, theme->menu_items_text_color, arrow); scaled_font_buffer_update(menuitem->selected.buffer, text, menuitem->native_width, &rc.font_menuitem, theme->menu_items_active_text_color, arrow); /* Center font nodes */ x = theme->menu_item_padding_x; y = (menu->item_height - menuitem->normal.buffer->height) / 2; wlr_scene_node_set_position(menuitem->normal.text, x, y); y = (menu->item_height - menuitem->selected.buffer->height) / 2; wlr_scene_node_set_position(menuitem->selected.text, x, y); /* Position the item in relation to its menu */ wlr_scene_node_set_position(&menuitem->tree->node, 0, menu->size.height); /* Hide selected state */ wlr_scene_node_set_enabled(&menuitem->selected.tree->node, false); /* Update menu extents */ menu->size.height += menuitem->height; wl_list_append(&menu->menuitems, &menuitem->link); wl_list_init(&menuitem->actions); return menuitem; } static struct menuitem * separator_create(struct menu *menu, const char *label) { struct menuitem *menuitem = znew(*menuitem); menuitem->parent = menu; menuitem->selectable = false; struct server *server = menu->server; struct theme *theme = server->theme; menuitem->height = theme->menu_separator_line_thickness + 2 * theme->menu_separator_padding_height; menuitem->tree = wlr_scene_tree_create(menu->scene_tree); node_descriptor_create(&menuitem->tree->node, LAB_NODE_DESC_MENUITEM, menuitem); menuitem->normal.tree = wlr_scene_tree_create(menuitem->tree); menuitem->normal.background = &wlr_scene_rect_create( menuitem->normal.tree, menu->size.width, menuitem->height, theme->menu_items_bg_color)->node; int width = menu->size.width - 2 * theme->menu_separator_padding_width; menuitem->normal.text = &wlr_scene_rect_create( menuitem->normal.tree, width > 0 ? width : 0, theme->menu_separator_line_thickness, theme->menu_separator_color)->node; wlr_scene_node_set_position(&menuitem->tree->node, 0, menu->size.height); /* Vertically center-align separator line */ wlr_scene_node_set_position(menuitem->normal.text, theme->menu_separator_padding_width, theme->menu_separator_padding_height); menu->size.height += menuitem->height; wl_list_append(&menu->menuitems, &menuitem->link); wl_list_init(&menuitem->actions); return menuitem; } /* * Handle the following: * <item label=""> * <action name=""> * <command></command> * </action> * </item> */ static void fill_item(char *nodename, char *content) { string_truncate_at_pattern(nodename, ".item.menu"); /* <item label=""> defines the start of a new item */ if (!strcmp(nodename, "label")) { current_item = item_create(current_menu, content, false); current_item_action = NULL; } else if (!current_item) { wlr_log(WLR_ERROR, "expect <item label=\"\"> element first. " "nodename: '%s' content: '%s'", nodename, content); } else if (!strcmp(nodename, "icon")) { /* * Do nothing as we don't support menu icons - just avoid * logging errors if a menu.xml file contains icon="" entries. */ } else if (!strcmp(nodename, "name.action")) { current_item_action = action_create(content); if (current_item_action) { wl_list_append(¤t_item->actions, ¤t_item_action->link); } } else if (!current_item_action) { wlr_log(WLR_ERROR, "expect <action name=\"\"> element first. " "nodename: '%s' content: '%s'", nodename, content); } else { action_arg_from_xml_node(current_item_action, nodename, content); } } static void item_destroy(struct menuitem *item) { wl_list_remove(&item->link); action_list_free(&item->actions); wlr_scene_node_destroy(&item->tree->node); free(item); } /* * We support XML CDATA for <command> in menu.xml in order to provide backward * compatibility with obmenu-generator. For example: * * <menu id="" label=""> * <item label=""> * <action name="Execute"> * <command><![CDATA[xdg-open .]]></command> * </action> * </item> * </menu> * * <execute> is an old, deprecated openbox variety of <command>. We support it * for backward compatibility with old openbox-menu generators. It has the same * function and <command> * * The match_glob() wildcard allows for nested menus giving nodenames with * ...menu.menu... or ...menu.menu.menu... and so on. */ static bool nodename_supports_cdata(char *nodename) { return match_glob("command.action.item.*menu.openbox_menu", nodename) || match_glob("execute.action.item.*menu.openbox_menu", nodename); } static void entry(xmlNode *node, char *nodename, char *content) { if (!nodename) { return; } xmlChar *cdata = NULL; if (!content && nodename_supports_cdata(nodename)) { cdata = xmlNodeGetContent(node); } if (!content && !cdata) { return; } string_truncate_at_pattern(nodename, ".openbox_menu"); if (getenv("LABWC_DEBUG_MENU_NODENAMES")) { printf("%s: %s\n", nodename, content ? content : (char *)cdata); } if (in_item) { fill_item(nodename, content ? content : (char *)cdata); } xmlFree(cdata); } static void process_node(xmlNode *node) { static char buffer[256]; char *content = (char *)node->content; if (xmlIsBlankNode(node)) { return; } char *name = nodename(node, buffer, sizeof(buffer)); entry(node, name, content); } static void xml_tree_walk(xmlNode *node, struct server *server); static void traverse(xmlNode *n, struct server *server) { xmlAttr *attr; process_node(n); for (attr = n->properties; attr; attr = attr->next) { xml_tree_walk(attr->children, server); } xml_tree_walk(n->children, server); } static int nr_parents(xmlNode *n) { assert(n); int i = 0; for (xmlNode *node = n->parent; node && i < INT_MAX; ++i) { node = node->parent; } return i; } /* * <menu> elements have three different roles: * * Definition of (sub)menu - has ID, LABEL and CONTENT * * Menuitem of pipemenu type - has EXECUTE and LABEL * * Menuitem of submenu type - has ID only */ static void handle_menu_element(xmlNode *n, struct server *server) { char *label = (char *)xmlGetProp(n, (const xmlChar *)"label"); char *execute = (char *)xmlGetProp(n, (const xmlChar *)"execute"); char *id = (char *)xmlGetProp(n, (const xmlChar *)"id"); if (execute) { wlr_log(WLR_ERROR, "we do not support pipemenus"); } else if ((label && id) || (id && nr_parents(n) == 2)) { /* * (label && id) refers to <menu id="" label=""> which is an * inline menu definition. * * (id && nr_parents(n) == 2) refers to: * <openbox_menu> * <menu id=""> * </menu> * </openbox> * * which is the highest level a menu can be defined at. * * Openbox spec requires a label="" defined here, but it is * actually pointless so we handle it with or without the label * attritute to make it easier for users to define "root-menu" * and "client-menu". */ struct menu **submenu = NULL; if (menu_level > 0) { /* * In a nested (inline) menu definition we need to * create an item pointing to the new submenu */ current_item = item_create(current_menu, label, true); if (current_item) { submenu = ¤t_item->submenu; } else { submenu = NULL; } } ++menu_level; current_menu = menu_create(server, id, label); if (submenu) { *submenu = current_menu; } traverse(n, server); current_menu = current_menu->parent; --menu_level; } else if (id) { /* * <menu id=""> creates an entry which points to a menu * defined elsewhere */ struct menu *menu = menu_get_by_id(server, id); if (menu) { current_item = item_create(current_menu, menu->label, true); if (current_item) { current_item->submenu = menu; } } else { wlr_log(WLR_ERROR, "no menu with id '%s'", id); } } free(label); free(execute); free(id); } /* This can be one of <separator> and <separator label=""> */ static void handle_separator_element(xmlNode *n) { char *label = (char *)xmlGetProp(n, (const xmlChar *)"label"); current_item = separator_create(current_menu, label); free(label); } static void xml_tree_walk(xmlNode *node, struct server *server) { for (xmlNode *n = node; n && n->name; n = n->next) { if (!strcasecmp((char *)n->name, "comment")) { continue; } if (!strcasecmp((char *)n->name, "menu")) { handle_menu_element(n, server); continue; } if (!strcasecmp((char *)n->name, "separator")) { handle_separator_element(n); continue; } if (!strcasecmp((char *)n->name, "item")) { in_item = true; traverse(n, server); in_item = false; continue; } traverse(n, server); } } /* * @stream can come from either of the following: * - fopen() in the case of reading a file such as menu.xml * - popen() when processing pipemenus */ static void parse(struct server *server, FILE *stream) { char *line = NULL; size_t len = 0; struct buf b; buf_init(&b); while (getline(&line, &len, stream) != -1) { char *p = strrchr(line, '\n'); if (p) { *p = '\0'; } buf_add(&b, line); } free(line); xmlDoc *d = xmlParseMemory(b.buf, b.len); if (!d) { wlr_log(WLR_ERROR, "xmlParseMemory()"); goto err; } xml_tree_walk(xmlDocGetRootElement(d), server); xmlFreeDoc(d); xmlCleanupParser(); err: free(b.buf); } static void parse_xml(const char *filename, struct server *server) { struct wl_list paths; paths_config_create(&paths, filename); bool should_merge_config = rc.merge_config; struct wl_list *(*iter)(struct wl_list *list); iter = should_merge_config ? paths_get_prev : paths_get_next; for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) { struct path *path = wl_container_of(elm, path, link); FILE *stream = fopen(path->string, "r"); if (!stream) { return; } wlr_log(WLR_INFO, "read menu file %s", path->string); parse(server, stream); fclose(stream); if (!should_merge_config) { break; } } paths_destroy(&paths); } static int menu_get_full_width(struct menu *menu) { int width = menu->size.width - menu->server->theme->menu_overlap_x; int child_width; int max_child_width = 0; struct menuitem *item; wl_list_for_each(item, &menu->menuitems, link) { if (!item->submenu) { continue; } child_width = menu_get_full_width(item->submenu); if (child_width > max_child_width) { max_child_width = child_width; } } return width + max_child_width; } static void menu_configure(struct menu *menu, int lx, int ly, enum menu_align align) { struct theme *theme = menu->server->theme; /* Get output local coordinates + output usable area */ double ox = lx; double oy = ly; struct wlr_output *wlr_output = wlr_output_layout_output_at( menu->server->output_layout, lx, ly); struct output *output = wlr_output ? output_from_wlr_output( menu->server, wlr_output) : NULL; if (!output) { wlr_log(WLR_ERROR, "Failed to position menu %s (%s) and its submenus: " "Not enough screen space", menu->id, menu->label); return; } wlr_output_layout_output_coords(menu->server->output_layout, wlr_output, &ox, &oy); if (align == LAB_MENU_OPEN_AUTO) { int full_width = menu_get_full_width(menu); if (ox + full_width > output->usable_area.width) { align = LAB_MENU_OPEN_LEFT; } else { align = LAB_MENU_OPEN_RIGHT; } } if (oy + menu->size.height > output->usable_area.height) { align &= ~LAB_MENU_OPEN_BOTTOM; align |= LAB_MENU_OPEN_TOP; } else { align &= ~LAB_MENU_OPEN_TOP; align |= LAB_MENU_OPEN_BOTTOM; } if (align & LAB_MENU_OPEN_LEFT) { lx -= menu->size.width - theme->menu_overlap_x; } if (align & LAB_MENU_OPEN_TOP) { ly -= menu->size.height; if (menu->parent) { /* For submenus adjust y to bottom left corner */ ly += menu->item_height; } } wlr_scene_node_set_position(&menu->scene_tree->node, lx, ly); int rel_y; int new_lx, new_ly; struct menuitem *item; wl_list_for_each(item, &menu->menuitems, link) { if (!item->submenu) { continue; } if (align & LAB_MENU_OPEN_RIGHT) { new_lx = lx + menu->size.width - theme->menu_overlap_x; } else { new_lx = lx; } rel_y = item->tree->node.y; new_ly = ly + rel_y - theme->menu_overlap_y; menu_configure(item->submenu, new_lx, new_ly, align); } } static void menu_hide_submenu(struct server *server, const char *id) { struct menu *menu, *hide_menu; hide_menu = menu_get_by_id(server, id); if (!hide_menu) { return; } wl_list_for_each(menu, &server->menus, link) { bool should_reposition = false; struct menuitem *item, *next; wl_list_for_each_safe(item, next, &menu->menuitems, link) { if (item->submenu == hide_menu) { item_destroy(item); should_reposition = true; } } if (!should_reposition) { continue; } /* Re-position items vertically */ menu->size.height = 0; wl_list_for_each(item, &menu->menuitems, link) { wlr_scene_node_set_position(&item->tree->node, 0, menu->size.height); menu->size.height += item->height; } } } static void init_rootmenu(struct server *server) { struct menu *menu = menu_get_by_id(server, "root-menu"); /* Default menu if no menu.xml found */ if (!menu) { current_menu = NULL; menu = menu_create(server, "root-menu", ""); } if (wl_list_empty(&menu->menuitems)) { current_item = item_create(menu, _("Reconfigure"), false); fill_item("name.action", "Reconfigure"); current_item = item_create(menu, _("Exit"), false); fill_item("name.action", "Exit"); } } static void init_windowmenu(struct server *server) { struct menu *menu = menu_get_by_id(server, "client-menu"); /* Default menu if no menu.xml found */ if (!menu) { current_menu = NULL; menu = menu_create(server, "client-menu", ""); } if (wl_list_empty(&menu->menuitems)) { current_item = item_create(menu, _("Minimize"), false); fill_item("name.action", "Iconify"); current_item = item_create(menu, _("Maximize"), false); fill_item("name.action", "ToggleMaximize"); current_item = item_create(menu, _("Fullscreen"), false); fill_item("name.action", "ToggleFullscreen"); current_item = item_create(menu, _("Roll up/down"), false); fill_item("name.action", "ToggleShade"); current_item = item_create(menu, _("Decorations"), false); fill_item("name.action", "ToggleDecorations"); current_item = item_create(menu, _("Always on Top"), false); fill_item("name.action", "ToggleAlwaysOnTop"); /* Workspace sub-menu */ struct menu *workspace_menu = menu_create(server, "workspaces", ""); current_item = item_create(workspace_menu, _("Move left"), false); /* * <action name="SendToDesktop"><follow> is true by default so * GoToDesktop will be called as part of the action. */ fill_item("name.action", "SendToDesktop"); fill_item("to.action", "left"); current_item = item_create(workspace_menu, _("Move right"), false); fill_item("name.action", "SendToDesktop"); fill_item("to.action", "right"); current_item = separator_create(workspace_menu, ""); current_item = item_create(workspace_menu, _("Always on Visible Workspace"), false); fill_item("name.action", "ToggleOmnipresent"); current_item = item_create(menu, _("Workspace"), true); current_item->submenu = workspace_menu; current_item = item_create(menu, _("Close"), false); fill_item("name.action", "Close"); } if (wl_list_length(&rc.workspace_config.workspaces) == 1) { menu_hide_submenu(server, "workspaces"); } } void menu_init(struct server *server) { wl_list_init(&server->menus); parse_xml("menu.xml", server); init_rootmenu(server); init_windowmenu(server); post_processing(server); validate(server); } void menu_finish(struct server *server) { struct menu *menu, *tmp_menu; wl_list_for_each_safe(menu, tmp_menu, &server->menus, link) { struct menuitem *item, *next; wl_list_for_each_safe(item, next, &menu->menuitems, link) { item_destroy(item); } /** * Destroying the root node will destroy everything, * including node descriptors and scaled_font_buffers. */ wlr_scene_node_destroy(&menu->scene_tree->node); wl_list_remove(&menu->link); zfree(menu); } } /* Sets selection (or clears selection if passing NULL) */ static void menu_set_selection(struct menu *menu, struct menuitem *item) { /* Clear old selection */ if (menu->selection.item) { wlr_scene_node_set_enabled( &menu->selection.item->normal.tree->node, true); wlr_scene_node_set_enabled( &menu->selection.item->selected.tree->node, false); } /* Set new selection */ if (item) { wlr_scene_node_set_enabled(&item->normal.tree->node, false); wlr_scene_node_set_enabled(&item->selected.tree->node, true); } menu->selection.item = item; } static void close_all_submenus(struct menu *menu) { struct menuitem *item; wl_list_for_each(item, &menu->menuitems, link) { if (item->submenu) { wlr_scene_node_set_enabled( &item->submenu->scene_tree->node, false); close_all_submenus(item->submenu); } } menu->selection.menu = NULL; } static void menu_close(struct menu *menu) { if (!menu) { wlr_log(WLR_ERROR, "Trying to close non exiting menu"); return; } wlr_scene_node_set_enabled(&menu->scene_tree->node, false); menu_set_selection(menu, NULL); if (menu->selection.menu) { menu_close(menu->selection.menu); menu->selection.menu = NULL; } } void menu_open(struct menu *menu, int x, int y) { assert(menu); if (menu->server->menu_current) { menu_close(menu->server->menu_current); } close_all_submenus(menu); menu_set_selection(menu, NULL); menu_configure(menu, x, y, LAB_MENU_OPEN_AUTO); wlr_scene_node_set_enabled(&menu->scene_tree->node, true); menu->server->menu_current = menu; menu->server->input_mode = LAB_INPUT_STATE_MENU; } static void menu_process_item_selection(struct menuitem *item) { assert(item); if (!item->selectable) { return; } /* We are on an item that has new mouse-focus */ menu_set_selection(item->parent, item); if (item->parent->selection.menu) { /* Close old submenu tree */ menu_close(item->parent->selection.menu); } if (item->submenu) { /* Sync the triggering view */ item->submenu->triggered_by_view = item->parent->triggered_by_view; /* Ensure the submenu has its parent set correctly */ item->submenu->parent = item->parent; /* And open the new submenu tree */ wlr_scene_node_set_enabled( &item->submenu->scene_tree->node, true); } item->parent->selection.menu = item->submenu; } /* Get the deepest submenu with active item selection or the root menu itself */ static struct menu * get_selection_leaf(struct server *server) { struct menu *menu = server->menu_current; if (!menu) { return NULL; } while (menu->selection.menu) { if (!menu->selection.menu->selection.item) { return menu; } menu = menu->selection.menu; } return menu; } /* Selects the next or previous sibling of the currently selected item */ static void menu_item_select(struct server *server, bool forward) { struct menu *menu = get_selection_leaf(server); if (!menu) { return; } struct menuitem *item = NULL; struct menuitem *selection = menu->selection.item; struct wl_list *start = selection ? &selection->link : &menu->menuitems; struct wl_list *current = start; while (!item || !item->selectable) { current = forward ? current->next : current->prev; if (current == start) { return; } if (current == &menu->menuitems) { /* Allow wrap around */ item = NULL; continue; } item = wl_container_of(current, item, link); } menu_process_item_selection(item); } static bool menu_execute_item(struct menuitem *item) { assert(item); if (item->submenu || !item->selectable) { /* We received a click on a separator or item that just opens a submenu */ return false; } /* * We close the menu here to provide a faster feedback to the user. * We do that without resetting the input state so src/cursor.c * can do its own clean up on the following RELEASE event. */ menu_close(item->parent->server->menu_current); item->parent->server->menu_current = NULL; struct server *server = item->parent->server; menu_close_root(server); cursor_update_focus(server); /* * We call the actions after menu_close_root() so that virtual keyboard * input is sent to the focused_surface instead of being absorbed by the * menu. Consider for example: `wlrctl keyboard type abc` */ actions_run(item->parent->triggered_by_view, server, &item->actions, 0); return true; } /* Keyboard based selection */ void menu_item_select_next(struct server *server) { menu_item_select(server, /* forward */ true); } void menu_item_select_previous(struct server *server) { menu_item_select(server, /* forward */ false); } bool menu_call_selected_actions(struct server *server) { struct menu *menu = get_selection_leaf(server); if (!menu || !menu->selection.item) { return false; } return menu_execute_item(menu->selection.item); } /* Selects the first item on the submenu attached to the current selection */ void menu_submenu_enter(struct server *server) { struct menu *menu = get_selection_leaf(server); if (!menu || !menu->selection.menu) { return; } struct wl_list *start = &menu->selection.menu->menuitems; struct wl_list *current = start; struct menuitem *item = NULL; while (!item || !item->selectable) { current = current->next; if (current == start) { return; } item = wl_container_of(current, item, link); } menu_process_item_selection(item); } /* Re-selects the selected item on the parent menu of the current selection */ void menu_submenu_leave(struct server *server) { struct menu *menu = get_selection_leaf(server); if (!menu || !menu->parent || !menu->parent->selection.item) { return; } menu_process_item_selection(menu->parent->selection.item); } /* Mouse based selection */ void menu_process_cursor_motion(struct wlr_scene_node *node) { assert(node && node->data); struct menuitem *item = node_menuitem_from_node(node); if (item->selectable && node == &item->selected.tree->node) { /* We are on an already selected item */ return; } menu_process_item_selection(item); } bool menu_call_actions(struct wlr_scene_node *node) { assert(node && node->data); struct menuitem *item = node_menuitem_from_node(node); return menu_execute_item(item); } void menu_close_root(struct server *server) { assert(server->input_mode == LAB_INPUT_STATE_MENU); if (server->menu_current) { menu_close(server->menu_current); server->menu_current = NULL; } server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; } void menu_reconfigure(struct server *server) { menu_finish(server); server->menu_current = NULL; menu_init(server); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/menu/meson.build��������������������������������������������������������������������0000664�0000000�0000000�00000000046�14570443012�0016504�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc_sources += files( 'menu.c', ) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/meson.build�������������������������������������������������������������������������0000664�0000000�0000000�00000001240�14570443012�0015535�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc_sources = files( 'action.c', 'buffer.c', 'debug.c', 'desktop.c', 'dnd.c', 'edges.c', 'foreign.c', 'idle.c', 'interactive.c', 'layers.c', 'main.c', 'node.c', 'osd.c', 'output.c', 'placement.c', 'regions.c', 'resistance.c', 'seat.c', 'server.c', 'session-lock.c', 'snap.c', 'tearing.c', 'theme.c', 'view.c', 'view-impl-common.c', 'window-rules.c', 'workspaces.c', 'xdg.c', 'xdg-popup.c', ) if have_xwayland labwc_sources += files( 'xwayland.c', 'xwayland-unmanaged.c', ) endif subdir('button') subdir('common') subdir('config') subdir('decorations') subdir('input') subdir('menu') subdir('ssd') ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/node.c������������������������������������������������������������������������������0000664�0000000�0000000�00000004644�14570443012�0014477�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <stdlib.h> #include "common/mem.h" #include "node.h" static void descriptor_destroy(struct node_descriptor *node_descriptor) { if (!node_descriptor) { return; } wl_list_remove(&node_descriptor->destroy.link); free(node_descriptor); } static void destroy_notify(struct wl_listener *listener, void *data) { struct node_descriptor *node_descriptor = wl_container_of(listener, node_descriptor, destroy); descriptor_destroy(node_descriptor); } void node_descriptor_create(struct wlr_scene_node *scene_node, enum node_descriptor_type type, void *data) { struct node_descriptor *node_descriptor = znew(*node_descriptor); node_descriptor->type = type; node_descriptor->data = data; node_descriptor->destroy.notify = destroy_notify; wl_signal_add(&scene_node->events.destroy, &node_descriptor->destroy); scene_node->data = node_descriptor; } struct view * node_view_from_node(struct wlr_scene_node *wlr_scene_node) { assert(wlr_scene_node->data); struct node_descriptor *node_descriptor = wlr_scene_node->data; assert(node_descriptor->type == LAB_NODE_DESC_VIEW || node_descriptor->type == LAB_NODE_DESC_XDG_POPUP); return (struct view *)node_descriptor->data; } struct lab_layer_surface * node_layer_surface_from_node(struct wlr_scene_node *wlr_scene_node) { assert(wlr_scene_node->data); struct node_descriptor *node_descriptor = wlr_scene_node->data; assert(node_descriptor->type == LAB_NODE_DESC_LAYER_SURFACE); return (struct lab_layer_surface *)node_descriptor->data; } struct lab_layer_popup * node_layer_popup_from_node(struct wlr_scene_node *wlr_scene_node) { assert(wlr_scene_node->data); struct node_descriptor *node_descriptor = wlr_scene_node->data; assert(node_descriptor->type == LAB_NODE_DESC_LAYER_POPUP); return (struct lab_layer_popup *)node_descriptor->data; } struct menuitem * node_menuitem_from_node(struct wlr_scene_node *wlr_scene_node) { assert(wlr_scene_node->data); struct node_descriptor *node_descriptor = wlr_scene_node->data; assert(node_descriptor->type == LAB_NODE_DESC_MENUITEM); return (struct menuitem *)node_descriptor->data; } struct ssd_button * node_ssd_button_from_node(struct wlr_scene_node *wlr_scene_node) { assert(wlr_scene_node->data); struct node_descriptor *node_descriptor = wlr_scene_node->data; assert(node_descriptor->type == LAB_NODE_DESC_SSD_BUTTON); return (struct ssd_button *)node_descriptor->data; } ��������������������������������������������������������������������������������������������labwc-0.7.1/src/osd.c�������������������������������������������������������������������������������0000664�0000000�0000000�00000033426�14570443012�0014337�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include "config.h" #include <assert.h> #include <cairo.h> #include <drm_fourcc.h> #include <pango/pangocairo.h> #include <wlr/util/log.h> #include <wlr/util/box.h> #include "buffer.h" #include "common/array.h" #include "common/buf.h" #include "common/font.h" #include "common/graphic-helpers.h" #include "common/scene-helpers.h" #include "config/rcxml.h" #include "labwc.h" #include "theme.h" #include "node.h" #include "view.h" #include "window-rules.h" #include "workspaces.h" static const char * get_formatted_app_id(struct view *view) { char *s = (char *)view_get_string_prop(view, "app_id"); if (!s) { return NULL; } return s; } static const char * get_trimmed_app_id(char *s) { if (!s) { return NULL; } /* remove the first two nodes of 'org.' strings */ if (!strncmp(s, "org.", 4)) { char *p = s + 4; p = strchr(p, '.'); if (p) { return ++p; } } return s; } static void destroy_osd_nodes(struct output *output) { struct wlr_scene_node *child, *next; struct wl_list *children = &output->osd_tree->children; wl_list_for_each_safe(child, next, children, link) { wlr_scene_node_destroy(child); } } static void osd_update_preview_outlines(struct view *view) { /* Create / Update preview outline tree */ struct server *server = view->server; struct multi_rect *rect = view->server->osd_state.preview_outline; if (!rect) { int line_width = server->theme->osd_border_width; float *colors[] = { server->theme->osd_bg_color, server->theme->osd_label_text_color, server->theme->osd_bg_color }; rect = multi_rect_create(&server->scene->tree, colors, line_width); wlr_scene_node_place_above(&rect->tree->node, &server->menu_tree->node); server->osd_state.preview_outline = rect; } struct wlr_box geo = ssd_max_extents(view); multi_rect_set_size(rect, geo.width, geo.height); wlr_scene_node_set_position(&rect->tree->node, geo.x, geo.y); } void osd_on_view_destroy(struct view *view) { assert(view); struct osd_state *osd_state = &view->server->osd_state; if (!osd_state->cycle_view) { /* OSD not active, no need for clean up */ return; } if (osd_state->cycle_view == view) { /* * If we are the current OSD selected view, cycle * to the next because we are dying. */ /* Also resets preview node */ osd_state->cycle_view = desktop_cycle_view(view->server, osd_state->cycle_view, LAB_CYCLE_DIR_BACKWARD); /* * If we cycled back to ourselves, then we have no more windows. * Just close the OSD for good. */ if (osd_state->cycle_view == view || !osd_state->cycle_view) { /* osd_finish() additionally resets cycle_view to NULL */ osd_finish(view->server); } } if (osd_state->cycle_view) { /* Update the OSD to reflect the view has now gone. */ osd_update(view->server); } if (view->scene_tree) { struct wlr_scene_node *node = &view->scene_tree->node; if (osd_state->preview_anchor == node) { /* * If we are the anchor for the current OSD selected view, * replace the anchor with the node before us. */ osd_state->preview_anchor = lab_wlr_scene_get_prev_node(node); } } } void osd_finish(struct server *server) { server->osd_state.preview_node = NULL; server->osd_state.preview_anchor = NULL; struct output *output; wl_list_for_each(output, &server->outputs, link) { destroy_osd_nodes(output); wlr_scene_node_set_enabled(&output->osd_tree->node, false); } if (server->osd_state.preview_outline) { /* Destroy the whole multi_rect so we can easily react to new themes */ wlr_scene_node_destroy(&server->osd_state.preview_outline->tree->node); server->osd_state.preview_outline = NULL; } /* Hiding OSD may need a cursor change */ cursor_update_focus(server); /* * We delay resetting cycle_view until after cursor_update_focus() * has been called to allow A-Tab keyboard focus switching even if * followMouse has been configured and the cursor is on a different * surface. Otherwise cursor_update_focus() would automatically * refocus the surface the cursor is currently on. */ server->osd_state.cycle_view = NULL; } void osd_preview_restore(struct server *server) { struct osd_state *osd_state = &server->osd_state; if (osd_state->preview_node) { if (osd_state->preview_anchor) { wlr_scene_node_place_above(osd_state->preview_node, osd_state->preview_anchor); } else { /* Selected view was the first node */ wlr_scene_node_lower_to_bottom(osd_state->preview_node); } /* Node was disabled / minimized before, disable again */ if (!osd_state->preview_was_enabled) { wlr_scene_node_set_enabled(osd_state->preview_node, false); } osd_state->preview_node = NULL; osd_state->preview_anchor = NULL; } } static void preview_cycled_view(struct view *view) { assert(view); assert(view->scene_tree); struct osd_state *osd_state = &view->server->osd_state; /* Move previous selected node back to its original place */ osd_preview_restore(view->server); /* Remember the sibling right before the selected node */ osd_state->preview_node = &view->scene_tree->node; osd_state->preview_anchor = lab_wlr_scene_get_prev_node( osd_state->preview_node); while (osd_state->preview_anchor && !osd_state->preview_anchor->data) { /* Ignore non-view nodes */ osd_state->preview_anchor = lab_wlr_scene_get_prev_node( osd_state->preview_anchor); } /* Store node enabled / minimized state and force-enable if disabled */ osd_state->preview_was_enabled = osd_state->preview_node->enabled; if (!osd_state->preview_was_enabled) { wlr_scene_node_set_enabled(osd_state->preview_node, true); } /* Finally raise selected node to the top */ wlr_scene_node_raise_to_top(osd_state->preview_node); } static const char * get_type(struct view *view) { switch (view->type) { case LAB_XDG_SHELL_VIEW: return "[xdg-shell]"; #if HAVE_XWAYLAND case LAB_XWAYLAND_VIEW: return "[xwayland]"; #endif } return ""; } static const char * get_app_id(struct view *view) { switch (view->type) { case LAB_XDG_SHELL_VIEW: return get_formatted_app_id(view); #if HAVE_XWAYLAND case LAB_XWAYLAND_VIEW: return view_get_string_prop(view, "class"); #endif } return ""; } static const char * get_title_if_different(struct view *view) { /* * XWayland clients return WM_CLASS for 'app_id' so we don't need a * special case for that here. */ const char *identifier = view_get_string_prop(view, "app_id"); const char *title = view_get_string_prop(view, "title"); if (!identifier) { return title; } return (!title || !strcmp(identifier, title)) ? NULL : title; } static void render_osd(struct server *server, cairo_t *cairo, int w, int h, struct wl_list *node_list, bool show_workspace, const char *workspace_name, struct wl_array *views) { struct view *cycle_view = server->osd_state.cycle_view; struct theme *theme = server->theme; cairo_surface_t *surf = cairo_get_target(cairo); /* Draw background */ set_cairo_color(cairo, theme->osd_bg_color); cairo_rectangle(cairo, 0, 0, w, h); cairo_fill(cairo); /* Draw border */ set_cairo_color(cairo, theme->osd_border_color); struct wlr_fbox fbox = { .width = w, .height = h, }; draw_cairo_border(cairo, fbox, theme->osd_border_width); /* Set up text rendering */ set_cairo_color(cairo, theme->osd_label_text_color); PangoLayout *layout = pango_cairo_create_layout(cairo); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); PangoFontDescription *desc = font_to_pango_desc(&rc.font_osd); pango_layout_set_font_description(layout, desc); pango_cairo_update_layout(cairo, layout); int y = theme->osd_border_width + theme->osd_window_switcher_padding; /* Draw workspace indicator */ if (show_workspace) { /* Center workspace indicator on the x axis */ int x = font_width(&rc.font_osd, workspace_name); x = (theme->osd_window_switcher_width - x) / 2; cairo_move_to(cairo, x, y + theme->osd_window_switcher_item_active_border_width); PangoWeight weight = pango_font_description_get_weight(desc); pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD); pango_layout_set_font_description(layout, desc); pango_layout_set_text(layout, workspace_name, -1); pango_cairo_show_layout(cairo, layout); pango_font_description_set_weight(desc, weight); pango_layout_set_font_description(layout, desc); y += theme->osd_window_switcher_item_height; } pango_font_description_free(desc); struct buf buf; buf_init(&buf); /* This is the width of the area available for text fields */ int available_width = w - 2 * theme->osd_border_width - 2 * theme->osd_window_switcher_padding - 2 * theme->osd_window_switcher_item_active_border_width; /* Draw text for each node */ struct view **view; wl_array_for_each(view, views) { /* * OSD border * +---------------------------------+ * | | * | item border | * |+-------------------------------+| * || || * ||padding between each field || * ||| field-1 | field-2 | field-n ||| * || || * || || * |+-------------------------------+| * | | * | | * +---------------------------------+ */ int x = theme->osd_border_width + theme->osd_window_switcher_padding + theme->osd_window_switcher_item_active_border_width + theme->osd_window_switcher_item_padding_x; int nr_fields = wl_list_length(&rc.window_switcher.fields); struct window_switcher_field *field; wl_list_for_each(field, &rc.window_switcher.fields, link) { buf.len = 0; cairo_move_to(cairo, x, y + theme->osd_window_switcher_item_padding_y + theme->osd_window_switcher_item_active_border_width); switch (field->content) { case LAB_FIELD_TYPE: buf_add(&buf, get_type(*view)); break; case LAB_FIELD_IDENTIFIER: buf_add(&buf, get_app_id(*view)); break; case LAB_FIELD_TRIMMED_IDENTIFIER: { char *s = (char *)get_app_id(*view); buf_add(&buf, get_trimmed_app_id(s)); break; } case LAB_FIELD_TITLE: buf_add(&buf, get_title_if_different(*view)); break; default: break; } int field_width = (available_width - (nr_fields + 1) * theme->osd_window_switcher_item_padding_x) * field->width / 100.0; pango_layout_set_width(layout, field_width * PANGO_SCALE); pango_layout_set_text(layout, buf.buf, -1); pango_cairo_show_layout(cairo, layout); x += field_width + theme->osd_window_switcher_item_padding_x; } if (*view == cycle_view) { /* Highlight current window */ struct wlr_fbox fbox = { .x = theme->osd_border_width + theme->osd_window_switcher_padding, .y = y, .width = theme->osd_window_switcher_width - 2 * theme->osd_border_width - 2 * theme->osd_window_switcher_padding, .height = theme->osd_window_switcher_item_height, }; draw_cairo_border(cairo, fbox, theme->osd_window_switcher_item_active_border_width); cairo_stroke(cairo); } y += theme->osd_window_switcher_item_height; } free(buf.buf); g_object_unref(layout); cairo_surface_flush(surf); } static void display_osd(struct output *output) { struct server *server = output->server; struct theme *theme = server->theme; struct wl_list *node_list = &server->workspace_current->tree->children; bool show_workspace = wl_list_length(&rc.workspace_config.workspaces) > 1; const char *workspace_name = server->workspace_current->name; struct wl_array views; wl_array_init(&views); view_array_append(server, &views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE | LAB_VIEW_CRITERIA_NO_ALWAYS_ON_TOP | LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER); float scale = output->wlr_output->scale; int w = theme->osd_window_switcher_width; int h = wl_array_len(&views) * rc.theme->osd_window_switcher_item_height + 2 * rc.theme->osd_border_width + 2 * rc.theme->osd_window_switcher_padding; if (show_workspace) { /* workspace indicator */ h += theme->osd_window_switcher_item_height; } /* Reset buffer */ if (output->osd_buffer) { wlr_buffer_drop(&output->osd_buffer->base); } output->osd_buffer = buffer_create_cairo(w, h, scale, true); if (!output->osd_buffer) { wlr_log(WLR_ERROR, "Failed to allocate cairo buffer for the window switcher"); return; } /* Render OSD image */ cairo_t *cairo = output->osd_buffer->cairo; render_osd(server, cairo, w, h, node_list, show_workspace, workspace_name, &views); wl_array_release(&views); struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_create( output->osd_tree, &output->osd_buffer->base); wlr_scene_buffer_set_dest_size(scene_buffer, w, h); /* Center OSD */ struct wlr_box output_box; wlr_output_layout_get_box(output->server->output_layout, output->wlr_output, &output_box); int lx = output->usable_area.x + output->usable_area.width / 2 - w / 2 + output_box.x; int ly = output->usable_area.y + output->usable_area.height / 2 - h / 2 + output_box.y; wlr_scene_node_set_position(&scene_buffer->node, lx, ly); wlr_scene_node_set_enabled(&output->osd_tree->node, true); /* Update cursor, in case it is within the area covered by OSD */ cursor_update_focus(server); } void osd_update(struct server *server) { struct wl_list *node_list = &server->workspace_current->tree->children; if (wl_list_empty(node_list) || !server->osd_state.cycle_view) { osd_finish(server); return; } if (rc.window_switcher.show && rc.theme->osd_window_switcher_width > 0) { /* Display the actual OSD */ struct output *output; wl_list_for_each(output, &server->outputs, link) { destroy_osd_nodes(output); if (output_is_usable(output)) { display_osd(output); } } } /* Outline current window */ if (rc.window_switcher.outlines) { if (view_is_focusable(server->osd_state.cycle_view)) { osd_update_preview_outlines(server->osd_state.cycle_view); } } if (rc.window_switcher.preview) { preview_cycled_view(server->osd_state.cycle_view); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/output.c����������������������������������������������������������������������������0000664�0000000�0000000�00000064730�14570443012�0015114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * output.c: labwc output and rendering * * Copyright (C) 2019-2021 Johan Malm * Copyright (C) 2020 The Sway authors */ #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <strings.h> #include <wlr/backend/drm.h> #include <wlr/backend/headless.h> #include <wlr/backend/wayland.h> #include <wlr/types/wlr_buffer.h> #include <wlr/types/wlr_drm_lease_v1.h> #include <wlr/types/wlr_output.h> #include <wlr/types/wlr_xdg_output_v1.h> #include <wlr/types/wlr_scene.h> #include <wlr/util/region.h> #include <wlr/util/log.h> #include "common/macros.h" #include "common/mem.h" #include "common/scene-helpers.h" #include "labwc.h" #include "layers.h" #include "node.h" #include "regions.h" #include "view.h" #include "xwayland.h" static bool get_tearing_preference(struct output *output) { struct server *server = output->server; /* Never allow tearing when disabled */ if (!rc.allow_tearing) { return false; } /* Tearing is only allowed for the output with the active view */ if (!server->active_view || server->active_view->output != output) { return false; } /* If the active view requests tearing, or it is toggled on with action, allow it */ return server->active_view->tearing_hint; } static void output_frame_notify(struct wl_listener *listener, void *data) { /* * This function is called every time an output is ready to display a * frame - which is typically at 60 Hz. */ struct output *output = wl_container_of(listener, output, frame); if (!output_is_usable(output)) { return; } struct wlr_output *wlr_output = output->wlr_output; struct server *server = output->server; if (output->gamma_lut_changed) { struct wlr_output_state pending; wlr_output_state_init(&pending); if (!wlr_scene_output_build_state(output->scene_output, &pending, NULL)) { return; } output->gamma_lut_changed = false; struct wlr_gamma_control_v1 *gamma_control = wlr_gamma_control_manager_v1_get_control( server->gamma_control_manager_v1, wlr_output); if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) { wlr_output_state_finish(&pending); return; } if (!wlr_output_commit_state(output->wlr_output, &pending)) { wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); wlr_output_state_finish(&pending); return; } wlr_damage_ring_rotate(&output->scene_output->damage_ring); wlr_output_state_finish(&pending); return; } output->wlr_output->pending.tearing_page_flip = get_tearing_preference(output); lab_wlr_scene_output_commit(output->scene_output); struct timespec now = { 0 }; clock_gettime(CLOCK_MONOTONIC, &now); wlr_scene_output_send_frame_done(output->scene_output, &now); } static void output_destroy_notify(struct wl_listener *listener, void *data) { struct output *output = wl_container_of(listener, output, destroy); regions_evacuate_output(output); regions_destroy(&output->server->seat, &output->regions); wl_list_remove(&output->link); wl_list_remove(&output->frame.link); wl_list_remove(&output->destroy.link); wl_list_remove(&output->request_state.link); seat_output_layout_changed(&output->server->seat); for (size_t i = 0; i < ARRAY_SIZE(output->layer_tree); i++) { wlr_scene_node_destroy(&output->layer_tree[i]->node); } wlr_scene_node_destroy(&output->layer_popup_tree->node); wlr_scene_node_destroy(&output->osd_tree->node); wlr_scene_node_destroy(&output->session_lock_tree->node); if (output->workspace_osd) { wlr_scene_node_destroy(&output->workspace_osd->node); output->workspace_osd = NULL; } struct view *view; struct server *server = output->server; wl_list_for_each(view, &server->views, link) { if (view->output == output) { view_on_output_destroy(view); } } /* * Ensure that we don't accidentally try to dereference * the output pointer in some output event handler like * set_gamma. */ output->wlr_output->data = NULL; /* * output->scene_output (if still around at this point) is * destroyed automatically when the wlr_output is destroyed */ free(output); } static void output_request_state_notify(struct wl_listener *listener, void *data) { /* This ensures nested backends can be resized */ struct output *output = wl_container_of(listener, output, request_state); const struct wlr_output_event_request_state *event = data; if (!wlr_output_commit_state(output->wlr_output, event->state)) { wlr_log(WLR_ERROR, "Backend requested a new state that could not be applied"); } } static void do_output_layout_change(struct server *server); static bool can_reuse_mode(struct wlr_output *wlr_output) { return wlr_output->current_mode && wlr_output_test(wlr_output); } static void add_output_to_layout(struct server *server, struct output *output) { struct wlr_output *wlr_output = output->wlr_output; struct wlr_output_layout_output *layout_output = wlr_output_layout_add_auto(server->output_layout, wlr_output); if (!layout_output) { wlr_log(WLR_ERROR, "unable to add output to layout"); return; } if (!output->scene_output) { output->scene_output = wlr_scene_output_create(server->scene, wlr_output); if (!output->scene_output) { wlr_log(WLR_ERROR, "unable to create scene output"); return; } /* * Note: wlr_scene_output_layout_add_output() is not * safe to call twice, so we call it only when initially * creating the scene_output. */ wlr_scene_output_layout_add_output(server->scene_layout, layout_output, output->scene_output); } } static void new_output_notify(struct wl_listener *listener, void *data) { /* * This event is rasied by the backend when a new output (aka display * or monitor) becomes available. */ struct server *server = wl_container_of(listener, server, new_output); struct wlr_output *wlr_output = data; /* Name virtual output */ if (wlr_output_is_headless(wlr_output) && server->headless.pending_output_name[0] != '\0') { wlr_output_set_name(wlr_output, server->headless.pending_output_name); server->headless.pending_output_name[0] = '\0'; } /* * We offer any display as available for lease, some apps like * gamescope, want to take ownership of a display when they can * to use planes and present directly. * This is also useful for debugging the DRM parts of * another compositor. */ if (server->drm_lease_manager && wlr_output_is_drm(wlr_output)) { wlr_drm_lease_v1_manager_offer_output( server->drm_lease_manager, wlr_output); } /* * Don't configure any non-desktop displays, such as VR headsets; */ if (wlr_output->non_desktop) { wlr_log(WLR_DEBUG, "Not configuring non-desktop output"); return; } /* * Configures the output created by the backend to use our allocator * and our renderer. Must be done once, before committing the output */ if (!wlr_output_init_render(wlr_output, server->allocator, server->renderer)) { wlr_log(WLR_ERROR, "unable to init output renderer"); return; } wlr_log(WLR_DEBUG, "enable output"); wlr_output_enable(wlr_output, true); /* * Try to re-use the existing mode if configured to do so. * Failing that, try to set the preferred mode. */ struct wlr_output_mode *preferred_mode = NULL; if (!rc.reuse_output_mode || !can_reuse_mode(wlr_output)) { wlr_log(WLR_DEBUG, "set preferred mode"); /* The mode is a tuple of (width, height, refresh rate). */ preferred_mode = wlr_output_preferred_mode(wlr_output); wlr_output_set_mode(wlr_output, preferred_mode); } /* * Sometimes the preferred mode is not available due to hardware * constraints (e.g. GPU or cable bandwidth limitations). In these * cases it's better to fallback to lower modes than to end up with * a black screen. See sway@4cdc4ac6 */ if (!wlr_output_test(wlr_output)) { wlr_log(WLR_DEBUG, "preferred mode rejected, falling back to another mode"); struct wlr_output_mode *mode; wl_list_for_each(mode, &wlr_output->modes, link) { if (mode == preferred_mode) { continue; } wlr_output_set_mode(wlr_output, mode); if (wlr_output_test(wlr_output)) { break; } } } if (rc.adaptive_sync == LAB_ADAPTIVE_SYNC_ENABLED) { output_enable_adaptive_sync(wlr_output, true); } wlr_output_commit(wlr_output); struct output *output = znew(*output); output->wlr_output = wlr_output; wlr_output->data = output; output->server = server; wlr_output_effective_resolution(wlr_output, &output->usable_area.width, &output->usable_area.height); wl_list_insert(&server->outputs, &output->link); output->destroy.notify = output_destroy_notify; wl_signal_add(&wlr_output->events.destroy, &output->destroy); output->frame.notify = output_frame_notify; wl_signal_add(&wlr_output->events.frame, &output->frame); output->request_state.notify = output_request_state_notify; wl_signal_add(&wlr_output->events.request_state, &output->request_state); wl_list_init(&output->regions); /* * Create layer-trees (background, bottom, top and overlay) and * a layer-popup-tree. */ for (size_t i = 0; i < ARRAY_SIZE(output->layer_tree); i++) { output->layer_tree[i] = wlr_scene_tree_create(&server->scene->tree); node_descriptor_create(&output->layer_tree[i]->node, LAB_NODE_DESC_TREE, NULL); } output->layer_popup_tree = wlr_scene_tree_create(&server->scene->tree); node_descriptor_create(&output->layer_popup_tree->node, LAB_NODE_DESC_TREE, NULL); output->osd_tree = wlr_scene_tree_create(&server->scene->tree); node_descriptor_create(&output->osd_tree->node, LAB_NODE_DESC_TREE, NULL); output->session_lock_tree = wlr_scene_tree_create(&server->scene->tree); node_descriptor_create(&output->session_lock_tree->node, LAB_NODE_DESC_TREE, NULL); /* * Set the z-positions to achieve the following order (from top to * bottom): * - session lock layer * - layer-shell popups * - overlay layer * - top layer * - views * - bottom layer * - background layer */ wlr_scene_node_lower_to_bottom(&output->layer_tree[1]->node); wlr_scene_node_lower_to_bottom(&output->layer_tree[0]->node); wlr_scene_node_raise_to_top(&output->layer_tree[2]->node); wlr_scene_node_raise_to_top(&output->layer_tree[3]->node); wlr_scene_node_raise_to_top(&output->layer_popup_tree->node); wlr_scene_node_raise_to_top(&output->session_lock_tree->node); /* * Wait until wlr_output_layout_add_auto() returns before * calling do_output_layout_change(); this ensures that the * wlr_output_cursor is created for the new output. */ server->pending_output_layout_change++; add_output_to_layout(server, output); /* Create regions from config */ regions_reconfigure_output(output); if (server->session_lock) { session_lock_output_create(server->session_lock, output); } server->pending_output_layout_change--; do_output_layout_change(server); seat_output_layout_changed(&output->server->seat); } void output_init(struct server *server) { server->gamma_control_manager_v1 = wlr_gamma_control_manager_v1_create(server->wl_display); server->new_output.notify = new_output_notify; wl_signal_add(&server->backend->events.new_output, &server->new_output); /* * Create an output layout, which is a wlroots utility for working with * an arrangement of screens in a physical layout. */ server->output_layout = wlr_output_layout_create(); if (!server->output_layout) { wlr_log(WLR_ERROR, "unable to create output layout"); exit(EXIT_FAILURE); } server->scene_layout = wlr_scene_attach_output_layout(server->scene, server->output_layout); if (!server->scene_layout) { wlr_log(WLR_ERROR, "unable to create scene layout"); exit(EXIT_FAILURE); } /* Enable screen recording with wf-recorder */ wlr_xdg_output_manager_v1_create(server->wl_display, server->output_layout); wl_list_init(&server->outputs); output_manager_init(server); } static void output_update_for_layout_change(struct server *server) { output_update_all_usable_areas(server, /*layout_changed*/ true); session_lock_update_for_layout_change(); /* * "Move" each wlr_output_cursor (in per-output coordinates) to * align with the seat cursor. Re-set the cursor image so that * the cursor isn't invisible on new outputs. */ wlr_cursor_move(server->seat.cursor, NULL, 0, 0); cursor_update_image(&server->seat); } static bool output_config_apply(struct server *server, struct wlr_output_configuration_v1 *config) { bool success = true; server->pending_output_layout_change++; struct wlr_output_configuration_head_v1 *head; wl_list_for_each(head, &config->heads, link) { struct wlr_output *o = head->state.output; struct output *output = output_from_wlr_output(server, o); bool output_enabled = head->state.enabled && !output->leased; bool need_to_add = output_enabled && !o->enabled; bool need_to_remove = !output_enabled && o->enabled; wlr_output_enable(o, output_enabled); if (output_enabled) { /* Output specific actions only */ if (head->state.mode) { wlr_output_set_mode(o, head->state.mode); } else { int32_t width = head->state.custom_mode.width; int32_t height = head->state.custom_mode.height; int32_t refresh = head->state.custom_mode.refresh; wlr_output_set_custom_mode(o, width, height, refresh); } wlr_output_set_scale(o, head->state.scale); wlr_output_set_transform(o, head->state.transform); output_enable_adaptive_sync(o, head->state.adaptive_sync_enabled); } if (!wlr_output_commit(o)) { /* * FIXME: This is only part of the story, we should revert * all previously commited outputs as well here. * * See https://github.com/labwc/labwc/pull/1528 */ wlr_log(WLR_INFO, "Output config commit failed: %s", o->name); success = false; break; } /* Only do Layout specific actions if the commit went trough */ if (need_to_add) { add_output_to_layout(server, output); } if (output_enabled) { struct wlr_box pos = {0}; wlr_output_layout_get_box(server->output_layout, o, &pos); if (pos.x != head->state.x || pos.y != head->state.y) { /* * This overrides the automatic layout * * wlr_output_layout_add() in fact means _move() */ wlr_output_layout_add(server->output_layout, o, head->state.x, head->state.y); } } if (need_to_remove) { regions_evacuate_output(output); /* * At time of writing, wlr_output_layout_remove() * indirectly destroys the wlr_scene_output, but * this behavior may change in future. To remove * doubt and avoid either a leak or double-free, * explicitly destroy the wlr_scene_output before * calling wlr_output_layout_remove(). */ wlr_scene_output_destroy(output->scene_output); wlr_output_layout_remove(server->output_layout, o); output->scene_output = NULL; } } server->pending_output_layout_change--; do_output_layout_change(server); return success; } static bool verify_output_config_v1(const struct wlr_output_configuration_v1 *config) { const char *err_msg = NULL; struct wlr_output_configuration_head_v1 *head; wl_list_for_each(head, &config->heads, link) { if (!head->state.enabled) { continue; } /* Handle custom modes */ if (!head->state.mode) { int32_t refresh = head->state.custom_mode.refresh; if (wlr_output_is_drm(head->state.output) && refresh == 0) { /* * wlroots has a bug which causes a divide by zero * when setting the refresh rate to 0 on a DRM output. * It is already fixed but not part of an official 0.17.x * release. Even it would be we still need to carry the * fix here to prevent older 0.17.x releases from crashing. * * https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3791 */ err_msg = "DRM backend does not support a refresh rate of 0"; goto custom_mode_failed; } if (wlr_output_is_wl(head->state.output) && refresh != 0) { /* Wayland backend does not support refresh rates */ err_msg = "Wayland backend refresh rates unsupported"; goto custom_mode_failed; } } if (wlr_output_is_wl(head->state.output) && !head->state.adaptive_sync_enabled) { err_msg = "Wayland backend requires adaptive sync"; goto custom_mode_failed; } /* * Ensure the new output state can be applied on * its own and inform the client when it can not. * * Applying the changes may still fail later when * getting mixed with wlr_output->pending which * may contain further unrelated changes. */ struct wlr_output_state output_state; wlr_output_state_init(&output_state); wlr_output_head_v1_state_apply(&head->state, &output_state); if (!wlr_output_test_state(head->state.output, &output_state)) { wlr_output_state_finish(&output_state); return false; } wlr_output_state_finish(&output_state); } return true; custom_mode_failed: assert(err_msg); wlr_log(WLR_INFO, "%s (%s: %dx%d@%d)", err_msg, head->state.output->name, head->state.custom_mode.width, head->state.custom_mode.height, head->state.custom_mode.refresh); return false; } static void handle_output_manager_test(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, output_manager_test); struct wlr_output_configuration_v1 *config = data; if (verify_output_config_v1(config)) { wlr_output_configuration_v1_send_succeeded(config); } else { wlr_output_configuration_v1_send_failed(config); } wlr_output_configuration_v1_destroy(config); } static void handle_output_manager_apply(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, output_manager_apply); struct wlr_output_configuration_v1 *config = data; bool config_is_good = verify_output_config_v1(config); if (config_is_good && output_config_apply(server, config)) { wlr_output_configuration_v1_send_succeeded(config); } else { wlr_output_configuration_v1_send_failed(config); } wlr_output_configuration_v1_destroy(config); struct output *output; wl_list_for_each(output, &server->outputs, link) { wlr_xcursor_manager_load(server->seat.xcursor_manager, output->wlr_output->scale); } /* Re-set cursor image in case scale changed */ cursor_update_focus(server); cursor_update_image(&server->seat); } /* * Take the way outputs are currently configured/laid out and turn that into * a struct that we send to clients via the wlr_output_configuration v1 * interface */ static struct wlr_output_configuration_v1 *create_output_config(struct server *server) { struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create(); if (!config) { wlr_log(WLR_ERROR, "wlr_output_configuration_v1_create()"); return NULL; } struct output *output; wl_list_for_each(output, &server->outputs, link) { struct wlr_output_configuration_head_v1 *head = wlr_output_configuration_head_v1_create(config, output->wlr_output); if (!head) { wlr_log(WLR_ERROR, "wlr_output_configuration_head_v1_create()"); wlr_output_configuration_v1_destroy(config); return NULL; } struct wlr_box box; wlr_output_layout_get_box(server->output_layout, output->wlr_output, &box); if (!wlr_box_empty(&box)) { head->state.x = box.x; head->state.y = box.y; } else { wlr_log(WLR_ERROR, "failed to get output layout box"); } } return config; } static void do_output_layout_change(struct server *server) { if (!server->pending_output_layout_change) { struct wlr_output_configuration_v1 *config = create_output_config(server); if (config) { wlr_output_manager_v1_set_configuration( server->output_manager, config); } else { wlr_log(WLR_ERROR, "wlr_output_manager_v1_set_configuration()"); } output_update_for_layout_change(server); } } static void handle_output_layout_change(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, output_layout_change); do_output_layout_change(server); } static void handle_gamma_control_set_gamma(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, gamma_control_set_gamma); const struct wlr_gamma_control_manager_v1_set_gamma_event *event = data; struct output *output = event->output->data; if (!output_is_usable(output)) { return; } output->gamma_lut_changed = true; wlr_output_schedule_frame(output->wlr_output); } void output_manager_init(struct server *server) { server->output_manager = wlr_output_manager_v1_create(server->wl_display); server->output_layout_change.notify = handle_output_layout_change; wl_signal_add(&server->output_layout->events.change, &server->output_layout_change); server->output_manager_apply.notify = handle_output_manager_apply; wl_signal_add(&server->output_manager->events.apply, &server->output_manager_apply); server->output_manager_test.notify = handle_output_manager_test; wl_signal_add(&server->output_manager->events.test, &server->output_manager_test); server->gamma_control_set_gamma.notify = handle_gamma_control_set_gamma; wl_signal_add(&server->gamma_control_manager_v1->events.set_gamma, &server->gamma_control_set_gamma); } struct output * output_from_wlr_output(struct server *server, struct wlr_output *wlr_output) { struct output *output; wl_list_for_each(output, &server->outputs, link) { if (output->wlr_output == wlr_output) { return output; } } return NULL; } struct output * output_from_name(struct server *server, const char *name) { struct output *output; wl_list_for_each(output, &server->outputs, link) { if (!output_is_usable(output) || !output->wlr_output->name) { continue; } if (!strcasecmp(name, output->wlr_output->name)) { return output; } } return NULL; } struct output * output_nearest_to(struct server *server, int lx, int ly) { double closest_x, closest_y; wlr_output_layout_closest_point(server->output_layout, NULL, lx, ly, &closest_x, &closest_y); return output_from_wlr_output(server, wlr_output_layout_output_at(server->output_layout, closest_x, closest_y)); } struct output * output_nearest_to_cursor(struct server *server) { return output_nearest_to(server, server->seat.cursor->x, server->seat.cursor->y); } bool output_is_usable(struct output *output) { /* output_is_usable(NULL) is safe and returns false */ return output && output->wlr_output->enabled && !output->leased; } /* returns true if usable area changed */ static bool update_usable_area(struct output *output) { struct wlr_box old = output->usable_area; layers_arrange(output); #if HAVE_XWAYLAND struct view *view; wl_list_for_each(view, &output->server->views, link) { if (view->mapped && view->type == LAB_XWAYLAND_VIEW) { xwayland_adjust_usable_area(view, output->server->output_layout, output->wlr_output, &output->usable_area); } } #endif return !wlr_box_equal(&old, &output->usable_area); } void output_update_usable_area(struct output *output) { if (update_usable_area(output)) { regions_update_geometry(output); #if HAVE_XWAYLAND xwayland_update_workarea(output->server); #endif desktop_arrange_all_views(output->server); } } void output_update_all_usable_areas(struct server *server, bool layout_changed) { bool usable_area_changed = false; struct output *output; wl_list_for_each(output, &server->outputs, link) { if (update_usable_area(output)) { usable_area_changed = true; regions_update_geometry(output); } else if (layout_changed) { regions_update_geometry(output); } } if (usable_area_changed || layout_changed) { #if HAVE_XWAYLAND xwayland_update_workarea(server); #endif desktop_arrange_all_views(server); } } struct wlr_box output_usable_area_in_layout_coords(struct output *output) { if (!output) { return (struct wlr_box){0}; } struct wlr_box box = output->usable_area; double ox = 0, oy = 0; wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy); box.x -= ox; box.y -= oy; return box; } struct wlr_box output_usable_area_scaled(struct output *output) { if (!output) { return (struct wlr_box){0}; } struct wlr_box usable = output_usable_area_in_layout_coords(output); if (usable.height == output->wlr_output->height && output->wlr_output->scale != 1) { usable.height /= output->wlr_output->scale; } if (usable.width == output->wlr_output->width && output->wlr_output->scale != 1) { usable.width /= output->wlr_output->scale; } return usable; } void handle_output_power_manager_set_mode(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, output_power_manager_set_mode); struct wlr_output_power_v1_set_mode_event *event = data; switch (event->mode) { case ZWLR_OUTPUT_POWER_V1_MODE_OFF: wlr_output_enable(event->output, false); wlr_output_commit(event->output); break; case ZWLR_OUTPUT_POWER_V1_MODE_ON: wlr_output_enable(event->output, true); if (!wlr_output_test(event->output)) { wlr_output_rollback(event->output); } wlr_output_commit(event->output); /* * Re-set the cursor image so that the cursor * isn't invisible on the newly enabled output. */ cursor_update_image(&server->seat); break; } } void output_add_virtual(struct server *server, const char *output_name) { if (output_name) { /* Prevent creating outputs with the same name */ struct output *output; wl_list_for_each(output, &server->outputs, link) { if (wlr_output_is_headless(output->wlr_output) && !strcmp(output->wlr_output->name, output_name)) { wlr_log(WLR_DEBUG, "refusing to create virtual output with duplicate name"); return; } } snprintf(server->headless.pending_output_name, sizeof(server->headless.pending_output_name), "%s", output_name); } else { server->headless.pending_output_name[0] = '\0'; } /* * Setting it to (0, 0) here disallows changing resolution from tools like * wlr-randr (returns error) */ wlr_headless_add_output(server->headless.backend, 1920, 1080); } void output_remove_virtual(struct server *server, const char *output_name) { struct output *output; wl_list_for_each(output, &server->outputs, link) { if (wlr_output_is_headless(output->wlr_output)) { if (output_name) { /* * Given virtual output name, find and destroy virtual output by * that name. */ if (!strcmp(output->wlr_output->name, output_name)) { wlr_output_destroy(output->wlr_output); return; } } else { /* * When virtual output name was no supplied by user, simply * destroy the first virtual output found. */ wlr_output_destroy(output->wlr_output); return; } } } } void output_enable_adaptive_sync(struct wlr_output *output, bool enabled) { wlr_output_enable_adaptive_sync(output, enabled); if (!wlr_output_test(output)) { wlr_output_enable_adaptive_sync(output, false); wlr_log(WLR_DEBUG, "failed to enable adaptive sync for output %s", output->name); } else { wlr_log(WLR_INFO, "adaptive sync %sabled for output %s", enabled ? "en" : "dis", output->name); } } ����������������������������������������labwc-0.7.1/src/placement.c�������������������������������������������������������������������������0000664�0000000�0000000�00000034446�14570443012�0015525�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <limits.h> #include <stdbool.h> #include <stdio.h> #include "common/macros.h" #include "common/mem.h" #include "labwc.h" #include "placement.h" #include "ssd.h" #include "view.h" #define overlap_bitmap_index(bmp, i, j) \ (bmp)->grid[i * ((bmp)->nr_cols - 1) + j] struct overlap_bitmap { int nr_rows; int nr_cols; int *rows; int *cols; int *grid; }; static int compare_ints(const void *a, const void *b) { return *(const int *)a - *(const int *)b; } static void destroy_bitmap(struct overlap_bitmap *bmp) { assert(bmp); zfree(bmp->rows); zfree(bmp->cols); zfree(bmp->grid); bmp->nr_rows = 0; bmp->nr_cols = 0; } /* Count the number of views on view->output, excluding *view itself */ static int count_views(struct view *view) { assert(view); struct server *server = view->server; struct output *output = view->output; if (!output_is_usable(output)) { return 0; } int nviews = 0; struct view *v; for_each_view(v, &server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) { /* Ignore the target view or anything on a different output */ if (v == view || v->output != output) { continue; } nviews++; } return nviews; } /* Sort and de-deplicate a list of points that define a 1-D grid */ static int order_grid(int *edges, int nedges) { /* Sort grid edges */ qsort(edges, nedges, sizeof(int), compare_ints); /* Skip over non-unique edges, counting the unique ones */ /* This is taken almost verbatim from Openbox. */ int i = 0; int j = 0; while (j < nedges) { int last = edges[j++]; edges[i++] = last; while (j < nedges && edges[j] == last) { ++j; } } return i; } /* * Construct an irregular grid that divides the usable area of view->output * by extending the edges of every view on the output (except for *view itself) * to infinity. The resulting grid will consist of rectangular intervals that * are either completely uncovered by any view, or entirely covered. * Furthermore, when any view intersects any interval on the grid, that view * overlaps the whole interval: no view ever partially intersects any interval. */ static void build_grid(struct overlap_bitmap *bmp, struct view *view) { assert(bmp); assert(view); struct server *server = view->server; /* Always start with a fresh bitmap */ destroy_bitmap(bmp); struct output *output = view->output; if (!output_is_usable(output)) { return; } int nviews = count_views(view); if (nviews < 1) { return; } /* Number of rows/columns is bounded by two per view plus screen edges */ int max_rc = 2 * nviews + 2; bmp->rows = xzalloc(max_rc * sizeof(int)); bmp->cols = xzalloc(max_rc * sizeof(int)); if (!bmp->rows || !bmp->cols) { destroy_bitmap(bmp); return; } /* First edges of grid are start of usable area of output */ struct wlr_box usable = output_usable_area_in_layout_coords(output); int usable_right = usable.x + usable.width; int usable_bottom = usable.y + usable.height; bmp->cols[0] = usable.x; bmp->rows[0] = usable.y; bmp->cols[1] = usable_right; bmp->rows[1] = usable_bottom; int nr_rows = 2; int nr_cols = 2; struct view *v; for_each_view(v, &server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) { if (v == view || v->output != output) { continue; } struct border margin = ssd_get_margin(v->ssd); int x = v->pending.x - margin.left; int y = v->pending.y - margin.top; /* Add a column if the left view edge is in the usable region */ if (x > usable.x && x < usable_right) { assert(nr_cols < max_rc); bmp->cols[nr_cols++] = x; } /* Add a row if the top view edge is in the usable region */ if (y > usable.y && y < usable_bottom) { assert(nr_rows < max_rc); bmp->rows[nr_rows++] = y; } x = v->pending.x + margin.right + v->pending.width; y = v->pending.y + margin.bottom + view_effective_height(v, /* use_pending */ true); /* Add a column if the right view edge is in the usable region */ if (x > usable.x && x < usable_right) { assert(nr_cols < max_rc); bmp->cols[nr_cols++] = x; } /* Add a row if the bottom view edge is in the usable region */ if (y > usable.y && y < usable_bottom) { assert(nr_rows < max_rc); bmp->rows[nr_rows++] = y; } } bmp->nr_rows = order_grid(bmp->rows, nr_rows); bmp->nr_cols = order_grid(bmp->cols, nr_cols); int grid_size = (bmp->nr_rows - 1) * (bmp->nr_cols - 1); bmp->grid = xzalloc(grid_size * sizeof(int)); if (!bmp->grid) { destroy_bitmap(bmp); return; } } /* * Perform a rightmost binary search along a list of edges in a 1-D grid for * the maximum index j such that edges[j] <= val. The list of edges must be * sorted in increasing order. * * For a returned index j: * * - The index j == -1 implies that val < edges[0]. * - An index 0 <= j < (nedges - 1) implies that edges[j] <= val < edges[j + 1]. * - The index j == (nedges - 1) implies that edges[nedges - 1] <= val. */ static int find_interval(int *edges, int nedges, double val) { int l = 0; int r = nedges; while (l < r) { int m = (l + r) / 2; if (edges[m] > val) { r = m; } else { l = m + 1; } } return r - 1; } /* * Construct an overlap bitmap for the irregular grid, computed by * build_grid(), that spans view->output. The overlap bitmap maps * each interval to the number of views on the output (excluding *view) * that overlap that interval. */ static void build_overlap(struct overlap_bitmap *bmp, struct view *view) { assert(bmp); assert(view); struct server *server = view->server; if (bmp->nr_rows < 1 || bmp->nr_cols < 1) { return; } struct output *output = view->output; if (!output_is_usable(output)) { return; } struct view *v; for_each_view(v, &server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) { if (v == view || v->output != output) { continue; } /* Find boundaries of the window */ struct border margin = ssd_get_margin(v->ssd); int lx = v->pending.x - margin.left; int ly = v->pending.y - margin.top; int hx = v->pending.x + margin.right + v->pending.width; int hy = v->pending.y + margin.bottom + view_effective_height(v, /* use_pending */ true); /* * Find the first and last row and column intervals spanned by * this view. We want the left and top edges to fall in a * half-open interval [low, high) but the right and bottom * edges to fall in a half-open interval (low, high] to ensure * that the results do not include intervals adjacent to the * view. View edges are guaranteed by construction to fall * exactly on the grid points, so we perturb the left and top * edges by +0.5 units, and the right and bottom edges by -0.5 * units, to ensure that we are always searching in the * interior of an interval. */ /* First row and column overlapping the view */ int fc = find_interval(bmp->cols, bmp->nr_cols, lx + 0.5); int fr = find_interval(bmp->rows, bmp->nr_rows, ly + 0.5); /* Clip first row/column to start of usable grid */ fc = MAX(fc, 0); fr = MAX(fr, 0); /* Last row and column overlapping the view */ int lc = find_interval(bmp->cols, bmp->nr_cols, hx - 0.5); int lr = find_interval(bmp->rows, bmp->nr_rows, hy - 0.5); /* * Increment the last indices to convert them to strict upper * bounds, then clip them to the limits of the usable grid. */ lc = MIN(bmp->nr_cols - 1, lc + 1); lr = MIN(bmp->nr_rows - 1, lr + 1); /* * Every interval in the region [fr, lr) x [fc, lc) is * completely covered by the view. Increment the overlap * counters these intervals to account for the view. */ for (int i = fr; i < lr; ++i) { for (int j = fc; j < lc; ++j) { overlap_bitmap_index(bmp, i, j) += 1; } } } } /* * Find the total overlap of an arbitrary region of a given width and height * with intervals in a pre-computed overlap bitmap. The starting interval for * the region is (i, j) in the bitmap grid. If the region is larger than * interval (i, j), neighboring regions will be considered width-wise rightward * (when right is true) or leftward (otherwise) and height-wise downward (when * down is true) or upward (otherwise). * * If the region would extend beyond the edges of the grid (i.e., beyond the * usable region of an output) in the prescribed directions, an overlap of * INT_MAX is returned. Otherwise, the overlap is the sum of the areas of each * interval covered by the region multiplied by its overlap count. For example, * an interval currently covered by three windows will be triply counted in the * overlap sum. */ static int compute_overlap(struct overlap_bitmap *bmp, int i, int j, int width, int height, bool right, bool down, bool *single) { /* * The number of row or column intervals is one less than corresponding * number of row or column grid points. */ int nri = bmp->nr_rows - 1; int nci = bmp->nr_cols - 1; int i_incr = down ? 1 : -1; int j_incr = right ? 1 : -1; int overlap = 0; int count = 0; /* Walk up or down along rows according to preference */ for (int ii = i; ii >= 0 && ii < nri && height > 0; ii += i_incr) { /* Height of this row */ int rh = bmp->rows[ii + 1] - bmp->rows[ii]; /* Height of overlap between this row and test region */ int mh = MAX(0, MIN(height, rh)); /* Remaining height to consider for next row */ height -= rh; /* Walk left or right along columns according to preference */ int ww = width; for (int jj = j; jj >= 0 && jj < nci && ww > 0; jj += j_incr) { /* Width of this column */ int cw = bmp->cols[jj + 1] - bmp->cols[jj]; /* Width of overlap between this column and test region */ int mw = MAX(0, MIN(ww, cw)); /* Add overlap contribution for this interval */ overlap += overlap_bitmap_index(bmp, ii, jj) * mh * mw; /* Count the number of overlapping intervals */ count++; /* Remaining width to consider for next column */ ww -= cw; } /* * If there is width left to consider after walking columns, * the region extends out of bounds and placement is invalid. */ if (ww > 0) { overlap = INT_MAX; break; } } /* * If there is height left ot consider after walking rows, the region * extends out of bounds and placement is invalid. */ if (height > 0) { overlap = INT_MAX; } /* Indicate whether overlap is confined to a single region */ if (single) { *single = (count == 1); } return overlap; } /* * Find the placement of *view, with an expected width and height of * geometry->width and geometry->height, respectively, that will minimize * overlap with all other views. The values geometry->x and geometry->y will be * overwritten with the optimum placement. */ bool placement_find_best(struct view *view, struct wlr_box *geometry) { assert(view); struct border margin = ssd_get_margin(view->ssd); struct output *output = view->output; if (!output_is_usable(output)) { return false; } /* Default placement is upper-left corner, respecting gaps */ struct wlr_box usable = output_usable_area_in_layout_coords(output); geometry->x = usable.x + margin.left + rc.gap; geometry->y = usable.y + margin.top + rc.gap; /* Build the placement grid and overlap bitmap */ struct overlap_bitmap bmp = { 0 }; build_grid(&bmp, view); build_overlap(&bmp, view); /* Dimensions include gap along all edges to ensure proper separation */ int height = geometry->height + margin.top + margin.bottom + 2 * rc.gap; int width = geometry->width + margin.left + margin.right + 2 * rc.gap; /* * Overlap search identifies corners of the target region; view * coordinates must by set in by the SSD margin and user gaps. */ int offset_x = margin.left + rc.gap; int offset_y = margin.top + rc.gap; int min_overlap = INT_MAX; int nri = bmp.nr_rows - 1; int nci = bmp.nr_cols - 1; /* * Convolve the view region with the overlap grid to determine the * total overlap of the view in all possible positions on the grid. * * When the view starts in a particular interval and is wider than the * interval, it can extend either rightward (by placing the left edge * of the view on the left edge of the interval) or leftward (by * placing the right edge of the view on the right edge of the * interval) into adjoining intervals. Likewise, when the view is wider * than the interval in which it starts, it can extend either upward * (by placing the bottom edge of the view on the bottom edge of the * interval) or downward (by placing the top edge of the view on the * top edge of the interval). All four possibilities produce different * overlap characteristics and need to be checked independently. * * If the view is no larger than the interval in which it starts, there * is no need to check multiple directions---the overlap will be the * same regardless of where in the interval the window is placed. * * The interval (and, when the view spans more than one interval, * directions in which it should extend) that produces the smallest * overlap with other windows will determine the view placement. */ for (int i = 0; i < nri; ++i) { for (int j = 0; j < nci; ++j) { /* * Search all directions, as a two-bit field, starting * from interval (i, j). */ for (int ii = 0; ii < 4; ++ii) { /* Left/right is determined by first bit */ bool rt = (ii & 0x1) == 0; /* Up/down is determined by second bit */ bool dn = (ii & 0x2) == 0; /* Track whether overlap comes from single region */ bool single = false; /* Compute overlap in specified direction */ int overlap = compute_overlap(&bmp, i, j, width, height, rt, dn, &single); /* Move on if overlap isn't reduced */ if (overlap >= min_overlap) { continue; } /* Place window in optimal direction */ min_overlap = overlap; if (rt) { /* Extend window right from left edge */ geometry->x = bmp.cols[j] + offset_x; } else { /* Extend window left from right edge */ geometry->x = bmp.cols[j + 1] - width + offset_x; } if (dn) { /* Extend window down from top edge */ geometry->y = bmp.rows[i] + offset_y; } else { /* Extend window up from bottom edge */ geometry->y = bmp.rows[i + 1] - height + offset_y; } /* If there is no overlap, the search is done. */ if (min_overlap <= 0) { goto final_placement; } /* * Skip multi-directional searches when the * view fits completely within one region. */ if (single) { break; } } } } final_placement: destroy_bitmap(&bmp); return true; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/regions.c���������������������������������������������������������������������������0000664�0000000�0000000�00000015070�14570443012�0015213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <float.h> #include <math.h> #include <string.h> #include <wlr/render/pixman.h> #include <wlr/types/wlr_scene.h> #include <wlr/util/box.h> #include <wlr/util/log.h> #include "common/graphic-helpers.h" #include "common/list.h" #include "common/mem.h" #include "input/keyboard.h" #include "labwc.h" #include "regions.h" #include "view.h" bool regions_should_snap(struct server *server) { if (server->input_mode != LAB_INPUT_STATE_MOVE || wl_list_empty(&rc.regions) || server->seat.region_prevent_snap) { return false; } struct wlr_keyboard *keyboard = &server->seat.keyboard_group->keyboard; return keyboard_any_modifiers_pressed(keyboard); } static void overlay_create(struct seat *seat) { assert(!seat->region_overlay.tree); struct server *server = seat->server; struct wlr_scene_tree *parent = wlr_scene_tree_create(&server->scene->tree); seat->region_overlay.tree = parent; wlr_scene_node_set_enabled(&parent->node, false); if (!wlr_renderer_is_pixman(server->renderer)) { /* Hardware assisted rendering: Half transparent overlay */ float color[4] = { 0.25, 0.25, 0.35, 0.5 }; seat->region_overlay.overlay = wlr_scene_rect_create(parent, 0, 0, color); } else { /* Software rendering: Outlines */ int line_width = server->theme->osd_border_width; float *colors[3] = { server->theme->osd_bg_color, server->theme->osd_label_text_color, server->theme->osd_bg_color }; seat->region_overlay.pixman_overlay = multi_rect_create(parent, colors, line_width); } } struct region * regions_from_name(const char *region_name, struct output *output) { assert(region_name); assert(output); struct region *region; wl_list_for_each(region, &output->regions, link) { if (!strcmp(region->name, region_name)) { return region; } } return NULL; } struct region * regions_from_cursor(struct server *server) { assert(server); double lx = server->seat.cursor->x; double ly = server->seat.cursor->y; struct wlr_output *wlr_output = wlr_output_layout_output_at( server->output_layout, lx, ly); struct output *output = output_from_wlr_output(server, wlr_output); if (!output) { return NULL; } double dist; double dist_min = DBL_MAX; struct region *closest_region = NULL; struct region *region; wl_list_for_each(region, &output->regions, link) { if (wlr_box_contains_point(®ion->geo, lx, ly)) { /* No need for sqrt((x1 - x2)^2 + (y1 - y2)^2) as we just compare */ dist = pow(region->center.x - lx, 2) + pow(region->center.y - ly, 2); if (dist < dist_min) { closest_region = region; dist_min = dist; } } } return closest_region; } void regions_show_overlay(struct view *view, struct seat *seat, struct region *region) { assert(view); assert(seat); assert(region); /* Don't show active region */ if (seat->region_active == region) { return; } if (!seat->region_overlay.tree) { overlay_create(seat); } /* Update overlay */ struct server *server = seat->server; struct wlr_scene_node *node = &seat->region_overlay.tree->node; if (!wlr_renderer_is_pixman(server->renderer)) { /* Hardware assisted rendering: Half transparent overlay */ wlr_scene_rect_set_size(seat->region_overlay.overlay, region->geo.width, region->geo.height); } else { /* Software rendering: Outlines */ multi_rect_set_size(seat->region_overlay.pixman_overlay, region->geo.width, region->geo.height); } if (node->parent != view->scene_tree->node.parent) { wlr_scene_node_reparent(node, view->scene_tree->node.parent); wlr_scene_node_place_below(node, &view->scene_tree->node); } wlr_scene_node_set_position(node, region->geo.x, region->geo.y); wlr_scene_node_set_enabled(node, true); seat->region_active = region; } void regions_hide_overlay(struct seat *seat) { assert(seat); if (!seat->region_active) { return; } struct server *server = seat->server; struct wlr_scene_node *node = &seat->region_overlay.tree->node; wlr_scene_node_set_enabled(node, false); if (node->parent != &server->scene->tree) { wlr_scene_node_reparent(node, &server->scene->tree); } seat->region_active = NULL; } void regions_reconfigure_output(struct output *output) { assert(output); /* Evacuate views and destroy current regions */ if (!wl_list_empty(&output->regions)) { regions_evacuate_output(output); regions_destroy(&output->server->seat, &output->regions); } /* Initialize regions from config */ struct region *region; wl_list_for_each(region, &rc.regions, link) { struct region *region_new = znew(*region_new); /* Create a copy */ region_new->output = output; region_new->name = xstrdup(region->name); region_new->percentage = region->percentage; wl_list_append(&output->regions, ®ion_new->link); } /* Update region geometries */ regions_update_geometry(output); } void regions_reconfigure(struct server *server) { struct output *output; /* Evacuate views and initialize regions from config */ wl_list_for_each(output, &server->outputs, link) { regions_reconfigure_output(output); } /* Tries to match the evacuated views to the new regions */ desktop_arrange_all_views(server); } void regions_update_geometry(struct output *output) { assert(output); struct region *region; struct wlr_box usable = output_usable_area_in_layout_coords(output); /* Update regions */ struct wlr_box *perc, *geo; wl_list_for_each(region, &output->regions, link) { geo = ®ion->geo; perc = ®ion->percentage; /* * Add percentages (x + width, y + height) before scaling * so that there is no gap between regions due to rounding * variations */ int left = usable.width * perc->x / 100; int right = usable.width * (perc->x + perc->width) / 100; int top = usable.height * perc->y / 100; int bottom = usable.height * (perc->y + perc->height) / 100; geo->x = usable.x + left; geo->y = usable.y + top; geo->width = right - left; geo->height = bottom - top; region->center.x = geo->x + geo->width / 2; region->center.y = geo->y + geo->height / 2; } } void regions_evacuate_output(struct output *output) { assert(output); struct view *view; wl_list_for_each(view, &output->server->views, link) { if (view->tiled_region && view->tiled_region->output == output) { view_evacuate_region(view); } } } void regions_destroy(struct seat *seat, struct wl_list *regions) { assert(regions); struct region *region, *region_tmp; wl_list_for_each_safe(region, region_tmp, regions, link) { wl_list_remove(®ion->link); zfree(region->name); if (seat && seat->region_active == region) { seat->region_active = NULL; } zfree(region); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/resistance.c������������������������������������������������������������������������0000664�0000000�0000000�00000011265�14570443012�0015707�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <limits.h> #include "common/border.h" #include "common/macros.h" #include "config/rcxml.h" #include "edges.h" #include "labwc.h" #include "resistance.h" #include "view.h" static void check_edge(int *next, struct edge current, struct edge target, struct edge oppose, struct edge align, int tolerance, bool lesser) { int cur = current.offset; int tgt = target.offset; int opp = oppose.offset; /* Ignore non-moving edges */ if (cur == tgt) { return; } /* * The edge defined by current and moving to target may encounter two * edges of another region: the opposing edge of the region is that in * the opposite orientation of the moving edge (i.e., left <-> right or * top <-> bottom); the aligned edge of the region is that in the same * orientation as the moving edge (i.e., left <->left, top <-> top, * right <-> right, bottom <-> bottom). * * Any opposing or aligned edge of a region is considered "valid" in * this search if the resist/attract zone (defined by tolerance) of * that edge contains the target position of the moving edge. */ /* Direction of motion for the edge */ const bool decreasing = tgt < cur; /* * Motion resists "entry" into the space of another window, but never * resist leaving it. Without edge attraction, this only happens when * the "leading" edge of a motion (top edge upward, bottom edge * downward, left edge leftward, right edge rightward) encounters an * opposing edge. If the motion is not of a leading edge, there is no * need to check for any resistance. * * However, if there is attraction, a "trailing" edge of a motion (top * edge downward, bottom edge upward, left edge rightward, right edge * leftward) may be grabbed by the opposing edge of another window as * it passes. Hence, trailing edges still need to be tested in * attractive cases. */ if (tolerance >= 0 && lesser != decreasing) { return; } /* Check the opposing edge */ bool valid = false; if (decreasing) { /* Check for decreasing movement across opposing edge */ const int lo = clipped_sub(opp, abs(tolerance)); const int hi = clipped_sub(opp, MIN(tolerance, 0)); valid = tgt >= lo && tgt < hi && cur >= opp; } else { /* Check for increasing movement across opposing edge */ const int lo = clipped_add(opp, MIN(tolerance, 0)); const int hi = clipped_add(opp, abs(tolerance)); valid = tgt > lo && tgt <= hi && cur <= opp; } if (valid && edges_traverse_edge(current, target, oppose)) { *next = edge_get_best(*next, opp, decreasing); } } static void check_edge_output(int *next, struct edge current, struct edge target, struct edge oppose, struct edge align, bool lesser) { check_edge(next, current, target, oppose, align, rc.screen_edge_strength, lesser); } static void check_edge_window(int *next, struct edge current, struct edge target, struct edge oppose, struct edge align, bool lesser) { check_edge(next, current, target, oppose, align, rc.window_edge_strength, lesser); } void resistance_move_apply(struct view *view, double *x, double *y) { assert(view); struct border next_edges; edges_initialize(&next_edges); struct wlr_box target = { .x = *x, .y = *y, .width = view->current.width, .height = view->current.height, }; if (rc.screen_edge_strength != 0) { /* Find any relevant output edges encountered by this move */ edges_find_outputs(&next_edges, view, target, NULL, check_edge_output, /* use_pending */ false); } if (rc.window_edge_strength != 0) { /* Find any relevant window edges encountered by this move */ edges_find_neighbors(&next_edges, view, target, NULL, check_edge_window, /* use_pending */ false, /* ignore_hidden */ true); } /* If any "best" edges were encountered during this move, snap motion */ edges_adjust_move_coords(view, next_edges, &target.x, &target.y, /* use_pending */ false); *x = target.x; *y = target.y; } void resistance_resize_apply(struct view *view, struct wlr_box *new_geom) { assert(view); assert(!view->shaded); struct border next_edges; edges_initialize(&next_edges); if (rc.screen_edge_strength != 0) { /* Find any relevant output edges encountered by this move */ edges_find_outputs(&next_edges, view, *new_geom, NULL, check_edge_output, /* use_pending */ false); } if (rc.window_edge_strength != 0) { /* Find any relevant window edges encountered by this move */ edges_find_neighbors(&next_edges, view, *new_geom, NULL, check_edge_window, /* use_pending */ false, /* ignore_hidden */ true); } /* If any "best" edges were encountered during this move, snap motion */ edges_adjust_resize_geom(view, next_edges, view->server->resize_edges, new_geom, /* use_pending */ false); } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/seat.c������������������������������������������������������������������������������0000664�0000000�0000000�00000052165�14570443012�0014507�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <stdbool.h> #include <strings.h> #include <wlr/backend/libinput.h> #include <wlr/types/wlr_input_device.h> #include <wlr/types/wlr_keyboard.h> #include <wlr/types/wlr_pointer.h> #include <wlr/types/wlr_touch.h> #include <wlr/util/log.h> #include "common/mem.h" #include "input/tablet.h" #include "input/tablet_pad.h" #include "input/input.h" #include "input/keyboard.h" #include "input/key-state.h" #include "labwc.h" #include "view.h" static void input_device_destroy(struct wl_listener *listener, void *data) { struct input *input = wl_container_of(listener, input, destroy); wl_list_remove(&input->link); wl_list_remove(&input->destroy.link); /* `struct keyboard` is derived and has some extra clean up to do */ if (input->wlr_input_device->type == WLR_INPUT_DEVICE_KEYBOARD) { struct keyboard *keyboard = (struct keyboard *)input; wl_list_remove(&keyboard->key.link); wl_list_remove(&keyboard->modifier.link); keyboard_cancel_keybind_repeat(keyboard); } free(input); } static enum lab_libinput_device_type device_type_from_wlr_device(struct wlr_input_device *wlr_input_device) { switch (wlr_input_device->type) { case WLR_INPUT_DEVICE_TOUCH: case WLR_INPUT_DEVICE_TABLET_TOOL: return LAB_LIBINPUT_DEVICE_TOUCH; default: break; } if (wlr_input_device->type == WLR_INPUT_DEVICE_POINTER && wlr_input_device_is_libinput(wlr_input_device)) { struct libinput_device *libinput_device = wlr_libinput_get_device_handle(wlr_input_device); if (libinput_device_config_tap_get_finger_count(libinput_device) > 0) { return LAB_LIBINPUT_DEVICE_TOUCHPAD; } } return LAB_LIBINPUT_DEVICE_NON_TOUCH; } /* * Get applicable profile (category) by matching first by name and secondly be * type (e.g. 'touch' and 'non-touch'). If not suitable match is found based on * those two criteria we fallback on 'default'. */ static struct libinput_category * get_category(struct wlr_input_device *device) { /* By name */ struct libinput_category *category; wl_list_for_each_reverse(category, &rc.libinput_categories, link) { if (category->name) { if (!strcasecmp(device->name, category->name)) { return category; } } } /* By type */ enum lab_libinput_device_type type = device_type_from_wlr_device(device); wl_list_for_each_reverse(category, &rc.libinput_categories, link) { if (category->type == type) { return category; } } /* Use default profile as a fallback */ return libinput_category_get_default(); } static void configure_libinput(struct wlr_input_device *wlr_input_device) { /* * TODO: We do not check any return values for the various * libinput_device_config_*_set_*() calls. It would * be nice if we could inform the users via log file * that some libinput setting could not be applied. * * TODO: We are currently using int32_t with -1 as default * to desribe the not-configured state. This is not * really optimal as we can't properly deal with * enum values that are 0. After some discussion via * IRC the best way forward seem to be to use a * uint32_t instead and UINT32_MAX as indicator for * a not-configured state. This allows to properly * test the enum being a member of a bitset via * mask & value == value. All libinput enums are * way below UINT32_MAX. */ if (!wlr_input_device) { wlr_log(WLR_ERROR, "no wlr_input_device"); return; } if (!wlr_input_device_is_libinput(wlr_input_device)) { return; } struct libinput_device *libinput_dev = wlr_libinput_get_device_handle(wlr_input_device); if (!libinput_dev) { wlr_log(WLR_ERROR, "no libinput_dev"); return; } struct libinput_category *dc = get_category(wlr_input_device); /* * The above logic should have always matched SOME category * (the default category if none other took precedence) */ assert(dc); if (libinput_device_config_tap_get_finger_count(libinput_dev) <= 0) { wlr_log(WLR_INFO, "tap unavailable"); } else { wlr_log(WLR_INFO, "tap configured"); libinput_device_config_tap_set_enabled(libinput_dev, dc->tap); libinput_device_config_tap_set_button_map(libinput_dev, dc->tap_button_map); } if (libinput_device_config_tap_get_finger_count(libinput_dev) <= 0 || dc->tap_and_drag < 0) { wlr_log(WLR_INFO, "tap-and-drag not configured"); } else { wlr_log(WLR_INFO, "tap-and-drag configured"); libinput_device_config_tap_set_drag_enabled( libinput_dev, dc->tap_and_drag); } if (libinput_device_config_tap_get_finger_count(libinput_dev) <= 0 || dc->drag_lock < 0) { wlr_log(WLR_INFO, "drag lock not configured"); } else { wlr_log(WLR_INFO, "drag lock configured"); libinput_device_config_tap_set_drag_lock_enabled( libinput_dev, dc->drag_lock); } if (libinput_device_config_scroll_has_natural_scroll(libinput_dev) <= 0 || dc->natural_scroll < 0) { wlr_log(WLR_INFO, "natural scroll not configured"); } else { wlr_log(WLR_INFO, "natural scroll configured"); libinput_device_config_scroll_set_natural_scroll_enabled( libinput_dev, dc->natural_scroll); } if (libinput_device_config_left_handed_is_available(libinput_dev) <= 0 || dc->left_handed < 0) { wlr_log(WLR_INFO, "left-handed mode not configured"); } else { wlr_log(WLR_INFO, "left-handed mode configured"); libinput_device_config_left_handed_set(libinput_dev, dc->left_handed); } if (libinput_device_config_accel_is_available(libinput_dev) == 0) { wlr_log(WLR_INFO, "pointer acceleration unavailable"); } else { wlr_log(WLR_INFO, "pointer acceleration configured"); if (dc->pointer_speed > -1) { libinput_device_config_accel_set_speed(libinput_dev, dc->pointer_speed); } if (dc->accel_profile > 0) { libinput_device_config_accel_set_profile(libinput_dev, dc->accel_profile); } } if (libinput_device_config_middle_emulation_is_available(libinput_dev) == 0 || dc->middle_emu < 0) { wlr_log(WLR_INFO, "middle emulation not configured"); } else { wlr_log(WLR_INFO, "middle emulation configured"); libinput_device_config_middle_emulation_set_enabled( libinput_dev, dc->middle_emu); } if (libinput_device_config_dwt_is_available(libinput_dev) == 0 || dc->dwt < 0) { wlr_log(WLR_INFO, "dwt not configured"); } else { wlr_log(WLR_INFO, "dwt configured"); libinput_device_config_dwt_set_enabled(libinput_dev, dc->dwt); } if ((dc->click_method != LIBINPUT_CONFIG_CLICK_METHOD_NONE && (libinput_device_config_click_get_methods(libinput_dev) & dc->click_method) == 0) || dc->click_method < 0) { wlr_log(WLR_INFO, "click method not configured"); } else { wlr_log(WLR_INFO, "click method configured"); /* * Note, the documentation claims that: * > [...] The device may require changing to a neutral state * > first before activating the new method. * * However, just setting the method seems to work without * issues. */ libinput_device_config_click_set_method(libinput_dev, dc->click_method); } if ((dc->send_events_mode != LIBINPUT_CONFIG_SEND_EVENTS_ENABLED && (libinput_device_config_send_events_get_modes(libinput_dev) & dc->send_events_mode) == 0) || dc->send_events_mode < 0) { wlr_log(WLR_INFO, "send events mode not configured"); } else { wlr_log(WLR_INFO, "send events mode configured"); libinput_device_config_send_events_set_mode(libinput_dev, dc->send_events_mode); } } static struct wlr_output * output_by_name(struct server *server, const char *name) { assert(name); struct output *output; wl_list_for_each(output, &server->outputs, link) { if (!strcasecmp(output->wlr_output->name, name)) { return output->wlr_output; } } return NULL; } static void map_input_to_output(struct seat *seat, struct wlr_input_device *dev, char *output_name) { struct wlr_output *output = NULL; if (output_name) { output = output_by_name(seat->server, output_name); } wlr_cursor_map_input_to_output(seat->cursor, dev, output); wlr_cursor_map_input_to_region(seat->cursor, dev, NULL); } static void map_pointer_to_output(struct seat *seat, struct wlr_input_device *dev) { struct wlr_pointer *pointer = wlr_pointer_from_input_device(dev); wlr_log(WLR_INFO, "map pointer to output %s\n", pointer->output_name); map_input_to_output(seat, dev, pointer->output_name); } static struct input * new_pointer(struct seat *seat, struct wlr_input_device *dev) { struct input *input = znew(*input); input->wlr_input_device = dev; configure_libinput(dev); wlr_cursor_attach_input_device(seat->cursor, dev); /* In support of running with WLR_WL_OUTPUTS set to >=2 */ if (dev->type == WLR_INPUT_DEVICE_POINTER) { map_pointer_to_output(seat, dev); } return input; } static struct input * new_keyboard(struct seat *seat, struct wlr_input_device *device, bool virtual) { struct wlr_keyboard *kb = wlr_keyboard_from_input_device(device); struct keyboard *keyboard = znew(*keyboard); keyboard->base.wlr_input_device = device; keyboard->wlr_keyboard = kb; keyboard->is_virtual = virtual; wlr_keyboard_set_keymap(kb, seat->keyboard_group->keyboard.keymap); /* * This needs to be before wlr_keyboard_group_add_keyboard(). * For some reason, wlroots takes the modifier state from the * new keyboard and syncs it to the others in the group, rather * than the other way around. */ keyboard_set_numlock(kb); if (!virtual) { wlr_keyboard_group_add_keyboard(seat->keyboard_group, kb); } keyboard_setup_handlers(keyboard); wlr_seat_set_keyboard(seat->seat, kb); return (struct input *)keyboard; } static void map_touch_to_output(struct seat *seat, struct wlr_input_device *dev) { struct wlr_touch *touch = wlr_touch_from_input_device(dev); char *touch_config_output_name = NULL; struct touch_config_entry *config_entry = touch_find_config_for_device(touch->base.name); if (config_entry) { touch_config_output_name = config_entry->output_name; } char *output_name = touch->output_name ? touch->output_name : touch_config_output_name; wlr_log(WLR_INFO, "map touch to output %s\n", output_name ? output_name : "unknown"); map_input_to_output(seat, dev, output_name); } static struct input * new_touch(struct seat *seat, struct wlr_input_device *dev) { struct input *input = znew(*input); input->wlr_input_device = dev; configure_libinput(dev); wlr_cursor_attach_input_device(seat->cursor, dev); /* In support of running with WLR_WL_OUTPUTS set to >=2 */ map_touch_to_output(seat, dev); return input; } static struct input * new_tablet(struct seat *seat, struct wlr_input_device *dev) { struct input *input = znew(*input); input->wlr_input_device = dev; tablet_init(seat, dev); wlr_cursor_attach_input_device(seat->cursor, dev); wlr_log(WLR_INFO, "map tablet to output %s\n", rc.tablet.output_name); map_input_to_output(seat, dev, rc.tablet.output_name); return input; } static struct input * new_tablet_pad(struct seat *seat, struct wlr_input_device *dev) { struct input *input = znew(*input); input->wlr_input_device = dev; tablet_pad_init(seat, dev); return input; } static void seat_update_capabilities(struct seat *seat) { struct input *input = NULL; uint32_t caps = 0; wl_list_for_each(input, &seat->inputs, link) { switch (input->wlr_input_device->type) { case WLR_INPUT_DEVICE_KEYBOARD: caps |= WL_SEAT_CAPABILITY_KEYBOARD; break; case WLR_INPUT_DEVICE_POINTER: caps |= WL_SEAT_CAPABILITY_POINTER; break; case WLR_INPUT_DEVICE_TOUCH: caps |= WL_SEAT_CAPABILITY_TOUCH; break; default: break; } } wlr_seat_set_capabilities(seat->seat, caps); } static void seat_add_device(struct seat *seat, struct input *input) { input->seat = seat; input->destroy.notify = input_device_destroy; wl_signal_add(&input->wlr_input_device->events.destroy, &input->destroy); wl_list_insert(&seat->inputs, &input->link); seat_update_capabilities(seat); } static void new_input_notify(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, new_input); struct wlr_input_device *device = data; struct input *input = NULL; switch (device->type) { case WLR_INPUT_DEVICE_KEYBOARD: input = new_keyboard(seat, device, false); break; case WLR_INPUT_DEVICE_POINTER: input = new_pointer(seat, device); break; case WLR_INPUT_DEVICE_TOUCH: input = new_touch(seat, device); break; case WLR_INPUT_DEVICE_TABLET_TOOL: input = new_tablet(seat, device); break; case WLR_INPUT_DEVICE_TABLET_PAD: input = new_tablet_pad(seat, device); break; default: wlr_log(WLR_INFO, "unsupported input device"); return; } seat_add_device(seat, input); } static void new_virtual_pointer(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, 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->pointer.base; struct input *input = new_pointer(seat, device); device->data = input; seat_add_device(seat, input); if (event->suggested_output) { wlr_cursor_map_input_to_output(seat->cursor, device, event->suggested_output); } } static void new_virtual_keyboard(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, virtual_keyboard_new); struct wlr_virtual_keyboard_v1 *virtual_keyboard = data; struct wlr_input_device *device = &virtual_keyboard->keyboard.base; struct input *input = new_keyboard(seat, device, true); device->data = input; seat_add_device(seat, input); } static void focus_change_notify(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, focus_change); struct wlr_seat_keyboard_focus_change_event *event = data; struct server *server = seat->server; struct wlr_surface *surface = event->new_surface; struct view *view = surface ? view_from_wlr_surface(surface) : NULL; /* Prevent focus switch to layershell client from updating view state */ if (surface && wlr_layer_surface_v1_try_from_wlr_surface(surface)) { return; } /* * If an xwayland-unmanaged surface was focused belonging to the * same application as the focused view, allow the view to remain * active. This fixes an issue with menus immediately closing in * some X11 apps (try LibreOffice with SAL_USE_VCLPLUGIN=gen). */ if (!view && server->active_view && event->new_surface && view_is_related(server->active_view, event->new_surface)) { return; } if (view != server->active_view) { if (server->active_view) { view_set_activated(server->active_view, false); } if (view) { view_set_activated(view, true); } server->active_view = view; } } void seat_init(struct server *server) { struct seat *seat = &server->seat; seat->server = server; seat->seat = wlr_seat_create(server->wl_display, "seat0"); if (!seat->seat) { wlr_log(WLR_ERROR, "cannot allocate seat"); exit(EXIT_FAILURE); } wl_list_init(&seat->touch_points); wl_list_init(&seat->constraint_commit.link); wl_list_init(&seat->inputs); seat->new_input.notify = new_input_notify; wl_signal_add(&server->backend->events.new_input, &seat->new_input); seat->focus_change.notify = focus_change_notify; wl_signal_add(&seat->seat->keyboard_state.events.focus_change, &seat->focus_change); seat->virtual_pointer = wlr_virtual_pointer_manager_v1_create( server->wl_display); wl_signal_add(&seat->virtual_pointer->events.new_virtual_pointer, &seat->virtual_pointer_new); seat->virtual_pointer_new.notify = new_virtual_pointer; seat->virtual_keyboard = wlr_virtual_keyboard_manager_v1_create( server->wl_display); wl_signal_add(&seat->virtual_keyboard->events.new_virtual_keyboard, &seat->virtual_keyboard_new); seat->virtual_keyboard_new.notify = new_virtual_keyboard; seat->cursor = wlr_cursor_create(); if (!seat->cursor) { wlr_log(WLR_ERROR, "unable to create cursor"); exit(EXIT_FAILURE); } wlr_cursor_attach_output_layout(seat->cursor, server->output_layout); input_handlers_init(seat); } void seat_finish(struct server *server) { struct seat *seat = &server->seat; wl_list_remove(&seat->new_input.link); wl_list_remove(&seat->focus_change.link); struct input *input, *next; wl_list_for_each_safe(input, next, &seat->inputs, link) { input_device_destroy(&input->destroy, NULL); } input_handlers_finish(seat); } static void configure_keyboard(struct seat *seat, struct input *input) { struct wlr_input_device *device = input->wlr_input_device; assert(device->type == WLR_INPUT_DEVICE_KEYBOARD); struct keyboard *keyboard = (struct keyboard *)input; struct wlr_keyboard *kb = wlr_keyboard_from_input_device(device); keyboard_configure(seat, kb, keyboard->is_virtual); } /* This is called on SIGHUP (generally in response to labwc --reconfigure */ void seat_reconfigure(struct server *server) { struct seat *seat = &server->seat; struct input *input; wl_list_for_each(input, &seat->inputs, link) { switch (input->wlr_input_device->type) { case WLR_INPUT_DEVICE_KEYBOARD: configure_keyboard(seat, input); break; case WLR_INPUT_DEVICE_POINTER: configure_libinput(input->wlr_input_device); map_pointer_to_output(seat, input->wlr_input_device); break; case WLR_INPUT_DEVICE_TOUCH: configure_libinput(input->wlr_input_device); map_touch_to_output(seat, input->wlr_input_device); break; case WLR_INPUT_DEVICE_TABLET_TOOL: map_input_to_output(seat, input->wlr_input_device, rc.tablet.output_name); break; default: break; } } } static void seat_focus(struct seat *seat, struct wlr_surface *surface, bool is_lock_surface) { /* * Respect session lock. This check is critical, DO NOT REMOVE. * It should also come before the !surface condition, or the * lock screen may lose focus and become impossible to unlock. */ struct server *server = seat->server; if (server->session_lock && !is_lock_surface) { return; } if (!surface) { wlr_seat_keyboard_notify_clear_focus(seat->seat); return; } /* Respect input inhibit (also used by some lock screens) */ if (input_inhibit_blocks_surface(seat, surface->resource)) { return; } /* * Key events associated with keybindings (both pressed and released) * are not sent to clients. When changing surface-focus it is therefore * important not to send the keycodes of _all_ pressed keys, but only * those that were actually _sent_ to clients (that is, those that were * not bound). */ uint32_t *pressed_sent_keycodes = key_state_pressed_sent_keycodes(); int nr_pressed_sent_keycodes = key_state_nr_pressed_sent_keycodes(); struct wlr_keyboard *kb = &seat->keyboard_group->keyboard; wlr_seat_keyboard_notify_enter(seat->seat, surface, pressed_sent_keycodes, nr_pressed_sent_keycodes, &kb->modifiers); struct wlr_pointer_constraint_v1 *constraint = wlr_pointer_constraints_v1_constraint_for_surface(server->constraints, surface, seat->seat); constrain_cursor(server, constraint); } void seat_focus_surface(struct seat *seat, struct wlr_surface *surface) { /* Respect layer-shell exclusive keyboard-interactivity. */ if (seat->focused_layer && seat->focused_layer->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { return; } seat_focus(seat, surface, /*is_lock_surface*/ false); } void seat_focus_lock_surface(struct seat *seat, struct wlr_surface *surface) { seat_focus(seat, surface, /*is_lock_surface*/ true); } void seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer) { if (!layer) { seat->focused_layer = NULL; desktop_focus_topmost_view(seat->server); return; } seat_focus(seat, layer->surface, /*is_lock_surface*/ false); if (layer->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { seat->focused_layer = layer; } } static void pressed_surface_destroy(struct wl_listener *listener, void *data) { struct seat *seat = wl_container_of(listener, seat, pressed_surface_destroy); /* * Using data directly prevents 'unused variable' * warning when compiling without asserts */ assert(data == seat->pressed.surface); seat_reset_pressed(seat); } void seat_set_pressed(struct seat *seat, struct view *view, struct wlr_scene_node *node, struct wlr_surface *surface, struct wlr_surface *toplevel, uint32_t resize_edges) { assert(view || surface); seat_reset_pressed(seat); seat->pressed.view = view; seat->pressed.node = node; seat->pressed.surface = surface; seat->pressed.toplevel = toplevel; seat->pressed.resize_edges = resize_edges; if (surface) { seat->pressed_surface_destroy.notify = pressed_surface_destroy; wl_signal_add(&surface->events.destroy, &seat->pressed_surface_destroy); } } void seat_reset_pressed(struct seat *seat) { if (seat->pressed.surface) { wl_list_remove(&seat->pressed_surface_destroy.link); } seat->pressed.view = NULL; seat->pressed.node = NULL; seat->pressed.surface = NULL; seat->pressed.toplevel = NULL; seat->pressed.resize_edges = 0; } void seat_output_layout_changed(struct seat *seat) { struct input *input = NULL; wl_list_for_each(input, &seat->inputs, link) { switch (input->wlr_input_device->type) { case WLR_INPUT_DEVICE_POINTER: map_pointer_to_output(seat, input->wlr_input_device); break; case WLR_INPUT_DEVICE_TOUCH: map_touch_to_output(seat, input->wlr_input_device); break; case WLR_INPUT_DEVICE_TABLET_TOOL: map_input_to_output(seat, input->wlr_input_device, rc.tablet.output_name); break; default: break; } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/server.c����������������������������������������������������������������������������0000664�0000000�0000000�00000040377�14570443012�0015063�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include "config.h" #include <signal.h> #include <sys/wait.h> #include <wlr/backend/headless.h> #include <wlr/backend/multi.h> #include <wlr/types/wlr_data_control_v1.h> #include <wlr/types/wlr_export_dmabuf_v1.h> #include <wlr/types/wlr_fractional_scale_v1.h> #include <wlr/types/wlr_gamma_control_v1.h> #include <wlr/types/wlr_input_inhibitor.h> #include <wlr/types/wlr_presentation_time.h> #include <wlr/types/wlr_primary_selection_v1.h> #include <wlr/types/wlr_screencopy_v1.h> #include <wlr/types/wlr_single_pixel_buffer_v1.h> #include <wlr/types/wlr_viewporter.h> #if HAVE_XWAYLAND #include <wlr/xwayland.h> #endif #include "drm-lease-v1-protocol.h" #include "config/rcxml.h" #include "config/session.h" #include "decorations.h" #include "idle.h" #include "labwc.h" #include "layers.h" #include "menu/menu.h" #include "regions.h" #include "resize_indicator.h" #include "theme.h" #include "view.h" #include "workspaces.h" #include "xwayland.h" #define LAB_WLR_COMPOSITOR_VERSION 5 #define LAB_WLR_FRACTIONAL_SCALE_V1_VERSION 1 static struct wlr_compositor *compositor; static struct wl_event_source *sighup_source; static struct wl_event_source *sigint_source; static struct wl_event_source *sigterm_source; static struct server *g_server; static void reload_config_and_theme(void) { rcxml_finish(); rcxml_read(rc.config_file); theme_finish(g_server->theme); theme_init(g_server->theme, rc.theme_name); struct view *view; wl_list_for_each(view, &g_server->views, link) { view_reload_ssd(view); } menu_reconfigure(g_server); seat_reconfigure(g_server); regions_reconfigure(g_server); resize_indicator_reconfigure(g_server); kde_server_decoration_update_default(); } static int handle_sighup(int signal, void *data) { session_environment_init(); reload_config_and_theme(); return 0; } static int handle_sigterm(int signal, void *data) { struct wl_display *display = data; wl_display_terminate(display); return 0; } static void seat_inhibit_input(struct seat *seat, struct wl_client *active_client) { seat->active_client_while_inhibited = active_client; if (seat->focused_layer && active_client != wl_resource_get_client(seat->focused_layer->resource)) { seat_set_focus_layer(seat, NULL); } struct wlr_surface *previous_kb_surface = seat->seat->keyboard_state.focused_surface; if (previous_kb_surface && active_client != wl_resource_get_client(previous_kb_surface->resource)) { seat_focus_surface(seat, NULL); /* keyboard focus */ } struct wlr_seat_client *previous_ptr_client = seat->seat->pointer_state.focused_client; if (previous_ptr_client && previous_ptr_client->client != active_client) { wlr_seat_pointer_clear_focus(seat->seat); } } static void seat_disinhibit_input(struct seat *seat) { seat->active_client_while_inhibited = NULL; /* * Triggers a refocus of the topmost surface layer if necessary * TODO: Make layer surface focus per-output based on cursor position */ output_update_all_usable_areas(seat->server, /*layout_changed*/ false); } static void handle_input_inhibit(struct wl_listener *listener, void *data) { wlr_log(WLR_INFO, "activate input inhibit"); struct server *server = wl_container_of(listener, server, input_inhibit_activate); seat_inhibit_input(&server->seat, server->input_inhibit->active_client); } static void handle_input_disinhibit(struct wl_listener *listener, void *data) { wlr_log(WLR_INFO, "deactivate input inhibit"); struct server *server = wl_container_of(listener, server, input_inhibit_deactivate); seat_disinhibit_input(&server->seat); } static void handle_drm_lease_request(struct wl_listener *listener, void *data) { struct wlr_drm_lease_request_v1 *req = data; struct wlr_drm_lease_v1 *lease = wlr_drm_lease_request_v1_grant(req); if (!lease) { wlr_log(WLR_ERROR, "Failed to grant lease request"); wlr_drm_lease_request_v1_reject(req); return; } for (size_t i = 0; i < req->n_connectors; ++i) { struct output *output = req->connectors[i]->output->data; if (!output) { continue; } wlr_output_enable(output->wlr_output, false); wlr_output_commit(output->wlr_output); wlr_output_layout_remove(output->server->output_layout, output->wlr_output); output->scene_output = NULL; output->leased = true; } } static bool server_global_filter(const struct wl_client *client, const struct wl_global *global, void *data) { const struct wl_interface *iface = wl_global_get_interface(global); struct server *server = (struct server *)data; /* Silence unused var compiler warnings */ (void)iface; (void)server; #if HAVE_XWAYLAND struct wl_client *xwayland_client = server->xwayland ? server->xwayland->server->client : NULL; if (xwayland_client && client == xwayland_client) { /* * Filter out wp_drm_lease_device_v1 for now as it is resulting in * issues with Xwayland applications lagging over time. * * https://github.com/labwc/labwc/issues/553 */ if (!strcmp(iface->name, wp_drm_lease_device_v1_interface.name)) { return false; } } #endif return true; } /* * This message is intended to help users who are trying labwc on * clean/minimalist systems without existing Desktop Environments (possibly * through Virtual Managers) where polkit is missing or GPU drivers do not * exist, in the hope that it will reduce the time required to get labwc running * and prevent some troubleshooting steps. */ static const char helpful_seat_error_message[] = "\n" "Some friendly trouble-shooting help\n" "===================================\n" "\n" "If a seat could not be created, this may be caused by lack of permission to the\n" "seat, input and video groups. If you are using a systemd setup, try installing\n" "polkit (sometimes called policykit-1). For other setups, search your OS/Distro's\n" "documentation on how to use seatd, elogind or similar. This is likely to involve\n" "manually adding users to groups.\n" "\n" "If the above does not work, try running with `WLR_RENDERER=pixman labwc` in\n" "order to use the software rendering fallback\n"; void server_init(struct server *server) { server->wl_display = wl_display_create(); if (!server->wl_display) { wlr_log(WLR_ERROR, "cannot allocate a wayland display"); exit(EXIT_FAILURE); } wl_display_set_global_filter(server->wl_display, server_global_filter, server); /* Catch SIGHUP */ struct wl_event_loop *event_loop = NULL; event_loop = wl_display_get_event_loop(server->wl_display); sighup_source = wl_event_loop_add_signal( event_loop, SIGHUP, handle_sighup, NULL); sigint_source = wl_event_loop_add_signal( event_loop, SIGINT, handle_sigterm, server->wl_display); sigterm_source = wl_event_loop_add_signal( event_loop, SIGTERM, handle_sigterm, server->wl_display); server->wl_event_loop = event_loop; /* * Prevent wayland clients that request the X11 clipboard but closing * their read fd prematurely to crash labwc because of the unhandled * SIGPIPE signal. It is caused by wlroots trying to write the X11 * clipboard data to the closed fd of the wayland client. * See https://github.com/labwc/labwc/issues/890#issuecomment-1524962995 * for a reproducer involving xclip and wl-paste | head -c 1. */ signal(SIGPIPE, SIG_IGN); /* * The backend is a feature which abstracts the underlying input and * output hardware. The autocreate option will choose the most suitable * backend based on the current environment, such as opening an x11 * window if an x11 server is running. */ server->backend = wlr_backend_autocreate( server->wl_display, &server->session); if (!server->backend) { wlr_log(WLR_ERROR, "unable to create backend"); fprintf(stderr, helpful_seat_error_message); exit(EXIT_FAILURE); } /* Create headless backend to enable adding virtual outputs later on */ server->headless.backend = wlr_headless_backend_create(server->wl_display); if (!server->headless.backend) { wlr_log(WLR_ERROR, "unable to create headless backend"); exit(EXIT_FAILURE); } wlr_multi_backend_add(server->backend, server->headless.backend); /* * If we don't populate headless backend with a virtual output (that we * create and immediately destroy), then virtual outputs being added * later do not work properly when overlaid on real output. Content is * drawn on the virtual output, but not drawn on the real output. */ wlr_output_destroy(wlr_headless_add_output(server->headless.backend, 0, 0)); /* * Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The * user can also specify a renderer using the WLR_RENDERER env var. * The renderer is responsible for defining the various pixel formats it * supports for shared memory, this configures that for clients. */ server->renderer = wlr_renderer_autocreate(server->backend); if (!server->renderer) { wlr_log(WLR_ERROR, "unable to create renderer"); exit(EXIT_FAILURE); } wlr_renderer_init_wl_display(server->renderer, server->wl_display); /* * Autocreates an allocator for us. The allocator is the bridge between * the renderer and the backend. It handles the buffer creation, * allowing wlroots to render onto the screen */ server->allocator = wlr_allocator_autocreate( server->backend, server->renderer); if (!server->allocator) { wlr_log(WLR_ERROR, "unable to create allocator"); exit(EXIT_FAILURE); } wl_list_init(&server->views); wl_list_init(&server->unmanaged_surfaces); server->ssd_hover_state = ssd_hover_state_new(); server->scene = wlr_scene_create(); if (!server->scene) { wlr_log(WLR_ERROR, "unable to create scene"); exit(EXIT_FAILURE); } /* * The order in which the scene-trees below are created determines the * z-order for nodes which cover the whole work-area. For per-output * scene-trees, see new_output_notify() in src/output.c * * | Type | Scene Tree | Per Output | Example * | ----------------- | ---------------- | ---------- | ------- * | ext-session | lock-screen | Yes | swaylock * | layer-shell | layer-popups | Yes | * | layer-shell | overlay-layer | Yes | * | layer-shell | top-layer | Yes | waybar * | server | labwc-menus | No | * | xwayland-OR | unmanaged | No | dmenu * | xdg-popups | xdg-popups | No | * | toplevels windows | always-on-top | No | * | toplevels windows | normal | No | firefox * | toplevels windows | always-on-bottom | No | pcmanfm-qt --desktop * | layer-shell | bottom-layer | Yes | waybar * | layer-shell | background-layer | Yes | swaybg */ server->view_tree_always_on_bottom = wlr_scene_tree_create(&server->scene->tree); server->view_tree = wlr_scene_tree_create(&server->scene->tree); server->view_tree_always_on_top = wlr_scene_tree_create(&server->scene->tree); server->xdg_popup_tree = wlr_scene_tree_create(&server->scene->tree); #if HAVE_XWAYLAND server->unmanaged_tree = wlr_scene_tree_create(&server->scene->tree); #endif server->menu_tree = wlr_scene_tree_create(&server->scene->tree); workspaces_init(server); output_init(server); /* * Create some hands-off wlroots interfaces. The compositor is * necessary for clients to allocate surfaces and the data device * manager handles the clipboard. Each of these wlroots interfaces has * room for you to dig your fingers in and play with their behavior if * you want. */ compositor = wlr_compositor_create(server->wl_display, LAB_WLR_COMPOSITOR_VERSION, server->renderer); if (!compositor) { wlr_log(WLR_ERROR, "unable to create the wlroots compositor"); exit(EXIT_FAILURE); } wlr_subcompositor_create(server->wl_display); struct wlr_data_device_manager *device_manager = NULL; device_manager = wlr_data_device_manager_create(server->wl_display); if (!device_manager) { wlr_log(WLR_ERROR, "unable to create data device manager"); exit(EXIT_FAILURE); } /* * Empirically, primary selection doesn't work with Gtk apps unless the * device manager is one of the earliest globals to be advertised. All * credit to Wayfire for discovering this, though their symptoms * (crash) are not the same as ours (silently does nothing). When adding * more globals above this line it would be as well to check that * middle-button paste still works with any Gtk app of your choice * * https://wayfire.org/2020/08/04/Wayfire-0-5.html */ wlr_primary_selection_v1_device_manager_create(server->wl_display); seat_init(server); xdg_shell_init(server); kde_server_decoration_init(server); xdg_server_decoration_init(server); struct wlr_presentation *presentation = wlr_presentation_create(server->wl_display, server->backend); if (!presentation) { wlr_log(WLR_ERROR, "unable to create presentation interface"); exit(EXIT_FAILURE); } wlr_scene_set_presentation(server->scene, presentation); wlr_export_dmabuf_manager_v1_create(server->wl_display); wlr_screencopy_manager_v1_create(server->wl_display); wlr_data_control_manager_v1_create(server->wl_display); wlr_viewporter_create(server->wl_display); wlr_single_pixel_buffer_manager_v1_create(server->wl_display); wlr_fractional_scale_manager_v1_create(server->wl_display, LAB_WLR_FRACTIONAL_SCALE_V1_VERSION); idle_manager_create(server->wl_display, server->seat.seat); server->relative_pointer_manager = wlr_relative_pointer_manager_v1_create( server->wl_display); server->constraints = wlr_pointer_constraints_v1_create( server->wl_display); server->new_constraint.notify = create_constraint; wl_signal_add(&server->constraints->events.new_constraint, &server->new_constraint); server->input_inhibit = wlr_input_inhibit_manager_create(server->wl_display); if (!server->input_inhibit) { wlr_log(WLR_ERROR, "unable to create input inhibit manager"); exit(EXIT_FAILURE); } wl_signal_add(&server->input_inhibit->events.activate, &server->input_inhibit_activate); server->input_inhibit_activate.notify = handle_input_inhibit; wl_signal_add(&server->input_inhibit->events.deactivate, &server->input_inhibit_deactivate); server->input_inhibit_deactivate.notify = handle_input_disinhibit; server->foreign_toplevel_manager = wlr_foreign_toplevel_manager_v1_create(server->wl_display); session_lock_init(server); server->drm_lease_manager = wlr_drm_lease_v1_manager_create( server->wl_display, server->backend); if (server->drm_lease_manager) { server->drm_lease_request.notify = handle_drm_lease_request; wl_signal_add(&server->drm_lease_manager->events.request, &server->drm_lease_request); } else { wlr_log(WLR_DEBUG, "Failed to create wlr_drm_lease_device_v1"); wlr_log(WLR_INFO, "VR will not be available"); } server->output_power_manager_v1 = wlr_output_power_manager_v1_create(server->wl_display); server->output_power_manager_set_mode.notify = handle_output_power_manager_set_mode; wl_signal_add(&server->output_power_manager_v1->events.set_mode, &server->output_power_manager_set_mode); server->tearing_control = wlr_tearing_control_manager_v1_create(server->wl_display, 1); server->tearing_new_object.notify = new_tearing_hint; wl_signal_add(&server->tearing_control->events.new_object, &server->tearing_new_object); layers_init(server); #if HAVE_XWAYLAND xwayland_server_init(server, compositor); #endif /* used when handling SIGHUP */ g_server = server; } void server_start(struct server *server) { /* Add a Unix socket to the Wayland display. */ const char *socket = wl_display_add_socket_auto(server->wl_display); if (!socket) { wlr_log_errno(WLR_ERROR, "unable to open wayland socket"); exit(EXIT_FAILURE); } /* * Start the backend. This will enumerate outputs and inputs, become * the DRM master, etc */ if (!wlr_backend_start(server->backend)) { wlr_log(WLR_ERROR, "unable to start the wlroots backend"); exit(EXIT_FAILURE); } if (setenv("WAYLAND_DISPLAY", socket, true) < 0) { wlr_log_errno(WLR_ERROR, "unable to set WAYLAND_DISPLAY"); } else { wlr_log(WLR_DEBUG, "WAYLAND_DISPLAY=%s", socket); } } void server_finish(struct server *server) { #if HAVE_XWAYLAND xwayland_server_finish(server); #endif if (sighup_source) { wl_event_source_remove(sighup_source); } wl_display_destroy_clients(server->wl_display); seat_finish(server); wlr_output_layout_destroy(server->output_layout); wl_display_destroy(server->wl_display); /* TODO: clean up various scene_tree nodes */ workspaces_destroy(server); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/session-lock.c����������������������������������������������������������������������0000664�0000000�0000000�00000022743�14570443012�0016163�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include "common/mem.h" #include "labwc.h" static struct wl_listener new_lock; static struct wl_listener manager_destroy; static struct wlr_session_lock_manager_v1 *wlr_session_lock_manager; static struct server *g_server; struct session_lock_output { struct wlr_scene_tree *tree; struct wlr_scene_rect *background; struct session_lock *lock; struct output *output; struct wlr_session_lock_surface_v1 *surface; struct wl_list link; /* session_lock.outputs */ struct wl_listener destroy; struct wl_listener commit; struct wl_listener surface_destroy; struct wl_listener surface_map; }; static void focus_surface(struct session_lock *lock, struct wlr_surface *focused) { lock->focused = focused; seat_focus_lock_surface(&g_server->seat, focused); } static void refocus_output(struct session_lock_output *output) { /* Try to focus another session-lock surface */ if (output->lock->focused != output->surface->surface) { return; } struct session_lock_output *iter; wl_list_for_each(iter, &output->lock->session_lock_outputs, link) { if (iter == output || !iter->surface || !iter->surface->surface) { continue; } if (iter->surface->surface->mapped) { focus_surface(output->lock, iter->surface->surface); return; } } focus_surface(output->lock, NULL); } static void handle_surface_map(struct wl_listener *listener, void *data) { struct session_lock_output *surf = wl_container_of(listener, surf, surface_map); if (!surf->lock->focused) { focus_surface(surf->lock, surf->surface->surface); } } static void handle_surface_destroy(struct wl_listener *listener, void *data) { struct session_lock_output *output = wl_container_of(listener, output, surface_destroy); refocus_output(output); assert(output->surface); output->surface = NULL; wl_list_remove(&output->surface_destroy.link); wl_list_remove(&output->surface_map.link); } static void lock_output_reconfigure(struct session_lock_output *output) { struct wlr_box box; wlr_output_layout_get_box(g_server->output_layout, output->output->wlr_output, &box); wlr_scene_rect_set_size(output->background, box.width, box.height); if (output->surface) { wlr_session_lock_surface_v1_configure(output->surface, box.width, box.height); } } static void handle_new_surface(struct wl_listener *listener, void *data) { struct session_lock *lock = wl_container_of(listener, lock, new_surface); struct wlr_session_lock_surface_v1 *lock_surface = data; struct output *output = lock_surface->output->data; struct session_lock_output *lock_output; wl_list_for_each(lock_output, &lock->session_lock_outputs, link) { if (lock_output->output == output) { goto found_lock_output; } } wlr_log(WLR_ERROR, "new lock surface, but no output"); /* TODO: Consider improving security by handling this better */ return; found_lock_output: lock_output->surface = lock_surface; wlr_scene_subsurface_tree_create(lock_output->tree, lock_surface->surface); lock_output->surface_destroy.notify = handle_surface_destroy; wl_signal_add(&lock_surface->events.destroy, &lock_output->surface_destroy); lock_output->surface_map.notify = handle_surface_map; wl_signal_add(&lock_surface->surface->events.map, &lock_output->surface_map); lock_output_reconfigure(lock_output); } static void session_lock_output_destroy(struct session_lock_output *output) { if (output->surface) { refocus_output(output); wl_list_remove(&output->surface_destroy.link); wl_list_remove(&output->surface_map.link); } wl_list_remove(&output->commit.link); wl_list_remove(&output->destroy.link); wl_list_remove(&output->link); free(output); } static void handle_destroy(struct wl_listener *listener, void *data) { struct session_lock_output *output = wl_container_of(listener, output, destroy); session_lock_output_destroy(output); } static void handle_commit(struct wl_listener *listener, void *data) { struct wlr_output_event_commit *event = data; struct session_lock_output *output = wl_container_of(listener, output, commit); uint32_t require_reconfigure = WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_SCALE | WLR_OUTPUT_STATE_TRANSFORM; if (event->state->committed & require_reconfigure) { lock_output_reconfigure(output); } } static void align_session_lock_tree(struct output *output) { struct wlr_box box; wlr_output_layout_get_box(g_server->output_layout, output->wlr_output, &box); wlr_scene_node_set_position(&output->session_lock_tree->node, box.x, box.y); } void session_lock_output_create(struct session_lock *lock, struct output *output) { struct session_lock_output *lock_output = znew(*lock_output); if (!lock_output) { wlr_log(WLR_ERROR, "session-lock: out of memory"); goto exit_session; } struct wlr_scene_tree *tree = wlr_scene_tree_create(output->session_lock_tree); if (!tree) { wlr_log(WLR_ERROR, "session-lock: wlr_scene_tree_create()"); free(lock_output); goto exit_session; } /* * The ext-session-lock protocol says that the compositor should blank * all outputs with an opaque color such that their normal content is * fully hidden */ float *black = (float[4]) { 0.f, 0.f, 0.f, 1.f }; struct wlr_scene_rect *background = wlr_scene_rect_create(tree, 0, 0, black); if (!background) { wlr_log(WLR_ERROR, "session-lock: wlr_scene_rect_create()"); wlr_scene_node_destroy(&tree->node); free(lock_output); goto exit_session; } align_session_lock_tree(output); lock_output->output = output; lock_output->tree = tree; lock_output->background = background; lock_output->lock = lock; lock_output->destroy.notify = handle_destroy; wl_signal_add(&tree->node.events.destroy, &lock_output->destroy); lock_output->commit.notify = handle_commit; wl_signal_add(&output->wlr_output->events.commit, &lock_output->commit); lock_output_reconfigure(lock_output); wl_list_insert(&lock->session_lock_outputs, &lock_output->link); return; exit_session: /* TODO: Consider a better - but secure - way to deal with this */ wlr_log(WLR_ERROR, "out of memory"); exit(EXIT_FAILURE); } static void session_lock_destroy(struct session_lock *lock) { struct session_lock_output *lock_output, *next; wl_list_for_each_safe(lock_output, next, &lock->session_lock_outputs, link) { wlr_scene_node_destroy(&lock_output->tree->node); } if (g_server->session_lock == lock) { g_server->session_lock = NULL; } if (!lock->abandoned) { wl_list_remove(&lock->destroy.link); wl_list_remove(&lock->unlock.link); wl_list_remove(&lock->new_surface.link); } free(lock); } static void handle_unlock(struct wl_listener *listener, void *data) { struct session_lock *lock = wl_container_of(listener, lock, unlock); session_lock_destroy(lock); desktop_focus_topmost_view(g_server); } static void handle_session_lock_destroy(struct wl_listener *listener, void *data) { struct session_lock *lock = wl_container_of(listener, lock, destroy); float *black = (float[4]) { 0.f, 0.f, 0.f, 1.f }; struct session_lock_output *lock_output; wl_list_for_each(lock_output, &lock->session_lock_outputs, link) { wlr_scene_rect_set_color(lock_output->background, black); } lock->abandoned = true; wl_list_remove(&lock->destroy.link); wl_list_remove(&lock->unlock.link); wl_list_remove(&lock->new_surface.link); } static void handle_new_session_lock(struct wl_listener *listener, void *data) { struct wlr_session_lock_v1 *lock = data; /* One already exists */ if (g_server->session_lock) { if (g_server->session_lock->abandoned) { wlr_log(WLR_INFO, "replacing abandoned lock"); session_lock_destroy(g_server->session_lock); } else { wlr_log(WLR_ERROR, "session already locked"); wlr_session_lock_v1_destroy(lock); return; } } struct session_lock *session_lock = znew(*session_lock); if (!session_lock) { wlr_log(WLR_ERROR, "session-lock: out of memory"); wlr_session_lock_v1_destroy(lock); return; } wl_list_init(&session_lock->session_lock_outputs); struct output *output; wl_list_for_each(output, &g_server->outputs, link) { session_lock_output_create(session_lock, output); } session_lock->new_surface.notify = handle_new_surface; wl_signal_add(&lock->events.new_surface, &session_lock->new_surface); session_lock->unlock.notify = handle_unlock; wl_signal_add(&lock->events.unlock, &session_lock->unlock); session_lock->destroy.notify = handle_session_lock_destroy; wl_signal_add(&lock->events.destroy, &session_lock->destroy); wlr_session_lock_v1_send_locked(lock); g_server->session_lock = session_lock; } static void handle_manager_destroy(struct wl_listener *listener, void *data) { if (g_server->session_lock) { session_lock_destroy(g_server->session_lock); } wl_list_remove(&new_lock.link); wl_list_remove(&manager_destroy.link); wlr_session_lock_manager = NULL; } void session_lock_init(struct server *server) { g_server = server; wlr_session_lock_manager = wlr_session_lock_manager_v1_create(server->wl_display); new_lock.notify = handle_new_session_lock; wl_signal_add(&wlr_session_lock_manager->events.new_lock, &new_lock); manager_destroy.notify = handle_manager_destroy; wl_signal_add(&wlr_session_lock_manager->events.destroy, &manager_destroy); } void session_lock_update_for_layout_change(void) { if (!g_server->session_lock) { return; } struct output *output; wl_list_for_each(output, &g_server->outputs, link) { align_session_lock_tree(output); } struct session_lock *lock = g_server->session_lock; struct session_lock_output *lock_output; wl_list_for_each(lock_output, &lock->session_lock_outputs, link) { lock_output_reconfigure(lock_output); } } �����������������������������labwc-0.7.1/src/snap.c������������������������������������������������������������������������������0000664�0000000�0000000�00000016421�14570443012�0014507�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <limits.h> #include <wlr/util/box.h> #include "common/border.h" #include "common/macros.h" #include "config/rcxml.h" #include "edges.h" #include "labwc.h" #include "resistance.h" #include "snap.h" #include "view.h" static void check_edge(int *next, struct edge current, struct edge target, struct edge oppose, struct edge align, bool lesser) { int cur = current.offset; int tgt = target.offset; int opp = oppose.offset; int aln = align.offset; if (cur == tgt) { return; } /* * The edge defined by current and moving to target may encounter two * edges of another region: the opposing edge of the region is that in * the opposite orientation of the moving edge (i.e., left <-> right or * top <-> bottom); the aligned edge of the region is that in the same * orientation as the moving edge (i.e., left <-> left, top <-> top, * right <-> right, bottom <-> bottom). * * Any opposing or aligned edge of a region is considered "valid" in * this search if the edge sits between the current and target * positions of the moving edge (including the target position itself). */ /* Direction of motion for the edge */ const bool decreasing = tgt < cur; /* Check the opposing edge */ if ((tgt <= opp && opp < cur) || (cur < opp && opp <= tgt)) { *next = edge_get_best(*next, opp, decreasing); } /* Check the aligned edge */ if ((tgt <= aln && aln < cur) || (cur < aln && aln <= tgt)) { *next = edge_get_best(*next, aln, decreasing); } } void snap_move_to_edge(struct view *view, enum view_edge direction, bool snap_to_windows, int *dx, int *dy) { assert(view); *dx = 0; *dy = 0; struct output *output = view->output; if (!output_is_usable(output)) { wlr_log(WLR_ERROR, "view has no output, not snapping to edge"); return; } struct wlr_box target = view->pending; struct border ssd = ssd_thickness(view); struct wlr_box usable = output_usable_area_in_layout_coords(output); /* * First try to move the view to the relevant edge of its output. If * the view is off-screen, such a move might actually run contrary to * the commanded direction (e.g., a view off the screen to the left, * when moved to the left edge, will actually move rightward). This is * counter-intuitive, so abandon any such movements. * * In addition, any view that is already at the desired screen edge * needs no further consideration. */ switch (direction) { case VIEW_EDGE_LEFT: target.x = usable.x + ssd.left + rc.gap; if (target.x >= view->pending.x) { return; } break; case VIEW_EDGE_RIGHT: target.x = usable.x + usable.width - rc.gap - target.width - ssd.right; if (target.x <= view->pending.x) { return; } break; case VIEW_EDGE_UP: target.y = usable.y + ssd.top + rc.gap; if (target.y >= view->pending.y) { return; } break; case VIEW_EDGE_DOWN: target.y = usable.y + usable.height - rc.gap - ssd.bottom - view_effective_height(view, /* use_pending */ true); if (target.y <= view->pending.y) { return; } break; default: return; } /* * Because the target has been updated to put the view at the edge of * an output, there is no need to check snapping to output edges. If * snapping to view is desired, check for snapping against any view on * the same output. */ if (snap_to_windows) { struct border next_edges; edges_initialize(&next_edges); edges_find_neighbors(&next_edges, view, target, output, check_edge, /* use_pending */ true, /* ignore_hidden */ false); /* If any "best" edges were encountered, limit motion */ edges_adjust_move_coords(view, next_edges, &target.x, &target.y, /* use_pending */ true); } *dx = target.x - view->pending.x; *dy = target.y - view->pending.y; } void snap_grow_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo) { assert(view); assert(!view->shaded); *geo = view->pending; struct output *output = view->output; if (!output_is_usable(output)) { wlr_log(WLR_ERROR, "view has no output, not growing to edge"); return; } struct border ssd = ssd_thickness(view); struct wlr_box usable = output_usable_area_in_layout_coords(output); uint32_t resize_edges; /* First try to grow the view to the relevant edge of its output. */ switch (direction) { case VIEW_EDGE_LEFT: geo->x = usable.x + ssd.left + rc.gap; geo->width = view->pending.x + view->pending.width - geo->x; resize_edges = WLR_EDGE_LEFT; break; case VIEW_EDGE_RIGHT: geo->width = usable.x + usable.width - rc.gap - ssd.right - view->pending.x; resize_edges = WLR_EDGE_RIGHT; break; case VIEW_EDGE_UP: geo->y = usable.y + ssd.top + rc.gap; geo->height = view->pending.y + view->pending.height - geo->y; resize_edges = WLR_EDGE_TOP; break; case VIEW_EDGE_DOWN: geo->height = usable.y + usable.height - rc.gap - ssd.bottom - view->pending.y; resize_edges = WLR_EDGE_BOTTOM; break; default: return; } /* No grow operation should ever shrink the view */ if (geo->width < view->pending.width || geo->height < view->pending.height) { *geo = view->pending; return; } /* If the view doesn't change size, there is no need for snap checks */ if (geo->width == view->pending.width && geo->height == view->pending.height) { *geo = view->pending; return; } struct border next_edges; edges_initialize(&next_edges); /* Limit motion to any intervening edge of other views on this output */ edges_find_neighbors(&next_edges, view, *geo, output, check_edge, /* use_pending */ true, /* ignore_hidden */ false); edges_adjust_resize_geom(view, next_edges, resize_edges, geo, /* use_pending */ true); } void snap_shrink_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo) { assert(view); assert(!view->shaded); *geo = view->pending; uint32_t resize_edges; /* * First shrink the view along the relevant edge. The maximum shrink * allowed is half the current size, but the window must also meet * minimum size requirements. */ switch (direction) { case VIEW_EDGE_RIGHT: geo->width = MAX(geo->width / 2, LAB_MIN_VIEW_WIDTH); geo->x = view->pending.x + view->pending.width - geo->width; resize_edges = WLR_EDGE_LEFT; break; case VIEW_EDGE_LEFT: geo->width = MAX(geo->width / 2, LAB_MIN_VIEW_WIDTH); resize_edges = WLR_EDGE_RIGHT; break; case VIEW_EDGE_DOWN: geo->height = MAX(geo->height / 2, LAB_MIN_VIEW_HEIGHT); geo->y = view->pending.y + view->pending.height - geo->height; resize_edges = WLR_EDGE_TOP; break; case VIEW_EDGE_UP: geo->height = MAX(geo->height / 2, LAB_MIN_VIEW_HEIGHT); resize_edges = WLR_EDGE_BOTTOM; break; default: return; } /* If the view doesn't change size, abandon the shrink */ if (geo->width == view->pending.width && geo->height == view->pending.height) { *geo = view->pending; return; } struct border next_edges; edges_initialize(&next_edges); /* Snap to output edges if the moving edge started off-screen */ edges_find_outputs(&next_edges, view, *geo, view->output, check_edge, /* use_pending */ true); /* Limit motion to any intervening edge of ther views on this output */ edges_find_neighbors(&next_edges, view, *geo, view->output, check_edge, /* use_pending */ true, /* ignore_hidden */ false); edges_adjust_resize_geom(view, next_edges, resize_edges, geo, /* use_pending */ true); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/ssd/��������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14570443012�0014167�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/ssd/meson.build���������������������������������������������������������������������0000664�0000000�0000000�00000000206�14570443012�0016327�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc_sources += files( 'resize_indicator.c', 'ssd.c', 'ssd_part.c', 'ssd_titlebar.c', 'ssd_border.c', 'ssd_extents.c', ) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/ssd/resize_indicator.c��������������������������������������������������������������0000664�0000000�0000000�00000013744�14570443012�0017701�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <wlr/types/wlr_scene.h> #include <wlr/util/box.h> #include <wlr/util/log.h> #include "common/macros.h" #include "common/scaled_font_buffer.h" #include "labwc.h" #include "resize_indicator.h" #include "view.h" static void resize_indicator_reconfigure_view(struct resize_indicator *indicator) { assert(indicator->tree); struct theme *theme = rc.theme; indicator->height = font_height(&rc.font_osd) + 2 * theme->osd_window_switcher_padding + 2 * theme->osd_border_width; /* Static positions */ wlr_scene_node_set_position(&indicator->background->node, theme->osd_border_width, theme->osd_border_width); wlr_scene_node_set_position(&indicator->text->scene_buffer->node, theme->osd_border_width + theme->osd_window_switcher_padding, theme->osd_border_width + theme->osd_window_switcher_padding); /* Colors */ wlr_scene_rect_set_color(indicator->border, theme->osd_border_color); wlr_scene_rect_set_color(indicator->background, theme->osd_bg_color); } static void resize_indicator_init(struct view *view) { assert(view); struct resize_indicator *indicator = &view->resize_indicator; assert(!indicator->tree); indicator->tree = wlr_scene_tree_create(view->scene_tree); indicator->border = wlr_scene_rect_create( indicator->tree, 0, 0, rc.theme->osd_border_color); indicator->background = wlr_scene_rect_create( indicator->tree, 0, 0, rc.theme->osd_bg_color); indicator->text = scaled_font_buffer_create(indicator->tree); wlr_scene_node_set_enabled(&indicator->tree->node, false); resize_indicator_reconfigure_view(indicator); } static bool wants_indicator(struct view *view) { assert(view); if (rc.resize_indicator == LAB_RESIZE_INDICATOR_NON_PIXEL) { if (view->server->input_mode != LAB_INPUT_STATE_RESIZE) { return false; } struct view_size_hints hints = view_get_size_hints(view); if (hints.width_inc && hints.height_inc) { return true; } } return rc.resize_indicator == LAB_RESIZE_INDICATOR_ALWAYS; } void resize_indicator_reconfigure(struct server *server) { struct view *view; wl_list_for_each(view, &server->views, link) { struct resize_indicator *indicator = &view->resize_indicator; if (indicator->tree) { resize_indicator_reconfigure_view(indicator); } if (view != server->grabbed_view) { continue; } /* This view is currently in an interactive move/resize operation */ if (indicator->tree && indicator->tree->node.enabled) { /* Indicator was active while reloading the config */ if (wants_indicator(view)) { /* Apply new font setting */ resize_indicator_update(view); } else { /* Indicator was disabled in config */ resize_indicator_hide(view); } } else if (wants_indicator(view)) { /* Indicator not yet active */ resize_indicator_show(view); } } } static void resize_indicator_set_size(struct resize_indicator *indicator, int width) { assert(indicator->tree); /* We are not using a width-cache-early-out here to allow for theme changes */ indicator->width = width + 2 * rc.theme->osd_window_switcher_padding + 2 * rc.theme->osd_border_width; wlr_scene_rect_set_size(indicator->border, indicator->width, indicator->height); wlr_scene_rect_set_size(indicator->background, indicator->width - 2 * rc.theme->osd_border_width, indicator->height - 2 * rc.theme->osd_border_width); } void resize_indicator_show(struct view *view) { assert(view); if (!wants_indicator(view)) { return; } struct resize_indicator *indicator = &view->resize_indicator; if (!indicator->tree) { /* Lazy initialize */ resize_indicator_init(view); } wlr_scene_node_raise_to_top(&indicator->tree->node); wlr_scene_node_set_enabled(&indicator->tree->node, true); resize_indicator_update(view); } void resize_indicator_update(struct view *view) { assert(view); assert(view == view->server->grabbed_view); if (!wants_indicator(view)) { return; } struct resize_indicator *indicator = &view->resize_indicator; if (!indicator->tree) { /* * Future-proofs this routine: * * This can only happen when either src/interactive.c * stops calling resize_indicator_show(), there is a * bug in this file or resize_indicator_reconfigure() * gets changed. */ wlr_log(WLR_INFO, "Warning: resize_indicator has to use a fallback path"); resize_indicator_show(view); } char text[32]; /* 12345 x 12345 would be 13 chars + 1 null byte */ int eff_height = view_effective_height(view, /* use_pending */ false); int eff_width = view->current.width; switch (view->server->input_mode) { case LAB_INPUT_STATE_RESIZE: ; /* works around "a label can only be part of a statement" */ struct view_size_hints hints = view_get_size_hints(view); snprintf(text, sizeof(text), "%d x %d", MAX(0, eff_width - hints.base_width) / MAX(1, hints.width_inc), MAX(0, eff_height - hints.base_height) / MAX(1, hints.height_inc)); break; case LAB_INPUT_STATE_MOVE: ; /* works around "a label can only be part of a statement" */ struct border margin = ssd_get_margin(view->ssd); snprintf(text, sizeof(text), "%d , %d", view->current.x - margin.left, view->current.y - margin.top); break; default: wlr_log(WLR_ERROR, "Invalid input mode for indicator update %u", view->server->input_mode); return; } /* Let the indicator change width as required by the content */ int width = font_width(&rc.font_osd, text); /* font_extents() adds 4 pixels to the calculated width */ width -= 4; resize_indicator_set_size(indicator, width); /* Center the indicator in the window */ wlr_scene_node_set_position(&indicator->tree->node, (eff_width - indicator->width) / 2, (eff_height - indicator->height) / 2); scaled_font_buffer_update(indicator->text, text, width, &rc.font_osd, rc.theme->osd_label_text_color, NULL /* const char *arrow */); } void resize_indicator_hide(struct view *view) { assert(view); struct resize_indicator *indicator = &view->resize_indicator; if (!indicator->tree) { return; } wlr_scene_node_set_enabled(&indicator->tree->node, false); } ����������������������������labwc-0.7.1/src/ssd/ssd.c���������������������������������������������������������������������������0000664�0000000�0000000�00000024405�14570443012�0015131�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * Helpers for view server side decorations * * Copyright (C) Johan Malm 2020-2021 */ #include <assert.h> #include "common/mem.h" #include "common/scene-helpers.h" #include "labwc.h" #include "ssd-internal.h" #include "theme.h" #include "view.h" struct border ssd_thickness(struct view *view) { assert(view); /* * Check preconditions for displaying SSD. Note that this * needs to work even before ssd_create() has been called. * * For that reason we are not using the .enabled state of * the titlebar node here but rather check for the view * boolean. If we were to use the .enabled state this would * cause issues on Reconfigure events with views which were * in border-only deco mode as view->ssd would only be set * after ssd_create() returns. */ if (!view->ssd_enabled || view->fullscreen) { return (struct border){ 0 }; } struct theme *theme = view->server->theme; if (view->maximized == VIEW_AXIS_BOTH) { struct border thickness = { 0 }; if (!view->ssd_titlebar_hidden) { thickness.top += theme->title_height; } return thickness; } struct border thickness = { .top = theme->title_height + theme->border_width, .bottom = theme->border_width, .left = theme->border_width, .right = theme->border_width, }; if (view->ssd_titlebar_hidden) { thickness.top -= theme->title_height; } return thickness; } struct wlr_box ssd_max_extents(struct view *view) { assert(view); struct border border = ssd_thickness(view); int eff_width = view->current.width; int eff_height = view_effective_height(view, /* use_pending */ false); return (struct wlr_box){ .x = view->current.x - border.left, .y = view->current.y - border.top, .width = eff_width + border.left + border.right, .height = eff_height + border.top + border.bottom, }; } bool ssd_is_button(enum ssd_part_type type) { return type == LAB_SSD_BUTTON_CLOSE || type == LAB_SSD_BUTTON_MAXIMIZE || type == LAB_SSD_BUTTON_ICONIFY || type == LAB_SSD_BUTTON_WINDOW_MENU; } enum ssd_part_type ssd_get_part_type(const struct ssd *ssd, struct wlr_scene_node *node) { if (!node) { return LAB_SSD_NONE; } else if (node->type == WLR_SCENE_NODE_BUFFER && lab_wlr_surface_from_node(node)) { return LAB_SSD_CLIENT; } else if (!ssd) { return LAB_SSD_NONE; } const struct wl_list *part_list = NULL; struct wlr_scene_tree *grandparent = node->parent ? node->parent->node.parent : NULL; struct wlr_scene_tree *greatgrandparent = grandparent ? grandparent->node.parent : NULL; /* active titlebar */ if (node->parent == ssd->titlebar.active.tree) { part_list = &ssd->titlebar.active.parts; } else if (grandparent == ssd->titlebar.active.tree) { part_list = &ssd->titlebar.active.parts; } else if (greatgrandparent == ssd->titlebar.active.tree) { part_list = &ssd->titlebar.active.parts; /* extents */ } else if (node->parent == ssd->extents.tree) { part_list = &ssd->extents.parts; /* active border */ } else if (node->parent == ssd->border.active.tree) { part_list = &ssd->border.active.parts; /* inactive titlebar */ } else if (node->parent == ssd->titlebar.inactive.tree) { part_list = &ssd->titlebar.inactive.parts; } else if (grandparent == ssd->titlebar.inactive.tree) { part_list = &ssd->titlebar.inactive.parts; } else if (greatgrandparent == ssd->titlebar.inactive.tree) { part_list = &ssd->titlebar.inactive.parts; /* inactive border */ } else if (node->parent == ssd->border.inactive.tree) { part_list = &ssd->border.inactive.parts; } if (part_list) { struct ssd_part *part; wl_list_for_each(part, part_list, link) { if (node == part->node) { return part->type; } } } return LAB_SSD_NONE; } enum ssd_part_type ssd_at(const struct ssd *ssd, struct wlr_scene *scene, double lx, double ly) { assert(scene); double sx, sy; struct wlr_scene_node *node = wlr_scene_node_at( &scene->tree.node, lx, ly, &sx, &sy); return ssd_get_part_type(ssd, node); } uint32_t ssd_resize_edges(enum ssd_part_type type) { switch (type) { case LAB_SSD_PART_TOP: return WLR_EDGE_TOP; case LAB_SSD_PART_RIGHT: return WLR_EDGE_RIGHT; case LAB_SSD_PART_BOTTOM: return WLR_EDGE_BOTTOM; case LAB_SSD_PART_LEFT: return WLR_EDGE_LEFT; case LAB_SSD_PART_CORNER_TOP_LEFT: return WLR_EDGE_TOP | WLR_EDGE_LEFT; case LAB_SSD_PART_CORNER_TOP_RIGHT: return WLR_EDGE_RIGHT | WLR_EDGE_TOP; case LAB_SSD_PART_CORNER_BOTTOM_RIGHT: return WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT; case LAB_SSD_PART_CORNER_BOTTOM_LEFT: return WLR_EDGE_BOTTOM | WLR_EDGE_LEFT; default: return WLR_EDGE_NONE; } } struct ssd * ssd_create(struct view *view, bool active) { assert(view); struct ssd *ssd = znew(*ssd); ssd->view = view; ssd->tree = wlr_scene_tree_create(view->scene_tree); wlr_scene_node_lower_to_bottom(&ssd->tree->node); ssd->titlebar.height = view->server->theme->title_height; ssd_extents_create(ssd); ssd_border_create(ssd); ssd_titlebar_create(ssd); if (view->ssd_titlebar_hidden) { /* Ensure we keep the old state on Reconfigure or when exiting fullscreen */ ssd_titlebar_hide(ssd); } ssd->margin = ssd_thickness(view); ssd_set_active(ssd, active); ssd_enable_keybind_inhibit_indicator(ssd, view->inhibits_keybinds); ssd->state.geometry = view->current; return ssd; } struct border ssd_get_margin(const struct ssd *ssd) { return ssd ? ssd->margin : (struct border){ 0 }; } void ssd_update_margin(struct ssd *ssd) { if (!ssd) { return; } ssd->margin = ssd_thickness(ssd->view); } void ssd_update_geometry(struct ssd *ssd) { if (!ssd) { return; } struct wlr_box cached = ssd->state.geometry; struct wlr_box current = ssd->view->current; int eff_width = current.width; int eff_height = view_effective_height(ssd->view, /* use_pending */ false); if (eff_width == cached.width && eff_height == cached.height) { if (current.x != cached.x || current.y != cached.y) { /* Dynamically resize extents based on position and usable_area */ ssd_extents_update(ssd); ssd->state.geometry = current; } bool maximized = (ssd->view->maximized == VIEW_AXIS_BOTH); if (ssd->state.was_maximized != maximized) { ssd_border_update(ssd); ssd_titlebar_update(ssd); /* * Not strictly necessary as ssd_titlebar_update() * already sets state.was_maximized but to future * proof this a bit we also set it here again. */ ssd->state.was_maximized = maximized; } return; } ssd_extents_update(ssd); ssd_border_update(ssd); ssd_titlebar_update(ssd); ssd->state.geometry = current; } void ssd_titlebar_hide(struct ssd *ssd) { if (!ssd || !ssd->titlebar.tree->node.enabled) { return; } wlr_scene_node_set_enabled(&ssd->titlebar.tree->node, false); ssd->titlebar.height = 0; ssd_border_update(ssd); ssd_extents_update(ssd); ssd->margin = ssd_thickness(ssd->view); } void ssd_destroy(struct ssd *ssd) { if (!ssd) { return; } /* Maybe reset hover view */ struct view *view = ssd->view; struct ssd_hover_state *hover_state; hover_state = view->server->ssd_hover_state; if (hover_state->view == view) { hover_state->view = NULL; hover_state->button = NULL; } /* Destroy subcomponents */ ssd_titlebar_destroy(ssd); ssd_border_destroy(ssd); ssd_extents_destroy(ssd); wlr_scene_node_destroy(&ssd->tree->node); free(ssd); } bool ssd_part_contains(enum ssd_part_type whole, enum ssd_part_type candidate) { if (whole == candidate) { return true; } if (whole == LAB_SSD_PART_TITLEBAR) { return candidate >= LAB_SSD_BUTTON_CLOSE && candidate <= LAB_SSD_PART_TITLE; } if (whole == LAB_SSD_PART_TITLE) { /* "Title" includes blank areas of "Titlebar" as well */ return candidate >= LAB_SSD_PART_TITLEBAR && candidate <= LAB_SSD_PART_TITLE; } if (whole == LAB_SSD_FRAME) { return candidate >= LAB_SSD_BUTTON_CLOSE && candidate <= LAB_SSD_CLIENT; } if (whole == LAB_SSD_PART_TOP) { return candidate == LAB_SSD_PART_CORNER_TOP_LEFT || candidate == LAB_SSD_PART_CORNER_BOTTOM_LEFT; } if (whole == LAB_SSD_PART_RIGHT) { return candidate == LAB_SSD_PART_CORNER_TOP_RIGHT || candidate == LAB_SSD_PART_CORNER_BOTTOM_RIGHT; } if (whole == LAB_SSD_PART_BOTTOM) { return candidate == LAB_SSD_PART_CORNER_BOTTOM_RIGHT || candidate == LAB_SSD_PART_CORNER_BOTTOM_LEFT; } if (whole == LAB_SSD_PART_LEFT) { return candidate == LAB_SSD_PART_CORNER_TOP_LEFT || candidate == LAB_SSD_PART_CORNER_BOTTOM_LEFT; } return false; } void ssd_set_active(struct ssd *ssd, bool active) { if (!ssd) { return; } wlr_scene_node_set_enabled(&ssd->border.active.tree->node, active); wlr_scene_node_set_enabled(&ssd->titlebar.active.tree->node, active); wlr_scene_node_set_enabled(&ssd->border.inactive.tree->node, !active); wlr_scene_node_set_enabled(&ssd->titlebar.inactive.tree->node, !active); } void ssd_enable_shade(struct ssd *ssd, bool enable) { if (!ssd) { return; } ssd_border_update(ssd); wlr_scene_node_set_enabled(&ssd->extents.tree->node, !enable); } void ssd_enable_keybind_inhibit_indicator(struct ssd *ssd, bool enable) { if (!ssd) { return; } float *color = enable ? rc.theme->window_toggled_keybinds_color : rc.theme->window_active_border_color; struct ssd_part *part = ssd_get_part(&ssd->border.active.parts, LAB_SSD_PART_TOP); struct wlr_scene_rect *rect = wlr_scene_rect_from_node(part->node); wlr_scene_rect_set_color(rect, color); } struct ssd_hover_state * ssd_hover_state_new(void) { return znew(struct ssd_hover_state); } enum ssd_part_type ssd_button_get_type(const struct ssd_button *button) { return button ? button->type : LAB_SSD_NONE; } struct view * ssd_button_get_view(const struct ssd_button *button) { return button ? button->view : NULL; } bool ssd_debug_is_root_node(const struct ssd *ssd, struct wlr_scene_node *node) { if (!ssd || !node) { return false; } return node == &ssd->tree->node; } const char * ssd_debug_get_node_name(const struct ssd *ssd, struct wlr_scene_node *node) { if (!ssd || !node) { return NULL; } if (node == &ssd->tree->node) { return "view->ssd"; } if (node == &ssd->titlebar.active.tree->node) { return "titlebar.active"; } if (node == &ssd->titlebar.inactive.tree->node) { return "titlebar.inactive"; } if (node == &ssd->border.active.tree->node) { return "border.active"; } if (node == &ssd->border.inactive.tree->node) { return "border.inactive"; } if (node == &ssd->extents.tree->node) { return "extents"; } return NULL; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/ssd/ssd_border.c��������������������������������������������������������������������0000664�0000000�0000000�00000010310�14570443012�0016454�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include "common/scene-helpers.h" #include "labwc.h" #include "ssd-internal.h" #include "theme.h" #include "view.h" #define FOR_EACH_STATE(ssd, tmp) FOR_EACH(tmp, \ &(ssd)->border.active, \ &(ssd)->border.inactive) void ssd_border_create(struct ssd *ssd) { assert(ssd); assert(!ssd->border.tree); struct view *view = ssd->view; struct theme *theme = view->server->theme; int width = view->current.width; int height = view_effective_height(view, /* use_pending */ false); int full_width = width + 2 * theme->border_width; float *color; struct wlr_scene_tree *parent; struct ssd_sub_tree *subtree; ssd->border.tree = wlr_scene_tree_create(ssd->tree); wlr_scene_node_set_position(&ssd->border.tree->node, -theme->border_width, 0); FOR_EACH_STATE(ssd, subtree) { subtree->tree = wlr_scene_tree_create(ssd->border.tree); parent = subtree->tree; if (subtree == &ssd->border.active) { color = theme->window_active_border_color; } else { color = theme->window_inactive_border_color; wlr_scene_node_set_enabled(&parent->node, false); } wl_list_init(&subtree->parts); add_scene_rect(&subtree->parts, LAB_SSD_PART_LEFT, parent, theme->border_width, height, 0, 0, color); add_scene_rect(&subtree->parts, LAB_SSD_PART_RIGHT, parent, theme->border_width, height, theme->border_width + width, 0, color); add_scene_rect(&subtree->parts, LAB_SSD_PART_BOTTOM, parent, full_width, theme->border_width, 0, height, color); add_scene_rect(&subtree->parts, LAB_SSD_PART_TOP, parent, width - 2 * SSD_BUTTON_WIDTH, theme->border_width, theme->border_width + SSD_BUTTON_WIDTH, -(ssd->titlebar.height + theme->border_width), color); } FOR_EACH_END if (view->maximized == VIEW_AXIS_BOTH) { wlr_scene_node_set_enabled(&ssd->border.tree->node, false); } } void ssd_border_update(struct ssd *ssd) { assert(ssd); assert(ssd->border.tree); struct view *view = ssd->view; if (view->maximized == VIEW_AXIS_BOTH && ssd->border.tree->node.enabled) { /* Disable borders on maximize */ wlr_scene_node_set_enabled(&ssd->border.tree->node, false); ssd->margin = ssd_thickness(ssd->view); } if (view->maximized == VIEW_AXIS_BOTH) { return; } else if (!ssd->border.tree->node.enabled) { /* And re-enabled them when unmaximized */ wlr_scene_node_set_enabled(&ssd->border.tree->node, true); ssd->margin = ssd_thickness(ssd->view); } struct theme *theme = view->server->theme; int width = view->current.width; int height = view_effective_height(view, /* use_pending */ false); int full_width = width + 2 * theme->border_width; struct ssd_part *part; struct wlr_scene_rect *rect; struct ssd_sub_tree *subtree; FOR_EACH_STATE(ssd, subtree) { wl_list_for_each(part, &subtree->parts, link) { rect = wlr_scene_rect_from_node(part->node); switch (part->type) { case LAB_SSD_PART_LEFT: wlr_scene_rect_set_size(rect, theme->border_width, height); continue; case LAB_SSD_PART_RIGHT: wlr_scene_rect_set_size(rect, theme->border_width, height); wlr_scene_node_set_position(part->node, theme->border_width + width, 0); continue; case LAB_SSD_PART_BOTTOM: wlr_scene_rect_set_size(rect, full_width, theme->border_width); wlr_scene_node_set_position(part->node, 0, height); continue; case LAB_SSD_PART_TOP: if (ssd->titlebar.height > 0) { wlr_scene_rect_set_size(rect, width - 2 * SSD_BUTTON_WIDTH, theme->border_width); wlr_scene_node_set_position(part->node, theme->border_width + SSD_BUTTON_WIDTH, -(ssd->titlebar.height + theme->border_width)); } else { wlr_scene_rect_set_size(rect, full_width, theme->border_width); wlr_scene_node_set_position(part->node, 0, -theme->border_width); } continue; default: continue; } } } FOR_EACH_END } void ssd_border_destroy(struct ssd *ssd) { assert(ssd); assert(ssd->border.tree); struct ssd_sub_tree *subtree; FOR_EACH_STATE(ssd, subtree) { ssd_destroy_parts(&subtree->parts); wlr_scene_node_destroy(&subtree->tree->node); subtree->tree = NULL; } FOR_EACH_END wlr_scene_node_destroy(&ssd->border.tree->node); ssd->border.tree = NULL; } #undef FOR_EACH_STATE ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/ssd/ssd_extents.c�������������������������������������������������������������������0000664�0000000�0000000�00000017452�14570443012�0016707�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <pixman.h> #include "common/mem.h" #include "common/scene-helpers.h" #include "labwc.h" #include "ssd-internal.h" #include "theme.h" #include "view.h" static struct ssd_part * add_extent(struct wl_list *part_list, enum ssd_part_type type, struct wlr_scene_tree *parent) { float invisible[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; struct ssd_part *part = add_scene_part(part_list, type); /* * Extents need additional geometry to enable dynamic * resize based on position and output->usable_area. * * part->geometry will get free'd automatically in ssd_destroy_parts(). */ part->node = &wlr_scene_rect_create(parent, 0, 0, invisible)->node; part->geometry = znew(struct wlr_box); return part; } void ssd_extents_create(struct ssd *ssd) { struct view *view = ssd->view; struct theme *theme = view->server->theme; struct wl_list *part_list = &ssd->extents.parts; int extended_area = SSD_EXTENDED_AREA; int corner_size = extended_area + theme->border_width + SSD_BUTTON_WIDTH / 2; ssd->extents.tree = wlr_scene_tree_create(ssd->tree); struct wlr_scene_tree *parent = ssd->extents.tree; if (view->fullscreen || view->maximized == VIEW_AXIS_BOTH) { wlr_scene_node_set_enabled(&parent->node, false); } wl_list_init(&ssd->extents.parts); wlr_scene_node_set_position(&parent->node, -(theme->border_width + extended_area), -(ssd->titlebar.height + theme->border_width + extended_area)); /* Initialize parts and set constant values for targeted geometry */ struct ssd_part *p; /* Top */ p = add_extent(part_list, LAB_SSD_PART_CORNER_TOP_LEFT, parent); p->geometry->width = corner_size; p->geometry->height = corner_size; p = add_extent(part_list, LAB_SSD_PART_TOP, parent); p->geometry->x = corner_size; p->geometry->height = extended_area; p = add_extent(part_list, LAB_SSD_PART_CORNER_TOP_RIGHT, parent); p->geometry->width = corner_size; p->geometry->height = corner_size; /* Sides */ p = add_extent(part_list, LAB_SSD_PART_LEFT, parent); p->geometry->y = corner_size; p->geometry->width = extended_area; p = add_extent(part_list, LAB_SSD_PART_RIGHT, parent); p->geometry->y = corner_size; p->geometry->width = extended_area; /* Bottom */ p = add_extent(part_list, LAB_SSD_PART_CORNER_BOTTOM_LEFT, parent); p->geometry->width = corner_size; p->geometry->height = corner_size; p = add_extent(part_list, LAB_SSD_PART_BOTTOM, parent); p->geometry->x = corner_size; p->geometry->height = extended_area; p = add_extent(part_list, LAB_SSD_PART_CORNER_BOTTOM_RIGHT, parent); p->geometry->width = corner_size; p->geometry->height = corner_size; /* Initial manual update to keep X11 applications happy */ ssd_extents_update(ssd); } void ssd_extents_update(struct ssd *ssd) { struct view *view = ssd->view; if (view->fullscreen || view->maximized == VIEW_AXIS_BOTH) { wlr_scene_node_set_enabled(&ssd->extents.tree->node, false); return; } if (!ssd->extents.tree->node.enabled) { wlr_scene_node_set_enabled(&ssd->extents.tree->node, true); } if (!view->output) { return; } struct theme *theme = view->server->theme; int width = view->current.width; int height = view_effective_height(view, /* use_pending */ false); int full_height = height + theme->border_width * 2 + ssd->titlebar.height; int full_width = width + 2 * theme->border_width; int extended_area = SSD_EXTENDED_AREA; int corner_size = extended_area + theme->border_width + SSD_BUTTON_WIDTH / 2; int side_width = full_width + extended_area * 2 - corner_size * 2; int side_height = full_height + extended_area * 2 - corner_size * 2; struct wlr_box part_box; struct wlr_box result_box; struct ssd_part *part; struct wlr_scene_rect *rect; /* Make sure we update the y offset based on titlebar shown / hidden */ wlr_scene_node_set_position(&ssd->extents.tree->node, -(theme->border_width + extended_area), -(ssd->titlebar.height + theme->border_width + extended_area)); /* * Convert all output usable areas that the * view is currently on into a pixman region */ int nrects; pixman_region32_t usable; pixman_region32_t intersection; pixman_region32_init(&usable); pixman_region32_init(&intersection); struct output *output; wl_list_for_each(output, &view->server->outputs, link) { if (!view_on_output(view, output)) { continue; } struct wlr_box usable_area = output_usable_area_in_layout_coords(output); pixman_region32_union_rect(&usable, &usable, usable_area.x, usable_area.y, usable_area.width, usable_area.height); } /* Remember base layout coordinates */ int base_x, base_y; wlr_scene_node_coords(&ssd->extents.tree->node, &base_x, &base_y); struct wlr_box *target; wl_list_for_each(part, &ssd->extents.parts, link) { rect = wlr_scene_rect_from_node(part->node); target = part->geometry; switch (part->type) { case LAB_SSD_PART_TOP: target->width = side_width; break; case LAB_SSD_PART_CORNER_TOP_RIGHT: target->x = corner_size + side_width; break; case LAB_SSD_PART_LEFT: target->height = side_height; break; case LAB_SSD_PART_RIGHT: target->x = extended_area + full_width; target->height = side_height; break; case LAB_SSD_PART_CORNER_BOTTOM_LEFT: target->y = corner_size + side_height; break; case LAB_SSD_PART_BOTTOM: target->width = side_width; target->y = extended_area + full_height; break; case LAB_SSD_PART_CORNER_BOTTOM_RIGHT: target->x = corner_size + side_width; target->y = corner_size + side_height; break; default: break; } /* Get layout geometry of what the part *should* be */ part_box.x = base_x + target->x; part_box.y = base_y + target->y; part_box.width = target->width; part_box.height = target->height; /* Constrain part to output->usable_area */ pixman_region32_clear(&intersection); pixman_region32_intersect_rect(&intersection, &usable, part_box.x, part_box.y, part_box.width, part_box.height); const pixman_box32_t *inter_rects = pixman_region32_rectangles(&intersection, &nrects); if (nrects == 0) { /* Not visible */ wlr_scene_node_set_enabled(part->node, false); continue; } /* * For each edge, the invisible grab area is resized * to not cover layer-shell clients such as panels. * However, only one resize operation is used per edge, * so if a window is in the unlikely position that it * is near a panel but also overspills onto another screen, * the invisible grab-area on the other screen would be * smaller than would normally be the case. * * Thus only use the first intersecting rect, this is * a compromise as it doesn't require us to create * multiple scene rects for a given extent edge * and still works in 95% of the cases. */ result_box = (struct wlr_box) { .x = inter_rects[0].x1, .y = inter_rects[0].y1, .width = inter_rects[0].x2 - inter_rects[0].x1, .height = inter_rects[0].y2 - inter_rects[0].y1 }; if (!part->node->enabled) { wlr_scene_node_set_enabled(part->node, true); } if (part_box.width != result_box.width || part_box.height != result_box.height) { /* Partly visible */ wlr_scene_rect_set_size(rect, result_box.width, result_box.height); wlr_scene_node_set_position(part->node, target->x + (result_box.x - part_box.x), target->y + (result_box.y - part_box.y)); continue; } /* Fully visible */ if (target->x != part->node->x || target->y != part->node->y) { wlr_scene_node_set_position(part->node, target->x, target->y); } if (target->width != rect->width || target->height != rect->height) { wlr_scene_rect_set_size(rect, target->width, target->height); } } pixman_region32_fini(&intersection); pixman_region32_fini(&usable); } void ssd_extents_destroy(struct ssd *ssd) { if (!ssd->extents.tree) { return; } ssd_destroy_parts(&ssd->extents.parts); wlr_scene_node_destroy(&ssd->extents.tree->node); ssd->extents.tree = NULL; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/ssd/ssd_part.c����������������������������������������������������������������������0000664�0000000�0000000�00000017547�14570443012�0016170�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include "common/list.h" #include "common/mem.h" #include "labwc.h" #include "node.h" #include "ssd-internal.h" /* Internal helpers */ static void ssd_button_destroy_notify(struct wl_listener *listener, void *data) { struct ssd_button *button = wl_container_of(listener, button, destroy); wl_list_remove(&button->destroy.link); free(button); } /* * Create a new node_descriptor containing a link to a new ssd_button struct. * Both will be destroyed automatically once the scene_node they are attached * to is destroyed. */ static struct ssd_button * ssd_button_descriptor_create(struct wlr_scene_node *node) { /* Create new ssd_button */ struct ssd_button *button = znew(*button); /* Let it destroy automatically when the scene node destroys */ button->destroy.notify = ssd_button_destroy_notify; wl_signal_add(&node->events.destroy, &button->destroy); /* And finally attach the ssd_button to a node descriptor */ node_descriptor_create(node, LAB_NODE_DESC_SSD_BUTTON, button); return button; } /* Internal API */ struct ssd_part * add_scene_part(struct wl_list *part_list, enum ssd_part_type type) { struct ssd_part *part = znew(*part); part->type = type; wl_list_append(part_list, &part->link); return part; } struct ssd_part * add_scene_rect(struct wl_list *list, enum ssd_part_type type, struct wlr_scene_tree *parent, int width, int height, int x, int y, float color[4]) { /* * When initialized without surface being mapped, * size may be negative. Just set to 0, next call * to ssd_*_update() will update the rect to use * its correct size. */ width = width >= 0 ? width : 0; height = height >= 0 ? height : 0; struct ssd_part *part = add_scene_part(list, type); part->node = &wlr_scene_rect_create( parent, width, height, color)->node; wlr_scene_node_set_position(part->node, x, y); return part; } struct ssd_part * add_scene_buffer(struct wl_list *list, enum ssd_part_type type, struct wlr_scene_tree *parent, struct wlr_buffer *buffer, int x, int y) { struct ssd_part *part = add_scene_part(list, type); part->node = &wlr_scene_buffer_create(parent, buffer)->node; wlr_scene_node_set_position(part->node, x, y); return part; } struct ssd_part * add_scene_button_corner(struct wl_list *part_list, enum ssd_part_type type, enum ssd_part_type corner_type, struct wlr_scene_tree *parent, struct wlr_buffer *corner_buffer, struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer, int x, struct view *view) { int offset_x; float invisible[4] = { 0, 0, 0, 0 }; if (corner_type == LAB_SSD_PART_CORNER_TOP_LEFT) { offset_x = rc.theme->border_width; } else if (corner_type == LAB_SSD_PART_CORNER_TOP_RIGHT) { offset_x = 0; } else { assert(false && "invalid corner button type"); wlr_log(WLR_ERROR, "invalid corner button type"); abort(); } struct ssd_part *button_root = add_scene_part(part_list, corner_type); parent = wlr_scene_tree_create(parent); button_root->node = &parent->node; wlr_scene_node_set_position(button_root->node, x, 0); /* * Background, x and y adjusted for border_width which is * already included in rendered theme.c / corner_buffer */ add_scene_buffer(part_list, corner_type, parent, corner_buffer, -offset_x, -rc.theme->border_width); /* Finally just put a usual theme button on top, using an invisible hitbox */ add_scene_button(part_list, type, parent, invisible, icon_buffer, hover_buffer, 0, view); return button_root; } static struct wlr_box get_scale_box(struct wlr_buffer *buffer, double container_width, double container_height) { struct wlr_box icon_geo = { .width = buffer->width, .height = buffer->height }; /* Scale down buffer if required */ if (icon_geo.width && icon_geo.height) { double scale = MIN(container_width / icon_geo.width, container_height / icon_geo.height); if (scale < 1.0f) { icon_geo.width = (double)icon_geo.width * scale; icon_geo.height = (double)icon_geo.height * scale; } } /* Center buffer on both axis */ icon_geo.x = (container_width - icon_geo.width) / 2; icon_geo.y = (container_height - icon_geo.height) / 2; return icon_geo; } struct ssd_part * add_scene_button(struct wl_list *part_list, enum ssd_part_type type, struct wlr_scene_tree *parent, float *bg_color, struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer, int x, struct view *view) { struct ssd_part *button_root = add_scene_part(part_list, type); parent = wlr_scene_tree_create(parent); button_root->node = &parent->node; wlr_scene_node_set_position(button_root->node, x, 0); /* Background */ struct ssd_part *bg_rect = add_scene_rect(part_list, type, parent, SSD_BUTTON_WIDTH, rc.theme->title_height, 0, 0, bg_color); /* Icon */ struct wlr_scene_tree *icon_tree = wlr_scene_tree_create(parent); struct wlr_box icon_geo = get_scale_box(icon_buffer, SSD_BUTTON_WIDTH, rc.theme->title_height); struct ssd_part *icon_part = add_scene_buffer(part_list, type, icon_tree, icon_buffer, icon_geo.x, icon_geo.y); /* Make sure big icons are scaled down if necessary */ wlr_scene_buffer_set_dest_size( wlr_scene_buffer_from_node(icon_part->node), icon_geo.width, icon_geo.height); /* Hover icon */ struct wlr_scene_tree *hover_tree = wlr_scene_tree_create(parent); wlr_scene_node_set_enabled(&hover_tree->node, false); struct wlr_box hover_geo = get_scale_box(hover_buffer, SSD_BUTTON_WIDTH, rc.theme->title_height); struct ssd_part *hover_part = add_scene_buffer(part_list, type, hover_tree, hover_buffer, hover_geo.x, hover_geo.y); /* Make sure big icons are scaled down if necessary */ wlr_scene_buffer_set_dest_size( wlr_scene_buffer_from_node(hover_part->node), hover_geo.width, hover_geo.height); struct ssd_button *button = ssd_button_descriptor_create(button_root->node); button->type = type; button->view = view; button->normal = icon_part->node; button->hover = hover_part->node; button->background = bg_rect->node; button->toggled = NULL; button->toggled_hover = NULL; button->icon_tree = icon_tree; button->hover_tree = hover_tree; return button_root; } void add_toggled_icon(struct wl_list *part_list, enum ssd_part_type type, struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer) { struct ssd_part *part = ssd_get_part(part_list, type); struct ssd_button *button = node_ssd_button_from_node(part->node); /* Alternate icon */ struct wlr_box icon_geo = get_scale_box(icon_buffer, SSD_BUTTON_WIDTH, rc.theme->title_height); struct ssd_part *alticon_part = add_scene_buffer(part_list, type, button->icon_tree, icon_buffer, icon_geo.x, icon_geo.y); wlr_scene_buffer_set_dest_size( wlr_scene_buffer_from_node(alticon_part->node), icon_geo.width, icon_geo.height); wlr_scene_node_set_enabled(alticon_part->node, false); struct wlr_box hover_geo = get_scale_box(hover_buffer, SSD_BUTTON_WIDTH, rc.theme->title_height); struct ssd_part *althover_part = add_scene_buffer(part_list, type, button->hover_tree, hover_buffer, hover_geo.x, hover_geo.y); wlr_scene_buffer_set_dest_size( wlr_scene_buffer_from_node(althover_part->node), hover_geo.width, hover_geo.height); wlr_scene_node_set_enabled(althover_part->node, false); button->toggled = alticon_part->node; button->toggled_hover = althover_part->node; } struct ssd_part * ssd_get_part(struct wl_list *part_list, enum ssd_part_type type) { struct ssd_part *part; wl_list_for_each(part, part_list, link) { if (part->type == type) { return part; } } return NULL; } void ssd_destroy_parts(struct wl_list *list) { struct ssd_part *part, *tmp; wl_list_for_each_reverse_safe(part, tmp, list, link) { if (part->node) { wlr_scene_node_destroy(part->node); part->node = NULL; } /* part->buffer will free itself along the scene_buffer node */ part->buffer = NULL; if (part->geometry) { free(part->geometry); part->geometry = NULL; } wl_list_remove(&part->link); free(part); } assert(wl_list_empty(list)); } ���������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/ssd/ssd_titlebar.c������������������������������������������������������������������0000664�0000000�0000000�00000032460�14570443012�0017017�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <string.h> #include "buffer.h" #include "common/mem.h" #include "common/scaled_font_buffer.h" #include "common/scene-helpers.h" #include "common/string-helpers.h" #include "labwc.h" #include "node.h" #include "ssd-internal.h" #include "theme.h" #include "view.h" #define FOR_EACH_STATE(ssd, tmp) FOR_EACH(tmp, \ &(ssd)->titlebar.active, \ &(ssd)->titlebar.inactive) static void set_squared_corners(struct ssd *ssd, bool enable); static void set_maximize_alt_icon(struct ssd *ssd, bool enable); void ssd_titlebar_create(struct ssd *ssd) { struct view *view = ssd->view; struct theme *theme = view->server->theme; int width = view->current.width; float *color; struct wlr_scene_tree *parent; struct wlr_buffer *corner_top_left; struct wlr_buffer *corner_top_right; struct wlr_buffer *menu_button_unpressed; struct wlr_buffer *iconify_button_unpressed; struct wlr_buffer *maximize_button_unpressed; struct wlr_buffer *restore_button_unpressed; struct wlr_buffer *close_button_unpressed; struct wlr_buffer *menu_button_hover; struct wlr_buffer *iconify_button_hover; struct wlr_buffer *maximize_button_hover; struct wlr_buffer *restore_button_hover; struct wlr_buffer *close_button_hover; ssd->titlebar.tree = wlr_scene_tree_create(ssd->tree); struct ssd_sub_tree *subtree; FOR_EACH_STATE(ssd, subtree) { subtree->tree = wlr_scene_tree_create(ssd->titlebar.tree); parent = subtree->tree; wlr_scene_node_set_position(&parent->node, 0, -theme->title_height); if (subtree == &ssd->titlebar.active) { color = theme->window_active_title_bg_color; corner_top_left = &theme->corner_top_left_active_normal->base; corner_top_right = &theme->corner_top_right_active_normal->base; menu_button_unpressed = &theme->button_menu_active_unpressed->base; iconify_button_unpressed = &theme->button_iconify_active_unpressed->base; close_button_unpressed = &theme->button_close_active_unpressed->base; maximize_button_unpressed = &theme->button_maximize_active_unpressed->base; restore_button_unpressed = &theme->button_restore_active_unpressed->base; menu_button_hover = &theme->button_menu_active_hover->base; iconify_button_hover = &theme->button_iconify_active_hover->base; close_button_hover = &theme->button_close_active_hover->base; maximize_button_hover = &theme->button_maximize_active_hover->base; restore_button_hover = &theme->button_restore_active_hover->base; } else { color = theme->window_inactive_title_bg_color; corner_top_left = &theme->corner_top_left_inactive_normal->base; corner_top_right = &theme->corner_top_right_inactive_normal->base; menu_button_unpressed = &theme->button_menu_inactive_unpressed->base; iconify_button_unpressed = &theme->button_iconify_inactive_unpressed->base; maximize_button_unpressed = &theme->button_maximize_inactive_unpressed->base; restore_button_unpressed = &theme->button_restore_inactive_unpressed->base; close_button_unpressed = &theme->button_close_inactive_unpressed->base; menu_button_hover = &theme->button_menu_inactive_hover->base; iconify_button_hover = &theme->button_iconify_inactive_hover->base; close_button_hover = &theme->button_close_inactive_hover->base; maximize_button_hover = &theme->button_maximize_inactive_hover->base; restore_button_hover = &theme->button_restore_inactive_hover->base; wlr_scene_node_set_enabled(&parent->node, false); } wl_list_init(&subtree->parts); /* Title */ add_scene_rect(&subtree->parts, LAB_SSD_PART_TITLEBAR, parent, width - SSD_BUTTON_WIDTH * SSD_BUTTON_COUNT, theme->title_height, SSD_BUTTON_WIDTH, 0, color); /* Buttons */ add_scene_button_corner(&subtree->parts, LAB_SSD_BUTTON_WINDOW_MENU, LAB_SSD_PART_CORNER_TOP_LEFT, parent, corner_top_left, menu_button_unpressed, menu_button_hover, 0, view); add_scene_button(&subtree->parts, LAB_SSD_BUTTON_ICONIFY, parent, color, iconify_button_unpressed, iconify_button_hover, width - SSD_BUTTON_WIDTH * 3, view); add_scene_button(&subtree->parts, LAB_SSD_BUTTON_MAXIMIZE, parent, color, maximize_button_unpressed, maximize_button_hover, width - SSD_BUTTON_WIDTH * 2, view); add_toggled_icon(&subtree->parts, LAB_SSD_BUTTON_MAXIMIZE, restore_button_unpressed, restore_button_hover); add_scene_button_corner(&subtree->parts, LAB_SSD_BUTTON_CLOSE, LAB_SSD_PART_CORNER_TOP_RIGHT, parent, corner_top_right, close_button_unpressed, close_button_hover, width - SSD_BUTTON_WIDTH * 1, view); } FOR_EACH_END ssd_update_title(ssd); if (view->maximized == VIEW_AXIS_BOTH) { set_squared_corners(ssd, true); set_maximize_alt_icon(ssd, true); ssd->state.was_maximized = true; } } static bool is_direct_child(struct wlr_scene_node *node, struct ssd_sub_tree *subtree) { return node->parent == subtree->tree; } static void set_squared_corners(struct ssd *ssd, bool enable) { struct ssd_part *part; struct ssd_sub_tree *subtree; enum ssd_part_type ssd_type[2] = { LAB_SSD_BUTTON_WINDOW_MENU, LAB_SSD_BUTTON_CLOSE }; FOR_EACH_STATE(ssd, subtree) { for (size_t i = 0; i < ARRAY_SIZE(ssd_type); i++) { part = ssd_get_part(&subtree->parts, ssd_type[i]); struct ssd_button *button = node_ssd_button_from_node(part->node); /* Toggle background between invisible and titlebar background color */ struct wlr_scene_rect *rect = wlr_scene_rect_from_node(button->background); wlr_scene_rect_set_color(rect, !enable ? (float[4]) {0, 0, 0, 0} : ( subtree == &ssd->titlebar.active ? rc.theme->window_active_title_bg_color : rc.theme->window_inactive_title_bg_color)); /* Toggle rounded corner image itself */ struct wlr_scene_node *rounded_corner = wl_container_of(part->node->link.prev, rounded_corner, link); wlr_scene_node_set_enabled(rounded_corner, !enable); } } FOR_EACH_END } static void set_maximize_alt_icon(struct ssd *ssd, bool enable) { struct ssd_part *part; struct ssd_button *button; struct ssd_sub_tree *subtree; FOR_EACH_STATE(ssd, subtree) { part = ssd_get_part(&subtree->parts, LAB_SSD_BUTTON_MAXIMIZE); button = node_ssd_button_from_node(part->node); if (button->toggled) { wlr_scene_node_set_enabled(button->toggled, enable); wlr_scene_node_set_enabled(button->normal, !enable); } if (button->toggled_hover) { wlr_scene_node_set_enabled(button->toggled_hover, enable); wlr_scene_node_set_enabled(button->hover, !enable); } } FOR_EACH_END } void ssd_titlebar_update(struct ssd *ssd) { struct view *view = ssd->view; int width = view->current.width; struct theme *theme = view->server->theme; bool maximized = (view->maximized == VIEW_AXIS_BOTH); if (ssd->state.was_maximized != maximized) { set_squared_corners(ssd, maximized); set_maximize_alt_icon(ssd, maximized); ssd->state.was_maximized = maximized; } if (width == ssd->state.geometry.width) { return; } struct ssd_part *part; struct ssd_sub_tree *subtree; FOR_EACH_STATE(ssd, subtree) { wl_list_for_each(part, &subtree->parts, link) { switch (part->type) { case LAB_SSD_PART_TITLEBAR: wlr_scene_rect_set_size( wlr_scene_rect_from_node(part->node), width - SSD_BUTTON_WIDTH * SSD_BUTTON_COUNT, theme->title_height); continue; case LAB_SSD_BUTTON_ICONIFY: if (is_direct_child(part->node, subtree)) { wlr_scene_node_set_position(part->node, width - SSD_BUTTON_WIDTH * 3, 0); } continue; case LAB_SSD_BUTTON_MAXIMIZE: if (is_direct_child(part->node, subtree)) { wlr_scene_node_set_position(part->node, width - SSD_BUTTON_WIDTH * 2, 0); } continue; case LAB_SSD_PART_CORNER_TOP_RIGHT: if (is_direct_child(part->node, subtree)) { wlr_scene_node_set_position(part->node, width - SSD_BUTTON_WIDTH * 1, 0); } continue; default: continue; } } } FOR_EACH_END ssd_update_title(ssd); } void ssd_titlebar_destroy(struct ssd *ssd) { if (!ssd->titlebar.tree) { return; } struct ssd_sub_tree *subtree; FOR_EACH_STATE(ssd, subtree) { ssd_destroy_parts(&subtree->parts); wlr_scene_node_destroy(&subtree->tree->node); subtree->tree = NULL; } FOR_EACH_END if (ssd->state.title.text) { free(ssd->state.title.text); ssd->state.title.text = NULL; } wlr_scene_node_destroy(&ssd->titlebar.tree->node); ssd->titlebar.tree = NULL; } /* * For ssd_update_title* we do not early out because * .active and .inactive may result in different sizes * of the title (font family/size) or background of * the title (different button/border width). * * Both, wlr_scene_node_set_enabled() and wlr_scene_node_set_position() * check for actual changes and return early if there is no change in state. * Always using wlr_scene_node_set_enabled(node, true) will thus not cause * any unnecessary screen damage and makes the code easier to follow. */ static void ssd_update_title_positions(struct ssd *ssd) { struct view *view = ssd->view; struct theme *theme = view->server->theme; int width = view->current.width; int title_bg_width = width - SSD_BUTTON_WIDTH * SSD_BUTTON_COUNT; int x, y; int buffer_height, buffer_width; struct ssd_part *part; struct ssd_sub_tree *subtree; FOR_EACH_STATE(ssd, subtree) { part = ssd_get_part(&subtree->parts, LAB_SSD_PART_TITLE); if (!part || !part->node) { /* view->surface never been mapped */ /* Or we somehow failed to allocate a scaled titlebar buffer */ continue; } buffer_width = part->buffer ? part->buffer->width : 0; buffer_height = part->buffer ? part->buffer->height : 0; x = SSD_BUTTON_WIDTH; y = (theme->title_height - buffer_height) / 2; if (title_bg_width <= 0) { wlr_scene_node_set_enabled(part->node, false); continue; } wlr_scene_node_set_enabled(part->node, true); if (theme->window_label_text_justify == LAB_JUSTIFY_CENTER) { if (buffer_width + SSD_BUTTON_WIDTH * 2 <= title_bg_width) { /* Center based on the full width */ x = (width - buffer_width) / 2; } else { /* * Center based on the width between the buttons. * Title jumps around once this is hit but its still * better than to hide behind the buttons on the right. */ x += (title_bg_width - buffer_width) / 2; } } else if (theme->window_label_text_justify == LAB_JUSTIFY_RIGHT) { x += title_bg_width - buffer_width; } else if (theme->window_label_text_justify == LAB_JUSTIFY_LEFT) { /* TODO: maybe add some theme x padding here? */ } wlr_scene_node_set_position(part->node, x, y); } FOR_EACH_END } void ssd_update_title(struct ssd *ssd) { if (!ssd) { return; } struct view *view = ssd->view; char *title = (char *)view_get_string_prop(view, "title"); if (string_null_or_empty(title)) { return; } struct theme *theme = view->server->theme; struct ssd_state_title *state = &ssd->state.title; bool title_unchanged = state->text && !strcmp(title, state->text); float *text_color; struct font *font = NULL; struct ssd_part *part; struct ssd_sub_tree *subtree; struct ssd_state_title_width *dstate; int title_bg_width = view->current.width - SSD_BUTTON_WIDTH * SSD_BUTTON_COUNT; FOR_EACH_STATE(ssd, subtree) { if (subtree == &ssd->titlebar.active) { dstate = &state->active; text_color = theme->window_active_label_text_color; font = &rc.font_activewindow; } else { dstate = &state->inactive; text_color = theme->window_inactive_label_text_color; font = &rc.font_inactivewindow; } if (title_bg_width <= 0) { dstate->truncated = true; continue; } if (title_unchanged && !dstate->truncated && dstate->width < title_bg_width) { /* title the same + we don't need to resize title */ continue; } part = ssd_get_part(&subtree->parts, LAB_SSD_PART_TITLE); if (!part) { /* Initialize part and wlr_scene_buffer without attaching a buffer */ part = add_scene_part(&subtree->parts, LAB_SSD_PART_TITLE); part->buffer = scaled_font_buffer_create(subtree->tree); if (part->buffer) { part->node = &part->buffer->scene_buffer->node; } else { wlr_log(WLR_ERROR, "Failed to create title node"); } } if (part->buffer) { scaled_font_buffer_update(part->buffer, title, title_bg_width, font, text_color, NULL); } /* And finally update the cache */ dstate->width = part->buffer ? part->buffer->width : 0; dstate->truncated = title_bg_width <= dstate->width; } FOR_EACH_END if (!title_unchanged) { if (state->text) { free(state->text); } state->text = xstrdup(title); } ssd_update_title_positions(ssd); } static void ssd_button_set_hover(struct ssd_button *button, bool enabled) { assert(button); wlr_scene_node_set_enabled(&button->hover_tree->node, enabled); wlr_scene_node_set_enabled(&button->icon_tree->node, !enabled); } void ssd_update_button_hover(struct wlr_scene_node *node, struct ssd_hover_state *hover_state) { struct ssd_button *button = NULL; if (!node || !node->data) { goto disable_old_hover; } struct node_descriptor *desc = node->data; if (desc->type == LAB_NODE_DESC_SSD_BUTTON) { button = node_ssd_button_from_node(node); if (button == hover_state->button) { /* Cursor is still on the same button */ return; } } disable_old_hover: if (hover_state->button) { ssd_button_set_hover(hover_state->button, false); hover_state->view = NULL; hover_state->button = NULL; } if (button) { ssd_button_set_hover(button, true); hover_state->view = button->view; hover_state->button = button; } } #undef FOR_EACH_STATE ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/tearing.c���������������������������������������������������������������������������0000664�0000000�0000000�00000003160�14570443012�0015173�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include "labwc.h" #include "view.h" struct tearing_controller { struct wlr_tearing_control_v1 *tearing_control; struct wl_listener set_hint; struct wl_listener destroy; }; static void set_tearing_hint(struct wl_listener *listener, void *data) { struct tearing_controller *controller = wl_container_of(listener, controller, set_hint); struct view *view = view_from_wlr_surface(controller->tearing_control->surface); if (view && controller->tearing_control->hint) { view->tearing_hint = true; } } static void tearing_controller_destroy(struct wl_listener *listener, void *data) { struct tearing_controller *controller = wl_container_of(listener, controller, destroy); free(controller); } void new_tearing_hint(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, tearing_new_object); struct wlr_tearing_control_v1 *tearing_control = data; enum wp_tearing_control_v1_presentation_hint hint = wlr_tearing_control_manager_v1_surface_hint_from_surface (server->tearing_control, tearing_control->surface); wlr_log(WLR_DEBUG, "New presentation hint %d received for surface %p", hint, tearing_control->surface); struct tearing_controller *controller = calloc(1, sizeof(struct tearing_controller)); if (!controller) { return; } controller->tearing_control = tearing_control; controller->set_hint.notify = set_tearing_hint; wl_signal_add(&tearing_control->events.set_hint, &controller->set_hint); controller->destroy.notify = tearing_controller_destroy; wl_signal_add(&tearing_control->events.destroy, &controller->destroy); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/theme.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000077772�14570443012�0014670�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * Theme engine for labwc * * Copyright (C) Johan Malm 2020-2023 */ #define _POSIX_C_SOURCE 200809L #include "config.h" #include <assert.h> #include <cairo.h> #include <drm_fourcc.h> #include <glib.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <wlr/util/box.h> #include <wlr/util/log.h> #include <strings.h> #include "common/macros.h" #include "common/dir.h" #include "common/font.h" #include "common/graphic-helpers.h" #include "common/match.h" #include "common/mem.h" #include "common/string-helpers.h" #include "config/rcxml.h" #include "button/button-png.h" #if HAVE_RSVG #include "button/button-svg.h" #endif #include "button/button-xbm.h" #include "theme.h" #include "buffer.h" #include "ssd.h" struct button { const char *name; const char *alt_name; const char *fallback_button; /* built-in 6x6 button */ struct { struct lab_data_buffer **buffer; float *rgba; } active, inactive; }; enum corner { LAB_CORNER_UNKNOWN = 0, LAB_CORNER_TOP_LEFT, LAB_CORNER_TOP_RIGHT, }; struct rounded_corner_ctx { struct wlr_box *box; double radius; double line_width; float *fill_color; float *border_color; enum corner corner; }; static struct lab_data_buffer *rounded_rect(struct rounded_corner_ctx *ctx); static void zdrop(struct lab_data_buffer **buffer) { if (*buffer) { wlr_buffer_drop(&(*buffer)->base); *buffer = NULL; } } static enum corner corner_from_icon_name(const char *icon_name) { assert(icon_name); /* * TODO: Once we implement titleLayout we can make the * return values depend on parsed config values. */ if (!strcmp(icon_name, "menu")) { return LAB_CORNER_TOP_LEFT; } else if (!strcmp(icon_name, "close")) { return LAB_CORNER_TOP_RIGHT; } return LAB_CORNER_UNKNOWN; } static void create_hover_fallback(struct theme *theme, const char *icon_name, struct lab_data_buffer **hover_buffer, struct lab_data_buffer *icon_buffer) { assert(icon_name); assert(icon_buffer); assert(!*hover_buffer); struct surface_context icon = get_cairo_surface_from_lab_data_buffer(icon_buffer); int icon_width = cairo_image_surface_get_width(icon.surface); int icon_height = cairo_image_surface_get_height(icon.surface); int width = SSD_BUTTON_WIDTH; int height = theme->title_height; if (width && height) { /* * Proportionately increase size of hover_buffer if the * non-hover 'donor' buffer is larger than the allocated space. * It will get scaled down again by wlroots when rendered and as * required by the current output scale. * * This ensures that icons > width or > height keep their aspect * ratio and are rendered the same as without the hover overlay. */ double scale = MAX((double)icon_width / width, (double)icon_height / height); if (scale > 1.0f) { width = (double)width * scale; height = (double)height * scale; } } *hover_buffer = buffer_create_cairo(width, height, 1.0, true); cairo_t *cairo = (*hover_buffer)->cairo; cairo_surface_t *surf = cairo_get_target(cairo); /* Background */ cairo_set_source_surface(cairo, icon.surface, (width - icon_width) / 2, (height - icon_height) / 2); cairo_paint(cairo); /* Overlay (non-multiplied alpha) */ float overlay_color[4] = { 0.5f, 0.5f, 0.5f, 0.3f}; enum corner corner = corner_from_icon_name(icon_name); if (corner == LAB_CORNER_UNKNOWN) { set_cairo_color(cairo, overlay_color); cairo_rectangle(cairo, 0, 0, width, height); cairo_fill(cairo); } else { struct rounded_corner_ctx rounded_ctx = { .box = &(struct wlr_box) { .width = width, .height = height }, .radius = rc.corner_radius, .line_width = theme->border_width, .fill_color = overlay_color, .border_color = overlay_color, .corner = corner }; struct lab_data_buffer *overlay_buffer = rounded_rect(&rounded_ctx); cairo_set_source_surface(cairo, cairo_get_target(overlay_buffer->cairo), 0, 0); cairo_paint(cairo); wlr_buffer_drop(&overlay_buffer->base); } cairo_surface_flush(surf); if (icon.is_duplicate) { cairo_surface_destroy(icon.surface); } } /* * We use the following button filename schema: "BUTTON [TOGGLED] [STATE]" * with the words separted by underscore, and the following meaning: * - BUTTON can be one of 'max', 'iconify', 'close', 'menu' * - TOGGLED is either 'toggled' or nothing * - STATE is 'hover' or nothing. In future, 'pressed' may be supported too. * * We believe that this is how the vast majority of extant openbox themes out * there are constructed and it is consistent with the openbox.org wiki. But * please be aware that it is actually different to vanilla Openbox which uses: * "BUTTON [STATE] [TOGGLED]" following an unfortunate commit in 2014 which * broke themes and led to some distros patching Openbox: * https://github.com/danakj/openbox/commit/35e92e4c2a45b28d5c2c9b44b64aeb4222098c94 * * Arch Linux and Debian patch Openbox to keep the old syntax (the one we use). * https://gitlab.archlinux.org/archlinux/packaging/packages/openbox/-/blob/main/debian-887908.patch?ref_type=heads * This patch does the following: * - reads "%s_toggled_pressed.xbm" and "%s_toggled_hover.xbm" instead of the * 'hover_toggled' equivalents. * - parses 'toggled.unpressed', toggled.pressed' and 'toggled.hover' instead * of the other way around ('*.toggled') when processing themerc. * * For compatibility with distros which do not apply similar patches, we support * the hover-before-toggle too, for example: * * .name = "max_toggled_hover", * .alt_name = "max_hover_toggled", * * ...in the button array definition below. */ static void load_buttons(struct theme *theme) { struct button buttons[] = { { .name = "menu", .fallback_button = (const char[]){ 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00 }, .active.buffer = &theme->button_menu_active_unpressed, .active.rgba = theme->window_active_button_menu_unpressed_image_color, .inactive.buffer = &theme->button_menu_inactive_unpressed, .inactive.rgba = theme->window_inactive_button_menu_unpressed_image_color, }, { .name = "iconify", .fallback_button = (const char[]){ 0x00, 0x00, 0x00, 0x00, 0x3f, 0x3f }, .active.buffer = &theme->button_iconify_active_unpressed, .active.rgba = theme->window_active_button_iconify_unpressed_image_color, .inactive.buffer = &theme->button_iconify_inactive_unpressed, .inactive.rgba = theme->window_inactive_button_iconify_unpressed_image_color, }, { .name = "max", .fallback_button = (const char[]){ 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f }, .active.buffer = &theme->button_maximize_active_unpressed, .active.rgba = theme->window_active_button_max_unpressed_image_color, .inactive.buffer = &theme->button_maximize_inactive_unpressed, .inactive.rgba = theme->window_inactive_button_max_unpressed_image_color, }, { .name = "max_toggled", .fallback_button = (const char[]){ 0x3e, 0x22, 0x2f, 0x29, 0x39, 0x0f }, .active.buffer = &theme->button_restore_active_unpressed, .active.rgba = theme->window_active_button_max_unpressed_image_color, .inactive.buffer = &theme->button_restore_inactive_unpressed, .inactive.rgba = theme->window_inactive_button_max_unpressed_image_color, }, { .name = "close", .fallback_button = (const char[]){ 0x33, 0x3f, 0x1e, 0x1e, 0x3f, 0x33 }, .active.buffer = &theme->button_close_active_unpressed, .active.rgba = theme->window_active_button_close_unpressed_image_color, .inactive.buffer = &theme->button_close_inactive_unpressed, .inactive.rgba = theme->window_inactive_button_close_unpressed_image_color, }, { .name = "menu_hover", /* no fallback (non-hover variant is used instead) */ .active.buffer = &theme->button_menu_active_hover, .active.rgba = theme->window_active_button_menu_unpressed_image_color, .inactive.buffer = &theme->button_menu_inactive_hover, .inactive.rgba = theme->window_inactive_button_menu_unpressed_image_color, }, { .name = "iconify_hover", /* no fallback (non-hover variant is used instead) */ .active.buffer = &theme->button_iconify_active_hover, .active.rgba = theme->window_active_button_iconify_unpressed_image_color, .inactive.buffer = &theme->button_iconify_inactive_hover, .inactive.rgba = theme->window_inactive_button_iconify_unpressed_image_color, }, { .name = "max_hover", /* no fallback (non-hover variant is used instead) */ .active.buffer = &theme->button_maximize_active_hover, .active.rgba = theme->window_active_button_max_unpressed_image_color, .inactive.buffer = &theme->button_maximize_inactive_hover, .inactive.rgba = theme->window_inactive_button_max_unpressed_image_color, }, { .name = "max_toggled_hover", .alt_name = "max_hover_toggled", /* no fallback (non-hover variant is used instead) */ .active.buffer = &theme->button_restore_active_hover, .active.rgba = theme->window_active_button_max_unpressed_image_color, .inactive.buffer = &theme->button_restore_inactive_hover, .inactive.rgba = theme->window_inactive_button_max_unpressed_image_color, }, { .name = "close_hover", /* no fallback (non-hover variant is used instead) */ .active.buffer = &theme->button_close_active_hover, .active.rgba = theme->window_active_button_close_unpressed_image_color, .inactive.buffer = &theme->button_close_inactive_hover, .inactive.rgba = theme->window_inactive_button_close_unpressed_image_color, }, }; char filename[4096] = {0}; for (size_t i = 0; i < ARRAY_SIZE(buttons); ++i) { struct button *b = &buttons[i]; zdrop(b->active.buffer); zdrop(b->inactive.buffer); /* PNG */ snprintf(filename, sizeof(filename), "%s-active.png", b->name); button_png_load(filename, b->active.buffer); snprintf(filename, sizeof(filename), "%s-inactive.png", b->name); button_png_load(filename, b->inactive.buffer); #if HAVE_RSVG /* SVG */ int size = theme->title_height - 2 * theme->padding_height; if (!*b->active.buffer) { snprintf(filename, sizeof(filename), "%s-active.svg", b->name); button_svg_load(filename, b->active.buffer, size); } if (!*b->inactive.buffer) { snprintf(filename, sizeof(filename), "%s-inactive.svg", b->name); button_svg_load(filename, b->inactive.buffer, size); } #endif /* XBM */ snprintf(filename, sizeof(filename), "%s.xbm", b->name); if (!*b->active.buffer) { button_xbm_load(filename, b->active.buffer, b->active.rgba); } if (!*b->inactive.buffer) { button_xbm_load(filename, b->inactive.buffer, b->inactive.rgba); } /* * XBM (alternative name) * For example max_hover_toggled instead of max_toggled_hover */ if (b->alt_name) { snprintf(filename, sizeof(filename), "%s.xbm", b->alt_name); } else { filename[0] = '\0'; } if (!*b->active.buffer) { button_xbm_load(filename, b->active.buffer, b->active.rgba); } if (!*b->inactive.buffer) { button_xbm_load(filename, b->inactive.buffer, b->inactive.rgba); } /* * Builtin bitmap * * Applicable to basic buttons such as max, max_toggled and * iconify. There are no bitmap fallbacks for *_hover icons. */ if (!b->fallback_button) { continue; } if (!*b->active.buffer) { button_xbm_from_bitmap(b->fallback_button, b->active.buffer, b->active.rgba); } if (!*b->inactive.buffer) { button_xbm_from_bitmap(b->fallback_button, b->inactive.buffer, b->inactive.rgba); } } /* * If hover-icons do not exist, add fallbacks by copying the non-hover * variant (base) and then adding an overlay. */ for (size_t i = 0; i < ARRAY_SIZE(buttons); i++) { struct button *hover_button = &buttons[i]; if (!strstr(hover_button->name, "_hover")) { continue; } /* If name=='foo_hover', basename='foo' */ char basename[64] = {0}; snprintf(basename, sizeof(basename), "%s", hover_button->name); trim_last_field(basename, '_'); for (size_t j = 0; j < ARRAY_SIZE(buttons); j++) { struct button *base = &buttons[j]; if (!strcmp(basename, base->name)) { if (!*hover_button->active.buffer) { create_hover_fallback(theme, basename, hover_button->active.buffer, *base->active.buffer); } if (!*hover_button->inactive.buffer) { create_hover_fallback(theme, basename, hover_button->inactive.buffer, *base->inactive.buffer); } break; } } } } static int hex_to_dec(char c) { if (c >= '0' && c <= '9') { return c - '0'; } if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } return 0; } /** * parse_hexstr - parse #rrggbb * @hex: hex string to be parsed * @rgba: pointer to float[4] for return value */ static void parse_hexstr(const char *hex, float *rgba) { if (!hex || hex[0] != '#' || strlen(hex) < 7) { return; } rgba[0] = (hex_to_dec(hex[1]) * 16 + hex_to_dec(hex[2])) / 255.0; rgba[1] = (hex_to_dec(hex[3]) * 16 + hex_to_dec(hex[4])) / 255.0; rgba[2] = (hex_to_dec(hex[5]) * 16 + hex_to_dec(hex[6])) / 255.0; if (strlen(hex) > 7) { rgba[3] = atoi(hex + 7) / 100.0; } else { rgba[3] = 1.0; } } static enum lab_justification parse_justification(const char *str) { if (!strcasecmp(str, "Center")) { return LAB_JUSTIFY_CENTER; } else if (!strcasecmp(str, "Right")) { return LAB_JUSTIFY_RIGHT; } else { return LAB_JUSTIFY_LEFT; } } /* * We generally use Openbox defaults, but if no theme file can be found it's * better to populate the theme variables with some sane values as no-one * wants to use openbox without a theme - it'll all just be black and white. * * Openbox doesn't actual start if it can't find a theme. As it's normally * packaged with Clearlooks, this is not a problem, but for labwc I thought * this was a bit hard-line. People might want to try labwc without having * Openbox (and associated themes) installed. * * theme_builtin() applies a theme that is similar to vanilla GTK */ static void theme_builtin(struct theme *theme) { theme->border_width = 1; theme->padding_height = 3; theme->title_height = INT_MIN; theme->menu_overlap_x = 0; theme->menu_overlap_y = 0; parse_hexstr("#e1dedb", theme->window_active_border_color); parse_hexstr("#f6f5f4", theme->window_inactive_border_color); parse_hexstr("#ff0000", theme->window_toggled_keybinds_color); parse_hexstr("#e1dedb", theme->window_active_title_bg_color); parse_hexstr("#f6f5f4", theme->window_inactive_title_bg_color); parse_hexstr("#000000", theme->window_active_label_text_color); parse_hexstr("#000000", theme->window_inactive_label_text_color); theme->window_label_text_justify = parse_justification("Center"); parse_hexstr("#000000", theme->window_active_button_menu_unpressed_image_color); parse_hexstr("#000000", theme->window_active_button_iconify_unpressed_image_color); parse_hexstr("#000000", theme->window_active_button_max_unpressed_image_color); parse_hexstr("#000000", theme->window_active_button_close_unpressed_image_color); parse_hexstr("#000000", theme->window_inactive_button_menu_unpressed_image_color); parse_hexstr("#000000", theme->window_inactive_button_iconify_unpressed_image_color); parse_hexstr("#000000", theme->window_inactive_button_max_unpressed_image_color); parse_hexstr("#000000", theme->window_inactive_button_close_unpressed_image_color); parse_hexstr("#fcfbfa", theme->menu_items_bg_color); parse_hexstr("#000000", theme->menu_items_text_color); parse_hexstr("#e1dedb", theme->menu_items_active_bg_color); parse_hexstr("#000000", theme->menu_items_active_text_color); theme->menu_item_padding_x = 7; theme->menu_item_padding_y = 4; theme->menu_min_width = 20; theme->menu_max_width = 200; theme->menu_separator_line_thickness = 1; theme->menu_separator_padding_width = 6; theme->menu_separator_padding_height = 3; parse_hexstr("#888888", theme->menu_separator_color); theme->osd_window_switcher_width = 600; theme->osd_window_switcher_padding = 4; theme->osd_window_switcher_item_padding_x = 10; theme->osd_window_switcher_item_padding_y = 1; theme->osd_window_switcher_item_active_border_width = 2; theme->osd_workspace_switcher_boxes_width = 20; theme->osd_workspace_switcher_boxes_height = 20; /* inherit settings in post_processing() if not set elsewhere */ theme->osd_bg_color[0] = FLT_MIN; theme->osd_border_width = INT_MIN; theme->osd_border_color[0] = FLT_MIN; theme->osd_label_text_color[0] = FLT_MIN; } static void entry(struct theme *theme, const char *key, const char *value) { if (!key || !value) { return; } /* * Note that in order for the pattern match to apply to more than just * the first instance, "else if" cannot be used throughout this function */ if (match_glob(key, "border.width")) { theme->border_width = atoi(value); } if (match_glob(key, "padding.height")) { theme->padding_height = atoi(value); } if (match_glob(key, "titlebar.height")) { theme->title_height = atoi(value); } if (match_glob(key, "menu.items.padding.x")) { theme->menu_item_padding_x = atoi(value); } if (match_glob(key, "menu.items.padding.y")) { theme->menu_item_padding_y = atoi(value); } if (match_glob(key, "menu.overlap.x")) { theme->menu_overlap_x = atoi(value); } if (match_glob(key, "menu.overlap.y")) { theme->menu_overlap_y = atoi(value); } if (match_glob(key, "window.active.border.color")) { parse_hexstr(value, theme->window_active_border_color); } if (match_glob(key, "window.inactive.border.color")) { parse_hexstr(value, theme->window_inactive_border_color); } /* border.color is obsolete, but handled for backward compatibility */ if (match_glob(key, "border.color")) { parse_hexstr(value, theme->window_active_border_color); parse_hexstr(value, theme->window_inactive_border_color); } if (match_glob(key, "window.active.indicator.toggled-keybind.color")) { parse_hexstr(value, theme->window_toggled_keybinds_color); } if (match_glob(key, "window.active.title.bg.color")) { parse_hexstr(value, theme->window_active_title_bg_color); } if (match_glob(key, "window.inactive.title.bg.color")) { parse_hexstr(value, theme->window_inactive_title_bg_color); } if (match_glob(key, "window.active.label.text.color")) { parse_hexstr(value, theme->window_active_label_text_color); } if (match_glob(key, "window.inactive.label.text.color")) { parse_hexstr(value, theme->window_inactive_label_text_color); } if (match_glob(key, "window.label.text.justify")) { theme->window_label_text_justify = parse_justification(value); } /* universal button */ if (match_glob(key, "window.active.button.unpressed.image.color")) { parse_hexstr(value, theme->window_active_button_menu_unpressed_image_color); parse_hexstr(value, theme->window_active_button_iconify_unpressed_image_color); parse_hexstr(value, theme->window_active_button_max_unpressed_image_color); parse_hexstr(value, theme->window_active_button_close_unpressed_image_color); } if (match_glob(key, "window.inactive.button.unpressed.image.color")) { parse_hexstr(value, theme->window_inactive_button_menu_unpressed_image_color); parse_hexstr(value, theme->window_inactive_button_iconify_unpressed_image_color); parse_hexstr(value, theme->window_inactive_button_max_unpressed_image_color); parse_hexstr(value, theme->window_inactive_button_close_unpressed_image_color); } /* individual buttons */ if (match_glob(key, "window.active.button.menu.unpressed.image.color")) { parse_hexstr(value, theme->window_active_button_menu_unpressed_image_color); } if (match_glob(key, "window.active.button.iconify.unpressed.image.color")) { parse_hexstr(value, theme->window_active_button_iconify_unpressed_image_color); } if (match_glob(key, "window.active.button.max.unpressed.image.color")) { parse_hexstr(value, theme->window_active_button_max_unpressed_image_color); } if (match_glob(key, "window.active.button.close.unpressed.image.color")) { parse_hexstr(value, theme->window_active_button_close_unpressed_image_color); } if (match_glob(key, "window.inactive.button.menu.unpressed.image.color")) { parse_hexstr(value, theme->window_inactive_button_menu_unpressed_image_color); } if (match_glob(key, "window.inactive.button.iconify.unpressed.image.color")) { parse_hexstr(value, theme->window_inactive_button_iconify_unpressed_image_color); } if (match_glob(key, "window.inactive.button.max.unpressed.image.color")) { parse_hexstr(value, theme->window_inactive_button_max_unpressed_image_color); } if (match_glob(key, "window.inactive.button.close.unpressed.image.color")) { parse_hexstr(value, theme->window_inactive_button_close_unpressed_image_color); } if (match_glob(key, "menu.width.min")) { theme->menu_min_width = atoi(value); } if (match_glob(key, "menu.width.max")) { theme->menu_max_width = atoi(value); } if (match_glob(key, "menu.items.bg.color")) { parse_hexstr(value, theme->menu_items_bg_color); } if (match_glob(key, "menu.items.text.color")) { parse_hexstr(value, theme->menu_items_text_color); } if (match_glob(key, "menu.items.active.bg.color")) { parse_hexstr(value, theme->menu_items_active_bg_color); } if (match_glob(key, "menu.items.active.text.color")) { parse_hexstr(value, theme->menu_items_active_text_color); } if (match_glob(key, "menu.separator.width")) { theme->menu_separator_line_thickness = atoi(value); } if (match_glob(key, "menu.separator.padding.width")) { theme->menu_separator_padding_width = atoi(value); } if (match_glob(key, "menu.separator.padding.height")) { theme->menu_separator_padding_height = atoi(value); } if (match_glob(key, "menu.separator.color")) { parse_hexstr(value, theme->menu_separator_color); } if (match_glob(key, "osd.bg.color")) { parse_hexstr(value, theme->osd_bg_color); } if (match_glob(key, "osd.border.width")) { theme->osd_border_width = atoi(value); } if (match_glob(key, "osd.border.color")) { parse_hexstr(value, theme->osd_border_color); } if (match_glob(key, "osd.window-switcher.width")) { theme->osd_window_switcher_width = atoi(value); } if (match_glob(key, "osd.window-switcher.padding")) { theme->osd_window_switcher_padding = atoi(value); } if (match_glob(key, "osd.window-switcher.item.padding.x")) { theme->osd_window_switcher_item_padding_x = atoi(value); } if (match_glob(key, "osd.window-switcher.item.padding.y")) { theme->osd_window_switcher_item_padding_y = atoi(value); } if (match_glob(key, "osd.window-switcher.item.active.border.width")) { theme->osd_window_switcher_item_active_border_width = atoi(value); } if (match_glob(key, "osd.workspace-switcher.boxes.width")) { theme->osd_workspace_switcher_boxes_width = atoi(value); } if (match_glob(key, "osd.workspace-switcher.boxes.height")) { theme->osd_workspace_switcher_boxes_height = atoi(value); } if (match_glob(key, "osd.label.text.color")) { parse_hexstr(value, theme->osd_label_text_color); } } static void parse_config_line(char *line, char **key, char **value) { char *p = strchr(line, ':'); if (!p) { return; } *p = '\0'; *key = string_strip(line); *value = string_strip(++p); } static void process_line(struct theme *theme, char *line) { if (line[0] == '\0' || line[0] == '#') { return; } char *key = NULL, *value = NULL; parse_config_line(line, &key, &value); entry(theme, key, value); } static void theme_read(struct theme *theme, struct wl_list *paths) { bool should_merge_config = rc.merge_config; struct wl_list *(*iter)(struct wl_list *list); iter = should_merge_config ? paths_get_prev : paths_get_next; for (struct wl_list *elm = iter(paths); elm != paths; elm = iter(elm)) { struct path *path = wl_container_of(elm, path, link); FILE *stream = fopen(path->string, "r"); if (!stream) { continue; } wlr_log(WLR_INFO, "read theme %s", path->string); char *line = NULL; size_t len = 0; while (getline(&line, &len, stream) != -1) { char *p = strrchr(line, '\n'); if (p) { *p = '\0'; } process_line(theme, line); } zfree(line); fclose(stream); if (!should_merge_config) { break; } } } static struct lab_data_buffer * rounded_rect(struct rounded_corner_ctx *ctx) { /* 1 degree in radians (=2π/360) */ double deg = 0.017453292519943295; if (ctx->corner == LAB_CORNER_UNKNOWN) { return NULL; } double w = ctx->box->width; double h = ctx->box->height; double r = ctx->radius; struct lab_data_buffer *buffer; /* TODO: scale */ buffer = buffer_create_cairo(w, h, 1, /*free_on_destroy*/ true); cairo_t *cairo = buffer->cairo; cairo_surface_t *surf = cairo_get_target(cairo); /* set transparent background */ cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); cairo_paint(cairo); /* * Create outline path and fill. Illustration of top-left corner buffer: * * _,,ooO"""""""""+ * ,oO"' ^ | * ,o" | | * o" |r | * o' | | * O r v | * O<--------->+ | * O | * O | * O | * +--------------------+ */ cairo_set_line_width(cairo, 0.0); cairo_new_sub_path(cairo); switch (ctx->corner) { case LAB_CORNER_TOP_LEFT: cairo_arc(cairo, r, r, r, 180 * deg, 270 * deg); cairo_line_to(cairo, w, 0); cairo_line_to(cairo, w, h); cairo_line_to(cairo, 0, h); break; case LAB_CORNER_TOP_RIGHT: cairo_arc(cairo, w - r, r, r, -90 * deg, 0 * deg); cairo_line_to(cairo, w, h); cairo_line_to(cairo, 0, h); cairo_line_to(cairo, 0, 0); break; default: wlr_log(WLR_ERROR, "unknown corner type"); } cairo_close_path(cairo); cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); set_cairo_color(cairo, ctx->fill_color); cairo_fill_preserve(cairo); cairo_stroke(cairo); /* * Stroke horizontal and vertical borders, shown by Xs and Ys * respectively in the figure below: * * _,,ooO"XXXXXXXXX * ,oO"' | * ,o" | * o" | * o' | * O | * Y | * Y | * Y | * Y | * Y--------------------+ */ cairo_set_line_cap(cairo, CAIRO_LINE_CAP_BUTT); set_cairo_color(cairo, ctx->border_color); cairo_set_line_width(cairo, ctx->line_width); double half_line_width = ctx->line_width / 2.0; switch (ctx->corner) { case LAB_CORNER_TOP_LEFT: cairo_move_to(cairo, half_line_width, h); cairo_line_to(cairo, half_line_width, r); cairo_move_to(cairo, r, half_line_width); cairo_line_to(cairo, w, half_line_width); break; case LAB_CORNER_TOP_RIGHT: cairo_move_to(cairo, 0, half_line_width); cairo_line_to(cairo, w - r, half_line_width); cairo_move_to(cairo, w - half_line_width, r); cairo_line_to(cairo, w - half_line_width, h); break; default: wlr_log(WLR_ERROR, "unknown corner type"); } cairo_stroke(cairo); /* * If radius==0 the borders stroked above go right up to (and including) * the corners, so there is not need to do any more. */ if (!r) { goto out; } /* * Stroke the arc section of the border of the corner piece. * * Note: This figure is drawn at a more zoomed in scale compared with * those above. * * ,,ooooO"" ^ * ,ooo""' | | * ,oOO" | | line-thickness * ,OO" | | * ,OO" _,,ooO"" v * ,O" ,oO"' * ,O' ,o" * ,O' o" * o' o' * O O * O---------O + * <-----------------> * radius * * We handle the edge-case where line-thickness > radius by merely * setting line-thickness = radius and in effect drawing a quadrant of a * circle. In this case the X and Y borders butt up against the arc and * overlap each other (as their line-thickessnes are greater than the * linethickness of the arc). As a result, there is no inner rounded * corners. * * So, in order to have inner rounded corners cornerRadius should be * greater than border.width. * * Also, see diagrams in https://github.com/labwc/labwc/pull/990 */ double line_width = MIN(ctx->line_width, r); cairo_set_line_width(cairo, line_width); half_line_width = line_width / 2.0; switch (ctx->corner) { case LAB_CORNER_TOP_LEFT: cairo_move_to(cairo, half_line_width, r); cairo_arc(cairo, r, r, r - half_line_width, 180 * deg, 270 * deg); break; case LAB_CORNER_TOP_RIGHT: cairo_move_to(cairo, w - r, half_line_width); cairo_arc(cairo, w - r, r, r - half_line_width, -90 * deg, 0 * deg); break; default: break; } cairo_stroke(cairo); out: cairo_surface_flush(surf); return buffer; } static void create_corners(struct theme *theme) { struct wlr_box box = { .x = 0, .y = 0, .width = SSD_BUTTON_WIDTH + theme->border_width, .height = theme->title_height + theme->border_width, }; struct rounded_corner_ctx ctx = { .box = &box, .radius = rc.corner_radius, .line_width = theme->border_width, .fill_color = theme->window_active_title_bg_color, .border_color = theme->window_active_border_color, .corner = LAB_CORNER_TOP_LEFT, }; theme->corner_top_left_active_normal = rounded_rect(&ctx); ctx.fill_color = theme->window_inactive_title_bg_color, ctx.border_color = theme->window_inactive_border_color, theme->corner_top_left_inactive_normal = rounded_rect(&ctx); ctx.corner = LAB_CORNER_TOP_RIGHT; ctx.fill_color = theme->window_active_title_bg_color, ctx.border_color = theme->window_active_border_color, theme->corner_top_right_active_normal = rounded_rect(&ctx); ctx.fill_color = theme->window_inactive_title_bg_color, ctx.border_color = theme->window_inactive_border_color, theme->corner_top_right_inactive_normal = rounded_rect(&ctx); } static void post_processing(struct theme *theme) { int h = MAX(font_height(&rc.font_activewindow), font_height(&rc.font_inactivewindow)); if (theme->title_height < h) { theme->title_height = h + 2 * theme->padding_height; } theme->osd_window_switcher_item_height = font_height(&rc.font_osd) + 2 * theme->osd_window_switcher_item_padding_y + 2 * theme->osd_window_switcher_item_active_border_width; if (rc.corner_radius >= theme->title_height) { rc.corner_radius = theme->title_height - 1; } if (theme->menu_max_width < theme->menu_min_width) { wlr_log(WLR_ERROR, "Adjusting menu.width.max: .max (%d) lower than .min (%d)", theme->menu_max_width, theme->menu_min_width); theme->menu_max_width = theme->menu_min_width; } /* Inherit OSD settings if not set */ if (theme->osd_bg_color[0] == FLT_MIN) { memcpy(theme->osd_bg_color, theme->window_active_title_bg_color, sizeof(theme->osd_bg_color)); } if (theme->osd_border_width == INT_MIN) { theme->osd_border_width = theme->border_width; } if (theme->osd_label_text_color[0] == FLT_MIN) { memcpy(theme->osd_label_text_color, theme->window_active_label_text_color, sizeof(theme->osd_label_text_color)); } if (theme->osd_border_color[0] == FLT_MIN) { /* * As per http://openbox.org/wiki/Help:Themes#osd.border.color * we should fall back to window_active_border_color but * that is usually the same as window_active_title_bg_color * and thus the fallback for osd_bg_color. Which would mean * they are both the same color and thus the border is invisible. * * Instead, we fall back to osd_label_text_color which in turn * falls back to window_active_label_text_color. */ memcpy(theme->osd_border_color, theme->osd_label_text_color, sizeof(theme->osd_border_color)); } if (theme->osd_workspace_switcher_boxes_width == 0) { theme->osd_workspace_switcher_boxes_height = 0; } if (theme->osd_workspace_switcher_boxes_height == 0) { theme->osd_workspace_switcher_boxes_width = 0; } } void theme_init(struct theme *theme, const char *theme_name) { /* * Set some default values. This is particularly important on * reconfigure as not all themes set all options */ theme_builtin(theme); /* Read <data-dir>/share/themes/$theme_name/openbox-3/themerc */ struct wl_list paths; paths_theme_create(&paths, theme_name, "themerc"); theme_read(theme, &paths); paths_destroy(&paths); /* Read <config-dir>/labwc/themerc-override */ paths_config_create(&paths, "themerc-override"); theme_read(theme, &paths); paths_destroy(&paths); post_processing(theme); create_corners(theme); load_buttons(theme); } void theme_finish(struct theme *theme) { zdrop(&theme->corner_top_left_active_normal); zdrop(&theme->corner_top_left_inactive_normal); zdrop(&theme->corner_top_right_active_normal); zdrop(&theme->corner_top_right_inactive_normal); } ������labwc-0.7.1/src/view-impl-common.c������������������������������������������������������������������0000664�0000000�0000000�00000006434�14570443012�0016750�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* view-impl-common.c: common code for shell view->impl functions */ #include <stdio.h> #include <strings.h> #include "common/list.h" #include "labwc.h" #include "view.h" #include "view-impl-common.h" #include "window-rules.h" void view_impl_move_to_front(struct view *view) { wl_list_remove(&view->link); wl_list_insert(&view->server->views, &view->link); wlr_scene_node_raise_to_top(&view->scene_tree->node); } void view_impl_move_to_back(struct view *view) { wl_list_remove(&view->link); wl_list_append(&view->server->views, &view->link); wlr_scene_node_lower_to_bottom(&view->scene_tree->node); } void view_impl_map(struct view *view) { desktop_focus_view(view, /*raise*/ true); view_update_title(view); view_update_app_id(view); if (!view->been_mapped) { window_rules_apply(view, LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP); } /* * It's tempting to just never create the foreign-toplevel handle in the * map handlers, but the app_id/title might not have been set at that * point, so it's safer to process the property here */ enum property ret = window_rules_get_property(view, "skipTaskbar"); if (ret == LAB_PROP_TRUE) { if (view->toplevel.handle) { wlr_foreign_toplevel_handle_v1_destroy(view->toplevel.handle); } } wlr_log(WLR_DEBUG, "[map] identifier=%s, title=%s\n", view_get_string_prop(view, "app_id"), view_get_string_prop(view, "title")); } void view_impl_unmap(struct view *view) { struct server *server = view->server; if (view == server->active_view) { desktop_focus_topmost_view(server); } if (view == server->last_raised_view) { server->last_raised_view = NULL; } } static bool resizing_edge(struct view *view, uint32_t edge) { struct server *server = view->server; return server->input_mode == LAB_INPUT_STATE_RESIZE && server->grabbed_view == view && (server->resize_edges & edge); } void view_impl_apply_geometry(struct view *view, int w, int h) { struct wlr_box *current = &view->current; struct wlr_box *pending = &view->pending; struct wlr_box old = *current; /* * Anchor right edge if resizing via left edge. * * Note that answering the question "are we resizing?" is a bit * tricky. The most obvious method is to look at the server * flags; but that method will not account for any late commits * that occur after the mouse button is released, as the client * catches up with pending configure requests. So as a fallback, * we resort to a geometry-based heuristic -- also not 100% * reliable on its own. The combination of the two methods * should catch 99% of resize cases that we care about. */ bool resizing_left_edge = resizing_edge(view, WLR_EDGE_LEFT); if (resizing_left_edge || (current->x != pending->x && current->x + current->width == pending->x + pending->width)) { current->x = pending->x + pending->width - w; } else { current->x = pending->x; } /* Anchor bottom edge if resizing via top edge */ bool resizing_top_edge = resizing_edge(view, WLR_EDGE_TOP); if (resizing_top_edge || (current->y != pending->y && current->y + current->height == pending->y + pending->height)) { current->y = pending->y + pending->height - h; } else { current->y = pending->y; } current->width = w; current->height = h; if (!wlr_box_equal(current, &old)) { view_moved(view); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/view.c������������������������������������������������������������������������������0000664�0000000�0000000�00000152561�14570443012�0014526�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <stdio.h> #include <strings.h> #include "common/macros.h" #include "common/match.h" #include "common/mem.h" #include "common/scene-helpers.h" #include "input/keyboard.h" #include "labwc.h" #include "menu/menu.h" #include "placement.h" #include "regions.h" #include "resize_indicator.h" #include "snap.h" #include "ssd.h" #include "view.h" #include "window-rules.h" #include "workspaces.h" #include "xwayland.h" #if HAVE_XWAYLAND #include <wlr/xwayland.h> #endif #define LAB_FALLBACK_WIDTH 640 #define LAB_FALLBACK_HEIGHT 480 struct view * view_from_wlr_surface(struct wlr_surface *surface) { assert(surface); /* * TODO: * - find a way to get rid of xdg/xwayland-specific stuff * - look up root/toplevel surface if passed a subsurface? */ struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface); if (xdg_surface) { return xdg_surface->data; } #if HAVE_XWAYLAND struct wlr_xwayland_surface *xsurface = wlr_xwayland_surface_try_from_wlr_surface(surface); if (xsurface) { return xsurface->data; } #endif return NULL; } void view_query_free(struct view_query *query) { wl_list_remove(&query->link); free(query->identifier); free(query->title); free(query); } bool view_matches_query(struct view *view, struct view_query *query) { bool match = true; bool empty = true; const char *identifier = view_get_string_prop(view, "app_id"); if (match && query->identifier) { empty = false; match &= match_glob(query->identifier, identifier); } const char *title = view_get_string_prop(view, "title"); if (match && query->title) { empty = false; match &= match_glob(query->title, title); } return !empty && match; } static bool matches_criteria(struct view *view, enum lab_view_criteria criteria) { if (!view_is_focusable(view)) { return false; } if (criteria & LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) { /* * Always-on-top views are always on the current desktop and are * special in that they live in a different tree. */ struct server *server = view->server; if (view->scene_tree->node.parent != server->workspace_current->tree && !view_is_always_on_top(view)) { return false; } } if (criteria & LAB_VIEW_CRITERIA_FULLSCREEN) { if (!view->fullscreen) { return false; } } if (criteria & LAB_VIEW_CRITERIA_ALWAYS_ON_TOP) { if (!view_is_always_on_top(view)) { return false; } } if (criteria & LAB_VIEW_CRITERIA_NO_ALWAYS_ON_TOP) { if (view_is_always_on_top(view)) { return false; } } if (criteria & LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER) { if (window_rules_get_property(view, "skipWindowSwitcher") == LAB_PROP_TRUE) { return false; } } return true; } struct view * view_next(struct wl_list *head, struct view *view, enum lab_view_criteria criteria) { assert(head); struct wl_list *elm = view ? &view->link : head; for (elm = elm->next; elm != head; elm = elm->next) { view = wl_container_of(elm, view, link); if (matches_criteria(view, criteria)) { return view; } } return NULL; } void view_array_append(struct server *server, struct wl_array *views, enum lab_view_criteria criteria) { struct view *view; for_each_view(view, &server->views, criteria) { struct view **entry = wl_array_add(views, sizeof(*entry)); if (!entry) { wlr_log(WLR_ERROR, "wl_array_add(): out of memory"); continue; } *entry = view; } } enum view_wants_focus view_wants_focus(struct view *view) { assert(view); if (view->impl->wants_focus) { return view->impl->wants_focus(view); } return VIEW_WANTS_FOCUS_ALWAYS; } bool view_is_focusable_from(struct view *view, struct wlr_surface *prev) { assert(view); if (!view->surface) { return false; } if (!view->mapped && !view->minimized) { return false; } enum view_wants_focus wants_focus = view_wants_focus(view); /* * Consider "offer focus" (Globally Active) views as focusable * only if another surface from the same application already had * focus. The goal is to allow focusing a parent window when a * dialog/popup is closed, but still avoid focusing standalone * panels/toolbars/notifications. Note that we are basically * guessing whether Globally Active views want focus, and will * probably be wrong some of the time. */ return (wants_focus == VIEW_WANTS_FOCUS_ALWAYS || (wants_focus == VIEW_WANTS_FOCUS_OFFER && prev && view_is_related(view, prev))); } /** * All view_apply_xxx_geometry() functions must *not* modify * any state besides repositioning or resizing the view. * * They may be called repeatably during output layout changes. */ enum view_edge view_edge_invert(enum view_edge edge) { switch (edge) { case VIEW_EDGE_LEFT: return VIEW_EDGE_RIGHT; case VIEW_EDGE_RIGHT: return VIEW_EDGE_LEFT; case VIEW_EDGE_UP: return VIEW_EDGE_DOWN; case VIEW_EDGE_DOWN: return VIEW_EDGE_UP; case VIEW_EDGE_CENTER: case VIEW_EDGE_INVALID: default: return VIEW_EDGE_INVALID; } } static struct wlr_box view_get_edge_snap_box(struct view *view, struct output *output, enum view_edge edge) { struct wlr_box usable = output_usable_area_scaled(output); int x_offset = edge == VIEW_EDGE_RIGHT ? (usable.width + rc.gap) / 2 : rc.gap; int y_offset = edge == VIEW_EDGE_DOWN ? (usable.height + rc.gap) / 2 : rc.gap; int base_width, base_height; switch (edge) { case VIEW_EDGE_LEFT: case VIEW_EDGE_RIGHT: base_width = (usable.width - 3 * rc.gap) / 2; base_height = usable.height - 2 * rc.gap; break; case VIEW_EDGE_UP: case VIEW_EDGE_DOWN: base_width = usable.width - 2 * rc.gap; base_height = (usable.height - 3 * rc.gap) / 2; break; default: case VIEW_EDGE_CENTER: base_width = usable.width - 2 * rc.gap; base_height = usable.height - 2 * rc.gap; break; } struct border margin = ssd_get_margin(view->ssd); struct wlr_box dst = { .x = x_offset + usable.x + margin.left, .y = y_offset + usable.y + margin.top, .width = base_width - margin.left - margin.right, .height = base_height - margin.top - margin.bottom, }; return dst; } static bool view_discover_output(struct view *view, struct wlr_box *geometry) { assert(view); assert(!view->fullscreen); if (!geometry) { geometry = &view->current; } struct output *output = output_nearest_to(view->server, geometry->x + geometry->width / 2, geometry->y + geometry->height / 2); if (output && output != view->output) { view->output = output; return true; } return false; } static void set_adaptive_sync_fullscreen(struct view *view) { if (rc.adaptive_sync != LAB_ADAPTIVE_SYNC_FULLSCREEN) { return; } /* Enable adaptive sync if view is fullscreen */ output_enable_adaptive_sync(view->output->wlr_output, view->fullscreen); wlr_output_commit(view->output->wlr_output); } void view_set_activated(struct view *view, bool activated) { assert(view); ssd_set_active(view->ssd, activated); if (view->impl->set_activated) { view->impl->set_activated(view, activated); } if (view->toplevel.handle) { wlr_foreign_toplevel_handle_v1_set_activated( view->toplevel.handle, activated); } if (rc.kb_layout_per_window) { if (!activated) { /* Store configured keyboard layout per view */ view->keyboard_layout = view->server->seat.keyboard_group->keyboard.modifiers.group; } else { /* Switch to previously stored keyboard layout */ keyboard_update_layout(&view->server->seat, view->keyboard_layout); } } set_adaptive_sync_fullscreen(view); } void view_set_output(struct view *view, struct output *output) { assert(view); assert(!view->fullscreen); if (!output_is_usable(output)) { wlr_log(WLR_ERROR, "invalid output set for view"); return; } view->output = output; } void view_close(struct view *view) { assert(view); if (view->impl->close) { view->impl->close(view); } } static void view_update_outputs(struct view *view) { struct output *output; struct wlr_output_layout *layout = view->server->output_layout; view->outputs = 0; wl_list_for_each(output, &view->server->outputs, link) { if (output_is_usable(output) && wlr_output_layout_intersects( layout, output->wlr_output, &view->current)) { view->outputs |= (1ull << output->scene_output->index); } } if (view->toplevel.handle) { foreign_toplevel_update_outputs(view); } } bool view_on_output(struct view *view, struct output *output) { assert(view); assert(output); return output->scene_output && (view->outputs & (1ull << output->scene_output->index)); } void view_move(struct view *view, int x, int y) { assert(view); view_move_resize(view, (struct wlr_box){ .x = x, .y = y, .width = view->pending.width, .height = view->pending.height }); } void view_moved(struct view *view) { assert(view); wlr_scene_node_set_position(&view->scene_tree->node, view->current.x, view->current.y); /* * Only floating views change output when moved. Non-floating * views (maximized/tiled/fullscreen) are tied to a particular * output when they enter that state. */ if (view_is_floating(view)) { view_discover_output(view, NULL); } view_update_outputs(view); ssd_update_geometry(view->ssd); cursor_update_focus(view->server); if (rc.resize_indicator && view->server->grabbed_view == view) { resize_indicator_update(view); } } void view_move_resize(struct view *view, struct wlr_box geo) { assert(view); if (view->impl->configure) { view->impl->configure(view, geo); } } void view_resize_relative(struct view *view, int left, int right, int top, int bottom) { assert(view); if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) { return; } view_set_shade(view, false); struct wlr_box newgeo = view->pending; newgeo.x -= left; newgeo.width += left + right; newgeo.y -= top; newgeo.height += top + bottom; view_move_resize(view, newgeo); view_set_untiled(view); } void view_move_relative(struct view *view, int x, int y) { assert(view); if (view->fullscreen) { return; } view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false); if (view_is_tiled(view)) { view_set_untiled(view); view_restore_to(view, view->natural_geometry); } view_move(view, view->pending.x + x, view->pending.y + y); } void view_move_to_cursor(struct view *view) { assert(view); struct output *pending_output = output_nearest_to_cursor(view->server); if (!output_is_usable(pending_output)) { return; } view_set_fullscreen(view, false); view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false); if (view_is_tiled(view)) { view_set_untiled(view); view_restore_to(view, view->natural_geometry); } struct border margin = ssd_thickness(view); struct wlr_box geo = view->pending; geo.width += margin.left + margin.right; geo.height += margin.top + margin.bottom; int x = view->server->seat.cursor->x - (geo.width / 2); int y = view->server->seat.cursor->y - (geo.height / 2); struct wlr_box usable = output_usable_area_in_layout_coords(pending_output); /* Limit usable region to account for gap */ usable.x += rc.gap; usable.y += rc.gap; usable.width -= 2 * rc.gap; usable.height -= 2 * rc.gap; if (x + geo.width > usable.x + usable.width) { x = usable.x + usable.width - geo.width; } x = MAX(x, usable.x) + margin.left; if (y + geo.height > usable.y + usable.height) { y = usable.y + usable.height - geo.height; } y = MAX(y, usable.y) + margin.top; view_move(view, x, y); } struct view_size_hints view_get_size_hints(struct view *view) { assert(view); if (view->impl->get_size_hints) { return view->impl->get_size_hints(view); } return (struct view_size_hints){0}; } static void substitute_nonzero(int *a, int *b) { if (!(*a)) { *a = *b; } else if (!(*b)) { *b = *a; } } static int round_to_increment(int val, int base, int inc) { if (base < 0 || inc <= 0) { return val; } return base + (val - base + inc / 2) / inc * inc; } void view_adjust_size(struct view *view, int *w, int *h) { assert(view); struct view_size_hints hints = view_get_size_hints(view); /* * "If a base size is not provided, the minimum size is to be * used in its place and vice versa." (ICCCM 4.1.2.3) */ substitute_nonzero(&hints.min_width, &hints.base_width); substitute_nonzero(&hints.min_height, &hints.base_height); /* * Snap width/height to requested size increments (if any). * Typically, terminal emulators use these to make sure that the * terminal is resized to a width/height evenly divisible by the * cell (character) size. */ *w = round_to_increment(*w, hints.base_width, hints.width_inc); *h = round_to_increment(*h, hints.base_height, hints.height_inc); /* * If a minimum width/height was not set, then use default. * This is currently always the case for xdg-shell views. */ if (hints.min_width < 1) { hints.min_width = LAB_MIN_VIEW_WIDTH; } if (hints.min_height < 1) { hints.min_height = LAB_MIN_VIEW_HEIGHT; } *w = MAX(*w, hints.min_width); *h = MAX(*h, hints.min_height); } static void _minimize(struct view *view, bool minimized) { assert(view); if (view->minimized == minimized) { return; } if (view->toplevel.handle) { wlr_foreign_toplevel_handle_v1_set_minimized( view->toplevel.handle, minimized); } if (view->impl->minimize) { view->impl->minimize(view, minimized); } view->minimized = minimized; if (minimized) { view->impl->unmap(view, /* client_request */ false); } else { view->impl->map(view); } } static void minimize_sub_views(struct view *view, bool minimized) { struct view **child; struct wl_array children; wl_array_init(&children); view_append_children(view, &children); wl_array_for_each(child, &children) { _minimize(*child, minimized); minimize_sub_views(*child, minimized); } wl_array_release(&children); } /* * Minimize the whole view-hierarchy from top to bottom regardless of which one * in the hierarchy requested the minimize. For example, if an 'About' or * 'Open File' dialog is minimized, its toplevel is minimized also. And vice * versa. */ void view_minimize(struct view *view, bool minimized) { assert(view); /* * Minimize the root window first because some xwayland clients send a * request-unmap to sub-windows at this point (for example gimp and its * 'open file' dialog), so it saves trying to unmap them twice */ struct view *root = view_get_root(view); _minimize(root, minimized); minimize_sub_views(root, minimized); } bool view_compute_centered_position(struct view *view, const struct wlr_box *ref, int w, int h, int *x, int *y) { assert(view); if (w <= 0 || h <= 0) { wlr_log(WLR_ERROR, "view has empty geometry, not centering"); return false; } if (!output_is_usable(view->output)) { wlr_log(WLR_ERROR, "view has no output, not centering"); return false; } struct border margin = ssd_get_margin(view->ssd); struct wlr_box usable = output_usable_area_in_layout_coords(view->output); int width = w + margin.left + margin.right; int height = h + margin.top + margin.bottom; /* If reference box is NULL then center to usable area */ if (!ref) { ref = &usable; } *x = ref->x + (ref->width - width) / 2; *y = ref->y + (ref->height - height) / 2; /* If view is bigger than usable area, just top/left align it */ if (*x < usable.x) { *x = usable.x; } if (*y < usable.y) { *y = usable.y; } *x += margin.left; *y += margin.top; return true; } static bool adjust_floating_geometry(struct view *view, struct wlr_box *geometry, bool midpoint_visibility) { assert(view); if (!output_is_usable(view->output)) { wlr_log(WLR_ERROR, "view has no output, not positioning"); return false; } /* Avoid moving panels out of their own reserved area ("strut") */ if (window_rules_get_property(view, "fixedPosition") == LAB_PROP_TRUE || view_has_strut_partial(view)) { return false; } bool adjusted = false; bool onscreen = false; if (wlr_output_layout_intersects(view->server->output_layout, view->output->wlr_output, geometry)) { /* Always make sure the titlebar starts within the usable area */ struct border margin = ssd_get_margin(view->ssd); struct wlr_box usable = output_usable_area_in_layout_coords(view->output); if (geometry->x < usable.x + margin.left) { geometry->x = usable.x + margin.left; adjusted = true; } if (geometry->y < usable.y + margin.top) { geometry->y = usable.y + margin.top; adjusted = true; } if (!midpoint_visibility) { /* * If midpoint visibility is not required, the view is * on screen if at least one pixel is visible. */ onscreen = true; } else { /* Otherwise, make sure the midpoint is on screen */ int mx = geometry->x + geometry->width / 2; int my = geometry->y + geometry->height / 2; onscreen = mx <= usable.x + usable.width && my <= usable.y + usable.height; } } if (onscreen) { return adjusted; } /* Reposition offscreen automatically if configured to do so */ if (rc.placement_policy == LAB_PLACE_AUTOMATIC) { if (placement_find_best(view, geometry)) { return true; } } /* If automatic placement failed or was not enabled, just center */ return view_compute_centered_position(view, NULL, geometry->width, geometry->height, &geometry->x, &geometry->y); } static void set_fallback_geometry(struct view *view) { view->natural_geometry.width = LAB_FALLBACK_WIDTH; view->natural_geometry.height = LAB_FALLBACK_HEIGHT; view_compute_centered_position(view, NULL, view->natural_geometry.width, view->natural_geometry.height, &view->natural_geometry.x, &view->natural_geometry.y); } #undef LAB_FALLBACK_WIDTH #undef LAB_FALLBACK_HEIGHT void view_store_natural_geometry(struct view *view) { assert(view); if (!view_is_floating(view)) { /* Do not overwrite the stored geometry with special cases */ return; } /** * If an application was started maximized or fullscreened, its * natural_geometry width/height may still be zero in which case we set * some fallback values. This is the case with foot and Qt applications. */ if (wlr_box_empty(&view->pending)) { set_fallback_geometry(view); } else { view->natural_geometry = view->pending; } } int view_effective_height(struct view *view, bool use_pending) { assert(view); if (view->shaded) { return 0; } return use_pending ? view->pending.height : view->current.height; } void view_center(struct view *view, const struct wlr_box *ref) { assert(view); int x, y; if (view_compute_centered_position(view, ref, view->pending.width, view->pending.height, &x, &y)) { view_move(view, x, y); } } void view_place_initial(struct view *view, bool allow_cursor) { if (allow_cursor && rc.placement_policy == LAB_PLACE_CURSOR) { view_move_to_cursor(view); return; } else if (rc.placement_policy == LAB_PLACE_AUTOMATIC) { struct wlr_box geometry = view->pending; if (placement_find_best(view, &geometry)) { view_move(view, geometry.x, geometry.y); return; } } view_center(view, NULL); } void view_constrain_size_to_that_of_usable_area(struct view *view) { if (!view || !view->output || view->fullscreen) { return; } struct wlr_box usable_area = output_usable_area_in_layout_coords(view->output); struct border margin = ssd_get_margin(view->ssd); int available_width = usable_area.width - margin.left - margin.right; int available_height = usable_area.height - margin.top - margin.bottom; if (available_width <= 0 || available_height <= 0) { return; } if (available_height >= view->pending.height && available_width >= view->pending.width) { return; } int width = MIN(view->pending.width, available_width); int height = MIN(view->pending.height, available_height); int right_edge = usable_area.x + usable_area.width; int bottom_edge = usable_area.y + usable_area.height; int x = MAX(usable_area.x + margin.left, MIN(view->pending.x, right_edge - width - margin.right)); int y = MAX(usable_area.y + margin.top, MIN(view->pending.y, bottom_edge - height - margin.bottom)); struct wlr_box box = { .x = x, .y = y, .width = width, .height = height, }; view_move_resize(view, box); } static void view_apply_natural_geometry(struct view *view) { assert(view); assert(view_is_floating(view)); struct wlr_box geometry = view->natural_geometry; adjust_floating_geometry(view, &geometry, /* midpoint_visibility */ false); view_move_resize(view, geometry); } static void view_apply_region_geometry(struct view *view) { assert(view); assert(view->tiled_region || view->tiled_region_evacuate); struct output *output = view->output; assert(output_is_usable(output)); if (view->tiled_region_evacuate) { /* View was evacuated from a destroying output */ /* Get new output local region, may be NULL */ view->tiled_region = regions_from_name( view->tiled_region_evacuate, output); /* Get rid of the evacuate instruction */ zfree(view->tiled_region_evacuate); if (!view->tiled_region) { /* Existing region name doesn't exist in rc.xml anymore */ view_set_untiled(view); view_apply_natural_geometry(view); return; } } /* Create a copy of the original region geometry */ struct wlr_box geo = view->tiled_region->geo; /* Adjust for rc.gap */ if (rc.gap) { double half_gap = rc.gap / 2.0; struct wlr_fbox offset = { .x = half_gap, .y = half_gap, .width = -rc.gap, .height = -rc.gap }; struct wlr_box usable = output_usable_area_in_layout_coords(output); if (geo.x == usable.x) { offset.x += half_gap; offset.width -= half_gap; } if (geo.y == usable.y) { offset.y += half_gap; offset.height -= half_gap; } if (geo.x + geo.width == usable.x + usable.width) { offset.width -= half_gap; } if (geo.y + geo.height == usable.y + usable.height) { offset.height -= half_gap; } geo.x += offset.x; geo.y += offset.y; geo.width += offset.width; geo.height += offset.height; } /* And adjust for current view */ struct border margin = ssd_get_margin(view->ssd); geo.x += margin.left; geo.y += margin.top; geo.width -= margin.left + margin.right; geo.height -= margin.top + margin.bottom; view_move_resize(view, geo); } static void view_apply_tiled_geometry(struct view *view) { assert(view); assert(view->tiled); assert(output_is_usable(view->output)); view_move_resize(view, view_get_edge_snap_box(view, view->output, view->tiled)); } static void view_apply_fullscreen_geometry(struct view *view) { assert(view); assert(view->fullscreen); assert(output_is_usable(view->output)); struct wlr_box box = { 0 }; wlr_output_effective_resolution(view->output->wlr_output, &box.width, &box.height); double ox = 0, oy = 0; wlr_output_layout_output_coords(view->server->output_layout, view->output->wlr_output, &ox, &oy); box.x -= ox; box.y -= oy; view_move_resize(view, box); } static void view_apply_maximized_geometry(struct view *view) { assert(view); assert(view->maximized != VIEW_AXIS_NONE); struct output *output = view->output; assert(output_is_usable(output)); struct wlr_box box = output_usable_area_in_layout_coords(output); if (box.height == output->wlr_output->height && output->wlr_output->scale != 1) { box.height /= output->wlr_output->scale; } if (box.width == output->wlr_output->width && output->wlr_output->scale != 1) { box.width /= output->wlr_output->scale; } /* * If one axis (horizontal or vertical) is unmaximized, it * should use the natural geometry. But if that geometry is not * on-screen on the output where the view is maximized, then * center the unmaximized axis. */ struct wlr_box natural = view->natural_geometry; if (view->maximized != VIEW_AXIS_BOTH) { struct wlr_box intersect; wlr_box_intersection(&intersect, &box, &natural); if (wlr_box_empty(&intersect)) { view_compute_centered_position(view, NULL, natural.width, natural.height, &natural.x, &natural.y); } } if (view->ssd_enabled) { struct border border = ssd_thickness(view); box.x += border.left; box.y += border.top; box.width -= border.right + border.left; box.height -= border.top + border.bottom; } if (view->maximized == VIEW_AXIS_VERTICAL) { box.x = natural.x; box.width = natural.width; } else if (view->maximized == VIEW_AXIS_HORIZONTAL) { box.y = natural.y; box.height = natural.height; } view_move_resize(view, box); } static void view_apply_special_geometry(struct view *view) { assert(view); assert(!view_is_floating(view)); if (!output_is_usable(view->output)) { wlr_log(WLR_ERROR, "view has no output, not updating geometry"); return; } if (view->fullscreen) { view_apply_fullscreen_geometry(view); } else if (view->maximized != VIEW_AXIS_NONE) { view_apply_maximized_geometry(view); } else if (view->tiled) { view_apply_tiled_geometry(view); } else if (view->tiled_region || view->tiled_region_evacuate) { view_apply_region_geometry(view); } else { assert(false); // not reached } } /* For internal use only. Does not update geometry. */ static void set_maximized(struct view *view, enum view_axis maximized) { if (view->impl->maximize) { view->impl->maximize(view, (maximized == VIEW_AXIS_BOTH)); } if (view->toplevel.handle) { wlr_foreign_toplevel_handle_v1_set_maximized( view->toplevel.handle, (maximized == VIEW_AXIS_BOTH)); } view->maximized = maximized; /* * Ensure that follow-up actions like SnapToEdge / SnapToRegion * use up-to-date SSD margin information. Otherwise we will end * up using an outdated ssd->margin to calculate offsets. */ ssd_update_margin(view->ssd); } /* * Un-maximize view and move it to specific geometry. Does not reset * tiled state (use view_set_untiled() if you want that). */ void view_restore_to(struct view *view, struct wlr_box geometry) { assert(view); if (view->fullscreen) { return; } if (view->maximized != VIEW_AXIS_NONE) { set_maximized(view, VIEW_AXIS_NONE); } view_move_resize(view, geometry); } bool view_is_tiled(struct view *view) { assert(view); return (view->tiled || view->tiled_region || view->tiled_region_evacuate); } bool view_is_floating(struct view *view) { assert(view); return !(view->fullscreen || (view->maximized != VIEW_AXIS_NONE) || view_is_tiled(view)); } static void view_notify_tiled(struct view *view) { assert(view); if (view->impl->notify_tiled) { view->impl->notify_tiled(view); } } /* Reset tiled state of view without changing geometry */ void view_set_untiled(struct view *view) { assert(view); view->tiled = VIEW_EDGE_INVALID; view->tiled_region = NULL; zfree(view->tiled_region_evacuate); view_notify_tiled(view); } void view_maximize(struct view *view, enum view_axis axis, bool store_natural_geometry) { assert(view); if (view->maximized == axis) { return; } if (view->fullscreen) { return; } view_set_shade(view, false); if (axis != VIEW_AXIS_NONE) { /* * Maximize via keybind or client request cancels * interactive move/resize since we can't move/resize * a maximized view. */ interactive_cancel(view); if (store_natural_geometry && view_is_floating(view)) { view_store_natural_geometry(view); view_invalidate_last_layout_geometry(view); } } set_maximized(view, axis); if (view_is_floating(view)) { view_apply_natural_geometry(view); } else { view_apply_special_geometry(view); } } void view_toggle_maximize(struct view *view, enum view_axis axis) { assert(view); switch (axis) { case VIEW_AXIS_HORIZONTAL: case VIEW_AXIS_VERTICAL: /* Toggle one axis (XOR) */ view_maximize(view, view->maximized ^ axis, /*store_natural_geometry*/ true); break; case VIEW_AXIS_BOTH: /* * Maximize in both directions if unmaximized or partially * maximized, otherwise unmaximize. */ view_maximize(view, (view->maximized == VIEW_AXIS_BOTH) ? VIEW_AXIS_NONE : VIEW_AXIS_BOTH, /*store_natural_geometry*/ true); break; default: break; } } void view_toggle_decorations(struct view *view) { assert(view); /* Reject decoration toggles when shaded */ if (view->shaded) { return; } if (rc.ssd_keep_border && view->ssd_enabled && view->ssd && !view->ssd_titlebar_hidden) { /* * ssd_titlebar_hidden has to be set before calling * ssd_titlebar_hide() to make ssd_thickness() happy. */ view->ssd_titlebar_hidden = true; ssd_titlebar_hide(view->ssd); if (!view_is_floating(view)) { view_apply_special_geometry(view); } return; } view_set_decorations(view, !view->ssd_enabled); } bool view_is_always_on_top(struct view *view) { assert(view); return view->scene_tree->node.parent == view->server->view_tree_always_on_top; } void view_toggle_always_on_top(struct view *view) { assert(view); if (view_is_always_on_top(view)) { view->workspace = view->server->workspace_current; wlr_scene_node_reparent(&view->scene_tree->node, view->workspace->tree); } else { wlr_scene_node_reparent(&view->scene_tree->node, view->server->view_tree_always_on_top); } } bool view_is_always_on_bottom(struct view *view) { assert(view); return view->scene_tree->node.parent == view->server->view_tree_always_on_bottom; } void view_toggle_always_on_bottom(struct view *view) { assert(view); if (view_is_always_on_bottom(view)) { view->workspace = view->server->workspace_current; wlr_scene_node_reparent(&view->scene_tree->node, view->workspace->tree); } else { wlr_scene_node_reparent(&view->scene_tree->node, view->server->view_tree_always_on_bottom); } } void view_toggle_visible_on_all_workspaces(struct view *view) { assert(view); view->visible_on_all_workspaces = !view->visible_on_all_workspaces; } void view_move_to_workspace(struct view *view, struct workspace *workspace) { assert(view); assert(workspace); if (view->workspace != workspace) { view->workspace = workspace; wlr_scene_node_reparent(&view->scene_tree->node, workspace->tree); } } static void decorate(struct view *view) { if (!view->ssd) { view->ssd = ssd_create(view, view == view->server->active_view); } } static void undecorate(struct view *view) { ssd_destroy(view->ssd); view->ssd = NULL; } void view_set_decorations(struct view *view, bool decorations) { assert(view); if (view->ssd_enabled != decorations && !view->fullscreen) { /* * Set view->ssd_enabled first since it is referenced * within the call tree of ssd_create() */ view->ssd_enabled = decorations; if (decorations) { decorate(view); } else { undecorate(view); view->ssd_titlebar_hidden = false; } if (!view_is_floating(view)) { view_apply_special_geometry(view); } } } void view_toggle_fullscreen(struct view *view) { assert(view); view_set_fullscreen(view, !view->fullscreen); } /* For internal use only. Does not update geometry. */ static void set_fullscreen(struct view *view, bool fullscreen) { /* When going fullscreen, unshade the window */ if (fullscreen) { view_set_shade(view, false); } /* Hide decorations when going fullscreen */ if (fullscreen && view->ssd_enabled) { undecorate(view); } if (view->impl->set_fullscreen) { view->impl->set_fullscreen(view, fullscreen); } if (view->toplevel.handle) { wlr_foreign_toplevel_handle_v1_set_fullscreen( view->toplevel.handle, fullscreen); } view->fullscreen = fullscreen; /* Re-show decorations when no longer fullscreen */ if (!fullscreen && view->ssd_enabled) { decorate(view); } /* Show fullscreen views above top-layer */ if (view->output) { desktop_update_top_layer_visiblity(view->server); } } void view_set_fullscreen(struct view *view, bool fullscreen) { assert(view); if (fullscreen == view->fullscreen) { return; } if (fullscreen) { if (!output_is_usable(view->output)) { /* Prevent fullscreen with no available outputs */ return; } /* * Fullscreen via keybind or client request cancels * interactive move/resize since we can't move/resize * a fullscreen view. */ interactive_cancel(view); view_store_natural_geometry(view); view_invalidate_last_layout_geometry(view); } set_fullscreen(view, fullscreen); if (view_is_floating(view)) { view_apply_natural_geometry(view); } else { view_apply_special_geometry(view); } set_adaptive_sync_fullscreen(view); } static bool last_layout_geometry_is_valid(struct view *view) { return view->last_layout_geometry.width > 0 && view->last_layout_geometry.height > 0; } static void update_last_layout_geometry(struct view *view) { /* * Only update an invalid last-layout geometry to prevent a series of * successive layout changes from continually replacing the "preferred" * location with whatever location the view currently holds. The * "preferred" location should be whatever state was set by user * interaction, not automatic responses to layout changes. */ if (last_layout_geometry_is_valid(view)) { return; } if (view_is_floating(view)) { view->last_layout_geometry = view->pending; } else { view->last_layout_geometry = view->natural_geometry; } } static bool apply_last_layout_geometry(struct view *view, bool force_update) { /* Only apply a valid last-layout geometry */ if (!last_layout_geometry_is_valid(view)) { return false; } /* * Unless forced, the last-layout geometry is only applied * when the relevant view geometry is distinct. */ if (!force_update) { struct wlr_box *relevant = view_is_floating(view) ? &view->pending : &view->natural_geometry; if (wlr_box_equal(relevant, &view->last_layout_geometry)) { return false; } } view->natural_geometry = view->last_layout_geometry; adjust_floating_geometry(view, &view->natural_geometry, /* midpoint_visibility */ true); return true; } void view_invalidate_last_layout_geometry(struct view *view) { assert(view); view->last_layout_geometry.width = 0; view->last_layout_geometry.height = 0; } void view_adjust_for_layout_change(struct view *view) { assert(view); bool was_fullscreen = view->fullscreen; bool is_floating = view_is_floating(view); if (!output_is_usable(view->output)) { /* A view losing an output should have a last-layout geometry */ update_last_layout_geometry(view); /* Exit fullscreen and re-assess floating status */ if (was_fullscreen) { set_fullscreen(view, false); is_floating = view_is_floating(view); } } /* Restore any full-screen window to natural geometry */ bool use_natural = was_fullscreen; /* Capture a pointer to the last-layout geometry (only if valid) */ struct wlr_box *last_geometry = NULL; if (last_layout_geometry_is_valid(view)) { last_geometry = &view->last_layout_geometry; } /* * Check if an output change is required: * - Floating views are always mapped to the nearest output * - Any view without a usable output needs to be repositioned * - Any view with a valid last-layout geometry might be better * positioned on another output */ if (is_floating || last_geometry || !output_is_usable(view->output)) { /* Move the view to an appropriate output, if needed */ bool output_changed = view_discover_output(view, last_geometry); /* * Try to apply the last-layout to the natural geometry * (adjusting to ensure that it fits on the screen). This is * forced if the output has changed, but will be done * opportunistically even on the same output if the last-layout * geometry is different from the view's governing geometry. */ if (apply_last_layout_geometry(view, output_changed)) { use_natural = true; } /* * Whether or not the view has moved, the layout has changed. * Ensure that the view now has a valid last-layout geometry. */ update_last_layout_geometry(view); } if (!is_floating) { view_apply_special_geometry(view); } else if (use_natural) { /* * Move the window to its natural location, either because it * was fullscreen or we are trying to restore a prior layout. */ view_apply_natural_geometry(view); } else { /* Otherwise, just ensure the view is on screen. */ struct wlr_box geometry = view->pending; if (adjust_floating_geometry(view, &geometry, /* midpoint_visibility */ true)) { view_move_resize(view, geometry); } } view_update_outputs(view); } void view_evacuate_region(struct view *view) { assert(view); assert(view->tiled_region); if (!view->tiled_region_evacuate) { view->tiled_region_evacuate = xstrdup(view->tiled_region->name); } view->tiled_region = NULL; } void view_on_output_destroy(struct view *view) { assert(view); /* * This is the only time we modify view->output for a fullscreen * view. We expect view_adjust_for_layout_change() to be called * shortly afterward, which will exit fullscreen. */ view->output = NULL; } struct output * view_get_adjacent_output(struct view *view, enum view_edge edge) { assert(view); struct output *output = view->output; if (!output_is_usable(output)) { wlr_log(WLR_ERROR, "view has no output, cannot find adjacent output"); return NULL; } /* Determine any adjacent output in the appropriate direction */ struct wlr_output *new_output = NULL; struct wlr_output *current_output = output->wlr_output; struct wlr_output_layout *layout = view->server->output_layout; switch (edge) { case VIEW_EDGE_LEFT: new_output = wlr_output_layout_adjacent_output( layout, WLR_DIRECTION_LEFT, current_output, 1, 0); break; case VIEW_EDGE_RIGHT: new_output = wlr_output_layout_adjacent_output( layout, WLR_DIRECTION_RIGHT, current_output, 1, 0); break; case VIEW_EDGE_UP: new_output = wlr_output_layout_adjacent_output( layout, WLR_DIRECTION_UP, current_output, 0, 1); break; case VIEW_EDGE_DOWN: new_output = wlr_output_layout_adjacent_output( layout, WLR_DIRECTION_DOWN, current_output, 0, 1); break; default: break; } /* When "adjacent" output is the same as the original, there is no adjacent */ if (!new_output || new_output == current_output) { return NULL; } output = output_from_wlr_output(view->server, new_output); if (!output_is_usable(output)) { wlr_log(WLR_ERROR, "invalid output in layout"); return NULL; } return output; } static int shift_view_to_usable_1d(int size, int cur_pos, int cur_lo, int cur_extent, int next_pos, int next_lo, int next_extent, int margin_lo, int margin_hi) { int cur_min = cur_lo + rc.gap + margin_lo; int cur_max = cur_lo + cur_extent - rc.gap - margin_hi; int next_min = next_lo + rc.gap + margin_lo; int next_max = next_lo + next_extent - rc.gap - margin_hi; /* * If the view is fully within the usable area of its original display, * ensure that it is also fully within the usable area of the target. */ if (cur_pos >= cur_min && cur_pos + size <= cur_max) { if (next_pos >= next_min && next_pos + size > next_max) { next_pos = next_max - size; } return MAX(next_pos, next_min); } /* * If the view was not fully within the usable area of its original * display, kick it onscreen if its midpoint will be off the target. */ int midpoint = next_pos + size / 2; if (next_pos >= next_min && midpoint > next_lo + next_extent) { next_pos = next_max - size; } return MAX(next_pos, next_min); } void view_move_to_edge(struct view *view, enum view_edge direction, bool snap_to_windows) { assert(view); if (!output_is_usable(view->output)) { wlr_log(WLR_ERROR, "view has no output, not moving to edge"); return; } int dx = 0, dy = 0; snap_move_to_edge(view, direction, snap_to_windows, &dx, &dy); if (dx != 0 || dy != 0) { /* Move the window if a change was discovered */ view_move(view, view->pending.x + dx, view->pending.y + dy); return; } /* If the view is maximized, do not attempt to jump displays */ if (view->maximized != VIEW_AXIS_NONE) { return; } /* Otherwise, move to edge of next adjacent display, if possible */ struct output *output = view_get_adjacent_output(view, direction); if (!output) { return; } /* When jumping to next output, attach to edge nearest the motion */ struct wlr_box usable = output_usable_area_in_layout_coords(output); struct border margin = ssd_get_margin(view->ssd); /* Bounds of the possible placement zone in this output */ int left = usable.x + rc.gap + margin.left; int right = usable.x + usable.width - rc.gap - margin.right; int top = usable.y + rc.gap + margin.top; int bottom = usable.y + usable.height - rc.gap - margin.bottom; /* Default target position on new output is current target position */ int destination_x = view->pending.x; int destination_y = view->pending.y; /* Compute the new position in the direction of motion */ direction = view_edge_invert(direction); switch (direction) { case VIEW_EDGE_LEFT: destination_x = left; break; case VIEW_EDGE_RIGHT: destination_x = right - view->pending.width; break; case VIEW_EDGE_UP: destination_y = top; break; case VIEW_EDGE_DOWN: destination_y = bottom - view_effective_height(view, /* use_pending */ true); break; default: return; } struct wlr_box original_usable = output_usable_area_in_layout_coords(view->output); /* Make sure the window is appropriately in view along the x direction */ destination_x = shift_view_to_usable_1d(view->pending.width, view->pending.x, original_usable.x, original_usable.width, destination_x, usable.x, usable.width, margin.left, margin.right); /* Make sure the window is appropriately in view along the y direction */ int eff_height = view_effective_height(view, /* use_pending */ true); destination_y = shift_view_to_usable_1d(eff_height, view->pending.y, original_usable.y, original_usable.height, destination_y, usable.y, usable.height, margin.top, margin.bottom); view_set_untiled(view); view_set_output(view, output); view_move(view, destination_x, destination_y); } void view_grow_to_edge(struct view *view, enum view_edge direction) { assert(view); /* TODO: allow grow to edge if maximized along the other axis */ if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) { return; } if (!output_is_usable(view->output)) { wlr_log(WLR_ERROR, "view has no output, not growing view"); return; } view_set_shade(view, false); struct wlr_box geo; snap_grow_to_next_edge(view, direction, &geo); view_move_resize(view, geo); } void view_shrink_to_edge(struct view *view, enum view_edge direction) { assert(view); /* TODO: allow shrink to edge if maximized along the other axis */ if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) { return; } if (!output_is_usable(view->output)) { wlr_log(WLR_ERROR, "view has no output, not shrinking view"); return; } view_set_shade(view, false); struct wlr_box geo = view->pending; snap_shrink_to_next_edge(view, direction, &geo); view_move_resize(view, geo); } enum view_axis view_axis_parse(const char *direction) { if (!direction) { return VIEW_AXIS_NONE; } if (!strcasecmp(direction, "horizontal")) { return VIEW_AXIS_HORIZONTAL; } else if (!strcasecmp(direction, "vertical")) { return VIEW_AXIS_VERTICAL; } else if (!strcasecmp(direction, "both")) { return VIEW_AXIS_BOTH; } else { return VIEW_AXIS_NONE; } } enum view_edge view_edge_parse(const char *direction) { if (!direction) { return VIEW_EDGE_INVALID; } if (!strcasecmp(direction, "left")) { return VIEW_EDGE_LEFT; } else if (!strcasecmp(direction, "up")) { return VIEW_EDGE_UP; } else if (!strcasecmp(direction, "right")) { return VIEW_EDGE_RIGHT; } else if (!strcasecmp(direction, "down")) { return VIEW_EDGE_DOWN; } else if (!strcasecmp(direction, "center")) { return VIEW_EDGE_CENTER; } else { return VIEW_EDGE_INVALID; } } void view_snap_to_edge(struct view *view, enum view_edge edge, bool across_outputs, bool store_natural_geometry) { assert(view); if (view->fullscreen) { return; } struct output *output = view->output; if (!output_is_usable(output)) { wlr_log(WLR_ERROR, "view has no output, not snapping to edge"); return; } view_set_shade(view, false); if (across_outputs && view->tiled == edge && view->maximized == VIEW_AXIS_NONE) { /* We are already tiled for this edge; try to switch outputs */ output = view_get_adjacent_output(view, edge); if (!output) { /* * No more output to move to * * We re-apply the tiled geometry without changing any * state because the window might have been moved away * (and thus got untiled) and then snapped back to the * original edge. */ view_apply_tiled_geometry(view); return; } /* When switching outputs, jump to the opposite edge */ edge = view_edge_invert(edge); } if (view->maximized != VIEW_AXIS_NONE) { /* Unmaximize + keep using existing natural_geometry */ view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false); } else if (store_natural_geometry) { /* store current geometry as new natural_geometry */ view_store_natural_geometry(view); view_invalidate_last_layout_geometry(view); } view_set_untiled(view); view_set_output(view, output); view->tiled = edge; view_notify_tiled(view); view_apply_tiled_geometry(view); } void view_snap_to_region(struct view *view, struct region *region, bool store_natural_geometry) { assert(view); assert(region); if (view->fullscreen) { return; } /* view_apply_region_geometry() needs a usable output */ if (!output_is_usable(view->output)) { wlr_log(WLR_ERROR, "view has no output, not snapping to region"); return; } view_set_shade(view, false); if (view->maximized != VIEW_AXIS_NONE) { /* Unmaximize + keep using existing natural_geometry */ view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false); } else if (store_natural_geometry) { /* store current geometry as new natural_geometry */ view_store_natural_geometry(view); view_invalidate_last_layout_geometry(view); } view_set_untiled(view); view->tiled_region = region; view_notify_tiled(view); view_apply_region_geometry(view); } void view_move_to_output(struct view *view, struct output *output) { assert(view); if (view->fullscreen) { return; } view_invalidate_last_layout_geometry(view); view_set_output(view, output); if (view_is_floating(view)) { struct wlr_box output_area = output_usable_area_in_layout_coords(output); view->pending.x = output_area.x; view->pending.y = output_area.y; view_place_initial(view, /* allow_cursor */ false); } else if (view->maximized != VIEW_AXIS_NONE) { view_apply_maximized_geometry(view); } else if (view->tiled) { view_apply_tiled_geometry(view); } else if (view->tiled_region) { struct region *region = regions_from_name(view->tiled_region->name, output); view_snap_to_region(view, region, /*store_natural_geometry*/ false); } } static void for_each_subview(struct view *view, void (*action)(struct view *)) { struct wl_array subviews; struct view **subview; wl_array_init(&subviews); view_append_children(view, &subviews); wl_array_for_each(subview, &subviews) { action(*subview); } wl_array_release(&subviews); } static void move_to_front(struct view *view) { if (view->impl->move_to_front) { view->impl->move_to_front(view); } view->server->last_raised_view = view; } static void move_to_back(struct view *view) { if (view->impl->move_to_back) { view->impl->move_to_back(view); } if (view == view->server->last_raised_view) { view->server->last_raised_view = NULL; } } /* * In the view_move_to_{front,back} functions, a modal dialog is always * shown above its parent window, and the two always move together, so * other windows cannot come between them. * This is consistent with GTK3/Qt5 applications on mutter and openbox. */ void view_move_to_front(struct view *view) { assert(view); /* * This function is called often, generally on every mouse * button press (more often for focus-follows-mouse). Avoid * unnecessarily raising the same view over and over, or * attempting to raise a root view above its own sub-view. */ struct view *last = view->server->last_raised_view; if (view == last || (last && view == view_get_root(last))) { return; } struct view *root = view_get_root(view); assert(root); move_to_front(root); for_each_subview(root, move_to_front); /* make sure view is in front of other sub-views */ if (view != root) { move_to_front(view); } cursor_update_focus(view->server); } void view_move_to_back(struct view *view) { assert(view); struct view *root = view_get_root(view); assert(root); for_each_subview(root, move_to_back); move_to_back(root); cursor_update_focus(view->server); } struct view * view_get_root(struct view *view) { assert(view); if (view->impl->get_root) { return view->impl->get_root(view); } return view; } void view_append_children(struct view *view, struct wl_array *children) { assert(view); if (view->impl->append_children) { view->impl->append_children(view, children); } } bool view_is_related(struct view *view, struct wlr_surface *surface) { assert(view); assert(surface); if (view->impl->is_related) { return view->impl->is_related(view, surface); } return false; } bool view_has_strut_partial(struct view *view) { assert(view); return view->impl->has_strut_partial && view->impl->has_strut_partial(view); } const char * view_get_string_prop(struct view *view, const char *prop) { assert(view); assert(prop); if (view->impl->get_string_prop) { return view->impl->get_string_prop(view, prop); } return ""; } void view_update_title(struct view *view) { assert(view); const char *title = view_get_string_prop(view, "title"); if (!view->toplevel.handle || !title) { return; } ssd_update_title(view->ssd); wlr_foreign_toplevel_handle_v1_set_title(view->toplevel.handle, title); } void view_update_app_id(struct view *view) { assert(view); const char *app_id = view_get_string_prop(view, "app_id"); if (!view->toplevel.handle || !app_id) { return; } wlr_foreign_toplevel_handle_v1_set_app_id( view->toplevel.handle, app_id); } void view_reload_ssd(struct view *view) { assert(view); if (view->ssd_enabled && !view->fullscreen) { undecorate(view); decorate(view); } } void view_toggle_keybinds(struct view *view) { assert(view); view->inhibits_keybinds = !view->inhibits_keybinds; if (view->inhibits_keybinds) { view->server->seat.nr_inhibited_keybind_views++; } else { view->server->seat.nr_inhibited_keybind_views--; } if (view->ssd_enabled) { ssd_enable_keybind_inhibit_indicator(view->ssd, view->inhibits_keybinds); } } void mappable_connect(struct mappable *mappable, struct wlr_surface *surface, wl_notify_func_t notify_map, wl_notify_func_t notify_unmap) { assert(mappable); assert(!mappable->connected); mappable->map.notify = notify_map; wl_signal_add(&surface->events.map, &mappable->map); mappable->unmap.notify = notify_unmap; wl_signal_add(&surface->events.unmap, &mappable->unmap); mappable->connected = true; } void mappable_disconnect(struct mappable *mappable) { assert(mappable); assert(mappable->connected); wl_list_remove(&mappable->map.link); wl_list_remove(&mappable->unmap.link); mappable->connected = false; } static void handle_map(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, mappable.map); view->impl->map(view); } static void handle_unmap(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, mappable.unmap); view->impl->unmap(view, /* client_request */ true); } /* * TODO: after the release of wlroots 0.17, consider incorporating this * function into a more general view_set_surface() function, which could * connect other surface event handlers (like commit) as well. */ void view_connect_map(struct view *view, struct wlr_surface *surface) { assert(view); mappable_connect(&view->mappable, surface, handle_map, handle_unmap); } void view_set_shade(struct view *view, bool shaded) { assert(view); if (view->shaded == shaded) { return; } /* Views without a title-bar or SSD cannot be shaded */ if (shaded && (!view->ssd || view->ssd_titlebar_hidden)) { return; } /* If this window is being resized, cancel the resize when shading */ if (shaded && view->server->input_mode == LAB_INPUT_STATE_RESIZE) { interactive_cancel(view); } view->shaded = shaded; ssd_enable_shade(view->ssd, view->shaded); wlr_scene_node_set_enabled(view->scene_node, !view->shaded); } void view_destroy(struct view *view) { assert(view); struct server *server = view->server; if (view->mappable.connected) { mappable_disconnect(&view->mappable); } wl_list_remove(&view->request_move.link); wl_list_remove(&view->request_resize.link); wl_list_remove(&view->request_minimize.link); wl_list_remove(&view->request_maximize.link); wl_list_remove(&view->request_fullscreen.link); wl_list_remove(&view->set_title.link); wl_list_remove(&view->destroy.link); if (view->toplevel.handle) { wlr_foreign_toplevel_handle_v1_destroy(view->toplevel.handle); } if (server->grabbed_view == view) { /* Application got killed while moving around */ server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; server->grabbed_view = NULL; regions_hide_overlay(&server->seat); } if (server->active_view == view) { server->active_view = NULL; } if (server->last_raised_view == view) { server->last_raised_view = NULL; } if (server->seat.pressed.view == view) { seat_reset_pressed(&server->seat); } if (view->tiled_region_evacuate) { zfree(view->tiled_region_evacuate); } if (view->inhibits_keybinds) { view->inhibits_keybinds = false; server->seat.nr_inhibited_keybind_views--; } osd_on_view_destroy(view); undecorate(view); /* * The layer-shell top-layer is disabled when an application is running * in fullscreen mode, so if that's the case, we may have to re-enable * it here. */ if (view->fullscreen && view->output) { view->fullscreen = false; desktop_update_top_layer_visiblity(server); if (rc.adaptive_sync == LAB_ADAPTIVE_SYNC_FULLSCREEN) { set_adaptive_sync_fullscreen(view); } } /* If we spawned a window menu, close it */ if (server->menu_current && server->menu_current->triggered_by_view == view) { menu_close_root(server); } /* * Destroy the view's scene tree. View methods assume this is non-NULL, * so we should avoid any calls to those between this and freeing the * view. */ if (view->scene_tree) { wlr_scene_node_destroy(&view->scene_tree->node); view->scene_tree = NULL; } /* Remove view from server->views */ wl_list_remove(&view->link); free(view); cursor_update_focus(server); } �����������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/window-rules.c����������������������������������������������������������������������0000664�0000000�0000000�00000006617�14570443012�0016213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <stdbool.h> #include <cairo.h> #include <glib.h> #include <strings.h> #include <wlr/util/log.h> #include "action.h" #include "common/match.h" #include "config/rcxml.h" #include "labwc.h" #include "view.h" #include "window-rules.h" static bool other_instances_exist(struct view *self, const char *id, const char *title) { struct wl_list *views = &self->server->views; const char *prop = NULL; struct view *view; wl_list_for_each(view, views, link) { if (view == self) { continue; } if (id) { prop = view_get_string_prop(view, "app_id"); if (prop && !strcmp(prop, id)) { return true; } } if (title) { prop = view_get_string_prop(view, "title"); if (prop && !strcmp(prop, title)) { return true; } } } return false; } /* Try to match against identifier AND title (if set) */ static bool view_matches_criteria(struct window_rule *rule, struct view *view) { const char *id = view_get_string_prop(view, "app_id"); const char *title = view_get_string_prop(view, "title"); if (rule->match_once && other_instances_exist(view, id, title)) { return false; } if (rule->identifier && rule->title) { if (!id || !title) { return false; } return match_glob(rule->identifier, id) && match_glob(rule->title, title); } else if (rule->identifier) { if (!id) { return false; } return match_glob(rule->identifier, id); } else if (rule->title) { if (!title) { return false; } return match_glob(rule->title, title); } else { wlr_log(WLR_ERROR, "rule has no identifier or title\n"); return false; } } void window_rules_apply(struct view *view, enum window_rule_event event) { struct window_rule *rule; wl_list_for_each(rule, &rc.window_rules, link) { if (rule->event != event) { continue; } if (view_matches_criteria(rule, view)) { actions_run(view, view->server, &rule->actions, 0); } } } enum property window_rules_get_property(struct view *view, const char *property) { assert(property); /* * We iterate in reverse here because later items in list have higher * priority. For example, in the config below we want the return value * for foot's "serverDecoration" property to be "default". * * <windowRules> * <windowRule identifier="*" serverDecoration="no"/> * <windowRule identifier="foot" serverDecoration="default"/> * </windowRules> */ struct window_rule *rule; wl_list_for_each_reverse(rule, &rc.window_rules, link) { /* * Only return if property != LAB_PROP_UNSPECIFIED otherwise a * <windowRule> which does not set a particular property * attribute would still return here if that property was asked * for. */ if (view_matches_criteria(rule, view)) { if (rule->server_decoration && !strcasecmp(property, "serverDecoration")) { return rule->server_decoration; } if (rule->skip_taskbar && !strcasecmp(property, "skipTaskbar")) { return rule->skip_taskbar; } if (rule->skip_window_switcher && !strcasecmp(property, "skipWindowSwitcher")) { return rule->skip_window_switcher; } if (rule->ignore_focus_request && !strcasecmp(property, "ignoreFocusRequest")) { return rule->ignore_focus_request; } if (rule->fixed_position && !strcasecmp(property, "fixedPosition")) { return rule->fixed_position; } } } return LAB_PROP_UNSPECIFIED; } �����������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/workspaces.c������������������������������������������������������������������������0000664�0000000�0000000�00000026235�14570443012�0015733�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <cairo.h> #include <pango/pangocairo.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include "config.h" #include "buffer.h" #include "common/font.h" #include "common/graphic-helpers.h" #include "common/list.h" #include "common/mem.h" #include "input/keyboard.h" #include "labwc.h" #include "view.h" #include "workspaces.h" #include "xwayland.h" /* Internal helpers */ static size_t parse_workspace_index(const char *name) { /* * We only want to get positive numbers which span the whole string. * * More detailed requirement: * .---------------.--------------. * | Input | Return value | * |---------------+--------------| * | "2nd desktop" | 0 | * | "-50" | 0 | * | "0" | 0 | * | "124" | 124 | * | "1.24" | 0 | * `------------------------------´ * * As atoi() happily parses any numbers until it hits a non-number we * can't really use it for this case. Instead, we use strtol() combined * with further checks for the endptr (remaining non-number characters) * and returned negative numbers. */ long index; char *endptr; errno = 0; index = strtol(name, &endptr, 10); if (errno || *endptr != '\0' || index < 0) { return 0; } return index; } static void _osd_update(struct server *server) { struct theme *theme = server->theme; /* Settings */ uint16_t margin = 10; uint16_t padding = 2; uint16_t rect_height = theme->osd_workspace_switcher_boxes_height; uint16_t rect_width = theme->osd_workspace_switcher_boxes_width; bool hide_boxes = theme->osd_workspace_switcher_boxes_width == 0 || theme->osd_workspace_switcher_boxes_height == 0; /* Dimensions */ size_t workspace_count = wl_list_length(&server->workspaces); uint16_t marker_width = workspace_count * (rect_width + padding) - padding; uint16_t width = margin * 2 + (marker_width < 200 ? 200 : marker_width); uint16_t height = margin * (hide_boxes ? 2 : 3) + rect_height + font_height(&rc.font_osd); cairo_t *cairo; cairo_surface_t *surface; struct workspace *workspace; struct output *output; wl_list_for_each(output, &server->outputs, link) { if (!output_is_usable(output)) { continue; } struct lab_data_buffer *buffer = buffer_create_cairo(width, height, output->wlr_output->scale, true); if (!buffer) { wlr_log(WLR_ERROR, "Failed to allocate buffer for workspace OSD"); continue; } cairo = buffer->cairo; /* Background */ set_cairo_color(cairo, theme->osd_bg_color); cairo_rectangle(cairo, 0, 0, width, height); cairo_fill(cairo); /* Border */ set_cairo_color(cairo, theme->osd_border_color); struct wlr_fbox fbox = { .width = width, .height = height, }; draw_cairo_border(cairo, fbox, theme->osd_border_width); /* Boxes */ uint16_t x; if (!hide_boxes) { x = (width - marker_width) / 2; wl_list_for_each(workspace, &server->workspaces, link) { bool active = workspace == server->workspace_current; set_cairo_color(cairo, server->theme->osd_label_text_color); cairo_rectangle(cairo, x, margin, rect_width - padding, rect_height); cairo_stroke(cairo); if (active) { cairo_rectangle(cairo, x, margin, rect_width - padding, rect_height); cairo_fill(cairo); } x += rect_width + padding; } } /* Text */ set_cairo_color(cairo, server->theme->osd_label_text_color); PangoLayout *layout = pango_cairo_create_layout(cairo); pango_layout_set_width(layout, (width - 2 * margin) * PANGO_SCALE); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); PangoFontDescription *desc = font_to_pango_desc(&rc.font_osd); pango_layout_set_font_description(layout, desc); /* Center workspace indicator on the x axis */ x = font_width(&rc.font_osd, server->workspace_current->name); x = (width - x) / 2; if (!hide_boxes) { cairo_move_to(cairo, x, margin * 2 + rect_height); } else { cairo_move_to(cairo, x, (height - font_height(&rc.font_osd)) / 2.0); } //pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD); pango_layout_set_font_description(layout, desc); pango_font_description_free(desc); pango_layout_set_text(layout, server->workspace_current->name, -1); pango_cairo_show_layout(cairo, layout); g_object_unref(layout); surface = cairo_get_target(cairo); cairo_surface_flush(surface); if (!output->workspace_osd) { output->workspace_osd = wlr_scene_buffer_create( &server->scene->tree, NULL); } /* Position the whole thing */ struct wlr_box output_box; wlr_output_layout_get_box(output->server->output_layout, output->wlr_output, &output_box); int lx = output->usable_area.x + (output->usable_area.width - width) / 2 + output_box.x; int ly = output->usable_area.y + (output->usable_area.height - height) / 2 + output_box.y; wlr_scene_node_set_position(&output->workspace_osd->node, lx, ly); wlr_scene_buffer_set_buffer(output->workspace_osd, &buffer->base); wlr_scene_buffer_set_dest_size(output->workspace_osd, buffer->unscaled_width, buffer->unscaled_height); /* And finally drop the buffer so it will get destroyed on OSD hide */ wlr_buffer_drop(&buffer->base); } } /* Internal API */ static void add_workspace(struct server *server, const char *name) { struct workspace *workspace = znew(*workspace); workspace->server = server; workspace->name = xstrdup(name); workspace->tree = wlr_scene_tree_create(server->view_tree); wl_list_append(&server->workspaces, &workspace->link); if (!server->workspace_current) { server->workspace_current = workspace; } else { wlr_scene_node_set_enabled(&workspace->tree->node, false); } } static struct workspace * get_prev(struct workspace *current, struct wl_list *workspaces, bool wrap) { struct wl_list *target_link = current->link.prev; if (target_link == workspaces) { /* Current workspace is the first one */ if (!wrap) { return NULL; } /* Roll over */ target_link = target_link->prev; } return wl_container_of(target_link, current, link); } static struct workspace * get_next(struct workspace *current, struct wl_list *workspaces, bool wrap) { struct wl_list *target_link = current->link.next; if (target_link == workspaces) { /* Current workspace is the last one */ if (!wrap) { return NULL; } /* Roll over */ target_link = target_link->next; } return wl_container_of(target_link, current, link); } static int _osd_handle_timeout(void *data) { struct seat *seat = data; workspaces_osd_hide(seat); /* Don't re-check */ return 0; } static void _osd_show(struct server *server) { if (!rc.workspace_config.popuptime) { return; } _osd_update(server); struct output *output; wl_list_for_each(output, &server->outputs, link) { if (output_is_usable(output) && output->workspace_osd) { wlr_scene_node_set_enabled(&output->workspace_osd->node, true); } } struct wlr_keyboard *keyboard = &server->seat.keyboard_group->keyboard; if (keyboard_any_modifiers_pressed(keyboard)) { /* Hidden by release of all modifiers */ server->seat.workspace_osd_shown_by_modifier = true; } else { /* Hidden by timer */ if (!server->seat.workspace_osd_timer) { server->seat.workspace_osd_timer = wl_event_loop_add_timer( server->wl_event_loop, _osd_handle_timeout, &server->seat); } wl_event_source_timer_update(server->seat.workspace_osd_timer, rc.workspace_config.popuptime); } } /* Public API */ void workspaces_init(struct server *server) { wl_list_init(&server->workspaces); struct workspace *conf; wl_list_for_each(conf, &rc.workspace_config.workspaces, link) { add_workspace(server, conf->name); } } /* * update_focus should normally be set to true. It is set to false only * when this function is called from desktop_focus_view(), in order to * avoid unnecessary extra focus changes and possible recursion. */ void workspaces_switch_to(struct workspace *target, bool update_focus) { assert(target); struct server *server = target->server; if (target == server->workspace_current) { return; } /* Disable the old workspace */ wlr_scene_node_set_enabled( &server->workspace_current->tree->node, false); /* Move Omnipresent views to new workspace */ struct view *view; enum lab_view_criteria criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; for_each_view(view, &server->views, criteria) { if (view->visible_on_all_workspaces) { view_move_to_workspace(view, target); } } /* Enable the new workspace */ wlr_scene_node_set_enabled(&target->tree->node, true); /* Save the last visited workspace */ server->workspace_last = server->workspace_current; /* Make sure new views will spawn on the new workspace */ server->workspace_current = target; /* * Make sure we are focusing what the user sees. * Only refocus if the focus is not already on an always-on-top view. */ if (update_focus) { struct view *view = server->active_view; if (!view || !view_is_always_on_top(view)) { desktop_focus_topmost_view(server); } } /* And finally show the OSD */ _osd_show(server); #if HAVE_XWAYLAND /* Ensure xwayland internal stacking order corresponds to the current workspace */ xwayland_adjust_stacking_order(server); #endif /* * Make sure we are not carrying around a * cursor image from the previous desktop */ cursor_update_focus(server); /* Ensure that only currently visible fullscreen windows hide the top layer */ desktop_update_top_layer_visiblity(server); } void workspaces_osd_hide(struct seat *seat) { assert(seat); struct output *output; struct server *server = seat->server; wl_list_for_each(output, &server->outputs, link) { if (!output->workspace_osd) { continue; } wlr_scene_node_set_enabled(&output->workspace_osd->node, false); wlr_scene_buffer_set_buffer(output->workspace_osd, NULL); } seat->workspace_osd_shown_by_modifier = false; /* Update the cursor focus in case it was on top of the OSD before */ cursor_update_focus(server); } struct workspace * workspaces_find(struct workspace *anchor, const char *name, bool wrap) { assert(anchor); if (!name) { return NULL; } size_t index = 0; struct workspace *target; size_t wants_index = parse_workspace_index(name); struct wl_list *workspaces = &anchor->server->workspaces; if (wants_index) { wl_list_for_each(target, workspaces, link) { if (wants_index == ++index) { return target; } } } else if (!strcasecmp(name, "current")) { return anchor; } else if (!strcasecmp(name, "last")) { return anchor->server->workspace_last; } else if (!strcasecmp(name, "left")) { return get_prev(anchor, workspaces, wrap); } else if (!strcasecmp(name, "right")) { return get_next(anchor, workspaces, wrap); } else { wl_list_for_each(target, workspaces, link) { if (!strcasecmp(target->name, name)) { return target; } } } wlr_log(WLR_ERROR, "Workspace '%s' not found", name); return NULL; } void workspaces_destroy(struct server *server) { struct workspace *workspace, *tmp; wl_list_for_each_safe(workspace, tmp, &server->workspaces, link) { wlr_scene_node_destroy(&workspace->tree->node); zfree(workspace->name); wl_list_remove(&workspace->link); free(workspace); } assert(wl_list_empty(&server->workspaces)); } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/xdg-popup.c�������������������������������������������������������������������������0000664�0000000�0000000�00000007464�14570443012�0015500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2020 the sway authors * * This file is only needed in support of * - unconstraining XDG popups * - keeping non-layer-shell xdg-popups outside the layers.c code */ #include "common/mem.h" #include "labwc.h" #include "node.h" #include "view.h" struct xdg_popup { struct view *parent_view; struct wlr_xdg_popup *wlr_popup; struct wl_listener commit; struct wl_listener destroy; struct wl_listener new_popup; }; static void popup_unconstrain(struct xdg_popup *popup) { struct view *view = popup->parent_view; struct server *server = view->server; struct wlr_box *popup_box = &popup->wlr_popup->current.geometry; struct wlr_output_layout *output_layout = server->output_layout; struct wlr_output *wlr_output = wlr_output_layout_output_at( output_layout, view->current.x + popup_box->x, view->current.y + popup_box->y); struct wlr_box output_box; wlr_output_layout_get_box(output_layout, wlr_output, &output_box); struct wlr_box output_toplevel_box = { .x = output_box.x - view->current.x, .y = output_box.y - view->current.y, .width = output_box.width, .height = output_box.height, }; wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, &output_toplevel_box); } static void handle_xdg_popup_destroy(struct wl_listener *listener, void *data) { struct xdg_popup *popup = wl_container_of(listener, popup, destroy); wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->new_popup.link); /* Usually already removed unless there was no commit at all */ if (popup->commit.notify) { wl_list_remove(&popup->commit.link); } free(popup); } static void handle_xdg_popup_commit(struct wl_listener *listener, void *data) { struct xdg_popup *popup = wl_container_of(listener, popup, commit); if (popup->wlr_popup->base->initial_commit) { popup_unconstrain(popup); /* Prevent getting called over and over again */ wl_list_remove(&popup->commit.link); popup->commit.notify = NULL; } } static void popup_handle_new_xdg_popup(struct wl_listener *listener, void *data) { struct xdg_popup *popup = wl_container_of(listener, popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; xdg_popup_create(popup->parent_view, wlr_popup); } void xdg_popup_create(struct view *view, struct wlr_xdg_popup *wlr_popup) { struct wlr_xdg_surface *parent = wlr_xdg_surface_try_from_wlr_surface(wlr_popup->parent); if (!parent) { wlr_log(WLR_ERROR, "parent is not a valid XDG surface"); return; } struct xdg_popup *popup = znew(*popup); popup->parent_view = view; popup->wlr_popup = wlr_popup; popup->destroy.notify = handle_xdg_popup_destroy; wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); popup->new_popup.notify = popup_handle_new_xdg_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); popup->commit.notify = handle_xdg_popup_commit; wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); /* * We must add xdg popups to the scene graph so they get rendered. The * wlroots scene graph provides a helper for this, but to use it we must * provide the proper parent scene node of the xdg popup. To enable * this, we always set the user data field of xdg_surfaces to the * corresponding scene node. * * xdg-popups live in server->xdg_popup_tree so that they can be * rendered above always-on-top windows */ struct wlr_scene_tree *parent_tree = NULL; if (parent->role == WLR_XDG_SURFACE_ROLE_POPUP) { parent_tree = parent->surface->data; } else { parent_tree = view->server->xdg_popup_tree; wlr_scene_node_set_position(&view->server->xdg_popup_tree->node, view->current.x, view->current.y); } wlr_popup->base->surface->data = wlr_scene_xdg_surface_create(parent_tree, wlr_popup->base); node_descriptor_create(wlr_popup->base->surface->data, LAB_NODE_DESC_XDG_POPUP, view); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/xdg.c�������������������������������������������������������������������������������0000664�0000000�0000000�00000057343�14570443012�0014340�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <wlr/types/wlr_fractional_scale_v1.h> #include "common/macros.h" #include "common/mem.h" #include "decorations.h" #include "labwc.h" #include "node.h" #include "view.h" #include "view-impl-common.h" #include "window-rules.h" #include "workspaces.h" #define LAB_XDG_SHELL_VERSION (2) #define CONFIGURE_TIMEOUT_MS 100 static struct xdg_toplevel_view * xdg_toplevel_view_from_view(struct view *view) { assert(view->type == LAB_XDG_SHELL_VIEW); return (struct xdg_toplevel_view *)view; } struct wlr_xdg_surface * xdg_surface_from_view(struct view *view) { assert(view->type == LAB_XDG_SHELL_VIEW); struct xdg_toplevel_view *xdg_toplevel_view = (struct xdg_toplevel_view *)view; assert(xdg_toplevel_view->xdg_surface); return xdg_toplevel_view->xdg_surface; } static struct wlr_xdg_toplevel * xdg_toplevel_from_view(struct view *view) { struct wlr_xdg_surface *xdg_surface = xdg_surface_from_view(view); assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); assert(xdg_surface->toplevel); return xdg_surface->toplevel; } static void handle_new_popup(struct wl_listener *listener, void *data) { struct xdg_toplevel_view *xdg_toplevel_view = wl_container_of(listener, xdg_toplevel_view, new_popup); struct view *view = &xdg_toplevel_view->base; struct wlr_xdg_popup *wlr_popup = data; xdg_popup_create(view, wlr_popup); } static bool has_ssd(struct view *view) { /* Window-rules take priority if they exist for this view */ switch (window_rules_get_property(view, "serverDecoration")) { case LAB_PROP_TRUE: return true; case LAB_PROP_FALSE: return false; default: break; } /* * view->ssd_preference may be set by the decoration implementation * e.g. src/decorations/xdg-deco.c or src/decorations/kde-deco.c. */ switch (view->ssd_preference) { case LAB_SSD_PREF_SERVER: return true; case LAB_SSD_PREF_CLIENT: return false; default: /* * We don't know anything about the client preference * so fall back to core.decoration settings in rc.xml */ return rc.xdg_shell_server_side_deco; } } static void handle_commit(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, commit); struct wlr_xdg_surface *xdg_surface = xdg_surface_from_view(view); assert(view->surface); struct wlr_box size; wlr_xdg_surface_get_geometry(xdg_surface, &size); /* * Qt applications occasionally fail to call set_window_geometry * after a configure request, but do correctly update the actual * surface extent. This results in a mismatch between the window * decorations (which follow the logical geometry) and the visual * size of the client area. As a workaround, we try to detect * this case and ignore the out-of-date window geometry. */ if (size.width != view->pending.width || size.height != view->pending.height) { struct wlr_box extent; wlr_surface_get_extends(xdg_surface->surface, &extent); if (extent.width == view->pending.width && extent.height == view->pending.height) { wlr_log(WLR_DEBUG, "window geometry for client (%s) " "appears to be incorrect - ignoring", view_get_string_prop(view, "app_id")); size = extent; /* Use surface extent instead */ } } struct wlr_box *current = &view->current; bool update_required = current->width != size.width || current->height != size.height; uint32_t serial = view->pending_configure_serial; if (serial > 0 && serial == xdg_surface->current.configure_serial) { assert(view->pending_configure_timeout); wl_event_source_remove(view->pending_configure_timeout); view->pending_configure_serial = 0; view->pending_configure_timeout = NULL; update_required = true; } if (update_required) { view_impl_apply_geometry(view, size.width, size.height); /* * Some views (e.g., terminals that scale as multiples of rows * and columns, or windows that impose a fixed aspect ratio), * may respond to a resize but alter the width or height. When * this happens, view->pending will be out of sync with the * actual geometry (size *and* position, depending on the edge * from which the resize was attempted). When no other * configure is pending, re-sync the pending geometry with the * actual view. */ if (!view->pending_configure_serial) { view->pending = view->current; /* * wlroots retains the size set by any call to * wlr_xdg_toplevel_set_size and will send the retained * values with every subsequent configure request. If a * client has resized itself in the meantime, a * configure request that sends the now-outated size * may prompt the client to resize itself unexpectedly. * * Calling wlr_xdg_toplevel_set_size to update the * value held by wlroots is undesirable here, because * that will trigger another configure event and we * don't want to get stuck in a request-response loop. * Instead, just manipulate the dimensions that *would* * be adjusted by the call, so the right values will * apply next time. * * This is not ideal, but it is the cleanest option. */ struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view); toplevel->scheduled.width = view->current.width; toplevel->scheduled.height = view->current.height; } } } static int handle_configure_timeout(void *data) { struct view *view = data; assert(view->pending_configure_serial > 0); assert(view->pending_configure_timeout); const char *app_id = view_get_string_prop(view, "app_id"); wlr_log(WLR_INFO, "client (%s) did not respond to configure request " "in %d ms", app_id, CONFIGURE_TIMEOUT_MS); wl_event_source_remove(view->pending_configure_timeout); view->pending_configure_serial = 0; view->pending_configure_timeout = NULL; view_impl_apply_geometry(view, view->current.width, view->current.height); /* Re-sync pending view with current state */ view->pending = view->current; return 0; /* ignored per wl_event_loop docs */ } static void set_pending_configure_serial(struct view *view, uint32_t serial) { view->pending_configure_serial = serial; if (!view->pending_configure_timeout) { view->pending_configure_timeout = wl_event_loop_add_timer(view->server->wl_event_loop, handle_configure_timeout, view); } wl_event_source_timer_update(view->pending_configure_timeout, CONFIGURE_TIMEOUT_MS); } static void handle_destroy(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, destroy); assert(view->type == LAB_XDG_SHELL_VIEW); struct xdg_toplevel_view *xdg_toplevel_view = xdg_toplevel_view_from_view(view); xdg_toplevel_view->xdg_surface->data = NULL; xdg_toplevel_view->xdg_surface = NULL; /* Remove xdg-shell view specific listeners */ wl_list_remove(&xdg_toplevel_view->set_app_id.link); wl_list_remove(&xdg_toplevel_view->new_popup.link); if (view->pending_configure_timeout) { wl_event_source_remove(view->pending_configure_timeout); view->pending_configure_timeout = NULL; } view_destroy(view); } static void handle_request_move(struct wl_listener *listener, void *data) { /* * This event is raised when a client would like to begin an interactive * move, typically because the user clicked on their client-side * decorations. Note that a more sophisticated compositor should check * the provied serial against a list of button press serials sent to * this * client, to prevent the client from requesting this whenever they * want. */ struct view *view = wl_container_of(listener, view, request_move); interactive_begin(view, LAB_INPUT_STATE_MOVE, 0); } static void handle_request_resize(struct wl_listener *listener, void *data) { /* * This event is raised when a client would like to begin an interactive * resize, typically because the user clicked on their client-side * decorations. Note that a more sophisticated compositor should check * the provied serial against a list of button press serials sent to * this * client, to prevent the client from requesting this whenever they * want. */ struct wlr_xdg_toplevel_resize_event *event = data; struct view *view = wl_container_of(listener, view, request_resize); interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges); } static void handle_request_minimize(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_minimize); view_minimize(view, xdg_toplevel_from_view(view)->requested.minimized); } static void handle_request_maximize(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_maximize); if (!view->mapped && !view->output) { view_set_output(view, output_nearest_to_cursor(view->server)); } bool maximized = xdg_toplevel_from_view(view)->requested.maximized; view_maximize(view, maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE, /*store_natural_geometry*/ true); } static void set_fullscreen_from_request(struct view *view, struct wlr_xdg_toplevel_requested *requested) { if (!view->fullscreen && requested->fullscreen && requested->fullscreen_output) { view_set_output(view, output_from_wlr_output(view->server, requested->fullscreen_output)); } view_set_fullscreen(view, requested->fullscreen); } static void handle_request_fullscreen(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_fullscreen); if (!view->mapped && !view->output) { view_set_output(view, output_nearest_to_cursor(view->server)); } set_fullscreen_from_request(view, &xdg_toplevel_from_view(view)->requested); } static void handle_set_title(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, set_title); view_update_title(view); } static void handle_set_app_id(struct wl_listener *listener, void *data) { struct xdg_toplevel_view *xdg_toplevel_view = wl_container_of(listener, xdg_toplevel_view, set_app_id); struct view *view = &xdg_toplevel_view->base; view_update_app_id(view); } static void xdg_toplevel_view_configure(struct view *view, struct wlr_box geo) { uint32_t serial = 0; view_adjust_size(view, &geo.width, &geo.height); /* * We do not need to send a configure request unless the size * changed (wayland has no notion of a global position). If the * size is the same (and there is no pending configure request) * then we can just move the view directly. */ if (geo.width != view->pending.width || geo.height != view->pending.height) { serial = wlr_xdg_toplevel_set_size(xdg_toplevel_from_view(view), geo.width, geo.height); } view->pending = geo; if (serial > 0) { set_pending_configure_serial(view, serial); } else if (view->pending_configure_serial == 0) { view->current.x = geo.x; view->current.y = geo.y; view_moved(view); } } static void xdg_toplevel_view_close(struct view *view) { wlr_xdg_toplevel_send_close(xdg_toplevel_from_view(view)); } static void xdg_toplevel_view_maximize(struct view *view, bool maximized) { wlr_xdg_toplevel_set_maximized(xdg_toplevel_from_view(view), maximized); } static void xdg_toplevel_view_minimize(struct view *view, bool minimized) { /* noop */ } static struct wlr_xdg_toplevel * top_parent_of(struct view *view) { struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view); while (toplevel->parent) { toplevel = toplevel->parent; } return toplevel; } /* Return the most senior parent (=root) view */ static struct view * xdg_toplevel_view_get_root(struct view *view) { struct wlr_xdg_toplevel *root = top_parent_of(view); struct wlr_xdg_surface *surface = (struct wlr_xdg_surface *)root->base; return (struct view *)surface->data; } static void xdg_toplevel_view_append_children(struct view *self, struct wl_array *children) { struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(self); struct view *view; wl_list_for_each_reverse(view, &self->server->views, link) { if (view == self) { continue; } if (view->type != LAB_XDG_SHELL_VIEW) { continue; } if (!view->mapped && !view->minimized) { continue; } if (top_parent_of(view) != toplevel) { continue; } struct view **child = wl_array_add(children, sizeof(*child)); *child = view; } } static void xdg_toplevel_view_set_activated(struct view *view, bool activated) { wlr_xdg_toplevel_set_activated(xdg_toplevel_from_view(view), activated); } static void xdg_toplevel_view_set_fullscreen(struct view *view, bool fullscreen) { wlr_xdg_toplevel_set_fullscreen(xdg_toplevel_from_view(view), fullscreen); } static void xdg_toplevel_view_notify_tiled(struct view *view) { /* Take no action if xdg-shell tiling is disabled */ if (rc.snap_tiling_events_mode == LAB_TILING_EVENTS_NEVER) { return; } enum wlr_edges edge = WLR_EDGE_NONE; bool want_edge = rc.snap_tiling_events_mode & LAB_TILING_EVENTS_EDGE; bool want_region = rc.snap_tiling_events_mode & LAB_TILING_EVENTS_REGION; /* * Edge-snapped view are considered tiled on the snapped edge and those * perpendicular to it. */ if (want_edge) { switch (view->tiled) { case VIEW_EDGE_LEFT: edge = WLR_EDGE_LEFT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM; break; case VIEW_EDGE_RIGHT: edge = WLR_EDGE_RIGHT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM; break; case VIEW_EDGE_UP: edge = WLR_EDGE_TOP | WLR_EDGE_LEFT | WLR_EDGE_RIGHT; break; case VIEW_EDGE_DOWN: edge = WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT; break; default: edge = WLR_EDGE_NONE; } } if (want_region && view->tiled_region) { /* Region-snapped views are considered tiled on all edges */ edge = WLR_EDGE_LEFT | WLR_EDGE_RIGHT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM; } wlr_xdg_toplevel_set_tiled(xdg_toplevel_from_view(view), edge); } static struct view * lookup_view_by_xdg_toplevel(struct server *server, struct wlr_xdg_toplevel *xdg_toplevel) { struct view *view; wl_list_for_each(view, &server->views, link) { if (view->type != LAB_XDG_SHELL_VIEW) { continue; } if (xdg_toplevel_from_view(view) == xdg_toplevel) { return view; } } return NULL; } static void set_initial_position(struct view *view) { struct wlr_xdg_toplevel *parent_xdg_toplevel = xdg_toplevel_from_view(view)->parent; view_constrain_size_to_that_of_usable_area(view); if (parent_xdg_toplevel) { /* Child views are center-aligned relative to their parents */ struct view *parent = lookup_view_by_xdg_toplevel( view->server, parent_xdg_toplevel); assert(parent); view_set_output(view, parent->output); view_center(view, &parent->pending); return; } /* All other views are placed according to a configured strategy */ view_place_initial(view, /* allow_cursor */ true); } static const char * xdg_toplevel_view_get_string_prop(struct view *view, const char *prop) { struct xdg_toplevel_view *xdg_view = xdg_toplevel_view_from_view(view); struct wlr_xdg_toplevel *xdg_toplevel = xdg_view->xdg_surface ? xdg_view->xdg_surface->toplevel : NULL; if (!xdg_toplevel) { /* * This may happen due to a matchOnce rule when * a view is destroyed while A-Tab is open. See * https://github.com/labwc/labwc/issues/1082#issuecomment-1716137180 */ return ""; } if (!strcmp(prop, "title")) { return xdg_toplevel->title; } if (!strcmp(prop, "app_id")) { return xdg_toplevel->app_id; } return ""; } static void init_foreign_toplevel(struct view *view) { foreign_toplevel_handle_create(view); struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view); if (!toplevel->parent) { return; } struct wlr_xdg_surface *surface = toplevel->parent->base; struct view *parent = surface->data; if (!parent->toplevel.handle) { return; } wlr_foreign_toplevel_handle_v1_set_parent(view->toplevel.handle, parent->toplevel.handle); } static void xdg_toplevel_view_map(struct view *view) { if (view->mapped) { return; } view->mapped = true; /* * An output should have been chosen when the surface was first * created, but take one more opportunity to assign an output if not. */ if (!view->output) { view_set_output(view, output_nearest_to_cursor(view->server)); } struct wlr_xdg_surface *xdg_surface = xdg_surface_from_view(view); view->surface = xdg_surface->surface; wlr_scene_node_set_enabled(&view->scene_tree->node, true); if (!view->been_mapped) { struct wlr_xdg_toplevel_requested *requested = &xdg_toplevel_from_view(view)->requested; init_foreign_toplevel(view); view_set_decorations(view, has_ssd(view)); /* * Set initial "pending" dimensions (may be modified by * view_set_fullscreen/view_maximize() below). "Current" * dimensions remain zero until handle_commit(). */ if (wlr_box_empty(&view->pending)) { struct wlr_box size; wlr_xdg_surface_get_geometry(xdg_surface, &size); view->pending.width = size.width; view->pending.height = size.height; } /* * Set initial "pending" position for floating views. * Do this before view_set_fullscreen/view_maximize() so * that the position is saved with the natural geometry. * * FIXME: the natural geometry is not saved if either * handle_request_fullscreen/handle_request_maximize() * is called before map (try "foot --maximized"). */ if (view_is_floating(view)) { set_initial_position(view); } set_fullscreen_from_request(view, requested); view_maximize(view, requested->maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE, /*store_natural_geometry*/ true); /* * Set initial "current" position directly before * calling view_moved() to reduce flicker */ view->current.x = view->pending.x; view->current.y = view->pending.y; view_moved(view); } view->commit.notify = handle_commit; wl_signal_add(&xdg_surface->surface->events.commit, &view->commit); view_impl_map(view); view->been_mapped = true; } static void xdg_toplevel_view_unmap(struct view *view, bool client_request) { if (view->mapped) { view->mapped = false; wlr_scene_node_set_enabled(&view->scene_tree->node, false); wl_list_remove(&view->commit.link); view_impl_unmap(view); } } static const struct view_impl xdg_toplevel_view_impl = { .configure = xdg_toplevel_view_configure, .close = xdg_toplevel_view_close, .get_string_prop = xdg_toplevel_view_get_string_prop, .map = xdg_toplevel_view_map, .set_activated = xdg_toplevel_view_set_activated, .set_fullscreen = xdg_toplevel_view_set_fullscreen, .notify_tiled = xdg_toplevel_view_notify_tiled, .unmap = xdg_toplevel_view_unmap, .maximize = xdg_toplevel_view_maximize, .minimize = xdg_toplevel_view_minimize, .move_to_front = view_impl_move_to_front, .move_to_back = view_impl_move_to_back, .get_root = xdg_toplevel_view_get_root, .append_children = xdg_toplevel_view_append_children, }; static void xdg_activation_handle_request(struct wl_listener *listener, void *data) { const struct wlr_xdg_activation_v1_request_activate_event *event = data; struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(event->surface); if (!xdg_surface) { return; } struct view *view = xdg_surface->data; if (!view) { wlr_log(WLR_INFO, "Not activating surface - no view attached to surface"); return; } if (!event->token->seat) { wlr_log(WLR_INFO, "Denying focus request, seat wasn't supplied"); return; } /* * We do not check for event->token->surface here because it may already * be destroyed and thus being NULL. With wlroots 0.17 we can hook into * the `new_token` signal, attach further information to the token and * then react to that information here instead. For now we just check * for the seat / serial being correct and then allow the request. */ if (window_rules_get_property(view, "ignoreFocusRequest") == LAB_PROP_TRUE) { wlr_log(WLR_INFO, "Ignoring focus request due to window rule configuration"); return; } wlr_log(WLR_DEBUG, "Activating surface"); desktop_focus_view(view, /*raise*/ true); } /* * We use the following struct user_data pointers: * - wlr_xdg_surface->data = view * for the wlr_xdg_toplevel_decoration_v1 implementation * - wlr_surface->data = scene_tree * to help the popups find their parent nodes */ static void xdg_surface_new(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, new_xdg_surface); struct wlr_xdg_surface *xdg_surface = data; /* * We deal with popups in xdg-popup.c and layers.c as they have to be * treated differently */ if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { return; } wlr_xdg_surface_ping(xdg_surface); struct xdg_toplevel_view *xdg_toplevel_view = znew(*xdg_toplevel_view); struct view *view = &xdg_toplevel_view->base; view->server = server; view->type = LAB_XDG_SHELL_VIEW; view->impl = &xdg_toplevel_view_impl; xdg_toplevel_view->xdg_surface = xdg_surface; /* * Pick an output for the surface as soon as its created, so that the * client can be notified about any fractional scale before it is given * the chance to configure itself (and possibly pick its dimensions). */ view_set_output(view, output_nearest_to_cursor(server)); if (view->output) { wlr_fractional_scale_v1_notify_scale(xdg_surface->surface, view->output->wlr_output->scale); } view->workspace = server->workspace_current; view->scene_tree = wlr_scene_tree_create(view->workspace->tree); wlr_scene_node_set_enabled(&view->scene_tree->node, false); struct wlr_scene_tree *tree = wlr_scene_xdg_surface_create( view->scene_tree, xdg_surface); if (!tree) { /* TODO: might need further clean up */ wl_resource_post_no_memory(xdg_surface->resource); free(xdg_toplevel_view); return; } view->scene_node = &tree->node; node_descriptor_create(&view->scene_tree->node, LAB_NODE_DESC_VIEW, view); /* * The xdg_toplevel_decoration and kde_server_decoration protocols * expects clients to use client side decorations unless server side * decorations are negotiated. So we default to client side ones here. * * TODO: We may want to assign the default based on a new rc.xml * config option like "enforce-server" in the future. */ view->ssd_preference = LAB_SSD_PREF_CLIENT; /* * xdg_toplevel_decoration and kde_server_decoration use this * pointer to connect the view to a decoration object that may * be created in the future. */ xdg_surface->data = view; /* * GTK4 initializes the decorations on the wl_surface before * converting it into a xdg surface. This call takes care of * connecting the view to an existing decoration. If there * is no existing decoration object available for the * wl_surface, this call is a no-op. */ kde_server_decoration_set_view(view, xdg_surface->surface); /* In support of xdg popups */ xdg_surface->surface->data = tree; view_connect_map(view, xdg_surface->surface); CONNECT_SIGNAL(xdg_surface, view, destroy); struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel; CONNECT_SIGNAL(toplevel, view, request_move); CONNECT_SIGNAL(toplevel, view, request_resize); CONNECT_SIGNAL(toplevel, view, request_minimize); CONNECT_SIGNAL(toplevel, view, request_maximize); CONNECT_SIGNAL(toplevel, view, request_fullscreen); CONNECT_SIGNAL(toplevel, view, set_title); /* Events specific to XDG toplevel views */ CONNECT_SIGNAL(toplevel, xdg_toplevel_view, set_app_id); CONNECT_SIGNAL(xdg_surface, xdg_toplevel_view, new_popup); wl_list_insert(&server->views, &view->link); } void xdg_shell_init(struct server *server) { server->xdg_shell = wlr_xdg_shell_create(server->wl_display, LAB_XDG_SHELL_VERSION); if (!server->xdg_shell) { wlr_log(WLR_ERROR, "unable to create the XDG shell interface"); exit(EXIT_FAILURE); } server->new_xdg_surface.notify = xdg_surface_new; wl_signal_add(&server->xdg_shell->events.new_surface, &server->new_xdg_surface); server->xdg_activation = wlr_xdg_activation_v1_create(server->wl_display); if (!server->xdg_activation) { wlr_log(WLR_ERROR, "unable to create xdg_activation interface"); exit(EXIT_FAILURE); } server->xdg_activation_request.notify = xdg_activation_handle_request; wl_signal_add(&server->xdg_activation->events.request_activate, &server->xdg_activation_request); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/xwayland-unmanaged.c����������������������������������������������������������������0000664�0000000�0000000�00000017112�14570443012�0017330�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <wlr/xwayland.h> #include "common/list.h" #include "common/macros.h" #include "common/mem.h" #include "labwc.h" #include "xwayland.h" static void handle_request_configure(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, request_configure); struct wlr_xwayland_surface *xsurface = unmanaged->xwayland_surface; struct wlr_xwayland_surface_configure_event *ev = data; wlr_xwayland_surface_configure(xsurface, ev->x, ev->y, ev->width, ev->height); if (unmanaged->node) { wlr_scene_node_set_position(unmanaged->node, ev->x, ev->y); cursor_update_focus(unmanaged->server); } } static void handle_set_geometry(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, set_geometry); struct wlr_xwayland_surface *xsurface = unmanaged->xwayland_surface; if (unmanaged->node) { wlr_scene_node_set_position(unmanaged->node, xsurface->x, xsurface->y); cursor_update_focus(unmanaged->server); } } static void handle_map(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, mappable.map); struct wlr_xwayland_surface *xsurface = unmanaged->xwayland_surface; assert(!unmanaged->node); /* Stack new surface on top */ wlr_xwayland_surface_restack(xsurface, NULL, XCB_STACK_MODE_ABOVE); wl_list_append(&unmanaged->server->unmanaged_surfaces, &unmanaged->link); CONNECT_SIGNAL(xsurface, unmanaged, set_geometry); if (wlr_xwayland_or_surface_wants_focus(xsurface)) { seat_focus_surface(&unmanaged->server->seat, xsurface->surface); } /* node will be destroyed automatically once surface is destroyed */ unmanaged->node = &wlr_scene_surface_create( unmanaged->server->unmanaged_tree, xsurface->surface)->buffer->node; wlr_scene_node_set_position(unmanaged->node, xsurface->x, xsurface->y); cursor_update_focus(unmanaged->server); } static void focus_next_surface(struct server *server, struct wlr_xwayland_surface *xsurface) { /* * Try to focus on parent surface * This seems to fix JetBrains/Intellij window focus issues */ if (xsurface->parent && xsurface->parent->surface && wlr_xwayland_or_surface_wants_focus(xsurface->parent)) { seat_focus_surface(&server->seat, xsurface->parent->surface); return; } /* Try to focus on last created unmanaged xwayland surface */ struct xwayland_unmanaged *u; struct wl_list *list = &server->unmanaged_surfaces; wl_list_for_each_reverse(u, list, link) { struct wlr_xwayland_surface *prev = u->xwayland_surface; if (wlr_xwayland_or_surface_wants_focus(prev)) { seat_focus_surface(&server->seat, prev->surface); return; } } /* * If we don't find a surface to focus fall back * to the topmost mapped view. This fixes dmenu * not giving focus back when closed with ESC. */ desktop_focus_topmost_view(server); } static void handle_unmap(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, mappable.unmap); struct wlr_xwayland_surface *xsurface = unmanaged->xwayland_surface; struct seat *seat = &unmanaged->server->seat; assert(unmanaged->node); wl_list_remove(&unmanaged->link); wl_list_remove(&unmanaged->set_geometry.link); wlr_scene_node_set_enabled(unmanaged->node, false); /* * Mark the node as gone so a racing configure event * won't try to reposition the node while unmapped. */ unmanaged->node = NULL; cursor_update_focus(unmanaged->server); if (seat->seat->keyboard_state.focused_surface == xsurface->surface) { focus_next_surface(unmanaged->server, xsurface); } } static void handle_associate(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, associate); assert(unmanaged->xwayland_surface && unmanaged->xwayland_surface->surface); mappable_connect(&unmanaged->mappable, unmanaged->xwayland_surface->surface, handle_map, handle_unmap); } static void handle_dissociate(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, dissociate); if (!unmanaged->mappable.connected) { /* * In some cases wlroots fails to emit the associate event * due to an early return in xwayland_surface_associate(). * This is arguably a wlroots bug, but nevertheless it * should not bring down labwc. * * TODO: Potentially remove when starting to track * wlroots 0.18 and it got fixed upstream. */ wlr_log(WLR_ERROR, "dissociate received before associate"); return; } mappable_disconnect(&unmanaged->mappable); } static void handle_destroy(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, destroy); if (unmanaged->mappable.connected) { mappable_disconnect(&unmanaged->mappable); } wl_list_remove(&unmanaged->associate.link); wl_list_remove(&unmanaged->dissociate.link); wl_list_remove(&unmanaged->request_configure.link); wl_list_remove(&unmanaged->set_override_redirect.link); wl_list_remove(&unmanaged->request_activate.link); wl_list_remove(&unmanaged->destroy.link); free(unmanaged); } static void handle_set_override_redirect(struct wl_listener *listener, void *data) { wlr_log(WLR_DEBUG, "handle unmanaged override_redirect"); struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, set_override_redirect); struct wlr_xwayland_surface *xsurface = unmanaged->xwayland_surface; struct server *server = unmanaged->server; bool mapped = xsurface->surface && xsurface->surface->mapped; if (mapped) { handle_unmap(&unmanaged->mappable.unmap, NULL); } handle_destroy(&unmanaged->destroy, NULL); xwayland_view_create(server, xsurface, mapped); } static void handle_request_activate(struct wl_listener *listener, void *data) { wlr_log(WLR_DEBUG, "handle unmanaged request_activate"); struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, request_activate); struct wlr_xwayland_surface *xsurface = unmanaged->xwayland_surface; if (!xsurface->surface || !xsurface->surface->mapped) { return; } struct server *server = unmanaged->server; struct seat *seat = &server->seat; /* * Validate that the unmanaged surface trying to grab focus is actually * a child of the topmost mapped view before granting the request. */ struct view *view = desktop_topmost_focusable_view(server); if (view && view->type == LAB_XWAYLAND_VIEW) { struct wlr_xwayland_surface *surf = wlr_xwayland_surface_try_from_wlr_surface(view->surface); if (surf && surf->pid != xsurface->pid) { return; } } seat_focus_surface(seat, xsurface->surface); } void xwayland_unmanaged_create(struct server *server, struct wlr_xwayland_surface *xsurface, bool mapped) { struct xwayland_unmanaged *unmanaged = znew(*unmanaged); unmanaged->server = server; unmanaged->xwayland_surface = xsurface; /* * xsurface->data is presumed to be a (struct view *) if set, * so it must be left NULL for an unmanaged surface (it should * be NULL already at this point). */ assert(!xsurface->data); CONNECT_SIGNAL(xsurface, unmanaged, associate); CONNECT_SIGNAL(xsurface, unmanaged, dissociate); CONNECT_SIGNAL(xsurface, unmanaged, destroy); CONNECT_SIGNAL(xsurface, unmanaged, request_activate); CONNECT_SIGNAL(xsurface, unmanaged, request_configure); CONNECT_SIGNAL(xsurface, unmanaged, set_override_redirect); if (xsurface->surface) { handle_associate(&unmanaged->associate, NULL); } if (mapped) { handle_map(&unmanaged->mappable.map, NULL); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/src/xwayland.c��������������������������������������������������������������������������0000664�0000000�0000000�00000110602�14570443012�0015371�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <stdlib.h> #include <wlr/xwayland.h> #include "common/array.h" #include "common/macros.h" #include "common/mem.h" #include "labwc.h" #include "node.h" #include "ssd.h" #include "view.h" #include "view-impl-common.h" #include "window-rules.h" #include "workspaces.h" #include "xwayland.h" xcb_atom_t atoms[ATOM_LEN] = {0}; static void xwayland_view_unmap(struct view *view, bool client_request); bool xwayland_surface_contains_window_type( struct wlr_xwayland_surface *surface, enum atom window_type) { assert(surface); for (size_t i = 0; i < surface->window_type_len; i++) { if (surface->window_type[i] == atoms[window_type]) { return true; } } return false; } static struct view_size_hints xwayland_view_get_size_hints(struct view *view) { xcb_size_hints_t *hints = xwayland_surface_from_view(view)->size_hints; if (!hints) { return (struct view_size_hints){0}; } return (struct view_size_hints){ .min_width = hints->min_width, .min_height = hints->min_height, .width_inc = hints->width_inc, .height_inc = hints->height_inc, .base_width = hints->base_width, .base_height = hints->base_height, }; } static enum view_wants_focus xwayland_view_wants_focus(struct view *view) { struct wlr_xwayland_surface *xsurface = xwayland_surface_from_view(view); switch (wlr_xwayland_icccm_input_model(xsurface)) { /* * Abbreviated from ICCCM section 4.1.7 (Input Focus): * * Passive Input - The client expects keyboard input but never * explicitly sets the input focus. * Locally Active Input - The client expects keyboard input and * explicitly sets the input focus, but it only does so when one * of its windows already has the focus. * * Passive and Locally Active clients set the input field of * WM_HINTS to True, which indicates that they require window * manager assistance in acquiring the input focus. */ case WLR_ICCCM_INPUT_MODEL_PASSIVE: case WLR_ICCCM_INPUT_MODEL_LOCAL: return VIEW_WANTS_FOCUS_ALWAYS; /* * Globally Active Input - The client expects keyboard input and * explicitly sets the input focus, even when it is in windows * the client does not own. ... It wants to prevent the window * manager from setting the input focus to any of its windows * [because it may or may not want focus]. * * Globally Active client windows may receive a WM_TAKE_FOCUS * message from the window manager. If they want the focus, they * should respond with a SetInputFocus request. * * [Currently, labwc does not fully support clients voluntarily * taking focus via the WM_TAKE_FOCUS + SetInputFocus mechanism. * Instead, we try to guess whether the window wants focus based * on some heuristics -- see below.] */ case WLR_ICCCM_INPUT_MODEL_GLOBAL: /* * Assume the window does want focus if it wants window * decorations (according to _MOTIF_WM_HINTS). This is * a stop-gap fix to ensure that various applications * (mainly Java-based ones such as IntelliJ IDEA) get * focus normally and appear in the window switcher. It * would be better to match based on _NET_WM_WINDOW_TYPE * instead, but that property isn't currently available * through wlroots API. */ return (xsurface->decorations == WLR_XWAYLAND_SURFACE_DECORATIONS_ALL) ? VIEW_WANTS_FOCUS_ALWAYS : VIEW_WANTS_FOCUS_OFFER; /* * No Input - The client never expects keyboard input. * * No Input and Globally Active clients set the input field to * False, which requests that the window manager not set the * input focus to their top-level window. */ case WLR_ICCCM_INPUT_MODEL_NONE: break; } return VIEW_WANTS_FOCUS_NEVER; } static bool xwayland_view_has_strut_partial(struct view *view) { struct wlr_xwayland_surface *xsurface = xwayland_surface_from_view(view); return (bool)xsurface->strut_partial; } static struct wlr_xwayland_surface * top_parent_of(struct view *view) { struct wlr_xwayland_surface *s = xwayland_surface_from_view(view); while (s->parent) { s = s->parent; } return s; } static struct xwayland_view * xwayland_view_from_view(struct view *view) { assert(view->type == LAB_XWAYLAND_VIEW); return (struct xwayland_view *)view; } struct wlr_xwayland_surface * xwayland_surface_from_view(struct view *view) { struct xwayland_view *xwayland_view = xwayland_view_from_view(view); assert(xwayland_view->xwayland_surface); return xwayland_view->xwayland_surface; } static void ensure_initial_geometry_and_output(struct view *view) { if (wlr_box_empty(&view->current)) { struct wlr_xwayland_surface *xwayland_surface = xwayland_surface_from_view(view); view->current.x = xwayland_surface->x; view->current.y = xwayland_surface->y; view->current.width = xwayland_surface->width; view->current.height = xwayland_surface->height; /* * If there is no pending move/resize yet, then set * current values (used in map()). */ if (wlr_box_empty(&view->pending)) { view->pending = view->current; } } if (!view->output) { /* * Just use the cursor output since we don't know yet * whether the surface position is meaningful. */ view_set_output(view, output_nearest_to_cursor(view->server)); } } static bool want_deco(struct wlr_xwayland_surface *xwayland_surface) { struct view *view = (struct view *)xwayland_surface->data; /* Window-rules take priority if they exist for this view */ switch (window_rules_get_property(view, "serverDecoration")) { case LAB_PROP_TRUE: return true; case LAB_PROP_FALSE: return false; default: break; } return xwayland_surface->decorations == WLR_XWAYLAND_SURFACE_DECORATIONS_ALL; } static void handle_commit(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, commit); assert(data && data == view->surface); /* Must receive commit signal before accessing surface->current* */ struct wlr_surface_state *state = &view->surface->current; struct wlr_box *current = &view->current; /* * If there is a pending move/resize, wait until the surface * size changes to update geometry. The hope is to update both * the position and the size of the view at the same time, * reducing visual glitches. */ if (current->width != state->width || current->height != state->height) { view_impl_apply_geometry(view, state->width, state->height); } } static void handle_request_move(struct wl_listener *listener, void *data) { /* * This event is raised when a client would like to begin an interactive * move, typically because the user clicked on their client-side * decorations. Note that a more sophisticated compositor should check * the provided serial against a list of button press serials sent to * this client, to prevent the client from requesting this whenever they * want. */ struct view *view = wl_container_of(listener, view, request_move); interactive_begin(view, LAB_INPUT_STATE_MOVE, 0); } static void handle_request_resize(struct wl_listener *listener, void *data) { /* * This event is raised when a client would like to begin an interactive * resize, typically because the user clicked on their client-side * decorations. Note that a more sophisticated compositor should check * the provided serial against a list of button press serials sent to * this client, to prevent the client from requesting this whenever they * want. */ struct wlr_xwayland_resize_event *event = data; struct view *view = wl_container_of(listener, view, request_resize); interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges); } static void handle_associate(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, associate); assert(xwayland_view->xwayland_surface && xwayland_view->xwayland_surface->surface); view_connect_map(&xwayland_view->base, xwayland_view->xwayland_surface->surface); } static void handle_dissociate(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, dissociate); if (!xwayland_view->base.mappable.connected) { /* * In some cases wlroots fails to emit the associate event * due to an early return in xwayland_surface_associate(). * This is arguably a wlroots bug, but nevertheless it * should not bring down labwc. * * TODO: Potentially remove when starting to track * wlroots 0.18 and it got fixed upstream. */ wlr_log(WLR_ERROR, "dissociate received before associate"); return; } mappable_disconnect(&xwayland_view->base.mappable); } static void handle_surface_destroy(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, surface_destroy); assert(data && data == view->surface); view->surface = NULL; wl_list_remove(&view->surface_destroy.link); } static void handle_destroy(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, destroy); struct xwayland_view *xwayland_view = xwayland_view_from_view(view); assert(xwayland_view->xwayland_surface->data == view); if (view->surface) { /* * We got the destroy signal from * wlr_xwayland_surface before the * destroy signal from wlr_surface. */ wl_list_remove(&view->surface_destroy.link); } view->surface = NULL; /* * Break view <-> xsurface association. Note that the xsurface * may not actually be destroyed at this point; it may become an * "unmanaged" surface instead (in that case it is important * that xsurface->data not point to the destroyed view). */ xwayland_view->xwayland_surface->data = NULL; xwayland_view->xwayland_surface = NULL; /* Remove XWayland view specific listeners */ wl_list_remove(&xwayland_view->associate.link); wl_list_remove(&xwayland_view->dissociate.link); wl_list_remove(&xwayland_view->request_activate.link); wl_list_remove(&xwayland_view->request_configure.link); wl_list_remove(&xwayland_view->set_class.link); wl_list_remove(&xwayland_view->set_decorations.link); wl_list_remove(&xwayland_view->set_override_redirect.link); wl_list_remove(&xwayland_view->set_strut_partial.link); wl_list_remove(&xwayland_view->set_window_type.link); view_destroy(view); } static void xwayland_view_configure(struct view *view, struct wlr_box geo) { view->pending = geo; wlr_xwayland_surface_configure(xwayland_surface_from_view(view), geo.x, geo.y, geo.width, geo.height); /* * For unknown reasons, XWayland surfaces that are completely * offscreen seem not to generate commit events. In rare cases, * this can prevent an offscreen window from moving onscreen * (since we wait for a commit event that never occurs). As a * workaround, move offscreen surfaces immediately. */ bool is_offscreen = !wlr_box_empty(&view->current) && !wlr_output_layout_intersects(view->server->output_layout, NULL, &view->current); /* If not resizing, process the move immediately */ if (is_offscreen || (view->current.width == geo.width && view->current.height == geo.height)) { view->current.x = geo.x; view->current.y = geo.y; view_moved(view); } } static void handle_request_configure(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_configure); struct view *view = &xwayland_view->base; struct wlr_xwayland_surface_configure_event *event = data; if (view_is_floating(view)) { /* Honor client configure requests for floating views */ struct wlr_box box = {.x = event->x, .y = event->y, .width = event->width, .height = event->height}; view_adjust_size(view, &box.width, &box.height); xwayland_view_configure(view, box); } else { /* * Do not allow clients to request geometry other than * what we computed for maximized/fullscreen/tiled * views. Ignore the client request and send back a * ConfigureNotify event with the computed geometry. */ xwayland_view_configure(view, view->pending); } } static void handle_request_activate(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, request_activate); struct view *view = &xwayland_view->base; if (window_rules_get_property(view, "ignoreFocusRequest") == LAB_PROP_TRUE) { wlr_log(WLR_INFO, "Ignoring focus request due to window rule configuration"); return; } desktop_focus_view(view, /*raise*/ true); } static void handle_request_minimize(struct wl_listener *listener, void *data) { struct wlr_xwayland_minimize_event *event = data; struct view *view = wl_container_of(listener, view, request_minimize); view_minimize(view, event->minimize); } static void handle_request_maximize(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_maximize); if (!view->mapped) { ensure_initial_geometry_and_output(view); /* * Set decorations early to avoid changing geometry * after maximize (reduces visual glitches). */ view_set_decorations(view, want_deco(xwayland_surface_from_view(view))); } view_toggle_maximize(view, VIEW_AXIS_BOTH); } static void handle_request_fullscreen(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_fullscreen); bool fullscreen = xwayland_surface_from_view(view)->fullscreen; if (!view->mapped) { ensure_initial_geometry_and_output(view); } view_set_fullscreen(view, fullscreen); } static void handle_set_title(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, set_title); view_update_title(view); } static void handle_set_class(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_class); struct view *view = &xwayland_view->base; view_update_app_id(view); } static void xwayland_view_close(struct view *view) { wlr_xwayland_surface_close(xwayland_surface_from_view(view)); } static const char * xwayland_view_get_string_prop(struct view *view, const char *prop) { struct xwayland_view *xwayland_view = xwayland_view_from_view(view); struct wlr_xwayland_surface *xwayland_surface = xwayland_view->xwayland_surface; if (!xwayland_surface) { /* * This may happen due to a matchOnce rule when * a view is destroyed while A-Tab is open. See * https://github.com/labwc/labwc/issues/1082#issuecomment-1716137180 */ return ""; } if (!strcmp(prop, "title")) { return xwayland_surface->title; } if (!strcmp(prop, "class")) { return xwayland_surface->class; } /* We give 'class' for wlr_foreign_toplevel_handle_v1_set_app_id() */ if (!strcmp(prop, "app_id")) { return xwayland_surface->class; } return ""; } static void handle_set_decorations(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_decorations); struct view *view = &xwayland_view->base; view_set_decorations(view, want_deco(xwayland_view->xwayland_surface)); } static void handle_set_window_type(struct wl_listener *listener, void *data) { /* Intentionally left blank */ } static void handle_set_override_redirect(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_override_redirect); struct view *view = &xwayland_view->base; struct wlr_xwayland_surface *xsurface = xwayland_view->xwayland_surface; struct server *server = view->server; bool mapped = xsurface->surface && xsurface->surface->mapped; if (mapped) { xwayland_view_unmap(view, /* client_request */ true); } handle_destroy(&view->destroy, xsurface); /* view is invalid after this point */ xwayland_unmanaged_create(server, xsurface, mapped); } static void handle_set_strut_partial(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_strut_partial); struct view *view = &xwayland_view->base; if (view->mapped) { output_update_all_usable_areas(view->server, false); } } static void set_initial_position(struct view *view, struct wlr_xwayland_surface *xwayland_surface) { /* Don't center views with position explicitly specified */ bool has_position = xwayland_surface->size_hints && (xwayland_surface->size_hints->flags & ( XCB_ICCCM_SIZE_HINT_US_POSITION | XCB_ICCCM_SIZE_HINT_P_POSITION)); if (has_position) { /* * Make sure a floating view is onscreen. For a * maximized/fullscreen view, do nothing; if it is * unmaximized/leaves fullscreen later, we will make * sure it is on-screen at that point. */ if (view_is_floating(view)) { view_adjust_for_layout_change(view); } } else { view_constrain_size_to_that_of_usable_area(view); if (view_is_floating(view)) { view_place_initial(view, /* allow_cursor */ true); } else { /* * View is maximized/fullscreen. Center the * stored natural geometry without actually * moving the view. */ view_compute_centered_position(view, NULL, view->natural_geometry.width, view->natural_geometry.height, &view->natural_geometry.x, &view->natural_geometry.y); } } } static void init_foreign_toplevel(struct view *view) { foreign_toplevel_handle_create(view); struct wlr_xwayland_surface *surface = xwayland_surface_from_view(view); if (!surface->parent) { return; } struct view *parent = (struct view *)surface->parent->data; if (!parent || !parent->toplevel.handle) { return; } wlr_foreign_toplevel_handle_v1_set_parent(view->toplevel.handle, parent->toplevel.handle); } static void xwayland_view_map(struct view *view) { struct wlr_xwayland_surface *xwayland_surface = xwayland_surface_from_view(view); if (view->mapped) { return; } if (!xwayland_surface->surface) { /* * We may get here if a user minimizes an xwayland dialog at the * same time as the client requests unmap, which xwayland * clients sometimes do without actually requesting destroy * even if they don't intend to use that view/surface anymore */ wlr_log(WLR_DEBUG, "Cannot map view without wlr_surface"); return; } view->mapped = true; ensure_initial_geometry_and_output(view); wlr_scene_node_set_enabled(&view->scene_tree->node, true); if (view->surface != xwayland_surface->surface) { if (view->surface) { wl_list_remove(&view->surface_destroy.link); } view->surface = xwayland_surface->surface; /* Required to set the surface to NULL when destroyed by the client */ view->surface_destroy.notify = handle_surface_destroy; wl_signal_add(&view->surface->events.destroy, &view->surface_destroy); /* Will be free'd automatically once the surface is being destroyed */ struct wlr_scene_tree *tree = wlr_scene_subsurface_tree_create( view->scene_tree, view->surface); if (!tree) { /* TODO: might need further clean up */ wl_resource_post_no_memory(view->surface->resource); return; } view->scene_node = &tree->node; } /* * Per the Extended Window Manager Hints (EWMH) spec: "The Window * Manager SHOULD honor _NET_WM_STATE whenever a withdrawn window * requests to be mapped." * * The following order of operations is intended to reduce the * number of resize (Configure) events: * 1. set fullscreen state * 2. set decorations (depends on fullscreen state) * 3. set maximized (geometry depends on decorations) */ view_set_fullscreen(view, xwayland_surface->fullscreen); if (!view->been_mapped) { view_set_decorations(view, want_deco(xwayland_surface)); } enum view_axis axis = VIEW_AXIS_NONE; if (xwayland_surface->maximized_horz) { axis |= VIEW_AXIS_HORIZONTAL; } if (xwayland_surface->maximized_vert) { axis |= VIEW_AXIS_VERTICAL; } view_maximize(view, axis, /*store_natural_geometry*/ true); if (!view->toplevel.handle) { init_foreign_toplevel(view); } if (!view->been_mapped) { set_initial_position(view, xwayland_surface); /* * When mapping the view for the first time, visual * artifacts are reduced if we display it immediately at * the final intended position/size rather than waiting * for handle_commit(). */ view->current = view->pending; view_moved(view); } /* Add commit here, as xwayland map/unmap can change the wlr_surface */ wl_signal_add(&xwayland_surface->surface->events.commit, &view->commit); view->commit.notify = handle_commit; view_impl_map(view); view->been_mapped = true; /* Update usable area to account for XWayland "struts" (panels) */ if (xwayland_surface->strut_partial) { output_update_all_usable_areas(view->server, false); } } static void xwayland_view_unmap(struct view *view, bool client_request) { if (!view->mapped) { goto out; } view->mapped = false; wl_list_remove(&view->commit.link); wlr_scene_node_set_enabled(&view->scene_tree->node, false); view_impl_unmap(view); /* Update usable area to account for XWayland "struts" (panels) */ if (xwayland_surface_from_view(view)->strut_partial) { output_update_all_usable_areas(view->server, false); } /* * If the view was explicitly unmapped by the client (rather * than just minimized), destroy the foreign toplevel handle so * the unmapped view doesn't show up in panels and the like. */ out: if (client_request && view->toplevel.handle) { wlr_foreign_toplevel_handle_v1_destroy(view->toplevel.handle); view->toplevel.handle = NULL; } } static void xwayland_view_maximize(struct view *view, bool maximized) { wlr_xwayland_surface_set_maximized(xwayland_surface_from_view(view), maximized); } static void xwayland_view_minimize(struct view *view, bool minimized) { wlr_xwayland_surface_set_minimized(xwayland_surface_from_view(view), minimized); } static void xwayland_view_move_to_front(struct view *view) { view_impl_move_to_front(view); /* * Update XWayland stacking order. * * FIXME: it would be better to restack above the next lower * view, rather than on top of all other surfaces. Restacking * the unmanaged surfaces afterward is ugly and still doesn't * account for always-on-top views. */ wlr_xwayland_surface_restack(xwayland_surface_from_view(view), NULL, XCB_STACK_MODE_ABOVE); /* Restack unmanaged surfaces on top */ struct wl_list *list = &view->server->unmanaged_surfaces; struct xwayland_unmanaged *u; wl_list_for_each(u, list, link) { wlr_xwayland_surface_restack(u->xwayland_surface, NULL, XCB_STACK_MODE_ABOVE); } } static void xwayland_view_move_to_back(struct view *view) { view_impl_move_to_back(view); /* Update XWayland stacking order */ wlr_xwayland_surface_restack(xwayland_surface_from_view(view), NULL, XCB_STACK_MODE_BELOW); } static struct view * xwayland_view_get_root(struct view *view) { struct wlr_xwayland_surface *root = top_parent_of(view); return (struct view *)root->data; } static void xwayland_view_append_children(struct view *self, struct wl_array *children) { struct wlr_xwayland_surface *surface = xwayland_surface_from_view(self); struct view *view; wl_list_for_each_reverse(view, &self->server->views, link) { if (view == self) { continue; } if (view->type != LAB_XWAYLAND_VIEW) { continue; } /* * This happens when a view has never been mapped or when a * client has requested a `handle_unmap`. */ if (!view->surface) { continue; } if (!view->mapped && !view->minimized) { continue; } if (top_parent_of(view) != surface) { continue; } struct view **child = wl_array_add(children, sizeof(*child)); *child = view; } } static bool xwayland_view_is_related(struct view *view, struct wlr_surface *surface) { struct wlr_xwayland_surface *xsurface = xwayland_surface_from_view(view); struct wlr_xwayland_surface *xsurface2 = wlr_xwayland_surface_try_from_wlr_surface(surface); return (xsurface2 && xsurface2->pid == xsurface->pid); } static void xwayland_view_set_activated(struct view *view, bool activated) { struct wlr_xwayland_surface *xwayland_surface = xwayland_surface_from_view(view); if (activated && xwayland_surface->minimized) { wlr_xwayland_surface_set_minimized(xwayland_surface, false); } wlr_xwayland_surface_activate(xwayland_surface, activated); } static void xwayland_view_set_fullscreen(struct view *view, bool fullscreen) { wlr_xwayland_surface_set_fullscreen(xwayland_surface_from_view(view), fullscreen); } static const struct view_impl xwayland_view_impl = { .configure = xwayland_view_configure, .close = xwayland_view_close, .get_string_prop = xwayland_view_get_string_prop, .map = xwayland_view_map, .set_activated = xwayland_view_set_activated, .set_fullscreen = xwayland_view_set_fullscreen, .unmap = xwayland_view_unmap, .maximize = xwayland_view_maximize, .minimize = xwayland_view_minimize, .move_to_front = xwayland_view_move_to_front, .move_to_back = xwayland_view_move_to_back, .get_root = xwayland_view_get_root, .append_children = xwayland_view_append_children, .is_related = xwayland_view_is_related, .get_size_hints = xwayland_view_get_size_hints, .wants_focus = xwayland_view_wants_focus, .has_strut_partial = xwayland_view_has_strut_partial, }; void xwayland_view_create(struct server *server, struct wlr_xwayland_surface *xsurface, bool mapped) { struct xwayland_view *xwayland_view = znew(*xwayland_view); struct view *view = &xwayland_view->base; view->server = server; view->type = LAB_XWAYLAND_VIEW; view->impl = &xwayland_view_impl; /* * Set two-way view <-> xsurface association. Usually the association * remains until the xsurface is destroyed (which also destroys the * view). The only exception is caused by setting override-redirect on * the xsurface, which removes it from the view (destroying the view) * and makes it an "unmanaged" surface. */ xwayland_view->xwayland_surface = xsurface; xsurface->data = view; view->workspace = server->workspace_current; view->scene_tree = wlr_scene_tree_create(view->workspace->tree); node_descriptor_create(&view->scene_tree->node, LAB_NODE_DESC_VIEW, view); CONNECT_SIGNAL(xsurface, view, destroy); CONNECT_SIGNAL(xsurface, view, request_minimize); CONNECT_SIGNAL(xsurface, view, request_maximize); CONNECT_SIGNAL(xsurface, view, request_fullscreen); CONNECT_SIGNAL(xsurface, view, request_move); CONNECT_SIGNAL(xsurface, view, request_resize); CONNECT_SIGNAL(xsurface, view, set_title); /* Events specific to XWayland views */ CONNECT_SIGNAL(xsurface, xwayland_view, associate); CONNECT_SIGNAL(xsurface, xwayland_view, dissociate); CONNECT_SIGNAL(xsurface, xwayland_view, request_activate); CONNECT_SIGNAL(xsurface, xwayland_view, request_configure); CONNECT_SIGNAL(xsurface, xwayland_view, set_class); CONNECT_SIGNAL(xsurface, xwayland_view, set_decorations); CONNECT_SIGNAL(xsurface, xwayland_view, set_override_redirect); CONNECT_SIGNAL(xsurface, xwayland_view, set_strut_partial); CONNECT_SIGNAL(xsurface, xwayland_view, set_window_type); wl_list_insert(&view->server->views, &view->link); if (xsurface->surface) { handle_associate(&xwayland_view->associate, NULL); } if (mapped) { xwayland_view_map(view); } } static void handle_new_surface(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, xwayland_new_surface); struct wlr_xwayland_surface *xsurface = data; wlr_xwayland_surface_ping(xsurface); /* * We do not create 'views' for xwayland override_redirect surfaces, * but add them to server.unmanaged_surfaces so that we can render them */ if (xsurface->override_redirect) { xwayland_unmanaged_create(server, xsurface, /* mapped */ false); } else { xwayland_view_create(server, xsurface, /* mapped */ false); } } static void sync_atoms(xcb_connection_t *xcb_conn) { assert(xcb_conn); wlr_log(WLR_DEBUG, "Syncing X11 atoms"); xcb_intern_atom_cookie_t cookies[ATOM_LEN]; /* First request everything and then loop over the results to reduce latency */ for (size_t i = 0; i < ATOM_LEN; i++) { cookies[i] = xcb_intern_atom(xcb_conn, 0, strlen(atom_names[i]), atom_names[i]); } for (size_t i = 0; i < ATOM_LEN; i++) { xcb_generic_error_t *err = NULL; xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(xcb_conn, cookies[i], &err); if (reply) { atoms[i] = reply->atom; wlr_log(WLR_DEBUG, "Got X11 atom for %s: %u", atom_names[i], reply->atom); } if (err) { wlr_log(WLR_INFO, "Failed to get X11 atom for %s", atom_names[i]); } free(reply); free(err); } } static void handle_server_ready(struct wl_listener *listener, void *data) { xcb_connection_t *xcb_conn = xcb_connect(NULL, NULL); if (xcb_connection_has_error(xcb_conn)) { wlr_log(WLR_ERROR, "Failed to create xcb connection"); /* Just clear all existing atoms */ for (size_t i = 0; i < ATOM_LEN; i++) { atoms[i] = XCB_ATOM_NONE; } return; } wlr_log(WLR_DEBUG, "Connected to xwayland"); sync_atoms(xcb_conn); wlr_log(WLR_DEBUG, "Disconnecting from xwayland"); xcb_disconnect(xcb_conn); } static void handle_xwm_ready(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, xwayland_xwm_ready); wlr_xwayland_set_seat(server->xwayland, server->seat.seat); xwayland_update_workarea(server); } void xwayland_server_init(struct server *server, struct wlr_compositor *compositor) { server->xwayland = wlr_xwayland_create(server->wl_display, compositor, true); if (!server->xwayland) { wlr_log(WLR_ERROR, "cannot create xwayland server"); exit(EXIT_FAILURE); } server->xwayland_new_surface.notify = handle_new_surface; wl_signal_add(&server->xwayland->events.new_surface, &server->xwayland_new_surface); server->xwayland_server_ready.notify = handle_server_ready; wl_signal_add(&server->xwayland->server->events.ready, &server->xwayland_server_ready); server->xwayland_xwm_ready.notify = handle_xwm_ready; wl_signal_add(&server->xwayland->events.ready, &server->xwayland_xwm_ready); if (setenv("DISPLAY", server->xwayland->display_name, true) < 0) { wlr_log_errno(WLR_ERROR, "unable to set DISPLAY for xwayland"); } else { wlr_log(WLR_DEBUG, "xwayland is running on display %s", server->xwayland->display_name); } struct wlr_xcursor *xcursor; xcursor = wlr_xcursor_manager_get_xcursor( server->seat.xcursor_manager, XCURSOR_DEFAULT, 1); if (xcursor) { struct wlr_xcursor_image *image = xcursor->images[0]; wlr_xwayland_set_cursor(server->xwayland, image->buffer, image->width * 4, image->width, image->height, image->hotspot_x, image->hotspot_y); } } /* * Until we expose the workspaces to xwayland we need a way to * ensure that xwayland views on the current workspace are always * stacked above xwayland views on other workspaces. * * If we fail to do so, issues arise in scenarios where we change * the mouse focus but do not change the (xwayland) stacking order. * * Reproducer: * - open at least two xwayland windows which allow scrolling * (some X11 terminal with 'man man' for example) * - switch to another workspace, open another xwayland * window which allows scrolling and maximize it * - switch back to the previous workspace with the two windows * - move the mouse to the xwayland window that does *not* have focus * - start scrolling * - all scroll events should end up on the maximized window on the other workspace */ void xwayland_adjust_stacking_order(struct server *server) { struct view **view; struct wl_array views; wl_array_init(&views); view_array_append(server, &views, LAB_VIEW_CRITERIA_ALWAYS_ON_TOP); view_array_append(server, &views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE | LAB_VIEW_CRITERIA_NO_ALWAYS_ON_TOP); /* * view_array_append() provides top-most windows * first so we simply reverse the iteration here */ wl_array_for_each_reverse(view, &views) { view_move_to_front(*view); } wl_array_release(&views); } void xwayland_server_finish(struct server *server) { struct wlr_xwayland *xwayland = server->xwayland; /* * Reset server->xwayland to NULL first to prevent callbacks (like * server_global_filter) from accessing it as it is destroyed */ server->xwayland = NULL; wlr_xwayland_destroy(xwayland); } static bool intervals_overlap(int start_a, int end_a, int start_b, int end_b) { /* check for empty intervals */ if (end_a <= start_a || end_b <= start_b) { return false; } return start_a < start_b ? start_b < end_a : /* B starts within A */ start_a < end_b; /* A starts within B */ } /* * Subtract the area of an XWayland view (e.g. panel) from the usable * area of the output based on _NET_WM_STRUT_PARTIAL property. */ void xwayland_adjust_usable_area(struct view *view, struct wlr_output_layout *layout, struct wlr_output *output, struct wlr_box *usable) { assert(view); assert(layout); assert(output); assert(usable); if (view->type != LAB_XWAYLAND_VIEW) { return; } xcb_ewmh_wm_strut_partial_t *strut = xwayland_surface_from_view(view)->strut_partial; if (!strut) { return; } /* these are layout coordinates */ struct wlr_box lb = { 0 }; wlr_output_layout_get_box(layout, NULL, &lb); struct wlr_box ob = { 0 }; wlr_output_layout_get_box(layout, output, &ob); /* * strut->right/bottom are offsets from the lower right corner * of the X11 screen, which should generally correspond with the * lower right corner of the output layout */ double strut_left = strut->left; double strut_right = (lb.x + lb.width) - strut->right; double strut_top = strut->top; double strut_bottom = (lb.y + lb.height) - strut->bottom; /* convert layout to output coordinates */ wlr_output_layout_output_coords(layout, output, &strut_left, &strut_top); wlr_output_layout_output_coords(layout, output, &strut_right, &strut_bottom); /* deal with right/bottom rather than width/height */ int usable_right = usable->x + usable->width; int usable_bottom = usable->y + usable->height; /* here we mix output and layout coordinates; be careful */ if (strut_left > usable->x && strut_left < usable_right && intervals_overlap(ob.y, ob.y + ob.height, strut->left_start_y, strut->left_end_y + 1)) { usable->x = strut_left; } if (strut_right > usable->x && strut_right < usable_right && intervals_overlap(ob.y, ob.y + ob.height, strut->right_start_y, strut->right_end_y + 1)) { usable_right = strut_right; } if (strut_top > usable->y && strut_top < usable_bottom && intervals_overlap(ob.x, ob.x + ob.width, strut->top_start_x, strut->top_end_x + 1)) { usable->y = strut_top; } if (strut_bottom > usable->y && strut_bottom < usable_bottom && intervals_overlap(ob.x, ob.x + ob.width, strut->bottom_start_x, strut->bottom_end_x + 1)) { usable_bottom = strut_bottom; } usable->width = usable_right - usable->x; usable->height = usable_bottom - usable->y; } void xwayland_update_workarea(struct server *server) { /* * Do nothing if called during destroy or before xwayland is ready. * This function will be called again from the ready signal handler. */ if (!server->xwayland || !server->xwayland->xwm) { return; } struct wlr_box lb; wlr_output_layout_get_box(server->output_layout, NULL, &lb); /* Compute outer edges of layout (excluding negative regions) */ int layout_left = MAX(0, lb.x); int layout_right = MAX(0, lb.x + lb.width); int layout_top = MAX(0, lb.y); int layout_bottom = MAX(0, lb.y + lb.height); /* Workarea is initially the entire layout */ int workarea_left = layout_left; int workarea_right = layout_right; int workarea_top = layout_top; int workarea_bottom = layout_bottom; struct output *output; wl_list_for_each(output, &server->outputs, link) { if (!output_is_usable(output)) { continue; } struct wlr_box ob; wlr_output_layout_get_box(server->output_layout, output->wlr_output, &ob); /* Compute edges of output */ int output_left = ob.x; int output_right = ob.x + ob.width; int output_top = ob.y; int output_bottom = ob.y + ob.height; /* Compute edges of usable area */ int usable_left = output_left + output->usable_area.x; int usable_right = usable_left + output->usable_area.width; int usable_top = output_top + output->usable_area.y; int usable_bottom = usable_top + output->usable_area.height; /* * Only adjust workarea edges for output edges that are * aligned with outer edges of layout */ if (output_left == layout_left) { workarea_left = MAX(workarea_left, usable_left); } if (output_right == layout_right) { workarea_right = MIN(workarea_right, usable_right); } if (output_top == layout_top) { workarea_top = MAX(workarea_top, usable_top); } if (output_bottom == layout_bottom) { workarea_bottom = MIN(workarea_bottom, usable_bottom); } } /* * Set _NET_WORKAREA property. We don't report virtual desktops * to XWayland, so we set only one workarea. */ struct wlr_box workarea = { .x = workarea_left, .y = workarea_top, .width = workarea_right - workarea_left, .height = workarea_bottom - workarea_top, }; wlr_xwayland_set_workareas(server->xwayland, &workarea, 1); } ������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/subprojects/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14570443012�0015152�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/subprojects/.gitignore������������������������������������������������������������������0000664�0000000�0000000�00000000014�14570443012�0017135�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* !/*.wrap ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/subprojects/seatd.wrap������������������������������������������������������������������0000664�0000000�0000000�00000000106�14570443012�0017142�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[wrap-git] url=https://git.sr.ht/~kennylevinsen/seatd revision=0.6.4 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������labwc-0.7.1/subprojects/wlroots.wrap����������������������������������������������������������������0000664�0000000�0000000�00000000172�14570443012�0017556�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[wrap-git] url = https://gitlab.freedesktop.org/wlroots/wlroots.git revision = 0.17 [provide] dependency_names = wlroots ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������