Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:fearlessgeekmedia
labwc
_service:obs_scm:labwc-0.6.0+git3.8fe2f2a.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:labwc-0.6.0+git3.8fe2f2a.obscpio of Package labwc
07070100000000000081A40000000000000000000000016378A523000000E3000000000000000000000000000000000000002700000000labwc-0.6.0+git3.8fe2f2a/.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 07070100000001000041ED0000000000000000000000036378A52300000000000000000000000000000000000000000000002100000000labwc-0.6.0+git3.8fe2f2a/.github07070100000002000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002B00000000labwc-0.6.0+git3.8fe2f2a/.github/workflows07070100000003000081A40000000000000000000000016378A52300001160000000000000000000000000000000000000003500000000labwc-0.6.0+git3.8fe2f2a/.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: Compile on: [pull_request] jobs: build: 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 '/^deb/ s/^deb/deb-src/' /etc/apt/sources.list > /tmp/src cat /tmp/src >> /etc/apt/sources.list 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 \ 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 07070100000004000081A40000000000000000000000016378A52300001046000000000000000000000000000000000000003300000000labwc-0.6.0+git3.8fe2f2a/.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 }}" 07070100000005000081A40000000000000000000000016378A523000030FC000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/CONTRIBUTING.md- [1. How to Contribute](#how-to-contribute) - [2. Debugging](#debugging) - [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) # 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. 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/ ``` 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]. 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]. 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. For example: ``` xgettext --keyword=_ --language=C --add-comments -o po/labwc.pot src/menu/menu.c ``` [See this tutorial for further guidance](https://www.labri.fr/perso/fleury/posts/programming/a-quick-gettext-tutorial.html) [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 07070100000006000081A40000000000000000000000016378A523000046AC000000000000000000000000000000000000002100000000labwc-0.6.0+git3.8fe2f2a/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. 07070100000007000081A40000000000000000000000016378A52300004931000000000000000000000000000000000000002100000000labwc-0.6.0+git3.8fe2f2a/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 | |------------|---------------|-----------------|---------------| | 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.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.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 07070100000008000081A40000000000000000000000016378A52300002062000000000000000000000000000000000000002300000000labwc-0.6.0+git3.8fe2f2a/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. What is this?](#1-what-is-this) - [1.1 Screenshot](#11-screenshot) - [1.2 Videos](#12-videos) - [2. Build and Installation](#2-build-and-installation) - [3. Configuration](#3-configuration) - [4. Theming](#4-theming) - [5. Usage](#5-usage) - [6. Integration](#6-integration) - [7. Scope](#7-scope) ## 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 only understands [wayland-protocols] & [wlr-protocols], and it cannot be controlled with dbus, sway/i3-IPC or other technology. The reason for this is that we believe that custom IPCs and protocols create a fragmentation that hinders general Wayland adoption. In order to avoid reinventing configuration and theme syntax, 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. Labwc supports the following: - [x] Config files (rc.xml, autostart, environment, menu.xml) - [x] Theme files and xbm icons - [x] Basic root-menu and client-menu - [x] HiDPI - [x] wlroots protocols such as `output-management`, `layer-shell` and `foreign-toplevel` - [x] Optionally xwayland See [scope] for full details on implemented features. ## 1.1 Screenshot <a href="https://i.imgur.com/vOelinT.png"> <img src="https://i.imgur.com/vOelinTl.png"> </a> ## 1.2 Videos | video link | date | content | -------------- | ------------| ------- | [Video (2:48)] | 31-Oct-2022 | pre-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 ## 2. Build and Installation To build, simply run: meson build/ ninja -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 For a step-by-step initial configuration guide, see [getting-started] User config files are located at `${XDG_CONFIG_HOME:-$HOME/.config/labwc/}` with the following four files being used: | file | man page | ------------- | -------- | [rc.xml] | [labwc-config(5)], [labwc-actions(5)] | [menu.xml] | [labwc-menu(5)] | [autostart] | [labwc(1)] | [environment] | [labwc-config(5)] The example [rc.xml] has been kept simple. For all options and default values, see [rc.xml.all] Configuration and theme files are reloaded on receiving SIGHUP (e.g. `killall -s SIGHUP labwc`) For keyboard settings, see [environment] and [xkeyboard-config(7)] ## 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. ## 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. ## 7. 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 details. High-level summary of 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). [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 [rc.xml]: docs/rc.xml [rc.xml.all]: docs/rc.xml.all [menu.xml]: docs/menu.xml [autostart]: docs/autostart [environment]: docs/environment [themerc]: docs/themerc [labwc(1)]: https://labwc.github.io/labwc.1.html [labwc-config(5)]: https://labwc.github.io/labwc-config.5.html [labwc-menu(5)]: https://labwc.github.io/labwc-menu.5.html [labwc-environment(5)]: https://labwc.github.io/labwc-environment.5.html [labwc-theme(5)]: https://labwc.github.io/labwc-theme.5.html [labwc-actions(5)]: https://labwc.github.io/labwc-actions.5.html [xkeyboard-config(7)]: https://manpages.debian.org/testing/xkb-data/xkeyboard-config.7.en.html [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 07070100000009000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000001E00000000labwc-0.6.0+git3.8fe2f2a/docs0707010000000A000081A40000000000000000000000016378A523000004E2000000000000000000000000000000000000002800000000labwc-0.6.0+git3.8fe2f2a/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 & 0707010000000B000081A40000000000000000000000016378A523000003A0000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/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 0707010000000C000081A40000000000000000000000016378A52300000864000000000000000000000000000000000000003200000000labwc-0.6.0+git3.8fe2f2a/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="Execute"><command>* 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="Iconify">* Iconify (minimize) focused window. *<action name="Move">* Begin interactive move of window under cursor *<action name="MoveToEdge"><direction>* 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>* Resize window to fill half the output in the given direction. Supports directions "left", "up", "right", "down" and "center". *<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>* 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="ToggleAlwaysOnTop">* Toggle always-on-top of focused window. *<action name="GoToDesktop"><to>* Switch to workspace. 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>* Send active window to workspace. Supported values are the same as for GoToDesktop. *<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) 0707010000000D000081A40000000000000000000000016378A523000029E4000000000000000000000000000000000000003100000000labwc-0.6.0+git3.8fe2f2a/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 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><cycleViewPreview>* [yes|no] Preview the contents of the selected window when cycling between windows. Default is no. *<core><cycleViewOutlines>* [yes|no] Draw an outline around the selected window when cycling between windows. Default is yes. ## 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><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. ## 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. *<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><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. ## 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) 0707010000000E000081A40000000000000000000000016378A52300000417000000000000000000000000000000000000002F00000000labwc-0.6.0+git3.8fe2f2a/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="" /> <!-- 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) # SEE ALSO labwc(1), labwc-action(5), labwc-config(5), labwc-theme(5) 0707010000000F000081A40000000000000000000000016378A523000011CD000000000000000000000000000000000000003000000000labwc-0.6.0+git3.8fe2f2a/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. # 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.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. *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 ("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) 07070100000010000081A40000000000000000000000016378A52300000514000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/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) 07070100000011000081A40000000000000000000000016378A52300000072000000000000000000000000000000000000002C00000000labwc-0.6.0+git3.8fe2f2a/docs/labwc.desktop[Desktop Entry] Name=labwc Comment=A wayland stacking compositor Exec=labwc Type=Application DesktopNames=wlroots 07070100000012000081A40000000000000000000000016378A52300000521000000000000000000000000000000000000002700000000labwc-0.6.0+git3.8fe2f2a/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> <menu id="workspaces" label="Workspace"> <item label="Move left"> <action name="SendToDesktop" to="left" /> <action name="GoToDesktop" to="left" /> </item> <item label="Move right"> <action name="SendToDesktop" to="right" /> <action name="GoToDesktop" 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</command></action> </item> <item label="Terminal"> <action name="Execute"><command>alacritty</command></action> </item> <item label="Reconfigure"> <action name="Reconfigure"></action> </item> <item label="Exit"> <action name="Exit"></action> </item> <item label="Poweroff"> <action name="Execute"><command>systemctl -i poweroff</command></action> </item> </menu> </openbox_menu> 07070100000013000081A40000000000000000000000016378A52300000210000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/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 07070100000014000081A40000000000000000000000016378A523000001FE000000000000000000000000000000000000002500000000labwc-0.6.0+git3.8fe2f2a/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</name><size>10</size></font> </theme> <keyboard> <default /> <keybind key="A-Return"> <action name="Execute"><command>sakura</command></action> </keybind> </keyboard> </labwc_config> 07070100000015000081A40000000000000000000000016378A52300002692000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/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> <cycleViewPreview>no</cycleViewPreview> <cycleViewOutlines>yes</cycleViewOutlines> </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> <!-- edge strength is in pixels --> <resistance> <screenEdgeStrength>20</screenEdgeStrength> </resistance> <focus> <followMouse>no</followMouse> <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 futher 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> <!-- 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. --> <keyboard> <repeatRate>25</repeatRate> <repeatDelay>600</repeatDelay> <keybind key="A-Tab"> <action name="NextWindow" /> </keybind> <keybind key="W-Return"> <action name="Execute"><command>alacritty</command></action> </keybind> <keybind key="A-F3"> <action name="Execute"><command>bemenu-run</command></action> </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</direction></action> </keybind> <keybind key="A-Right"> <action name="MoveToEdge"><direction>right</direction></action> </keybind> <keybind key="A-Up"> <action name="MoveToEdge"><direction>up</direction></action> </keybind> <keybind key="A-Down"> <action name="MoveToEdge"><direction>down</direction></action> </keybind> <keybind key="W-Left"> <action name="SnapToEdge"><direction>left</direction></action> </keybind> <keybind key="W-Right"> <action name="SnapToEdge"><direction>right</direction></action> </keybind> <keybind key="W-Up"> <action name="SnapToEdge"><direction>up</direction></action> </keybind> <keybind key="W-Down"> <action name="SnapToEdge"><direction>down</direction></action> </keybind> <keybind key="A-Space"> <action name="ShowMenu"><menu>client-menu</menu></action> </keybind> <keybind key="XF86_AudioLowerVolume"> <action name="Execute"><command>amixer sset Master 5%-</command></action> </keybind> <keybind key="XF86_AudioRaiseVolume"> <action name="Execute"><command>amixer sset Master 5%+</command></action> </keybind> <keybind key="XF86_AudioMute"> <action name="Execute"><command>amixer sset Master toggle</command></action> </keybind> <keybind key="XF86_MonBrightnessUp"> <action name="Execute"><command>brightnessctl set +10%</command></action> </keybind> <keybind key="XF86_MonBrightnessDown"> <action name="Execute"><command>brightnessctl set 10%-</command></action> </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" --> <mouse> <!-- time is in ms --> <doubleClickTime>500</doubleClickTime> <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</menu> </action> </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</menu> </action> </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</menu></action> </mousebind> <mousebind button="Right" action="Press"> <action name="ShowMenu"><menu>root-menu</menu></action> </mousebind> <mousebind button="Middle" action="Press"> <action name="ShowMenu"><menu>root-menu</menu></action> </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> </labwc_config> 07070100000016000081A40000000000000000000000016378A52300000483000000000000000000000000000000000000002600000000labwc-0.6.0+git3.8fe2f2a/docs/themerc# general border.width: 1 padding.height: 3 menu.overlap.x: 0 menu.overlap.y: 0 # 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 "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.items.bg.color: #fcfbfa menu.items.text.color: #000000 menu.items.active.bg.color: #dddad6 menu.items.active.text.color: #000000 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 07070100000017000041ED0000000000000000000000076378A52300000000000000000000000000000000000000000000002100000000labwc-0.6.0+git3.8fe2f2a/include07070100000018000081A40000000000000000000000016378A523000002E1000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/include/action.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_ACTION_H #define __LABWC_ACTION_H struct view; struct server; struct wl_list; 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 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 */ 07070100000019000081A40000000000000000000000016378A523000007C3000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/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 */ 0707010000001A000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002800000000labwc-0.6.0+git3.8fe2f2a/include/common0707010000001B000081A40000000000000000000000016378A523000002FA000000000000000000000000000000000000002E00000000labwc-0.6.0+git3.8fe2f2a/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 */ 0707010000001C000081A40000000000000000000000016378A52300000124000000000000000000000000000000000000002E00000000labwc-0.6.0+git3.8fe2f2a/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 */ 0707010000001D000081A40000000000000000000000016378A523000000C4000000000000000000000000000000000000003200000000labwc-0.6.0+git3.8fe2f2a/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 */ 0707010000001E000081A40000000000000000000000016378A52300000546000000000000000000000000000000000000002F00000000labwc-0.6.0+git3.8fe2f2a/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, FONT_SLANT_ITALIC }; enum font_weight { FONT_WEIGHT_NORMAL, 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 */ 0707010000001F000081A40000000000000000000000016378A5230000016E000000000000000000000000000000000000003400000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000020000081A40000000000000000000000016378A52300000529000000000000000000000000000000000000003A00000000labwc-0.6.0+git3.8fe2f2a/include/common/graphic-helpers.h/* SPDX-License-Identifier: GPL-2.0-only */ #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); 07070100000021000081A40000000000000000000000016378A523000002C7000000000000000000000000000000000000002F00000000labwc-0.6.0+git3.8fe2f2a/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); } 07070100000022000081A40000000000000000000000016378A523000005B0000000000000000000000000000000000000002E00000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000023000081A40000000000000000000000016378A523000001DB000000000000000000000000000000000000003300000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000024000081A40000000000000000000000016378A5230000064E000000000000000000000000000000000000003D00000000labwc-0.6.0+git3.8fe2f2a/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 struct font; 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 destoyed 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); #endif /* __LAB_COMMON_SCALED_FONT_BUFFER_H */ 07070100000025000081A40000000000000000000000016378A52300000948000000000000000000000000000000000000003E00000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000026000081A40000000000000000000000016378A52300000278000000000000000000000000000000000000003800000000labwc-0.6.0+git3.8fe2f2a/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); 07070100000027000081A40000000000000000000000016378A52300000107000000000000000000000000000000000000003000000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000028000081A40000000000000000000000016378A52300000229000000000000000000000000000000000000003900000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000029000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002800000000labwc-0.6.0+git3.8fe2f2a/include/config0707010000002A000081A40000000000000000000000016378A52300000322000000000000000000000000000000000000003200000000labwc-0.6.0+git3.8fe2f2a/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); #endif /* __LABWC_KEYBIND_H */ 0707010000002B000081A40000000000000000000000016378A52300000307000000000000000000000000000000000000003300000000labwc-0.6.0+git3.8fe2f2a/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); #endif /* __LABWC_LIBINPUT_H */ 0707010000002C000081A40000000000000000000000016378A52300000530000000000000000000000000000000000000003400000000labwc-0.6.0+git3.8fe2f2a/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); #endif /* __LABWC_MOUSEBIND_H */ 0707010000002D000081A40000000000000000000000016378A5230000055E000000000000000000000000000000000000003000000000labwc-0.6.0+git3.8fe2f2a/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" struct rcxml { char *config_dir; /* core */ bool xdg_shell_server_side_deco; int gap; bool adaptive_sync; /* focus */ bool focus_follow_mouse; 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 */ /* libinput */ struct wl_list libinput_categories; /* resistance */ int screen_edge_strength; /* window snapping */ int snap_edge_range; bool snap_top_maximize; /* cycle view (alt+tab) */ bool cycle_preview_contents; bool cycle_preview_outlines; struct { int popuptime; struct wl_list workspaces; /* struct workspace.link */ } workspace_config; }; 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 */ 0707010000002E000081A40000000000000000000000016378A5230000028B000000000000000000000000000000000000003200000000labwc-0.6.0+git3.8fe2f2a/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 */ 0707010000002F000081A40000000000000000000000016378A52300000D49000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/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); void cursor_init(struct seat *seat); void cursor_finish(struct seat *seat); #endif /* __LABWC_CURSOR_H */ 07070100000030000081A40000000000000000000000016378A523000000A7000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000031000081A40000000000000000000000016378A5230000025E000000000000000000000000000000000000002700000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000032000081A40000000000000000000000016378A523000003BB000000000000000000000000000000000000002D00000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000033000081A40000000000000000000000016378A52300004985000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/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_server_decoration.h> #include <wlr/types/wlr_subcompositor.h> #include <wlr/types/wlr_xcursor_manager.h> #include <wlr/types/wlr_xdg_decoration_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> #if HAVE_XWAYLAND #include <wlr/xwayland.h> #endif #include <xkbcommon/xkbcommon.h> #include "cursor.h" #include "config/keybind.h" #include "config/rcxml.h" #include "ssd.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 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; /* 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; 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 xdg_toplevel_decoration; #if HAVE_XWAYLAND struct wlr_xwayland *xwayland; struct wl_listener xwayland_ready; struct wl_listener new_xwayland_surface; #endif struct wlr_input_inhibit_manager *input_inhibit; struct wl_listener input_inhibit_activate; struct wl_listener input_inhibit_deactivate; 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 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 wl_list layers[LAB_NR_LAYERS]; 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_buffer *workspace_osd; struct wlr_box usable_area; struct lab_data_buffer *osd_buffer; struct wl_listener destroy; struct wl_listener frame; bool leased; }; #undef LAB_NR_LAYERS enum view_type { LAB_XDG_SHELL_VIEW, #if HAVE_XWAYLAND LAB_XWAYLAND_VIEW, #endif }; 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 (*move)(struct view *view, int x, int y); 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); }; struct border { int top; int right; int bottom; int left; }; struct view { struct server *server; enum view_type type; const struct view_impl *impl; struct wl_list link; struct output *output; struct workspace *workspace; union { struct wlr_xdg_surface *xdg_surface; #if HAVE_XWAYLAND struct wlr_xwayland_surface *xwayland_surface; #endif }; struct wlr_surface *surface; struct wlr_scene_tree *scene_tree; struct wlr_scene_node *scene_node; bool mapped; bool been_mapped; bool minimized; bool maximized; uint32_t tiled; /* private, enum view_edge in src/view.c */ struct wlr_output *fullscreen; /* geometry of the wlr_surface contained within the view */ int x, y, w, h; /* user defined geometry before maximize / tiling / fullscreen */ struct wlr_box natural_geometry; /* * margin refers to the space between the extremities of the * 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 view_pending_move_resize { bool update_x, update_y; int x, y; uint32_t width, height; uint32_t configure_serial; } pending_move_resize; struct ssd ssd; struct wlr_foreign_toplevel_handle_v1 *toplevel_handle; struct wl_listener toplevel_handle_request_maximize; struct wl_listener toplevel_handle_request_minimize; struct wl_listener toplevel_handle_request_fullscreen; struct wl_listener toplevel_handle_request_activate; struct wl_listener toplevel_handle_request_close; 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_configure; /* xwayland only */ struct wl_listener request_activate; struct wl_listener request_minimize; struct wl_listener request_maximize; struct wl_listener request_fullscreen; struct wl_listener set_title; struct wl_listener set_app_id; /* class on xwayland */ struct wl_listener set_decorations; /* xwayland only */ struct wl_listener override_redirect; /* xwayland only */ struct wl_listener new_popup; /* xdg-shell only */ /* Not (yet) implemented */ /* struct wl_listener set_role; */ /* struct wl_listener set_window_type; */ /* struct wl_listener set_hints; */ }; #if HAVE_XWAYLAND 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; }; #endif 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_toplevel_decoration(struct wl_listener *listener, void *data); void xdg_surface_new(struct wl_listener *listener, void *data); #if HAVE_XWAYLAND void xwayland_surface_new(struct wl_listener *listener, void *data); struct xwayland_unmanaged *xwayland_unmanaged_create(struct server *server, struct wlr_xwayland_surface *xsurface); void unmanaged_handle_map(struct wl_listener *listener, void *data); #endif void view_set_activated(struct view *view); 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); /* view_wlr_output - return the output that a view is mostly on */ struct wlr_output *view_wlr_output(struct view *view); void view_center(struct view *view); void view_maximize(struct view *view, bool maximize); void view_set_fullscreen(struct view *view, bool fullscreen, struct wlr_output *wlr_output); void view_toggle_maximize(struct view *view); void view_toggle_decorations(struct view *view); void view_toggle_always_on_top(struct view *view); 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_discover_output(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); 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_impl_map(struct view *view); void view_adjust_size(struct view *view, int *w, int *h); void view_on_output_destroy(struct view *view); void view_destroy(struct view *view); void foreign_toplevel_handle_create(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_move_to_front(struct view *view); void desktop_move_to_back(struct view *view); void desktop_focus_and_activate_view(struct seat *seat, struct view *view); void desktop_arrange_all_views(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_end(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 wlr_box output_usable_area_in_layout_coords(struct output *output); struct wlr_box output_usable_area_from_cursor_coords(struct server *server); 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 */ 07070100000034000081A40000000000000000000000016378A523000003DA000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/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 wl_list link; /* output::layers */ struct wlr_scene_layer_surface_v1 *scene_layer_surface; struct wl_listener destroy; struct wl_listener map; struct wl_listener unmap; struct wl_listener surface_commit; struct wl_listener output_destroy; struct wl_listener new_popup; struct wlr_box geo; bool mapped; /* TODO: add extent? */ struct server *server; }; 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 */ 07070100000035000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002600000000labwc-0.6.0+git3.8fe2f2a/include/menu07070100000036000081A40000000000000000000000016378A52300000B1C000000000000000000000000000000000000002D00000000labwc-0.6.0+git3.8fe2f2a/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; 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_rootmenu(struct server *server); void menu_init_windowmenu(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 */ 07070100000037000081A40000000000000000000000016378A5230000003E000000000000000000000000000000000000002D00000000labwc-0.6.0+git3.8fe2f2a/include/meson.buildconfigure_file(output: 'config.h', configuration: conf_data) 07070100000038000081A40000000000000000000000016378A523000009C9000000000000000000000000000000000000002800000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000039000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/include/private0707010000003A000081A40000000000000000000000016378A5230000021E000000000000000000000000000000000000003200000000labwc-0.6.0+git3.8fe2f2a/include/private/action.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_PRIVATE_ACTION_H #define __LABWC_PRIVATE_ACTION_H /* Don't include ourself as search path starts at current directory */ #include "../action.h" enum action_arg_type { LAB_ACTION_ARG_STR = 0, }; 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; }; #endif /* __LABWC_PRIVATE_ACTION_H */ 0707010000003B000081A40000000000000000000000016378A5230000011F000000000000000000000000000000000000002E00000000labwc-0.6.0+git3.8fe2f2a/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 */ 0707010000003C000081A40000000000000000000000016378A52300001490000000000000000000000000000000000000002700000000labwc-0.6.0+git3.8fe2f2a/include/ssd.h/* SPDX-License-Identifier: GPL-2.0-only */ #ifndef __LABWC_SSD_H #define __LABWC_SSD_H #include "buffer.h" #include <wlr/util/box.h> #define BUTTON_COUNT 4 #define BUTTON_WIDTH 26 #define EXTENDED_AREA 8 #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 } /* * 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 view; struct wl_list; struct wlr_box; struct wlr_scene_tree; struct scaled_font_buffer; 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 { bool enabled; 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 { int x; int y; int width; int height; struct ssd_state_title { char *text; struct ssd_state_title_width active; struct ssd_state_title_width inactive; } title; } state; /* An invisble 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; }; 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; }; /* Public SSD API */ void ssd_create(struct view *view); void ssd_set_active(struct view *view, bool active); void ssd_update_title(struct view *view); void ssd_update_geometry(struct view *view); void ssd_reload(struct view *view); void ssd_destroy(struct view *view); void ssd_update_button_hover(struct wlr_scene_node *node, struct ssd_hover_state *hover_state); /* Public SSD helpers */ enum ssd_part_type ssd_at(struct view *view, double lx, double ly); enum ssd_part_type ssd_get_part_type( struct view *view, 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); /* 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 view *view); void ssd_titlebar_update(struct view *view); void ssd_titlebar_destroy(struct view *view); void ssd_border_create(struct view *view); void ssd_border_update(struct view *view); void ssd_border_destroy(struct view *view); void ssd_extents_create(struct view *view); void ssd_extents_update(struct view *view); void ssd_extents_destroy(struct view *view); /* TODO: clean up / update */ struct border ssd_thickness(struct view *view); struct wlr_box ssd_max_extents(struct view *view); #endif /* __LABWC_SSD_H */ 0707010000003D000081A40000000000000000000000016378A52300000B33000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/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 */ 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_separator_width; 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 */ 0707010000003E000081A40000000000000000000000016378A5230000033E000000000000000000000000000000000000002E00000000labwc-0.6.0+git3.8fe2f2a/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_send_to(struct view *view, 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 */ 0707010000003F000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002500000000labwc-0.6.0+git3.8fe2f2a/include/xbm07070100000040000081A40000000000000000000000016378A5230000031D000000000000000000000000000000000000002D00000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000041000081A40000000000000000000000016378A5230000022D000000000000000000000000000000000000003000000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000042000081A40000000000000000000000016378A5230000011B000000000000000000000000000000000000002B00000000labwc-0.6.0+git3.8fe2f2a/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 */ 07070100000043000081A40000000000000000000000016378A523000009E0000000000000000000000000000000000000002500000000labwc-0.6.0+git3.8fe2f2a/meson.buildproject( 'labwc', 'c', version: '0.6.0', 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( [ '-Wno-unused-parameter', '-Wundef', ]), 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') 07070100000044000081A40000000000000000000000016378A52300000127000000000000000000000000000000000000002B00000000labwc-0.6.0+git3.8fe2f2a/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') 07070100000045000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000001C00000000labwc-0.6.0+git3.8fe2f2a/po07070100000046000081A40000000000000000000000016378A5230000000C000000000000000000000000000000000000002400000000labwc-0.6.0+git3.8fe2f2a/po/LINGUASde es it sv 07070100000047000081A40000000000000000000000016378A52300000010000000000000000000000000000000000000002800000000labwc-0.6.0+git3.8fe2f2a/po/POTFILES.insrc/menu/menu.c 07070100000048000081A40000000000000000000000016378A52300000454000000000000000000000000000000000000002200000000labwc-0.6.0+git3.8fe2f2a/po/de.po# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR <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: 2022-04-30 16:43+1000\n" "PO-Revision-Date: 2022-04-30 16:50+1000\n" "Last-Translator: Consolatis <https://github.com/Consolatis>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: German\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:428 msgid "Reconfigure" msgstr "Rekonfigurieren" #: src/menu/menu.c:430 msgid "Exit" msgstr "Beenden" #: src/menu/menu.c:446 msgid "Minimize" msgstr "Minimieren" #: src/menu/menu.c:448 msgid "Maximize" msgstr "Maximieren" #: src/menu/menu.c:450 msgid "Fullscreen" msgstr "Vollbild" #: src/menu/menu.c:452 msgid "Decorations" msgstr "Dekorationen" #: src/menu/menu.c:454 msgid "AlwaysOnTop" msgstr "Immer im Vordergrund" #: src/menu/menu.c:456 msgid "Close" msgstr "Schließen" 07070100000049000081A40000000000000000000000016378A52300000436000000000000000000000000000000000000002200000000labwc-0.6.0+git3.8fe2f2a/po/es.po# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR <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: 2022-04-30 16:43+1000\n" "PO-Revision-Date: 2022-04-30 16:50+1000\n" "Last-Translator: Mick Amadio <01micko@gmail.com>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: Spanish\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:428 msgid "Reconfigure" msgstr "Reconfigurar" #: src/menu/menu.c:430 msgid "Exit" msgstr "Salir" #: src/menu/menu.c:446 msgid "Minimize" msgstr "Minimizar" #: src/menu/menu.c:448 msgid "Maximize" msgstr "Maximizar" #: src/menu/menu.c:450 msgid "Fullscreen" msgstr "Pantalla completa" #: src/menu/menu.c:452 msgid "Decorations" msgstr "Decoraciones" #: src/menu/menu.c:454 msgid "AlwaysOnTop" msgstr "Siempre encima" #: src/menu/menu.c:456 msgid "Close" msgstr "Cerrar" 0707010000004A000081A40000000000000000000000016378A52300000431000000000000000000000000000000000000002200000000labwc-0.6.0+git3.8fe2f2a/po/it.po# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR <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: 2022-04-30 16:43+1000\n" "PO-Revision-Date: 2022-04-30 16:50+1000\n" "Last-Translator: Mick Amadio <01micko@gmail.com>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: Italian\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:428 msgid "Reconfigure" msgstr "Riconfigurare" #: src/menu/menu.c:430 msgid "Exit" msgstr "Uscita" #: src/menu/menu.c:446 msgid "Minimize" msgstr "Riduci" #: src/menu/menu.c:448 msgid "Maximize" msgstr "Ingrandisci" #: src/menu/menu.c:450 msgid "Fullscreen" msgstr "Schermo intero" #: src/menu/menu.c:452 msgid "Decorations" msgstr "Decorazioni" #: src/menu/menu.c:454 msgid "AlwaysOnTop" msgstr "Sempre sopra" #: src/menu/menu.c:456 msgid "Close" msgstr "Chiudi" 0707010000004B000081A40000000000000000000000016378A523000003D1000000000000000000000000000000000000002600000000labwc-0.6.0+git3.8fe2f2a/po/labwc.pot# Labwc pot file # Copyright (C) 2022 # 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: 2022-04-30 16:43+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:428 msgid "Reconfigure" msgstr "" #: src/menu/menu.c:430 msgid "Exit" msgstr "" #: src/menu/menu.c:446 msgid "Minimize" msgstr "" #: src/menu/menu.c:448 msgid "Maximize" msgstr "" #: src/menu/menu.c:450 msgid "Fullscreen" msgstr "" #: src/menu/menu.c:452 msgid "Decorations" msgstr "" #: src/menu/menu.c:454 msgid "AlwaysOnTop" msgstr "" #: src/menu/menu.c:456 msgid "Close" msgstr "" 0707010000004C000081A40000000000000000000000016378A5230000019E000000000000000000000000000000000000002800000000labwc-0.6.0+git3.8fe2f2a/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', '--no-location', '--keyword=_', '--msgid-bugs=https://github.com/labwc/labwc/issues'], preset: 'glib' ) 0707010000004D000081A40000000000000000000000016378A5230000042D000000000000000000000000000000000000002200000000labwc-0.6.0+git3.8fe2f2a/po/sv.po# Labwc pot file # Copyright (C) 2022 # This file is distributed under the same license as the labwc package. # FIRST AUTHOR <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: 2022-04-30 16:43+1000\n" "PO-Revision-Date: 2022-04-30 16:50+1000\n" "Last-Translator: Johan Malm <jgm323@gmail.com\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: Swedish\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/menu/menu.c:428 msgid "Reconfigure" msgstr "Konfigurera om" #: src/menu/menu.c:430 msgid "Exit" msgstr "UtgÃ¥ng" #: src/menu/menu.c:446 msgid "Minimize" msgstr "Minimera" #: src/menu/menu.c:448 msgid "Maximize" msgstr "Maximera" #: src/menu/menu.c:450 msgid "Fullscreen" msgstr "Fullskärm" #: src/menu/menu.c:452 msgid "Decorations" msgstr "Dekorationer" #: src/menu/menu.c:454 msgid "AlwaysOnTop" msgstr "Alltid överst" #: src/menu/menu.c:456 msgid "Close" msgstr "Stäng" 0707010000004E000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002300000000labwc-0.6.0+git3.8fe2f2a/protocols0707010000004F000081A40000000000000000000000016378A52300000478000000000000000000000000000000000000002F00000000labwc-0.6.0+git3.8fe2f2a/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', '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, ) 07070100000050000081A40000000000000000000000016378A52300000C54000000000000000000000000000000000000004700000000labwc-0.6.0+git3.8fe2f2a/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> 07070100000051000081A40000000000000000000000016378A5230000481E000000000000000000000000000000000000004300000000labwc-0.6.0+git3.8fe2f2a/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> 07070100000052000081A40000000000000000000000016378A52300005DFA000000000000000000000000000000000000004900000000labwc-0.6.0+git3.8fe2f2a/protocols/wlr-output-management-unstable-v1.xml<?xml version="1.0" encoding="UTF-8"?> <protocol name="wlr_output_management_unstable_v1"> <copyright> Copyright © 2019 Purism SPC 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> <description summary="protocol to configure output devices"> This protocol exposes interfaces to obtain and modify output device configuration. 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_manager_v1" version="2"> <description summary="output device configuration manager"> This interface is a manager that allows reading and writing the current output device configuration. Output devices that display pixels (e.g. a physical monitor or a virtual output in a window) are represented as heads. Heads cannot be created nor destroyed by the client, but they can be enabled or disabled and their properties can be changed. Each head may have one or more available modes. Whenever a head appears (e.g. a monitor is plugged in), it will be advertised via the head event. Immediately after the output manager is bound, all current heads are advertised. Whenever a head's properties change, the relevant wlr_output_head events will be sent. Not all head properties will be sent: only properties that have changed need to. Whenever a head disappears (e.g. a monitor is unplugged), a wlr_output_head.finished event will be sent. After one or more heads appear, change or disappear, the done event will be sent. It carries a serial which can be used in a create_configuration request to update heads properties. The information obtained from this protocol should only be used for output configuration purposes. This protocol is not designed to be a generic output property advertisement protocol for regular clients. Instead, protocols such as xdg-output should be used. </description> <event name="head"> <description summary="introduce a new head"> This event introduces a new head. This happens whenever a new head appears (e.g. a monitor is plugged in) or after the output manager is bound. </description> <arg name="head" type="new_id" interface="zwlr_output_head_v1"/> </event> <event name="done"> <description summary="sent all information about current configuration"> This event is sent after all information has been sent after binding to the output manager object and after any subsequent changes. This applies to child head and mode objects as well. In other words, this event is sent whenever a head or mode is created or destroyed and whenever one of their properties has been changed. Not all state is re-sent each time the current configuration changes: only the actual changes are sent. This allows changes to the output configuration to be seen as atomic, even if they happen via multiple events. A serial is sent to be used in a future create_configuration request. </description> <arg name="serial" type="uint" summary="current configuration serial"/> </event> <request name="create_configuration"> <description summary="create a new output configuration object"> Create a new output configuration object. This allows to update head properties. </description> <arg name="id" type="new_id" interface="zwlr_output_configuration_v1"/> <arg name="serial" type="uint"/> </request> <request name="stop"> <description summary="stop sending events"> Indicates the client no longer wishes to receive events for output configuration changes. However the compositor may emit further events, until the finished event is emitted. The client must not send any more requests after this one. </description> </request> <event name="finished"> <description summary="the compositor has finished with the manager"> This event indicates that the compositor is done sending manager events. The compositor will destroy the object immediately after sending this event, so it will become invalid and the client should release any resources associated with it. </description> </event> </interface> <interface name="zwlr_output_head_v1" version="2"> <description summary="output device"> A head is an output device. The difference between a wl_output object and a head is that heads are advertised even if they are turned off. A head object only advertises properties and cannot be used directly to change them. A head has some read-only properties: modes, name, description and physical_size. These cannot be changed by clients. Other properties can be updated via a wlr_output_configuration object. Properties sent via this interface are applied atomically via the wlr_output_manager.done event. No guarantees are made regarding the order in which properties are sent. </description> <event name="name"> <description summary="head name"> This event describes the head name. The naming convention is compositor defined, but limited to alphanumeric characters and dashes (-). Each name is unique among all wlr_output_head objects, but if a wlr_output_head object is destroyed the same name may be reused later. The names will also remain consistent across sessions with the same hardware and software configuration. Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc. If the compositor implements the xdg-output protocol and this head is enabled, the xdg_output.name event must report the same name. The name event is sent after a wlr_output_head object is created. This event is only sent once per object, and the name does not change over the lifetime of the wlr_output_head object. </description> <arg name="name" type="string"/> </event> <event name="description"> <description summary="head description"> This event describes a human-readable description of the head. The description is a UTF-8 string with no convention defined for its contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 output via :1'. However, do not assume that the name is a reflection of the make, model, serial of the underlying DRM connector or the display name of the underlying X11 connection, etc. If the compositor implements xdg-output and this head is enabled, the xdg_output.description must report the same description. The description event is sent after a wlr_output_head object is created. This event is only sent once per object, and the description does not change over the lifetime of the wlr_output_head object. </description> <arg name="description" type="string"/> </event> <event name="physical_size"> <description summary="head physical size"> This event describes the physical size of the head. This event is only sent if the head has a physical size (e.g. is not a projector or a virtual device). </description> <arg name="width" type="int" summary="width in millimeters of the output"/> <arg name="height" type="int" summary="height in millimeters of the output"/> </event> <event name="mode"> <description summary="introduce a mode"> This event introduces a mode for this head. It is sent once per supported mode. </description> <arg name="mode" type="new_id" interface="zwlr_output_mode_v1"/> </event> <event name="enabled"> <description summary="head is enabled or disabled"> This event describes whether the head is enabled. A disabled head is not mapped to a region of the global compositor space. When a head is disabled, some properties (current_mode, position, transform and scale) are irrelevant. </description> <arg name="enabled" type="int" summary="zero if disabled, non-zero if enabled"/> </event> <event name="current_mode"> <description summary="current mode"> This event describes the mode currently in use for this head. It is only sent if the output is enabled. </description> <arg name="mode" type="object" interface="zwlr_output_mode_v1"/> </event> <event name="position"> <description summary="current position"> This events describes the position of the head in the global compositor space. It is only sent if the output is enabled. </description> <arg name="x" type="int" summary="x position within the global compositor space"/> <arg name="y" type="int" summary="y position within the global compositor space"/> </event> <event name="transform"> <description summary="current transformation"> This event describes the transformation currently applied to the head. It is only sent if the output is enabled. </description> <arg name="transform" type="int" enum="wl_output.transform"/> </event> <event name="scale"> <description summary="current scale"> This events describes the scale of the head in the global compositor space. It is only sent if the output is enabled. </description> <arg name="scale" type="fixed"/> </event> <event name="finished"> <description summary="the head has been destroyed"> The compositor will destroy the object immediately after sending this event, so it will become invalid and the client should release any resources associated with it. </description> </event> <!-- Version 2 additions --> <event name="make" since="2"> <description summary="head manufacturer"> This event describes the manufacturer of the head. This must report the same make as the wl_output interface does in its geometry event. Together with the model and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the make of the head or the definition of a make is not sensible in the current setup, for example in a virtual session. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the make string in UI to users. For that the string provided by the description event should be preferred. </description> <arg name="make" type="string"/> </event> <event name="model" since="2"> <description summary="head model"> This event describes the model of the head. This must report the same model as the wl_output interface does in its geometry event. Together with the make and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the model of the head or the definition of a model is not sensible in the current setup, for example in a virtual session. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the model string in UI to users. For that the string provided by the description event should be preferred. </description> <arg name="model" type="string"/> </event> <event name="serial_number" since="2"> <description summary="head serial number"> This event describes the serial number of the head. Together with the make and model events the purpose is to allow clients to recognize heads from previous sessions and for example load head- specific configurations back. It is not guaranteed this event will be ever sent. A reason for that can be that the compositor does not have information about the serial number of the head or the definition of a serial number is not sensible in the current setup. Clients can still try to identify the head by available information from other events but should be aware that there is an increased risk of false positives. It is not recommended to display the serial_number string in UI to users. For that the string provided by the description event should be preferred. </description> <arg name="serial_number" type="string"/> </event> </interface> <interface name="zwlr_output_mode_v1" version="2"> <description summary="output mode"> This object describes an output mode. Some heads don't support output modes, in which case modes won't be advertised. Properties sent via this interface are applied atomically via the wlr_output_manager.done event. No guarantees are made regarding the order in which properties are sent. </description> <event name="size"> <description summary="mode size"> This event describes the mode size. The size is given in physical hardware units of the output device. This is not necessarily the same as the output size in the global compositor space. For instance, the output may be scaled or transformed. </description> <arg name="width" type="int" summary="width of the mode in hardware units"/> <arg name="height" type="int" summary="height of the mode in hardware units"/> </event> <event name="refresh"> <description summary="mode refresh rate"> This event describes the mode's fixed vertical refresh rate. It is only sent if the mode has a fixed refresh rate. </description> <arg name="refresh" type="int" summary="vertical refresh rate in mHz"/> </event> <event name="preferred"> <description summary="mode is preferred"> This event advertises this mode as preferred. </description> </event> <event name="finished"> <description summary="the mode has been destroyed"> The compositor will destroy the object immediately after sending this event, so it will become invalid and the client should release any resources associated with it. </description> </event> </interface> <interface name="zwlr_output_configuration_v1" version="2"> <description summary="output configuration"> This object is used by the client to describe a full output configuration. First, the client needs to setup the output configuration. Each head can be either enabled (and configured) or disabled. It is a protocol error to send two enable_head or disable_head requests with the same head. It is a protocol error to omit a head in a configuration. Then, the client can apply or test the configuration. The compositor will then reply with a succeeded, failed or cancelled event. Finally the client should destroy the configuration object. </description> <enum name="error"> <entry name="already_configured_head" value="1" summary="head has been configured twice"/> <entry name="unconfigured_head" value="2" summary="head has not been configured"/> <entry name="already_used" value="3" summary="request sent after configuration has been applied or tested"/> </enum> <request name="enable_head"> <description summary="enable and configure a head"> Enable a head. This request creates a head configuration object that can be used to change the head's properties. </description> <arg name="id" type="new_id" interface="zwlr_output_configuration_head_v1" summary="a new object to configure the head"/> <arg name="head" type="object" interface="zwlr_output_head_v1" summary="the head to be enabled"/> </request> <request name="disable_head"> <description summary="disable a head"> Disable a head. </description> <arg name="head" type="object" interface="zwlr_output_head_v1" summary="the head to be disabled"/> </request> <request name="apply"> <description summary="apply the configuration"> Apply the new output configuration. In case the configuration is successfully applied, there is no guarantee that the new output state matches completely the requested configuration. For instance, a compositor might round the scale if it doesn't support fractional scaling. After this request has been sent, the compositor must respond with an succeeded, failed or cancelled event. Sending a request that isn't the destructor is a protocol error. </description> </request> <request name="test"> <description summary="test the configuration"> Test the new output configuration. The configuration won't be applied, but will only be validated. Even if the compositor succeeds to test a configuration, applying it may fail. After this request has been sent, the compositor must respond with an succeeded, failed or cancelled event. Sending a request that isn't the destructor is a protocol error. </description> </request> <event name="succeeded"> <description summary="configuration changes succeeded"> Sent after the compositor has successfully applied the changes or tested them. Upon receiving this event, the client should destroy this object. If the current configuration has changed, events to describe the changes will be sent followed by a wlr_output_manager.done event. </description> </event> <event name="failed"> <description summary="configuration changes failed"> Sent if the compositor rejects the changes or failed to apply them. The compositor should revert any changes made by the apply request that triggered this event. Upon receiving this event, the client should destroy this object. </description> </event> <event name="cancelled"> <description summary="configuration has been cancelled"> Sent if the compositor cancels the configuration because the state of an output changed and the client has outdated information (e.g. after an output has been hotplugged). The client can create a new configuration with a newer serial and try again. Upon receiving this event, the client should destroy this object. </description> </event> <request name="destroy" type="destructor"> <description summary="destroy the output configuration"> Using this request a client can tell the compositor that it is not going to use the configuration object anymore. Any changes to the outputs that have not been applied will be discarded. This request also destroys wlr_output_configuration_head objects created via this object. </description> </request> </interface> <interface name="zwlr_output_configuration_head_v1" version="2"> <description summary="head configuration"> This object is used by the client to update a single head's configuration. It is a protocol error to set the same property twice. </description> <enum name="error"> <entry name="already_set" value="1" summary="property has already been set"/> <entry name="invalid_mode" value="2" summary="mode doesn't belong to head"/> <entry name="invalid_custom_mode" value="3" summary="mode is invalid"/> <entry name="invalid_transform" value="4" summary="transform value outside enum"/> <entry name="invalid_scale" value="5" summary="scale negative or zero"/> </enum> <request name="set_mode"> <description summary="set the mode"> This request sets the head's mode. </description> <arg name="mode" type="object" interface="zwlr_output_mode_v1"/> </request> <request name="set_custom_mode"> <description summary="set a custom mode"> This request assigns a custom mode to the head. The size is given in physical hardware units of the output device. If set to zero, the refresh rate is unspecified. It is a protocol error to set both a mode and a custom mode. </description> <arg name="width" type="int" summary="width of the mode in hardware units"/> <arg name="height" type="int" summary="height of the mode in hardware units"/> <arg name="refresh" type="int" summary="vertical refresh rate in mHz or zero"/> </request> <request name="set_position"> <description summary="set the position"> This request sets the head's position in the global compositor space. </description> <arg name="x" type="int" summary="x position in the global compositor space"/> <arg name="y" type="int" summary="y position in the global compositor space"/> </request> <request name="set_transform"> <description summary="set the transform"> This request sets the head's transform. </description> <arg name="transform" type="int" enum="wl_output.transform"/> </request> <request name="set_scale"> <description summary="set the scale"> This request sets the head's scale. </description> <arg name="scale" type="fixed"/> </request> </interface> </protocol> 07070100000053000081A40000000000000000000000016378A523000015DD000000000000000000000000000000000000004F00000000labwc-0.6.0+git3.8fe2f2a/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> 07070100000054000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002100000000labwc-0.6.0+git3.8fe2f2a/scripts07070100000055000081A40000000000000000000000016378A52300000206000000000000000000000000000000000000002B00000000labwc-0.6.0+git3.8fe2f2a/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 07070100000056000081ED0000000000000000000000016378A5230000033D000000000000000000000000000000000000002700000000labwc-0.6.0+git3.8fe2f2a/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 "$@" 07070100000057000081ED0000000000000000000000016378A52300037476000000000000000000000000000000000000002F00000000labwc-0.6.0+git3.8fe2f2a/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 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; } 07070100000058000041ED0000000000000000000000076378A52300000000000000000000000000000000000000000000001D00000000labwc-0.6.0+git3.8fe2f2a/src07070100000059000081A40000000000000000000000016378A52300002345000000000000000000000000000000000000002600000000labwc-0.6.0+git3.8fe2f2a/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 "common/list.h" #include "common/mem.h" #include "common/spawn.h" #include "debug.h" #include "labwc.h" #include "menu/menu.h" #include "private/action.h" #include "ssd.h" #include "workspaces.h" enum action_type { ACTION_TYPE_INVALID = 0, ACTION_TYPE_NONE, ACTION_TYPE_CLOSE, 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_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_RESIZE, ACTION_TYPE_GO_TO_DESKTOP, ACTION_TYPE_SEND_TO_DESKTOP, }; const char *action_names[] = { "INVALID", "None", "Close", "Debug", "Execute", "Exit", "MoveToEdge", "SnapToEdge", "NextWindow", "PreviousWindow", "Reconfigure", "ShowMenu", "ToggleMaximize", "ToggleFullscreen", "ToggleDecorations", "ToggleAlwaysOnTop", "Focus", "Iconify", "Move", "Raise", "Resize", "GoToDesktop", "SendToDesktop", NULL }; static char * action_str_from_arg(struct action_arg *arg) { assert(arg->type == LAB_ACTION_ARG_STR); return ((struct action_arg_str *)arg)->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; } struct action *action = znew(*action); action->type = action_type_from_str(action_name); wl_list_init(&action->args); return action; } 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(action_str_from_arg(arg)); } zfree(arg); } zfree(action); } } static void show_menu(struct server *server, struct view *view, const char *menu_name) { 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, 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->x; y = view->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) { wlr_log(WLR_DEBUG, "Handling action %s (%u)", action_names[action->type], action->type); /* Get arg now so we don't have to repeat every time we only need one */ arg = action_get_first_arg(action); /* * 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_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) { view_move_to_edge(view, action_str_from_arg(arg)); } else { wlr_log(WLR_ERROR, "Missing argument for MoveToEdge"); } break; case ACTION_TYPE_SNAP_TO_EDGE: if (arg) { view_snap_to_edge(view, action_str_from_arg(arg)); } else { wlr_log(WLR_ERROR, "Missing argument for SnapToEdge"); } 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_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) { desktop_move_to_front(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; 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 (!arg) { wlr_log(WLR_ERROR, "Missing argument for SendToDesktop"); break; } if (view) { struct workspace *target; char *target_name = action_str_from_arg(arg); target = workspaces_find(view->workspace, target_name); if (target) { workspaces_send_to(view, target); } } break; case ACTION_TYPE_NONE: 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); } } } 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); } 0707010000005A000081A40000000000000000000000016378A523000010BA000000000000000000000000000000000000002600000000labwc-0.6.0+git3.8fe2f2a/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; } 0707010000005B000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002400000000labwc-0.6.0+git3.8fe2f2a/src/common0707010000005C000081A40000000000000000000000016378A523000006CD000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/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; } 0707010000005D000081A40000000000000000000000016378A52300000B83000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/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); } } 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); } 0707010000005E000081A40000000000000000000000016378A523000003FE000000000000000000000000000000000000002E00000000labwc-0.6.0+git3.8fe2f2a/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"); } } 0707010000005F000081A40000000000000000000000016378A52300000F43000000000000000000000000000000000000002B00000000labwc-0.6.0+git3.8fe2f2a/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); } 07070100000060000081A40000000000000000000000016378A52300000252000000000000000000000000000000000000003000000000labwc-0.6.0+git3.8fe2f2a/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; } 07070100000061000081A40000000000000000000000016378A52300000A31000000000000000000000000000000000000003600000000labwc-0.6.0+git3.8fe2f2a/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]); } 07070100000062000081A40000000000000000000000016378A52300000295000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/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; } 07070100000063000081A40000000000000000000000016378A523000000FB000000000000000000000000000000000000003000000000labwc-0.6.0+git3.8fe2f2a/src/common/meson.buildlabwc_sources += files( 'buf.c', 'dir.c', 'fd_util.c', 'font.c', 'grab-file.c', 'graphic-helpers.c', 'mem.c', 'nodename.c', 'scaled_font_buffer.c', 'scaled_scene_buffer.c', 'scene-helpers.c', 'spawn.c', 'string-helpers.c', ) 07070100000064000081A40000000000000000000000016378A5230000029D000000000000000000000000000000000000002F00000000labwc-0.6.0+git3.8fe2f2a/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; } } } 07070100000065000081A40000000000000000000000016378A5230000095D000000000000000000000000000000000000003900000000labwc-0.6.0+git3.8fe2f2a/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); } 07070100000066000081A40000000000000000000000016378A5230000184A000000000000000000000000000000000000003A00000000labwc-0.6.0+git3.8fe2f2a/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); } 07070100000067000081A40000000000000000000000016378A52300000437000000000000000000000000000000000000003400000000labwc-0.6.0+git3.8fe2f2a/src/common/scene-helpers.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <wlr/types/wlr_scene.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; } 07070100000068000081A40000000000000000000000016378A523000004F5000000000000000000000000000000000000002C00000000labwc-0.6.0+git3.8fe2f2a/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); } 07070100000069000081A40000000000000000000000016378A523000001FD000000000000000000000000000000000000003500000000labwc-0.6.0+git3.8fe2f2a/src/common/string-helpers.c// SPDX-License-Identifier: GPL-2.0-only #include <ctype.h> #include <stdio.h> #include <string.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'; } 0707010000006A000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002400000000labwc-0.6.0+git3.8fe2f2a/src/config0707010000006B000081A40000000000000000000000016378A523000006E1000000000000000000000000000000000000002E00000000labwc-0.6.0+git3.8fe2f2a/src/config/keybind.c// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #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; } } 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 (int 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; } 0707010000006C000081A40000000000000000000000016378A52300000386000000000000000000000000000000000000002F00000000labwc-0.6.0+git3.8fe2f2a/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->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; } 0707010000006D000081A40000000000000000000000016378A52300000066000000000000000000000000000000000000003000000000labwc-0.6.0+git3.8fe2f2a/src/config/meson.buildlabwc_sources += files( 'rcxml.c', 'keybind.c', 'session.c', 'mousebind.c', 'libinput.c', ) 0707010000006E000081A40000000000000000000000016378A52300000FF0000000000000000000000000000000000000003000000000labwc-0.6.0+git3.8fe2f2a/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; } 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; } 0707010000006F000081A40000000000000000000000016378A52300005BC0000000000000000000000000000000000000002C00000000labwc-0.6.0+git3.8fe2f2a/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/log.h> #include "action.h" #include "common/list.h" #include "common/mem.h" #include "common/nodename.h" #include "common/string-helpers.h" #include "config/keybind.h" #include "config/libinput.h" #include "config/mousebind.h" #include "config/rcxml.h" #include "workspaces.h" static bool in_keybind; static bool in_mousebind; static bool in_libinput_category; 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; 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); static void fill_common(char *nodename, char *content, struct action *action) { if (!strcmp(nodename, "command.action")) { /* Execute */ 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, NULL, 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); 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 { fill_common(nodename, content, current_keybind_action); } } 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); 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 { fill_common(nodename, content, current_mousebind_action); } } static bool get_bool(const char *s) { if (!s) { return false; } if (!strcasecmp(s, "yes")) { return true; } if (!strcasecmp(s, "true")) { return true; } return false; } 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")) { current_libinput_category->natural_scroll = get_bool(content) ? 1 : 0; } else if (!strcasecmp(nodename, "leftHanded")) { current_libinput_category->left_handed = get_bool(content) ? 1 : 0; } 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")) { current_libinput_category->tap = get_bool(content) ? 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")) { current_libinput_category->middle_emu = get_bool(content) ? LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED : LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; } else if (!strcasecmp(nodename, "disableWhileTyping")) { current_libinput_category->dwt = get_bool(content) ? 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); } /* 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")) { rc.adaptive_sync = get_bool(content); } 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")) { rc.focus_follow_mouse = get_bool(content); } else if (!strcasecmp(nodename, "raiseOnFocus.focus")) { rc.raise_on_focus = get_bool(content); } 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, "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")) { rc.snap_top_maximize = get_bool(content); } else if (!strcasecmp(nodename, "cycleViewPreview.core")) { rc.cycle_preview_contents = get_bool(content); } else if (!strcasecmp(nodename, "cycleViewOutlines.core")) { rc.cycle_preview_outlines = get_bool(content); } 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; } 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 rcxml_init() { static bool has_run; if (has_run) { return; } has_run = true; wl_list_init(&rc.keybinds); wl_list_init(&rc.mousebinds); wl_list_init(&rc.libinput_categories); rc.xdg_shell_server_side_deco = true; rc.corner_radius = 8; rc.font_activewindow.size = 10; rc.font_menuitem.size = 10; rc.font_osd.size = 10; rc.doubleclick_time = 500; rc.repeat_rate = 25; rc.repeat_delay = 600; rc.screen_edge_strength = 20; rc.snap_edge_range = 1; rc.snap_top_maximize = true; rc.cycle_preview_contents = false; rc.cycle_preview_outlines = true; rc.workspace_config.popuptime = INT_MIN; wl_list_init(&rc.workspace_config.workspaces); } 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 merge_mouse_bindings(void) { uint32_t replaced = 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 (existing->context == current->context && existing->button == current->button && existing->direction == current->direction && existing->mouse_event == current->mouse_event && existing->modifiers == current->modifiers) { wl_list_remove(&existing->link); action_list_free(&existing->actions); free(existing); replaced++; break; } } } if (replaced) { wlr_log(WLR_DEBUG, "Replaced %u mousebinds", replaced); } } 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 mousebindings by later ones */ merge_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 (!wl_list_length(&rc.libinput_categories)) { /* So we still allow tap to click by default */ struct libinput_category *l = libinput_category_create(); l->type = DEFAULT_DEVICE; } 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; } } 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); } /* 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; } 07070100000070000081A40000000000000000000000016378A52300000D64000000000000000000000000000000000000002E00000000labwc-0.6.0+git3.8fe2f2a/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" 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); } 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); } 07070100000071000081A40000000000000000000000016378A5230000865F000000000000000000000000000000000000002600000000labwc-0.6.0+git3.8fe2f2a/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 "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 "resistance.h" #include "ssd.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); } 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 = { .x = view->x, .y = view->y, .width = view->w, .height = view->h }; 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; } 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->x; ly = view->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 && view->xdg_surface) { struct wlr_box geo; wlr_xdg_surface_get_geometry(view->xdg_surface, &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) { resize_edges |= (int)cursor->x < ctx->view->x + ctx->view->w / 2 ? WLR_EDGE_LEFT : WLR_EDGE_RIGHT; resize_edges |= (int)cursor->y < ctx->view->y + ctx->view->h / 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) { desktop_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; } 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); cursor_update_common(server, &ctx, msec(&now), /*cursor_has_moved*/ false); } 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); } 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) { 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) { 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 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); if (!seat->current_constraint) { /* * 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, &event->pointer->base, event->delta_x, event->delta_y); } process_cursor_motion(seat->server, event->time_msec); } 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); if (!seat->current_constraint) { /* * 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, &event->pointer->base, dx, dy); } process_cursor_motion(seat->server, event->time_msec); } 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_end(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); } } 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; } 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; } 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, event->delta, event->delta_discrete, event->source); } } 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); if (wlr_xcursor_manager_get_xcursor(seat->xcursor_manager, cursors_xdg[LAB_CURSOR_DEFAULT], 1)) { cursor_names = cursors_xdg; } else { wlr_log(WLR_INFO, "Cursor theme is missing cursor names, using fallback"); cursor_names = cursors_x11; } 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); } 07070100000072000081A40000000000000000000000016378A523000012F4000000000000000000000000000000000000002500000000labwc-0.6.0+git3.8fe2f2a/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 "buffer.h" #include "labwc.h" #include "node.h" #include "common/scene-helpers.h" #define HEADER_CHARS "------------------------------" #define INDENT_SIZE 3 #define IGNORE_SSD true #define IGNORE_MENU true #define LEFT_COL_SPACE 35 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 || !view->ssd.tree) { return NULL; } if (node == &view->ssd.tree->node) { return "view->ssd"; } if (node == &view->ssd.titlebar.active.tree->node) { return "titlebar.active"; } if (node == &view->ssd.titlebar.inactive.tree->node) { return "titlebar.inactive"; } if (node == &view->ssd.border.active.tree->node) { return "border.active"; } if (node == &view->ssd.border.inactive.tree->node) { return "border.inactive"; } if (node == &view->ssd.extents.tree->node) { return "extents"; } return NULL; } static const char * get_special(struct server *server, struct wlr_scene_node *node, struct view **last_view) { 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"; } 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) { *last_view = node_view_from_node(node); } if (node->parent == server->view_tree_always_on_top) { *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) { static struct view *view; const char *type = get_special(server, node, &view); 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 && view && view->ssd.tree && node == &view->ssd.tree->node)) { printf("%*c%s\n", pos + 4 + INDENT_SIZE, ' ', "<skipping children>"); return; } if (node->type == WLR_SCENE_NODE_TREE) { struct wlr_scene_node *child; struct wlr_scene_tree *tree = 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"); } 07070100000073000081A40000000000000000000000016378A523000022C7000000000000000000000000000000000000002700000000labwc-0.6.0+git3.8fe2f2a/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 "workspaces.h" static void 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); } #if HAVE_XWAYLAND static struct wlr_xwayland_surface * top_parent_of(struct view *view) { struct wlr_xwayland_surface *s = view->xwayland_surface; while (s->parent) { s = s->parent; } return s; } static void move_xwayland_sub_views_to_front(struct view *parent) { if (!parent || parent->type != LAB_XWAYLAND_VIEW) { return; } 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; } move_to_front(view); /* TODO: we should probably focus on these too here */ } } #endif void desktop_move_to_front(struct view *view) { if (!view) { return; } move_to_front(view); #if HAVE_XWAYLAND move_xwayland_sub_views_to_front(view); #endif cursor_update_focus(view->server); } void desktop_move_to_back(struct view *view) { if (!view) { return; } wl_list_remove(&view->link); wl_list_append(&view->server->views, &view->link); } void desktop_arrange_all_views(struct server *server) { /* Adjust window positions/sizes */ struct view *view; wl_list_for_each(view, &server->views, link) { 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)) { 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) { 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); 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; } static struct view * 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) { 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 = topmost_mapped_view(server); desktop_focus_and_activate_view(&server->seat, view); desktop_move_to_front(view); } /* 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 (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_surface *surface = lab_wlr_surface_from_node(node); if (surface && wlr_surface_is_layer_surface(surface)) { ret.type = LAB_SSD_LAYER_SURFACE; ret.surface = surface; return ret; } #if HAVE_XWAYLAND 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, 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 = button->type; ret.view = button->view; return ret; } case LAB_NODE_DESC_LAYER_SURFACE: case LAB_NODE_DESC_LAYER_POPUP: ret.type = LAB_SSD_CLIENT; ret.surface = lab_wlr_surface_from_node(ret.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; } } /* node->parent is always a *wlr_scene_tree */ node = node->parent ? &node->parent->node : NULL; } wlr_log(WLR_ERROR, "Unknown node detected"); return ret; } 07070100000074000081A40000000000000000000000016378A52300001492000000000000000000000000000000000000002300000000labwc-0.6.0+git3.8fe2f2a/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); } 07070100000075000081A40000000000000000000000016378A52300000D82000000000000000000000000000000000000002700000000labwc-0.6.0+git3.8fe2f2a/src/foreign.c// SPDX-License-Identifier: GPL-2.0-only #include "labwc.h" #include "workspaces.h" static void handle_toplevel_handle_request_minimize(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, toplevel_handle_request_minimize); struct wlr_foreign_toplevel_handle_v1_minimized_event *event = data; if (view) { view_minimize(view, event->minimized); } } static void handle_toplevel_handle_request_maximize(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, toplevel_handle_request_maximize); struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data; if (view) { view_maximize(view, event->maximized); } } static void handle_toplevel_handle_request_fullscreen(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, toplevel_handle_request_fullscreen); struct wlr_foreign_toplevel_handle_v1_fullscreen_event *event = data; if (view) { view_set_fullscreen(view, event->fullscreen, NULL); } } static void handle_toplevel_handle_request_activate(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, toplevel_handle_request_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) { if (view->workspace != view->server->workspace_current) { workspaces_switch_to(view->workspace); } desktop_focus_and_activate_view(&view->server->seat, view); desktop_move_to_front(view); } } static void handle_toplevel_handle_request_close(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, toplevel_handle_request_close); if (view) { view_close(view); } } void foreign_toplevel_handle_create(struct view *view) { view->toplevel_handle = wlr_foreign_toplevel_handle_v1_create( view->server->foreign_toplevel_manager); if (!view->toplevel_handle) { wlr_log(WLR_ERROR, "cannot create foreign toplevel handle for (%s)", view_get_string_prop(view, "title")); return; } struct wlr_output *wlr_output = view_wlr_output(view); if (!wlr_output) { wlr_log(WLR_ERROR, "no wlr_output for (%s)", view_get_string_prop(view, "title")); return; } wlr_foreign_toplevel_handle_v1_output_enter(view->toplevel_handle, wlr_output); view->toplevel_handle_request_maximize.notify = handle_toplevel_handle_request_maximize; wl_signal_add(&view->toplevel_handle->events.request_maximize, &view->toplevel_handle_request_maximize); view->toplevel_handle_request_minimize.notify = handle_toplevel_handle_request_minimize; wl_signal_add(&view->toplevel_handle->events.request_minimize, &view->toplevel_handle_request_minimize); view->toplevel_handle_request_fullscreen.notify = handle_toplevel_handle_request_fullscreen; wl_signal_add(&view->toplevel_handle->events.request_fullscreen, &view->toplevel_handle_request_fullscreen); view->toplevel_handle_request_activate.notify = handle_toplevel_handle_request_activate; wl_signal_add(&view->toplevel_handle->events.request_activate, &view->toplevel_handle_request_activate); view->toplevel_handle_request_close.notify = handle_toplevel_handle_request_close; wl_signal_add(&view->toplevel_handle->events.request_close, &view->toplevel_handle_request_close); /* TODO: hook up remaining signals (destroy) */ } 07070100000076000081A40000000000000000000000016378A523000010BA000000000000000000000000000000000000002B00000000labwc-0.6.0+git3.8fe2f2a/src/interactive.c// SPDX-License-Identifier: GPL-2.0-only #include "labwc.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) { if (mode == LAB_INPUT_STATE_MOVE && 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 (mode == LAB_INPUT_STATE_RESIZE && (view->fullscreen || view->maximized)) { /* We don't allow resizing while in maximized or fullscreen state */ return; } /* * 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 seat *seat = &view->server->seat; struct server *server = view->server; server->grabbed_view = view; server->input_mode = mode; /* 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.x = view->x; server->grab_box.y = view->y; server->grab_box.width = view->w; server->grab_box.height = view->h; server->resize_edges = edges; if (view->maximized || view->tiled) { if (mode == LAB_INPUT_STATE_MOVE) { /* Exit maximized or tiled mode */ int new_x = max_move_scale(view->server->seat.cursor->x, view->x, view->w, view->natural_geometry.width); int new_y = max_move_scale(view->server->seat.cursor->y, view->y, view->h, view->natural_geometry.height); view->natural_geometry.x = new_x; view->natural_geometry.y = new_y; if (view->maximized) { view_maximize(view, false); } if (view->tiled) { view_move_resize(view, view->natural_geometry); } /** * view_maximize() / view_move_resize() indirectly calls * view->impl->configure which is async but we are using * the current values in server->grab_box. We pretend the * configure already happened by setting them manually. */ server->grab_box.x = new_x; server->grab_box.y = new_y; server->grab_box.width = view->natural_geometry.width; server->grab_box.height = view->natural_geometry.height; } } /* Moving or resizing always resets tiled state */ view->tiled = 0; switch (mode) { case LAB_INPUT_STATE_MOVE: cursor_set(&server->seat, LAB_CURSOR_GRAB); break; case LAB_INPUT_STATE_RESIZE: cursor_set(&server->seat, cursor_get_from_edge(edges)); break; default: break; } } void interactive_end(struct view *view) { if (view->server->grabbed_view == view) { bool should_snap = view->server->input_mode == LAB_INPUT_STATE_MOVE && rc.snap_edge_range; view->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH; view->server->grabbed_view = NULL; if (should_snap) { int snap_range = rc.snap_edge_range; struct wlr_box *area = &view->output->usable_area; /* 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); if (cursor_x <= area->x + snap_range) { view_snap_to_edge(view, "left"); } else if (cursor_x >= area->x + area->width - snap_range) { view_snap_to_edge(view, "right"); } else if (cursor_y <= area->y + snap_range) { if (rc.snap_top_maximize) { view_maximize(view, true); /* * When unmaximizing later on restore * original position */ view->natural_geometry.x = view->server->grab_box.x; view->natural_geometry.y = view->server->grab_box.y; } else { view_snap_to_edge(view, "up"); } } else if (cursor_y >= area->y + area->height - snap_range) { view_snap_to_edge(view, "down"); } } /* Update focus/cursor image */ cursor_update_focus(view->server); } } 07070100000077000081A40000000000000000000000016378A52300000796000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/src/key-state.c// SPDX-License-Identifier: GPL-2.0-only #include <stdbool.h> #include <stdint.h> #include <string.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; } 07070100000078000081A40000000000000000000000016378A523000024F3000000000000000000000000000000000000002800000000labwc-0.6.0+git3.8fe2f2a/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 "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); desktop_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_key_event *event = data; struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; if (server->osd_state.cycle_view || seat->workspace_osd_shown_by_modifier) { if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED && !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); } } } 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_reverse(keybind, &rc.keybinds, link) { if (modifiers ^ keybind->modifiers) { 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 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; } } 07070100000079000081A40000000000000000000000016378A52300002F2C000000000000000000000000000000000000002600000000labwc-0.6.0+git3.8fe2f2a/src/layers.c// SPDX-License-Identifier: GPL-2.0-only /* * layers.c - layer-shell implementation * * Based on * - https://git.sr.ht/~sircmpwm/wio * - https://github.com/swaywm/sway * Copyright (C) 2019 Drew DeVault and Sway developers */ #include <assert.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" void layers_arrange(struct output *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 wlr_box old_usable_area = output->usable_area; struct server *server = output->server; struct wlr_scene_output *scene_output = wlr_scene_get_scene_output(server->scene, output->wlr_output); if (!scene_output) { wlr_log(WLR_DEBUG, "no wlr_scene_output"); return; } int nr_layers = sizeof(output->layers) / sizeof(output->layers[0]); for (int i = 0; i < nr_layers; i++) { struct lab_layer_surface *lab_layer_surface; /* * First we go over the list of surfaces that have * exclusive_zone set (e.g. statusbars) because we have to * determine the usable area before processing regular layouts. */ wl_list_for_each(lab_layer_surface, &output->layers[i], link) { struct wlr_scene_layer_surface_v1 *scene_layer_surface = lab_layer_surface->scene_layer_surface; if (scene_layer_surface->layer_surface->current.exclusive_zone) { wlr_scene_layer_surface_v1_configure( scene_layer_surface, &full_area, &usable_area); } } /* Now we process regular layouts */ wl_list_for_each(lab_layer_surface, &output->layers[i], link) { struct wlr_scene_layer_surface_v1 *scene_layer_surface = lab_layer_surface->scene_layer_surface; if (!scene_layer_surface->layer_surface->current.exclusive_zone) { wlr_scene_layer_surface_v1_configure( scene_layer_surface, &full_area, &usable_area); } } wlr_scene_node_set_position(&output->layer_tree[i]->node, scene_output->x, scene_output->y); } memcpy(&output->usable_area, &usable_area, sizeof(struct wlr_box)); /* Find topmost keyboard interactive layer, if such a layer exists */ uint32_t layers_above_shell[] = { ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, ZWLR_LAYER_SHELL_V1_LAYER_TOP, }; size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]); struct lab_layer_surface *layer, *topmost = NULL; for (size_t i = 0; i < nlayers; ++i) { wl_list_for_each_reverse(layer, &output->layers[layers_above_shell[i]], link) { struct wlr_layer_surface_v1 *layer_surface = layer->scene_layer_surface->layer_surface; if (layer_surface->current.keyboard_interactive) { topmost = layer; break; } } if (topmost) { break; } } struct seat *seat = &output->server->seat; if (topmost) { seat_set_focus_layer(seat, topmost->scene_layer_surface->layer_surface); } else if (seat->focused_layer && !seat->focused_layer->current.keyboard_interactive) { seat_set_focus_layer(seat, NULL); } /* Finally re-arrange all views based on usable_area */ if (old_usable_area.width != output->usable_area.width || old_usable_area.height != output->usable_area.height) { desktop_arrange_all_views(server); } } static void output_destroy_notify(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; } static void surface_commit_notify(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; } if (layer_surface->current.committed || layer->mapped != layer_surface->mapped) { layer->mapped = layer_surface->mapped; struct output *output = output_from_wlr_output(layer->server, wlr_output); layers_arrange(output); } } static void unmap(struct lab_layer_surface *layer) { struct seat *seat = &layer->server->seat; if (seat->focused_layer == layer->scene_layer_surface->layer_surface) { seat_set_focus_layer(seat, NULL); } } static void destroy_notify(struct wl_listener *listener, void *data) { struct lab_layer_surface *layer = wl_container_of( listener, layer, destroy); unmap(layer); wl_list_remove(&layer->link); wl_list_remove(&layer->destroy.link); wl_list_remove(&layer->map.link); wl_list_remove(&layer->unmap.link); wl_list_remove(&layer->surface_commit.link); if (layer->scene_layer_surface->layer_surface->output) { wl_list_remove(&layer->output_destroy.link); struct output *output = output_from_wlr_output(layer->server, layer->scene_layer_surface->layer_surface->output); layers_arrange(output); } free(layer); } static void unmap_notify(struct wl_listener *listener, void *data) { return; struct lab_layer_surface *lab_layer_surface = wl_container_of(listener, lab_layer_surface, unmap); unmap(lab_layer_surface); } static void map_notify(struct wl_listener *listener, void *data) { return; struct wlr_layer_surface_v1 *layer_surface = data; wlr_surface_send_enter(layer_surface->surface, layer_surface->output); } 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 = output_from_wlr_output(server, wlr_output); 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 new_popup_notify(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; int lx, ly; struct server *server = toplevel->server; struct wlr_scene_layer_surface_v1 *surface = toplevel->scene_layer_surface; wlr_scene_node_coords(&surface->tree->node, &lx, &ly); if (!surface->layer_surface->output) { /* Work-around for moving layer shell surfaces on output destruction */ struct wlr_output *wlr_output; wlr_output = wlr_output_layout_output_at(server->output_layout, lx, ly); surface->layer_surface->output = wlr_output; } struct output *output = surface->layer_surface->output->data; 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 new_layer_surface_notify(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); layer_surface->output = output; } struct lab_layer_surface *surface = znew(*surface); surface->surface_commit.notify = surface_commit_notify; wl_signal_add(&layer_surface->surface->events.commit, &surface->surface_commit); surface->destroy.notify = destroy_notify; wl_signal_add(&layer_surface->events.destroy, &surface->destroy); surface->map.notify = map_notify; wl_signal_add(&layer_surface->events.map, &surface->map); surface->unmap.notify = unmap_notify; wl_signal_add(&layer_surface->events.unmap, &surface->unmap); surface->new_popup.notify = new_popup_notify; wl_signal_add(&layer_surface->events.new_popup, &surface->new_popup); 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->output_destroy.notify = output_destroy_notify; wl_signal_add(&layer_surface->output->events.destroy, &surface->output_destroy); if (!output) { wlr_log(WLR_ERROR, "no output for layer"); return; } wl_list_append(&output->layers[layer_surface->pending.layer], &surface->link); /* * 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; layers_arrange(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 = new_layer_surface_notify; wl_signal_add(&server->layer_shell->events.new_surface, &server->new_layer_surface); } 0707010000007A000081A40000000000000000000000016378A523000010BB000000000000000000000000000000000000002400000000labwc-0.6.0+git3.8fe2f2a/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_rootmenu(&server); menu_init_windowmenu(&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; } 0707010000007B000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002200000000labwc-0.6.0+git3.8fe2f2a/src/menu0707010000007C000081A40000000000000000000000016378A52300005504000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/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/string-helpers.h" #include "labwc.h" #include "menu/menu.h" #include "node.h" #include "theme.h" #define MENUWIDTH (110) #define MENU_ITEM_PADDING_Y (4) #define MENU_ITEM_PADDING_X (7) /* 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 = MENUWIDTH; /* 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 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; if (!menu->item_height) { menu->item_height = font_height(&rc.font_menuitem) + 2 * MENU_ITEM_PADDING_Y; } menuitem->height = menu->item_height; int x, y; int item_max_width = MENUWIDTH - 2 * MENU_ITEM_PADDING_X; /* 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, MENUWIDTH, menu->item_height, theme->menu_items_bg_color)->node; menuitem->selected.background = &wlr_scene_rect_create( menuitem->selected.tree, MENUWIDTH, 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 */ const char *arrow = show_arrow ? "›" : NULL; scaled_font_buffer_update(menuitem->normal.buffer, text, item_max_width, &rc.font_menuitem, theme->menu_items_text_color, arrow); scaled_font_buffer_update(menuitem->selected.buffer, text, item_max_width, &rc.font_menuitem, theme->menu_items_active_text_color, arrow); /* Center font nodes */ x = 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_width + 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, MENUWIDTH, menuitem->height, theme->menu_items_bg_color)->node; /* theme->menu_separator_width is the line-thickness (so height here) */ int width = MENUWIDTH - 2 * theme->menu_separator_padding_width; menuitem->normal.text = &wlr_scene_rect_create( menuitem->normal.tree, width > 0 ? width : 0, theme->menu_separator_width, 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); 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 if (!strcmp(nodename, "command.action")) { /* Execute */ action_arg_add_str(current_item_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(current_item_action, NULL, content); } else if (!strcmp(nodename, "to.action")) { /* GoToDesktop, SendToDesktop */ action_arg_add_str(current_item_action, NULL, 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); if (!wlr_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); struct wlr_box usable = output_usable_area_from_cursor_coords(menu->server); if (align == LAB_MENU_OPEN_AUTO) { int full_width = menu_get_full_width(menu); if (ox + full_width > usable.width) { align = LAB_MENU_OPEN_LEFT; } else { align = LAB_MENU_OPEN_RIGHT; } } if (oy + menu->size.height > usable.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 -= MENUWIDTH - 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 + MENUWIDTH - 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; } } } void menu_init_rootmenu(struct server *server) { parse_xml("menu.xml", 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"); } } void menu_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); fill_item("name.action", "SendToDesktop"); fill_item("to.action", "left"); fill_item("name.action", "GoToDesktop"); fill_item("to.action", "left"); current_item = item_create(workspace_menu, _("Move right"), false); fill_item("name.action", "SendToDesktop"); fill_item("to.action", "right"); fill_item("name.action", "GoToDesktop"); 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_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_rootmenu(server); menu_init_windowmenu(server); } 0707010000007D000081A40000000000000000000000016378A52300000026000000000000000000000000000000000000002E00000000labwc-0.6.0+git3.8fe2f2a/src/menu/meson.buildlabwc_sources += files( 'menu.c', ) 0707010000007E000081A40000000000000000000000016378A52300000240000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/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', 'resistance.c', 'seat.c', 'server.c', 'touch.c', 'theme.c', 'view.c', 'view-impl.c', 'workspaces.c', 'xdg.c', 'xdg-deco.c', 'xdg-popup.c', ) if have_xwayland labwc_sources += files( 'xwayland.c', 'xwayland-unmanaged.c', ) endif subdir('common') subdir('config') subdir('xbm') subdir('menu') subdir('ssd') 0707010000007F000081A40000000000000000000000016378A523000009A4000000000000000000000000000000000000002400000000labwc-0.6.0+git3.8fe2f2a/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; } 07070100000080000081A40000000000000000000000016378A52300002A37000000000000000000000000000000000000002300000000labwc-0.6.0+git3.8fe2f2a/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 "workspaces.h" #define OSD_ITEM_HEIGHT (20) #define OSD_ITEM_WIDTH (600) #define OSD_ITEM_PADDING (10) #define OSD_BORDER_WIDTH (6) #define OSD_TAB1 (120) #define OSD_TAB2 (300) /* 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->xwayland_surface->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) { 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.cycle_view = NULL; 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; } } 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); /* 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); } 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; } struct theme *theme = server->theme; bool show_workspace = wl_list_length(&rc.workspace_config.workspaces) > 1; struct buf buf; buf_init(&buf); struct view *view; struct output *output; struct wlr_scene_node *node; wl_list_for_each(output, &server->outputs, link) { destroy_osd_nodes(output); if (!output->wlr_output->enabled) { continue; } 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; } if (output->osd_buffer) { wlr_buffer_drop(&output->osd_buffer->base); } output->osd_buffer = buffer_create_cairo(w, h, scale, true); cairo_t *cairo = output->osd_buffer->cairo; cairo_surface_t *surf = cairo_get_target(cairo); /* background */ set_cairo_color(cairo, theme->osd_bg_color); cairo_rectangle(cairo, 0, 0, w, h); cairo_fill(cairo); /* Border */ set_cairo_color(cairo, theme->osd_border_color); draw_cairo_border(cairo, w, h, theme->osd_border_width); int y = OSD_BORDER_WIDTH; if (show_workspace) { /* workspace indicator */ y += OSD_ITEM_HEIGHT; } /* highlight current window */ wl_list_for_each_reverse(node, node_list, link) { view = node_view_from_node(node); if (!isfocusable(view)) { continue; } if (view == server->osd_state.cycle_view) { set_cairo_color(cairo, theme->osd_label_text_color); cairo_rectangle(cairo, OSD_BORDER_WIDTH, y, OSD_ITEM_WIDTH, OSD_ITEM_HEIGHT); cairo_stroke(cairo); if (rc.cycle_preview_outlines) { osd_update_preview_outlines(view); } break; } y += OSD_ITEM_HEIGHT; } /* text */ 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); PangoTabArray *tabs = pango_tab_array_new_with_positions(2, TRUE, PANGO_TAB_LEFT, OSD_TAB1, PANGO_TAB_LEFT, OSD_TAB2); pango_layout_set_tabs(layout, tabs); pango_tab_array_free(tabs); pango_cairo_update_layout(cairo, layout); 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; if (show_workspace) { /* Center workspace indicator on the x axis */ int x = font_width(&rc.font_osd, server->workspace_current->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, server->workspace_current->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); wl_list_for_each_reverse(node, node_list, link) { view = node_view_from_node(node); if (!isfocusable(view)) { continue; } buf.len = 0; cairo_move_to(cairo, OSD_BORDER_WIDTH + OSD_ITEM_PADDING, y); switch (view->type) { case LAB_XDG_SHELL_VIEW: buf_add(&buf, "[xdg-shell]\t"); buf_add(&buf, get_formatted_app_id(view)); buf_add(&buf, "\t"); break; #if HAVE_XWAYLAND case LAB_XWAYLAND_VIEW: buf_add(&buf, "[xwayland]\t"); buf_add(&buf, view_get_string_prop(view, "class")); buf_add(&buf, "\t"); break; #endif } if (is_title_different(view)) { buf_add(&buf, view_get_string_prop(view, "title")); } pango_layout_set_text(layout, buf.buf, -1); pango_cairo_show_layout(cairo, layout); y += OSD_ITEM_HEIGHT; } g_object_unref(layout); cairo_surface_flush(surf); 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); } free(buf.buf); if (rc.cycle_preview_contents) { preview_cycled_view(server->osd_state.cycle_view); } } 07070100000081000081A40000000000000000000000016378A523000035EC000000000000000000000000000000000000002600000000labwc-0.6.0+git3.8fe2f2a/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 <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" static void output_frame_notify(struct wl_listener *listener, void *data) { struct output *output = wl_container_of(listener, output, frame); if (!output->wlr_output->enabled) { 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); wl_list_remove(&output->link); wl_list_remove(&output->frame.link); wl_list_remove(&output->destroy.link); 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 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); /* The mode is a tuple of (width, height, refresh rate). */ wlr_log(WLR_DEBUG, "set preferred mode"); struct wlr_output_mode *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; } } } 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); /* * Create layer-trees (background, bottom, top and overlay) and * a layer-popup-tree. */ int nr_layers = sizeof(output->layers) / sizeof(output->layers[0]); for (int i = 0; i < nr_layers; i++) { wl_list_init(&output->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); /* * Set the z-positions to achieve the following order (from top to * bottom): * - 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); if (rc.adaptive_sync) { wlr_log(WLR_INFO, "enable adaptive sync on %s", wlr_output->name); struct wlr_output_state pending = { 0 }; wlr_output_state_set_adaptive_sync_enabled(&pending, true); if (!wlr_output_test_state(wlr_output, &pending)) { wlr_log(WLR_ERROR, "adaptive sync failed, ignoring"); wlr_output_state_set_adaptive_sync_enabled(&pending, false); } if (!wlr_output_commit_state(wlr_output, &pending)) { wlr_log(WLR_ERROR, "failed to commit output %s", wlr_output->name); } } /* * 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); 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) { /* Adjust window positions/sizes */ desktop_arrange_all_views(server); /* * "Move" each wlr_output_cursor (in per-output coordinates) to * align with the seat cursor. Set a default cursor image so * that the cursor isn't invisible on new outputs. * * TODO: remember the most recent cursor image (see cursor.c) * and set that rather than XCURSOR_DEFAULT */ wlr_cursor_move(server->seat.cursor, NULL, 0, 0); wlr_xcursor_manager_set_cursor_image(server->seat.xcursor_manager, XCURSOR_DEFAULT, server->seat.cursor); } 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); } 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) { wlr_output_layout_move(server->output_layout, o, head->state.x, head->state.y); } if (need_to_remove) { 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); } } /* * 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()"); } struct output *output; wl_list_for_each(output, &server->outputs, link) { if (output) { layers_arrange(output); } } 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 wlr_box output_usable_area_in_layout_coords(struct output *output) { struct wlr_box box = output->usable_area; double ox = 0, oy = 0; wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy); box.x -= ox; box.y -= oy; return box; } struct wlr_box output_usable_area_from_cursor_coords(struct server *server) { struct wlr_output *wlr_output; wlr_output = wlr_output_layout_output_at(server->output_layout, server->seat.cursor->x, server->seat.cursor->y); struct output *output = output_from_wlr_output(server, wlr_output); return output_usable_area_in_layout_coords(output); } 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; } } 07070100000082000081A40000000000000000000000016378A523000014FD000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/src/resistance.c// SPDX-License-Identifier: GPL-2.0-only #include "labwc.h" #include "config/rcxml.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 = {.x = view->x, .y = view->y, .width = view->w, .height = view->h}; struct wlr_box tgeom = {.x = *x, .y = *y, .width = view->w, .height = view->h}; struct output *output; struct border border = view->margin; 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 = view->x - border.left + 1; view_edges.top = view->y - border.top + 1; view_edges.right = view->x + view->w + border.right; view_edges.bottom = view->y + view->h + border.bottom; target_edges.left = *x - border.left; target_edges.top = *y - border.top; target_edges.right = *x + view->w + border.right; target_edges.bottom = *y + view->h + border.bottom; if (!rc.screen_edge_strength) { return; } wl_list_for_each(output, &server->outputs, link) { if (!output->wlr_output->enabled) { 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 - view->w - border.right; } if (flags.top == 1) { *y = other_edges.top + border.top; } else if (flags.bottom == 1) { *y = other_edges.bottom - view->h - 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 = {.x = view->x, .y = view->y, .width = view->w, .height = view->h}; struct wlr_box tgeom = {.x = new_view_geo->x, .y = new_view_geo->y, .width = new_view_geo->width, .height = new_view_geo->height}; struct border border = view->margin; 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 = view->x - border.left; view_edges.top = view->y - border.top; view_edges.right = view->x + view->w + border.right; view_edges.bottom = view->y + view->h + 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->wlr_output->enabled) { 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 = view->w; } } 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 = view->h; } } 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; } } 07070100000083000081A40000000000000000000000016378A52300003AF5000000000000000000000000000000000000002400000000labwc-0.6.0+git3.8fe2f2a/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; } 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) { dc = device_category; } } if (!dc) { wlr_log(WLR_INFO, "Skipping libinput configuration for device"); return; } 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; if (wlr_input_device_is_libinput(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; if (wlr_input_device_is_libinput(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); } void seat_reconfigure(struct server *server) { struct seat *seat = &server->seat; struct input *input; wl_list_for_each(input, &seat->inputs, link) { /* We don't configure keyboards by libinput, so skip them */ if (wlr_input_device_is_libinput(input->wlr_input_device) && input->wlr_input_device->type == WLR_INPUT_DEVICE_POINTER) { configure_libinput(input->wlr_input_device); } else if (input->wlr_input_device->type == WLR_INPUT_DEVICE_KEYBOARD) { struct wlr_keyboard *kb = wlr_keyboard_from_input_device(input->wlr_input_device); wlr_keyboard_set_repeat_info(kb, rc.repeat_rate, rc.repeat_delay); } } } void seat_focus_surface(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_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_surface(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; } 07070100000084000081A40000000000000000000000016378A52300003600000000000000000000000000000000000000002600000000labwc-0.6.0+git3.8fe2f2a/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_viewporter.h> #include "config/rcxml.h" #include "config/session.h" #include "labwc.h" #include "layers.h" #include "menu/menu.h" #include "ssd.h" #include "theme.h" #include "workspaces.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) { if (!view->mapped || !view->ssd.enabled) { continue; } ssd_reload(view); } menu_reconfigure(g_server); seat_reconfigure(g_server); } 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 && (wl_resource_get_client(seat->focused_layer->resource) != active_client)) { seat_set_focus_layer(seat, NULL); } struct wlr_surface *previous_kb_surface = seat->seat->keyboard_state.focused_surface; if (previous_kb_surface && wl_resource_get_client(previous_kb_surface->resource) != active_client) { 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 */ struct output *output; wl_list_for_each(output, &seat->server->outputs, link) { layers_arrange(output); } } 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; } } #if HAVE_XWAYLAND static void handle_xwayland_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); } #endif 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); } /* 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; /* * 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"); 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->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); /* Disable CSD */ 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; struct wlr_server_decoration_manager *deco_mgr = NULL; deco_mgr = wlr_server_decoration_manager_create(server->wl_display); if (!deco_mgr) { wlr_log(WLR_ERROR, "unable to create the server deco manager"); exit(EXIT_FAILURE); } wlr_server_decoration_manager_set_default_mode( deco_mgr, rc.xdg_shell_server_side_deco ? WLR_SERVER_DECORATION_MANAGER_MODE_SERVER : WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT); 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); 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); 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 /* Init xwayland */ 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->new_xwayland_surface.notify = xwayland_surface_new; wl_signal_add(&server->xwayland->events.new_surface, &server->new_xwayland_surface); server->xwayland_ready.notify = handle_xwayland_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); } #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 wlr_xwayland_destroy(server->xwayland); #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); } 07070100000085000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002100000000labwc-0.6.0+git3.8fe2f2a/src/ssd07070100000086000081A40000000000000000000000016378A5230000006E000000000000000000000000000000000000002D00000000labwc-0.6.0+git3.8fe2f2a/src/ssd/meson.buildlabwc_sources += files( 'ssd.c', 'ssd_part.c', 'ssd_titlebar.c', 'ssd_border.c', 'ssd_extents.c', ) 07070100000087000081A40000000000000000000000016378A52300001D2F000000000000000000000000000000000000002700000000labwc-0.6.0+git3.8fe2f2a/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 "config/rcxml.h" #include "common/font.h" #include "labwc.h" #include "theme.h" #include "ssd.h" #include "common/scene-helpers.h" struct border ssd_thickness(struct view *view) { if (!view->ssd.enabled) { struct border border = { 0 }; return border; } struct theme *theme = view->server->theme; struct border border = { .top = theme->title_height + theme->border_width, .bottom = theme->border_width, .left = theme->border_width, .right = theme->border_width, }; return border; } struct wlr_box ssd_max_extents(struct view *view) { struct border border = ssd_thickness(view); struct wlr_box box = { .x = view->x - border.left, .y = view->y - border.top, .width = view->w + border.left + border.right, .height = view->h + border.top + border.bottom, }; return box; } 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(struct view *view, 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 (!view->ssd.tree) { return LAB_SSD_NONE; } 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 == view->ssd.titlebar.active.tree) { part_list = &view->ssd.titlebar.active.parts; } else if (grandparent == view->ssd.titlebar.active.tree) { part_list = &view->ssd.titlebar.active.parts; } else if (greatgrandparent == view->ssd.titlebar.active.tree) { part_list = &view->ssd.titlebar.active.parts; /* extents */ } else if (node->parent == view->ssd.extents.tree) { part_list = &view->ssd.extents.parts; /* active border */ } else if (node->parent == view->ssd.border.active.tree) { part_list = &view->ssd.border.active.parts; /* inactive titlebar */ } else if (node->parent == view->ssd.titlebar.inactive.tree) { part_list = &view->ssd.titlebar.inactive.parts; } else if (grandparent == view->ssd.titlebar.inactive.tree) { part_list = &view->ssd.titlebar.inactive.parts; } else if (greatgrandparent == view->ssd.titlebar.inactive.tree) { part_list = &view->ssd.titlebar.inactive.parts; /* inactive border */ } else if (node->parent == view->ssd.border.inactive.tree) { part_list = &view->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(struct view *view, double lx, double ly) { double sx, sy; struct wlr_scene_node *node = wlr_scene_node_at( &view->server->scene->tree.node, lx, ly, &sx, &sy); return ssd_get_part_type(view, 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; } } void ssd_create(struct view *view) { bool is_active = view->server->focused_view == view; if (view->ssd.tree) { /* SSD was hidden. Just enable it */ wlr_scene_node_set_enabled(&view->ssd.tree->node, true); ssd_set_active(view, is_active); return; } view->ssd.tree = wlr_scene_tree_create(view->scene_tree); wlr_scene_node_lower_to_bottom(&view->ssd.tree->node); ssd_extents_create(view); ssd_border_create(view); ssd_titlebar_create(view); view->margin = ssd_thickness(view); ssd_set_active(view, is_active); } void ssd_update_geometry(struct view *view) { if (!view->ssd.tree || !view->scene_node) { return; } if (!view->ssd.enabled) { if (view->ssd.tree->node.enabled) { wlr_scene_node_set_enabled(&view->ssd.tree->node, false); view->margin = ssd_thickness(view); } return; } else if (!view->ssd.tree->node.enabled) { wlr_scene_node_set_enabled(&view->ssd.tree->node, true); view->margin = ssd_thickness(view); } int width = view->w; int height = view->h; if (width == view->ssd.state.width && height == view->ssd.state.height) { if (view->x != view->ssd.state.x || view->y != view->ssd.state.y) { /* Dynamically resize extents based on position and usable_area */ ssd_extents_update(view); view->ssd.state.x = view->x; view->ssd.state.y = view->y; } return; } ssd_extents_update(view); ssd_border_update(view); ssd_titlebar_update(view); view->ssd.state.width = width; view->ssd.state.height = height; view->ssd.state.x = view->x; view->ssd.state.y = view->y; } void ssd_reload(struct view *view) { if (!view->ssd.tree) { return; } ssd_destroy(view); ssd_create(view); } void ssd_destroy(struct view *view) { if (!view->ssd.tree) { return; } /* Maybe reset hover 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(view); ssd_border_destroy(view); ssd_extents_destroy(view); wlr_scene_node_destroy(&view->ssd.tree->node); view->ssd.tree = NULL; } 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 view *view, bool active) { if (!view->ssd.tree) { return; } wlr_scene_node_set_enabled(&view->ssd.border.active.tree->node, active); wlr_scene_node_set_enabled(&view->ssd.titlebar.active.tree->node, active); wlr_scene_node_set_enabled(&view->ssd.border.inactive.tree->node, !active); wlr_scene_node_set_enabled(&view->ssd.titlebar.inactive.tree->node, !active); } 07070100000088000081A40000000000000000000000016378A52300000B6E000000000000000000000000000000000000002E00000000labwc-0.6.0+git3.8fe2f2a/src/ssd/ssd_border.c// SPDX-License-Identifier: GPL-2.0-only #include "labwc.h" #include "ssd.h" #include "theme.h" #include "common/scene-helpers.h" #define FOR_EACH_STATE(view, tmp) FOR_EACH(tmp, \ &(view)->ssd.border.active, \ &(view)->ssd.border.inactive) void ssd_border_create(struct view *view) { struct theme *theme = view->server->theme; int width = view->w; int height = view->h; int full_width = width + 2 * theme->border_width; float *color; struct wlr_scene_tree *parent; struct ssd_sub_tree *subtree; FOR_EACH_STATE(view, subtree) { subtree->tree = wlr_scene_tree_create(view->ssd.tree); parent = subtree->tree; wlr_scene_node_set_position(&parent->node, -theme->border_width, 0); if (subtree == &view->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 * BUTTON_WIDTH, theme->border_width, theme->border_width + BUTTON_WIDTH, -(theme->title_height + theme->border_width), color); } FOR_EACH_END } void ssd_border_update(struct view *view) { struct theme *theme = view->server->theme; int width = view->w; int height = view->h; 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(view, 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 * BUTTON_WIDTH, theme->border_width); continue; default: continue; } } } FOR_EACH_END } void ssd_border_destroy(struct view *view) { if (!view->ssd.border.active.tree) { return; } struct ssd_sub_tree *subtree; FOR_EACH_STATE(view, subtree) { ssd_destroy_parts(&subtree->parts); wlr_scene_node_destroy(&subtree->tree->node); subtree->tree = NULL; } FOR_EACH_END } #undef FOR_EACH_STATE 07070100000089000081A40000000000000000000000016378A52300001956000000000000000000000000000000000000002F00000000labwc-0.6.0+git3.8fe2f2a/src/ssd/ssd_extents.c// SPDX-License-Identifier: GPL-2.0-only #include "labwc.h" #include "ssd.h" #include "theme.h" #include "common/mem.h" #include "common/scene-helpers.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; } static void lab_wlr_output_layout_layout_coords(struct wlr_output_layout *layout, struct wlr_output *output, int *x, int *y) { struct wlr_output_layout_output *l_output; l_output = wlr_output_layout_get(layout, output); *x += l_output->x; *y += l_output->y; } void ssd_extents_create(struct view *view) { struct theme *theme = view->server->theme; struct wl_list *part_list = &view->ssd.extents.parts; int extended_area = EXTENDED_AREA; int corner_size = extended_area + theme->border_width + BUTTON_WIDTH / 2; view->ssd.extents.tree = wlr_scene_tree_create(view->ssd.tree); struct wlr_scene_tree *parent = view->ssd.extents.tree; if (view->maximized || view->fullscreen) { wlr_scene_node_set_enabled(&parent->node, false); } wl_list_init(&view->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(view); } void ssd_extents_update(struct view *view) { if (view->maximized || view->fullscreen) { wlr_scene_node_set_enabled(&view->ssd.extents.tree->node, false); return; } if (!view->ssd.extents.tree->node.enabled) { wlr_scene_node_set_enabled(&view->ssd.extents.tree->node, true); } if (!view->output) { return; } struct theme *theme = view->server->theme; int width = view->w; int height = view->h; int full_height = height + theme->border_width * 2 + theme->title_height; int full_width = width + 2 * theme->border_width; int extended_area = EXTENDED_AREA; int corner_size = extended_area + theme->border_width + 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; memcpy(&usable_area, &view->output->usable_area, sizeof(struct wlr_box)); lab_wlr_output_layout_layout_coords(view->server->output_layout, view->output->wlr_output, &usable_area.x, &usable_area.y); /* Remember base layout coordinates */ int base_x, base_y; wlr_scene_node_coords(&view->ssd.extents.tree->node, &base_x, &base_y); struct wlr_box *target; wl_list_for_each(part, &view->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 view *view) { if (!view->ssd.extents.tree) { return; } ssd_destroy_parts(&view->ssd.extents.parts); wlr_scene_node_destroy(&view->ssd.extents.tree->node); view->ssd.extents.tree = NULL; } 0707010000008A000081A40000000000000000000000016378A5230000144A000000000000000000000000000000000000002C00000000labwc-0.6.0+git3.8fe2f2a/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.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; } 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, BUTTON_WIDTH, rc.theme->title_height, 0, 0, bg_color); /* Icon */ add_scene_buffer(part_list, type, parent, icon_buffer, (BUTTON_WIDTH - icon_buffer->width) / 2, (rc.theme->title_height - icon_buffer->height) / 2); /* Hover overlay */ hover = add_scene_rect(part_list, type, parent, 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)); } 0707010000008B000081A40000000000000000000000016378A523000022A1000000000000000000000000000000000000003000000000labwc-0.6.0+git3.8fe2f2a/src/ssd/ssd_titlebar.c// SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <string.h> #include "labwc.h" #include "ssd.h" #include "theme.h" #include "common/font.h" #include "common/mem.h" #include "common/scaled_font_buffer.h" #include "common/scene-helpers.h" #include "node.h" #define FOR_EACH_STATE(view, tmp) FOR_EACH(tmp, \ &(view)->ssd.titlebar.active, \ &(view)->ssd.titlebar.inactive) void ssd_titlebar_create(struct view *view) { struct theme *theme = view->server->theme; int width = view->w; float *color; struct wlr_scene_tree *parent; struct wlr_buffer *corner_top_left; struct wlr_buffer *corner_top_right; struct ssd_sub_tree *subtree; FOR_EACH_STATE(view, subtree) { subtree->tree = wlr_scene_tree_create(view->ssd.tree); parent = subtree->tree; wlr_scene_node_set_position(&parent->node, 0, -theme->title_height); if (subtree == &view->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; } 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; 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 - BUTTON_WIDTH * BUTTON_COUNT, theme->title_height, 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, &theme->xbm_menu_active_unpressed->base, 0, view); add_scene_button(&subtree->parts, LAB_SSD_BUTTON_ICONIFY, parent, color, &theme->xbm_iconify_active_unpressed->base, width - BUTTON_WIDTH * 3, view); add_scene_button(&subtree->parts, LAB_SSD_BUTTON_MAXIMIZE, parent, color, &theme->xbm_maximize_active_unpressed->base, width - BUTTON_WIDTH * 2, view); add_scene_button_corner(&subtree->parts, LAB_SSD_BUTTON_CLOSE, LAB_SSD_PART_CORNER_TOP_RIGHT, parent, corner_top_right, &theme->xbm_close_active_unpressed->base, width - BUTTON_WIDTH * 1, view); } FOR_EACH_END ssd_update_title(view); } 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 view *view) { int width = view->w; if (width == view->ssd.state.width) { return; } struct theme *theme = view->server->theme; struct ssd_part *part; struct ssd_sub_tree *subtree; FOR_EACH_STATE(view, 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 - BUTTON_WIDTH * 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 - 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 - 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 - BUTTON_WIDTH * 1, 0); } continue; default: continue; } } } FOR_EACH_END ssd_update_title(view); } void ssd_titlebar_destroy(struct view *view) { if (!view->ssd.titlebar.active.tree) { return; } struct ssd_sub_tree *subtree; FOR_EACH_STATE(view, subtree) { ssd_destroy_parts(&subtree->parts); wlr_scene_node_destroy(&subtree->tree->node); subtree->tree = NULL; } FOR_EACH_END if (view->ssd.state.title.text) { free(view->ssd.state.title.text); view->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 view *view) { struct theme *theme = view->server->theme; int width = view->w; int title_bg_width = width - BUTTON_WIDTH * BUTTON_COUNT; int x, y; int buffer_height, buffer_width; struct ssd_part *part; struct ssd_sub_tree *subtree; FOR_EACH_STATE(view, 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 = 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 + 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 view *view) { if (!view->ssd.tree) { return; } char *title = (char *)view_get_string_prop(view, "title"); if (!title || !*title) { return; } struct theme *theme = view->server->theme; struct ssd_state_title *state = &view->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->w - BUTTON_WIDTH * BUTTON_COUNT; FOR_EACH_STATE(view, subtree) { if (subtree == &view->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(view); } 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 0707010000008C000081A40000000000000000000000016378A52300003CC1000000000000000000000000000000000000002500000000labwc-0.6.0+git3.8fe2f2a/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/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_separator_width = 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 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; } 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(key, "border.width")) { theme->border_width = atoi(value); } if (match(key, "padding.height")) { theme->padding_height = atoi(value); } if (match(key, "menu.overlap.x")) { theme->menu_overlap_x = atoi(value); } if (match(key, "menu.overlap.y")) { theme->menu_overlap_y = atoi(value); } if (match(key, "window.active.border.color")) { parse_hexstr(value, theme->window_active_border_color); } if (match(key, "window.inactive.border.color")) { parse_hexstr(value, theme->window_inactive_border_color); } /* border.color is obsolete, but handled for backward compatibility */ if (match(key, "border.color")) { parse_hexstr(value, theme->window_active_border_color); parse_hexstr(value, theme->window_inactive_border_color); } if (match(key, "window.active.title.bg.color")) { parse_hexstr(value, theme->window_active_title_bg_color); } if (match(key, "window.inactive.title.bg.color")) { parse_hexstr(value, theme->window_inactive_title_bg_color); } if (match(key, "window.active.label.text.color")) { parse_hexstr(value, theme->window_active_label_text_color); } if (match(key, "window.inactive.label.text.color")) { parse_hexstr(value, theme->window_inactive_label_text_color); } if (match(key, "window.label.text.justify")) { theme->window_label_text_justify = parse_justification(value); } /* universal button */ if (match(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(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(key, "window.active.button.iconify.unpressed.image.color")) { parse_hexstr(value, theme->window_active_button_iconify_unpressed_image_color); } if (match(key, "window.active.button.max.unpressed.image.color")) { parse_hexstr(value, theme->window_active_button_max_unpressed_image_color); } if (match(key, "window.active.button.close.unpressed.image.color")) { parse_hexstr(value, theme->window_active_button_close_unpressed_image_color); } if (match(key, "window.inactive.button.iconify.unpressed.image.color")) { parse_hexstr(value, theme->window_inactive_button_iconify_unpressed_image_color); } if (match(key, "window.inactive.button.max.unpressed.image.color")) { parse_hexstr(value, theme->window_inactive_button_max_unpressed_image_color); } if (match(key, "window.inactive.button.close.unpressed.image.color")) { parse_hexstr(value, theme->window_inactive_button_close_unpressed_image_color); } if (match(key, "menu.items.bg.color")) { parse_hexstr(value, theme->menu_items_bg_color); } if (match(key, "menu.items.text.color")) { parse_hexstr(value, theme->menu_items_text_color); } if (match(key, "menu.items.active.bg.color")) { parse_hexstr(value, theme->menu_items_active_bg_color); } if (match(key, "menu.items.active.text.color")) { parse_hexstr(value, theme->menu_items_active_text_color); } if (match(key, "menu.separator.width")) { theme->menu_separator_width = atoi(value); } if (match(key, "menu.separator.padding.width")) { theme->menu_separator_padding_width = atoi(value); } if (match(key, "menu.separator.padding.height")) { theme->menu_separator_padding_height = atoi(value); } if (match(key, "menu.separator.color")) { parse_hexstr(value, theme->menu_separator_color); } if (match(key, "osd.bg.color")) { parse_hexstr(value, theme->osd_bg_color); } if (match(key, "osd.border.width")) { theme->osd_border_width = atoi(value); } if (match(key, "osd.border.color")) { parse_hexstr(value, theme->osd_border_color); } if (match(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); } 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 = 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; } /* 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); theme_read(theme, theme_name); 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; } 0707010000008D000081A40000000000000000000000016378A52300000AAF000000000000000000000000000000000000002500000000labwc-0.6.0+git3.8fe2f2a/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); } 0707010000008E000081A40000000000000000000000016378A5230000015E000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/src/view-impl.c// SPDX-License-Identifier: GPL-2.0-only /* view-impl.c: common code for shell view->impl functions */ #include <stdio.h> #include <strings.h> #include "labwc.h" void view_impl_map(struct view *view) { desktop_focus_and_activate_view(&view->server->seat, view); desktop_move_to_front(view); view_update_title(view); view_update_app_id(view); } 0707010000008F000081A40000000000000000000000016378A523000052C1000000000000000000000000000000000000002400000000labwc-0.6.0+git3.8fe2f2a/src/view.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include <stdio.h> #include <strings.h> #include <xcb/xcb_icccm.h> #include "common/scene-helpers.h" #include "labwc.h" #include "ssd.h" #include "menu/menu.h" #include "workspaces.h" #define LAB_FALLBACK_WIDTH 640 #define LAB_FALLBACK_HEIGHT 480 #define MAX(a, b) (((a) > (b)) ? (a) : (b)) /** * 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 wlr_box dst = { .x = x_offset + usable.x + view->margin.left, .y = y_offset + usable.y + view->margin.top, .width = base_width - view->margin.left - view->margin.right, .height = base_height - view->margin.top - view->margin.bottom, }; return dst; } static void _view_set_activated(struct view *view, bool activated) { if (view->ssd.tree) { ssd_set_active(view, 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_close(struct view *view) { if (view->impl->close) { view->impl->close(view); } } void view_move(struct view *view, int x, int y) { if (view->impl->move) { view->impl->move(view, x, y); } } void view_moved(struct view *view) { wlr_scene_node_set_position(&view->scene_tree->node, view->x, view->y); view_discover_output(view); ssd_update_geometry(view); cursor_update_focus(view->server); } /* N.B. Use view_move() if not resizing. */ void view_move_resize(struct view *view, struct wlr_box geo) { if (view->impl->configure) { view->impl->configure(view, geo); } } #define MIN_VIEW_WIDTH (100) #define MIN_VIEW_HEIGHT (60) #if HAVE_XWAYLAND 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; } #endif void view_adjust_size(struct view *view, int *w, int *h) { int min_width = MIN_VIEW_WIDTH; int min_height = MIN_VIEW_HEIGHT; #if HAVE_XWAYLAND if (view->type == LAB_XWAYLAND_VIEW) { xcb_size_hints_t *hints = view->xwayland_surface->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); min_width = MAX(1, hints->min_width); min_height = MAX(1, hints->min_height); } } #endif *w = MAX(*w, min_width); *h = MAX(*h, min_height); } void view_minimize(struct view *view, bool minimized) { 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); desktop_move_to_back(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); } } /* view_wlr_output - return the output that a view is mostly on */ struct wlr_output * view_wlr_output(struct view *view) { double closest_x, closest_y; struct wlr_output *wlr_output = NULL; wlr_output_layout_closest_point(view->server->output_layout, wlr_output, view->x + view->w / 2, view->y + view->h / 2, &closest_x, &closest_y); wlr_output = wlr_output_layout_output_at(view->server->output_layout, closest_x, closest_y); return wlr_output; } static struct output * view_output(struct view *view) { struct wlr_output *wlr_output = view_wlr_output(view); return output_from_wlr_output(view->server, wlr_output); } static bool view_compute_centered_position(struct view *view, int w, int h, int *x, int *y) { struct output *output = view_output(view); if (!output) { return false; } struct wlr_output *wlr_output = output->wlr_output; if (!wlr_output) { return false; } struct wlr_box usable = output_usable_area_in_layout_coords(output); int width = w + view->margin.left + view->margin.right; int height = h + view->margin.top + view->margin.bottom; *x = usable.x + (usable.width - width) / 2; *y = usable.y + (usable.height - height) / 2; /* If view is bigger than usable area, just top/left align it */ if (*x < 0) { *x = 0; } if (*y < 0) { *y = 0; } #if HAVE_XWAYLAND /* TODO: refactor xwayland.c functions to get rid of this */ if (view->type == LAB_XWAYLAND_VIEW) { *x += view->margin.left; *y += view->margin.top; } #endif 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, view->natural_geometry.width, view->natural_geometry.height, &view->natural_geometry.x, &view->natural_geometry.y); } #undef LAB_FALLBACK_WIDTH #undef LAB_FALLBACK_HEIGHT static void view_store_natural_geometry(struct view *view) { /** * 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 (!view->w || !view->h) { set_fallback_geometry(view); } else { view->natural_geometry.x = view->x; view->natural_geometry.y = view->y; view->natural_geometry.width = view->w; view->natural_geometry.height = view->h; } } void view_center(struct view *view) { int x, y; if (view_compute_centered_position(view, view->w, view->h, &x, &y)) { view_move(view, x, y); } } static void view_apply_tiled_geometry(struct view *view, struct output *output) { assert(view->tiled); if (!output) { output = view_output(view); } if (!output) { wlr_log(WLR_ERROR, "Can't tile: no output"); return; } struct wlr_box dst = view_get_edge_snap_box(view, output, view->tiled); if (view->w == dst.width && view->h == dst.height) { /* move horizontally/vertically without changing size */ view_move(view, dst.x, dst.y); } else { view_move_resize(view, dst); } } static void view_apply_fullscreen_geometry(struct view *view, struct wlr_output *wlr_output) { struct output *output = output_from_wlr_output(view->server, wlr_output); struct wlr_box box = { 0 }; wlr_output_effective_resolution(wlr_output, &box.width, &box.height); 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; view_move_resize(view, box); } static void view_apply_maximized_geometry(struct view *view) { /* * The same code handles both initial maximize and re-maximize * to account for layout changes. In either case, view_output() * gives the output closest to the current geometry (which may * be different from the output originally maximized onto). * view_output() can return NULL if all outputs are disabled. */ struct output *output = view_output(view); if (!output) { return; } 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_unmaximized_geometry(struct view *view) { struct wlr_output_layout *layout = view->server->output_layout; if (wlr_output_layout_intersects(layout, NULL, &view->natural_geometry)) { /* 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, box.width, box.height, &box.x, &box.y)) { view_move_resize(view, box); } } } void view_maximize(struct view *view, bool maximize) { if (view->maximized == maximize) { return; } if (view->fullscreen) { return; } if (view->impl->maximize) { view->impl->maximize(view, maximize); } if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_set_maximized( view->toplevel_handle, maximize); } if (maximize) { interactive_end(view); if (!view->tiled) { view_store_natural_geometry(view); } view_apply_maximized_geometry(view); view->maximized = true; } else { /* unmaximize */ if (view->tiled) { view_apply_tiled_geometry(view, NULL); } else { view_apply_unmaximized_geometry(view); } view->maximized = false; } } void view_toggle_maximize(struct view *view) { view_maximize(view, !view->maximized); } void view_toggle_decorations(struct view *view) { if (!view->fullscreen) { view->ssd.enabled = !view->ssd.enabled; ssd_update_geometry(view); if (view->maximized) { view_apply_maximized_geometry(view); } else if (view->tiled) { view_apply_tiled_geometry(view, NULL); } } } static bool is_always_on_top(struct view *view) { return view->scene_tree->node.parent == view->server->view_tree_always_on_top; } void view_toggle_always_on_top(struct view *view) { if (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_set_decorations(struct view *view, bool decorations) { if (view->ssd.enabled != decorations && !view->fullscreen) { view->ssd.enabled = decorations; ssd_update_geometry(view); if (view->maximized) { view_apply_maximized_geometry(view); } else if (view->tiled) { view_apply_tiled_geometry(view, NULL); } } } void view_toggle_fullscreen(struct view *view) { view_set_fullscreen(view, !view->fullscreen, NULL); } void view_set_fullscreen(struct view *view, bool fullscreen, struct wlr_output *wlr_output) { if (fullscreen != !view->fullscreen) { return; } 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); } if (!wlr_output) { wlr_output = view_wlr_output(view); } if (fullscreen) { interactive_end(view); if (!view->maximized && !view->tiled) { view_store_natural_geometry(view); } view->fullscreen = wlr_output; view_apply_fullscreen_geometry(view, view->fullscreen); } else { /* restore to normal */ if (view->maximized) { view_apply_maximized_geometry(view); } else if (view->tiled) { view_apply_tiled_geometry(view, NULL); } else { view_apply_unmaximized_geometry(view); } view->fullscreen = false; } /* Show fullscreen views above top-layer */ struct output *output = output_from_wlr_output(view->server, wlr_output); if (!output) { return; } uint32_t top = ZWLR_LAYER_SHELL_V1_LAYER_TOP; wlr_scene_node_set_enabled(&output->layer_tree[top]->node, !fullscreen); } void view_adjust_for_layout_change(struct view *view) { struct wlr_output_layout *layout = view->server->output_layout; if (view->fullscreen) { if (wlr_output_layout_get(layout, view->fullscreen)) { /* recompute fullscreen geometry */ view_apply_fullscreen_geometry(view, view->fullscreen); } else { /* output is gone, exit fullscreen */ view_set_fullscreen(view, false, NULL); } } else if (view->maximized) { /* recompute maximized geometry */ view_apply_maximized_geometry(view); } else if (view->tiled) { /* recompute tiled geometry */ view_apply_tiled_geometry(view, NULL); } else { /* reposition view if it's offscreen */ struct wlr_box box = { view->x, view->y, view->w, view->h }; if (!wlr_output_layout_intersects(layout, NULL, &box)) { view_center(view); } } } static void view_output_enter(struct view *view, struct wlr_output *wlr_output) { if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_output_enter( view->toplevel_handle, wlr_output); } } static void view_output_leave(struct view *view, struct wlr_output *wlr_output) { if (view->toplevel_handle) { wlr_foreign_toplevel_handle_v1_output_leave( view->toplevel_handle, wlr_output); } } /* * At present, a view can only 'enter' one output at a time, although the view * may span multiple outputs. Ideally we would handle multiple outputs, but * this method is the simplest form of what we want. */ void view_discover_output(struct view *view) { struct output *old_output = view->output; struct output *new_output = view_output(view); if (old_output != new_output) { view->output = new_output; if (new_output) { view_output_enter(view, new_output->wlr_output); } if (old_output) { view_output_leave(view, old_output->wlr_output); } } } void view_on_output_destroy(struct view *view) { view_output_leave(view, view->output->wlr_output); view->output = NULL; } void view_move_to_edge(struct view *view, const char *direction) { if (!view) { wlr_log(WLR_ERROR, "no view"); return; } struct output *output = view_output(view); if (!output) { wlr_log(WLR_ERROR, "no output"); return; } if (!direction) { wlr_log(WLR_ERROR, "invalid edge"); return; } 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 + view->margin.left + rc.gap; y = view->y; } else if (!strcasecmp(direction, "up")) { x = view->x; y = usable.y + view->margin.top + rc.gap; } else if (!strcasecmp(direction, "right")) { x = usable.x + usable.width - view->w - view->margin.right - rc.gap; y = view->y; } else if (!strcasecmp(direction, "down")) { x = view->x; y = usable.y + usable.height - view->h - view->margin.bottom - rc.gap; } else { wlr_log(WLR_ERROR, "invalid edge"); 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) { if (!view) { wlr_log(WLR_ERROR, "no view"); return; } if (view->fullscreen) { return; } struct output *output = view_output(view); if (!output) { wlr_log(WLR_ERROR, "no output"); return; } enum view_edge edge = view_edge_parse(direction); if (edge == VIEW_EDGE_INVALID) { wlr_log(WLR_ERROR, "invalid edge"); return; } if (view->tiled == edge) { /* 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); } else { /* No more output to move to */ return; } } if (view->maximized) { /* Unmaximize + keep using existing natural_geometry */ view_maximize(view, false); } else if (!view->tiled) { /* store current geometry as new natural_geometry */ view_store_natural_geometry(view); } view->tiled = edge; view_apply_tiled_geometry(view, output); } const char * view_get_string_prop(struct view *view, const char *prop) { if (view->impl->get_string_prop) { return view->impl->get_string_prop(view, prop); } return ""; } void view_update_title(struct view *view) { const char *title = view_get_string_prop(view, "title"); if (!view->toplevel_handle || !title) { return; } ssd_update_title(view); wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, title); } void view_update_app_id(struct view *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_destroy(struct view *view) { struct server *server = view->server; bool need_cursor_update = false; 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; } if (server->focused_view == view) { server->focused_view = NULL; need_cursor_update = true; } if (server->seat.pressed.view == view) { seat_reset_pressed(&server->seat); } osd_on_view_destroy(view); if (view->scene_tree) { ssd_destroy(view); 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) { struct output *output = output_from_wlr_output(server, view->fullscreen); uint32_t top = ZWLR_LAYER_SHELL_V1_LAYER_TOP; wlr_scene_node_set_enabled(&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); } } 07070100000090000081A40000000000000000000000016378A523000025E2000000000000000000000000000000000000002A00000000labwc-0.6.0+git3.8fe2f2a/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 "common/font.h" #include "common/graphic-helpers.h" #include "common/list.h" #include "common/mem.h" #include "labwc.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); if (target == target->server->workspace_current) { return; } /* Disable the old workspace */ wlr_scene_node_set_enabled( &target->server->workspace_current->tree->node, false); /* Enable the new workspace */ wlr_scene_node_set_enabled(&target->tree->node, true); /* Save the last visited workspace */ target->server->workspace_last = target->server->workspace_current; /* Make sure new views will spawn on the new workspace */ target->server->workspace_current = target; /** * Make sure we are focusing what the user sees. * * TODO: This is an issue for always-on-top views as they will * loose keyboard focus once switching to another workspace. */ desktop_focus_topmost_mapped_view(target->server); /* And finally show the OSD */ _osd_show(target->server); } void workspaces_send_to(struct view *view, struct workspace *target) { assert(view); assert(target); if (view->workspace == target) { return; } wlr_scene_node_reparent(&view->scene_tree->node, target->tree); view->workspace = target; } 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; } 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)); } 07070100000091000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002100000000labwc-0.6.0+git3.8fe2f2a/src/xbm07070100000092000081A40000000000000000000000016378A52300000042000000000000000000000000000000000000002D00000000labwc-0.6.0+git3.8fe2f2a/src/xbm/meson.buildlabwc_sources += files( 'parse.c', 'tokenize.c', 'xbm.c', ) 07070100000093000081A40000000000000000000000016378A52300000880000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/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; } 07070100000094000081A40000000000000000000000016378A52300000958000000000000000000000000000000000000002C00000000labwc-0.6.0+git3.8fe2f2a/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() { 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() { 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; } 07070100000095000081A40000000000000000000000016378A52300000BAE000000000000000000000000000000000000002700000000labwc-0.6.0+git3.8fe2f2a/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); } 07070100000096000081A40000000000000000000000016378A5230000077A000000000000000000000000000000000000002800000000labwc-0.6.0+git3.8fe2f2a/src/xdg-deco.c// SPDX-License-Identifier: GPL-2.0-only #include "common/mem.h" #include "labwc.h" struct xdg_deco { struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration; struct server *server; 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_decoration->requested_mode; if (client_mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE) { client_mode = rc.xdg_shell_server_side_deco ? WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; } wlr_xdg_toplevel_decoration_v1_set_mode(xdg_deco->wlr_decoration, client_mode); view_set_decorations(xdg_deco->view, client_mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); } void xdg_toplevel_decoration(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, xdg_toplevel_decoration); struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration = data; struct xdg_deco *xdg_deco = znew(*xdg_deco); xdg_deco->wlr_decoration = wlr_decoration; xdg_deco->server = server; xdg_deco->view = wlr_decoration->surface->data; xdg_deco->destroy.notify = xdg_deco_destroy; wl_signal_add(&wlr_decoration->events.destroy, &xdg_deco->destroy); xdg_deco->request_mode.notify = xdg_deco_request_mode; wl_signal_add(&wlr_decoration->events.request_mode, &xdg_deco->request_mode); xdg_deco_request_mode(&xdg_deco->request_mode, wlr_decoration); } 07070100000097000081A40000000000000000000000016378A52300000B54000000000000000000000000000000000000002900000000labwc-0.6.0+git3.8fe2f2a/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" 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->x + popup_box->x, view->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->x, .y = output_box.y - view->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); } 07070100000098000081A40000000000000000000000016378A52300003049000000000000000000000000000000000000002300000000labwc-0.6.0+git3.8fe2f2a/src/xdg.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include "common/mem.h" #include "labwc.h" #include "node.h" #include "ssd.h" #include "workspaces.h" static void handle_new_xdg_popup(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, new_popup); struct wlr_xdg_popup *wlr_popup = data; xdg_popup_create(view, wlr_popup); } static bool has_ssd(struct view *view) { if (!rc.xdg_shell_server_side_deco) { return false; } /* * Some XDG shells refuse to disable CSD in which case their * geometry.{x,y} seems to be greater than zero. We filter on that * on the assumption that this will remain true. */ struct wlr_xdg_surface_state *current = &view->xdg_surface->current; if (current->geometry.x || current->geometry.y) { return false; } return true; } static void handle_commit(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, commit); assert(view->surface); struct wlr_box size; wlr_xdg_surface_get_geometry(view->xdg_surface, &size); bool update_required = false; if (view->w != size.width || view->h != size.height) { update_required = true; view->w = size.width; view->h = size.height; } uint32_t serial = view->pending_move_resize.configure_serial; if (serial > 0 && serial >= view->xdg_surface->current.configure_serial) { if (view->pending_move_resize.update_x) { update_required = true; view->x = view->pending_move_resize.x + view->pending_move_resize.width - size.width; } if (view->pending_move_resize.update_y) { update_required = true; view->y = view->pending_move_resize.y + view->pending_move_resize.height - size.height; } if (serial == view->xdg_surface->current.configure_serial) { view->pending_move_resize.configure_serial = 0; } } if (update_required) { view_moved(view); } } 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); /* Reset XDG specific surface for good measure */ view->xdg_surface = NULL; /* Remove XDG specific handlers */ wl_list_remove(&view->destroy.link); /* And finally destroy / free the view */ 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, view->xdg_surface->toplevel->requested.minimized); } static void handle_request_maximize(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_maximize); view_maximize(view, view->xdg_surface->toplevel->requested.maximized); } static void handle_request_fullscreen(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_fullscreen); view_set_fullscreen(view, view->xdg_surface->toplevel->requested.fullscreen, view->xdg_surface->toplevel->requested.fullscreen_output); } static void handle_set_title(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, set_title); assert(view); view_update_title(view); } static void handle_set_app_id(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, set_app_id); assert(view); view_update_app_id(view); } static void xdg_toplevel_view_configure(struct view *view, struct wlr_box geo) { view_adjust_size(view, &geo.width, &geo.height); view->pending_move_resize.update_x = geo.x != view->x; view->pending_move_resize.update_y = geo.y != view->y; view->pending_move_resize.x = geo.x; view->pending_move_resize.y = geo.y; view->pending_move_resize.width = geo.width; view->pending_move_resize.height = geo.height; uint32_t serial = wlr_xdg_toplevel_set_size(view->xdg_surface->toplevel, (uint32_t)geo.width, (uint32_t)geo.height); if (serial > 0) { view->pending_move_resize.configure_serial = serial; } else if (view->pending_move_resize.configure_serial == 0) { view->x = geo.x; view->y = geo.y; view_moved(view); } } static void xdg_toplevel_view_move(struct view *view, int x, int y) { view->x = x; view->y = y; view_moved(view); } static void xdg_toplevel_view_close(struct view *view) { wlr_xdg_toplevel_send_close(view->xdg_surface->toplevel); } static void xdg_toplevel_view_maximize(struct view *view, bool maximized) { wlr_xdg_toplevel_set_maximized(view->xdg_surface->toplevel, maximized); } static void xdg_toplevel_view_set_activated(struct view *view, bool activated) { struct wlr_xdg_surface *surface = view->xdg_surface; if (surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { wlr_xdg_toplevel_set_activated(surface->toplevel, activated); } } static void xdg_toplevel_view_set_fullscreen(struct view *view, bool fullscreen) { wlr_xdg_toplevel_set_fullscreen(view->xdg_surface->toplevel, fullscreen); } static bool istopmost(struct view *view) { return !view->xdg_surface->toplevel->parent; } static struct view * parent_of(struct view *view) { struct view *p; wl_list_for_each(p, &view->server->views, link) { if (p->xdg_surface->toplevel == view->xdg_surface->toplevel->parent) { return p; } } return NULL; } static void position_xdg_toplevel_view(struct view *view) { if (istopmost(view)) { struct wlr_box box = output_usable_area_from_cursor_coords(view->server); view->x = box.x; view->y = box.y; view->w = view->xdg_surface->current.geometry.width; view->h = view->xdg_surface->current.geometry.height; if (view->w && view->h) { view_center(view); } } else { /* * If child-toplevel-views, we center-align relative to their * parents */ struct view *parent = parent_of(view); assert(parent); int center_x = parent->x + parent->w / 2; int center_y = parent->y + parent->h / 2; view->x = center_x - view->xdg_surface->current.geometry.width / 2; view->y = center_y - view->xdg_surface->current.geometry.height / 2; } view->x += view->margin.left; view->y += view->margin.top; } static const char * xdg_toplevel_view_get_string_prop(struct view *view, const char *prop) { if (!strcmp(prop, "title")) { return view->xdg_surface->toplevel->title; } if (!strcmp(prop, "app_id")) { return view->xdg_surface->toplevel->app_id; } return ""; } static void xdg_toplevel_view_map(struct view *view) { if (view->mapped) { return; } view->mapped = true; view->surface = view->xdg_surface->surface; wlr_scene_node_set_enabled(&view->scene_tree->node, true); if (!view->been_mapped) { struct wlr_xdg_toplevel_requested *requested = &view->xdg_surface->toplevel->requested; foreign_toplevel_handle_create(view); view->ssd.enabled = has_ssd(view); if (view->ssd.enabled) { ssd_create(view); } position_xdg_toplevel_view(view); if (!view->fullscreen && requested->fullscreen) { view_set_fullscreen(view, true, requested->fullscreen_output); } else if (!view->maximized && requested->maximized) { view_maximize(view, true); } view_moved(view); view->been_mapped = true; } view->commit.notify = handle_commit; wl_signal_add(&view->xdg_surface->surface->events.commit, &view->commit); view_impl_map(view); } 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, .move = xdg_toplevel_view_move, .set_activated = xdg_toplevel_view_set_activated, .set_fullscreen = xdg_toplevel_view_set_fullscreen, .unmap = xdg_toplevel_view_unmap, .maximize = xdg_toplevel_view_maximize, }; /* * 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 view *view = znew(*view); view->server = server; view->type = LAB_XDG_SHELL_VIEW; view->impl = &xdg_toplevel_view_impl; 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, view->xdg_surface); if (!tree) { /* TODO: might need further clean up */ wl_resource_post_no_memory(view->surface->resource); return; } view->scene_node = &tree->node; node_descriptor_create(&view->scene_tree->node, LAB_NODE_DESC_VIEW, view); /* In support of xdg_toplevel_decoration */ xdg_surface->data = view; /* 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); view->new_popup.notify = handle_new_xdg_popup; wl_signal_add(&xdg_surface->events.new_popup, &view->new_popup); 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); view->set_app_id.notify = handle_set_app_id; wl_signal_add(&toplevel->events.set_app_id, &view->set_app_id); wl_list_insert(&server->views, &view->link); } 07070100000099000081A40000000000000000000000016378A5230000161B000000000000000000000000000000000000003200000000labwc-0.6.0+git3.8fe2f2a/src/xwayland-unmanaged.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include "common/list.h" #include "common/mem.h" #include "labwc.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); } } 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, "override_redirect not handled\n"); } static void unmanaged_handle_request_activate(struct wl_listener *listener, void *data) { wlr_log(WLR_DEBUG, "request_activate not handled\n"); } struct xwayland_unmanaged * xwayland_unmanaged_create(struct server *server, struct wlr_xwayland_surface *xsurface) { struct xwayland_unmanaged *unmanaged = znew(*unmanaged); unmanaged->server = server; unmanaged->xwayland_surface = xsurface; 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; return unmanaged; } 0707010000009A000081A40000000000000000000000016378A52300003C59000000000000000000000000000000000000002800000000labwc-0.6.0+git3.8fe2f2a/src/xwayland.c// SPDX-License-Identifier: GPL-2.0-only #include <assert.h> #include "common/mem.h" #include "labwc.h" #include "node.h" #include "ssd.h" #include "workspaces.h" static void handle_commit(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, commit); assert(view->surface); /* Must receive commit signal before accessing surface->current* */ struct wlr_surface_state *state = &view->surface->current; struct view_pending_move_resize *pending = &view->pending_move_resize; if (view->w == state->width && view->h == state->height) { return; } view->w = state->width; view->h = state->height; if (view->x != pending->x) { /* Adjust x for queued up configure events */ view->x = pending->x + pending->width - view->w; } if (view->y != pending->y) { /* Adjust y for queued up configure events */ view->y = pending->y + pending->height - view->h; } view_moved(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_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); struct wlr_xwayland_surface *xsurface = data; if (xsurface != view->xwayland_surface) { xsurface->data = view; view->xwayland_surface = xsurface; } 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(view->type == LAB_XWAYLAND_VIEW); struct wlr_surface *surface = data; assert(surface == 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); assert(view->type == LAB_XWAYLAND_VIEW); /* Reset XWayland specific surface for good measure */ 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; view->xwayland_surface = NULL; /* Remove XWayland specific handlers */ 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_configure.link); wl_list_remove(&view->request_activate.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->set_app_id.link); wl_list_remove(&view->set_decorations.link); wl_list_remove(&view->override_redirect.link); wl_list_remove(&view->destroy.link); /* And finally destroy / free the view */ view_destroy(view); } static void configure(struct view *view, struct wlr_box geo) { assert(view->xwayland_surface); view->pending_move_resize.x = geo.x; view->pending_move_resize.y = geo.y; view->pending_move_resize.width = geo.width; view->pending_move_resize.height = geo.height; wlr_xwayland_surface_configure(view->xwayland_surface, geo.x, geo.y, geo.width, geo.height); /* If not resizing, process the move immediately */ if (view->w == geo.width && view->h == geo.height) { view->x = geo.x; view->y = geo.y; view_moved(view); } } static void handle_request_configure(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_configure); struct wlr_xwayland_surface_configure_event *event = data; int width = event->width; int height = event->height; view_adjust_size(view, &width, &height); configure(view, (struct wlr_box){event->x, event->y, width, height}); } static void handle_request_activate(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, request_activate); assert(view); desktop_focus_and_activate_view(&view->server->seat, view); desktop_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); assert(view); 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); assert(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 = view->xwayland_surface->fullscreen; view_set_fullscreen(view, fullscreen, NULL); } static void handle_set_title(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, set_title); assert(view); view_update_title(view); } static void handle_set_class(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, set_app_id); assert(view); view_update_app_id(view); } static void move(struct view *view, int x, int y) { assert(view->xwayland_surface); view->x = x; view->y = y; /* override any previous pending move */ view->pending_move_resize.x = x; view->pending_move_resize.y = y; struct wlr_xwayland_surface *s = view->xwayland_surface; wlr_xwayland_surface_configure(s, (int16_t)x, (int16_t)y, (uint16_t)s->width, (uint16_t)s->height); view_moved(view); } static void _close(struct view *view) { wlr_xwayland_surface_close(view->xwayland_surface); } static const char * get_string_prop(struct view *view, const char *prop) { if (!strcmp(prop, "title")) { return view->xwayland_surface->title; } if (!strcmp(prop, "class")) { return view->xwayland_surface->class; } /* We give 'class' for wlr_foreign_toplevel_handle_v1_set_app_id() */ if (!strcmp(prop, "app_id")) { return view->xwayland_surface->class; } return ""; } static bool want_deco(struct view *view) { return view->xwayland_surface->decorations == WLR_XWAYLAND_SURFACE_DECORATIONS_ALL; } static void handle_set_decorations(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, set_decorations); view_set_decorations(view, want_deco(view)); } static void handle_override_redirect(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, override_redirect); struct wlr_xwayland_surface *xsurface = data; struct server *server = view->server; bool mapped = xsurface->mapped; if (mapped) { handle_unmap(&view->unmap, NULL); } handle_destroy(&view->destroy, view); xsurface->data = NULL; struct xwayland_unmanaged *unmanaged = xwayland_unmanaged_create(server, xsurface); if (mapped) { unmanaged_handle_map(&unmanaged->map, xsurface); } } static void set_initial_position(struct view *view) { /* Don't center views with position explicitly specified */ bool has_position = view->xwayland_surface->size_hints && (view->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 { struct wlr_box box = output_usable_area_from_cursor_coords(view->server); view->x = box.x; view->y = box.y; view_center(view); } } static void top_left_edge_boundary_check(struct view *view) { struct wlr_box deco = ssd_max_extents(view); if (deco.x < 0) { view->x -= deco.x; } if (deco.y < 0) { view->y -= deco.y; } struct wlr_box box = { .x = view->x, .y = view->y, .width = view->w, .height = view->h }; view->impl->configure(view, box); } static void map(struct view *view) { if (view->mapped) { return; } view->mapped = true; wlr_scene_node_set_enabled(&view->scene_tree->node, true); if (!view->fullscreen && view->xwayland_surface->fullscreen) { view_set_fullscreen(view, true, NULL); } if (!view->maximized && !view->fullscreen) { view->x = view->xwayland_surface->x; view->y = view->xwayland_surface->y; view->w = view->xwayland_surface->width; view->h = view->xwayland_surface->height; } if (view->surface != view->xwayland_surface->surface) { if (view->surface) { wl_list_remove(&view->surface_destroy.link); } view->surface = view->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->ssd.enabled = want_deco(view); if (view->ssd.enabled) { ssd_create(view); } if (!view->maximized && !view->fullscreen) { set_initial_position(view); } view_moved(view); view->been_mapped = true; } if (view->ssd.enabled && !view->fullscreen && !view->maximized) { top_left_edge_boundary_check(view); } /* Add commit here, as xwayland map/unmap can change the wlr_surface */ wl_signal_add(&view->xwayland_surface->surface->events.commit, &view->commit); view->commit.notify = handle_commit; view_impl_map(view); } static void 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 maximize(struct view *view, bool maximized) { wlr_xwayland_surface_set_maximized(view->xwayland_surface, maximized); } static void set_activated(struct view *view, bool activated) { struct wlr_xwayland_surface *surface = view->xwayland_surface; if (activated && surface->minimized) { wlr_xwayland_surface_set_minimized(surface, false); } wlr_xwayland_surface_activate(surface, activated); if (activated) { wlr_xwayland_surface_restack(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 set_fullscreen(struct view *view, bool fullscreen) { wlr_xwayland_surface_set_fullscreen(view->xwayland_surface, fullscreen); } static const struct view_impl xwl_view_impl = { .configure = configure, .close = _close, .get_string_prop = get_string_prop, .map = map, .move = move, .set_activated = set_activated, .set_fullscreen = set_fullscreen, .unmap = unmap, .maximize = maximize }; void xwayland_surface_new(struct wl_listener *listener, void *data) { struct server *server = wl_container_of(listener, server, new_xwayland_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); return; } struct view *view = znew(*view); view->server = server; view->type = LAB_XWAYLAND_VIEW; view->impl = &xwl_view_impl; view->xwayland_surface = xsurface; 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); xsurface->data = 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_configure.notify = handle_request_configure; wl_signal_add(&xsurface->events.request_configure, &view->request_configure); view->request_activate.notify = handle_request_activate; wl_signal_add(&xsurface->events.request_activate, &view->request_activate); 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); view->set_app_id.notify = handle_set_class; wl_signal_add(&xsurface->events.set_class, &view->set_app_id); view->set_decorations.notify = handle_set_decorations; wl_signal_add(&xsurface->events.set_decorations, &view->set_decorations); view->override_redirect.notify = handle_override_redirect; wl_signal_add(&xsurface->events.set_override_redirect, &view->override_redirect); wl_list_insert(&view->server->views, &view->link); } 0707010000009B000041ED0000000000000000000000026378A52300000000000000000000000000000000000000000000002500000000labwc-0.6.0+git3.8fe2f2a/subprojects0707010000009C000081A40000000000000000000000016378A5230000000C000000000000000000000000000000000000003000000000labwc-0.6.0+git3.8fe2f2a/subprojects/.gitignore/* !/*.wrap 0707010000009D000081A40000000000000000000000016378A52300000046000000000000000000000000000000000000003000000000labwc-0.6.0+git3.8fe2f2a/subprojects/seatd.wrap[wrap-git] url=https://git.sr.ht/~kennylevinsen/seatd revision=0.6.4 0707010000009E000081A40000000000000000000000016378A5230000007C000000000000000000000000000000000000003200000000labwc-0.6.0+git3.8fe2f2a/subprojects/wlroots.wrap[wrap-git] url = https://gitlab.freedesktop.org/wlroots/wlroots.git revision = 0.16.0 [provide] dependency_names = wlroots 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1660 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