Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:mantarimay
labwc
_service:obs_scm:labwc-0.6.3+git0.63db731.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:labwc-0.6.3+git0.63db731.obscpio of Package labwc
07070100000000000081A4000000000000000000000001645896B8000000E3000000000000000000000000000000000000002700000000labwc-0.6.3+git0.63db731/.editorconfigroot = 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 07070100000001000081A4000000000000000000000001645896B800000097000000000000000000000000000000000000002800000000labwc-0.6.3+git0.63db731/.gitattributes# Exclude checkpatch.pl from language stats # https://github.com/github/linguist/blob/master/docs/overrides.md scripts/checkpatch.pl linguist-vendored 07070100000002000041ED000000000000000000000003645896B800000000000000000000000000000000000000000000002100000000labwc-0.6.3+git0.63db731/.github07070100000003000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002B00000000labwc-0.6.3+git0.63db731/.github/workflows07070100000004000081A4000000000000000000000001645896B800001213000000000000000000000000000000000000003500000000labwc-0.6.3+git0.63db731/.github/workflows/build.yml# Void-musl images: # https://github.com/void-linux/void-docker/pkgs/container/void-linux/versions # # Void dependencies based on: # https://github.com/void-linux/void-packages/blob/master/srcpkgs/wlroots/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 build: name: Build needs: codestyle 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: macos-12 env: TARGET: 'ssh freebsd /bin/sh -xe' - name: Void-musl os: ubuntu-latest container: ghcr.io/void-linux/void-linux:latest-thin-x86_64-musl 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 - 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 clang \ hwdata \ libxml2-dev libcairo2-dev libpango1.0-dev apt-get build-dep -y wlroots - name: Install FreeBSD dependencies if: matrix.name == 'FreeBSD' uses: vmactions/freebsd-vm@v0 with: usesh: true prepare: | sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf pkg install -y git meson gcc pkgconf cairo pango evdev-proto \ hwdata wayland-protocols wlroots 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 wlroots libxml2-devel \ wayland-devel glslang libgbm-devel libglvnd-devel libseat-devel \ eudev-libudev-devel libdrm-devel libinput-devel libxkbcommon-devel \ pixman-devel wayland-devel wayland-protocols xcb-util-errors-devel \ xcb-util-wm-devel xcb-util-renderutil-devel libxcb-devel \ xcb-util-cursor-devel xcb-util-devel xcb-util-image-devel \ xcb-util-keysyms-devel xcb-util-xrm-devel xorg-server-xwayland \ hwids \ libglib-devel cairo-devel pango-devel - name: Build with gcc run: | echo ' cd "$GITHUB_WORKSPACE" export CC=gcc meson build-gcc -Dxwayland=enabled --werror meson compile -C build-gcc ' | $TARGET - name: Build with clang run: | echo ' cd "$GITHUB_WORKSPACE" export CC=clang meson 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 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 build-clang-no-xwayland -Dxwayland=disabled --werror meson compile -C build-clang-no-xwayland ' | $TARGET 07070100000005000081A4000000000000000000000001645896B800001046000000000000000000000000000000000000003300000000labwc-0.6.3+git0.63db731/.github/workflows/irc.ymlname: "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 }}" 07070100000006000081A4000000000000000000000001645896B8000001FD000000000000000000000000000000000000003F00000000labwc-0.6.3+git0.63db731/.github/workflows/labwc.github.io.yml# 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 07070100000007000081A4000000000000000000000001645896B80000368C000000000000000000000000000000000000002900000000labwc-0.6.3+git0.63db731/CONTRIBUTING.md- [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 approachies which may prove useful. ## Backtraces If the compositor crashes, a good starting point is to produce a backtrace by building with ASAN/UBSAN: ``` meson -Db_sanitize=address,undefined build/ ninja -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) - @jbeich (FreeBSD) - @adcdam (Slackware) 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] nameley: 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. Identation 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] ### 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/LINGAUS` 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) # 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 on X.Y.Z'` 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 6. Re-build labwc.github.io man pages [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 [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 [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 docuementation 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 07070100000008000081A4000000000000000000000001645896B8000046AC000000000000000000000000000000000000002100000000labwc-0.6.3+git0.63db731/LICENSE 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. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> 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. <signature of Ty Coon>, 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. 07070100000009000081A4000000000000000000000001645896B800006A5E000000000000000000000000000000000000002100000000labwc-0.6.3+git0.63db731/NEWS.md# 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 | Release notes | wlroots version | lines-of-code | |------------|---------------|-----------------|---------------| | 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.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: <windowRules> <windowRule identifier="some-application"> <action name="Maximize"/> </windowRule> <windowRule identifier="foo*" serverDecoration="yes|no"/> </windowRules> - Support configuration of window switcher field definitions. Issues #852 #855 #879 <windowSwitcher show="yes" preview="yes" outlines="yes"> <fields> <field content="type" width="25%" /> <field content="app_id" width="25%" /> <field content="title" width="50%" /> </fields> </windowSwitcher> - 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 `<decoration>` 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 `<focus><followMouse>` cursor glitches and issues with focus switching via Alt-Tab. Issue #830 #849 ### Changed - Make `<windowSwitcher>` a toplevel element rather than a child of `<core>` - 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 `<core><windowSwitcher show="no" />` to hide windowSwitcher (also known as On Screen Display) when switching windows. - Enable config option `<core><windowSwitcher preview="" />` 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 `<default />` and then selectively removing keybinds. For example the following could be used to allow using A-Left/Right with Firefox. <keyboard> <default/> <keybind key="A-Left"><action name="None" /></keybind> <keybind key="A-Right"><action name="None" /></keybind> </keyboard> ### 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 `<cycleView*>` to `<windowSwitcher>`. Use `<core><windowSwitcher show="yes" preview="no" outlines="yes" />` instead of: <core> <cycleViewOSD>yes</cycleViewOSD> <cycleViewOutlines>yes</cycleViewOutlines> <cycleViewPreview>yes</cycleViewPreview> </core> ## 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 `<regions>` 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 `<Kill>` action to send SIGTERM to a client process. Written-by: @bi4k8 - Add config option `<core><reuseOutputMode>` to support flicker free boot (issue #724). Written-by: @Consolatis - Enable single-pixel-buffer-v1 - Support theme setting override by reading `<config-dir>/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 `<default />` 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 `<core><cycleViewOutlines>` 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: `<openbox_menu><menu id="root-menu">...</menu></openbox>`. 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 `<separator />` 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 preperation 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 `<menu><item><action name="Execute"><execute>` `<exectue>` is a deprecated name for `<command>`, 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 `<default />`. 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 `<font>` 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: `<mousebind button="Left" action="Drag"><action name="Move"/></mousebind>` - 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 `<core><cycleViewPreview>` 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 `<mousebind>` 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 `<keyboard><default />` - `<keyboard><repeatRate>` and `<keyboard><repeatDelay>` - Specify distance between views and output edges with `<core><gap>` - `<core><adaptiveSync>` - Set menu item font with `<theme><font place="MenuItem">` - Allow `<theme><font>` without place="" attribute, thus enabling simpler config files - Support `<mousebind>` 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) - `<libinput>` configuration. Written-by: @ARDiDo - `<resistance><screenEdgeStrength>` - 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 `<lab><xdg_shell_server_side_deco>` has changed to `<core><decoration>` (breaking change) ## [0.3.0] - 2021-06-28 Compile with wlroots 0.14.0 ### Added - Add config options `<focus><followMouse>` and `<focus><raiseOnFocus>` (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 protcol 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/ [0.6.3]: https://github.com/labwc/labwc/releases/tag/0.6.3 [0.6.2]: https://github.com/labwc/labwc/releases/tag/0.6.2 [0.6.1]: https://github.com/labwc/labwc/releases/tag/0.6.1 [0.6.0]: https://github.com/labwc/labwc/releases/tag/0.6.0 [0.5.3]: https://github.com/labwc/labwc/releases/tag/0.5.3 [0.5.2]: https://github.com/labwc/labwc/releases/tag/0.5.2 [0.5.1]: https://github.com/labwc/labwc/releases/tag/0.5.1 [0.5.0]: https://github.com/labwc/labwc/releases/tag/0.5.0 [0.4.0]: https://github.com/labwc/labwc/releases/tag/0.4.0 [0.3.0]: https://github.com/labwc/labwc/releases/tag/0.3.0 [0.2.0]: https://github.com/labwc/labwc/releases/tag/0.2.0 [0.1.0]: https://github.com/labwc/labwc/releases/tag/0.1.0 0707010000000A000081A4000000000000000000000001645896B800002915000000000000000000000000000000000000002300000000labwc-0.6.3+git0.63db731/README.md# labwc <h3 align="center">[<a href="https://labwc.github.io/">Website</a>] [<a href="https://github.com/labwc/labwc-scope#readme">Scope</a>] [<a href="https://web.libera.chat/gamja/?channels=#labwc">IRC Channel</a>] [<a href="NEWS.md">Release Notes</a>]</h3> - [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. Usage](#5-usage) - [5.1 Gaming](#51-gaming) - [6. Integration](#6-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 icons (except window buttons), animations, decorative gradients and any other options not required to reasonably render common themes. 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 icons - [x] Basic desktop and client menus - [x] HiDPI - [x] wlroots protocols such as `output-management`, `layer-shell` and `foreign-toplevel` - [x] Optionally xwayland Items which are *not* intended to be implemented: - Icons (except window buttons) - Animations - Gradients for decoration and menus - Any theme option not required to reasonably render common themes (it is amazing how few options are actually required). ### 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: <a href="https://i.imgur.com/vOelinT.png"> <img src="https://i.imgur.com/vOelinTl.png"> </a> ## 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 - 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 see [wiki]. ## 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/\<theme-name\>/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. Usage ./build/labwc [-s <command>] > **_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. ### 5.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%`. ## 6. 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 0707010000000B000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000001E00000000labwc-0.6.3+git0.63db731/docs0707010000000C000081A4000000000000000000000001645896B8000000A7000000000000000000000000000000000000002500000000labwc-0.6.3+git0.63db731/docs/READMEConfig layout for ~/.config/labwc/ - autostart - environment - menu.xml - rc.xml - themerc-override See `man labwc-config and `man labwc-theme` for further details. 0707010000000D000081A4000000000000000000000001645896B8000004E2000000000000000000000000000000000000002800000000labwc-0.6.3+git0.63db731/docs/autostart# 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 <whatever> --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 & 0707010000000E000081A4000000000000000000000001645896B8000003A0000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/docs/environment# Example environment file # This allows xdg-desktop-portal-wlr to function (e.g. for screen-recording) XDG_CURRENT_DESKTOP=wlroots # Set keyboard layout to Swedish XKB_DEFAULT_LAYOUT=se # Set two keyboard layouts and toggle between them using alt+shift XKB_DEFAULT_LAYOUT=se,de XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle # Force firefox to use wayland backend MOZ_ENABLE_WAYLAND=1 # Set cursor theme. # Find icons themes with the command below or similar: # find /usr/share/icons/ -type d -name "cursors" XCURSOR_THEME=breeze_cursors # Disable hardware cursors. Most users wouldn't want to do this, but if you # are experiencing issues with disappearing cursors, this might fix it. WLR_NO_HARDWARE_CURSORS=1 # For Java applications such as JetBrains/Intellij Idea, set this variable # to avoid menus with incorrect offset and blank windows # See https://github.com/swaywm/sway/issues/595 _JAVA_AWT_WM_NONREPARENTING=1 0707010000000F000081A4000000000000000000000001645896B800000CEA000000000000000000000000000000000000003200000000labwc-0.6.3+git0.63db731/docs/labwc-actions.5.scdlabwc-actions(5) # NAME labwc - actions # ACTIONS Actions are used in menus and keyboard/mouse bindings. *<action name="Close">* Close top-most window. *<action name="Kill">* Kill the process associated with the current window by sending it the SIGTERM signal. *<action name="Execute" command="value" />* Execute command. Note that in the interest of backward compatibility, labwc supports <execute> as an alternative to <command> even though openbox documentation states that it is deprecated. *<action name="Exit">* Exit labwc. *<action name="Focus">* Give focus to window under cursor. *<action name="Raise">* Restack the current window above other open windows. *<action name="Lower">* Restack the current window below other open windows. *<action name="Iconify">* Iconify (minimize) focused window. *<action name="Move">* Begin interactive move of window under cursor *<action name="MoveToEdge" direction="value" />* Move window to edge of outputs. Understands directions "left", "up", "right" and "down". *<action name="Resize">* Begin interactive resize of window under cursor *<action name="SnapToEdge" direction="value" />* Resize window to fill half the output in the given direction. Supports directions "left", "up", "right", "down" and "center". *<action name="SnapToRegion" region="value" />* Resize and move active window according to the given region. See labwc-config(5) for further information on how to define regions. *<action name="NextWindow">* Cycle focus to next window. *<action name="PreviousWindow">* Cycle focus to previous window. *<action name="Reconfigure">* Re-load configuration and theme files. *<action name="ShowMenu" menu="value" />* Show menu. Valid menu names are "root-menu" and "client-menu". *<action name="ToggleDecorations">* Toggle decorations of focused window. *<action name="ToggleFullscreen">* Toggle fullscreen state of focused window. *<action name="ToggleMaximize">* Toggle maximize state of focused window. *<action name="Maximize">* Maximize focused window. *<action name="ToggleAlwaysOnTop">* Toggle always-on-top of focused window. *<action name="ToggleKeybinds">* 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. *<action name="FocusOutput" output="HDMI-A-1" />* 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. *<action name="GoToDesktop" to="value" />* Switch to workspace. *to* The workspace to switch to. Supported values are "last", "left", "right" or the full name of a workspace or its index (starting at 1) as configured in rc.xml. *<action name="SendToDesktop" to="value" follow="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. *<action name="None">* If used as the only action for a binding: clear an earlier defined binding. # SEE ALSO labwc(1), labwc-config(5), labwc-theme(5) 07070100000010000081A4000000000000000000000001645896B800003686000000000000000000000000000000000000003100000000labwc-0.6.3+git0.63db731/docs/labwc-config.5.scdlabwc-config(5) # NAME labwc - configuration files # CONFIGURATION 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 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. The *menu.xml* file defines the context/root-menus and is described in labwc-menu(5) There is a small <theme> section in rc.xml, for example to set rouned 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 must be wrapped in a <labwc_config> root-element, like this: ``` <?xml version="1.0"?> <labwc_config> <!-- settings --> </labwc_config> ``` The rest of this man page describes configuration options. ## CORE *<core><decoration>* [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. *<core><gap>* The distance in pixels between views and output edges when using movement actions, for example MoveToEdge. Default is 0. *<core><adaptiveSync>* [yes|no] Enable adaptive sync. Default is no. *<core><reuseOutputMode>* [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. ## WINDOW SWITCHER *<windowSwitcher show="" preview="" outlines="">* *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. *<windowSwitcher><fields><field content="" width="%">* Define window switcher fields. *content* defines what the field shows and can be any of: - *type* Show view type ("xdg-shell" or "xwayland") - *app_id* Show app_id (class for XWayland) - *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 *<resistance><screenEdgeStrength>* Screen Edge Strength is how far past the screen's edge your cursor must move before the window will move with it. Resistance is counted in pixels. Default is 20 pixels. ## FOCUS *<focus><followMouse>* [yes|no] Make focus follow mouse, i.e. focus is given to window under mouse cursor. Default is no. *<focus><followMouseRequiresMovement>* [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. *<focus><raiseOnFocus>* [yes|no] Raise window to top when focused. Default is no. ## WINDOW SNAPPING *<snapping><range>* The distance in pixels from the edge of an ouput for window Move operations to trigger SnapToEdge. A range of 0 disables window snapping. Default is 1. *<snapping><topMaximize>* [yes|no] Maximize window if Move operation ends on the top edge. Default is yes. ## REGIONS *<regions><region name="snap-1" x="10%" y="10%" width="80%" height="80%">* 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 *<desktops><names><name>* 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. *<desktops><popupTime>* Define the timeout after which to hide the workspace OSD. A setting of 0 disables the OSD. Default is 1000 ms. ## THEME *<theme><name>* The name of the Openbox theme to use. It is not set by default. *<theme><cornerRadius>* The radius of server side decoration top corners. Default is 8. *<theme><font place="">* The font to use for a specific element of a window, menu or OSD. Places can be any of: - ActiveWindow - titlebar of active window - 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. *<theme><font place=""><name>* Describes font name. Default is sans. *<theme><font place=""><size>* Font size in pixels. Default is 10. *<theme><font place=""><slant>* Font slant (normal or italic). Default is normal. *<theme><font place=""><weight>* Font weight (normal or bold). Default is normal. ## KEYBOARD *<keyboard><keybind key="">* Define a key binding in the format *modifier-key*, where supported modifiers include S (shift); C (control); A (alt); W (super). 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. *<keyboard><keybind key=""><action name="">* Keybind action. See labwc-action(5) *<keyboard><default />* 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 <keyboard><keybind> entries exist, the same default keybinds will be loaded even if the <default /> element is not provided. ``` A-Tab - next window W-Return - alacritty A-F3 - run bemenu A-F4 - close window W-a - toggle maximize A-<arrow> - move window to edge W-<arrow> - resize window to fill half the output ``` Audio and MonBrightness keys are also bound to amixer and brightnessctl respectively *<keyboard><repeatRate>* Set the rate at which keypresses are repeated per second. Default is 25. *<keyboard><repeatDelay>* Set the delay before keypresses are repeated in milliseconds. Default is 600. ## MOUSE *<mouse><doubleClickTime>* Set double click time in milliseconds. Default is 500. *<mouse><scrollFactor>* Set scroll factor. Default is 1.0. *<mouse><context name=""><mousebind button="" direction=""><action>* Multiple *<mousebind>* can exist within one *<context>*; and multiple *<action>* can exist within one *<mousebind>* 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 Supported scroll *directions* are: - Up - Down - Left - Right 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. *<mouse><default />* 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 <mouse><mousebind> entries exist, the same default mousebinds will be loaded even if the <default /> element is not provided. ## LIBINPUT *<libinput><device category="">* Define a category of devices to use the configuration values that follow. The category can be set to touch (devices that define a width and height), non-touch, default, or the name of a device. You can obtain your devices name by running *libinput list-devices* (you may need to be root or a part of the input group to perform this.) Any members of this category that are not set use the default for the device. With the exception of tap-to-click, which is enabled by default. *<libinput><device category=""><naturalScroll>* [yes|no] Use natural scrolling for this category if available. *<libinput><device category=""><leftHanded>* [yes|no] Use your devices left-handed mode if available. *<libinput><device category=""><pointerSpeed>* [\-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. *<libinput><device category=""><accelProfile>* [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. *<libinput><device category=""><tap>* [yes|no] Enable or disable tap-to-click for this category. This is enabled by default for all categories. *<libinput><device category=""><tapButtonMap>* [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). *<libinput><device category=""><middleEmulation>* [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). *<libinput><device category=""><disableWhileTyping>* [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. ## WINDOW RULES Two types of window rules are supported, actions and properties. They are defined as shown below. ``` <windowRules> <!-- Action --> <windowRule identifier=""> <action name=""/> </windowRule> <!-- Property --> <windowRule identifier="" serverDecoration="" /> </windowRules> ``` *Actions* *<windowRules><windowRule identifier="">* Define a window rule for any window which matches the criteria defined by the attribute *identifier*. 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. *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. *<windowRules><windowRule serverDecoration="">* [yes|no|default] *serverDecoration* over-rules any other setting for server-side window decoration on first map. ## 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) 07070100000011000081A4000000000000000000000001645896B800000465000000000000000000000000000000000000002F00000000labwc-0.6.3+git0.63db731/docs/labwc-menu.5.scdlabwc-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 A menu file must be entirely enclosed within <openbox_menu> and </openbox_menu> tags. Inside these tags, menus are specified as follows: ``` <menu id=""> <!-- A menu entry with an action, for example to execute an application --> <item label=""> <action></action> </item> <!-- A submenu defined elsewhere --> <menu id="" /> <!-- Horizontal line > <separator /> <!-- An inline submenu --> <menu id="" label=""> ...some content... </menu> </menu> ``` *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. *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) *menu.separator* Horizontal line. # SEE ALSO labwc(1), labwc-action(5), labwc-config(5), labwc-theme(5) 07070100000012000081A4000000000000000000000001645896B80000147C000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/docs/labwc-theme.5.scdlabwc-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/<theme-name>/openbox-3/ - $HOME/.themes/<theme-name>/openbox-3/ - /usr/share/themes/<theme-name>/openbox-3/ - /usr/local/share/themes/<theme-name>/openbox-3/ - /opt/share/themes/<theme-name>/openbox-3/ Choosing a theme is done by editing the <name> key in the <theme> section of the rc.xml configuration file (labwc-config(5)). A theme consists of a themerc file and optionally some xbm icons. 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 RGB values* Colors can be specified by hexadecimal RGB values in the format #rrggbb. Other formats will be supported later for better openbox theme compatibility. *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. *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.title.bg.color* Background color for the focussed window's titlebar *window.inactive.title.bg.color* Background color for non-focussed windows' titlebars *window.active.label.text.color* Text color for the focussed window's titlebar *window.inactive.label.text.color* Text color non-focussed 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 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 *osd.label.text.color* Text color of on-screen-display *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 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 within your theme as the themerc file. Here are all the possible xbm files looked for: - max.xbm - iconify.xbm - close.xbm - menu.xbm More will be supported later. Note: menu.xbm is not part of openbox-3.6 spec # DEFINITIONS The handle is the window edge decoration at the bottom of the window. # DERIVED DIMENSIONS The window title bar height is equal to the vertical font extents of the title. Padding will be added to this later. # SEE ALSO labwc(1), labwc-config(5), labwc-actions(5) 07070100000013000081A4000000000000000000000001645896B800000514000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/docs/labwc.1.scdlabwc(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 <signal> $LABWC_PID killall -s <signal> 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* <config-file> Specify a config file with path *-C, --config-dir* <config-directory> Specify a config directory *-d, --debug* Enable full logging, including debug information *-e, --exit* Exit the compositor *-h, --help* Show help message and quit *-r, --reconfigure* Reload the compositor configuration *-s, --startup* <command> 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) 07070100000014000081A4000000000000000000000001645896B800000072000000000000000000000000000000000000002C00000000labwc-0.6.3+git0.63db731/docs/labwc.desktop[Desktop Entry] Name=labwc Comment=A wayland stacking compositor Exec=labwc Type=Application DesktopNames=wlroots 07070100000015000081A4000000000000000000000001645896B8000005D6000000000000000000000000000000000000002700000000labwc-0.6.3+git0.63db731/docs/menu.xml<?xml version="1.0" encoding="UTF-8"?> <openbox_menu> <menu id="client-menu"> <item label="Minimize"> <action name="Iconify" /> </item> <item label="Maximize"> <action name="ToggleMaximize" /> </item> <item label="Fullscreen"> <action name="ToggleFullscreen" /> </item> <item label="Decorations"> <action name="ToggleDecorations" /> </item> <item label="AlwaysOnTop"> <action name="ToggleAlwaysOnTop" /> </item> <!-- Any menu with the id "workspaces" will be hidden if there is only a single workspace available. --> <menu id="workspaces" label="Workspace"> <item label="Move left"> <action name="SendToDesktop" to="left" /> </item> <item label="Move right"> <action name="SendToDesktop" to="right" /> </item> </menu> <item label="Close"> <action name="Close" /> </item> </menu> <menu id="root-menu"> <item label="Web browser"> <action name="Execute" command="firefox" /> </item> <item label="Terminal"> <action name="Execute" command="alacritty" /> </item> <item label="Reconfigure"> <action name="Reconfigure" /> </item> <item label="Exit"> <action name="Exit" /> </item> <item label="Poweroff"> <action name="Execute" command="systemctl -i poweroff" /> </item> </menu> <menu id="some-custom-menu"> <item label="Reconfigure"> <action name="Reconfigure" /> </item> <item label="Exit"> <action name="Exit" /> </item> </menu> </openbox_menu> 07070100000016000081A4000000000000000000000001645896B8000002DE000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/docs/meson.buildscdoc = 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() ) 07070100000017000081A4000000000000000000000001645896B8000003C7000000000000000000000000000000000000002500000000labwc-0.6.3+git0.63db731/docs/rc.xml<?xml version="1.0"?> <!-- This is a very simple config file with many options missing. For a complete set of options with comments, see docs/rc.xml.all --> <labwc_config> <core> <gap>10</gap> </core> <theme> <name></name> <cornerRadius>8</cornerRadius> <font name="sans" size="10" /> </theme> <keyboard> <default /> <!-- Use a different terminal emulator --> <keybind key="W-Return"> <action name="Execute" command="foot" /> </keybind> <!-- Remove a previously defined keybind A shorter alternative is <keybind key="W-F4" /> --> <keybind key="W-F4"> <action name="None" /> </keybind> </keyboard> <mouse> <default /> <!-- Show a custom menu on desktop right click --> <context name="Root"> <mousebind button="Right" action="Press"> <action name="ShowMenu" menu="some-custom-menu" /> </mousebind> </context> </mouse> </labwc_config> 07070100000018000081A4000000000000000000000001645896B800002F55000000000000000000000000000000000000002900000000labwc-0.6.3+git0.63db731/docs/rc.xml.all<?xml version="1.0"?> <!-- This file contains all supported config elements & attributes with default values. --> <labwc_config> <core> <decoration>server</decoration> <gap>0</gap> <adaptiveSync>no</adaptiveSync> <reuseOutputMode>no</reuseOutputMode> </core> <!-- <font><theme> can be defined without an attribute to set all places --> <theme> <name></name> <cornerRadius>8</cornerRadius> <font place="ActiveWindow"> <name>sans</name> <size>10</size> <slant>normal</slant> <weight>normal</weight> </font> <font place="MenuItem"> <name>sans</name> <size>10</size> <slant>normal</slant> <weight>normal</weight> </font> <font place="OnScreenDisplay"> <name>sans</name> <size>10</size> <slant>normal</slant> <weight>normal</weight> </font> </theme> <windowSwitcher show="yes" preview="yes" outlines="yes"> <fields> <field content="type" width="25%" /> <field content="app_id" width="25%" /> <field content="title" width="50%" /> </fields> </windowSwitcher> <!-- edge strength is in pixels --> <resistance> <screenEdgeStrength>20</screenEdgeStrength> </resistance> <focus> <followMouse>no</followMouse> <followMouseRequiresMovement>yes</followMouseRequiresMovement> <raiseOnFocus>no</raiseOnFocus> </focus> <!-- Set range to 0 to disable window snapping completely --> <snapping> <range>1</range> <topMaximize>yes</topMaximize> </snapping> <!-- Use GoToDesktop left | right to switch workspaces. Use SendToDesktop left | right to move windows. See man labwc-actions for further information. Workspaces can be configured like this: <desktops> <popupTime>1000</popupTime> <names> <name>Workspace 1</name> <name>Workspace 2</name> <name>Workspace 3</name> </names> </desktops> --> <desktops> <!-- popupTime defaults to 1000 so could be left out. Set to 0 to completely disable the workspace OSD. --> <popupTime>1000</popupTime> <names> <name>Default</name> </names> </desktops> <!-- Percent based regions based on output usable area, % char is required --> <!-- <regions> <region name="top-left" x="0%" y="0%" height="50%" width="50%" /> <region name="top" x="0%" y="0%" height="50%" width="100%" /> <region name="top-right" x="50%" y="0%" height="50%" width="50%" /> <region name="left" x="0%" y="0%" height="100%" width="50%" /> <region name="center" x="10%" y="10%" height="80%" width="80%" /> <region name="right" x="50%" y="0%" height="100%" width="50%" /> <region name="bottom-left" x="0%" y="50%" height="50%" width="50%" /> <region name="bottom" x="0%" y="50%" height="50%" width="100%" /> <region name="bottom-right" x="50%" y="50%" height="50%" width="50%" /> </regions> --> <!-- Keybind actions are specified in labwc-actions(5) The following keybind modifiers are supported: W - window/super/logo A - alt C - ctrl S - shift Use <keyboard><default /> to load all the default keybinds (those listed below). If the default keybinds are largely what you want, a sensible approach could be to start the <keyboard> section with a <default /> element, and then (re-)define any special binds you need such as launching your favourite terminal or application launcher. See rc.xml for an example. --> <keyboard> <repeatRate>25</repeatRate> <repeatDelay>600</repeatDelay> <keybind key="A-Tab"> <action name="NextWindow" /> </keybind> <keybind key="W-Return"> <action name="Execute" command="alacritty" /> </keybind> <keybind key="A-F3"> <action name="Execute" command="bemenu-run" /> </keybind> <keybind key="A-F4"> <action name="Close" /> </keybind> <keybind key="W-a"> <action name="ToggleMaximize" /> </keybind> <keybind key="A-Left"> <action name="MoveToEdge" direction="left" /> </keybind> <keybind key="A-Right"> <action name="MoveToEdge" direction="right" /> </keybind> <keybind key="A-Up"> <action name="MoveToEdge" direction="up" /> </keybind> <keybind key="A-Down"> <action name="MoveToEdge" direction="down" /> </keybind> <keybind key="W-Left"> <action name="SnapToEdge" direction="left" /> </keybind> <keybind key="W-Right"> <action name="SnapToEdge" direction="right" /> </keybind> <keybind key="W-Up"> <action name="SnapToEdge" direction="up" /> </keybind> <keybind key="W-Down"> <action name="SnapToEdge" direction="down" /> </keybind> <keybind key="A-Space"> <action name="ShowMenu" menu="client-menu" /> </keybind> <keybind key="XF86_AudioLowerVolume"> <action name="Execute" command="amixer sset Master 5%-" /> </keybind> <keybind key="XF86_AudioRaiseVolume"> <action name="Execute" command="amixer sset Master 5%+" /> </keybind> <keybind key="XF86_AudioMute"> <action name="Execute" command="amixer sset Master toggle" /> </keybind> <keybind key="XF86_MonBrightnessUp"> <action name="Execute" command="brightnessctl set +10%" /> </keybind> <keybind key="XF86_MonBrightnessDown"> <action name="Execute" command="brightnessctl set 10%-" /> </keybind> <!-- SnapToRegion via W-Numpad --> <!-- <keybind key="W-KP_7"><action name="SnapToRegion" region="top-left" /></keybind> <keybind key="W-KP_8"><action name="SnapToRegion" region="top" /></keybind> <keybind key="W-KP_9"><action name="SnapToRegion" region="top-right" /></keybind> <keybind key="W-KP_4"><action name="SnapToRegion" region="left" /></keybind> <keybind key="W-KP_5"><action name="SnapToRegion" region="center" /></keybind> <keybind key="W-KP_6"><action name="SnapToRegion" region="right" /></keybind> <keybind key="W-KP_1"><action name="SnapToRegion" region="bottom-left" /></keybind> <keybind key="W-KP_2"><action name="SnapToRegion" region="bottom" /></keybind> <keybind key="W-KP_3"><action name="SnapToRegion" region="bottom-right" /></keybind> --> </keyboard> <!-- Multiple <mousebind> can exist within one <context> Multiple <actions> can exist within one <mousebind> Currently, the only openbox-action not supported is "Unshade" Use <mouse><default /> to load all the default mousebinds (those listed below). If the default mousebinds are largely what you want, a sensible approach could be to start the <mouse> section with a <default /> element, and then (re-)define any special binds you need such as launching a custom menu when right-clicking on your desktop. See rc.xml for an example. --> <mouse> <!-- time is in ms --> <doubleClickTime>500</doubleClickTime> <scrollFactor>1.0</scrollFactor> <context name="Frame"> <mousebind button="A-Left" action="Press"> <action name="Focus" /> <action name="Raise" /> </mousebind> <mousebind button="A-Left" action="Drag"> <action name="Move" /> </mousebind> <mousebind button="A-Right" action="Press"> <action name="Focus" /> <action name="Raise" /> </mousebind> <mousebind button="A-Right" action="Drag"> <action name="Resize" /> </mousebind> </context> <context name="Top"> <mousebind button="Left" action="Drag"> <action name="Resize" /> </mousebind> </context> <context name="Left"> <mousebind button="Left" action="Drag"> <action name="Resize" /> </mousebind> </context> <context name="Right"> <mousebind button="Left" action="Drag"> <action name="Resize" /> </mousebind> </context> <context name="Bottom"> <mousebind button="Left" action="Drag"> <action name="Resize" /> </mousebind> </context> <context name="TRCorner"> <mousebind button="Left" action="Drag"> <action name="Resize" /> </mousebind> </context> <context name="BRCorner"> <mousebind button="Left" action="Drag"> <action name="Resize" /> </mousebind> </context> <context name="TLCorner"> <mousebind button="Left" action="Drag"> <action name="Resize" /> </mousebind> </context> <context name="BLCorner"> <mousebind button="Left" action="Drag"> <action name="Resize" /> </mousebind> </context> <context name="TitleBar"> <mousebind button="Left" action="Press"> <action name="Focus" /> <action name="Raise" /> </mousebind> <mousebind button="Right" action="Click"> <action name="Focus" /> <action name="Raise" /> <action name="ShowMenu" menu="client-menu" /> </mousebind> </context> <context name="Title"> <mousebind button="Left" action="Drag"> <action name="Move" /> </mousebind> <mousebind button="Left" action="DoubleClick"> <action name="ToggleMaximize" /> </mousebind> </context> <context name="Maximize"> <mousebind button="Left" action="Click"> <action name="Focus" /> <action name="Raise" /> <action name="ToggleMaximize" /> </mousebind> </context> <context name="WindowMenu"> <mousebind button="Left" action="Click"> <action name="ShowMenu" menu="client-menu" /> </mousebind> </context> <context name="Iconify"> <mousebind button="left" action="Click"> <action name="Iconify" /> </mousebind> </context> <context name="Close"> <mousebind button="Left" action="Click"> <action name="Close" /> </mousebind> </context> <context name="Client"> <mousebind button="Left" action="Press"> <action name="Focus" /> <action name="Raise" /> </mousebind> <mousebind button="Middle" action="Press"> <action name="Focus" /> <action name="Raise" /> </mousebind> <mousebind button="Right" action="Press"> <action name="Focus" /> <action name="Raise" /> </mousebind> </context> <context name="Root"> <mousebind button="Left" action="Press"> <action name="ShowMenu" menu="root-menu" /> </mousebind> <mousebind button="Right" action="Press"> <action name="ShowMenu" menu="root-menu" /> </mousebind> <mousebind button="Middle" action="Press"> <action name="ShowMenu" menu="root-menu" /> </mousebind> <mousebind direction="Up" action="Scroll"> <action name="GoToDesktop" to="left" /> </mousebind> <mousebind direction="Down" action="Scroll"> <action name="GoToDesktop" to="right" /> </mousebind> </context> </mouse> <!-- The *category* element can be set to touch, non-touch, default or the name of a device. You can obtain device names by running *libinput list-devices* as root or member of the input group. Tap is set to *yes* be default. All others are left blank in order to use device defaults. All values are [yes|no] except for: - pointerSpeed [-1.0 to 1.0] - accelProfile [flat|adaptive] - tapButtonMap [lrm|lmr] --> <libinput> <device category=""> <naturalScroll></naturalScroll> <leftHanded></leftHanded> <pointerSpeed></pointerSpeed> <accelProfile></accelProfile> <tap>yes</tap> <tapButtonMap></tapButtonMap> <middleEmulation></middleEmulation> <disableWhileTyping></disableWhileTyping> </device> </libinput> <!-- <windowRules> <windowRule identifier="*"> <action name="Maximize" /> </windowRule> <windowRule identifier="some-application" serverDecoration="yes" /> </windowRules> --> </labwc_config> 07070100000019000081A4000000000000000000000001645896B80000063F000000000000000000000000000000000000002600000000labwc-0.6.3+git0.63db731/docs/themerc# 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 # window border window.active.border.color: #dddad6 window.inactive.border.color: #f6f5f4 # window titlebar background window.active.title.bg.color: #dddad6 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: #dddad6 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 0707010000001A000041ED000000000000000000000006645896B800000000000000000000000000000000000000000000002100000000labwc-0.6.3+git0.63db731/include0707010000001B000081A4000000000000000000000001645896B800000388000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/include/action.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_ACTION_H #define __LABWC_ACTION_H #include <wayland-util.h> 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); void action_arg_add_str(struct action *action, char *key, const char *value); void action_arg_from_xml_node(struct action *action, char *nodename, 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_list_free(struct wl_list *action_list); #endif /* __LABWC_ACTION_H */ 0707010000001C000081A4000000000000000000000001645896B8000007C3000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/include/buffer.h/* 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 <cairo.h> #include <wlr/types/wlr_buffer.h> 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 */ 0707010000001D000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002800000000labwc-0.6.3+git0.63db731/include/common0707010000001E000081A4000000000000000000000001645896B8000002FA000000000000000000000000000000000000002E00000000labwc-0.6.3+git0.63db731/include/common/buf.h/* 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 <stdio.h> #include <stdlib.h> #include <string.h> struct buf { char *buf; int alloc; int len; }; /** * buf_expand_shell_variables - expand $foo, ${foo} and ~ 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 */ 0707010000001F000081A4000000000000000000000001645896B800000124000000000000000000000000000000000000002E00000000labwc-0.6.3+git0.63db731/include/common/dir.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_DIR_H #define __LABWC_DIR_H char *config_dir(void); /** * theme_dir - find theme directory containing theme @theme_name * @theme_name: theme to search for */ char *theme_dir(const char *theme_name); #endif /* __LABWC_DIR_H */ 07070100000020000081A4000000000000000000000001645896B8000000C4000000000000000000000000000000000000003200000000labwc-0.6.3+git0.63db731/include/common/fd_util.h/* 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 */ 07070100000021000081A4000000000000000000000001645896B80000054E000000000000000000000000000000000000002F00000000labwc-0.6.3+git0.63db731/include/common/font.h/* 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 */ 07070100000022000081A4000000000000000000000001645896B80000016E000000000000000000000000000000000000003400000000labwc-0.6.3+git0.63db731/include/common/grab-file.h/* 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 */ 07070100000023000081A4000000000000000000000001645896B80000053C000000000000000000000000000000000000003A00000000labwc-0.6.3+git0.63db731/include/common/graphic-helpers.h/* SPDX-License-Identifier: GPL-2.0-only */ #include <cairo.h> #include <wayland-server-core.h> struct wlr_scene_tree; struct wlr_scene_rect; 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, double width, double height, double line_width); 07070100000024000081A4000000000000000000000001645896B8000002C7000000000000000000000000000000000000002F00000000labwc-0.6.3+git0.63db731/include/common/list.h/* SPDX-License-Identifier: GPL-2.0-only */ #include <wayland-server-core.h> /** * 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); } 07070100000025000081A4000000000000000000000001645896B800000180000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/include/common/match.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_MATCH_H #define __LABWC_MATCH_H #include <glib.h> /** * 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 gchar *pattern, const gchar *string); #endif /* __LABWC_MATCH_H */ 07070100000026000081A4000000000000000000000001645896B8000005B0000000000000000000000000000000000000002E00000000labwc-0.6.3+git0.63db731/include/common/mem.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_MEM_H #define __LABWC_MEM_H #include <stdlib.h> /* * 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 * <expr>, which may be either a type name or value expression. * * znew() allocates space for one object. * znew_n() allocates space for an array of <n> 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 <str>; calls exit() on error. * Requires (str != NULL) and never returns NULL. */ char *xstrdup(const char *str); /* * Frees memory pointed to by <ptr> and sets <ptr> to NULL. * Does nothing if <ptr> is already NULL. */ #define zfree(ptr) do { \ free(ptr); (ptr) = NULL; \ } while (0) #endif /* __LABWC_MEM_H */ 07070100000027000081A4000000000000000000000001645896B8000001DB000000000000000000000000000000000000003300000000labwc-0.6.3+git0.63db731/include/common/nodename.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_NODENAME_H #define __LABWC_NODENAME_H #include <libxml/parser.h> #include <libxml/tree.h> #include <stdio.h> /** * 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 <a><b><c></c></b></a> would return the * name c.b.a */ char *nodename(xmlNode * node, char *buf, int len); #endif /* __LABWC_NODENAME_H */ 07070100000028000081A4000000000000000000000001645896B800000315000000000000000000000000000000000000003500000000labwc-0.6.3+git0.63db731/include/common/parse-bool.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_PARSE_BOOL_H #define __LABWC_PARSE_BOOL_H #include <stdbool.h> /** * 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 */ 07070100000029000081A4000000000000000000000001645896B80000076B000000000000000000000000000000000000003D00000000labwc-0.6.3+git0.63db731/include/common/scaled_font_buffer.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LAB_COMMON_SCALED_FONT_BUFFER_H #define __LAB_COMMON_SCALED_FONT_BUFFER_H #include "common/font.h" struct wlr_scene_tree; struct wlr_scene_buffer; struct scaled_scene_buffere; 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 /* __LAB_COMMON_SCALED_FONT_BUFFER_H */ 0707010000002A000081A4000000000000000000000001645896B800000948000000000000000000000000000000000000003E00000000labwc-0.6.3+git0.63db731/include/common/scaled_scene_buffer.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LAB_COMMON_SCALED_SCENE_BUFFER_H #define __LAB_COMMON_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 */ 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. */ struct scaled_scene_buffer *scaled_scene_buffer_create( struct wlr_scene_tree *parent, const struct scaled_scene_buffer_impl *implementation); /* 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 /* __LAB_COMMON_SCALED_SCENE_BUFFER_H */ 0707010000002B000081A4000000000000000000000001645896B800000278000000000000000000000000000000000000003800000000labwc-0.6.3+git0.63db731/include/common/scene-helpers.h/* SPDX-License-Identifier: GPL-2.0-only */ struct wlr_scene_node; struct wlr_scene_rect; struct wlr_scene_tree; struct wlr_surface; struct wlr_scene_rect *lab_wlr_scene_get_rect(struct wlr_scene_node *node); struct wlr_scene_tree *lab_scene_tree_from_node(struct wlr_scene_node *node); 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); 0707010000002C000081A4000000000000000000000001645896B800000107000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/include/common/spawn.h/* 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 */ 0707010000002D000081A4000000000000000000000001645896B800000229000000000000000000000000000000000000003900000000labwc-0.6.3+git0.63db731/include/common/string-helpers.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_STRING_HELPERS_H #define __LABWC_STRING_HELPERS_H /** * 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); #endif /* __LABWC_STRING_HELPERS_H */ 0707010000002E000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002800000000labwc-0.6.3+git0.63db731/include/config0707010000002F000081A4000000000000000000000001645896B800000360000000000000000000000000000000000000003200000000labwc-0.6.3+git0.63db731/include/config/keybind.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_KEYBIND_H #define __LABWC_KEYBIND_H #include <wlr/types/wlr_keyboard.h> #include <xkbcommon/xkbcommon.h> #define MAX_KEYSYMS 32 struct keybind { uint32_t modifiers; xkb_keysym_t *keysyms; size_t keysyms_len; 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); #endif /* __LABWC_KEYBIND_H */ 07070100000030000081A4000000000000000000000001645896B800000346000000000000000000000000000000000000003300000000labwc-0.6.3+git0.63db731/include/config/libinput.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_LIBINPUT_H #define __LABWC_LIBINPUT_H #include <libinput.h> #include <string.h> #include <wayland-server-core.h> enum device_type { DEFAULT_DEVICE, TOUCH_DEVICE, NON_TOUCH_DEVICE, }; struct libinput_category { enum 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; enum libinput_config_accel_profile accel_profile; enum libinput_config_middle_emulation_state middle_emu; enum libinput_config_dwt_state dwt; }; enum 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 */ 07070100000031000081A4000000000000000000000001645896B800000573000000000000000000000000000000000000003400000000labwc-0.6.3+git0.63db731/include/config/mousebind.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_MOUSEBIND_H #define __LABWC_MOUSEBIND_H #include <wayland-util.h> #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 */ 07070100000032000081A4000000000000000000000001645896B800000780000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/include/config/rcxml.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_RCXML_H #define __LABWC_RCXML_H #include <stdbool.h> #include <stdio.h> #include <wayland-server-core.h> #include "common/buf.h" #include "common/font.h" #include "config/libinput.h" #include "theme.h" enum window_switcher_field_content { LAB_FIELD_NONE = 0, LAB_FIELD_TYPE, LAB_FIELD_APP_ID, LAB_FIELD_TITLE, }; struct window_switcher_field { enum window_switcher_field_content content; int width; struct wl_list link; /* struct rcxml.window_switcher.fields */ }; struct rcxml { char *config_dir; /* core */ bool xdg_shell_server_side_deco; int gap; bool adaptive_sync; bool reuse_output_mode; /* focus */ bool focus_follow_mouse; bool focus_follow_mouse_requires_movement; bool raise_on_focus; /* theme */ char *theme_name; int corner_radius; struct font font_activewindow; struct font font_menuitem; struct font font_osd; /* Pointer to current theme */ struct theme *theme; /* keyboard */ int repeat_rate; int repeat_delay; struct wl_list keybinds; /* struct keybind.link */ /* mouse */ long doubleclick_time; /* in ms */ struct wl_list mousebinds; /* struct mousebind.link */ double scroll_factor; /* libinput */ struct wl_list libinput_categories; /* resistance */ int screen_edge_strength; /* window snapping */ int snap_edge_range; bool snap_top_maximize; struct { int popuptime; 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 */ 07070100000033000081A4000000000000000000000001645896B80000028B000000000000000000000000000000000000003200000000labwc-0.6.3+git0.63db731/include/config/session.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_SESSION_H #define __LABWC_SESSION_H /** * session_environment_init - set enrivonment variables based on <key>=<value> * @dir: path to config directory * pairs in `${XDG_CONFIG_DIRS:-/etc/xdg}/lawbc/environment` with user override * in `${XDG_CONFIG_HOME:-$HOME/.config}` */ void session_environment_init(const char *dir); /** * session_autostart_init - run autostart file as shell script * @dir: path to config directory * Note: Same as `sh ~/.config/labwc/autostart` (or equivalent XDG config dir) */ void session_autostart_init(const char *dir); #endif /* __LABWC_SESSION_H */ 07070100000034000081A4000000000000000000000001645896B800000E69000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/include/cursor.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_CURSOR_H #define __LABWC_CURSOR_H #include <wlr/util/edges.h> #include "ssd.h" struct view; struct seat; struct server; struct wlr_surface; struct wlr_scene_node; /* 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_finish(struct seat *seat); #endif /* __LABWC_CURSOR_H */ 07070100000035000081A4000000000000000000000001645896B8000000A7000000000000000000000000000000000000002900000000labwc-0.6.3+git0.63db731/include/debug.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __DEBUG_H #define __DEBUG_H struct server; void debug_dump_scene(struct server *server); #endif /* __DEBUG_H */ 07070100000036000081A4000000000000000000000001645896B8000001AF000000000000000000000000000000000000002F00000000labwc-0.6.3+git0.63db731/include/decorations.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LAB_DECORATIONS_H #define __LAB_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 /* __LAB_DECORATIONS_H */ 07070100000037000081A4000000000000000000000001645896B80000025E000000000000000000000000000000000000002700000000labwc-0.6.3+git0.63db731/include/dnd.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LAB_DND_H #define __LAB_DND_H #include <wayland-server-core.h> struct seat; struct wlr_drag_icon; struct wlr_scene_tree; struct drag_icon { struct wlr_scene_tree *icon_tree; struct wlr_drag_icon *icon; struct { struct wl_listener map; struct wl_listener commit; struct wl_listener unmap; struct wl_listener destroy; } events; }; 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 /* __LAB_DND_H */ 07070100000038000081A4000000000000000000000001645896B8000003BB000000000000000000000000000000000000002D00000000labwc-0.6.3+git0.63db731/include/key-state.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_KEY_STATE_H #define __LABWC_KEY_STATE_H /* * 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 ispressed); void key_state_store_pressed_keys_as_bound(void); bool key_state_corresponding_press_event_was_bound(uint32_t keycode); void key_state_bound_key_remove(uint32_t keycode); int key_state_nr_keys(void); #endif /* __LABWC_KEY_STATE_H */ 07070100000039000081A4000000000000000000000001645896B800003862000000000000000000000000000000000000002900000000labwc-0.6.3+git0.63db731/include/labwc.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_H #define __LABWC_H #include "config.h" #include <getopt.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include <wayland-server-core.h> #include <wlr/backend.h> #include <wlr/render/allocator.h> #include <wlr/render/wlr_renderer.h> #include <wlr/types/wlr_compositor.h> #include <wlr/types/wlr_buffer.h> #include <wlr/types/wlr_cursor.h> #include <wlr/types/wlr_data_device.h> #include <wlr/types/wlr_foreign_toplevel_management_v1.h> #include <wlr/types/wlr_idle.h> #include <wlr/types/wlr_idle_inhibit_v1.h> #include <wlr/types/wlr_input_device.h> #include <wlr/types/wlr_keyboard.h> #include <wlr/types/wlr_keyboard_group.h> #include <wlr/types/wlr_layer_shell_v1.h> #include <wlr/types/wlr_matrix.h> #include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output_management_v1.h> #include <wlr/types/wlr_output_power_management_v1.h> #include <wlr/types/wlr_output_layout.h> #include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_relative_pointer_v1.h> #include <wlr/types/wlr_pointer.h> #include <wlr/types/wlr_pointer_constraints_v1.h> #include <wlr/types/wlr_pointer_gestures_v1.h> #include <wlr/types/wlr_seat.h> #include <wlr/types/wlr_subcompositor.h> #include <wlr/types/wlr_xcursor_manager.h> #include <wlr/types/wlr_xdg_activation_v1.h> #include <wlr/types/wlr_xdg_shell.h> #include <wlr/types/wlr_drm_lease_v1.h> #include <wlr/types/wlr_virtual_pointer_v1.h> #include <wlr/types/wlr_virtual_keyboard_v1.h> #include <wlr/util/log.h> #include <xkbcommon/xkbcommon.h> #include "cursor.h" #include "config/keybind.h" #include "config/rcxml.h" #include "regions.h" #include "session-lock.h" #if HAVE_NLS #include <libintl.h> #include <locale.h> #define _ gettext #else #define _(s) (s) #endif #define XCURSOR_DEFAULT "left_ptr" #define XCURSOR_SIZE 24 #ifndef MIN #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif #ifndef MAX #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #endif 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/modifer 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; /* * 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; struct wlr_idle *wlr_idle; struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_manager; /* In support for ToggleKeybinds */ bool inhibit_keybinds; /* 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 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_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 idle_inhibitor_create; 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 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_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; /* 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; /* SSD state */ struct view *focused_view; struct ssd_hover_state *ssd_hover_state; /* Tree for all non-layer xdg/xwayland-shell surfaces */ struct wlr_scene_tree *view_tree; /* Tree for all non-layer xdg/xwayland-shell surfaces with always-on-top */ struct wlr_scene_tree *view_tree_always_on_top; #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_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 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; /* 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; }; #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; bool leased; }; #undef LAB_NR_LAYERS struct constraint { struct seat *seat; struct wlr_pointer_constraint_v1 *constraint; struct wl_listener destroy; }; struct idle_inhibitor { struct seat *seat; struct wlr_idle_inhibitor_v1 *wlr_inhibitor; struct wl_listener destroy; }; void xdg_popup_create(struct view *view, struct wlr_xdg_popup *wlr_popup); void xdg_activation_handle_request(struct wl_listener *listener, void *data); void xdg_surface_new(struct wl_listener *listener, void *data); 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. */ void desktop_focus_and_activate_view(struct seat *seat, struct view *view); void desktop_arrange_all_views(struct server *server); void desktop_focus_output(struct output *output); struct view *desktop_topmost_mapped_view(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); struct view *desktop_focused_view(struct server *server); void desktop_focus_topmost_mapped_view(struct server *server); bool isfocusable(struct view *view); void keyboard_cancel_keybind_repeat(struct keyboard *keyboard); void keyboard_key_notify(struct wl_listener *listener, void *data); void keyboard_modifiers_notify(struct wl_listener *listener, void *data); void keyboard_init(struct seat *seat); bool keyboard_any_modifiers_pressed(struct wlr_keyboard *keyboard); void keyboard_finish(struct seat *seat); void touch_init(struct seat *seat); void touch_finish(struct seat *seat); 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); 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 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); void handle_output_power_manager_set_mode(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 */ 0707010000003A000081A4000000000000000000000001645896B800000386000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/include/layers.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_LAYERS_H #define __LABWC_LAYERS_H #include <wayland-server.h> #include <wlr/types/wlr_layer_shell_v1.h> 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 destroy; struct wl_listener new_popup; }; void layers_init(struct server *server); void layers_arrange(struct output *output); #endif /* __LABWC_LAYERS_H */ 0707010000003B000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002600000000labwc-0.6.3+git0.63db731/include/menu0707010000003C000081A4000000000000000000000001645896B800000AF4000000000000000000000000000000000000002D00000000labwc-0.6.3+git0.63db731/include/menu/menu.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_MENU_H #define __LABWC_MENU_H #include <wayland-server.h> /* 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 */ }; void menu_init(struct server *server); void menu_finish(void); /** * menu_get_by_id - get menu by id * * @id id string defined in menu.xml like "root-menu" */ struct menu *menu_get_by_id(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 */ 0707010000003D000081A4000000000000000000000001645896B80000003E000000000000000000000000000000000000002D00000000labwc-0.6.3+git0.63db731/include/meson.buildconfigure_file(output: 'config.h', configuration: conf_data) 0707010000003E000081A4000000000000000000000001645896B8000009C9000000000000000000000000000000000000002800000000labwc-0.6.3+git0.63db731/include/node.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_NODE_DESCRIPTOR_H #define __LABWC_NODE_DESCRIPTOR_H #include <wlr/types/wlr_scene.h> 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 */ 0707010000003F000081A4000000000000000000000001645896B800000A08000000000000000000000000000000000000002B00000000labwc-0.6.3+git0.63db731/include/regions.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_REGIONS_H #define __LABWC_REGIONS_H #include <wlr/util/box.h> 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 */ 07070100000040000081A4000000000000000000000001645896B80000011F000000000000000000000000000000000000002E00000000labwc-0.6.3+git0.63db731/include/resistance.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __RESISTANCE_H #define __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 /* __RESISTANCE_H */ 07070100000041000081A4000000000000000000000001645896B800000226000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/include/session-lock.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LAB_SESSION_LOCK_H #define __LAB_SESSION_LOCK_H #include <wlr/types/wlr_session_lock_v1.h> 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); #endif /* __LAB_SESSION_LOCK_H */ 07070100000042000081A4000000000000000000000001645896B800000E5A000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/include/ssd-internal.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_SSD_INTERNAL_H #define __LABWC_SSD_INTERNAL_H #include <wlr/util/box.h> #include "ssd.h" #define FOR_EACH(tmp, ...) \ { \ __typeof__(tmp) _x[] = { __VA_ARGS__, NULL }; \ size_t _i = 0; \ for ((tmp) = _x[_i]; _i < sizeof(_x) / sizeof(_x[0]) - 1; (tmp) = _x[++_i]) #define FOR_EACH_END } struct ssd_button { struct view *view; enum ssd_part_type type; struct wlr_scene_node *hover; 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 { 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 { /* struct wlr_scene_tree *tree; unused for now */ struct ssd_sub_tree active; struct ssd_sub_tree inactive; } titlebar; /* Borders allow resizing as well */ struct { /* struct wlr_scene_tree *tree; unused for now */ 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 wlr_scene_node *node; }; 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, int x, struct view *view); 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, 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 */ 07070100000043000081A4000000000000000000000001645896B800000AD2000000000000000000000000000000000000002700000000labwc-0.6.3+git0.63db731/include/ssd.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_SSD_H #define __LABWC_SSD_H #include <wayland-server-core.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_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; struct border { int top; int right; int bottom; int left; }; /* * 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_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); 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 */ 07070100000044000081A4000000000000000000000001645896B800000B9C000000000000000000000000000000000000002900000000labwc-0.6.3+git0.63db731/include/theme.h/* 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 <stdio.h> #include <wlr/render/wlr_renderer.h> enum lab_justification { LAB_JUSTIFY_LEFT, LAB_JUSTIFY_CENTER, LAB_JUSTIFY_RIGHT, }; struct theme { int border_width; int padding_height; int menu_overlap_x; int menu_overlap_y; /* colors */ float window_active_border_color[4]; float window_inactive_border_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]; /* textures */ struct lab_data_buffer *xbm_close_active_unpressed; struct lab_data_buffer *xbm_maximize_active_unpressed; struct lab_data_buffer *xbm_iconify_active_unpressed; struct lab_data_buffer *xbm_menu_active_unpressed; struct lab_data_buffer *xbm_close_inactive_unpressed; struct lab_data_buffer *xbm_maximize_inactive_unpressed; struct lab_data_buffer *xbm_iconify_inactive_unpressed; struct lab_data_buffer *xbm_menu_inactive_unpressed; 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 title_height; }; /** * theme_init - read openbox theme and generate button textures * @theme: theme data * @theme_name: theme-name in <theme-dir>/<theme-name>/openbox-3/themerc * Note <theme-dir> 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 */ 07070100000045000081A4000000000000000000000001645896B8000002DF000000000000000000000000000000000000003400000000labwc-0.6.3+git0.63db731/include/view-impl-common.h/* 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); /* * 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 */ 07070100000046000081A4000000000000000000000001645896B8000019C4000000000000000000000000000000000000002800000000labwc-0.6.3+git0.63db731/include/view.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_VIEW_H #define __LABWC_VIEW_H #include "config.h" #include <stdbool.h> #include <stdint.h> #include <wayland-util.h> #include <wlr/util/box.h> /* * 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, }; struct view; 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 (*unmap)(struct view *view); void (*maximize)(struct view *view, bool maximize); void (*move_to_front)(struct view *view); void (*move_to_back)(struct view *view); }; struct view { struct server *server; enum view_type type; const struct view_impl *impl; struct wl_list link; /* * The 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; 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; enum ssd_preference ssd_preference; bool minimized; bool maximized; bool fullscreen; uint32_t tiled; /* private, enum view_edge in src/view.c */ /* 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; /* used by xdg-shell views */ uint32_t pending_configure_serial; struct wl_event_source *pending_configure_timeout; struct ssd *ssd; 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 wl_listener map; struct wl_listener unmap; 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 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; }; void view_set_activated(struct view *view); 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_move(struct view *view, int x, int y); void view_moved(struct view *view); void view_minimize(struct view *view, bool minimized); void view_store_natural_geometry(struct view *view); /** * 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); void view_restore_to(struct view *view, struct wlr_box geometry); void view_set_untiled(struct view *view); void view_maximize(struct view *view, bool maximize, bool store_natural_geometry); void view_set_fullscreen(struct view *view, bool fullscreen); void view_toggle_maximize(struct view *view); void view_toggle_decorations(struct view *view); void view_toggle_always_on_top(struct view *view); bool view_is_always_on_top(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_adjust_for_layout_change(struct view *view); void view_move_to_edge(struct view *view, const char *direction); void view_snap_to_edge(struct view *view, const char *direction, bool store_natural_geometry); void view_snap_to_region(struct view *view, struct region *region, bool store_natural_geometry); void view_move_to_front(struct view *view); void view_move_to_back(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_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_destroy(struct view *view); /* xdg.c */ struct wlr_xdg_surface *xdg_surface_from_view(struct view *view); #endif /* __LABWC_VIEW_H */ 07070100000047000081A4000000000000000000000001645896B800000303000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/include/window-rules.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __WINDOW_RULES_H #define __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; enum window_rule_event event; struct wl_list actions; enum property server_decoration; 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 /* __WINDOW_RULES_H */ 07070100000048000081A4000000000000000000000001645896B8000002F8000000000000000000000000000000000000002E00000000labwc-0.6.3+git0.63db731/include/workspaces.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_WORKSPACES_H #define __LABWC_WORKSPACES_H struct seat; struct view; struct server; struct wl_list; /* 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); void workspaces_destroy(struct server *server); void workspaces_osd_hide(struct seat *seat); struct workspace *workspaces_find(struct workspace *anchor, const char *name); #endif /* __LABWC_WORKSPACES_H */ 07070100000049000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002500000000labwc-0.6.3+git0.63db731/include/xbm0707010000004A000081A4000000000000000000000001645896B80000031D000000000000000000000000000000000000002D00000000labwc-0.6.3+git0.63db731/include/xbm/parse.h/* SPDX-License-Identifier: GPL-2.0-only */ /* * Parse xbm token to create pixmap * * Copyright Johan Malm 2020 */ #ifndef __LABWC_PARSE_H #define __LABWC_PARSE_H #include "xbm/tokenize.h" #include <stdint.h> struct pixmap { uint32_t *data; int width; int height; }; /** * parse_set_color - set color to be used when parsing icons * @rgba: four floats representing red, green, blue, alpha */ void parse_set_color(float *rgba); /** * parse_xbm_tokens - parse xbm tokens and create pixmap * @tokens: token vector */ struct pixmap parse_xbm_tokens(struct token *tokens); /** * parse_xbm_builtin - parse builtin xbm button and create pixmap * @button: button byte array (xbm format) */ struct pixmap parse_xbm_builtin(const char *button, int size); #endif /* __LABWC_PARSE_H */ 0707010000004B000081A4000000000000000000000001645896B80000022D000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/include/xbm/tokenize.h/* SPDX-License-Identifier: GPL-2.0-only */ /* * XBM file tokenizer * * Copyright Johan Malm 2020 */ #ifndef __LABWC_TOKENIZE_H #define __LABWC_TOKENIZE_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; }; /** * tokenize - tokenize xbm file * @buffer: buffer containing xbm file * return token vector */ struct token *tokenize_xbm(char *buffer); #endif /* __LABWC_TOKENIZE_H */ 0707010000004C000081A4000000000000000000000001645896B80000011B000000000000000000000000000000000000002B00000000labwc-0.6.3+git0.63db731/include/xbm/xbm.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_XBM_H #define __LABWC_XBM_H #include <wlr/render/wlr_renderer.h> #include "xbm/parse.h" /** * xbm_load - load theme xbm files into global theme struct */ void xbm_load(struct theme *theme); #endif /* __LABWC_XBM_H */ 0707010000004D000081A4000000000000000000000001645896B80000067F000000000000000000000000000000000000002C00000000labwc-0.6.3+git0.63db731/include/xwayland.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_XWAYLAND_H #define __LABWC_XWAYLAND_H #include "config.h" #if HAVE_XWAYLAND #include "view.h" struct wlr_compositor; struct xwayland_unmanaged { struct server *server; struct wlr_xwayland_surface *xwayland_surface; struct wlr_scene_node *node; struct wl_list link; struct wl_listener request_activate; struct wl_listener request_configure; /* struct wl_listener request_fullscreen; */ struct wl_listener set_geometry; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; struct wl_listener override_redirect; }; struct xwayland_view { struct view base; struct wlr_xwayland_surface *xwayland_surface; /* Events unique to XWayland views */ struct wl_listener request_activate; struct wl_listener request_configure; struct wl_listener set_app_id; /* TODO: s/set_app_id/class/ */ struct wl_listener set_decorations; struct wl_listener override_redirect; /* Not (yet) implemented */ /* struct wl_listener set_role; */ /* struct wl_listener set_window_type; */ /* 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); struct wlr_xwayland_surface *xwayland_surface_from_view(struct view *view); bool xwayland_apply_size_hints(struct view *view, int *w, int *h); void xwayland_server_init(struct server *server, struct wlr_compositor *compositor); void xwayland_server_finish(struct server *server); #endif /* HAVE_XWAYLAND */ #endif /* __LABWC_XWAYLAND_H */ 0707010000004E000081A4000000000000000000000001645896B800000AF5000000000000000000000000000000000000002500000000labwc-0.6.3+git0.63db731/meson.buildproject( 'labwc', 'c', version: '0.6.3', 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.16.0', '<0.17.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') math = cc.find_library('m') 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) 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, math, ] subdir('include') subdir('src') subdir('docs') executable( meson.project_name(), labwc_sources, include_directories: [labwc_inc], dependencies: labwc_deps, install: true, ) install_data('docs/labwc.desktop', install_dir: get_option('datadir') / 'wayland-sessions') 0707010000004F000081A4000000000000000000000001645896B800000127000000000000000000000000000000000000002B00000000labwc-0.6.3+git0.63db731/meson_options.txtoption('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('nls', type: 'feature', value: 'auto', description: 'Enable native language support') 07070100000050000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000001C00000000labwc-0.6.3+git0.63db731/po07070100000051000081A4000000000000000000000001645896B80000001B000000000000000000000000000000000000002400000000labwc-0.6.3+git0.63db731/po/LINGUASde es id it ka pl ru sv tr 07070100000052000081A4000000000000000000000001645896B800000010000000000000000000000000000000000000002800000000labwc-0.6.3+git0.63db731/po/POTFILES.insrc/menu/menu.c 07070100000053000081A4000000000000000000000001645896B800000505000000000000000000000000000000000000002200000000labwc-0.6.3+git0.63db731/po/de.po# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # Consolatis <https://github.com/Consolatis>, 2022. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2023-01-02 11:22+1000\n" "PO-Revision-Date: 2022-04-30 16:50+1000\n" "Last-Translator: Consolatis <https://github.com/Consolatis>\n" "Language-Team: German <de>\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:631 msgid "Reconfigure" msgstr "Rekonfigurieren" #: src/menu/menu.c:633 msgid "Exit" msgstr "Beenden" #: src/menu/menu.c:649 msgid "Minimize" msgstr "Minimieren" #: src/menu/menu.c:651 msgid "Maximize" msgstr "Maximieren" #: src/menu/menu.c:653 msgid "Fullscreen" msgstr "Vollbild" #: src/menu/menu.c:655 msgid "Decorations" msgstr "Dekorationen" #: src/menu/menu.c:657 msgid "AlwaysOnTop" msgstr "Immer im Vordergrund" #: src/menu/menu.c:662 msgid "Move left" msgstr "nach links" #: src/menu/menu.c:667 msgid "Move right" msgstr "nach rechts" #: src/menu/menu.c:672 msgid "Workspace" msgstr "Arbeitsfläche" #: src/menu/menu.c:675 msgid "Close" msgstr "Schließen" 07070100000054000081A4000000000000000000000001645896B8000004FD000000000000000000000000000000000000002200000000labwc-0.6.3+git0.63db731/po/es.po# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # Mick Amadio <01micko@gmail.com>, 2022. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2023-01-02 11:22+1000\n" "PO-Revision-Date: 2022-04-30 16:50+1000\n" "Last-Translator: Mick Amadio <01micko@gmail.com>\n" "Language-Team: Spanish <es>\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:631 msgid "Reconfigure" msgstr "Reconfigurar" #: src/menu/menu.c:633 msgid "Exit" msgstr "Salir" #: src/menu/menu.c:649 msgid "Minimize" msgstr "Minimizar" #: src/menu/menu.c:651 msgid "Maximize" msgstr "Maximizar" #: src/menu/menu.c:653 msgid "Fullscreen" msgstr "Pantalla completa" #: src/menu/menu.c:655 msgid "Decorations" msgstr "Decoraciones" #: src/menu/menu.c:657 msgid "AlwaysOnTop" msgstr "Siempre encima" #: src/menu/menu.c:662 msgid "Move left" msgstr "Mover a la izquierda" #: src/menu/menu.c:667 msgid "Move right" msgstr "Mover a la derecha" #: src/menu/menu.c:672 msgid "Workspace" msgstr "Espacio de trabajo" #: src/menu/menu.c:675 msgid "Close" msgstr "Cerrar" 07070100000055000081A4000000000000000000000001645896B800000507000000000000000000000000000000000000002200000000labwc-0.6.3+git0.63db731/po/id.po# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # May Mantari <https://github.com/mantarimay>, 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2023-01-02 11:22+1000\n" "PO-Revision-Date: 2023-04-14 20:40+0700\n" "Last-Translator: May Mantari <https://github.com/mantarimay\n" "Language-Team: Indonesian <id>\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:631 msgid "Reconfigure" msgstr "Atur Ulang" #: src/menu/menu.c:633 msgid "Exit" msgstr "Keluar" #: src/menu/menu.c:649 msgid "Minimize" msgstr "Sembunyikan" #: src/menu/menu.c:651 msgid "Maximize" msgstr "Perluas Jendela" #: src/menu/menu.c:653 msgid "Fullscreen" msgstr "Tampil Menyeluruh" #: src/menu/menu.c:655 msgid "Decorations" msgstr "Dekorasi" #: src/menu/menu.c:657 msgid "AlwaysOnTop" msgstr "Selalu di Muka" #: src/menu/menu.c:662 msgid "Move left" msgstr "Geser ke Kiri" #: src/menu/menu.c:667 msgid "Move right" msgstr "Geser ke Kanan" #: src/menu/menu.c:672 msgid "Workspace" msgstr "Ruang Kerja" #: src/menu/menu.c:675 msgid "Close" msgstr "Tutup" 07070100000056000081A4000000000000000000000001645896B8000004EE000000000000000000000000000000000000002200000000labwc-0.6.3+git0.63db731/po/it.po# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # Mick Amadio <01micko@gmail.com>, 2022. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2023-01-02 11:22+1000\n" "PO-Revision-Date: 2022-04-30 16:50+1000\n" "Last-Translator: Mick Amadio <01micko@gmail.com>\n" "Language-Team: Italian <it>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:631 msgid "Reconfigure" msgstr "Riconfigurare" #: src/menu/menu.c:633 msgid "Exit" msgstr "Uscita" #: src/menu/menu.c:649 msgid "Minimize" msgstr "Riduci" #: src/menu/menu.c:651 msgid "Maximize" msgstr "Ingrandisci" #: src/menu/menu.c:653 msgid "Fullscreen" msgstr "Schermo intero" #: src/menu/menu.c:655 msgid "Decorations" msgstr "Decorazioni" #: src/menu/menu.c:657 msgid "AlwaysOnTop" msgstr "Sempre sopra" #: src/menu/menu.c:662 msgid "Move left" msgstr "Sposta a sinistra" #: src/menu/menu.c:667 msgid "Move right" msgstr "Sposta a destra" #: src/menu/menu.c:672 msgid "Workspace" msgstr "Area di lavoro" #: src/menu/menu.c:675 msgid "Close" msgstr "Chiudi" 07070100000057000081A4000000000000000000000001645896B80000065B000000000000000000000000000000000000002200000000labwc-0.6.3+git0.63db731/po/ka.po# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # Temuri Doghonadze <temuri.doghonadze@gmail.com>, 2023. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2023-01-02 11:22+1000\n" "PO-Revision-Date: 2023-02-22 09:43+0100\n" "Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n" "Language-Team: Georgian <ka>\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:631 msgid "Reconfigure" msgstr "თავიდან მორგება" #: src/menu/menu.c:633 msgid "Exit" msgstr "გასვლა" #: src/menu/menu.c:649 msgid "Minimize" msgstr "ჩაკეცვა" #: src/menu/menu.c:651 msgid "Maximize" msgstr "გადიდება" #: src/menu/menu.c:653 msgid "Fullscreen" msgstr "მთელ ეკრანზე" #: src/menu/menu.c:655 msgid "Decorations" msgstr "დეკორაციები" #: src/menu/menu.c:657 msgid "AlwaysOnTop" msgstr "ყოველთვისყველაზეზემოდან" #: src/menu/menu.c:662 msgid "Move left" msgstr "მარცხნივ გაწევა" #: src/menu/menu.c:667 msgid "Move right" msgstr "მარჯვნივ გაწევა" #: src/menu/menu.c:672 msgid "Workspace" msgstr "სამუშაო ადგილი" #: src/menu/menu.c:675 msgid "Close" msgstr "დახურვა" 07070100000058000081A4000000000000000000000001645896B80000046E000000000000000000000000000000000000002600000000labwc-0.6.3+git0.63db731/po/labwc.pot# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2023-01-02 11:22+1000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\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:631 msgid "Reconfigure" msgstr "" #: src/menu/menu.c:633 msgid "Exit" msgstr "" #: src/menu/menu.c:649 msgid "Minimize" msgstr "" #: src/menu/menu.c:651 msgid "Maximize" msgstr "" #: src/menu/menu.c:653 msgid "Fullscreen" msgstr "" #: src/menu/menu.c:655 msgid "Decorations" msgstr "" #: src/menu/menu.c:657 msgid "AlwaysOnTop" msgstr "" #: src/menu/menu.c:662 msgid "Move left" msgstr "" #: src/menu/menu.c:667 msgid "Move right" msgstr "" #: src/menu/menu.c:672 msgid "Workspace" msgstr "" #: src/menu/menu.c:675 msgid "Close" msgstr "" 07070100000059000081A4000000000000000000000001645896B800000189000000000000000000000000000000000000002800000000labwc-0.6.3+git0.63db731/po/meson.buildi18n = 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' ) 0707010000005A000081A4000000000000000000000001645896B800000513000000000000000000000000000000000000002200000000labwc-0.6.3+git0.63db731/po/pl.po# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # Marcin Puc <tranzystorek.io@protonmail.com>, 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2023-01-02 11:22+1000\n" "PO-Revision-Date: 2023-04-24 14:45+0200\n" "Last-Translator: Marcin Puc <tranzystorek.io@protonmail.com>\n" "Language-Team: Polish <pl>\n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:631 msgid "Reconfigure" msgstr "Rekonfiguruj" #: src/menu/menu.c:633 msgid "Exit" msgstr "Wyjdź" #: src/menu/menu.c:649 msgid "Minimize" msgstr "Minimalizuj" #: src/menu/menu.c:651 msgid "Maximize" msgstr "Maksymalizuj" #: src/menu/menu.c:653 msgid "Fullscreen" msgstr "Pełny ekran" #: src/menu/menu.c:655 msgid "Decorations" msgstr "Dekoracje" #: src/menu/menu.c:657 msgid "AlwaysOnTop" msgstr "Zawsze na wierzchu" #: src/menu/menu.c:662 msgid "Move left" msgstr "Przenieś w lewo" #: src/menu/menu.c:667 msgid "Move right" msgstr "Przenieś w prawo" #: src/menu/menu.c:672 msgid "Workspace" msgstr "Przestrzeń robocza" #: src/menu/menu.c:675 msgid "Close" msgstr "Zamknij" 0707010000005B000081A4000000000000000000000001645896B80000059D000000000000000000000000000000000000002200000000labwc-0.6.3+git0.63db731/po/ru.po# Labwc pot file # Copyright (C) 2023 # This file is distributed under the same license as the labwc package. # Aleksey Samoilov <samoilov.lex@gmail.com>, 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2023-01-02 11:22+1000\n" "PO-Revision-Date: 2023-04-14 15:18+1000\n" "Last-Translator: Aleksey Samoilov <samoilov.lex@gmail.com>\n" "Language-Team: Russian <ru>\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:631 msgid "Reconfigure" msgstr "Перенастроить" #: src/menu/menu.c:633 msgid "Exit" msgstr "Выход" #: src/menu/menu.c:649 msgid "Minimize" msgstr "Свернуть" #: src/menu/menu.c:651 msgid "Maximize" msgstr "Распахнуть" #: src/menu/menu.c:653 msgid "Fullscreen" msgstr "На весь экран" #: src/menu/menu.c:655 msgid "Decorations" msgstr "Декорации" #: src/menu/menu.c:657 msgid "AlwaysOnTop" msgstr "Всегда на переднем плане" #: src/menu/menu.c:662 msgid "Move left" msgstr "Переместить влево" #: src/menu/menu.c:667 msgid "Move right" msgstr "Переместить вправо" #: src/menu/menu.c:672 msgid "Workspace" msgstr "Рабочее пространство" #: src/menu/menu.c:675 msgid "Close" msgstr "Закрыть" 0707010000005C000081A4000000000000000000000001645896B8000004EA000000000000000000000000000000000000002200000000labwc-0.6.3+git0.63db731/po/sv.po# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # Johan Malm <jgm323@gmail.com>, 2022. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2023-01-02 11:22+1000\n" "PO-Revision-Date: 2022-04-30 16:50+1000\n" "Last-Translator: Johan Malm <jgm323@gmail.com\n" "Language-Team: Swedish <sv>\n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:631 msgid "Reconfigure" msgstr "Konfigurera om" #: src/menu/menu.c:633 msgid "Exit" msgstr "Utgång" #: src/menu/menu.c:649 msgid "Minimize" msgstr "Minimera" #: src/menu/menu.c:651 msgid "Maximize" msgstr "Maximera" #: src/menu/menu.c:653 msgid "Fullscreen" msgstr "Fullskärm" #: src/menu/menu.c:655 msgid "Decorations" msgstr "Dekorationer" #: src/menu/menu.c:657 msgid "AlwaysOnTop" msgstr "Alltid överst" #: src/menu/menu.c:662 msgid "Move left" msgstr "Flytta till vänster" #: src/menu/menu.c:667 msgid "Move right" msgstr "Flytta till höger" #: src/menu/menu.c:672 msgid "Workspace" msgstr "Arbetsyta" #: src/menu/menu.c:675 msgid "Close" msgstr "Stäng" 0707010000005D000081A4000000000000000000000001645896B8000004DA000000000000000000000000000000000000002200000000labwc-0.6.3+git0.63db731/po/tr.po# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # Oğuz Ersen <oguz@ersen.moe>, 2022. # msgid "" msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2023-01-02 11:22+1000\n" "PO-Revision-Date: 2022-11-20 17:04+0300\n" "Last-Translator: Oğuz Ersen <oguz@ersen.moe>\n" "Language-Team: Turkish <tr>\n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:631 msgid "Reconfigure" msgstr "Yeniden yapılandır" #: src/menu/menu.c:633 msgid "Exit" msgstr "Çıkış" #: src/menu/menu.c:649 msgid "Minimize" msgstr "Küçült" #: src/menu/menu.c:651 msgid "Maximize" msgstr "Büyüt" #: src/menu/menu.c:653 msgid "Fullscreen" msgstr "Tam ekran" #: src/menu/menu.c:655 msgid "Decorations" msgstr "Süslemeler" #: src/menu/menu.c:657 msgid "AlwaysOnTop" msgstr "Her zaman üstte" #: src/menu/menu.c:662 msgid "Move left" msgstr "Sola git" #: src/menu/menu.c:667 msgid "Move right" msgstr "Sağa git" #: src/menu/menu.c:672 msgid "Workspace" msgstr "Çalışma alanı" #: src/menu/menu.c:675 msgid "Close" msgstr "Kapat" 0707010000005E000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002300000000labwc-0.6.3+git0.63db731/protocols0707010000005F000081A4000000000000000000000001645896B8000004B1000000000000000000000000000000000000002F00000000labwc-0.6.3+git0.63db731/protocols/meson.buildwl_protocol_dir = wayland_protos.get_variable(pkgconfig: '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/drm-lease/drm-lease-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, ) 07070100000060000081A4000000000000000000000001645896B800000C54000000000000000000000000000000000000004700000000labwc-0.6.3+git0.63db731/protocols/wlr-input-inhibitor-unstable-v1.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="wlr_input_inhibit_unstable_v1"> <copyright> 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. </copyright> <interface name="zwlr_input_inhibit_manager_v1" version="1"> <description summary="inhibits input events to other clients"> 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. </description> <request name="get_inhibitor"> <description summary="inhibit input to other clients"> Activates the input inhibitor. As long as the inhibitor is active, the compositor will not send input events to other clients. </description> <arg name="id" type="new_id" interface="zwlr_input_inhibitor_v1"/> </request> <enum name="error"> <entry name="already_inhibited" value="0" summary="an input inhibitor is already in use on the compositor"/> </enum> </interface> <interface name="zwlr_input_inhibitor_v1" version="1"> <description summary="inhibits input 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). </description> <request name="destroy" type="destructor"> <description summary="destroy the input inhibitor object"> Destroy the inhibitor and allow other clients to receive input. </description> </request> </interface> </protocol> 07070100000061000081A4000000000000000000000001645896B80000481E000000000000000000000000000000000000004300000000labwc-0.6.3+git0.63db731/protocols/wlr-layer-shell-unstable-v1.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="wlr_layer_shell_unstable_v1"> <copyright> 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. </copyright> <interface name="zwlr_layer_shell_v1" version="4"> <description summary="create surfaces that are layers of the desktop"> 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. </description> <request name="get_layer_surface"> <description summary="create a layer_surface from a surface"> 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. </description> <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/> <arg name="surface" type="object" interface="wl_surface"/> <arg name="output" type="object" interface="wl_output" allow-null="true"/> <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/> <arg name="namespace" type="string" summary="namespace for the layer surface"/> </request> <enum name="error"> <entry name="role" value="0" summary="wl_surface has another role"/> <entry name="invalid_layer" value="1" summary="layer value is invalid"/> <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/> </enum> <enum name="layer"> <description summary="available layers for surfaces"> 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. </description> <entry name="background" value="0"/> <entry name="bottom" value="1"/> <entry name="top" value="2"/> <entry name="overlay" value="3"/> </enum> <!-- Version 3 additions --> <request name="destroy" type="destructor" since="3"> <description summary="destroy the layer_shell object"> 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. </description> </request> </interface> <interface name="zwlr_layer_surface_v1" version="4"> <description summary="layer metadata interface"> 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. </description> <request name="set_size"> <description summary="sets the size of the surface"> 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. </description> <arg name="width" type="uint"/> <arg name="height" type="uint"/> </request> <request name="set_anchor"> <description summary="configures the anchor point of the surface"> 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. </description> <arg name="anchor" type="uint" enum="anchor"/> </request> <request name="set_exclusive_zone"> <description summary="configures the exclusive geometry of this surface"> 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. </description> <arg name="zone" type="int"/> </request> <request name="set_margin"> <description summary="sets a margin from the anchor point"> 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. </description> <arg name="top" type="int"/> <arg name="right" type="int"/> <arg name="bottom" type="int"/> <arg name="left" type="int"/> </request> <enum name="keyboard_interactivity"> <description summary="types of keyboard interaction possible for a layer shell surface"> 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. </description> <entry name="none" value="0"> <description summary="no keyboard focus is possible"> 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. </description> </entry> <entry name="exclusive" value="1"> <description summary="request exclusive keyboard focus"> 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. </description> </entry> <entry name="on_demand" value="2" since="4"> <description summary="request regular keyboard focus semantics"> 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. </description> </entry> </enum> <request name="set_keyboard_interactivity"> <description summary="requests keyboard events"> 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. </description> <arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/> </request> <request name="get_popup"> <description summary="assign this layer_surface as an xdg_popup parent"> 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. </description> <arg name="popup" type="object" interface="xdg_popup"/> </request> <request name="ack_configure"> <description summary="ack a configure event"> 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. </description> <arg name="serial" type="uint" summary="the serial from the configure event"/> </request> <request name="destroy" type="destructor"> <description summary="destroy the layer_surface"> This request destroys the layer surface. </description> </request> <event name="configure"> <description summary="suggest a surface change"> 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. </description> <arg name="serial" type="uint"/> <arg name="width" type="uint"/> <arg name="height" type="uint"/> </event> <event name="closed"> <description summary="surface should be closed"> 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. </description> </event> <enum name="error"> <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/> <entry name="invalid_size" value="1" summary="size is invalid"/> <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/> <entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/> </enum> <enum name="anchor" bitfield="true"> <entry name="top" value="1" summary="the top edge of the anchor rectangle"/> <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/> <entry name="left" value="4" summary="the left edge of the anchor rectangle"/> <entry name="right" value="8" summary="the right edge of the anchor rectangle"/> </enum> <!-- Version 2 additions --> <request name="set_layer" since="2"> <description summary="change the layer of the surface"> Change the layer that the surface is rendered on. Layer is double-buffered, see wl_surface.commit. </description> <arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/> </request> </interface> </protocol> 07070100000062000081A4000000000000000000000001645896B8000015DD000000000000000000000000000000000000004F00000000labwc-0.6.3+git0.63db731/protocols/wlr-output-power-management-unstable-v1.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="wlr_output_power_management_unstable_v1"> <copyright> 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. </copyright> <description summary="Control power management modes of outputs"> 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. </description> <interface name="zwlr_output_power_manager_v1" version="1"> <description summary="manager to create per-output power management"> This interface is a manager that allows creating per-output power management mode controls. </description> <request name="get_output_power"> <description summary="get a power management for an output"> Create an output power management mode control that can be used to adjust the power management mode for a given output. </description> <arg name="id" type="new_id" interface="zwlr_output_power_v1"/> <arg name="output" type="object" interface="wl_output"/> </request> <request name="destroy" type="destructor"> <description summary="destroy the manager"> All objects created by the manager will still remain valid, until their appropriate destroy request has been called. </description> </request> </interface> <interface name="zwlr_output_power_v1" version="1"> <description summary="adjust power management mode for an output"> This object offers requests to set the power management mode of an output. </description> <enum name="mode"> <entry name="off" value="0" summary="Output is turned off."/> <entry name="on" value="1" summary="Output is turned on, no power saving"/> </enum> <enum name="error"> <entry name="invalid_mode" value="1" summary="nonexistent power save mode"/> </enum> <request name="set_mode"> <description summary="Set an outputs power save mode"> 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. </description> <arg name="mode" type="uint" enum="mode" summary="the power save mode to set"/> </request> <event name="mode"> <description summary="Report a power management mode change"> 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. </description> <arg name="mode" type="uint" enum="mode" summary="the output's new power management mode"/> </event> <event name="failed"> <description summary="object no longer valid"> 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. </description> </event> <request name="destroy" type="destructor"> <description summary="destroy this power management"> Destroys the output power management mode control object. </description> </request> </interface> </protocol> 07070100000063000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002100000000labwc-0.6.3+git0.63db731/scripts07070100000064000081A4000000000000000000000001645896B800000206000000000000000000000000000000000000002B00000000labwc-0.6.3+git0.63db731/scripts/README.mdThese 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 <file>` [checkpatch.pl]: https://raw.githubusercontent.com/torvalds/linux/4ce9f970457899defdf68e26e0502c7245002eb3/scripts/checkpatch.pl 07070100000065000081ED000000000000000000000001645896B80000033D000000000000000000000000000000000000002700000000labwc-0.6.3+git0.63db731/scripts/check#!/bin/sh file= usage_message="Usage: check [OPTIONS] OPTIONS: --file=<filename> 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 "$@" 07070100000066000081ED000000000000000000000001645896B8000375C4000000000000000000000000000000000000002F00000000labwc-0.6.3+git0.63db731/scripts/checkpatch.pl#!/usr/bin/env perl # SPDX-License-Identifier: GPL-2.0 # # (c) 2001, Dave Jones. (the file handling bit) # (c) 2005, Joel Schopp <jschopp@austin.ibm.com> (the ugly bit) # (c) 2007,2008, Andy Whitcroft <apw@uk.ibm.com> (new conditions, test suite) # (c) 2008-2010 Andy Whitcroft <apw@canonical.com> # (c) 2010-2018 Joe Perches <joe@perches.com> 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", "ARRAY_SIZE", "INITIALISED_STATIC", "UNNECESSARY_ELSE", ); 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: <rev> <rev>^ <rev>~n multiple git commits with: <rev1>..<rev2> <rev1>...<rev2> <rev>-<count> 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 "<inputfile>.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 <joe@smith.com" bad if ($name ne "" && $address !~ /^<[^>]+>$/) { $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/(?<!\\)"/\\"/g; ##escape quotes $name = "\"$name\""; } return ($name, $name_comment, $address, $comment); } sub format_email { my ($name, $name_comment, $address, $comment) = @_; my $formatted_email; $name =~ s/^[ \"]+|[ \"]+$//g; $address = trim($address); $address =~ s/(?:\.|\,|\")+$//; ##trailing commas, dots or quotes if ($name =~ /[^\w \-]/i) { ##has "must quote" chars $name =~ s/(?<!\\)"/\\"/g; ##escape quotes $name = "\"$name\""; } $name_comment = trim($name_comment); $name_comment = " $name_comment" if ($name_comment ne ""); $comment = trim($comment); $comment = " $comment" if ($comment ne ""); if ("$name" eq "") { $formatted_email = "$address"; } else { $formatted_email = "$name$name_comment <$address>"; } $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-<architecture>\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/" </ </; # Don't force email to have quotes # Allow just an angle bracketed address if (!same_email_addresses($email, $suggested_email)) { if (WARN("BAD_SIGN_OFF", "email address '$email' might be better as '$suggested_email'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$email\E/$suggested_email/; } } # Address part shouldn't have comments my $stripped_address = $email_address; $stripped_address =~ s/\([^\(\)]*\)//g; if ($email_address ne $stripped_address) { if (WARN("BAD_SIGN_OFF", "address part of email should not have comments: '$email_address'\n" . $herecurr) && $fix) { $fixed[$fixlinenr] =~ s/\Q$email_address\E/$stripped_address/; } } # Only one name comment should be allowed my $comment_count = () = $name_comment =~ /\([^\)]+\)/g; if ($comment_count > 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 <SHA-1 hash length 12+ chars> ("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> (\"<title line>\")' - 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); } } } # 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 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]/ && #Ignore some pango and libxml2 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 !~ /^(?:GString|GError)/ && $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; } 07070100000067000041ED000000000000000000000008645896B800000000000000000000000000000000000000000000001D00000000labwc-0.6.3+git0.63db731/src07070100000068000081A4000000000000000000000001645896B800003854000000000000000000000000000000000000002600000000labwc-0.6.3+git0.63db731/src/action.c// 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/list.h" #include "common/mem.h" #include "common/parse-bool.h" #include "common/spawn.h" #include "debug.h" #include "labwc.h" #include "menu/menu.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, }; 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; }; 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_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_FOCUS, ACTION_TYPE_ICONIFY, ACTION_TYPE_MOVE, ACTION_TYPE_RAISE, ACTION_TYPE_LOWER, ACTION_TYPE_RESIZE, ACTION_TYPE_GO_TO_DESKTOP, ACTION_TYPE_SEND_TO_DESKTOP, ACTION_TYPE_SNAP_TO_REGION, ACTION_TYPE_TOGGLE_KEYBINDS, ACTION_TYPE_FOCUS_OUTPUT, }; const char *action_names[] = { "INVALID", "None", "Close", "Kill", "Debug", "Execute", "Exit", "MoveToEdge", "SnapToEdge", "NextWindow", "PreviousWindow", "Reconfigure", "ShowMenu", "ToggleMaximize", "Maximize", "ToggleFullscreen", "ToggleDecorations", "ToggleAlwaysOnTop", "Focus", "Iconify", "Move", "Raise", "Lower", "Resize", "GoToDesktop", "SendToDesktop", "SnapToRegion", "ToggleKeybinds", "FocusOutput", NULL }; void action_arg_add_str(struct action *action, char *key, const char *value) { assert(value && "Tried to add NULL action string argument"); struct action_arg_str *arg = znew(*arg); arg->base.type = LAB_ACTION_ARG_STR; if (key) { 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, char *key, bool value) { struct action_arg_bool *arg = znew(*arg); arg->base.type = LAB_ACTION_ARG_BOOL; if (key) { arg->base.key = xstrdup(key); } arg->value = value; wl_list_append(&action->args, &arg->base.link); } void action_arg_from_xml_node(struct action *action, char *nodename, char *content) { assert(action); if (!strcmp(nodename, "command.action")) { /* Execute */ action_arg_add_str(action, NULL, content); } else if (!strcmp(nodename, "execute.action")) { /* * <action name="Execute"><execute>foo</execute></action> * is deprecated, but we support it anyway for backward * compatibility with old openbox-menu generators */ action_arg_add_str(action, NULL, content); } else if (!strcmp(nodename, "direction.action")) { /* MoveToEdge, SnapToEdge */ action_arg_add_str(action, NULL, content); } else if (!strcmp(nodename, "menu.action")) { /* ShowMenu */ action_arg_add_str(action, NULL, content); } else if (!strcmp(nodename, "to.action")) { /* GoToDesktop, SendToDesktop */ action_arg_add_str(action, "to", content); } else if (!strcmp(nodename, "follow.action")) { /* SendToDesktop */ action_arg_add_bool(action, "follow", parse_bool(content, true)); } else if (!strcmp(nodename, "region.action")) { /* SnapToRegion */ action_arg_add_str(action, NULL, content); } else if (!strcmp(nodename, "output.action")) { /* FocusOutput */ action_arg_add_str(action, NULL, content); } } static const char * action_str_from_arg(struct action_arg *arg) { assert(arg->type == LAB_ACTION_ARG_STR); return ((struct action_arg_str *)arg)->value; } static const char * get_arg_value_str(struct action *action, const char *key, const char *default_value) { assert(key); struct action_arg *arg; wl_list_for_each(arg, &action->args, link) { if (!arg->key) { continue; } if (!strcasecmp(key, arg->key)) { return action_str_from_arg(arg); } } return default_value; } static bool get_arg_value_bool(struct action *action, const char *key, bool default_value) { assert(key); struct action_arg *arg; wl_list_for_each(arg, &action->args, link) { if (!arg->key) { continue; } if (!strcasecmp(key, arg->key)) { assert(arg->type == LAB_ACTION_ARG_BOOL); return ((struct action_arg_bool *)arg)->value; } } return default_value; } static struct action_arg * action_get_first_arg(struct action *action) { struct action_arg *arg; struct wl_list *item = action->args.next; if (item == &action->args) { return NULL; } return wl_container_of(item, arg, link); } 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; } void action_list_free(struct wl_list *action_list) { struct action_arg *arg, *arg_tmp; struct action *action, *action_tmp; /* Free actions */ wl_list_for_each_safe(action, action_tmp, action_list, link) { wl_list_remove(&action->link); /* Free args */ 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) { free((void *)action_str_from_arg(arg)); } zfree(arg); } zfree(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(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 desktop_focused_view(server); } } 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; struct action_arg *arg; wl_list_for_each(action, actions, link) { /* Get arg now so we don't have to repeat every time we only need one */ arg = action_get_first_arg(action); if (arg && arg->type == LAB_ACTION_ARG_STR) { wlr_log(WLR_DEBUG, "Handling action %u: %s %s", action->type, action_names[action->type], action_str_from_arg(arg)); } else { 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: if (!arg) { wlr_log(WLR_ERROR, "Missing argument for Execute"); break; } struct buf cmd; buf_init(&cmd); buf_add(&cmd, action_str_from_arg(arg)); buf_expand_shell_variables(&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 (!arg) { wlr_log(WLR_ERROR, "Missing argument for MoveToEdge"); break; } if (view) { view_move_to_edge(view, action_str_from_arg(arg)); } break; case ACTION_TYPE_SNAP_TO_EDGE: if (!arg) { wlr_log(WLR_ERROR, "Missing argument for SnapToEdge"); break; } if (view) { view_snap_to_edge(view, action_str_from_arg(arg), /*store_natural_geometry*/ true); } 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: if (arg) { show_menu(server, view, action_str_from_arg(arg)); } else { wlr_log(WLR_ERROR, "Missing argument for ShowMenu"); } break; case ACTION_TYPE_TOGGLE_MAXIMIZE: if (view) { view_toggle_maximize(view); } break; case ACTION_TYPE_MAXIMIZE: if (view) { view_maximize(view, true, /*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_FOCUS: if (view) { desktop_focus_and_activate_view(&server->seat, view); } 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_GO_TO_DESKTOP: if (!arg) { wlr_log(WLR_ERROR, "Missing argument for GoToDesktop"); break; } struct workspace *target; const char *target_name = action_str_from_arg(arg); target = workspaces_find(server->workspace_current, target_name); if (target) { workspaces_switch_to(target); } break; case ACTION_TYPE_SEND_TO_DESKTOP: if (view) { const char *to = get_arg_value_str(action, "to", NULL); if (!to) { wlr_log(WLR_ERROR, "Missing 'to' argument for SendToDesktop"); break; } bool follow = get_arg_value_bool(action, "follow", true); struct workspace *target = workspaces_find(view->workspace, to); if (target) { view_move_to_workspace(view, target); if (follow) { workspaces_switch_to(target); } } } break; case ACTION_TYPE_SNAP_TO_REGION: if (!arg) { wlr_log(WLR_ERROR, "Missing argument for SnapToRegion"); break; } if (!view) { break; } struct output *output = view->output; if (!output) { break; } const char *region_name = action_str_from_arg(arg); 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: server->seat.inhibit_keybinds = !server->seat.inhibit_keybinds; wlr_log(WLR_DEBUG, "%s keybinds", server->seat.inhibit_keybinds ? "Disabled" : "Enabled"); break; case ACTION_TYPE_FOCUS_OUTPUT: if (!arg) { wlr_log(WLR_ERROR, "Missing argument for FocusOutput"); break; } const char *output_name = action_str_from_arg(arg); desktop_focus_output(output_from_name(server, output_name)); 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); } } } 07070100000069000081A4000000000000000000000001645896B8000010BA000000000000000000000000000000000000002600000000labwc-0.6.3+git0.63db731/src/buffer.c// 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->data = pixel_data; buffer->format = DRM_FORMAT_ARGB8888; buffer->stride = stride; buffer->free_on_destroy = free_on_destroy; return buffer; } 0707010000006A000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002400000000labwc-0.6.3+git0.63db731/src/common0707010000006B000081A4000000000000000000000001645896B8000006CD000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/src/common/buf.c// SPDX-License-Identifier: GPL-2.0-only #include <ctype.h> #include "common/buf.h" #include "common/mem.h" static void strip_curly_braces(char *s) { size_t len = strlen(s); if (s[0] != '{' || s[len - 1] != '}') { return; } len -= 2; memcpy(s, s + 1, len); s[len] = 0; } 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] == '$') { /* expand environment variable */ environment_variable.len = 0; buf_add(&environment_variable, s->buf + i + 1); char *p = environment_variable.buf; while (isalnum(*p) || *p == '_' || *p == '{' || *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 if (s->buf[i] == '~') { /* expand tilde */ buf_add(&new, getenv("HOME")); } else { /* just add one character at a time */ if (new.alloc <= new.len + 1) { new.alloc = new.alloc * 3 / 2 + 16; new.buf = xrealloc(new.buf, new.alloc); } new.buf[new.len++] = s->buf[i]; new.buf[new.len] = '\0'; } } free(environment_variable.buf); free(s->buf); s->buf = new.buf; } 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; } 0707010000006C000081A4000000000000000000000001645896B800000B72000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/src/common/dir.c// SPDX-License-Identifier: GPL-2.0-only /* * Find the configuration and theme directories * * Copyright Johan Malm 2020 */ #include <glib.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include "common/dir.h" struct dir { const char *prefix; const char *path; }; static struct dir config_dirs[] = { { "XDG_CONFIG_HOME", "labwc" }, { "HOME", ".config/labwc" }, { "XDG_CONFIG_DIRS", "labwc" }, { NULL, "/etc/xdg/labwc" }, { NULL, NULL } }; static struct dir theme_dirs[] = { { "XDG_DATA_HOME", "themes" }, { "HOME", ".local/share/themes" }, { "HOME", ".themes" }, { "XDG_DATA_DIRS", "themes" }, { NULL, "/usr/share/themes" }, { NULL, "/usr/local/share/themes" }, { NULL, "/opt/share/themes" }, { NULL, NULL } }; static bool isdir(const char *path) { struct stat st; return (!stat(path, &st) && S_ISDIR(st.st_mode)); } struct ctx { void (*build_path_fn)(struct ctx *ctx, char *prefix, const char *path); char *buf; size_t len; struct dir *dirs; const char *theme_name; }; static void build_config_path(struct ctx *ctx, char *prefix, const char *path) { if (!prefix) { snprintf(ctx->buf, ctx->len, "%s", path); } else { snprintf(ctx->buf, ctx->len, "%s/%s", prefix, path); } } static void build_theme_path(struct ctx *ctx, char *prefix, const char *path) { if (!prefix) { snprintf(ctx->buf, ctx->len, "%s/%s/openbox-3", path, ctx->theme_name); } else { snprintf(ctx->buf, ctx->len, "%s/%s/%s/openbox-3", prefix, path, ctx->theme_name); } } static char * 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]; if (!d.prefix) { /* handle /etc/xdg... */ ctx->build_path_fn(ctx, NULL, d.path); if (debug) { fprintf(stderr, "%s\n", ctx->buf); } if (isdir(ctx->buf)) { return ctx->buf; } } else { /* handle $HOME/.config/... and $XDG_* */ char *prefix = getenv(d.prefix); if (!prefix) { continue; } gchar * *prefixes; prefixes = g_strsplit(prefix, ":", -1); for (gchar * *p = prefixes; *p; p++) { ctx->build_path_fn(ctx, *p, d.path); if (debug) { fprintf(stderr, "%s\n", ctx->buf); } if (isdir(ctx->buf)) { g_strfreev(prefixes); return ctx->buf; } } g_strfreev(prefixes); } } /* no directory was found */ ctx->buf[0] = '\0'; return ctx->buf; } char * config_dir(void) { static char buf[4096] = { 0 }; if (buf[0] != '\0') { return buf; } struct ctx ctx = { .build_path_fn = build_config_path, .buf = buf, .len = sizeof(buf), .dirs = config_dirs }; return find_dir(&ctx); } char * theme_dir(const char *theme_name) { static char buf[4096] = { 0 }; struct ctx ctx = { .build_path_fn = build_theme_path, .buf = buf, .len = sizeof(buf), .dirs = theme_dirs, .theme_name = theme_name }; return find_dir(&ctx); } 0707010000006D000081A4000000000000000000000001645896B8000003FE000000000000000000000000000000000000002E00000000labwc-0.6.3+git0.63db731/src/common/fd_util.c// 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"); } } 0707010000006E000081A4000000000000000000000001645896B800000F43000000000000000000000000000000000000002B00000000labwc-0.6.3+git0.63db731/src/common/font.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #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 "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 :) */ 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 (!text || !*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); } 0707010000006F000081A4000000000000000000000001645896B800000252000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/src/common/grab-file.c// 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; } 07070100000070000081A4000000000000000000000001645896B800000A31000000000000000000000000000000000000003600000000labwc-0.6.3+git0.63db731/src/common/graphic-helpers.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <cairo.h> #include <stdlib.h> #include <wlr/types/wlr_scene.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, double width, double height, double line_width) { cairo_save(cairo); double x, y, w, h; /* The anchor point of a line is in the center */ x = line_width / 2; y = x; w = width - line_width; h = height - line_width; cairo_set_line_width(cairo, line_width); cairo_rectangle(cairo, x, y, w, h); 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]); } 07070100000071000081A4000000000000000000000001645896B80000015B000000000000000000000000000000000000002C00000000labwc-0.6.3+git0.63db731/src/common/match.c// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <stdbool.h> #include "common/match.h" bool match_glob(const gchar *pattern, const gchar *string) { gchar *p = g_utf8_casefold(pattern, -1); gchar *s = g_utf8_casefold(string, -1); bool ret = g_pattern_match_simple(p, s); g_free(p); g_free(s); return ret; } 07070100000072000081A4000000000000000000000001645896B800000295000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/src/common/mem.c// 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; } 07070100000073000081A4000000000000000000000001645896B80000011A000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/src/common/meson.buildlabwc_sources += files( 'buf.c', 'dir.c', 'fd_util.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', ) 07070100000074000081A4000000000000000000000001645896B80000029D000000000000000000000000000000000000002F00000000labwc-0.6.3+git0.63db731/src/common/nodename.c// 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 superflous '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; } } } 07070100000075000081A4000000000000000000000001645896B80000033C000000000000000000000000000000000000003100000000labwc-0.6.3+git0.63db731/src/common/parse-bool.c// SPDX-License-Identifier: GPL-2.0-only #include <string.h> #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, "no")) { return false; } else if (!strcasecmp(str, "false")) { 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; } 07070100000076000081A4000000000000000000000001645896B800000A12000000000000000000000000000000000000003900000000labwc-0.6.3+git0.63db731/src/common/scaled_font_buffer.c// 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; zfree(self->text); zfree(self->font.name); zfree(self->arrow); zfree(scaled_buffer->data); } 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); 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); } 07070100000077000081A4000000000000000000000001645896B80000184A000000000000000000000000000000000000003A00000000labwc-0.6.3+git0.63db731/src/common/scaled_scene_buffer.c// 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_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) { wl_list_remove(&cache_entry->link); if (cache_entry->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); 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) { 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); } 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) { 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; 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); } assert(wl_list_empty(&self->cache)); _update_buffer(self, self->active_scale); } 07070100000078000081A4000000000000000000000001645896B800000459000000000000000000000000000000000000003400000000labwc-0.6.3+git0.63db731/src/common/scene-helpers.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <wlr/types/wlr_scene.h> #include "common/scene-helpers.h" struct wlr_scene_rect * lab_wlr_scene_get_rect(struct wlr_scene_node *node) { assert(node->type == WLR_SCENE_NODE_RECT); return (struct wlr_scene_rect *)node; } struct wlr_scene_tree * lab_scene_tree_from_node(struct wlr_scene_node *node) { assert(node->type == WLR_SCENE_NODE_TREE); return (struct wlr_scene_tree *)node; } 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_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; } 07070100000079000081A4000000000000000000000001645896B8000004F5000000000000000000000000000000000000002C00000000labwc-0.6.3+git0.63db731/src/common/spawn.c// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <glib.h> #include <signal.h> #include <stdint.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); 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); } 0707010000007A000081A4000000000000000000000001645896B800000220000000000000000000000000000000000000003500000000labwc-0.6.3+git0.63db731/src/common/string-helpers.c// SPDX-License-Identifier: GPL-2.0-only #include <ctype.h> #include <stdio.h> #include <string.h> #include "common/string-helpers.h" 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'; } 0707010000007B000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002400000000labwc-0.6.3+git0.63db731/src/config0707010000007C000081A4000000000000000000000001645896B800000823000000000000000000000000000000000000002E00000000labwc-0.6.3+git0.63db731/src/config/keybind.c// 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" uint32_t parse_modifier(const char *symname) { if (!strcmp(symname, "S")) { return WLR_MODIFIER_SHIFT; } else if (!strcmp(symname, "C")) { return WLR_MODIFIER_CTRL; } else if (!strcmp(symname, "A")) { return WLR_MODIFIER_ALT; } else if (!strcmp(symname, "W")) { return WLR_MODIFIER_LOGO; } 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; } struct keybind * keybind_create(const char *keybind) { 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 { xkb_keysym_t sym = xkb_keysym_to_lower( xkb_keysym_from_name(symname, XKB_KEYSYM_CASE_INSENSITIVE)); 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; } 0707010000007D000081A4000000000000000000000001645896B80000051C000000000000000000000000000000000000002F00000000labwc-0.6.3+git0.63db731/src/config/libinput.c// SPDX-License-Identifier: GPL-2.0-only #include <string.h> #include <strings.h> #include "common/mem.h" #include "config/libinput.h" #include "config/rcxml.h" static void libinput_category_init(struct libinput_category *l) { l->type = DEFAULT_DEVICE; 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->accel_profile = -1; l->middle_emu = -1; l->dwt = -1; } enum device_type get_device_type(const char *s) { if (!s) { return DEFAULT_DEVICE; } if (!strcasecmp(s, "touch")) { return TOUCH_DEVICE; } if (!strcasecmp(s, "non-touch")) { return NON_TOUCH_DEVICE; } return DEFAULT_DEVICE; } struct libinput_category * libinput_category_create(void) { struct libinput_category *l = znew(*l); libinput_category_init(l); wl_list_insert(&rc.libinput_categories, &l->link); return l; } /* * The default category is the first one with type == DEFAULT_DEVICE and * no name. After rcxml_read(), a default category always exists. */ struct libinput_category * libinput_category_get_default(void) { struct libinput_category *l; wl_list_for_each(l, &rc.libinput_categories, link) { if (l->type == DEFAULT_DEVICE && !l->name) { return l; } } return NULL; } 0707010000007E000081A4000000000000000000000001645896B800000066000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/src/config/meson.buildlabwc_sources += files( 'rcxml.c', 'keybind.c', 'session.c', 'mousebind.c', 'libinput.c', ) 0707010000007F000081A4000000000000000000000001645896B8000010F0000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/src/config/mousebind.c// 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; } 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; } 07070100000080000081A4000000000000000000000001645896B80000841B000000000000000000000000000000000000002C00000000labwc-0.6.3+git0.63db731/src/config/rcxml.c// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <libxml/parser.h> #include <libxml/tree.h> #include <stdbool.h> #include <stdio.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/list.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/rcxml.h" #include "regions.h" #include "window-rules.h" #include "workspaces.h" static bool in_regions; static bool in_keybind; static bool in_mousebind; static bool in_libinput_category; static bool in_window_switcher_field; static bool in_window_rules; static struct keybind *current_keybind; static struct mousebind *current_mousebind; 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; enum font_place { FONT_PLACE_NONE = 0, FONT_PLACE_UNKNOWN, FONT_PLACE_ACTIVEWINDOW, 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); /* 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"); } else if (!strcmp(nodename, "identifier")) { free(current_window_rule->identifier); current_window_rule->identifier = xstrdup(content); } 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; } } else if (!strcasecmp(nodename, "serverDecoration")) { set_property(content, ¤t_window_rule->server_decoration); } 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, "app_id")) { current_field->content = LAB_FIELD_APP_ID; } 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_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 (!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 enum libinput_config_accel_profile get_accel_profile(const char *s) { if (!s) { return LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; } if (!strcasecmp(s, "flat")) { return LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; } if (!strcasecmp(s, "adaptive")) { return LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; } return LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; } static void fill_libinput_category(char *nodename, char *content) { if (!strcmp(nodename, "category.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")) { if (!strcmp(content, "touch") || !strcmp(content, "non-touch") || !strcmp(content, "default")) { current_libinput_category->type = get_device_type(content); } else { 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, "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; } } 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_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_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, "MenuItem")) { return FONT_PLACE_MENUITEM; } else if (!strcasecmp(place, "OnScreenDisplay") || !strcasecmp(place, "OSD")) { return FONT_PLACE_OSD; } return FONT_PLACE_UNKNOWN; } static void entry(xmlNode *node, char *nodename, char *content) { /* current <theme><font place=""></font></theme> */ static enum font_place font_place = FONT_PLACE_NONE; 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_keybind) { fill_keybind(nodename, content); } if (in_mousebind) { fill_mousebind(nodename, content); } if (in_libinput_category) { fill_libinput_category(nodename, content); } 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; } /* 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_bool(content, &rc.adaptive_sync); } else if (!strcasecmp(nodename, "reuseOutputMode.core")) { set_bool(content, &rc.reuse_output_mode); } else if (!strcmp(nodename, "name.theme")) { rc.theme_name = xstrdup(content); } else if (!strcmp(nodename, "cornerradius.theme")) { rc.corner_radius = atoi(content); } 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, "screenEdgeStrength.resistance")) { rc.screen_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); /* <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); } } 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, "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, "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; } 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.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); } has_run = true; rc.xdg_shell_server_side_deco = true; rc.corner_radius = 8; init_font_defaults(&rc.font_activewindow); 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.repeat_rate = 25; rc.repeat_delay = 600; rc.screen_edge_strength = 20; rc.snap_edge_range = 1; rc.snap_top_maximize = true; rc.window_switcher.show = true; rc.window_switcher.preview = true; rc.window_switcher.outlines = true; rc.workspace_config.popuptime = INT_MIN; } static struct { const char *binding, *action, *command; } key_combos[] = { { "A-Tab", "NextWindow", NULL }, { "W-Return", "Execute", "alacritty" }, { "A-F3", "Execute", "bemenu-run" }, { "A-F4", "Close", NULL }, { "W-a", "ToggleMaximize", NULL }, { "A-Left", "MoveToEdge", "left" }, { "A-Right", "MoveToEdge", "right" }, { "A-Up", "MoveToEdge", "up" }, { "A-Down", "MoveToEdge", "down" }, { "W-Left", "SnapToEdge", "left" }, { "W-Right", "SnapToEdge", "right" }, { "W-Up", "SnapToEdge", "up" }, { "W-Down", "SnapToEdge", "down" }, { "A-Space", "ShowMenu", "client-menu"}, { "XF86_AudioLowerVolume", "Execute", "amixer sset Master 5%-" }, { "XF86_AudioRaiseVolume", "Execute", "amixer sset Master 5%+" }, { "XF86_AudioMute", "Execute", "amixer sset Master toggle" }, { "XF86_MonBrightnessUp", "Execute", "brightnessctl set +10%" }, { "XF86_MonBrightnessDown", "Execute", "brightnessctl set 10%-" }, { 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].command) { action_arg_add_str(action, NULL, key_combos[i].command); } } } static struct mouse_combos { const char *context, *button, *event, *action, *command; } mouse_combos[] = { { "Left", "Left", "Drag", "Resize", NULL}, { "Top", "Left", "Drag", "Resize", NULL}, { "Bottom", "Left", "Drag", "Resize", NULL}, { "Right", "Left", "Drag", "Resize", NULL}, { "TLCorner", "Left", "Drag", "Resize", NULL}, { "TRCorner", "Left", "Drag", "Resize", NULL}, { "BRCorner", "Left", "Drag", "Resize", NULL}, { "BLCorner", "Left", "Drag", "Resize", NULL}, { "Frame", "A-Left", "Press", "Focus", NULL}, { "Frame", "A-Left", "Press", "Raise", NULL}, { "Frame", "A-Left", "Drag", "Move", NULL}, { "Frame", "A-Right", "Press", "Focus", NULL}, { "Frame", "A-Right", "Press", "Raise", NULL}, { "Frame", "A-Right", "Drag", "Resize", NULL}, { "Titlebar", "Left", "Press", "Focus", NULL}, { "Titlebar", "Left", "Press", "Raise", NULL}, { "Title", "Left", "Drag", "Move", NULL }, { "Title", "Left", "DoubleClick", "ToggleMaximize", NULL }, { "TitleBar", "Right", "Click", "Focus", NULL}, { "TitleBar", "Right", "Click", "Raise", NULL}, { "TitleBar", "Right", "Click", "ShowMenu", "client-menu"}, { "Close", "Left", "Click", "Close", NULL }, { "Iconify", "Left", "Click", "Iconify", NULL}, { "Maximize", "Left", "Click", "ToggleMaximize", NULL}, { "WindowMenu", "Left", "Click", "ShowMenu", "client-menu"}, { "Root", "Left", "Press", "ShowMenu", "root-menu"}, { "Root", "Right", "Press", "ShowMenu", "root-menu"}, { "Root", "Middle", "Press", "ShowMenu", "root-menu"}, { "Root", "Up", "Scroll", "GoToDesktop", "left"}, { "Root", "Down", "Scroll", "GoToDesktop", "right"}, { "Client", "Left", "Press", "Focus", NULL}, { "Client", "Left", "Press", "Raise", NULL}, { "Client", "Right", "Press", "Focus", NULL}, { "Client", "Right", "Press", "Raise", NULL}, { "Client", "Middle", "Press", "Focus", NULL}, { "Client", "Middle", "Press", "Raise", 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); if (current->command) { action_arg_add_str(action, NULL, current->command); } } 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_APP_ID, 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_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(); assert(l && libinput_category_get_default() == l); } if (!wl_list_length(&rc.workspace_config.workspaces)) { struct workspace *workspace = znew(*workspace); workspace->name = xstrdup("Default"); wl_list_append(&rc.workspace_config.workspaces, &workspace->link); } if (rc.workspace_config.popuptime == INT_MIN) { rc.workspace_config.popuptime = 1000; } 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); } } if (!wl_list_length(&rc.window_switcher.fields)) { wlr_log(WLR_INFO, "load default window switcher fields"); load_default_window_switcher_fields(); } } static void rcxml_path(char *buf, size_t len) { if (!rc.config_dir) { return; } snprintf(buf, len, "%s/rc.xml", rc.config_dir); } static void find_config_file(char *buffer, size_t len, const char *filename) { if (filename) { snprintf(buffer, len, "%s", filename); return; } rcxml_path(buffer, len); } void rcxml_read(const char *filename) { FILE *stream; char *line = NULL; size_t len = 0; struct buf b; static char rcxml[4096] = {0}; rcxml_init(); /* * rcxml_read() can be called multiple times, but we only set rcxml[] * the first time. The specified 'filename' is only respected the first * time. */ if (rcxml[0] == '\0') { find_config_file(rcxml, sizeof(rcxml), filename); } if (rcxml[0] == '\0') { wlr_log(WLR_INFO, "cannot find rc.xml config file"); goto no_config; } /* Reading file into buffer before parsing - better for unit tests */ stream = fopen(rcxml, "r"); if (!stream) { wlr_log(WLR_ERROR, "cannot read (%s)", rcxml); goto no_config; } wlr_log(WLR_INFO, "read config file %s", rcxml); buf_init(&b); while (getline(&line, &len, stream) != -1) { char *p = strrchr(line, '\n'); if (p) { *p = '\0'; } buf_add(&b, line); } free(line); fclose(stream); rcxml_parse_xml(&b); free(b.buf); no_config: post_processing(); } void rcxml_finish(void) { zfree(rc.font_activewindow.name); zfree(rc.font_menuitem.name); zfree(rc.font_osd.name); zfree(rc.theme_name); 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 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) { wl_list_remove(&rule->link); zfree(rule->identifier); action_list_free(&rule->actions); zfree(rule); } /* Reset state vars for starting fresh when Reload is triggered */ current_keybind = NULL; current_mousebind = NULL; current_libinput_category = NULL; current_mouse_context = NULL; current_keybind_action = NULL; current_mousebind_action = NULL; current_region = NULL; current_field = NULL; current_window_rule = NULL; current_window_rule_action = NULL; } 07070100000081000081A4000000000000000000000001645896B800000D87000000000000000000000000000000000000002E00000000labwc-0.6.3+git0.63db731/src/config/session.c// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <ctype.h> #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/mem.h" #include "common/spawn.h" #include "common/string-helpers.h" #include "config/session.h" static bool isfile(const char *path) { struct stat st; return (!stat(path, &st)); } static bool string_empty(const char *s) { return !s || !*s; } static void process_line(char *line) { if (string_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); if (string_empty(key) || !value.len) { goto error; } setenv(key, value.buf, 1); error: free(value.buf); } static void read_environment_file(const char *filename) { char *line = NULL; size_t len = 0; FILE *stream = fopen(filename, "r"); if (!stream) { return; } 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); } static const char * build_path(const char *dir, const char *filename) { if (string_empty(dir) || string_empty(filename)) { return NULL; } int len = strlen(dir) + strlen(filename) + 2; char *buffer = znew_n(char, len); strcat(buffer, dir); strcat(buffer, "/"); strcat(buffer, filename); return buffer; } 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; const char *dbus = "dbus-update-activation-environment "; const char *systemd = "systemctl --user import-environment "; cmd = znew_n(char, strlen(dbus) + strlen(env_keys) + 1); strcat(cmd, dbus); strcat(cmd, env_keys); spawn_async_no_shell(cmd); free(cmd); cmd = znew_n(char, strlen(systemd) + strlen(env_keys) + 1); strcat(cmd, systemd); strcat(cmd, env_keys); spawn_async_no_shell(cmd); free(cmd); } void session_environment_init(const char *dir) { /* * Set default for XDG_CURRENT_DESKTOP so xdg-desktop-portal-wlr is happy. * May be overriden either by already having a value set or by the user * supplied environment file. */ setenv("XDG_CURRENT_DESKTOP", "wlroots", 0); const char *environment = build_path(dir, "environment"); if (!environment) { return; } read_environment_file(environment); free((void *)environment); } void session_autostart_init(const char *dir) { /* Update dbus and systemd user environment, each may fail gracefully */ update_activation_env("DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP"); const char *autostart = build_path(dir, "autostart"); if (!autostart) { return; } if (!isfile(autostart)) { wlr_log(WLR_ERROR, "no autostart file"); goto out; } wlr_log(WLR_INFO, "run autostart file %s", autostart); int len = strlen(autostart) + 4; char *cmd = znew_n(char, len); strcat(cmd, "sh "); strcat(cmd, autostart); spawn_async_no_shell(cmd); free(cmd); out: free((void *)autostart); } 07070100000082000081A4000000000000000000000001645896B80000965A000000000000000000000000000000000000002600000000labwc-0.6.3+git0.63db731/src/cursor.c// 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_primary_selection.h> #include <wlr/util/region.h> #include "action.h" #include "common/mem.h" #include "common/scene-helpers.h" #include "config/mousebind.h" #include "dnd.h" #include "labwc.h" #include "menu/menu.h" #include "regions.h" #include "resistance.h" #include "ssd.h" #include "view.h" 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" }; #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) 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"); #undef ARRAY_SIZE 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); } } 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 && wlr_surface_is_xdg_surface(surface)) { struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_from_wlr_surface(surface); if (!xdg_surface) { return NULL; } 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_surface_is_layer_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 is * 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_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) { 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) { 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) { /* 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) { /* anchor right edge */ 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_xcursor_manager_set_cursor_image( seat->xcursor_manager, cursor_names[cursor], seat->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; } wlr_xcursor_manager_set_cursor_image( seat->xcursor_manager, cursor_names[cursor], seat->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 (seat->seat->pointer_state.grab == seat->seat->pointer_state.default_grab) { 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_surface_is_layer_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) { cursor_set(seat, cursor_get_from_ssd(ctx->type)); } } } 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 && rc.focus_follow_mouse) { desktop_focus_and_activate_view(seat, ctx.view); if (rc.raise_on_focus) { view_move_to_front(ctx.view); } } 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 && rc.focus_follow_mouse && !rc.focus_follow_mouse_requires_movement && !server->osd_state.cycle_view) { /* Prevents changing keyboard focus during A-Tab */ desktop_focus_and_activate_view(&server->seat, ctx.view); if (rc.raise_on_focus) { view_move_to_front(ctx.view); } } 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->focused_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->focused_view->current.x + sx, seat->server->focused_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; 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 = desktop_focused_view(server); 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->focused_view->current.x; sy -= seat->server->focused_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; wlr_idle_notify_activity(seat->wlr_idle, 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; wlr_idle_notify_activity(seat->wlr_idle, 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, struct wlr_pointer_button_event *event) { 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 are closing the menu on RELEASE to not leak a stray release */ if (ctx.type != LAB_SSD_MENU) { close_menu = true; } else if (menu_call_actions(ctx.node)) { /* Action was successfull, may fail if item just opens a submenu */ close_menu = true; } return; } /* Handle _press_ on a layer surface */ if (ctx.type == LAB_SSD_LAYER_SURFACE) { struct wlr_layer_surface_v1 *layer = wlr_layer_surface_v1_from_wlr_surface(ctx.surface); if (layer->current.keyboard_interactive) { seat_set_focus_layer(seat, layer); } } /* Bindings to the Frame context swallow mouse events if activated */ bool consumed_by_frame_context = handle_press_mousebinding(server, &ctx, event->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, event->time_msec, event->button, event->state); } } static void cursor_button_release(struct seat *seat, struct wlr_pointer_button_event *event) { 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 (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, event->time_msec, event->button, event->state); return; } if (server->input_mode == LAB_INPUT_STATE_MENU) { if (close_menu) { menu_close_root(server); cursor_update_common(server, &ctx, event->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); return; } /* Bindings to the Frame context swallow mouse events if activated */ bool consumed_by_frame_context = handle_release_mousebinding(server, &ctx, event->button); if (ctx.surface && !consumed_by_frame_context) { /* Notify client with pointer focus of button release */ wlr_seat_pointer_notify_button(seat->seat, event->time_msec, event->button, event->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; wlr_idle_notify_activity(seat->wlr_idle, seat->seat); switch (event->state) { case WLR_BUTTON_PRESSED: cursor_button_press(seat, event); break; case WLR_BUTTON_RELEASED: cursor_button_release(seat, event); 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); wlr_idle_notify_activity(seat->wlr_idle, 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); } 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 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); 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); seat->request_cursor.notify = request_cursor_notify; wl_signal_add(&seat->seat->events.request_set_cursor, &seat->request_cursor); 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); 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); wl_list_remove(&seat->request_cursor.link); wl_list_remove(&seat->request_set_selection.link); wlr_xcursor_manager_destroy(seat->xcursor_manager); wlr_cursor_destroy(seat->cursor); dnd_finish(seat); } 07070100000083000081A4000000000000000000000001645896B80000121B000000000000000000000000000000000000002500000000labwc-0.6.3+git0.63db731/src/debug.c// SPDX-License-Identifier: GPL-2.0-only #include <wlr/types/wlr_layer_shell_v1.h> #include <wlr/types/wlr_scene.h> #include "common/scene-helpers.h" #include "debug.h" #include "labwc.h" #include "node.h" #include "ssd.h" #include "view.h" #define HEADER_CHARS "------------------------------" #define INDENT_SIZE 3 #define IGNORE_SSD true #define IGNORE_MENU true #define LEFT_COL_SPACE 35 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 "layer-background"; case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: return "layer-bottom"; case ZWLR_LAYER_SHELL_V1_LAYER_TOP: return "layer-top"; case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: return "layer-overlay"; default: abort(); } } static const char * get_view_part(struct view *view, struct wlr_scene_node *node) { if (view && node == &view->scene_tree->node) { return "view"; } if (view && node == view->scene_node) { return "view->scene_node"; } if (view) { return ssd_debug_get_node_name(view->ssd, node); } return NULL; } 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_top->node) { return "server->view_tree_always_on_top"; } if (node->parent == server->view_tree) { /* Add node_descriptor just to get the name here? */ return "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->popup_tree"; } for (int i = 0; i < 4; i++) { if (node == &output->layer_tree[i]->node) { return get_layer_name(i); } } } } #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 padding = LEFT_COL_SPACE - pos - strlen(type); if (!pos) { padding += 3; } printf("%s %*c %4d %4d [%p]\n", 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))) { 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 = lab_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; } 07070100000084000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002900000000labwc-0.6.3+git0.63db731/src/decorations07070100000085000081A4000000000000000000000001645896B800000EA7000000000000000000000000000000000000003400000000labwc-0.6.3+git0.63db731/src/decorations/kde-deco.c// 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_surface_is_xdg_surface(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_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; } 07070100000086000081A4000000000000000000000001645896B80000003A000000000000000000000000000000000000003500000000labwc-0.6.3+git0.63db731/src/decorations/meson.buildlabwc_sources += files( 'kde-deco.c', 'xdg-deco.c', ) 07070100000087000081A4000000000000000000000001645896B800000BAE000000000000000000000000000000000000003400000000labwc-0.6.3+git0.63db731/src/decorations/xdg-deco.c// 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->surface; 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; } 07070100000088000081A4000000000000000000000001645896B800002BB6000000000000000000000000000000000000002700000000labwc-0.6.3+git0.63db731/src/desktop.c// SPDX-License-Identifier: GPL-2.0-only #include "config.h" #include <assert.h> #include "common/list.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 "workspaces.h" #include "xwayland.h" 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_and_activate_view(struct seat *seat, struct view *view) { if (!view) { seat_focus_surface(seat, NULL); return; } /* * Guard against views with no mapped surfaces when handling * 'request_activate' and 'request_minimize'. * See notes by isfocusable() */ if (!view->surface) { return; } if (input_inhibit_blocks_surface(seat, view->surface->resource) || seat->server->session_lock) { return; } if (view->minimized) { /* * Unminimizing will map the view which triggers a call to this * function again. */ view_minimize(view, false); return; } if (!view->mapped) { return; } struct wlr_surface *prev_surface; prev_surface = seat->seat->keyboard_state.focused_surface; /* Do not re-focus an already focused surface. */ if (prev_surface == view->surface) { return; } view_set_activated(view); seat_focus_surface(seat, view->surface); } /* * Some xwayland apps produce unmapped surfaces on startup and also leave * some unmapped surfaces kicking around on 'close' (for example leafpad's * "about" dialogue). Whilst this is not normally a problem, we have to be * careful when cycling between views. The only views we should focus are * those that are already mapped and those that have been minimized. */ bool isfocusable(struct view *view) { /* filter out those xwayland surfaces that have never been mapped */ if (!view->surface) { return false; } return (view->mapped || view->minimized); } 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 (isfocusable(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 != desktop_focused_view(server)) { 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); if (isfocusable(view)) { return view; } } while (view != start_view); /* No focusable views found, including the one we started with */ return NULL; } struct view * desktop_topmost_mapped_view(struct server *server) { 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) { return view; } } return NULL; } struct view * desktop_focused_view(struct server *server) { struct seat *seat = &server->seat; struct wlr_surface *focused_surface; focused_surface = seat->seat->keyboard_state.focused_surface; if (!focused_surface) { return NULL; } struct view *view; wl_list_for_each(view, &server->views, link) { if (view->surface == focused_surface) { return view; } } return NULL; } void desktop_focus_topmost_mapped_view(struct server *server) { struct view *view = desktop_topmost_mapped_view(server); desktop_focus_and_activate_view(&server->seat, view); if (view) { view_move_to_front(view); } } 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 (!isfocusable(view)) { continue; } if (wlr_output_layout_intersects(layout, output->wlr_output, &view->current)) { desktop_focus_and_activate_view(&output->server->seat, view); 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); } 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_surface_is_layer_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; return ret; } } } /* node->parent is always a *wlr_scene_tree */ node = node->parent ? &node->parent->node : NULL; } wlr_log(WLR_ERROR, "Unknown node detected"); return ret; } 07070100000089000081A4000000000000000000000001645896B800001492000000000000000000000000000000000000002300000000labwc-0.6.3+git0.63db731/src/dnd.c// 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 "common/mem.h" #include "dnd.h" #include "labwc.h" /* for struct seat */ /* Internal DnD icon handlers */ static void handle_icon_map(struct wl_listener *listener, void *data) { struct drag_icon *self = wl_container_of(listener, self, events.map); struct wlr_drag_icon *icon = data; if (icon->data) { struct wlr_scene_tree *surface_tree = icon->data; wlr_scene_node_set_enabled(&surface_tree->node, true); } else { icon->data = wlr_scene_subsurface_tree_create( self->icon_tree, icon->surface); } } static void handle_surface_commit(struct wl_listener *listener, void *data) { struct drag_icon *self = wl_container_of(listener, self, events.commit); struct wlr_surface *surface = data; struct wlr_scene_tree *surface_tree = self->icon->data; if (surface_tree) { wlr_scene_node_set_position(&surface_tree->node, surface->sx, surface->sy); } } static void handle_icon_unmap(struct wl_listener *listener, void *data) { struct drag_icon *self = wl_container_of(listener, self, events.unmap); struct wlr_drag_icon *icon = data; struct wlr_scene_tree *surface_tree = icon->data; if (surface_tree) { wlr_scene_node_set_enabled(&surface_tree->node, false); } } static void handle_icon_destroy(struct wl_listener *listener, void *data) { struct drag_icon *self = wl_container_of(listener, self, events.destroy); wl_list_remove(&self->events.map.link); wl_list_remove(&self->events.commit.link); wl_list_remove(&self->events.unmap.link); wl_list_remove(&self->events.destroy.link); if (self->icon->data) { struct wlr_scene_tree *tree = self->icon->data; wlr_scene_node_destroy(&tree->node); } self->icon = NULL; self->icon_tree = NULL; free(self); } static void drag_icon_create(struct seat *seat, struct wlr_drag_icon *wlr_icon) { assert(seat); assert(wlr_icon); struct drag_icon *self = znew(*self); self->icon = wlr_icon; self->icon_tree = seat->drag.icons; /* Position will be updated by cursor movement */ wlr_scene_node_set_position(&self->icon_tree->node, seat->cursor->x, seat->cursor->y); wlr_scene_node_raise_to_top(&self->icon_tree->node); /* Set up events */ self->events.map.notify = handle_icon_map; self->events.commit.notify = handle_surface_commit; self->events.unmap.notify = handle_icon_unmap; self->events.destroy.notify = handle_icon_destroy; wl_signal_add(&wlr_icon->events.map, &self->events.map); wl_signal_add(&wlr_icon->surface->events.commit, &self->events.commit); wl_signal_add(&wlr_icon->events.unmap, &self->events.unmap); wl_signal_add(&wlr_icon->events.destroy, &self->events.destroy); } /* 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.detroy */ drag_icon_create(seat, 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); /* TODO: Not sure we actually need the following */ desktop_focus_topmost_mapped_view(seat->server); } /* 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); } 0707010000008A000081A4000000000000000000000001645896B80000105A000000000000000000000000000000000000002700000000labwc-0.6.3+git0.63db731/src/foreign.c// 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, /*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. */ if (view->workspace != view->server->workspace_current) { workspaces_switch_to(view->workspace); } desktop_focus_and_activate_view(&view->server->seat, view); view_move_to_front(view); } 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 wlr_output_layout *layout = view->server->output_layout; struct output *output; wl_list_for_each(output, &view->server->outputs, link) { if (output_is_usable(output) && wlr_output_layout_intersects( layout, output->wlr_output, &view->current)) { 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); } } } 0707010000008B000081A4000000000000000000000001645896B8000014B4000000000000000000000000000000000000002B00000000labwc-0.6.3+git0.63db731/src/interactive.c// SPDX-License-Identifier: GPL-2.0-only #include "labwc.h" #include "regions.h" #include "view.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; } 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->maximized || view_is_tiled(view)) { /* * Un-maximize 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_restore_to(view, geometry); } else { /* Store natural geometry at start of move */ view_store_natural_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->maximized || view->fullscreen) { /* * We don't allow resizing while maximized or * fullscreen. */ return; } /* * Reset tiled state but keep the same geometry as the * starting point for the resize. */ view_set_untiled(view); 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; } /* 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; } /* 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, view->output->wlr_output, &cursor_x, &cursor_y); /* * Don't store natural geometry here (it was * stored already in interactive_begin()) */ struct wlr_box *area = &view->output->usable_area; if (cursor_x <= area->x + snap_range) { view_snap_to_edge(view, "left", /*store_natural_geometry*/ false); } else if (cursor_x >= area->x + area->width - snap_range) { view_snap_to_edge(view, "right", /*store_natural_geometry*/ false); } else if (cursor_y <= area->y + snap_range) { if (rc.snap_top_maximize) { view_maximize(view, true, /*store_natural_geometry*/ false); } else { view_snap_to_edge(view, "up", /*store_natural_geometry*/ false); } } else if (cursor_y >= area->y + area->height - snap_range) { view_snap_to_edge(view, "down", /*store_natural_geometry*/ false); } else { /* Not close to any edge */ return 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) { regions_hide_overlay(&view->server->seat); if (view->server->input_mode == LAB_INPUT_STATE_MOVE) { if (!snap_to_region(view)) { if (!snap_to_edge(view)) { /* Reset tiled state if not snapped */ view_set_untiled(view); } } } view->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; view->server->grabbed_view = NULL; /* Update focus/cursor image */ cursor_update_focus(view->server); } } /* * 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) { view->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; view->server->grabbed_view = NULL; /* Update focus/cursor image */ cursor_update_focus(view->server); } } 0707010000008C000081A4000000000000000000000001645896B8000007AD000000000000000000000000000000000000002900000000labwc-0.6.3+git0.63db731/src/key-state.c// SPDX-License-Identifier: GPL-2.0-only #include <stdbool.h> #include <stdint.h> #include <string.h> #include "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, bound, pressed_sent; 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) { /* 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]); } 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 ispressed) { if (ispressed) { add_key(&pressed, keycode); } else { remove_key(&pressed, keycode); } } void key_state_store_pressed_keys_as_bound(void) { memcpy(bound.keys, pressed.keys, MAX_PRESSED_KEYS * sizeof(uint32_t)); bound.nr_keys = pressed.nr_keys; } 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_keys(void) { return bound.nr_keys; } 0707010000008D000081A4000000000000000000000001645896B80000265C000000000000000000000000000000000000002800000000labwc-0.6.3+git0.63db731/src/keyboard.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <wlr/backend/multi.h> #include <wlr/backend/session.h> #include "action.h" #include "key-state.h" #include "labwc.h" #include "regions.h" #include "view.h" #include "workspaces.h" static bool should_cancel_cycling_on_next_key_release; static void change_vt(struct server *server, unsigned int vt) { if (!wlr_backend_is_multi(server->backend)) { return; } struct wlr_session *session = wlr_backend_get_session(server->backend); if (session) { wlr_session_change_vt(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) { desktop_focus_and_activate_view(&server->seat, server->osd_state.cycle_view); if (server->osd_state.cycle_view) { view_move_to_front(server->osd_state.cycle_view); } /* osd_finish() additionally resets cycle_view to NULL */ osd_finish(server); should_cancel_cycling_on_next_key_release = false; } 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_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 bool handle_keybinding(struct server *server, uint32_t modifiers, xkb_keysym_t sym) { struct keybind *keybind; wl_list_for_each(keybind, &rc.keybinds, link) { if (modifiers ^ keybind->modifiers) { continue; } if (server->seat.inhibit_keybinds && !actions_contain_toggle_keybinds(&keybind->actions)) { continue; } for (size_t i = 0; i < keybind->keysyms_len; i++) { if (xkb_keysym_to_lower(sym) == keybind->keysyms[i]) { key_state_store_pressed_keys_as_bound(); actions_run(NULL, server, &keybind->actions, 0); return true; } } } return false; } static bool is_modifier_key(xkb_keysym_t sym) { return sym == XKB_KEY_Shift_L || sym == XKB_KEY_Shift_R || sym == XKB_KEY_Alt_L || sym == XKB_KEY_Alt_R || sym == XKB_KEY_Control_L || sym == XKB_KEY_Control_R || sym == XKB_KEY_Super_L || sym == XKB_KEY_Super_R; } static bool 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; /* Translate libinput keycode -> xkbcommon */ uint32_t keycode = event->keycode + 8; /* Get a list of keysyms based on the keymap for this keyboard */ const xkb_keysym_t *syms; int nsyms = xkb_state_key_get_syms(wlr_keyboard->xkb_state, keycode, &syms); bool handled = false; key_state_set_pressed(event->keycode, event->state == WL_KEYBOARD_KEY_STATE_PRESSED); /* * 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 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 && event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { end_cycling(server); handled = true; goto out; } /* * If a press event was handled by a compositor binding, then do not * forward the corresponding release event to clients */ if (key_state_corresponding_press_event_was_bound(event->keycode) && event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { key_state_bound_key_remove(event->keycode); return true; } uint32_t modifiers = wlr_keyboard_get_modifiers(wlr_keyboard); /* Catch C-A-F1 to C-A-F12 to change tty */ if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { for (int i = 0; i < nsyms; i++) { unsigned int vt = syms[i] - XKB_KEY_XF86Switch_VT_1 + 1; if (vt >= 1 && vt <= 12) { change_vt(server, vt); /* * Don't send any key events to clients when * changing tty */ handled = true; goto out; } } } if (server->osd_state.cycle_view) { if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { for (int i = 0; i < nsyms; i++) { if (syms[i] == XKB_KEY_Escape) { /* * Cancel view-cycle * * osd_finish() additionally resets * cycle_view to NULL */ osd_preview_restore(server); osd_finish(server); handled = true; goto out; } } /* cycle to next */ bool backwards = modifiers & WLR_MODIFIER_SHIFT; /* ignore if this is a modifier key being pressed */ bool ignore = false; for (int i = 0; i < nsyms; i++) { ignore |= is_modifier_key(syms[i]); } if (!ignore) { 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); } } /* don't send any key events to clients when osd onscreen */ handled = true; goto out; } /* Handle compositor key bindings */ if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { for (int i = 0; i < nsyms; i++) { handled |= handle_keybinding(server, modifiers, syms[i]); } } out: if (handled) { key_state_store_pressed_keys_as_bound(); } return handled; } 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; } } 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; struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; wlr_idle_notify_activity(seat->wlr_idle, seat->seat); /* any new press/release cancels current keybind repeat */ keyboard_cancel_keybind_repeat(keyboard); bool handled = handle_compositor_keybindings(keyboard, event); if (handled) { if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { start_keybind_repeat(seat->server, keyboard, event); } } else { wlr_seat_set_keyboard(wlr_seat, wlr_keyboard); wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, event->keycode, event->state); } } void keyboard_init(struct seat *seat) { seat->keyboard_group = wlr_keyboard_group_create(); struct wlr_keyboard *kb = &seat->keyboard_group->keyboard; 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) { wlr_keyboard_set_keymap(kb, keymap); xkb_keymap_unref(keymap); } else { wlr_log(WLR_ERROR, "Failed to create xkb keymap"); } xkb_context_unref(context); wlr_keyboard_set_repeat_info(kb, rc.repeat_rate, rc.repeat_delay); } void keyboard_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; } } 0707010000008E000081A4000000000000000000000001645896B80000349D000000000000000000000000000000000000002600000000labwc-0.6.3+git0.63db731/src/layers.c// 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 <wayland-server.h> #include <wlr/types/wlr_layer_shell_v1.h> #include <wlr/util/log.h> #include "common/list.h" #include "common/mem.h" #include "layers.h" #include "labwc.h" #include "node.h" static void arrange_one_layer(struct output *output, 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; 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; } int nr_layers = sizeof(output->layer_tree) / sizeof(output->layer_tree[0]); for (int i = 0; i < nr_layers; 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. */ arrange_one_layer(output, &full_area, &usable_area, layer, true); arrange_one_layer(output, &full_area, &usable_area, layer, 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->mapped) { layer->mapped = layer_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); free(popup); } 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 wlr_box *output_toplevel_sx_box) { 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); wlr_xdg_popup_unconstrain_from_box(wlr_popup, output_toplevel_sx_box); 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, &lab_layer_popup->output_toplevel_sx_box); 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, &output_toplevel_sx_box); 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->events.map, &surface->map); surface->unmap.notify = handle_unmap; wl_signal_add(&layer_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); server->new_layer_surface.notify = handle_new_layer_surface; wl_signal_add(&server->layer_shell->events.new_surface, &server->new_layer_surface); } 0707010000008F000081A4000000000000000000000001645896B800001092000000000000000000000000000000000000002400000000labwc-0.6.3+git0.63db731/src/main.c// 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'}, {"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" " -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; char *config_file = NULL; enum wlr_log_importance verbosity = WLR_ERROR; int c; while (1) { int index = 0; c = getopt_long(argc, argv, "c:C:dehrs:vV", long_options, &index); if (c == -1) { break; } switch (c) { case 'c': config_file = optarg; break; case 'C': rc.config_dir = xstrdup(optarg); break; case 'd': verbosity = WLR_DEBUG; break; case 'e': send_signal_to_labwc_pid(SIGTERM); exit(0); 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(); if (!rc.config_dir) { rc.config_dir = config_dir(); } wlr_log(WLR_INFO, "using config dir (%s)\n", rc.config_dir); session_environment_init(rc.config_dir); rcxml_read(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(rc.config_dir); if (startup_cmd) { spawn_async_no_shell(startup_cmd); } wl_display_run(server.wl_display); server_finish(&server); menu_finish(); theme_finish(&theme); rcxml_finish(); font_finish(); return 0; } 07070100000090000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002200000000labwc-0.6.3+git0.63db731/src/menu07070100000091000081A4000000000000000000000001645896B8000059EC000000000000000000000000000000000000002900000000labwc-0.6.3+git0.63db731/src/menu/menu.c// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <ctype.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/font.h" #include "common/list.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; /* vector for <menu id="" label=""> elements */ static struct menu *menus; static int nr_menus, alloc_menus; static struct menu * menu_create(struct server *server, const char *id, const char *label) { if (nr_menus == alloc_menus) { alloc_menus = (alloc_menus + 16) * 2; menus = xrealloc(menus, alloc_menus * sizeof(struct menu)); } struct menu *menu = menus + nr_menus; memset(menu, 0, sizeof(*menu)); nr_menus++; 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(const char *id) { if (!id) { return NULL; } struct menu *menu; for (int i = 0; i < nr_menus; ++i) { menu = menus + i; 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( lab_wlr_scene_get_rect(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( lab_wlr_scene_get_rect(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( lab_wlr_scene_get_rect(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; for (int i = 0; i < nr_menus; ++i) { menu = menus + i; menu_update_width(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_insert(&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_insert(&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); } static void entry(xmlNode *node, char *nodename, char *content) { if (!nodename || !content) { return; } string_truncate_at_pattern(nodename, ".openbox_menu"); if (in_item) { fill_item(nodename, content); } } 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(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); } } zfree(label); zfree(execute); zfree(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); if (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); } } static void parse_xml(const char *filename, struct server *server) { FILE *stream; char *line = NULL; size_t len = 0; struct buf b; static char menuxml[4096] = { 0 }; if (!rc.config_dir) { return; } snprintf(menuxml, sizeof(menuxml), "%s/%s", rc.config_dir, filename); stream = fopen(menuxml, "r"); if (!stream) { wlr_log(WLR_ERROR, "cannot read %s", menuxml); return; } wlr_log(WLR_INFO, "read menu file %s", menuxml); buf_init(&b); while (getline(&line, &len, stream) != -1) { char *p = strrchr(line, '\n'); if (p) { *p = '\0'; } buf_add(&b, line); } free(line); fclose(stream); 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 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_reverse(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_reverse(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(const char *id) { struct menu *menu, *hide_menu; hide_menu = menu_get_by_id(id); if (!hide_menu) { return; } for (int i = 0; i < nr_menus; ++i) { menu = menus + i; bool should_reposition = false; struct menuitem *item, *next; wl_list_for_each_reverse_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_reverse(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("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("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, _("Decorations"), false); fill_item("name.action", "ToggleDecorations"); current_item = item_create(menu, _("AlwaysOnTop"), 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 = 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("workspaces"); } } void menu_init(struct server *server) { parse_xml("menu.xml", server); init_rootmenu(server); init_windowmenu(server); post_processing(server); } void menu_finish(void) { struct menu *menu; for (int i = 0; i < nr_menus; ++i) { menu = menus + i; 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); } zfree(menus); alloc_menus = 0; nr_menus = 0; } /* 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; } void menu_process_cursor_motion(struct wlr_scene_node *node) { assert(node && node->data); struct menuitem *item = node_menuitem_from_node(node); /* This function shall only be called for menu nodes */ assert(item); if (!item->selectable) { return; } if (node == &item->selected.tree->node) { /* We are on an already selected item */ 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; /* And open the new submenu tree */ wlr_scene_node_set_enabled( &item->submenu->scene_tree->node, true); } item->parent->selection.menu = item->submenu; } bool menu_call_actions(struct wlr_scene_node *node) { assert(node && node->data); struct menuitem *item = node_menuitem_from_node(node); if (item->submenu) { /* We received a click on an item that just opens a submenu */ return false; } actions_run(item->parent->triggered_by_view, item->parent->server, &item->actions, 0); /** * 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; return true; } 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->menu_current = NULL; menu_init(server); } 07070100000092000081A4000000000000000000000001645896B800000026000000000000000000000000000000000000002E00000000labwc-0.6.3+git0.63db731/src/menu/meson.buildlabwc_sources += files( 'menu.c', ) 07070100000093000081A4000000000000000000000001645896B800000284000000000000000000000000000000000000002900000000labwc-0.6.3+git0.63db731/src/meson.buildlabwc_sources = files( 'action.c', 'buffer.c', 'cursor.c', 'debug.c', 'desktop.c', 'dnd.c', 'foreign.c', 'interactive.c', 'keyboard.c', 'key-state.c', 'layers.c', 'main.c', 'node.c', 'osd.c', 'output.c', 'regions.c', 'resistance.c', 'seat.c', 'server.c', 'session-lock.c', 'touch.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('common') subdir('config') subdir('decorations') subdir('xbm') subdir('menu') subdir('ssd') 07070100000094000081A4000000000000000000000001645896B8000009A4000000000000000000000000000000000000002400000000labwc-0.6.3+git0.63db731/src/node.c// 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; } 07070100000095000081A4000000000000000000000001645896B800003124000000000000000000000000000000000000002300000000labwc-0.6.3+git0.63db731/src/osd.c// 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 "buffer.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 "workspaces.h" #define OSD_ITEM_HEIGHT (20) #define OSD_ITEM_WIDTH (600) #define OSD_ITEM_PADDING (10) #define OSD_BORDER_WIDTH (6) /* is title different from app_id/class? */ static int is_title_different(struct view *view) { switch (view->type) { case LAB_XDG_SHELL_VIEW: return g_strcmp0(view_get_string_prop(view, "title"), view_get_string_prop(view, "app_id")); #if HAVE_XWAYLAND case LAB_XWAYLAND_VIEW: return g_strcmp0(view_get_string_prop(view, "title"), view_get_string_prop(view, "class")); #endif } return 1; } static const char * get_formatted_app_id(struct view *view) { char *s = (char *)view_get_string_prop(view, "app_id"); 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 int get_osd_height(struct wl_list *node_list) { int height = 0; struct view *view; struct wlr_scene_node *node; wl_list_for_each(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 (!isfocusable(view)) { continue; } height += OSD_ITEM_HEIGHT; } height += 2 * OSD_BORDER_WIDTH; return height; } 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(struct view *view) { if (is_title_different(view)) { return view_get_string_prop(view, "title"); } else { return ""; } } 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 view *cycle_view = server->osd_state.cycle_view; struct theme *theme = server->theme; struct wlr_scene_node *node; 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); draw_cairo_border(cairo, w, h, 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_width(layout, (OSD_ITEM_WIDTH - 2 * OSD_ITEM_PADDING) * 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); pango_cairo_update_layout(cairo, layout); int y = OSD_BORDER_WIDTH; /* Center text entries on the y axis */ int y_offset = (OSD_ITEM_HEIGHT - font_height(&rc.font_osd)) / 2; y += y_offset; /* Draw workspace indicator */ if (show_workspace) { /* Center workspace indicator on the x axis */ int x = font_width(&rc.font_osd, workspace_name); x = (OSD_ITEM_WIDTH - x) / 2; cairo_move_to(cairo, x, y); 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 += OSD_ITEM_HEIGHT; } pango_font_description_free(desc); struct buf buf; buf_init(&buf); /* Draw text for each node */ wl_list_for_each_reverse(node, node_list, link) { if (!node->data) { /* We found some non-view, most likely the region overlay */ continue; } struct view *view = node_view_from_node(node); if (!isfocusable(view)) { continue; } int x = OSD_BORDER_WIDTH + OSD_ITEM_PADDING; struct window_switcher_field *field; wl_list_for_each(field, &rc.window_switcher.fields, link) { buf.len = 0; cairo_move_to(cairo, x, y); switch (field->content) { case LAB_FIELD_TYPE: buf_add(&buf, get_type(view)); break; case LAB_FIELD_APP_ID: buf_add(&buf, get_app_id(view)); break; case LAB_FIELD_TITLE: buf_add(&buf, get_title(view)); break; default: break; } int field_width = field->width / 100.0 * OSD_ITEM_WIDTH; 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; } if (view == cycle_view) { /* Highlight current window */ cairo_rectangle(cairo, OSD_BORDER_WIDTH, y - y_offset, OSD_ITEM_WIDTH, OSD_ITEM_HEIGHT); cairo_stroke(cairo); } y += OSD_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 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; float scale = output->wlr_output->scale; int w = OSD_ITEM_WIDTH + (2 * OSD_BORDER_WIDTH); int h = get_osd_height(node_list); if (show_workspace) { /* workspace indicator */ h += OSD_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); /* Render OSD image */ cairo_t *cairo = output->osd_buffer->cairo; render_osd(server, cairo, w, h, node_list, show_workspace, workspace_name); 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) { /* 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 (isfocusable(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); } } 07070100000096000081A4000000000000000000000001645896B800004099000000000000000000000000000000000000002600000000labwc-0.6.3+git0.63db731/src/output.c// 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/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/mem.h" #include "labwc.h" #include "layers.h" #include "node.h" #include "regions.h" #include "view.h" static void output_frame_notify(struct wl_listener *listener, void *data) { struct output *output = wl_container_of(listener, output, frame); if (!output_is_usable(output)) { return; } 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); int nr_layers = sizeof(output->layer_tree) / sizeof(output->layer_tree[0]); for (int i = 0; i < nr_layers; 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); 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); } } free(output); } 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 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; /* * 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_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 commiting 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) { wlr_output_enable_adaptive_sync(wlr_output, true); if (!wlr_output_test(wlr_output)) { wlr_output_enable_adaptive_sync(wlr_output, false); wlr_log(WLR_DEBUG, "failed to enable adaptive sync for output %s", wlr_output->name); } else { wlr_log(WLR_INFO, "adaptive sync enabled for output %s", wlr_output->name); } } 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); wl_list_init(&output->regions); /* * Create layer-trees (background, bottom, top and overlay) and * a layer-popup-tree. */ int nr_layers = sizeof(output->layer_tree) / sizeof(output->layer_tree[0]); for (int i = 0; i < nr_layers; 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++; wlr_output_layout_add_auto(server->output_layout, wlr_output); output->scene_output = wlr_scene_get_scene_output(server->scene, wlr_output); assert(output->scene_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); } void output_init(struct server *server) { 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); } wlr_scene_attach_output_layout(server->scene, server->output_layout); /* 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); /* * "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 void output_config_apply(struct server *server, struct wlr_output_configuration_v1 *config) { 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 specifc 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); wlr_output_enable_adaptive_sync(o, head->state.adaptive_sync_enabled); } if (!wlr_output_commit(o)) { wlr_log(WLR_ERROR, "Output config commit failed"); continue; } /* Only do Layout specific actions if the commit went trough */ if (need_to_add) { wlr_output_layout_add_auto(server->output_layout, o); output->scene_output = wlr_scene_get_scene_output(server->scene, o); assert(output->scene_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_move(server->output_layout, o, head->state.x, head->state.y); } } if (need_to_remove) { regions_evacuate_output(output); wlr_output_layout_remove(server->output_layout, o); output->scene_output = NULL; } } server->pending_output_layout_change--; do_output_layout_change(server); } static bool verify_output_config_v1(const struct wlr_output_configuration_v1 *config) { /* TODO implement */ return true; } 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/layed 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); } 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); } 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); 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); 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) { 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; } void handle_output_power_manager_set_mode(struct wl_listener *listener, void *data) { 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); break; } } 07070100000097000081A4000000000000000000000001645896B800001913000000000000000000000000000000000000002700000000labwc-0.6.3+git0.63db731/src/regions.c// 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 "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; geo->x = usable.x + usable.width * perc->x / 100; geo->y = usable.y + usable.height * perc->y / 100; geo->width = usable.width * perc->width / 100; geo->height = usable.height * perc->height / 100; 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); } } 07070100000098000081A4000000000000000000000001645896B8000014A6000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/src/resistance.c// SPDX-License-Identifier: GPL-2.0-only #include "config/rcxml.h" #include "labwc.h" #include "resistance.h" #include "view.h" struct edges { int left; int top; int right; int bottom; }; static void is_within_resistance_range(struct edges view, struct edges target, struct edges other, struct edges *flags, int strength) { if (view.left >= other.left && target.left < other.left && target.left >= other.left - strength) { flags->left = 1; } else if (view.right <= other.right && target.right > other.right && target.right <= other.right + strength) { flags->right = 1; } if (view.top >= other.top && target.top < other.top && target.top >= other.top - strength) { flags->top = 1; } else if (view.bottom <= other.bottom && target.bottom > other.bottom && target.bottom <= other.bottom + strength) { flags->bottom = 1; } } void resistance_move_apply(struct view *view, double *x, double *y) { struct server *server = view->server; struct wlr_box mgeom, intersection; struct wlr_box vgeom = view->current; struct wlr_box tgeom = {.x = *x, .y = *y, .width = vgeom.width, .height = vgeom.height}; struct output *output; struct border border = ssd_get_margin(view->ssd); struct edges view_edges; /* The edges of the current view */ struct edges target_edges; /* The desired edges */ struct edges other_edges; /* The edges of the monitor/other view */ struct edges flags = { 0 }; view_edges.left = vgeom.x - border.left + 1; view_edges.top = vgeom.y - border.top + 1; view_edges.right = vgeom.x + vgeom.width + border.right; view_edges.bottom = vgeom.y + vgeom.height + border.bottom; target_edges.left = *x - border.left; target_edges.top = *y - border.top; target_edges.right = *x + vgeom.width + border.right; target_edges.bottom = *y + vgeom.height + border.bottom; if (!rc.screen_edge_strength) { return; } wl_list_for_each(output, &server->outputs, link) { if (!output_is_usable(output)) { continue; } mgeom = output_usable_area_in_layout_coords(output); if (!wlr_box_intersection(&intersection, &vgeom, &mgeom) && !wlr_box_intersection(&intersection, &tgeom, &mgeom)) { continue; } other_edges.left = mgeom.x; other_edges.top = mgeom.y; other_edges.right = mgeom.x + mgeom.width; other_edges.bottom = mgeom.y + mgeom.height; is_within_resistance_range(view_edges, target_edges, other_edges, &flags, rc.screen_edge_strength); if (flags.left == 1) { *x = other_edges.left + border.left; } else if (flags.right == 1) { *x = other_edges.right - vgeom.width - border.right; } if (flags.top == 1) { *y = other_edges.top + border.top; } else if (flags.bottom == 1) { *y = other_edges.bottom - vgeom.height - border.bottom; } /* reset the flags */ flags.left = 0; flags.top = 0; flags.right = 0; flags.bottom = 0; } } void resistance_resize_apply(struct view *view, struct wlr_box *new_view_geo) { struct server *server = view->server; struct output *output; struct wlr_box mgeom, intersection; struct wlr_box vgeom = view->current; struct wlr_box tgeom = *new_view_geo; struct border border = ssd_get_margin(view->ssd); struct edges view_edges; /* The edges of the current view */ struct edges target_edges; /* The desired edges */ struct edges other_edges; /* The edges of the monitor/other view */ struct edges flags = { 0 }; view_edges.left = vgeom.x - border.left; view_edges.top = vgeom.y - border.top; view_edges.right = vgeom.x + vgeom.width + border.right; view_edges.bottom = vgeom.y + vgeom.height + border.bottom; target_edges.left = new_view_geo->x - border.left; target_edges.top = new_view_geo->y - border.top; target_edges.right = new_view_geo->x + new_view_geo->width + border.right; target_edges.bottom = new_view_geo->y + new_view_geo->height + border.bottom; if (!rc.screen_edge_strength) { return; } wl_list_for_each(output, &server->outputs, link) { if (!output_is_usable(output)) { continue; } mgeom = output_usable_area_in_layout_coords(output); if (!wlr_box_intersection(&intersection, &vgeom, &mgeom) && !wlr_box_intersection(&intersection, &tgeom, &mgeom)) { continue; } other_edges.left = mgeom.x; other_edges.top = mgeom.y; other_edges.right = mgeom.x + mgeom.width; other_edges.bottom = mgeom.y + mgeom.height; is_within_resistance_range(view_edges, target_edges, other_edges, &flags, rc.screen_edge_strength); if (server->resize_edges & WLR_EDGE_LEFT) { if (flags.left == 1) { new_view_geo->x = other_edges.left + border.left; new_view_geo->width = vgeom.width; } } else if (server->resize_edges & WLR_EDGE_RIGHT) { if (flags.right == 1) { new_view_geo->width = other_edges.right - view_edges.left - border.right - border.left; } } if (server->resize_edges & WLR_EDGE_TOP) { if (flags.top == 1) { new_view_geo->y = other_edges.top + border.top; new_view_geo->height = vgeom.height; } } else if (server->resize_edges & WLR_EDGE_BOTTOM) { if (flags.bottom == 1) { new_view_geo->height = other_edges.bottom - view_edges.top - border.bottom - border.top; } } /* reset the flags */ flags.left = 0; flags.top = 0; flags.right = 0; flags.bottom = 0; } } 07070100000099000081A4000000000000000000000001645896B800003C92000000000000000000000000000000000000002400000000labwc-0.6.3+git0.63db731/src/seat.c// 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 "key-state.h" #include "labwc.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 bool is_touch_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 true; default: break; } return false; } static void configure_libinput(struct wlr_input_device *wlr_input_device) { 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; } enum device_type current_type; current_type = is_touch_device(wlr_input_device) ? TOUCH_DEVICE : NON_TOUCH_DEVICE; struct libinput_category *device_category, *dc = NULL; wl_list_for_each(device_category, &rc.libinput_categories, link) { if (device_category->name) { if (!strcasecmp(wlr_input_device->name, device_category->name)) { dc = device_category; break; } } else if (device_category->type == current_type) { dc = device_category; } else if (device_category->type == DEFAULT_DEVICE && !dc) { /* Match default category as last-resort */ dc = device_category; } } /* * 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_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); } } 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 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) { struct wlr_output *output = NULL; struct wlr_pointer *pointer = wlr_pointer_from_input_device(dev); wlr_log(WLR_INFO, "map pointer to output %s\n", pointer->output_name); if (pointer->output_name) { output = output_by_name(seat->server, pointer->output_name); } wlr_cursor_map_input_to_output(seat->cursor, dev, output); wlr_cursor_map_input_to_region(seat->cursor, dev, NULL); } 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); if (!virtual) { wlr_keyboard_group_add_keyboard(seat->keyboard_group, kb); } keyboard->key.notify = keyboard_key_notify; wl_signal_add(&kb->events.key, &keyboard->key); keyboard->modifier.notify = keyboard_modifiers_notify; wl_signal_add(&kb->events.modifiers, &keyboard->modifier); wlr_seat_set_keyboard(seat->seat, kb); return (struct input *)keyboard; } 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 */ if (dev->type == WLR_INPUT_DEVICE_TOUCH) { struct wlr_output *output = NULL; struct wlr_touch *touch = wlr_touch_from_input_device(dev); wlr_log(WLR_INFO, "map touch to output %s\n", touch->output_name); if (touch->output_name) { output = output_by_name(seat->server, touch->output_name); } wlr_cursor_map_input_to_output(seat->cursor, dev, output); wlr_cursor_map_input_to_region(seat->cursor, dev, NULL); } 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; default: wlr_log(WLR_INFO, "unsupported input device"); return; } seat_add_device(seat, input); } static void destroy_idle_inhibitor(struct wl_listener *listener, void *data) { struct idle_inhibitor *idle_inhibitor = wl_container_of(listener, idle_inhibitor, destroy); struct seat *seat = idle_inhibitor->seat; wl_list_remove(&idle_inhibitor->destroy.link); wlr_idle_set_enabled(seat->wlr_idle, seat->seat, wl_list_length( &seat->wlr_idle_inhibit_manager->inhibitors) <= 1); free(idle_inhibitor); } static void new_idle_inhibitor(struct wl_listener *listener, void *data) { struct wlr_idle_inhibitor_v1 *wlr_inhibitor = data; struct seat *seat = wl_container_of(listener, seat, idle_inhibitor_create); struct idle_inhibitor *inhibitor = znew(*inhibitor); inhibitor->seat = seat; inhibitor->wlr_inhibitor = wlr_inhibitor; inhibitor->destroy.notify = destroy_idle_inhibitor; wl_signal_add(&wlr_inhibitor->events.destroy, &inhibitor->destroy); wlr_idle_set_enabled(seat->wlr_idle, seat->seat, 0); } 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); } 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->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->wlr_idle = wlr_idle_create(server->wl_display); seat->wlr_idle_inhibit_manager = wlr_idle_inhibit_v1_create( server->wl_display); wl_signal_add(&seat->wlr_idle_inhibit_manager->events.new_inhibitor, &seat->idle_inhibitor_create); seat->idle_inhibitor_create.notify = new_idle_inhibitor; 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); keyboard_init(seat); cursor_init(seat); touch_init(seat); } void seat_finish(struct server *server) { struct seat *seat = &server->seat; wl_list_remove(&seat->new_input.link); struct input *input, *next; wl_list_for_each_safe(input, next, &seat->inputs, link) { input_device_destroy(&input->destroy, NULL); } keyboard_finish(seat); /* * Caution - touch_finish() unregisters event listeners from * seat->cursor and must come before cursor_finish(), otherwise * a use-after-free occurs. */ touch_finish(seat); cursor_finish(seat); } static void configure_keyboard(struct wlr_input_device *device) { assert(device->type == WLR_INPUT_DEVICE_KEYBOARD); struct wlr_keyboard *kb = wlr_keyboard_from_input_device(device); wlr_keyboard_set_repeat_info(kb, rc.repeat_rate, rc.repeat_delay); } 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(input->wlr_input_device); break; case WLR_INPUT_DEVICE_POINTER: configure_libinput(input->wlr_input_device); break; default: break; } } } static void seat_focus(struct seat *seat, struct wlr_surface *surface) { if (!surface) { wlr_seat_keyboard_notify_clear_focus(seat->seat); return; } struct wlr_keyboard *kb = &seat->keyboard_group->keyboard; /* * 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(); wlr_seat_keyboard_notify_enter(seat->seat, surface, pressed_sent_keycodes, nr_pressed_sent_keycodes, &kb->modifiers); struct server *server = seat->server; 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 exlusive keyboard-interactivity. */ if (seat->focused_layer && seat->focused_layer->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { return; } seat_focus(seat, surface); } void seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer) { if (!layer) { seat->focused_layer = NULL; desktop_focus_topmost_mapped_view(seat->server); return; } seat_focus(seat, layer->surface); 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 wlr_surface *surface = data; struct seat *seat = wl_container_of(listener, seat, pressed_surface_destroy); assert(surface == 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; } 0707010000009A000081A4000000000000000000000001645896B80000399E000000000000000000000000000000000000002600000000labwc-0.6.3+git0.63db731/src/server.c// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include "config.h" #include <signal.h> #include <sys/wait.h> #include <wlr/types/wlr_data_control_v1.h> #include <wlr/types/wlr_export_dmabuf_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 "labwc.h" #include "layers.h" #include "menu/menu.h" #include "regions.h" #include "theme.h" #include "view.h" #include "workspaces.h" #include "xwayland.h" #define LAB_XDG_SHELL_VERSION (2) 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(NULL); 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); kde_server_decoration_update_default(); } static int handle_sighup(int signal, void *data) { session_environment_init(rc.config_dir); 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); if (!server->backend) { wlr_log(WLR_ERROR, "unable to create backend"); fprintf(stderr, helpful_seat_error_message); exit(EXIT_FAILURE); } /* * 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); } server->view_tree = wlr_scene_tree_create(&server->scene->tree); server->view_tree_always_on_top = 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, 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); /* Init xdg-shell */ 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); kde_server_decoration_init(server); xdg_server_decoration_init(server); 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); 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_gamma_control_manager_v1_create(server->wl_display); wlr_viewporter_create(server->wl_display); wlr_single_pixel_buffer_manager_v1_create(server->wl_display); 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); 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); } 0707010000009B000081A4000000000000000000000001645896B8000023A8000000000000000000000000000000000000002C00000000labwc-0.6.3+git0.63db731/src/session-lock.c// 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_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) { continue; } if (iter->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->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->committed & require_reconfigure) { lock_output_reconfigure(output); } } 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; } 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); 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_mapped_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); } 0707010000009C000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002100000000labwc-0.6.3+git0.63db731/src/ssd0707010000009D000081A4000000000000000000000001645896B80000006E000000000000000000000000000000000000002D00000000labwc-0.6.3+git0.63db731/src/ssd/meson.buildlabwc_sources += files( 'ssd.c', 'ssd_part.c', 'ssd_titlebar.c', 'ssd_border.c', 'ssd_extents.c', ) 0707010000009E000081A4000000000000000000000001645896B800001F77000000000000000000000000000000000000002700000000labwc-0.6.3+git0.63db731/src/ssd/ssd.c// 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. */ if (!view->ssd_enabled || view->fullscreen) { return (struct border){ 0 }; } struct theme *theme = view->server->theme; return (struct border){ .top = theme->title_height + theme->border_width, .bottom = theme->border_width, .left = theme->border_width, .right = theme->border_width, }; } struct wlr_box ssd_max_extents(struct view *view) { assert(view); struct border border = ssd_thickness(view); return (struct wlr_box){ .x = view->current.x - border.left, .y = view->current.y - border.top, .width = view->current.width + border.left + border.right, .height = view->current.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_extents_create(ssd); ssd_border_create(ssd); ssd_titlebar_create(ssd); ssd->margin = ssd_thickness(view); ssd_set_active(ssd, active); 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_geometry(struct ssd *ssd) { if (!ssd) { return; } struct wlr_box cached = ssd->state.geometry; struct wlr_box current = ssd->view->current; if (current.width == cached.width && current.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; } return; } ssd_extents_update(ssd); ssd_border_update(ssd); ssd_titlebar_update(ssd); ssd->state.geometry = current; } 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->node = 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); } 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; } 0707010000009F000081A4000000000000000000000001645896B800000BE4000000000000000000000000000000000000002E00000000labwc-0.6.3+git0.63db731/src/ssd/ssd_border.c// SPDX-License-Identifier: GPL-2.0-only #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) { struct view *view = ssd->view; struct theme *theme = view->server->theme; int width = view->current.width; int height = view->current.height; int full_width = width + 2 * theme->border_width; float *color; struct wlr_scene_tree *parent; struct ssd_sub_tree *subtree; FOR_EACH_STATE(ssd, subtree) { subtree->tree = wlr_scene_tree_create(ssd->tree); parent = subtree->tree; wlr_scene_node_set_position(&parent->node, -theme->border_width, 0); 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, -(theme->title_height + theme->border_width), color); } FOR_EACH_END } void ssd_border_update(struct ssd *ssd) { struct view *view = ssd->view; struct theme *theme = view->server->theme; int width = view->current.width; int height = view->current.height; 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 = lab_wlr_scene_get_rect(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: wlr_scene_rect_set_size(rect, width - 2 * SSD_BUTTON_WIDTH, theme->border_width); continue; default: continue; } } } FOR_EACH_END } void ssd_border_destroy(struct ssd *ssd) { if (!ssd->border.active.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 } #undef FOR_EACH_STATE 070701000000A0000081A4000000000000000000000001645896B8000018AF000000000000000000000000000000000000002F00000000labwc-0.6.3+git0.63db731/src/ssd/ssd_extents.c// SPDX-License-Identifier: GPL-2.0-only #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->maximized || view->fullscreen) { 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), -(theme->title_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->maximized || view->fullscreen) { 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 wlr_output_layout_output *l_output = wlr_output_layout_get( view->server->output_layout, view->output->wlr_output); if (!l_output) { return; } struct theme *theme = view->server->theme; int width = view->current.width; int height = view->current.height; int full_height = height + theme->border_width * 2 + theme->title_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; /* Convert usable area into layout coordinates */ struct wlr_box usable_area = view->output->usable_area; usable_area.x += l_output->x; usable_area.y += l_output->y; /* 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 = lab_wlr_scene_get_rect(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 */ if (!wlr_box_intersection(&result_box, &part_box, &usable_area)) { /* Not visible */ wlr_scene_node_set_enabled(part->node, false); continue; } else 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); } } } 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; } 070701000000A1000081A4000000000000000000000001645896B8000017E8000000000000000000000000000000000000002C00000000labwc-0.6.3+git0.63db731/src/ssd/ssd_part.c// 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, 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"); } 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, 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, int x, struct view *view) { struct wlr_scene_node *hover; float hover_bg[4] = {0.15f, 0.15f, 0.15f, 0.3f}; 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 */ add_scene_rect(part_list, type, parent, SSD_BUTTON_WIDTH, rc.theme->title_height, 0, 0, bg_color); /* Icon */ 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, parent, 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 overlay */ hover = add_scene_rect(part_list, type, parent, SSD_BUTTON_WIDTH, rc.theme->title_height, 0, 0, hover_bg)->node; wlr_scene_node_set_enabled(hover, false); struct ssd_button *button = ssd_button_descriptor_create(button_root->node); button->type = type; button->view = view; button->hover = hover; return button_root; } 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)); } 070701000000A2000081A4000000000000000000000001645896B800002616000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/src/ssd/ssd_titlebar.c// 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 "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) 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 *close_button_unpressed; struct ssd_sub_tree *subtree; FOR_EACH_STATE(ssd, subtree) { subtree->tree = wlr_scene_tree_create(ssd->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->xbm_menu_active_unpressed->base; iconify_button_unpressed = &theme->xbm_iconify_active_unpressed->base; close_button_unpressed = &theme->xbm_close_active_unpressed->base; maximize_button_unpressed = &theme->xbm_maximize_active_unpressed->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->xbm_menu_inactive_unpressed->base; iconify_button_unpressed = &theme->xbm_iconify_inactive_unpressed->base; maximize_button_unpressed = &theme->xbm_maximize_inactive_unpressed->base; close_button_unpressed = &theme->xbm_close_inactive_unpressed->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, 0, view); add_scene_button(&subtree->parts, LAB_SSD_BUTTON_ICONIFY, parent, color, iconify_button_unpressed, width - SSD_BUTTON_WIDTH * 3, view); add_scene_button(&subtree->parts, LAB_SSD_BUTTON_MAXIMIZE, parent, color, maximize_button_unpressed, width - SSD_BUTTON_WIDTH * 2, view); add_scene_button_corner(&subtree->parts, LAB_SSD_BUTTON_CLOSE, LAB_SSD_PART_CORNER_TOP_RIGHT, parent, corner_top_right, close_button_unpressed, width - SSD_BUTTON_WIDTH * 1, view); } FOR_EACH_END ssd_update_title(ssd); } static bool is_direct_child(struct wlr_scene_node *node, struct ssd_sub_tree *subtree) { return node->parent == subtree->tree; } void ssd_titlebar_update(struct ssd *ssd) { struct view *view = ssd->view; int width = view->current.width; if (width == ssd->state.geometry.width) { return; } struct theme *theme = view->server->theme; 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( lab_wlr_scene_get_rect(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.active.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; } } /* * 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 (!title || !*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 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; } else { dstate = &state->inactive; text_color = theme->window_inactive_label_text_color; } 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) { /* TODO: Do we only have active window fonts? */ scaled_font_buffer_update(part->buffer, title, title_bg_width, &rc.font_activewindow, 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); } 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 == hover_state->node) { /* Cursor is still on the same button */ return; } } disable_old_hover: if (hover_state->node) { wlr_scene_node_set_enabled(hover_state->node, false); hover_state->view = NULL; hover_state->node = NULL; } if (button) { wlr_scene_node_set_enabled(button->hover, true); hover_state->view = button->view; hover_state->node = button->hover; } } #undef FOR_EACH_STATE 070701000000A3000081A4000000000000000000000001645896B80000435C000000000000000000000000000000000000002500000000labwc-0.6.3+git0.63db731/src/theme.c// SPDX-License-Identifier: GPL-2.0-only /* * Theme engine for labwc * * Copyright (C) Johan Malm 2020-2021 */ #define _POSIX_C_SOURCE 200809L #include <cairo.h> #include <ctype.h> #include <drm_fourcc.h> #include <glib.h> #include <math.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/dir.h" #include "common/font.h" #include "common/graphic-helpers.h" #include "common/match.h" #include "common/string-helpers.h" #include "config/rcxml.h" #include "theme.h" #include "xbm/xbm.h" #include "buffer.h" #include "ssd.h" 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->menu_overlap_x = 0; theme->menu_overlap_y = 0; parse_hexstr("#dddad6", theme->window_active_border_color); parse_hexstr("#f6f5f4", theme->window_inactive_border_color); parse_hexstr("#dddad6", 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("#dddad6", 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); /* 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, "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.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.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, const char *theme_name) { FILE *stream = NULL; char *line = NULL; size_t len = 0; char themerc[4096]; if (strlen(theme_dir(theme_name))) { snprintf(themerc, sizeof(themerc), "%s/themerc", theme_dir(theme_name)); stream = fopen(themerc, "r"); } if (!stream) { if (theme_name) { wlr_log(WLR_INFO, "cannot find theme %s", theme_name); } return; } wlr_log(WLR_INFO, "read theme %s", themerc); while (getline(&line, &len, stream) != -1) { char *p = strrchr(line, '\n'); if (p) { *p = '\0'; } process_line(theme, line); } free(line); fclose(stream); } static void theme_read_override(struct theme *theme) { char f[4096] = { 0 }; snprintf(f, sizeof(f), "%s/themerc-override", rc.config_dir); FILE *stream = fopen(f, "r"); if (!stream) { wlr_log(WLR_INFO, "no theme override '%s'", f); return; } wlr_log(WLR_INFO, "read theme-override %s", f); 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); } free(line); fclose(stream); } struct rounded_corner_ctx { struct wlr_box *box; double radius; double line_width; float *fill_color; float *border_color; enum { LAB_CORNER_UNKNOWN = 0, LAB_CORNER_TOP_LEFT, LAB_CORNER_TOP_RIGHT, } corner; }; 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, 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); /* fill */ 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); /* border */ cairo_set_line_cap(cairo, CAIRO_LINE_CAP_ROUND); 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 + half_line_width); cairo_arc(cairo, r, r, r - half_line_width, 180 * deg, 270 * deg); 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_arc(cairo, w - r, r, r - half_line_width, -90 * deg, 0 * deg); cairo_line_to(cairo, w - half_line_width, h); break; default: wlr_log(WLR_ERROR, "unknown corner type"); } cairo_stroke(cairo); 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) { theme->title_height = font_height(&rc.font_activewindow) + 2 * theme->padding_height; if (rc.corner_radius >= theme->title_height) { theme->title_height = rc.corner_radius + 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)); } } 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 */ theme_read(theme, theme_name); /* Read <config-dir>/labwc/themerc-override */ theme_read_override(theme); post_processing(theme); create_corners(theme); xbm_load(theme); } void theme_finish(struct theme *theme) { wlr_buffer_drop(&theme->corner_top_left_active_normal->base); wlr_buffer_drop(&theme->corner_top_left_inactive_normal->base); wlr_buffer_drop(&theme->corner_top_right_active_normal->base); wlr_buffer_drop(&theme->corner_top_right_inactive_normal->base); theme->corner_top_left_active_normal = NULL; theme->corner_top_left_inactive_normal = NULL; theme->corner_top_right_active_normal = NULL; theme->corner_top_right_inactive_normal = NULL; } 070701000000A4000081A4000000000000000000000001645896B800000AAF000000000000000000000000000000000000002500000000labwc-0.6.3+git0.63db731/src/touch.c// SPDX-License-Identifier: GPL-2.0-only #include <wlr/types/wlr_touch.h> #include "labwc.h" #include "common/scene-helpers.h" static struct wlr_surface* touch_get_coords(struct seat *seat, struct wlr_touch *touch, double x, double y, double *sx, double *sy) { /* 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); struct wlr_scene_node *node = wlr_scene_node_at(&seat->server->scene->tree.node, lx, ly, sx, 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; wlr_idle_notify_activity(seat->wlr_idle, seat->seat); double sx, sy; if (touch_get_coords(seat, event->touch, event->x, event->y, &sx, &sy)) { wlr_seat_touch_notify_motion(seat->seat, event->time_msec, event->touch_id, sx, sy); } } 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; double sx, sy; struct wlr_surface *surface = touch_get_coords(seat, event->touch, event->x, event->y, &sx, &sy); 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; 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); } 070701000000A5000081A4000000000000000000000001645896B800000A36000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/src/view-impl-common.c// 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) { if (!view->been_mapped) { window_rules_apply(view, LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP); } desktop_focus_and_activate_view(&view->server->seat, view); view_move_to_front(view); view_update_title(view); view_update_app_id(view); } 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); } } 070701000000A6000081A4000000000000000000000001645896B8000068E6000000000000000000000000000000000000002400000000labwc-0.6.3+git0.63db731/src/view.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <stdio.h> #include <strings.h> #include "common/mem.h" #include "common/scene-helpers.h" #include "labwc.h" #include "menu/menu.h" #include "regions.h" #include "ssd.h" #include "view.h" #include "window-rules.h" #include "workspaces.h" #include "xwayland.h" #define LAB_MIN_VIEW_WIDTH 100 #define LAB_MIN_VIEW_HEIGHT 60 #define LAB_FALLBACK_WIDTH 640 #define LAB_FALLBACK_HEIGHT 480 /** * 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_INVALID = 0, VIEW_EDGE_LEFT, VIEW_EDGE_RIGHT, VIEW_EDGE_UP, VIEW_EDGE_DOWN, VIEW_EDGE_CENTER, }; static 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_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; } 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 void view_discover_output(struct view *view) { assert(view); assert(!view->fullscreen); view->output = output_nearest_to(view->server, view->current.x + view->current.width / 2, view->current.y + view->current.height / 2); } static void _view_set_activated(struct view *view, bool activated) { 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); } } void view_set_activated(struct view *view) { assert(view); struct view *last = view->server->focused_view; if (last == view) { return; } if (last) { _view_set_activated(last, false); } _view_set_activated(view, true); view->server->focused_view = 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); } } 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); } ssd_update_geometry(view->ssd); cursor_update_focus(view->server); if (view->toplevel.handle) { foreign_toplevel_update_outputs(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_adjust_size(struct view *view, int *w, int *h) { assert(view); #if HAVE_XWAYLAND if (xwayland_apply_size_hints(view, w, h)) { /* We don't want to cap the size to keep the aspect ratio */ return; } #endif *w = MAX(*w, LAB_MIN_VIEW_WIDTH); *h = MAX(*h, LAB_MIN_VIEW_HEIGHT); } void view_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); } view->minimized = minimized; if (minimized) { view->impl->unmap(view); _view_set_activated(view, false); if (view == view->server->focused_view) { /* * Prevents clearing the active view when * we don't actually have keyboard focus. * * This may happen when using a custom mouse * focus configuration or by using the foreign * toplevel protocol via some panel. */ view->server->focused_view = NULL; } } else { view->impl->map(view); } } static bool view_compute_centered_position(struct view *view, const struct wlr_box *ref, int w, int h, int *x, int *y) { 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 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; } } 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); } } static void view_apply_natural_geometry(struct view *view) { assert(view); assert(view_is_floating(view)); struct wlr_output_layout *layout = view->server->output_layout; if (wlr_output_layout_intersects(layout, NULL, &view->natural_geometry) || wl_list_empty(&layout->outputs)) { /* restore to original geometry */ view_move_resize(view, view->natural_geometry); } else { /* reposition if original geometry is offscreen */ struct wlr_box box = view->natural_geometry; if (view_compute_centered_position(view, NULL, box.width, box.height, &box.x, &box.y)) { view_move_resize(view, box); } } } 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); 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 (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; } 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_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, bool maximized) { if (view->impl->maximize) { view->impl->maximize(view, maximized); } if (view->toplevel.handle) { wlr_foreign_toplevel_handle_v1_set_maximized( view->toplevel.handle, maximized); } view->maximized = maximized; } /* * 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) { set_maximized(view, false); } view_move_resize(view, geometry); } bool view_is_tiled(struct view *view) { return view && (view->tiled || view->tiled_region || view->tiled_region_evacuate); } bool view_is_floating(struct view *view) { return view && !(view->fullscreen || view->maximized || view->tiled || view->tiled_region || view->tiled_region_evacuate); } /* 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); } void view_maximize(struct view *view, bool maximize, bool store_natural_geometry) { assert(view); if (view->maximized == maximize) { return; } if (view->fullscreen) { return; } if (maximize) { /* * 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_store_natural_geometry(view); } } set_maximized(view, maximize); if (view_is_floating(view)) { view_apply_natural_geometry(view); } else { view_apply_special_geometry(view); } } void view_toggle_maximize(struct view *view) { assert(view); view_maximize(view, !view->maximized, /*store_natural_geometry*/ true); } void view_toggle_decorations(struct view *view) { assert(view); 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); } } 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->focused_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); } 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) { /* 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) { uint32_t top = ZWLR_LAYER_SHELL_V1_LAYER_TOP; wlr_scene_node_set_enabled(&view->output->layer_tree[top]->node, !fullscreen); } } 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); } set_fullscreen(view, fullscreen); if (view_is_floating(view)) { view_apply_natural_geometry(view); } else { view_apply_special_geometry(view); } } void view_adjust_for_layout_change(struct view *view) { assert(view); /* Exit fullscreen if output is lost */ bool was_fullscreen = view->fullscreen; if (was_fullscreen && !output_is_usable(view->output)) { set_fullscreen(view, false); } /* * Floating views are always assigned to the nearest output. * Maximized/tiled views remain on the same output if possible. */ bool is_floating = view_is_floating(view); if (is_floating || !output_is_usable(view->output)) { view_discover_output(view); } if (!is_floating) { view_apply_special_geometry(view); } else if (was_fullscreen) { view_apply_natural_geometry(view); } else { /* reposition view if it's offscreen */ if (!wlr_output_layout_intersects(view->server->output_layout, NULL, &view->pending)) { view_center(view, NULL); } } if (view->toplevel.handle) { foreign_toplevel_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; } void view_move_to_edge(struct view *view, const char *direction) { assert(view); struct output *output = view->output; if (!output_is_usable(output)) { wlr_log(WLR_ERROR, "view has no output, not moving to edge"); return; } if (!direction) { wlr_log(WLR_ERROR, "invalid edge, not moving view"); return; } struct border margin = ssd_get_margin(view->ssd); 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; } int x = 0, y = 0; if (!strcasecmp(direction, "left")) { x = usable.x + margin.left + rc.gap; y = view->pending.y; } else if (!strcasecmp(direction, "up")) { x = view->pending.x; y = usable.y + margin.top + rc.gap; } else if (!strcasecmp(direction, "right")) { x = usable.x + usable.width - view->pending.width - margin.right - rc.gap; y = view->pending.y; } else if (!strcasecmp(direction, "down")) { x = view->pending.x; y = usable.y + usable.height - view->pending.height - margin.bottom - rc.gap; } else { wlr_log(WLR_ERROR, "invalid edge, not moving view"); return; } view_move(view, x, y); } static 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, const char *direction, 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; } enum view_edge edge = view_edge_parse(direction); if (edge == VIEW_EDGE_INVALID) { wlr_log(WLR_ERROR, "invalid edge, not snapping view"); return; } if (view->tiled == edge && !view->maximized) { /* We are already tiled for this edge and thus should switch outputs */ 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; } if (new_output && new_output != current_output) { /* Move to next output */ edge = view_edge_invert(edge); output = output_from_wlr_output(view->server, new_output); if (!output_is_usable(output)) { wlr_log(WLR_ERROR, "invalid output in layout"); return; } } else { /* * 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. * * TODO: The described pattern will cause another bug * in multi monitor setups: it will snap the * window to the inverted edge of the nearest * output. This is the desired behavior when * caused by a keybind but doesn't make sense * when caused by mouse movement. */ view_apply_tiled_geometry(view); return; } } if (view->maximized) { /* Unmaximize + keep using existing natural_geometry */ view_maximize(view, false, /*store_natural_geometry*/ false); } else if (store_natural_geometry) { /* store current geometry as new natural_geometry */ view_store_natural_geometry(view); } view_set_untiled(view); view_set_output(view, output); view->tiled = edge; 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; } if (view->maximized) { /* Unmaximize + keep using existing natural_geometry */ view_maximize(view, false, /*store_natural_geometry*/ false); } else if (store_natural_geometry) { /* store current geometry as new natural_geometry */ view_store_natural_geometry(view); } view_set_untiled(view); view->tiled_region = region; view_apply_region_geometry(view); } void view_move_to_front(struct view *view) { assert(view); if (view->impl->move_to_front) { view->impl->move_to_front(view); cursor_update_focus(view->server); } } void view_move_to_back(struct view *view) { assert(view); if (view->impl->move_to_back) { view->impl->move_to_back(view); cursor_update_focus(view->server); } } 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_destroy(struct view *view) { assert(view); struct server *server = view->server; bool need_cursor_update = false; wl_list_remove(&view->map.link); wl_list_remove(&view->unmap.link); 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; need_cursor_update = true; regions_hide_overlay(&server->seat); } if (server->focused_view == view) { server->focused_view = NULL; need_cursor_update = true; } if (server->seat.pressed.view == view) { seat_reset_pressed(&server->seat); } if (view->tiled_region_evacuate) { zfree(view->tiled_region_evacuate); } osd_on_view_destroy(view); undecorate(view); if (view->scene_tree) { wlr_scene_node_destroy(&view->scene_tree->node); view->scene_tree = NULL; } /* * The layer-shell top-layer is disabled when an application is running * in fullscreen mode, so if that's the case, we have to re-enable it * here. */ if (view->fullscreen && view->output) { uint32_t top = ZWLR_LAYER_SHELL_V1_LAYER_TOP; wlr_scene_node_set_enabled(&view->output->layer_tree[top]->node, true); } /* If we spawned a window menu, close it */ if (server->menu_current && server->menu_current->triggered_by_view == view) { menu_close_root(server); } /* Remove view from server->views */ wl_list_remove(&view->link); free(view); if (need_cursor_update) { cursor_update_focus(server); } } 070701000000A7000081A4000000000000000000000001645896B800000789000000000000000000000000000000000000002C00000000labwc-0.6.3+git0.63db731/src/window-rules.c// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include "config.h" #include <assert.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" void window_rules_apply(struct view *view, enum window_rule_event event) { const char *identifier = view_get_string_prop(view, "app_id"); if (!identifier) { return; } struct window_rule *rule; wl_list_for_each(rule, &rc.window_rules, link) { if (!rule->identifier || rule->event != event) { continue; } if (match_glob(rule->identifier, identifier)) { actions_run(view, view->server, &rule->actions, 0); } } } enum property window_rules_get_property(struct view *view, const char *property) { assert(property); const char *identifier = view_get_string_prop(view, "app_id"); if (!identifier) { goto out; } /* * 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) { if (!rule->identifier) { continue; } /* * 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 (match_glob(rule->identifier, identifier)) { if (!strcasecmp(property, "serverDecoration")) { if (rule->server_decoration) { return rule->server_decoration; } } } } out: return LAB_PROP_UNSPECIFIED; } 070701000000A8000081A4000000000000000000000001645896B800002611000000000000000000000000000000000000002A00000000labwc-0.6.3+git0.63db731/src/workspaces.c// 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 "buffer.h" #include "common/font.h" #include "common/graphic-helpers.h" #include "common/list.h" #include "common/mem.h" #include "labwc.h" #include "view.h" #include "workspaces.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 = 20; uint16_t rect_width = 20; /* 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 * 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) { 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); draw_cairo_border(cairo, width, height, theme->osd_border_width); uint16_t 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; cairo_move_to(cairo, x, margin * 2 + rect_height); //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) { struct wl_list *target_link = current->link.prev; if (target_link == workspaces) { /* Current workspace is the first one, 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) { struct wl_list *target_link = current->link.next; if (target_link == workspaces) { /* Current workspace is the last one, 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) { 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); } } void workspaces_switch_to(struct workspace *target) { 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); /* 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. */ struct view *view = desktop_focused_view(server); if (!view || !view_is_always_on_top(view)) { desktop_focus_topmost_mapped_view(server); } /* And finally show the OSD */ _osd_show(server); /* * Make sure we are not carrying around a * cursor image from the previous desktop */ cursor_update_focus(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) { 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) { 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, "last")) { return anchor->server->workspace_last; } else if (!strcasecmp(name, "left")) { return get_prev(anchor, workspaces); } else if (!strcasecmp(name, "right")) { return get_next(anchor, workspaces); } 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)); } 070701000000A9000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002100000000labwc-0.6.3+git0.63db731/src/xbm070701000000AA000081A4000000000000000000000001645896B800000042000000000000000000000000000000000000002D00000000labwc-0.6.3+git0.63db731/src/xbm/meson.buildlabwc_sources += files( 'parse.c', 'tokenize.c', 'xbm.c', ) 070701000000AB000081A4000000000000000000000001645896B80000087A000000000000000000000000000000000000002900000000labwc-0.6.3+git0.63db731/src/xbm/parse.c// SPDX-License-Identifier: GPL-2.0-only /* * Parse xbm token to create pixmap * * Copyright Johan Malm 2020 */ #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "common/mem.h" #include "xbm/parse.h" static uint32_t color; static uint32_t u32(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); } void parse_set_color(float *rgba) { color = u32(rgba); } 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; } } 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) 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; } 070701000000AC000081A4000000000000000000000001645896B800000960000000000000000000000000000000000000002C00000000labwc-0.6.3+git0.63db731/src/xbm/tokenize.c// SPDX-License-Identifier: GPL-2.0-only /* * XBM file tokenizer * * Copyright Johan Malm 2020 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "common/mem.h" #include "xbm/tokenize.h" 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++; } 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; } 070701000000AD000081A4000000000000000000000001645896B800000B8D000000000000000000000000000000000000002700000000labwc-0.6.3+git0.63db731/src/xbm/xbm.c// SPDX-License-Identifier: GPL-2.0-only /* * Create wlr textures based on xbm data * * Copyright Johan Malm 2020 */ #include <stdio.h> #include <stdlib.h> #include <drm_fourcc.h> #include "common/dir.h" #include "common/grab-file.h" #include "config/rcxml.h" #include "theme.h" #include "xbm/parse.h" #include "xbm/xbm.h" #include "buffer.h" /* built-in 6x6 buttons */ char menu_button_normal[] = { 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00 }; char iconify_button_normal[] = { 0x00, 0x00, 0x00, 0x00, 0x3f, 0x3f }; char max_button_normal[] = { 0x3f, 0x3f, 0x21, 0x21, 0x21, 0x3f }; char max_button_toggled[] = { 0x3e, 0x22, 0x2f, 0x29, 0x39, 0x0f }; char close_button_normal[] = { 0x33, 0x3f, 0x1e, 0x1e, 0x3f, 0x33 }; static char * xbm_path(const char *button) { static char buffer[4096] = { 0 }; snprintf(buffer, sizeof(buffer), "%s/%s", theme_dir(rc.theme_name), button); return buffer; } static void load_button(const char *filename, struct lab_data_buffer **buffer, char *button) { struct pixmap pixmap = {0}; if (*buffer) { wlr_buffer_drop(&(*buffer)->base); *buffer = NULL; } /* Read file into memory as it's easier to tokenzie that way */ char *token_buffer = grab_file(xbm_path(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) { pixmap = parse_xbm_builtin(button, 6); } /* Create buffer with free_on_destroy being true */ *buffer = buffer_create_wrap(pixmap.data, pixmap.width, pixmap.height, pixmap.width * 4, true); } void xbm_load(struct theme *theme) { parse_set_color(theme->window_active_button_menu_unpressed_image_color); load_button("menu.xbm", &theme->xbm_menu_active_unpressed, menu_button_normal); parse_set_color(theme->window_active_button_iconify_unpressed_image_color); load_button("iconify.xbm", &theme->xbm_iconify_active_unpressed, iconify_button_normal); parse_set_color(theme->window_active_button_max_unpressed_image_color); load_button("max.xbm", &theme->xbm_maximize_active_unpressed, max_button_normal); parse_set_color(theme->window_active_button_close_unpressed_image_color); load_button("close.xbm", &theme->xbm_close_active_unpressed, close_button_normal); parse_set_color(theme->window_inactive_button_menu_unpressed_image_color); load_button("menu.xbm", &theme->xbm_menu_inactive_unpressed, menu_button_normal); parse_set_color(theme->window_inactive_button_iconify_unpressed_image_color); load_button("iconify.xbm", &theme->xbm_iconify_inactive_unpressed, iconify_button_normal); parse_set_color(theme->window_inactive_button_max_unpressed_image_color); load_button("max.xbm", &theme->xbm_maximize_inactive_unpressed, max_button_normal); parse_set_color(theme->window_inactive_button_close_unpressed_image_color); load_button("close.xbm", &theme->xbm_close_inactive_unpressed, close_button_normal); } 070701000000AE000081A4000000000000000000000001645896B800000B88000000000000000000000000000000000000002900000000labwc-0.6.3+git0.63db731/src/xdg-popup.c// 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 destroy; struct wl_listener new_popup; }; static void popup_unconstrain(struct view *view, struct wlr_xdg_popup *popup) { struct server *server = view->server; struct wlr_box *popup_box = &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, &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); free(popup); } 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_surface_is_xdg_surface(wlr_popup->parent) ? wlr_xdg_surface_from_wlr_surface(wlr_popup->parent) : NULL; 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); /* * 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. */ struct wlr_scene_tree *parent_tree = parent->surface->data; 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); popup_unconstrain(view, wlr_popup); } 070701000000AF000081A4000000000000000000000001645896B8000048DC000000000000000000000000000000000000002300000000labwc-0.6.3+git0.63db731/src/xdg.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.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 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_xdg_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); 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); } } 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); 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_map(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, map); view->impl->map(view); } static void handle_unmap(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, unmap); view->impl->unmap(view); } 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)); } view_maximize(view, xdg_toplevel_from_view(view)->requested.maximized, /*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) { /* * We can't assume here that view->current is equal to * view->pending because some clients (e.g. terminals) * refuse to accept the exact size we requested. To * account for the size difference and avoid visual * glitches during resize, we apply the same position * adjustments here as in handle_commit(). */ view_impl_apply_geometry(view, view->current.width, view->current.height); } } 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_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 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 position_xdg_toplevel_view(struct view *view) { struct wlr_xdg_toplevel *parent_xdg_toplevel = xdg_toplevel_from_view(view)->parent; if (!parent_xdg_toplevel) { view_center(view, NULL); } else { /* * If child-toplevel-views, we center-align 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); } } static const char * xdg_toplevel_view_get_string_prop(struct view *view, const char *prop) { struct wlr_xdg_toplevel *xdg_toplevel = xdg_toplevel_from_view(view); if (!strcmp(prop, "title")) { return xdg_toplevel->title; } if (!strcmp(prop, "app_id")) { return xdg_toplevel->app_id; } return ""; } static void xdg_toplevel_view_map(struct view *view) { if (view->mapped) { return; } view->mapped = true; 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; foreign_toplevel_handle_create(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)) { position_xdg_toplevel_view(view); } if (!view->fullscreen && requested->fullscreen) { set_fullscreen_from_request(view, requested); } else if (!view->maximized && requested->maximized) { view_maximize(view, true, /*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) { if (view->mapped) { view->mapped = false; wlr_scene_node_set_enabled(&view->scene_tree->node, false); wl_list_remove(&view->commit.link); desktop_focus_topmost_mapped_view(view->server); } } 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, .unmap = xdg_toplevel_view_unmap, .maximize = xdg_toplevel_view_maximize, .move_to_front = view_impl_move_to_front, .move_to_back = view_impl_move_to_back, }; void xdg_activation_handle_request(struct wl_listener *listener, void *data) { const struct wlr_xdg_activation_v1_request_activate_event *event = data; if (!wlr_surface_is_xdg_surface(event->surface)) { return; } struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_from_wlr_surface(event->surface); struct view *view = xdg_surface ? xdg_surface->data : NULL; 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. */ /* * TODO: This is the exact same code as used in foreign.c. * Refactor it into a public helper function somewhere. */ wlr_log(WLR_DEBUG, "Activating surface"); if (view->workspace != view->server->workspace_current) { workspaces_switch_to(view->workspace); } desktop_focus_and_activate_view(&view->server->seat, view); view_move_to_front(view); } /* * 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 */ 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; 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->map.notify = handle_map; wl_signal_add(&xdg_surface->events.map, &view->map); view->unmap.notify = handle_unmap; wl_signal_add(&xdg_surface->events.unmap, &view->unmap); view->destroy.notify = handle_destroy; wl_signal_add(&xdg_surface->events.destroy, &view->destroy); struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel; view->request_move.notify = handle_request_move; wl_signal_add(&toplevel->events.request_move, &view->request_move); view->request_resize.notify = handle_request_resize; wl_signal_add(&toplevel->events.request_resize, &view->request_resize); view->request_minimize.notify = handle_request_minimize; wl_signal_add(&toplevel->events.request_minimize, &view->request_minimize); view->request_maximize.notify = handle_request_maximize; wl_signal_add(&toplevel->events.request_maximize, &view->request_maximize); view->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&toplevel->events.request_fullscreen, &view->request_fullscreen); view->set_title.notify = handle_set_title; wl_signal_add(&toplevel->events.set_title, &view->set_title); /* Events specific to XDG toplevel views */ xdg_toplevel_view->set_app_id.notify = handle_set_app_id; wl_signal_add(&toplevel->events.set_app_id, &xdg_toplevel_view->set_app_id); xdg_toplevel_view->new_popup.notify = handle_new_xdg_popup; wl_signal_add(&xdg_surface->events.new_popup, &xdg_toplevel_view->new_popup); wl_list_insert(&server->views, &view->link); } 070701000000B0000081A4000000000000000000000001645896B800001AFB000000000000000000000000000000000000003200000000labwc-0.6.3+git0.63db731/src/xwayland-unmanaged.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <wlr/xwayland.h> #include "common/list.h" #include "common/mem.h" #include "labwc.h" #include "xwayland.h" static void unmanaged_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 unmanaged_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 unmanaged_handle_map(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, 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); wl_signal_add(&xsurface->events.set_geometry, &unmanaged->set_geometry); unmanaged->set_geometry.notify = unmanaged_handle_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_mapped_view(server); } static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, 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 unmanaged_handle_destroy(struct wl_listener *listener, void *data) { struct xwayland_unmanaged *unmanaged = wl_container_of(listener, unmanaged, destroy); wl_list_remove(&unmanaged->request_configure.link); wl_list_remove(&unmanaged->override_redirect.link); wl_list_remove(&unmanaged->request_activate.link); wl_list_remove(&unmanaged->map.link); wl_list_remove(&unmanaged->unmap.link); wl_list_remove(&unmanaged->destroy.link); free(unmanaged); } static void unmanaged_handle_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, override_redirect); struct wlr_xwayland_surface *xsurface = unmanaged->xwayland_surface; struct server *server = unmanaged->server; bool mapped = xsurface->mapped; if (mapped) { unmanaged_handle_unmap(&unmanaged->unmap, NULL); } unmanaged_handle_destroy(&unmanaged->destroy, NULL); xsurface->data = NULL; xwayland_view_create(server, xsurface, mapped); } static void unmanaged_handle_request_activate(struct wl_listener *listener, void *data) { wlr_log(WLR_DEBUG, "handle unmanaged request_activate"); struct wlr_xwayland_surface *xsurface = data; if (!xsurface->mapped) { return; } struct xwayland_unmanaged *unmanaged = xsurface->data; 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_mapped_view(server); if (view && view->type == LAB_XWAYLAND_VIEW) { struct wlr_xwayland_surface *surf = wlr_xwayland_surface_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 = unmanaged; wl_signal_add(&xsurface->events.request_configure, &unmanaged->request_configure); unmanaged->request_configure.notify = unmanaged_handle_request_configure; wl_signal_add(&xsurface->events.map, &unmanaged->map); unmanaged->map.notify = unmanaged_handle_map; wl_signal_add(&xsurface->events.unmap, &unmanaged->unmap); unmanaged->unmap.notify = unmanaged_handle_unmap; wl_signal_add(&xsurface->events.destroy, &unmanaged->destroy); unmanaged->destroy.notify = unmanaged_handle_destroy; wl_signal_add(&xsurface->events.set_override_redirect, &unmanaged->override_redirect); unmanaged->override_redirect.notify = unmanaged_handle_override_redirect; wl_signal_add(&xsurface->events.request_activate, &unmanaged->request_activate); unmanaged->request_activate.notify = unmanaged_handle_request_activate; if (mapped) { unmanaged_handle_map(&unmanaged->map, xsurface); } } 070701000000B1000081A4000000000000000000000001645896B8000054F4000000000000000000000000000000000000002800000000labwc-0.6.3+git0.63db731/src/xwayland.c// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <stdlib.h> #include <wlr/xwayland.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" 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; } bool xwayland_apply_size_hints(struct view *view, int *w, int *h) { assert(view); if (view->type == LAB_XWAYLAND_VIEW) { xcb_size_hints_t *hints = xwayland_surface_from_view(view)->size_hints; /* * Honor size increments from WM_SIZE_HINTS. Typically, X11 * terminal emulators will use WM_SIZE_HINTS to make sure that * the terminal is resized to a width/height evenly divisible by * the cell (character) size. */ if (hints) { *w = round_to_increment(*w, hints->base_width, hints->width_inc); *h = round_to_increment(*h, hints->base_height, hints->height_inc); *w = MAX(*w, MAX(1, hints->min_width)); *h = MAX(*h, MAX(1, hints->min_height)); return true; } } return false; } 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_map(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, map); view->impl->map(view); } static void handle_unmap(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, unmap); view->impl->unmap(view); /* * Some xwayland clients leave unmapped child views around, typically * when a dialog window is closed. Although handle_destroy() is not * called for these, we have to deal with them as such in terms of the * foreign-toplevel protocol to avoid panels and the like still showing * them. */ if (view->toplevel.handle) { wlr_foreign_toplevel_handle_v1_destroy(view->toplevel.handle); view->toplevel.handle = NULL; } } 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(data && data == xwayland_view->xwayland_surface); 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. */ xwayland_view->xwayland_surface->data = NULL; xwayland_view->xwayland_surface = NULL; /* Remove XWayland view specific listeners */ wl_list_remove(&xwayland_view->request_activate.link); wl_list_remove(&xwayland_view->request_configure.link); wl_list_remove(&xwayland_view->set_app_id.link); wl_list_remove(&xwayland_view->set_decorations.link); wl_list_remove(&xwayland_view->override_redirect.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); /* If not resizing, process the move immediately */ if (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; int width = event->width; int height = event->height; view_adjust_size(view, &width, &height); xwayland_view_configure(view, (struct wlr_box){event->x, event->y, width, height}); } 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; desktop_focus_and_activate_view(&view->server->seat, view); view_move_to_front(view); } 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); } 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_app_id); 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 wlr_xwayland_surface *xwayland_surface = xwayland_surface_from_view(view); 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; assert(data && data == xwayland_view->xwayland_surface); view_set_decorations(view, want_deco(xwayland_view->xwayland_surface)); } static void handle_override_redirect(struct wl_listener *listener, void *data) { struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, override_redirect); struct view *view = &xwayland_view->base; struct wlr_xwayland_surface *xsurface = data; assert(xsurface && xsurface == xwayland_view->xwayland_surface); struct server *server = view->server; bool mapped = xsurface->mapped; if (mapped) { handle_unmap(&view->unmap, xsurface); } handle_destroy(&view->destroy, xsurface); /* view is invalid after this point */ xwayland_unmanaged_create(server, xsurface, mapped); } 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) { /* Just make sure the view is on-screen */ view_adjust_for_layout_change(view); } else { view_center(view, NULL); } } static void top_left_edge_boundary_check(struct view *view) { struct wlr_box deco = ssd_max_extents(view); if (deco.x < 0) { view->current.x -= deco.x; } if (deco.y < 0) { view->current.y -= deco.y; } view->impl->configure(view, view->current); } static void xwayland_view_map(struct view *view) { if (view->mapped) { return; } view->mapped = true; ensure_initial_geometry_and_output(view); wlr_scene_node_set_enabled(&view->scene_tree->node, true); struct wlr_xwayland_surface *xwayland_surface = xwayland_surface_from_view(view); if (!view->fullscreen && xwayland_surface->fullscreen) { view_set_fullscreen(view, 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; } if (!view->toplevel.handle) { foreign_toplevel_handle_create(view); } if (!view->been_mapped) { view_set_decorations(view, want_deco(xwayland_surface)); if (view_is_floating(view)) { 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); } if (view->ssd_enabled && view_is_floating(view)) { top_left_edge_boundary_check(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; } static void xwayland_view_unmap(struct view *view) { if (!view->mapped) { return; } view->mapped = false; wl_list_remove(&view->commit.link); wlr_scene_node_set_enabled(&view->scene_tree->node, false); desktop_focus_topmost_mapped_view(view->server); } static void xwayland_view_maximize(struct view *view, bool maximized) { wlr_xwayland_surface_set_maximized(xwayland_surface_from_view(view), maximized); } enum z_direction { LAB_TO_FRONT, LAB_TO_BACK, }; static void move_sub_views(struct view *parent, enum z_direction z_direction) { assert(parent); if (parent->type != LAB_XWAYLAND_VIEW) { return; } struct wlr_xwayland_surface *parent_xwayland_surface = xwayland_surface_from_view(parent); struct view *view, *next; wl_list_for_each_reverse_safe(view, next, &parent->server->views, link) { /* need to stop here, otherwise loops keeps going forever */ if (view == parent) { break; } if (view->type != LAB_XWAYLAND_VIEW) { continue; } if (!view->mapped && !view->minimized) { continue; } if (top_parent_of(view) != parent_xwayland_surface) { continue; } if (z_direction == LAB_TO_FRONT) { view_impl_move_to_front(view); } else if (z_direction == LAB_TO_BACK) { view_impl_move_to_back(view); } } } static void xwayland_view_move_to_front(struct view *view) { view_impl_move_to_front(view); move_sub_views(view, LAB_TO_FRONT); } static void xwayland_view_move_to_back(struct view *view) { view_impl_move_to_back(view); move_sub_views(view, LAB_TO_BACK); } 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); if (activated) { wlr_xwayland_surface_restack(xwayland_surface, NULL, XCB_STACK_MODE_ABOVE); /* Restack unmanaged surfaces on top */ struct xwayland_unmanaged *u; struct wl_list *list = &view->server->unmanaged_surfaces; wl_list_for_each(u, list, link) { wlr_xwayland_surface_restack(u->xwayland_surface, NULL, XCB_STACK_MODE_ABOVE); } } } 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, .move_to_front = xwayland_view_move_to_front, .move_to_back = xwayland_view_move_to_back, }; 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); view->map.notify = handle_map; wl_signal_add(&xsurface->events.map, &view->map); view->unmap.notify = handle_unmap; wl_signal_add(&xsurface->events.unmap, &view->unmap); view->destroy.notify = handle_destroy; wl_signal_add(&xsurface->events.destroy, &view->destroy); view->request_minimize.notify = handle_request_minimize; wl_signal_add(&xsurface->events.request_minimize, &view->request_minimize); view->request_maximize.notify = handle_request_maximize; wl_signal_add(&xsurface->events.request_maximize, &view->request_maximize); view->request_fullscreen.notify = handle_request_fullscreen; wl_signal_add(&xsurface->events.request_fullscreen, &view->request_fullscreen); view->request_move.notify = handle_request_move; wl_signal_add(&xsurface->events.request_move, &view->request_move); view->request_resize.notify = handle_request_resize; wl_signal_add(&xsurface->events.request_resize, &view->request_resize); view->set_title.notify = handle_set_title; wl_signal_add(&xsurface->events.set_title, &view->set_title); /* Events specific to XWayland views */ xwayland_view->request_activate.notify = handle_request_activate; wl_signal_add(&xsurface->events.request_activate, &xwayland_view->request_activate); xwayland_view->request_configure.notify = handle_request_configure; wl_signal_add(&xsurface->events.request_configure, &xwayland_view->request_configure); xwayland_view->set_app_id.notify = handle_set_class; wl_signal_add(&xsurface->events.set_class, &xwayland_view->set_app_id); xwayland_view->set_decorations.notify = handle_set_decorations; wl_signal_add(&xsurface->events.set_decorations, &xwayland_view->set_decorations); xwayland_view->override_redirect.notify = handle_override_redirect; wl_signal_add(&xsurface->events.set_override_redirect, &xwayland_view->override_redirect); wl_list_insert(&view->server->views, &view->link); 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 handle_ready(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, xwayland_ready); wlr_xwayland_set_seat(server->xwayland, server->seat.seat); } 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_ready.notify = handle_ready; wl_signal_add(&server->xwayland->events.ready, &server->xwayland_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); } } void xwayland_server_finish(struct server *server) { wlr_xwayland_destroy(server->xwayland); server->xwayland = NULL; } 070701000000B2000041ED000000000000000000000002645896B800000000000000000000000000000000000000000000002500000000labwc-0.6.3+git0.63db731/subprojects070701000000B3000081A4000000000000000000000001645896B80000000C000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/subprojects/.gitignore/* !/*.wrap 070701000000B4000081A4000000000000000000000001645896B800000046000000000000000000000000000000000000003000000000labwc-0.6.3+git0.63db731/subprojects/seatd.wrap[wrap-git] url=https://git.sr.ht/~kennylevinsen/seatd revision=0.6.4 070701000000B5000081A4000000000000000000000001645896B80000007A000000000000000000000000000000000000003200000000labwc-0.6.3+git0.63db731/subprojects/wlroots.wrap[wrap-git] url = https://gitlab.freedesktop.org/wlroots/wlroots.git revision = 0.16 [provide] dependency_names = wlroots 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1865 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor